MyBatis配置详解

2024/1/10 MyBatis框架

# 前言

每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先配置的 Configuration 实例来构建出 SqlSessionFactory 实例。XML配置文件中包含了对 MyBatis 系统的核心设置

mybatis-config.xml

<?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">
<!--mybatis核心配置文件-->
<configuration>
    <!--添加properties配置文件路径(外部配置、动态替换)-->
    <properties resource="jdbc.properties" />
    <!--配置环境-->
    <environments default="development">
        <environment id="development">
            <!--配置事务管理-->
            <transactionManager type="JDBC"></transactionManager>
            <!--配置数据源(连接池)-->
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>
    <!-- 每一个mapper.xml都需要在这个配置,获取指定的Mapper.xml文件-->
    <mappers>
        <mapper resource="mapper/UserDao.xml"/>
    </mappers>
</configuration>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

这是第一个入门案例中配置的mybatis-config.xml配置文件,是MyBatis的核心配置文件。MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。

# 1、properties(属性)

# 1.1、properties使用

properties配置的属性相当于Java中类的成员变量(属性变量)。

<!--添加properties配置文件路径(外部配置、动态替换)-->
<properties resource="jdbc.properties" />
1
2

resource指向文件路径地址,读取jdbc.properties文件中的内容,并把内容加载到properties中。

上面的这个配置还可以不读取jdbc.properties配置文件,直接设置属性值。

<!-- 定义变量 -->
<properties>
  <property name="driver" value="com.mysql.jdbc.Driver"/>
  <property name="url" value="jdbc:mysql://localhost:3306/xygalaxy?useSSL=false&amp;useUnicode=true&amp;characterEncoding=utf8"/>
  <property name="username" value="root"/>
  <property name="password" value="123456"/>
</properties>

<!-- 使用上面定义的变量设置为环境值 -->
<environments default="development">
    <environment id="development">
        <!--配置数据源(连接池)-->
        <dataSource type="POOLED">
            <property name="driver" value="${driver}"/>
            <property name="url" value="${url}"/>
            <property name="username" value="${username}"/>
            <property name="password" value="${password}"/>
        </dataSource>
    </environment>
</environments>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

resource指定配置文件和属性定义可以同时存在

<properties resource="org/mybatis/example/config.properties">
  <property name="username" value="dev_user"/>
  <property name="password" value="F2Fa3!33TYyg"/>
</properties>
1
2
3
4

# 1.2、Java配置方式使用

java.util包下提供了Properties类,该Properties类继承了Hashtable,我们知道HashtableMap的实现类,所以对于Properties类,具有Map的特性,也就是key-value格式存储。

Properties的set和get

Properties properties = new Properties();
properties.setProperty("driver","com.mysql.jdbc.Driver");
properties.setProperty("url","jdbc:mysql://localhost:3306/xygalaxy?useSSL=false&amp;useUnicode=true&amp;characterEncoding=utf8");
properties.setProperty("username","root");
properties.setProperty("password","123456");


String driverClass = properties.getProperty("driver");
String url = properties.getProperty("url");
String username = properties.getProperty("username");
String password = properties.getProperty("password");
1
2
3
4
5
6
7
8
9
10
11

读取jdbc.properties配置文件

MyBatis提供了一个Resources类可以方便的读取类路径下的配置文件,使用方法如下:

InputStream inputStream = Resources.getResourceAsStream("jdbc.properties");
Properties properties = new Properties();
roperties.load(inputStream);

String driverClass = properties.getProperty("jdbc.driver");
String url = properties.getProperty("jdbc.url");
String username = properties.getProperty("jdbc.username");
String password = properties.getProperty("jdbc.password");
1
2
3
4
5
6
7
8

配置MyBatisConfig的属性

// @PropertySource("jdbc.properties") 配合@Value("${key}")使用
public class MyBatisConfig {

    //定义属性 为属性注入数据(数据的来源上面引入的jdbc.properties文件)
    private static String driverClass;

    private static String url;

    private static String username;

    private static String password;

