Mybatis学习二:xml配置详解和实践常用设置(set

mybatis.config 配置项介绍可参考官方文档:mybatis.config 官方入门手册
主要介绍和实践mybatis.config 文件中的配置参数具体是什么作用/效果。
将由易到难依次实践每个参数的效果,部分复杂的配置项可能需要结合spring框架进行实践(后续补充)。

常用设置(settings)

  • logImpl: 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。
    • STDOUT_LOGGING 控制台日志
    • NO_LOGGING 无日志
    • SLF4J
    • LOG4J
    • LOG4J2
    • DK_LOGGING
    • COMMONS_LOGGING

这个比较好理解,配置日志输出方式,没有配置的话默认输出到控制台。下面是一个使用slf4j 当门面日志, log4j 当日志实现的配置 。(对了,log4j今天被扫描出来有漏洞,记得及时修复,官方补丁 )

<!--日志框架-->
    <!--slf4j 门面日志框架包 -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.32</version>
</dependency>
    <!--slf4j 绑定log4j的中间包 -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.7.32</version>
</dependency>
    <!--log4j日志包 -->
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>
复制代码
## log4j的形式就是 logger(日志输出源对象) - 绑定 appender(日志输出目的地)

# 配置根logger ,第一个为最低级别 [ level ] , appenderName1, appenderName2
log4j.rootLogger = debug , stdout , file
# 配置日志信息输出目的地
log4j.appender.stdout = org.apache.log4j.ConsoleAppender
# 配置日志输出格式
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} - %-4r %-5p [%t] %C:%L %x - %m%n

# 配置日志信息输出目的地
log4j.appender.file = org.apache.log4j.DailyRollingFileAppender
# 配置日志输出格式
log4j.appender.file.Encoding=UTF-8
# 输出文件位置此为项目根目录下的logs文件夹中
log4j.appender.file.File=D:/java/log/test.log
log4j.appender.file.DatePattern = '_'yyyy-MM-dd'.log'
# 配置file为自定义布局模式
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %F %p %m%n
       
##日志输出目的地
#(1)org.apache.log4j.ConsoleAppender(控制台)
#(2)org.apache.log4j.FileAppender(文件)
#(3)org.apache.log4j.DailyRollingFileAppender(每天产生一个日志文件)
#(4)org.apache.log4j.RollingFileAppender(文件大小到达指定尺寸的时候产生一个新的文件)
#(5)org.apache.log4j.WriterAppender(将日志信息以流格式发送到任意指定的地方)

## 日志输出格式
#(1)org.apache.log4j.HTMLLayout(以HTML表格形式布局)
#(2)org.apache.log4j.PatternLayout(可以灵活地指定布局模式)
#(3)org.apache.log4j.SimpleLayout(包含日志信息的级别和信息字符串)
#(4)org.apache.log4j.TTCCLayout(包含日志产生的时间、线程、类别等等信息)
复制代码
  • mapUnderscoreToCamelCase : 是否开启驼峰命名自动映射

默认为关闭。开启后,比如数据库中的 own_phone 可以映射到实体对象的 ownPhone 上。

@Data
public class Student implements Serializable {

    private static final long serialVersionUID = -1774428258743097453L;

    private Integer id;

    private String name;

    private Integer age;

    private String ownPhone;
}
复制代码
CREATE TABLE `student` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `name` varchar(20) DEFAULT NULL,
  `age` int(11) DEFAULT '0',
  `own_phone` varchar(11) DEFAULT '0',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4
复制代码
  • 未开启前,查询到的ownPhone都为null
INFO  [main] com.test.MybatisApplicationRun:32  - Student(id=1, name=xiao, age=10, ownPhone=null)
INFO  [main] com.test.MybatisApplicationRun:32  - Student(id=2, name=nana, age=11, ownPhone=null)
复制代码
  • 开启后
