上文介绍了MyBatis的简介以及核心组件,详情请看《MyBatis之简介及其核心组件》。本篇博客主要讲解MyBatis的相关配置。
1 概述
Mybatis配置文件并不复杂,它所有的元素如下所示:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<!-- 配置 -->
<configuration>
<!-- 属性 -->
<properties />
<!-- 设置 -->
<settings />
<!-- 类型命名 -->
<typeAliases />
<!-- 类型处理器 -->
<typeHandlers />
<!-- 对象工厂 -->
<objectFactory />
<!-- 插件 -->
<plugins />
<!--配置环境 -->
<environments>
<!--环境变量 -->
<environment>
<!--事务管理器 -->
<transactionManager />
<!--数据源 -->
<dataSource />
</environment>
</environments>
<!-- 数据库厂商标识 -->
<databaseIdProvider />
<!-- 映射器 -->
<mappers />
</configuration>
这里需要注意的是,MyBatis配置项的顺序不能颠倒。如果颠倒了他们的顺序,那么在MyBatis启动阶段就会发生异常,导致程序无法运行。
2 properties属性
properties属性可以给系统配置一些运行参数,可以放在XML文件或者properties文件中,而不是放在Java编码中,这样的好处在于方便参数修改,而不会引起代码的重新编译。一般而言,MyBatis提供了3种方式让我们使用properties:
- property子元素
- properties文件
- 程序代码传递
2.1 property子元素
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties>
<property name="database.driver"
value="com.mysql.cj.jdbc.Driver" />
<property name="database.url"
value="jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC&useSSL=false" />
<property name="database.username" value="root" />
<property name="database.password" value="root" />
</properties>
<!-- 别名 -->
<typeAliases>
<typeAlias alias="role"
type="com.hys.mybatis.example1.pojo.Role" />
</typeAliases>
<!--数据库环境 -->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="${database.driver}" />
<property name="url" value="${database.url}" />
<property name="username" value="${database.username}" />
<property name="password" value="${database.password}" />
</dataSource>
</environment>
</environments>
<!-- 映射文件 -->
<mappers>
<mapper
resource="com/hys/mybatis/example1/mapper/RoleMapper.xml" />
</mappers>
</configuration>
这里使用了元素<properties>下的子元素<property>定义,用字符串database.username定义数据库用户名,然后就可以在数据库定义中引入这个已经定义好的属性参数,如${database.username},这样定义一次就可以到处引用了。但是如果属性参数有成百上千个,显然使用这样的方式就不是一个很好的选择,这个时候可以使用properties文件。
2.2 使用properties文件
使用properties文件是比较普遍的方法,一方面这个文件十分简单,其逻辑就是键值对应,我们可以配置多个键值放在一个properties文件中,也可以把多个键值放到多个properties文件中,这些都是允许的,它方便日后维护和修改。
properties文件如下:
database.driver=com.mysql.cj.jdbc.Driver
database.url=jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC&useSSL=false
database.username=root
database.password=root
在MyBatis配置文件中通过<properties>的属性resource来引用properties文件:
<properties
resource="com/hys/mybatis/example2/config/jdbc.properties" />
这种方式也可以按${database.username}的方法引入properties文件的属性参数到MyBatis配置文件中。这个时候通过维护properties文件就可以维护我们的配置内容了。建议使用该方式来实现properties属性。
2.3 使用程序传递方式传递参数
在真实的生产环境中,数据库的用户密码是对开发人员和其他人员保密的。运维人员为了保密,一般都需要把用户和密码经过加密成为密文后,配置到properties文件中。对于开发人员及其他人员而言,就不知道其真实的用户密码了,数据库也不可能使用已经加密的字符串来连接,此时往往需要通过解密才能得到真实的用户名和密码。
String resource = "com/hys/mybatis/example2/config/mybatis-config.xml";
InputStream inputStream;
InputStream in = Resources.getResourceAsStream("com/hys/mybatis/example2/config/jdbc.properties");
Properties props = new Properties();
props.load(in);
String username = props.getProperty("database.username");
String password = props.getProperty("database.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);
首先使用Resources对象读取了一个jdbc.properties配置文件,然后获取了它原来配置的用户名和密码,用给定的解密规则进行解密并重置,最后使用SqlSessionFactoryBuilder的build方法,传递多个properties参数来完成,这将覆盖之前配置的密文,这样就能连接数据库了,同时也满足了运维人员对数据库用户和密码的要求。
3 settings设置
settings是MyBatis中最复杂的配置,它能深刻影响MyBatis底层的运行,但是在大部分情况下使用默认值便可以运行,所以在大部分情况下不需要大量配置它,只需要修改一些常用的规则即可,比如自动映射、驼峰命名映射、级联规则、是否启动缓存、执行器(Executor)类型等。一些常用的setting配置项说明如下:
配置项 | 作用 | 配置选项说明 | 默认值 |
---|---|---|---|
cacheEnabled | 该配置影响所有映射器中配置缓存的全局开关 | true|false | true |
lazyLoadingEnabled | 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。在特定关联关系中可通过设置fetchType属性来覆盖该项的开关状态 | true|false | false |
aggressiveLazyLoading | 当启用时,对任意延迟属性的调用会使带有延迟加载属性的对象完整加载;反之,每种属性将会按需加载 | true|false | 版本3.4.1(不包含)之前true,之后false |
autoMappingBehavior | 指定MyBatis应如何自动映射列到字段或属性。NONE表示取消自动映射;PARTIAL表示只会自动映射,没有定义嵌套结果集和映射结果集;FULL会自动映射任意复杂的结果集(无论是否嵌套) | NONE、PARTIAL、FULL | PARTIAL |
defaultExecutorType | 配置默认的执行器。SIMPLE是普通的执行器;REUSE会重用预处理语句(perpared statements);BATCH执行器将重用语句并执行批量更新 | SIMPLE、REUSE、BATCH | SIMPLE |
mapUnderscoreToCamelCase | 是否开启自动驼峰命名规则映射,即从经典数据库列名A_COLUMN到经典Java属性名aColumn的类似映射 | true|false | false |
<settings>
<setting name="cacheEnabled" value="true" />
<setting name="autoMappingBehavior" value="PARTIAL" />
<setting name="defaultExecutorType" value="SIMPLE" />
</settings>
4 typeAliases别名
由于类的全限定名称很长,需要大量使用的时候,总写那么长的名称不方便。在MyBatis中允许定义一个简写来代表这个类,这就是别名,别名分为系统别名和自定义别名。在MyBatis中别名由类TypeAliasRegistry(org.apache.ibatis.type.TypeAliasRegistry)来定义。注意,在MyBatis中别名不区分大小写。
4.1 系统定义别名
部分系统定义别名如下(完整系统定义别名请查看相关文档):
别名 | Java类型 | 是否支持数组 |
---|---|---|
_int | int | 是 |
_double | double | 是 |
_boolean | boolean | 是 |
string | String | 是 |
int | Integer | 是 |
double | Double | 是 |
boolean | Boolean | 是 |
date | Date | 是 |
bigdecimal | BigDecimal | 是 |
object | Object | 是 |
hashmap | HashMap | 否 |
list | List | 否 |
iterator | Iterator | 否 |
ResultSet | ResultSet | 否 |
4.2 自定义别名
由于在实际开发中存在很多对象,比如User这个对象有时候需要大量重复地使用,因此MyBatis也提供了用户自定义别名的规则。我们可以通过TypeAliasRegistry类的registerAlias方法注册,也可以采用配置文件或者扫描方式来自定义它。如下:
<typeAliases>
<typeAlias alias="role"
type="com.hys.mybatis.example1.pojo.Role" />
<typeAlias alias="user"
type="com.hys.mybatis.example2.pojo.User" />
</typeAliases>
这样就可以定义一个别名了。如果有很多类需要定义别名,那么用这样的方式进行配置就不那么轻松了。MyBatis还支持扫描别名。如下:
<typeAliases>
<package name="com.hys.mybatis.example2" />
</typeAliases>
这样MyBatis将扫描这个包下面的所有类,将其第一个字母变为小写作为其别名。但是这样的规则,有时候会出现重名,导致出现异常,这个时候可以使用MyBatis提供的注解@Ailas进行区分,如下:
import org.apache.ibatis.type.Alias;
@Alias("user1")
public class User {
//do something...
}
这样就能够避免因为别名重名导致的扫描失败的问题。
5 typeHandler类型转换器
在JDBC中,需要在PreparedStatement对象中设置那些已经预编译过的SQL语句的参数。执行SQL后,会通过ResultSet对象获取得到数据库的数据,而这些MyBatis是根据数据的类型通过typeHandler来实现的。在typeHandler中,分为jdbcType和javaType,其中jdbcType用于定义数据库类型,而javaType用于定义Java类型,那么typeHandler的作用就是根据jdbcType和javaType之间的相互转换。在很多情况下我们并不需要去配置typeHandler、jdbcType、javaType,因为MyBatis会探测应该使用什么类型的typeHandler进行处理,但是有些场景无法探测到。对于那些需要使用自定义枚举的场景,或者数据库使用特殊数据类型的场景,可以使用自定义的typeHandler来处理类型之间的转换问题。
5.1 系统定义的typeHandler
MyBatis内部定义了许多有用的typeHandler,部分如下(完整typeHandler请查看相关文档):
类型处理器 | Java类型 | JDBC类型 |
---|---|---|
IntegerTypeHandler | java.lang.Integer,int | 数据库兼容的NUMERIC或INTEGER |
DoubleTypeHandler | java.lang.Double,double | 数据库兼容的NUMERIC或DOUBLE |
BigDecimalTypeHandler | java.math.BigDecimal | 数据库兼容的NUMERIC或DECIMAL |
StringTypeHandler | java.lang.String | CHAR、VARCHAR |
ClobTypeHandler | java.lang.String | CLOB、LONGVARCHAR |
DateTypeHandler | java.util.Date | TIMESTAMP |
EnumTypeHandler | Enumeration Type | VARCHAR任何兼容的字符串类型,存储枚举的名称(而不是索引) |
5.2 自定义typeHandler
在MyBatis中typeHandler都要实现接口org.apache.ibatis.type.TypeHandler,或者继承org.apache.ibatis.type.BaseTypeHandler(实际上,BaseTypeHandler实现了typeHandler接口。自定义typeHandler类继承BaseTypeHandler类需要实现BaseTypeHandler类的4个抽象方法。这里使用了模板方法模式,如若对模板方法模式不熟悉,可以查看笔者之前的博客《设计模式之模板方法模式》)。这里笔者实现了一个类似StringTypeHandler功能的typeHandler,代码如下:
package com.hys.mybatis.example2.typehandler;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeHandler;
public class MyTypeHandler implements TypeHandler<String> {
@Override
public void setParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
System.out.println("设置string参数【" + parameter + "】");
ps.setString(i, parameter);
}
@Override
public String getResult(ResultSet rs, String columnName) throws SQLException {
String result = rs.getString(columnName);
System.out.println("读取string参数1【" + result + "】");
return result;
}
@Override
public String getResult(ResultSet rs, int columnIndex) throws SQLException {
String result = rs.getString(columnIndex);
System.out.println("读取string参数2【" + result + "】");
return result;
}
@Override
public String getResult(CallableStatement cs, int columnIndex) throws SQLException {
String result = cs.getString(columnIndex);
System.out.println("读取string参数3【" + result + "】");
return result;
}
}
- setParameter方法,是使用typeHandler通过PreparedStatement对象进行设置SQL参数的时候使用的具体方法,其中i是参数在SQL的下标,parameter是参数,jdbcType是数据库类型。
- 其中有3个getResult的方法,它的作用是从JDBC结果集中获取数据进行转换,要么使用列名(columnName)要么使用下标(columnIndex)获取数据库的数据,其中最后一个getResult方法是存储过程专用的。
mybatis-config.xml中需要配置typeHandler,如下:
<typeHandlers>
<typeHandler jdbcType="VARCHAR" javaType="string"
handler="com.hys.mybatis.example2.typehandler.MyTypeHandler" />
</typeHandlers>
现在UserMapper.xml中就可以显示启用自定义的typeHandler了,如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.hys.mybatis.example2.mapper.UserMapper">
<resultMap type="user" id="userMapper">
<result property="id" column="id" />
<result property="userName" column="user_name"
jdbcType="VARCHAR" javaType="string" />
<result property="password" column="password"
typeHandler="com.hys.mybatis.example2.typehandler.MyTypeHandler" />
<result property="sex" column="sex" />
<result property="mobile" column="mobile" />
<result property="tel" column="tel" />
<result property="email" column="email" />
<result property="note" column="note" />
</resultMap>
<select id="getUser" parameterType="string"
resultMap="userMapper">
select
id,user_name,password,sex,mobile,tel,email,note from
t_user where
user_name=#{user_name,jdbcType=VARCHAR,javaType=string}
</select>
<select id="getUser1" parameterType="string"
resultMap="userMapper">
select
id,user_name,password,sex,mobile,tel,email,note from
t_user where
user_name=#{user_name,typeHandler=com.hys.mybatis.example2.typehandler.MyTypeHandler}
</select>
</mapper>
要么指定了与自定义typeHandler一致的jdbcType和javaType,要么直接使用typeHandler指定具体的实现类。运行结果如下:
log4j:WARN No appenders could be found for logger (org.apache.ibatis.logging.LogFactory).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
设置string参数【Robert Hou】
读取string参数1【Robert Hou】
读取string参数1【1234】
User [id=1, userName=Robert Hou, password=1234, sex=MALE, mobile=185, tel=833, email=hys@qq, note=Programmer]
5.3 枚举typeHandler
MyBatis已经定义了两个类作为枚举类型的支持,分别是EnumOrdinalTypeHandler和EnumTypeHandler。
- EnumOrdinalTypeHandler:是按MyBatis根据枚举数组下标索引的方式进行匹配的,也是枚举类型的默认转换类,它要求数据库返回一个整数作为其下标,它会根据下标找到对应的枚举类型。
- EnumTypeHandler:会把使用的名称转化为对应的枚举,比如它会根据数据库返回的字符串"MALE"进行Enum.valueOf(SexEnum.class, "MALE");转换。
但是这两个typeHandler的作用不大,所以在大部分情况下,我们都不用它们,这里也不做过多介绍。下面来自定义实现一个枚举typeHandler。
首先定义一个枚举类SexEnum,如下:
package com.hys.mybatis.example2.pojo;
public enum SexEnum {
MALE(1, "男"), FEMALE(0, "女");
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
private 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;
}
}
POJO:
package com.hys.mybatis.example2.pojo;
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;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public SexEnum getSex() {
return sex;
}
public void setSex(SexEnum sex) {
this.sex = sex;
}
public String getMobile() {
return mobile;
}
public void setMobile(String mobile) {
this.mobile = mobile;
}
public String getTel() {
return tel;
}
public void setTel(String tel) {
this.tel = tel;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getNote() {
return note;
}
public void setNote(String note) {
this.note = note;
}
@Override
public String toString() {
return "User [id=" + id + ", userName=" + userName + ", password=" + password + ", sex=" + sex + ", mobile=" + mobile + ", tel=" + tel + ", email=" + email + ", note=" + note + "]";
}
}
UserMapper.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.hys.mybatis.example2.mapper.UserMapper">
<resultMap type="user" id="userMapper">
<result property="id" column="id" />
<result property="userName" column="user_name" />
<result property="password" column="password" />
<result property="sex" column="sex"
typeHandler="sexEnumTypeHandler" />
<result property="mobile" column="mobile" />
<result property="tel" column="tel" />
<result property="email" column="email" />
<result property="note" column="note" />
</resultMap>
<select id="getUser" parameterType="string"
resultMap="userMapper">
select
id,user_name,password,sex,mobile,tel,email,note from
t_user where
user_name=#{user_name}
</select>
</mapper>
6 environments(运行环境)
在MyBatis中,运行环境主要的作用是配置数据库信息,它可以配置多个数据库,一般而言之需要配置其中的一个就可以了。它下面又分为两个可配置的元素:事务管理器(transactionManager)和数据源(dataSource)。在实际的工作中,大部分情况下会采用Spring对数据源和数据库的事务进行管理。这里我们先讨论MyBatis自身实现的类。
6.1 transactionManager(事务管理器)
在MyBatis中,transactionManager提供了两个实现类,它需要实现接口org.apache.ibatis.transaction.Transaction。MyBatis为Transaction提供了两个实现类:JdbcTransaction和ManagedTransaction。于是它对应着两种工厂:JdbcTransactionFactory和ManagedTransactionFactory,这个工厂需要实现TransactionFactory接口,通过它们会生成对应的Transaction对象。于是事务管理器配置成为以下两种方式:
<transactionManager type="JDBC" />
<transactionManager type="MANAGED" />
- JDBC使用JdbcTransactionFactory生成的JdbcTransaction对象实现。它是以JDBC的方式对数据库的提交和回滚进行操作。
- MANAGED使用ManagedTransactionFactory生成的ManagedTransaction对象实现。它的提交和回滚方法不用任何操作,而是把事务交给容器处理。在默认情况下,它会关闭连接,然而一些容器并不希望这样,因此需要将closeConnection属性设置为false来阻止它默认的关闭行为。
6.2 environment数据源环境
environment的主要作用是配置数据库,在MyBatis中,数据库通过PooledDataSourceFactory、UnpooledDataSourceFactory和JndiDataSourceFactory三个工厂类来提供,前两者对应产生PooledDataSource、UnpooledDataSpurce类对象,而JndiDataSourceFactory则会根据JNDI的信息拿到外部容器实现的数据库连接对象。无论如何这三个工厂类,最后生成的产品都会是一个实现了DataSource接口的数据库连接对象。
<dataSource type="UNPOOLED">
<dataSource type="POOLED">
<dataSource type="JNDI">
- UNOPOOLED:采用非数据库池的管理方式,每次请求都会打开一个新的数据库连接,所以创建会比较慢。在一些对性能没有很高要求的场合可以使用它。对有些数据库而言,使用连接池并不重要,那么它也是一个比较理想的选择。
- POOLED:数据源POOLED利用“池”的概念将JDBC的Connection对象组织起来,它开始会有一些空置,并且已经连接好的数据库连接,所以请求时,无需再建立和验证,省去了创建新的连接实例时所必需的初始化和认证时间。它还控制最大连接数,避免过多的连接导致系统瓶颈。
- JNDI:数据源JNDI的实现是为了能在如EJB或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个个JNDI上下文的引用。
MyBatis也支持第三方数据源,例如使用DBCP数据源,那么需要提供一个自定义的DataSourceFactory。
7 引入映射器的方法
- 用文件路径引入映射器
<mappers>
<mapper
resource="com/hys/mybatis/example2/mapper/UserMapper.xml" />
</mappers>
- 用包名引入映射器
<mappers>
<package name="com.hys.mybatis.example2.mapper" />
</mappers>
- 用类注册引入映射器
<mappers>
<mapper class="com.hys.mybatis.example2.mapper.UserMapper" />
</mappers>
- 用userMapper.xml引入映射器
<mappers>
<mapper url="file:///E:/EclipseWorkspace/SSM/mybatis/src/com/hys/mybatis/example2/mapper/UserMapper.xml" />
</mappers>
参考资料:[1]杨开振 周吉文 梁华辉 谭茂华.Java EE 互联网轻量级框架整合开发:SSM框架(Spring MVC+Spring+MyBatis)和Redis实现[M].北京:电子工业出版社,2017.7