    /**
     * @PropertySource()读取不了jdbc.properties文件,没找到什么原因,换用构造读取写入
     * @throws IOException
     */
    static {
        Properties properties = new Properties();
        try {
            InputStream inputStream = Resources.getResourceAsStream("jdbc.properties");
            properties.load(inputStream);
        } catch (IOException e) {
            log.error("读取jdbc.properties文件失败",e);
        }

        driverClass = properties.getProperty("jdbc.driver");
        url = properties.getProperty("jdbc.url");
        username = properties.getProperty("jdbc.username");
        password = properties.getProperty("jdbc.password");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

# 2、settings(设置)

settings(设置)是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。每项都有对应的默认值,可以按需修改。

# 2.1、xml方式配置

开启缓存和延迟加载

<?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">
<!--mybatis核心配置文件-->
<configuration>
    <settings>
        <setting name="cacheEnabled" value="true"/>
        <setting name="lazyLoadingEnabled" value="true"/>
    </settings>
</configuration>
1
2
3
4
5
6
7
8
9
10
11

一个配置完整的 settings 元素,参考:Mybatis-设置(settings) (opens new window)

<settings>  
  <!-- 开启缓存 -->  
  <setting name="cacheEnabled" value="true"/>  
  <!-- 开启延迟加载 -->  
  <setting name="lazyLoadingEnabled" value="true"/>  
  <!-- 启用侵略性延迟加载 -->  
  <setting name="aggressiveLazyLoading" value="true"/>  
  <!-- 启用多个结果集 -->  
  <setting name="multipleResultSetsEnabled" value="true"/>  
  <!-- 使用列标签 -->  
  <setting name="useColumnLabel" value="true"/>  
  <!-- 不使用生成的主键 -->  
  <setting name="useGeneratedKeys" value="false"/>  
  <!-- 自动映射的行为设置为PARTIAL -->  
  <setting name="autoMappingBehavior" value="PARTIAL"/>  
  <!-- 未知列的自动映射行为设置为WARNING -->  
  <setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>  
  <!-- 默认的执行类型是SIMPLE -->  
  <setting name="defaultExecutorType" value="SIMPLE"/>  
  <!-- 默认的语句超时时间为25秒 -->  
  <setting name="defaultStatementTimeout" value="25"/>  
  <!-- 默认的fetch size为100 -->  
  <setting name="defaultFetchSize" value="100"/>  
  <!-- 不使用安全的行边界 -->  
  <setting name="safeRowBoundsEnabled" value="false"/>  
  <!-- 启用安全的结果处理器 -->  
  <setting name="safeResultHandlerEnabled" value="true"/>  
  <!-- 不将下划线映射为驼峰式命名 -->  
  <setting name="mapUnderscoreToCamelCase" value="false"/>  
  <!-- 本地缓存的范围设置为SESSION -->  
  <setting name="localCacheScope" value="SESSION"/>  
  <!-- 对于null的jdbc类型设置为OTHER -->  
  <setting name="jdbcTypeForNull" value="OTHER"/>  
  <!-- 当触发懒加载的方法为equals, clone, hashCode, toString -->  
  <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>  
  <!-- 默认的脚本语言驱动是org.apache.ibatis.scripting.xmltags.XMLLanguageDriver -->  
  <setting name="defaultScriptingLanguage" value="org.apache.ibatis.scripting.xmltags.XMLLanguageDriver"/>  
  <!-- 默认的枚举类型处理器是org.apache.ibatis.type.EnumTypeHandler -->  
  <setting name="defaultEnumTypeHandler" value="org.apache.ibatis.type.EnumTypeHandler"/>  
  <!-- 不在null上调用setter方法 -->  
  <setting name="callSettersOnNulls" value="false"/>  
  <!-- 对于空行不返回实例 -->  
  <setting name="returnInstanceForEmptyRow" value="false"/>  
  <!-- 日志前缀为exampleLogPreFix_ -->  
  <setting name="logPrefix" value="exampleLogPreFix_"/>  
  <!-- 日志实现为SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING -->  
  <setting name="logImpl" value="SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING"/>  
  <!-- 代理工厂为CGLIB或JAVASSIST -->  
  <setting name="proxyFactory" value="CGLIB | JAVASSIST"/>  
  <!-- VFS实现为org.mybatis.example.YourselfVfsImpl -->  
  <setting name="vfsImpl" value="org.mybatis.example.YourselfVfsImpl"/>  
  <!-- 使用实际的参数名 -->  
  <setting name="useActualParamName" value="true"/>  
  <!-- 配置工厂为org.mybatis.example.ConfigurationFactory -->  
  <setting name="configurationFactory" value="org.mybatis.example.ConfigurationFactory"/>  
</settings>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56

# 2.2、Java配置方式使用

MyBatis更趋向于XML配置方式,对于Java配置方式配置最后章节会讲,这里只是说明对于MyBatis配置来说,XML方式和Java配置方式都可以。SpringBoot开发项目时这些都会简化配置,现在这些只是学习过程。

resources/config下创建mybatis核心配置文件mybatis-config-spring.xml,数据源和SqlSessionFactoryBean我们已经都交给Spring管理了,这里就不需要额外的配置了,只要设置一些我们需要的就行。

<?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">
<!--mybatis核心配置文件-->
<configuration>
    <settings>  
        <!-- 开启缓存 -->  
        <setting name="cacheEnabled" value="true"/>  
        <!-- 开启延迟加载 -->  
        <setting name="lazyLoadingEnabled" value="true"/>  
    </settings>
</configuration>
1
2
3
4
5
6
7
8
9
10
11
12
13

MyBatisConfig配置类中,将核心配置加入

@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource){
    SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
    sqlSessionFactoryBean.setDataSource(dataSource);
    sqlSessionFactoryBean.setMapperLocations(resolveMapperLocations());
    // 设置核心配置文件
    sqlSessionFactoryBean.setConfigLocation(new ClassPathResource("classpath*:config/mybatis-config-spring.xml"));
    return sqlSessionFactoryBean;
}
1
2
3
4
5
6
7
8
9

对于开启缓存这里说两点配置方式,后面会详细说明。