INFO  [main] com.test.MybatisApplicationRun:32  - Student(id=1, name=xiao, age=10, ownPhone=151123456)
INFO  [main] com.test.MybatisApplicationRun:32  - Student(id=2, name=nana, age=11, ownPhone=151123435)
复制代码
  • useGeneratedKeys 允许 JDBC 支持自动生成主键,需要数据库驱动支持。如果设置为 true,将强制使用自动生成主键。 默认为 false

    效果:当需要插入的对象中主键为空,打算由数据库来生成时,在执行完插入操作后,是否将主键值回写到对象上(也就是是否可以在java代码中直接在对象上获取到对象的主键列,而不是再执行一次查询)。

    示例:

    private static void insertStudent(SqlSession sqlSession) {
        log.info("插入记录");
        Student student = new Student(null, "mapper插入",10,"151213", "中国东边");
        Long count = sqlSession.getMapper(StudentMapper.class).insertStudent(student);
        log.info("插入记录条数:{},student{}", count, student);
    }
    
    复制代码
    • 未开启时:可以看到 id 列为 null
    DEBUG [main] org.apache.ibatis.logging.jdbc.BaseJdbcLogger:143  - ==>  Preparing: insert into student(id,name,age,own_phone,address) value(?, ?, ?, ?, ?) 
    DEBUG [main] org.apache.ibatis.logging.jdbc.BaseJdbcLogger:143  - ==> Parameters: null, mapper插入(String), 10(Integer), 151213(String), 中国东边(String)
    DEBUG [main] org.apache.ibatis.logging.jdbc.BaseJdbcLogger:143  - <==    Updates: 1
    INFO  [main] com.test.MybatisApplicationRun:45  - 插入记录条数:1,studentStudent(id=null, name=mapper插入, age=10, ownPhone=151213, address=中国东边)
    复制代码
    • 开启后: 可以看到此次自动生成的主键ID值为 1236
    DEBUG [main] org.apache.ibatis.logging.jdbc.BaseJdbcLogger:143  - ==>  Preparing: insert into student(id,name,age,own_phone,address) value(?, ?, ?, ?, ?) 
    DEBUG [main] org.apache.ibatis.logging.jdbc.BaseJdbcLogger:143  - ==> Parameters: null, mapper插入(String), 10(Integer), 151213(String), 中国东边(String)
    DEBUG [main] org.apache.ibatis.logging.jdbc.BaseJdbcLogger:143  - <==    Updates: 1
    INFO  [main] com.test.MybatisApplicationRun:45  - 插入记录条数:1,studentStudent(id=1236, name=mapper插入, age=10, ownPhone=151213, address=中国东边)
    复制代码

    使用地方:

    • mybatis.config
    <setting name="useGeneratedKeys" value="false"/>
    复制代码
    • xxxMapper.xml 中具体的语句
      <insert id="insertStudentByXml"  parameterType="student"  useGeneratedKeys = true keyProperty="id">
          <selectKey resultType="int" keyProperty="id" order="BEFORE" >
              SELECT FLOOR(RAND()*100) FROM DUAL
          </selectKey>
          insert into student(id,name,age,own_phone,address)
          value(#{id}, #{name}, #{age}, #{ownPhone}, #{address})
      </insert>
    复制代码

    自动生成主键有两种方式,一种直接是数据库中自增 (比如mysql) ,第二种是使用 selectKey 标签生成主键。mysql 和 oracle 都支持第二种。

    • xxMapper.java 中具体的方法
    /**
     * 插入学生
     * @param student
     * @return
     */
    @Insert("insert into student(id,name,age,own_phone,address) value(" +
            "#{id}, #{name}, #{age}, #{ownPhone}, #{address})")
    @Options(useGeneratedKeys = true, keyProperty = "id")
    Long insertStudent(Student student);
    复制代码
    • 影响范围 和优先级:

    mybatis.config < xxMapper.xml ,不影响 xxMapper.java

    一般建议,不配置mybatis.config, 在具体的语句上开启或者关闭即可。

  • defaultExecutorType 配置默认的执行器。

SIMPLE 就是普通的执行器;
REUSE 执行器会重用预处理语句(PreparedStatement);
BATCH 执行器不仅重用语句还会执行批量更新。
如果需要使用批量更新,建议设置成 BATCH

示例:在一个sqlsession 中 ,插入 2万条记录,

- 使用xml 中的 foreach ,因为mysql每条sql 最大字节数为 1M,因此每次提交100条记录   
(一般每次提交在100是比较合理的,java自带的orm框架JPA中批量提交默认也是50)
- simple 执行耗时:1310毫秒
- REUSE 执行耗时:1150毫秒
- BATCH 执行耗时:770毫秒 

- 代码中使用fori
- simple 执行耗时:22411毫秒
- REUSE 执行耗时:21076毫秒
- BATCH 执行耗时:1524毫秒
复制代码
  • multipleResultSetsEnabled 是否允许单个语句返回多结果集

建议参看:multipleResultSetsEnabled

我的理解是,执行存储过程,可以根据不同的参数返回不同的结果集,比如结果集1 对应实体A, 结果集2对应实体B

<resultMap id="studentMap" type="com.po.vo.Student">
       <id column="id" property="id" jdbcType="INTEGER"/>
       <result column="name" property="name" jdbcType="VARCHAR"/>
       <result column="age" property="age" jdbcType="INTEGER"/>
       <result column="address" property="address" jdbcType="VARCHAR"/>
       <result column="own_phone" property="ownPhone" jdbcType="VARCHAR"/>
       <result column="birthday" property="birthday" jdbcType="DATE"/>
   </resultMap>

   <resultMap id="teacherMap" type="com.po.vo.Teacher">
       <id column="id" property="id" jdbcType="INTEGER"/>
       <result column="name" property="name" jdbcType="VARCHAR"/>
       <result column="age" property="age" jdbcType="INTEGER"/>
       <result column="address" property="address" jdbcType="VARCHAR"/>
       <result column="own_phone" property="ownPhone" jdbcType="VARCHAR"/>
       <result column="birthday" property="birthday" jdbcType="DATE"/>
   </resultMap>

   <select id="selectStudent" resultMap="studentMap, teacherMap" >
       select * from Student
 </select>
复制代码

对于mysql数据库,如果上面这样配置,可以正常查询,但是返回的结果集只能是Student类型的集合。
//TODO 后面抽时间再拔下源码

建议默认设置

  • autoMappingBehavior : 指定 MyBatis 应如何自动映射列到字段或属性。 NONE 表示关闭自动映射;PARTIAL 只会自动映射没有定义嵌套结果映射的字段。 FULL 会自动映射任何复杂的结果集(无论是否嵌套)

    默认值:PARTIAL

    • 开启
    INFO  [main] com.test.MybatisApplicationRun:38  - {"age":10,"id":1,"name":"xiao","ownPhone":"151123456"}
    INFO  [main] com.test.MybatisApplicationRun:38  - {"age":11,"id":2,"name":"nana","ownPhone":"151123435"}
    复制代码
    • 关闭
    INFO  [main] com.test.MybatisApplicationRun:38  - null
    INFO  [main] com.test.MybatisApplicationRun:38  - null
    复制代码
  • autoMappingUnknownColumnBehavior 指定发现自动映射目标未知列(或未知属性类型)的行为。默认为 NONE

    • NONE: 不做任何反应

    未发现任何异常

    • WARNING: 输出警告日志('org.apache.ibatis.session.AutoMappingUnknownColumnBehavior' 的日志等级必须设置为 WARN

    只设置 WARNING ,不配置日志logger,跟默认无区别;
    配置logger 后

      #在log4j.properties中新增自定义logger的级别   
     org.apache.ibatis.session.AutoMappingUnknownColumnBehavior = WARN
     
     日志输出:
     WARN  [main] org.apache.ibatis.session.AutoMappingUnknownColumnBehavior$2:47  - Unknown column is detected on 'com.mybatis.mapper.StudentMapper.selectStudent' auto-mapping. Mapping parameters are [columnName=address,propertyName=address,propertyType=null]
    复制代码
    • FAILING: 映射失败 (抛出 SqlSessionException)
     Cause: org.apache.ibatis.session.SqlSessionException: Unknown column is detected on 'com.mybatis.mapper.StudentMapper.selectStudent' auto-mapping. Mapping parameters are [columnName=address,propertyName=address,propertyType=null]
     at org.apache.ibatis.exceptions.ExceptionFactory.wrapException(ExceptionFactory.java:30)
     at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:149)
     at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:140)
     at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:135)
    复制代码
  • useColumnLabel : 使用列标签代替列名,默认开启

  • jdbcTypeForNull : 当没有为参数指定特定的 JDBC 类型时,空值的默认 JDBC 类型。 某些数据库驱动需要指定列的 JDBC 类型,多数情况直接用一般类型即可,比如 NULL、VARCHAR 或 OTHER。

