MyBatis,Java互联网时代的首选持久层框架。一般推荐使用XML配置的方式,因为注解方式不利于SQL的维护和编写。
MyBatis3官方文档:http://www.mybatis.org/mybatis-3/zh/configuration.html
本blog实例代码:https://github.com/JeeLearner/learning-ssmr/tree/master/chapter04
mybatis-config.xml配置
前言:MyBatis配置文件并不复杂,但是注意顺序不要颠倒!!
一、
三种方式:1.property子元素 2.properties文件 3.程序代码传递
注意:这三种方式是有优先级的,mybatis根据优先级来覆盖原先配置的属性值。
优先级:程序传递>文件>子元素
前两种方式:
jdbc.properties
database.driver=com.mysql.jdbc.Driver
database.url=jdbc:mysql://localhost:3306/test-ssmr
database.username=root
database.password=root
方式三:程序代码传递
场景:运维人员为了保密,一般都需要把用户名和密码经过加密成为密文后,配置到properties文件中。对于开发人员和其他人员就需要通过解密才能得到真实的用户和密码。那么我们在创建SqlSessionFactory之前就需要把用户名和密码解密,然后把解密后的字符串重置到properties属性中。
public class SqlSessionFactoryUtil {
private final static Class LOCK = SqlSessionFactoryUtil.class;
private static SqlSessionFactory sqlSessionFactory = null;
//构造函数私有化
private SqlSessionFactoryUtil(){
}
public static SqlSessionFactory getSqlSessionFactory(){
synchronized (LOCK){
if (sqlSessionFactory != null){
return sqlSessionFactory;
}
String resource = "mybatis-config.xml";
InputStream inputStream;
try {
InputStream in = Resources.getResourceAsStream("jdbc.properties");
Properties props = new Properties();
props.load(in);
String username = props.getProperty("database.username");
String password = props.getProperty("batabase.password");
//解密用户名和密码,并在属性中重置
props.put("database.username", CodeUtils.decode(username));
props.put("database.password", CodeUtils.decode(password));
inputStream = Resources.getResourceAsStream(resource);
// 使用程序传递的方式覆盖原有的properties属性参数
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream, props);
} catch (IOException e){
e.printStackTrace();
return null;
}
return sqlSessionFactory;
}
}
}
二、
最复杂的配置,影响mybatis底层运行,但是大部分情况使用默认值即可。
下面给出一个全量的配置样例:
三、
别名分为系统定义别名和自定义别名。
注意:在MyBatis中别名不区分大小写。
1.系统定义别名
使用TypeAliasRefistry类的registerAlias方法就可以注册别名。一般是通过Configuration获取TypeAliasRegistry类对象,如configuration.getTypeAliasRegistry()。系统别名定义好了哪些?可以参考网络资料。
2.自定义别名
扫描别名的方式是将其第一个字母变为小写作为其别名,如role,user。这样如意出现别名重复的问题如
这样就会出现异常。这时可以用@Alias(user3)进行区分
@Alias("user3")
public class User {
}
四、
1.系统定义的typeHandler
public interface TypeHandler
public abstract class BaseTypeHandler
常用的StringTypeHandler: extends BaseTypeHandler
MyBatis采用org.apache.ibatis.type.TypeHandlerRegistry类对象的register方法进行注册。
2.自定义TypeHandler
MyBatis的TypeHandler就能应付一般的场景,但有时候不够用,如枚举(有特殊转换规则)
2.1仿造StringTypeHandler来实现。
(1)编写MyTypeHandler.java
public class MyTypeHandler implements TypeHandler {
Logger logger = Logger.getLogger(MyTypeHandler.class);
@Override
public void setParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
logger.info("设置string参数【" + parameter+"】");
ps.setString(i, parameter);
}
@Override
public String getResult(ResultSet rs, String columnName) throws SQLException {
String result = rs.getString(columnName);
logger.info("读取string参数1【" + result+"】");
return result;
}
@Override
public String getResult(ResultSet rs, int columnIndex) throws SQLException {
String result = rs.getString(columnIndex);
logger.info("读取string参数2【" + result+"】");
return result;
}
@Override
public String getResult(CallableStatement cs, int columnIndex) throws SQLException {
String result = cs.getString(columnIndex);
logger.info("读取string参数3【" + result+"】");
return result;
}
}
(2)mybatis-config.xml配置typeHandler (注册)
(3)显示启用自定义TypeHandler
有时候由于枚举类型很多,系统需要的typeHandler也会很多,如果采用配置也会很麻烦,这是可以考虑使用包扫描的形式:
这样就没法指定javaType和jdbcType了,我们可以使用注解来处理他们:
//启用扫描注册的时候需要注解
@MappedTypes(String.class)
@MappedJdbcTypes(JdbcType.VARCHAR)
public class MyTypeHandler implements TypeHandler {
在绝大多数情况下,typeHandler因为枚举而使用,MyBatis已经定义了两个类作为枚举类型的支持,它们是:EnumOrdinalTypeHandler和EnumTypeHandler
2.2.1EnumOrdinalTypeHandler
EnumOrdinalTypeHandler是按MyBatis根据枚举数组下标索引的方式进行匹配的,也是枚举类型的默认转换类,它要求数据库返回一个整数作为其下标,它会根据下标找到对应的枚举类型。
(1)创建性别枚举类SexEnum和用户POJO类User
public enum SexEnum {
NALE(1, "男"),
FENALE(0, "女");
private int id;
private String name;
SexEnum(int id, String name) {
this.id = id;
this.name = name;
}
public static SexEnum getSexById(int id) {
for (SexEnum sex : SexEnum.values()) {
if (sex.getId() == id) {
return sex;
}
}
return null;
}
/**getter/setter*/
}
public class User {
private Long id;
private String userName;
private String password;
private SexEnum sex;
private String mobile;
private String tel;
private String email;
private String note;
/** setter and getter **/
}
(2)使用EnumOrdinalTypeHandler定义UserMapper.xml
(3)测试
此时数据库sex字段是char类型,有一条数据(值为1,代表女性)
private static void testTypeHandler() {
Logger log = Logger.getLogger(Main.class);
SqlSession sqlSession = null;
try {
sqlSession = SqlSessionFactoryUtil.openSqlSession();
UserDao userDao = sqlSession.getMapper(UserDao.class);
User user = userDao.getUser(1L);
System.out.println(user.getSex().getName());
} finally {
if (sqlSession != null) {
sqlSession.close();
}
}
}
2.2.2EnumTypeHandler
EnumTypeHandler会把使用的名称转化为对应的枚举,比如它会根据数据库返回的字符串“MALE”,进行Enum.valueOf(SexEnum.class, "MALE");转换,所以为了测试EnumTypeHandler的转换,需要把数据库的sex字段修改为字符型(varchar(10)),并把sex=1的数据修改为FEMALE。
在UserMapper.xml中修改EnumTypeHandler即可按上面的测试方法测试。输出结果:女
2.2.3自定义枚举typeHandler
MyBatis内部提供的两种转换的typeHandler有很大的局限性,更多时候我们希望使用自定义的typeHandler。
把数据库的sex字段改为整数型,sex=1为男性,sex=0为女性。
@MappedTypes(SexEnum.class)
@MappedJdbcTypes(JdbcType.INTEGER)
public class SexEnumTypeHandler implements TypeHandler{
@Override
public void setParameter(PreparedStatement ps, int i, SexEnum parameter,
JdbcType jdbcType) throws SQLException {
ps.setInt(i, parameter.getId());
}
@Override
public SexEnum getResult(ResultSet rs, String columnName)
throws SQLException {
int id = rs.getInt(columnName);
return SexEnum.getSexById(id);
}
@Override
public SexEnum getResult(ResultSet rs, int columnIndex) throws SQLException {
int id = rs.getInt(columnIndex);
return SexEnum.getSexById(id);
}
@Override
public SexEnum getResult(CallableStatement cs, int columnIndex)
throws SQLException {
int id = cs.getInt(columnIndex);
return SexEnum.getSexById(id);
}
}
修改UserMapper.xml文件
2.3typeHandler文件操作
MyBatis对数据库的Blob字段也进行了支持,它提供了一个BlobTypeHandler,为了应付更多场景,它还提供了ByteArrayTypeHandler,只是它不太常用。这里只介绍BlobTypeHandler的用法。
(1)数据库建表:
CREATE TABLE t_file(
id INT(12) NOT NULL AUTO_INCREMENT,
content BLOB NOT NULL,
PRIMARY KEY(id)
);
(2)创建文件POJO
public class TestFile {
long id;
byte[] content;
/** setter and getter **/
}
(3)FileMapper.xml
注意:
1.实际上,不加入
2.现实中,一次性地将大量数据加载到JVM中,会给服务器造成很大的压力,所以在更多时候,应考虑使用文件流的形式。这时只要把content的属性改为InputStream即可,如果没有typeHandler声明,系统就会探测并使用BlobInputStreamTypeHandler为你转换结果,就需要将FileMapper.xml中typeHandler修改为org.apache.ibatis.type.BlobInputStreamTypeHandler。
3.文件的操作在大型互联网的网站上不常用,一般采用文件服务器到的形式,通过更为高速的文件系统操作。
五、
在默认情况下,MyBatis会使用其定义的对象工厂--DefaultObjectFactory(org.apache.ibatis.reflection.factory.DafaultObjectFactory)来完成对应的工作。
(1)自定义ObjectFactory
在这个类中MyBatis创建了一个List对象和一个Role对象,它先调用方法1,然后调用方法2,只是最后生成了同一个对象,所以在写入的判断中,始终写入的是true。因为返回的是一个Role对象,所以它最后适配为一个Role对象,这就是它的工作过程。
public class MyObjectFactory extends DefaultObjectFactory {
private static final long serialVersionUID = -2368068322576151574L;
Logger log = Logger.getLogger(MyObjectFactory.class);
private Object temp = null;
@Override
public void setProperties(Properties properties){
super.setProperties(properties);
log.info("初始化参数:【" + properties.toString() +"】");
}
//方法2
@Override
public T create(Class type){
T result = super.create(type);
log.info("创建对象:" + result.toString());
log.info("是否和上次创建的是同一对象:【" + (temp == result) + "】");
return result;
}
//方法1
@Override
public T create(Class type, List> constructorArgType, List
(2)配置MyObjectFactory(mybatis-config.xml中)
(3)测试
private static void testObjectFactory() {
Logger log = Logger.getLogger(Main.class);
SqlSession sqlSession = null;
try {
sqlSession = SqlSessionFactoryUtil.openSqlSession();
UserDao userDao = sqlSession.getMapper(UserDao.class);
User user = userDao.getUser(1L);
System.out.println(user.getSex().getName());
} finally {
if (sqlSession != null) {
sqlSession.close();
}
}
}
六、
七、
这里用到了两个元素:transactionManager事务管理器、environment数据源。
1.transactionManager事务管理器
常见用法有一下三种:
①JDBC使用JdbcTransactionFactory生成的JdbcTransaction对象实现。以JDBC的方式对数据库的提交和回滚进行操作的。
②MANAGED使用ManagedTransactionFactory生成的ManagedTransaction对象实现。它的提交和回滚不用任何操作,而是把事务交给容器处理。在默认情况下,它会关闭连接,然而一些容器不希望这样,因此需要将closeConnection属性设置为false来阻止它默认的关闭行为。
③自定义事务管理器。
自定义事务工厂:
public class MyObjectFactory extends DefaultObjectFactory {
private static final long serialVersionUID = -2368068322576151574L;
Logger log = Logger.getLogger(MyObjectFactory.class);
private Object temp = null;
@Override
public void setProperties(Properties properties){
super.setProperties(properties);
log.info("初始化参数:【" + properties.toString() +"】");
}
//方法2
@Override
public T create(Class type){
T result = super.create(type);
log.info("创建对象:" + result.toString());
log.info("是否和上次创建的是同一对象:【" + (temp == result) + "】");
return result;
}
//方法1
@Override
public T create(Class type, List> constructorArgType, List
自定义事务类:
public class MyTransaction extends JdbcTransaction implements Transaction {
public MyTransaction(DataSource ds, TransactionIsolationLevel desiredLevel, boolean desiredAutoCommit) {
super(ds, desiredLevel, desiredAutoCommit);
}
public MyTransaction(Connection connection) {
super(connection);
}
@Override
public Connection getConnection() throws SQLException{
return super.getConnection();
}
@Override
public void commit() throws SQLException {
super.commit();
}
@Override
public void rollback() throws SQLException {
super.rollback();
}
@Override
public void close() throws SQLException {
super.close();
}
@Override
public Integer getTimeout() throws SQLException {
return super.getTimeout();
}
}
数据源有三种,当然也可以自定义数据源。
在MyBatis中,数据库通过PooledDataSourceFactory、UnpooledDataSourceFactory和JndiDataSourceFactory三个工厂类来提供,前两者对应产生PooledDataSource、UnpooledDataSource类对象,而JndiDataSourceFactory则会根据JNDI的信息拿到外部容器实现的数据库连接对象。无论如何,这三个工厂类最后生成的产品都会是一个实现了DataSource接口的数据库连接对象。
自定义数据源工厂,如DBCP数据源:
public class DbcpDataSourceFactory implements DataSourceFactory{
private Properties props = null;
@Override
public void setProperties(Properties properties) {
this.props = properties;
}
@Override
public DataSource getDataSource() {
DataSource dataSource = null;
try {
dataSource = BasicDataSourceFactory.createDataSource(props);
} catch (Exception ex) {
ex.printStackTrace();
}
return dataSource;
}
}
mybatis-config.xml配置:
八、
解析:property元素的属性name是数据库的名称,如果不会填写,可用JDBC创建其数据库连接对象connection,然后connection.getMetaData().getDatabaseProductName()获取。而属性value是它的一个别名,在MyBatis里可以通过这个别名标识一条SQL适用于哪种数据库运行。
注意:①多数据库SQL时需要配置databaseIdProvidertype的属性。
②系统优先取到和数据库配置一致的SQL;如果没有,则取没有databaseId的属性,可以把它当作默认值;如果还是取不到则会抛出异常。
2.databaseIdProvider也可以自定义
实现接口DatabaseIdProvider
public class MyDatabaseIdProvider implements DatabaseIdProvider {
private static final String DATEBASE_TYPE_DB2 = "DB2";
private static final String DATEBASE_TYPE_MYSQL = "MySQL";
private static final String DATEBASE_TYPE_ORACLE = "Oralce";
private Logger log = Logger.getLogger(MyDatabaseIdProvider.class);
@Override
public void setProperties(Properties props) {
log.info(props);
}
@Override
public String getDatabaseId(DataSource dataSource) throws SQLException {
Connection connection = dataSource.getConnection();
String dbProductName = connection.getMetaData().getDatabaseProductName();
if (MyDatabaseIdProvider.DATEBASE_TYPE_DB2.equals(dbProductName)) {
return "db2";
} else if (MyDatabaseIdProvider.DATEBASE_TYPE_MYSQL
.equals(dbProductName)) {
return "mysql";
} else if (MyDatabaseIdProvider.DATEBASE_TYPE_ORACLE
.equals(dbProductName)) {
return "oracle";
} else {
return null;
}
}
}
mybatis-config.xml中配置:
九、
引入映射器的方法:
①文件路径引入
②包名引入
③类注册引入
④用userMapper.xml引入