  • 注解开启缓存
@CacheNamespace(implementation = org.mybatis.caches.ehcache.EhcacheCache.class)  
public interface UserMapper {  
    @Select("SELECT * FROM users WHERE id = #{id}")  
    UserPO getUserById(int id);  
}
1
2
3
4
5

@CacheNamespace 是 MyBatis-Spring 集成中的一个注解,用于启用缓存。并指定了 Ehcache 作为缓存实现(可以不指定用默认的)。

  • Mapper.xml中开启缓存
<!-- useCache="true"表示当前查询开启二级缓存,一级缓存默认开启 -->
<!-- flushCache的默认值是false,表示任何时候语句被调用,都不会去清空本地缓存和二级缓存。 -->
<select id="getUserList" resultType="com.xygalaxy.pojo.UserPO" useCache="true" flushCache="true">
    select * from user
</select>

<!-- 表示整个Mapper映射文件 -->
<mapper namespace="com.xygalaxy.mapper.UserMapper">
    <!--当前映射文件开启二级缓存-->
    <cache></cache>

</mapper>
1
2
3
4
5
6
7
8
9
10
11
12

# 3、typeAliases(类型别名)

类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。

MyBatis提供了三种方式来设置类型别名:

  • 单一类型设置别名
  • 包全局设置别名
  • 注解设置别名

# 3.1、单一类型设置别名

typeAliases需要配置在environments之前

<?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">
<!--mybatis核心配置文件-->
<configuration>
    <!-- 配置别名  -->
    <typeAliases>
        <typeAlias alias="user" type="com.xygalaxy.pojo.UserPO"/>
    </typeAliases>
</configuration>
1
2
3
4
5
6
7
8
9
10
11

UserMapper.xml中使用时

<!-- 原来的resultType为全限定类名 -->
<select id="getUserList" resultType="com.xygalaxy.pojo.UserPO">
    select * from user
</select>

<!-- 直接使用别名 -->
<select id="getUserList" resultType="user">
    select * from user
</select>
1
2
3
4
5
6
7
8
9

# 3.2、包全局设置别名

指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean,会使用 Bean 的首字母小写的非限定类名来作为它的别名。比如com.xygalaxy.pojo.UserPO的别名为userPO

<?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">
<!--mybatis核心配置文件-->
<configuration>
    <!-- 配置别名  -->
    <typeAliases>
        <!--   扫描整个包     -->
        <package name="com.xygalaxy.pojo"/>
    </typeAliases>
</configuration>
1
2
3
4
5
6
7
8
9
10
11
12

# 3.3、注解设置别名

在没有注解的情况下,包扫描后,UserPO的别名为userPO。但加了注解后,别名就为注解中的别名user

@Alias("user")
public class UserPO {
    
}
1
2
3
4

# 3.4、Java配置方式配置别名

通过SqlSessionFactoryBean设置别名包扫描,如果是单个的话,就用注解简单些。

@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource){
    SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
    // 别名包扫描
    sqlSessionFactoryBean.setTypeAliasesPackage("com.xygalaxy.pojo");
    return sqlSessionFactoryBean;
}
1
2
3
4
5
6
7