mysql 对于null值,即使没有设置 JdbcType也能正常入库;
oracle 对于null 值,如果没有设置 JdbcType ,将无法正常入库,会报错,建议使用oracle数据库的可以设置为 NULL ,或者给每个字段都设置jdbcType ,比如 #{birthday,jdbcType=DATE}

  • shrinkWhitespacesInSql : 从SQL中删除多余的空格字符。请注意,这也会影响SQL中的文字字符串。 (新增于 3.5.5) |

  • defaultScriptingLanguage 指定动态 SQL 生成使用的默认脚本语言。

  • cacheEnabled 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存 默认为 true

  • lazyLoadingEnabled 延迟加载的全局开关

  • aggressiveLazyLoading 开启时,任一方法的调用都会加载该对象的所有延迟加载属性

  • localCacheScope MyBatis 利用本地缓存机制(Local Cache)防止循环引用和加速重复的嵌套查询。 默认值为 SESSION,会缓存一个会话中执行的所有查询。 若设置值为 STATEMENT,本地缓存将仅用于执行语句,对相同 SqlSession 的不同查询将不会进行缓存。

以上几个延迟和缓存相关的参数都是为了mybatis用来解决循环依赖的情况,具体可以查询下相关资料

无意义或无效设置

  • defaultSqlProviderType: 给类似 @SelectProvider 指定默认的type

