Mybatis的全局配置

1. 配置

MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。 配置文档的顶层结构如下:

  • configuration(配置)

  • properties(属性)

  • settings(设置)

  • typeAliases(类型别名)

  • typeHandlers(类型处理器)

  • objectFactory(对象工厂)

  • plugins(插件)

  • environments(环境配置)

    • environment(环境变量)
    • transactionManager(事务管理器)
    • dataSource(数据源)
  • databaseIdProvider(数据库厂商标识)

  • mappers(映射器)

2. properties(属性)

属性可以在外部进行配置,并可以进行动态替换。典型的就是将外部配置的属性,在配置文件中使用${}进行动态替换

首先,在resources目录下创建jdbc.properties文件(文件名可以自己定义),并添加以下内容:

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/book?useSSL=false
username=root
password=

,然后在mybatis-config.xml文件中添加以下内容:

<properties resource="jdbc.properties"/>

接着将${driver}${url}${username}${password}替换为jdbc.properties文件中的属性值。

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
<!--                <property name="driver" value="com.mysql.jdbc.Driver"/>-->
<!--                <property name="url" value="jdbc:mysql://localhost:3306/book?useSSL=false&amp;useUnicode=true&amp;ucharacterEncoding=utf8"/>-->
<!--                <property name="username" value="root"/>-->
<!--                <property name="password" value=""/>-->
                <!-- 分割线,上面是原来的内容 下面是使用了动态替换后的写法 -->
                <property name="driver" value="${driver}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>

tip:需要注意的是,使用properties标签引入外部属性文件时,需要注意标签的位置,注意顺序,否则会报错

3. settings(设置)

这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。 下表描述了设置中各项设置的含义、默认值等。

具体设置详细介绍查看官方文档

常用的主要使用的是:

  • cacheEnabled(是否启用二级缓存), 默认为true

  • mapUnderscoreToCamelCase(是否开启驼峰命名规则),默认为false

  • logImpl(日志实现类)

3.1 引入日志

  1. 首先,加入LOG4J依赖
    <dependency>
      <groupId>log4j</groupId>
      <artifactId>log4j</artifactId>
      <version>1.2.12</version>
    </dependency>
  1. 设置LOG4J的配置文件,在resources目录下创建log4j.properties
# 全局日志配置
# ERROR是级别,从低到高依次是:DEBUG,INFO,WARN,ERROR,FATAL  日志从详细到简单  
# stdout: standard output 标准输出,其实就是 输出到控制台
log4j.rootLogger=ERROR, stdout
# MyBatis 日志配置
log4j.logger.org.example.mapper.BookInfoMapper=DEBUG

# 控制台输出
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n

这样子配置以后,对于其他类日志只打印错误信息,对于BookInfoMapper类日志打印DEBUG信息,如下:

DEBUG [main] - ==>  Preparing: select * from bookinfo where id = ?
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <==      Total: 1
BookInfo(id=1, name=道听途说, author=何金银, desc=都市异闻、悬疑灵异、神秘文化、中式恐怖……15个让你忘记呼吸的精彩故事。惊喜收录神秘篇章《爱你,林西》。亲眼所见,未必真实。道听途说,未必虚假。, price=29.7)
  1. 然后在mybatis-config.xml中加入以下内容:
<settings>
    <setting name="logImpl" value="org.apache.log4j.Logger"/>
</settings>

3.2 驼峰自动映射

驼峰自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。

例如,在数据库中,经常会见到create_timeupdate_time这类列名,
但是在实体类中,编写属性时,我们习惯于使用驼峰命名法,即createTimeupdateTime
这样子属性和数据库字段不匹配,那我们也可以将属性改为和字段一致的命名,但是在java中,下划线常用于静态常量,用来命名属性,是不符合规范的,所以mybatis提供了驼峰自动映射的功能,用来自动将数据库中的create_time这类字段使用驼峰命名法进行映射,使得属性可以正常编写为createTime

那么如何进行设置呢?

mybatis-config.xml中加入以下内容:

<settings>
    <setting name="mapUnderscoreToCamelCase" value="false"/>
</settings>

4. typeAliases(类型别名)

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