# 4、typeHandlers(类型处理器)

MyBatis 在设置预处理语句(PreparedStatement)中的参数或从结果集中取出一个值时, 都会用类型处理器将获取到的值以合适的方式转换成 Java 类型。

MyBatis中支持的类型处理器很完善了,大多数情况都可以直接使用,不需要自己定义。

# 4.1、自定义类型处理器

自定义类型处理器方式有两种,实现TypeHandler接口,或继承抽象类BaseTypeHandle,并可以指定转换后的字段类型

BaseTypeHandler也是继承了TypeHandler接口,在实现的TypeHandler接口的方法中调用的是自身抽象方法。

public abstract class BaseTypeHandler<T> extends TypeReference<T> implements TypeHandler<T> {
  
  public T getResult(ResultSet rs, String columnName) throws SQLException {
      Object result;
      try {
          result = this.getNullableResult(rs, columnName);
      } catch (Exception var5) {
          throw new ResultMapException("Error attempting to get column '" + columnName + "' from result set.  Cause: " + var5, var5);
      }

      return rs.wasNull() ? null : result;
  }

  public abstract T getNullableResult(ResultSet var1, String var2) throws SQLException;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  • 指定处理类
<!-- mybatis-config.xml中配置全局的类型处理器 -->
<typeHandlers>
  <typeHandler handler="org.mybatis.example.ExampleTypeHandler"/>
</typeHandlers>

<!-- 局部类型处理,在Mapper中指定需要转换的字段,jdbcType指数据库中的类型,javaType指Java中的类型,typeHandler指定自定义的转换器 -->
<result column="flag" property="flag" jdbcType="TINYINT" javaType="java.lang.String" typeHandler="com.rangers.MyTypeHandler"></result>
1
2
3
4
5
6
7
  • 继承BaseTypeHandler类,重写处理方法
@MappedJdbcTypes(value="JdbcType.VARCHAR",includeNullJdbcType=false)
public class ExampleTypeHandler extends BaseTypeHandler<String> {

  @Override
  public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
    ps.setString(i, parameter);
  }

  @Override
  public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
    return rs.getString(columnName);
  }

  @Override
  public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
    return rs.getString(columnIndex);
  }

  @Override
  public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
    return cs.getString(columnIndex);
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

@MappedJdbcTypes 注解有两个属性可以设置,全局处理器可以设置:

  • value:用于指定支持的 JdbcType 类型。这里设置为 JdbcType.VARCHAR 表示我们的自定义 Java 类型可以映射到数据库的 VARCHAR 类型。
  • includeNullJdbcType:用于指定是否要将 null 值的数据库类型也包括在内。默认情况下,该属性为 false,即不包括 null 值的类型。

想了解更多参考:类型处理器(typehandlers) (opens new window)

# 4.2、处理枚举类型

在开发中,我们用到的类型处理可能相对较少,但是枚举类的处理会非常多,Mybatis可以将枚举中的值进行转换使用,比如:数据库中的User表,性别字段,数据库保存了man、women表示,但是我们需要返回结果为男、女,一般这类固定的都用枚举类保存,这中间就需要进行转换。

MyBatis内置两个枚举转换器:org.apache.ibatis.type.EnumTypeHandlerorg.apache.ibatis.type.EnumOrdinalTypeHandler

  • EnumTypeHandler:这是默认的枚举转换器,该转换器将枚举实例转换为实例名称的字符串,即将UserSexEnum.MAN转换MAN。
  • EnumOrdinalTypeHandler:这个转换器将枚举实例的ordinal属性作为取值,从0开始,比如:数据表中为1,则枚举取值为UserSexEnum.WOMAN。
  • 默认拦截器配置
<?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">
<!--mybatis核心配置文件-->
<configuration>
    <typeHandlers>
        <typeHandler handler="org.apache.ibatis.type.EnumTypeHandler" javaType="com.xygalaxy.enums.UserSexEnum"/>
    </typeHandlers>
</configuration>
1
2
3
4
5
6
7
8
9
10

新增枚举UserSexEnum

@Getter
@AllArgsConstructor
@NoArgsConstructor
public enum UserSexEnum{
    
    MAN,WOMAN;

    private String sexValue;
}
1
2
3
4
5
6
7
8
9

UserPO新增枚举字段