@SelectProvider 语法如下,其实就是生成一个sql

public interface StudentMapper {

    /**
     * 演示使用注解 @SelectProvider 执行sql操作
     * @return
     */
    @SelectProvider(type = StudentMapperProvider.class, method = "getSql")
    List<Student> getStudentListByAnnation();

    class StudentMapperProvider{

        public String getSql() {
            SQL sql = new SQL();
            sql.FROM("student");
            sql.SELECT("*")
                .WHERE("1=1");
            return sql.toString();
        }
    }
}
复制代码
@Slf4j
public class MybatisApplicationRun {

    public static void main(String[] args) {
        String mybatisConfig = "mybatisConfig.xml";
        InputStream inputStream = null;
        try {
            inputStream = Resources.getResourceAsStream(mybatisConfig);
        } catch (IOException e) {
            log.error("mybatis配置文件加载出错:" + e.getMessage(), e);
        }
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        List<Student> list = sqlSession.selectList("com.mybatis.mapper.StudentMapper." + "selectStudent");
        list.forEach(i -> log.info(JSONObject.toJSONString(i, SerializerFeature.WriteNullStringAsEmpty)));
        log.info("使用@selectProvider进行数据库查询");
        List<Student> list2 = sqlSession.getMapper(StudentMapper.class).getStudentListByAnnation();
        list2.forEach(i -> log.info(JSONObject.toJSONString(i, SerializerFeature.WriteNullStringAsEmpty)));
        sqlSession.close();
    }
}
复制代码

输出日志如下:

[frame] 2021-12-12 00:30:50,447 - 459  INFO  [main] com.test.MybatisApplicationRun:35  - {"age":10,"id":1,"name":"xiao","ownPhone":"151123456"}
[frame] 2021-12-12 00:30:50,447 - 459  INFO  [main] com.test.MybatisApplicationRun:35  - {"age":11,"id":2,"name":"nana","ownPhone":"151123435"}
[frame] 2021-12-12 00:30:50,447 - 459  INFO  [main] com.test.MybatisApplicationRun:36  - 使用@selectProvider进行数据库查询
[frame] 2021-12-12 00:30:50,447 - 459  DEBUG [main] org.apache.ibatis.logging.jdbc.BaseJdbcLogger:143  - ==>  Preparing: SELECT * FROM student WHERE (1=1) 
[frame] 2021-12-12 00:30:50,447 - 459  DEBUG [main] org.apache.ibatis.logging.jdbc.BaseJdbcLogger:143  - ==> Parameters: 
[frame] 2021-12-12 00:30:50,458 - 470  DEBUG [main] org.apache.ibatis.logging.jdbc.BaseJdbcLogger:143  - <==      Total: 2
[frame] 2021-12-12 00:30:50,458 - 470  INFO  [main] com.test.MybatisApplicationRun:38  - {"age":10,"id":1,"name":"xiao","ownPhone":"151123456"}
[frame] 2021-12-12 00:30:50,458 - 470  INFO  [main] com.test.MybatisApplicationRun:38  - {"age":11,"id":2,"name":"nana","ownPhone":"151123435"}
复制代码
  • defaultStatementTimeout : 设置超时时间,它决定数据库驱动等待数据库响应的秒数。

经过测试发现,没有设置该值时,mysql的默认超时在4秒 左右,设置该值为1秒 或者 10秒 等没有太大的影响。用的是mysql5.7.24 , oracle 没有试验

[frame] 2021-12-12 01:20:22,110 - 181  DEBUG [main] org.apache.ibatis.transaction.jdbc.JdbcTransaction:136  - Opening JDBC Connection
[frame] 2021-12-12 01:20:26,240 - 4311 ERROR [main] com.test.MybatisApplicationRun:38  - 
### Error querying database.  Cause: com.mysql.cj.jdbc.exceptions.CommunicationsException: Communications link failure
复制代码
  • vfsImpl : 指定 VFS 的实现 (无意义)

  • configurationFactory : 指定一个提供 Configuration 实例的类

一般不这么写,直接生成一个configuration Bean即可

  • useActualParamName : 允许使用方法签名中的名称作为语句参数名称。 为了使用该特性,你的项目必须采用 Java 8 编译,并且加上 -parameters 选项。(新增于 3.4.1)默认 true (无意义)

  • logPrefix : 指定 MyBatis 增加到日志名称的前缀。 (无效)

  • defaultEnumTypeHandler : 指定 Enum 使用的默认 TypeHandler (无意义,一般都不允许数据库中使用枚举值类型)

  • defaultScriptingLanguage : 指定动态 SQL 生成使用的默认脚本语言。 (使用默认即可)