例如,在映射文件mapper文件中,resultType返回的类型,总是使用的全限定类名,例如org.example.model.BookInfo

    <select id="findBookInfoByMap" resultType="org.example.model.BookInfo">
        select * from bookinfo where name like concat('%',#{nameKey},'%')
                                 and author like concat('%',#{authorKey},'%')
    </select>

这样子,书写起来非常冗余,所以我们可以为org.example.model.BookInfo设置一个别名,在mybatis-config.xml中加入以下内容:

    <typeAliases>
        <!-- 第一种,单独为类进行设置别名 -->
        <typeAlias type="org.example.model.BookInfo" alias="BookInfo"/>
        <!--        第二种,为包下的类自动设置别名 每一个在包 org.example.model 中的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。 例如 包下的 BookInfo 没有使用注解@Alias,则它的别名为 bookInfo。如果使用了注解,则以注解编写的别名为准-->
        <!-- <package name="org.example.model"/> -->
    </typeAliases>

如果想使用了包下的自动设置别名,并且使用注解设置别名,可以这样子写,但是要注意设置了别名,对应mapper文件中也应该一致

/**
 * 注解配置 别名为Book  对应的mapper文件中应该也使用Book
 *
 * @Alias("Book")
 */
package org.example.model;

import lombok.Data;
import org.apache.ibatis.type.Alias;

/**
 * 注解配置 别名为Book  对应的mapper文件中应该也使用Book
 *
 * @Alias("Book")
 */
@Data
public class BookInfo {
    private Integer id;
    private String name;
    private String author;
    private String desc;
    private Double price;
}

接下来,再将mapper文件中使用到的全限定类名替换为配置的别名,

    <select id="findBookInfoById" resultType="BookInfo">
        select * from bookinfo where id = #{id}
    </select>

在测试类中进行测试输出

输出:

DEBUG [main] - ==>  Preparing: select * from bookinfo where id = ?
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <==      Total: 1
BookInfo(id=1, name=道听途说, author=何金银, desc=都市异闻、悬疑灵异、神秘文化、中式恐怖……15个让你忘记呼吸的精彩故事。惊喜收录神秘篇章《爱你,林西》。亲眼所见,未必真实。道听途说,未必虚假。, price=29.7)

5. Plugins(插件)

插件就是对有用功能的扩展,mybatis插件的原理就是拦截mybatis的执行过程,在执行sql之前或之后加入自己的功能,比如分页插件,性能分析插件等。

我们现在不再制作插件,现在学习经常使用的分页插件-PageHelper

以前实现分页功能,需要

  1. findAll() 查询全部数据
  2. count(*) 查询数据总数目
  3. findAll(int pageNo,int pageSize) 设置页数,页面大小

现在使用分页插件后,只需要编写正常的sql语句即可,插件会自动进行分页操作
分页插件的使用步骤:

  1. 引入分页插件依赖
    <dependency>
      <groupId>com.github.pagehelper</groupId>
      <artifactId>pagehelper</artifactId>
      <version>5.3.3</version>
    </dependency>
  1. 全局配置文件中使用插件
    <plugins>
        <plugin interceptor="com.github.pagehelper.PageInterceptor"/>
    </plugins>
  1. 在查询时使用分页插件
    @Test
public void testFindAllBookInfo(){
    SqlSession session = null;
    try {
        // 获取SqlSession对象
        session = MyBatisUtil.getSqlSession();

        BookInfoMapper mapper = session.getMapper(BookInfoMapper.class);
        //在查询前先设置  参数一  当前页  参数二   每页  数据显示的条数
        PageHelper.startPage(1,4);


        // 调用BookInfoMapper中的findAllBookInfo方法,查询全部图书信息
        List<BookInfo> bookInfoList = mapper.findAllBookInfo();
        System.out.println(bookInfoList);
    } finally {
        // 关闭SqlSession对象,释放资源
        MyBatisUtil.closeSqlSession(session);
    }
}

输出第一页:

DEBUG [main] - ==>  Preparing: SELECT count(0) FROM bookinfo
DEBUG [main] - ==> Parameters: 
DEBUG [main] - <==      Total: 1
DEBUG [main] - ==>  Preparing: select * from bookinfo LIMIT ?
DEBUG [main] - ==> Parameters: 4(Integer)
DEBUG [main] - <==      Total: 4
Page{count=true, pageNum=1, pageSize=4, startRow=0, endRow=4, total=10, pages=3, reasonable=false, pageSizeZero=false}[BookInfo(id=1, name=道听途说, author=何金银, desc=都市异闻、悬疑灵异、神秘文化、中式恐怖……15个让你忘记呼吸的精彩故事。惊喜收录神秘篇章《爱你,林西》。亲眼所见,未必真实。道听途说,未必虚假。, price=29.7), BookInfo(id=2, name=第十三位陪审员, author=史蒂夫·卡瓦纳, desc=金BI首奖得主史蒂夫卡瓦纳法庭推理神作。如果无法击败主宰者,那就成为他。连环谋杀,一场与时间博弈的竞赛,杀人只是游戏的开端。, price=21.6), BookInfo(id=3, name=一本关于我们的书, author=李晔, desc=精装全彩,有趣、有用、有心!感情升温神器,炙手可热的DIY创意礼物。世界那么大,很幸运我和你成为“我们”。手残党的福音,手艺人的乐园,跟着提示,一笔一画写成专属于你和TA的书。, price=32.8), BookInfo(id=4, name=自成人间, author=季羡林, desc=感动中国获奖人物,东方世界文学巨擘。白岩松、金庸、贾平凹、林青霞极力推崇。收录多篇入选语文教材、中高考阅读佳作,央视《朗读者》一读再读。恰似人间惊鸿客,只等秋风过耳边。, price=22.5)]

可以看到,我们只编写了正常的sql语句,插件自动进行了查询数据数量,然后根据我们设置的PageHelper.startPage(1,4);对数据进行分页,根据结果可以看到,总共有十条数据,我们设置的每页显示4条,所以共有三页,插件自动进行了分页操作,只返回了第一页的数据。

那我们接下来输出最后一页的两条数据:

DEBUG [main] - ==>  Preparing: SELECT count(0) FROM bookinfo
DEBUG [main] - ==> Parameters: 
DEBUG [main] - <==      Total: 1
DEBUG [main] - ==>  Preparing: select * from bookinfo LIMIT ?, ?
DEBUG [main] - ==> Parameters: 8(Long), 4(Integer)
DEBUG [main] - <==      Total: 2
Page{count=true, pageNum=3, pageSize=4, startRow=8, endRow=12, total=10, pages=3, reasonable=false, pageSizeZero=false}[BookInfo(id=12, name=我与地坛, author=史铁生, desc=2024年百班千人寒假书单 九年级推荐阅读, price=17.5), BookInfo(id=13, name=我与地坛, author=史铁生, desc=2024年百班千人寒假书单 九年级推荐阅读, price=17.5)]

也可以观察到,插件自动完成了数据的筛选,只返回了最后两条数据。

还可以使用方法获得更多的分页信息,如下:

@Test
    public void testFindAllBookInfo(){
        SqlSession session = null;
        try {
            // 获取SqlSession对象
            session = MyBatisUtil.getSqlSession();

            BookInfoMapper mapper = session.getMapper(BookInfoMapper.class);
            //在查询前先设置  参数一  当前页  参数二   每页  数据显示的条数
            PageHelper.startPage(1,4);
            // 调用BookInfoMapper中的findAllBookInfo方法,查询全部图书信息
            List<BookInfo> bookInfoList = mapper.findAllBookInfo();
            PageInfo<BookInfo> pageInfo = new PageInfo<BookInfo>(bookInfoList);
            System.out.println("获取分页数据,页面数量:" + pageInfo.getPages());
            System.out.println("获取分页数据,数据总条数:" + pageInfo.getTotal());
            System.out.println("获取分页数据,页面大小:" + pageInfo.getPageSize());
            //            System.out.println(bookInfoList);
        } finally {
            // 关闭SqlSession对象,释放资源
            MyBatisUtil.closeSqlSession(session);
        }
    }

输出

DEBUG [main] - ==>  Preparing: SELECT count(0) FROM bookinfo
DEBUG [main] - ==> Parameters: 
DEBUG [main] - <==      Total: 1
DEBUG [main] - ==>  Preparing: select * from bookinfo LIMIT ?
DEBUG [main] - ==> Parameters: 4(Integer)
DEBUG [main] - <==      Total: 4
获取分页数据,页面数量:3
获取分页数据,数据总条数:10
获取分页数据,页面大小:4 

可以看到,输出了分页的详细信息,如页面总数量,数据总条数,页面大小等等,还有很多方法,可以通过new PageInfo()方法获得更多分页信息。

6. environments 环境配置

<!--    环境配置-->
<!--    使用 default指定环境的id  一般dev就是开发环境  还有测试环境,生产环境等-->
    <environments default="dev">
        <environment id="dev">
<!--            事务管理,一般使用jdbc就可以  因为jdbc提供了 提交和回滚-->
            <transactionManager type="JDBC"/>
<!--            这个连接池是mybatis自己提供的 -->
<!--         数据源   POOLED– 也就是数据库连接池,避免了创建新的连接实例时所必需的初始化和认证时间。 -->
            <dataSource type="POOLED">
<!--                <property name="driver" value="com.mysql.jdbc.Driver"/>-->
<!--                <property name="url" value="jdbc:mysql://localhost:3306/book?useSSL=false&amp;useUnicode=true&amp;ucharacterEncoding=utf8"/>-->
<!--                <property name="username" value="root"/>-->
<!--                <property name="password" value=""/>-->
                <property name="driver" value="${driver}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>

以上使用的连接池技术是使用的mybatis自己提供的,接下来将数据源替换为alibaba的

  1. 首先,引入依赖:
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.2.8</version>
    </dependency>
  1. 配置jdbc.properties文件
# 数据库驱动类
jdbc.driver=com.mysql.jdbc.Driver
# MySQL JDBC驱动的全限定类名

# 数据库连接URL
jdbc.url=jdbc:mysql://localhost:3306/book?useSSL=false
# 数据库连接的URL,包括主机地址、端口号、数据库名和连接参数
# localhost:3306 是MySQL服务器的地址和端口
# book 是数据库名
# useSSL=false 表示不使用SSL连接

# 数据库用户名
jdbc.username=root
# 连接数据库的用户名

# 数据库密码
jdbc.password=
# 连接数据库的密码,这里为空,表示没有密码

# 初始连接池大小
jdbc.initialSize=5
# 连接池初始化时创建的连接数

# 最大活跃连接数
jdbc.maxActive=20
# 连接池中最大活跃连接数,超过这个数量的请求会被排队等待

# 最小空闲连接数
jdbc.minIdle=3
# 连接池中最小空闲连接数,低于这个数量时会创建新的连接

# 获取连接的最大等待时间
jdbc.maxWait=0
# 当连接池中的连接都被占用时,等待获取连接的最大时间(毫秒)
# 0 表示无限等待

# 连接池维护线程的运行间隔时间
jdbc.timeBetweenEvictionRunsMillis=0
# 连接池维护线程检查连接的有效性的频率(毫秒)
# 0 表示不运行维护线程

# 连接在池中最小生存时间
jdbc.minEvictableIdleTimeMillis=0
# 连接在池中空闲的最长时间(毫秒),超过这个时间的连接会被回收
# 0 表示不回收空闲连接

  1. 替换原有的连接池类型

需要创建一个AlibabaDruidDataSourceFactory类,org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory 可被用作父类来构建新的数据源适配器

package org.example.util;

import com.alibaba.druid.pool.DruidDataSource;
import org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory;


public class AlibabaDruidDataSourceFactory  extends UnpooledDataSourceFactory {
    public AlibabaDruidDataSourceFactory() {
        //创建阿里巴巴的连接池
        this.dataSource = new DruidDataSource();
    }
}

接下来修改数据源:

<!--            使用自写的阿里巴巴的连接池-->
            <dataSource type="org.example.util.AlibabaDruidDataSourceFactory">
                <property name="driverClass" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>

注意这里的driverClass,这是阿里巴巴的数据源要求的,必须为这个,更改为其他的会发生报错,如刚开始使用的是默认的driver,则发生报错如下:

Caused by: java.lang.ExceptionInInitializerError: Exception org.apache.ibatis.exceptions.PersistenceException: 
### Error building SqlSession.
### The error may exist in SQL Mapper Configuration
### Cause: org.apache.ibatis.builder.BuilderException: Error parsing SQL Mapper Configuration. Cause: org.apache.ibatis.reflection.ReflectionException: Could not set property 'driver' of 'class com.alibaba.druid.pool.DruidDataSource' with value 'com.mysql.jdbc.Driver' Cause: java.lang.IllegalArgumentException: argument type mismatch [in thread "main"]
	at org.apache.ibatis.exceptions.ExceptionFactory.wrapException(ExceptionFactory.java:30)
	at org.apache.ibatis.session.SqlSessionFactoryBuilder.build(SqlSessionFactoryBuilder.java:82)
	at org.apache.ibatis.session.SqlSessionFactoryBuilder.build(SqlSessionFactoryBuilder.java:66)
	at org.example.util.MyBatisUtil.<clinit>(MyBatisUtil.java:19)
	at org.example.test.TestMybatis.testFindAllBookInfo(TestMybatis.java:49)
	... 27 more

7. mappers 映射器

既然 MyBatis 的行为已经由上述元素配置完了,我们现在就要来定义 SQL 映射语句了。 但首先,我们需要告诉 MyBatis 到哪里去找到这些语句。 在自动查找资源方面,Java 并没有提供一个很好的解决方案,所以最好的办法是直接告诉 MyBatis 到哪里去找映射文件。 你可以使用相对于类路径的资源引用,或完全限定资源定位符(包括 file:/// 形式的 URL),或类名和包名等。例如:

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


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

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

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

在实际开发中,随着功能和文件的增多,上面三种方法都会随着文件增多而变得更加臃肿,所以经常使用指定包的位置,来自动获得映射文件

    <mappers>
<!--        表明xml文件所在的位置-->
<!--        这种写法,会随着功能变多,加载文件的增多,代码会变得更加臃肿,所以不使用这个,
而是将mapper文件放在mapper目录下,和mapper接口同级 这时使用package指定映射文件包所在,将自动映射包下的mapper文件-->
<!--        <mapper resource="BookInfoMapper.xml"/>-->
        <package name="org.example.mapper"/>
    </mappers>

修改完配置文件后,再将映射文件移动到mapper目录下,这时候在运行

org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): org.example.mapper.BookInfoMapper.findBookInfoById

	at org.apache.ibatis.binding.MapperMethod$SqlCommand.<init>(MapperMethod.java:229)
	at org.apache.ibatis.binding.MapperMethod.<init>(MapperMethod.java:53)
	at org.apache.ibatis.binding.MapperProxy.lambda$cachedInvoker$0(MapperProxy.java:96)
	at java.base/java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1708)
...
...

发现报错了,org.apache.ibatis.binding.BindingException: Invalid bound statement,我们可以查看target文件下,发现mapper文件并不存在,这是因为idea的问题,这个时候我们需要在pom文件中添加

  <build>
<!--    在IDEA的MAVEN项目中,默认源代码(java)目录下的xml等资源文件并不在编译的时候y一块打包进classes文件夹,-->
<!--    而是直接舍弃掉,为了解决这个问题,有两种方法-->
<!--    方法1 将xml或者properties等配置文件放在resources下,并修改配置文件的代码,比如说注册xml文件的位置-->
<!--    方法2 在maven中添加过滤,将java下的xml进行过 滤-->
    <resources>
      <resource>
        <directory>src/main/java</directory>
        <includes>
<!--          默认  新添加自定义则失效-->
          <include>*.xml</include>
<!--          新添加 */代表一级目录 **/代表多级目录-->
          <include>**/*.xml</include>
        </includes>
        <filtering>true</filtering>
      </resource>
    </resources>
  </build>

这个时候在运行测试,可以看到,正常输出

DEBUG [main] - ==>  Preparing: select * from bookinfo where id = ?
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <==      Total: 1
BookInfo(id=1, name=道听途说, author=何金银, desc=都市异闻、悬疑灵异、神秘文化、中式恐怖……15个让你忘记呼吸的精彩故事。惊喜收录神秘篇章《爱你,林西》。亲眼所见,未必真实。道听途说,未必虚假。, price=29.7)

tip:推荐使用指定包来获取映射文件的方式,但是需要注意的是,mapper映射文件必须与mapper类在同一个包下,并且文件名必须一致。