@Data // 提供get和set方法
@AllArgsConstructor // 全构造
@NoArgsConstructor // 无参构造
public class UserPO implements Serializable {

    private Integer id;

    private String username;

    private String password;

    private String email;

    private UserSexEnum sex;

}


// 测试
@Test
public void testEnum(){
    UserPO userPO = userMapper.selectAllUserList().get(0);
    log.info("Enum ==> "+userPO.getSex()+" ===>"+userPO);
}
// 结果:Enum ==> MAN ===>UserPO(id=1, username=updateUser, password=323232, email=222@qq.com, sex=MAN)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

使用EnumTypeHandler结果

数据库是MAN,对应的sex枚举也是MAN

使用EnumOrdinalTypeHandler结果

数据库是0,对应的sex枚举是第一个值,也就是MAN,如果数据库是1,那么就是WOMAN。

# 4.3、自定义枚举转换器

MyBatis提供的内置枚举类型处理都是对应简单的枚举转换,也不怎么好用,我们可以自定义枚举使用,以下是通用的枚举转换器,并兼容默认的枚举转换器。

  • 定义枚举接口BaseEnum
public interface BaseEnum<K,V> {

    K getKey();

    V getValue();

}
1
2
3
4
5
6
7
  • 新增UserSexEnum枚举类并实现枚举接口,将key和value返回,如果只有一个值的,可以都一样,或者用默认的枚举转换器就行
@Getter
@AllArgsConstructor
@NoArgsConstructor
public enum UserSexEnum implements BaseEnum {

    MAN("man","男"),
    WOMAN("woman","女");

    private String sexKey;

    private String sexValue;

    @Override
    public String getKey() {
        return sexKey;
    }

    @Override
    public String getValue() {
        return sexValue;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
  • 新增枚举处理器CodeEnumTypeHandler
public class CodeEnumTypeHandler<E extends Enum<?> & BaseEnum> extends BaseTypeHandler<BaseEnum> {

    private Class<E> type;

    public CodeEnumTypeHandler(Class<E> type) {
        if (type == null) {
            throw new IllegalArgumentException("Type argument cannot be null");
        }
        this.type = type;
    }

    /**
     * 写入数据库时调用,用于定义设置参数时,该如何把Java类型的参数转换为对应的数据库类型
     */
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, BaseEnum parameter, JdbcType jdbcType)
            throws SQLException {
        ps.setString(i, parameter.getKey().toString());
    }

    /**
     * getNullableResult三个方法是在查询结果集中获取指定列的枚举值,一个是根据列名获取值,一个是根据列索引位置获取值,最后一个是存储过程。
     */
    @Override
    public E getNullableResult(ResultSet rs, String columnName) throws SQLException {
        String code = rs.getString(columnName);
        return rs.wasNull() ? null : getCodeValue(code);
    }

    @Override
    public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        String code = String.valueOf(rs.getInt(columnIndex));
        return rs.wasNull() ? null : getCodeValue(code);
    }

    @Override
    public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        String code = String.valueOf(cs.getInt(columnIndex));
        return cs.wasNull() ? null : getCodeValue(code);
    }

    private E getCodeValue(String code){
        E[] enumConstants = type.getEnumConstants();
        return Arrays.stream(enumConstants)
                .filter(i -> i.getKey().equals(code))
                .findAny()
                .orElseThrow(() -> new IllegalArgumentException("未知的枚举类型:" + code + ",请核对" + this.type.getSimpleName()));
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
  • 兼容原有枚举处理器,如果有需要兼容,则用AutoEnumTypeHandler,如果觉得没必要,直接用CodeEnumTypeHandler
public class AutoEnumTypeHandler<E extends Enum<E>> extends BaseTypeHandler<E> {

    private BaseTypeHandler typeHandler = null;

    public AutoEnumTypeHandler(Class<E> type) {
        if (type == null) {
            throw new IllegalArgumentException("Type argument cannot be null");
        }
        // 如果是枚举,并且实现了BaseAutoEnum,则用自定义枚举转换
        if(type.isEnum()&&BaseEnum.class.isAssignableFrom(type)){
            typeHandler = new CodeEnumTypeHandler(type);
        }else {
            // 默认转换器 也可换成 EnumOrdinalTypeHandler
            typeHandler = new EnumTypeHandler<>(type);
        }
    }

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType) throws SQLException {
        typeHandler.setNonNullParameter(ps,i, parameter,jdbcType);
    }

    @Override
    public E getNullableResult(ResultSet rs, String columnName) throws SQLException {
        return (E) typeHandler.getNullableResult(rs,columnName);
    }

    @Override
    public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        return (E) typeHandler.getNullableResult(rs,columnIndex);
    }

    @Override
    public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        return (E) typeHandler.getNullableResult(cs,columnIndex);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
  • 配置枚举处理器为自定义处理器
