Java -Mybatis的全局配置
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&useUnicode=true&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 引入日志
- 首先,加入
LOG4J依赖
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
- 设置
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)
- 然后在
mybatis-config.xml中加入以下内容:
<settings>
<setting name="logImpl" value="org.apache.log4j.Logger"/>
</settings>
3.2 驼峰自动映射
驼峰自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。
例如,在数据库中,经常会见到create_time和update_time这类列名,
但是在实体类中,编写属性时,我们习惯于使用驼峰命名法,即createTime和updateTime。
这样子属性和数据库字段不匹配,那我们也可以将属性改为和字段一致的命名,但是在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
以前实现分页功能,需要
- findAll() 查询全部数据
- count(*) 查询数据总数目
- findAll(int pageNo,int pageSize) 设置页数,页面大小
现在使用分页插件后,只需要编写正常的sql语句即可,插件会自动进行分页操作
分页插件的使用步骤:
- 引入分页插件依赖
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.3.3</version>
</dependency>
- 全局配置文件中使用插件
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor"/>
</plugins>
- 在查询时使用分页插件
@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&useUnicode=true&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的
- 首先,引入依赖:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.8</version>
</dependency>
- 配置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 表示不回收空闲连接
- 替换原有的连接池类型
需要创建一个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类在同一个包下,并且文件名必须一致。