<?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">
<!--mybatis核心配置文件-->
<configuration>
    <typeHandlers>
        <!-- handler自定义处理器,javaType需要处理的枚举类型 -->
        <typeHandler handler="com.xygalaxy.handler.AutoEnumTypeHandler" javaType="com.xygalaxy.enums.UserSexEnum"/>
    </typeHandlers>
</configuration>
1
2
3
4
5
6
7
8
9
10
11
  • 测试
@Test
public void testEnum(){
    UserPO userPO = userMapper.selectAllUserList().get(0);
    log.info("Enum ==> "+userPO.getSex().getValue()+" ===>"+userPO);
}

// 结果:Enum ==> 男 ===>UserPO(id=1, username=updateUser, password=323232, email=222@qq.com, sex=MAN)
1
2
3
4
5
6
7

# 5、objectFactory(对象工厂)

每次 MyBatis 创建结果对象的新实例时,它都会使用一个对象工厂(ObjectFactory)实例来完成实例化工作。 默认的对象工厂需要做的仅仅是实例化目标类,要么通过默认无参构造方法,要么通过存在的参数映射来调用带有参数的构造方法。 如果想覆盖对象工厂的默认行为,可以通过创建自己的对象工厂来实现。比如:

// ExampleObjectFactory.java
public class ExampleObjectFactory extends DefaultObjectFactory {
  @Override
  public <T> T create(Class<T> type) {
    return super.create(type);
  }

  @Override
  public <T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
    return super.create(type, constructorArgTypes, constructorArgs);
  }

  @Override
  public void setProperties(Properties properties) {
    super.setProperties(properties);
  }

  @Override
  public <T> boolean isCollection(Class<T> type) {
    return Collection.class.isAssignableFrom(type);
  }}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!-- mybatis-config.xml -->
<objectFactory type="org.mybatis.example.ExampleObjectFactory">
  <property name="someProperty" value="100"/>
</objectFactory>
1
2
3
4

ObjectFactory 接口很简单,它包含两个创建实例用的方法,一个是处理默认无参构造方法的,另外一个是处理带参数的构造方法的。 另外,setProperties 方法可以被用来配置 ObjectFactory,在初始化你的 ObjectFactory 实例后, objectFactory 元素体中定义的属性会被传递给 setProperties 方法。

# 6、plugins(插件)

MyBatis的插件实际上是一个拦截器,可以在MyBatis的各个操作(如查询、插入、更新、删除等)执行前后进行拦截,并对这些操作进行增强或修改。MyBatis的插件可以作用于MyBatis中的四大接口,分别为:Executor,ParameterHandler,ResultSetHandler和StatementHandler。

可作用接口 可作用方法 拦截器用途
Executor update(),query(),flushStatements(),commit(),rollback(),getTransaction(),close(),isClosed() 拦截执行器中的方法
ParameterHandler getParameterObject(),setParameters() 拦截对参数的处理
ResultSetHandler handleResultSets(),handleOutputParameters() 拦截对结果集的处理
StatementHandler prepare(),parameterize(),batch(),update(),query() 拦截SQL构建的处理

# 6.1、自定义插件使用

  • 定义拦截器,实现Interceptor接口
@Slf4j
@Intercepts({@Signature(type = Executor.class, method = "query",
        args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})
public class ExecutorInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 获取被拦截的对象
        Object target = invocation.getTarget();
        log.info("target=============>"+target);
        // 获取被拦截的方法
        Method method = invocation.getMethod();
        log.info("method=========>"+method);
        // 获取被拦截的方法的参数
        Object[] args = invocation.getArgs();
        log.info("args=========>"+args);

        log.info("start Before=========>");
        // 执行被拦截的方法
        Object result = invocation.proceed();
        log.info("result==========>"+result);

        // 返回执行结果
        return result;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

  • @Intercepts: 这是 MyBatis 插件的核心注解,它告诉 MyBatis 这个类是一个插件,并且该插件会拦截某些方法。
  • @Signature: 这个注解定义了插件要拦截的方法的详细信息。
    • type: 指定要拦截的类的类型,这里是 Executor 类。这意味着这个插件会拦截 Executor 类的所有方法。
    • method: 指定要拦截的具体方法名,这里是 "query"。这意味着这个插件只会拦截 Executor 类中的 query 方法。
    • args: 指定 method 方法参数的类型列表。这意味着只有当 query 方法的参数类型与这里指定的类型相匹配时,该方法才会被拦截。在这个例子中,query 方法接受四个参数:MappedStatement、Object、RowBounds 和 ResultHandler。
  • 配置拦截器
<!-- mybatis-config-spring.xml -->
<?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">
<!--mybatis核心配置文件-->
<configuration>
    <!-- 配置插件拦截器 -->
    <plugins>
        <plugin interceptor="com.xygalaxy.interceptor.ExecutorInterceptor"/>
    </plugins>
</configuration>
1
2
3
4
5
6
7
8
9
10
11
12
  • 测试
@Test
public void testExecutorInterceptor(){
    UserPO userPO = userMapper.selectAllUserList().get(0);
    log.info("Enum ==> "+userPO.getSex().getValue()+" ===>"+userPO);
}
1
2
3
4
5

插件测试结果

# 6.2、多插件执行顺序

<plugins>
    <plugin intercepter="插件1"></plugin>
    <plugin intercepter="插件2"></plugin>
</plugins>
1
2
3
4

四大对象植入插件逻辑时,是根据声明插件时的顺序从里向外一层一层的生成代理对象,反过来四大对象实际运行时,是从外向里一层一层的调用插件的逻辑。

# 7、environments(环境配置)

MyBatis 可以配置成适应多种环境,例如,开发、测试和生产环境需要有不同的配置。想要了解更多可以去看看MyBatis官网。这里比较少用,而且SpringBoot的配置不同环境更为简便。

说一下数据源(data Source)配置

<!-- default="development" 这个就是MyBatis的环境配置属性,通过id确认使用 -->
<environments default="development">
    <environment id="development">
        <!--配置事务管理-->
        <transactionManager type="JDBC"></transactionManager>
        <!--配置数据源(连接池)-->
        <dataSource type="POOLED">
            <property name="driver" value="${jdbc.driver}"/>
            <property name="url" value="${jdbc.url}"/>
            <property name="username" value="${jdbc.username}"/>
            <property name="password" value="${jdbc.password}"/>
        </dataSource>
    </environment>
</environments>
1
2
3
4
5
6
7
8
9
10
11
12
13
14

type="POOLED"

  • MyBatis有有三种内建的数据源类型(也就是 type="[UNPOOLED|POOLED|JNDI]")
  • UNPOOLED:这个数据源的实现会每次请求时打开和关闭连接。虽然有点慢,但对那些数据库连接可用性要求不高的简单应用程序来说,是一个很好的选择。 性能表现则依赖于使用的数据库,对某些数据库来说,使用连接池并不重要,这个配置就很适合这种情形。
  • POOLED:这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。 这种处理方式很流行,能使并发 Web 应用快速响应请求。
  • JNDI:这个数据源实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的数据源引用。

# 8、mappers(映射器)

<!-- 每一个mapper.xml都需要在这个配置,获取指定的Mapper.xml文件-->
<mappers>
    <mapper resource="mapper/UserDao.xml"/>
</mappers>
1
2
3
4

MyBatis查找定义的 SQL 映射语句,我们需要告诉 MyBatis 到哪里去找到这些语句。在自动查找资源方面,Java 并没有提供一个很好的解决方案,所以最好的办法是直接告诉 MyBatis 到哪里去找映射文件。

映射器查询,MyBatis提供了四种方式:

# 8.1、resource查找

<!-- 使用相对于类路径的资源引用 -->
<mappers>
  <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
  <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
  <mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
1
2
3
4
5
6

# 8.2、url查找

<!-- 使用完全限定资源定位符(URL) -->
<mappers>
  <mapper url="file:///var/mappers/AuthorMapper.xml"/>
  <mapper url="file:///var/mappers/BlogMapper.xml"/>
  <mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>
1
2
3
4
5
6

# 8.3、class查找

<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
  <mapper class="org.mybatis.builder.AuthorMapper"/>
  <mapper class="org.mybatis.builder.BlogMapper"/>
  <mapper class="org.mybatis.builder.PostMapper"/>
</mappers>
1
2
3
4
5
6

# 8.4、包名查找

<!-- 将包内的映射器接口全部注册为映射器 -->
<mappers>
  <package name="org.mybatis.builder"/>
</mappers>
1
2
3
4

# 9、Java配置方式

对于MyBatis的核心配置,我们通常会使用XML配置文件,但是也可以使用Java配置方式来进行配置。

@Configuration  // 声明配置类
@MapperScan("com.xygalaxy.mapper")   // MyBatis扫描dao层接口,也就是mapper接口
@Slf4j // 日志注解
@EnableTransactionManagement // 开启事务支持
public class MyBatisConfig {

    //定义属性 为属性注入数据(数据的来源上面引入的jdbc.properties文件)
    private static String driverClass;

    private static String url;

    private static String username;

    private static String password;

    /**
     * @PropertySource()读取不了jdbc.properties文件,没找到什么原因,换用构造读取写入
     * @throws IOException
     */
    static {
        Properties properties = new Properties();
        try {
            InputStream inputStream = Resources.getResourceAsStream("jdbc.properties");
            properties.load(inputStream);
        } catch (IOException e) {
            log.error("读取jdbc.properties文件失败",e);
        }

        driverClass = properties.getProperty("jdbc.driver");
        url = properties.getProperty("jdbc.url");
        username = properties.getProperty("jdbc.username");
        password = properties.getProperty("jdbc.password");
    }

    /**
     * 创建数据源返回数据源,Spring会自动调用该方法,并将该对象交给IOC容器管理
     * @return
     */
    @Bean
    public DataSource dataSource(){
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setDriverClassName(driverClass);
        druidDataSource.setUrl(url);
        druidDataSource.setUsername(username);
        druidDataSource.setPassword(password);
        druidDataSource.setMaxActive(100);  //连接池最大连接数
        druidDataSource.setMinIdle(20);     //连接池最小连接数
        druidDataSource.setMaxWait(1000);   //连接池超时时间
        return druidDataSource;
    }

    /**
     * 事务给Spring管理
     * @param dataSource
     * @return
     */
    @Bean
    public TransactionManager getTrans(DataSource dataSource){
        DataSourceTransactionManager trans =  new DataSourceTransactionManager();
        //设置数据源
        trans.setDataSource(dataSource);
        return trans;
    }


    /**
     * Java配置方式配置MyBatis核心配置
     * @param dataSource
     * @return
     */
    @Bean
    public SqlSessionFactoryBean sqlSessionFactoryBean2(DataSource dataSource,TransactionManager transactionManager) throws Exception {
        // 创建SqlSessionFactoryBean
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        // 数据源设置
        sqlSessionFactoryBean.setDataSource(dataSource);

        // 事务管理
        TransactionFactory transactionFactory = (TransactionFactory) transactionManager;
        sqlSessionFactoryBean.setTransactionFactory(transactionFactory);
        // 创建MyBatis核心配置中的环境
        Environment environment = new Environment("development",transactionFactory,dataSource);
        // 创建MyBatis核心配置整个Configuration
        org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration(environment);
        // 将MyBatis整个核心配置给SqlSessionFactoryBean
        sqlSessionFactoryBean.setConfiguration(configuration);

        // 配置类型别名扫描包
        sqlSessionFactoryBean.setTypeAliasesPackage("com.xygalaxy.pojo");
        // mappers(映射器)映射文件设置
        sqlSessionFactoryBean.setMapperLocations(resolveMapperLocations());
        // MyBatis核心配置xml方式读取,这里我们完全不用这种,如果用这种,可以不用后面的那些配置了,看个人喜欢哪种方式
        //sqlSessionFactoryBean.setConfigLocation(new ClassPathResource("config/mybatis-config-spring.xml"));

        // 类型转换注册器设置
        sqlSessionFactoryBean.setDefaultEnumTypeHandler(AutoEnumTypeHandler.class);

        return sqlSessionFactoryBean;
    }

    /**
     * 扫描mapper的文件,获取resources/mapper下,所有的mapper.xml文件
     * @return
     */
    public Resource[] resolveMapperLocations() {
        ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
        Resource[] mappers= null;
        try {
            mappers = resourceResolver.getResources("classpath*:mapper/*.xml");
        } catch (IOException e) {
            log.error("加总mapper文件失败");
        }
        return mappers;
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116