一、简介
Mybatis是什么
- MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架
- MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集
- MyBatis 可以使用简单的XML或注解用于配置和原始映射,将接口和Java的POJO映射成数据库中的记录
MyBatis历史
- 原是Apache的一个开源项目iBatis, 2010年6月这个项目由Apache Software Foundation 迁移到了Google Code,随着开发团队转投Google Code旗下, iBatis3.x正式更名为MyBatis ,代码于2013年11月迁移到Github。
- iBatis一词来源于“internet”和“abatis”的组合,是一个基于Java的持久层框架。 iBatis提供的持久层框架包括SQL Maps和Data Access Objects
使用MyBatis原因
-
JDBC
- SQL夹在Java代码块里,耦合度高导致硬编码内伤
- 维护不易且实际开发需求中sql是有变化,频繁修改的情况多见
-
Hibernate和JPA
- 长难复杂SQL,对于Hibernate而言处理也不容易
- 内部自动生产的SQL,不容易做特殊优化
- 基于全映射的全自动框架,大量字段的POJO进行部分映射时比较困难,导致数据库性能下降
-
MyBatis是一个半自动化的持久化层框架
- 对开发人员而言,核心sql还是需要自己优化
- sql和java编码分开,功能边界清晰,一个专注业务、一个专注数据
二、示例
示例代码
1.创建示例数据库及表
CREATE SCHEMA `mybatis` ;
USE `mybatis`;
DROP TABLE IF EXISTS `tbl_employee`;
CREATE TABLE `tbl_employee` (
`id` int NOT NULL AUTO_INCREMENT,
`last_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL,
`gender` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL,
`email` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb3;
2.创建Java项目,并将依赖jar引入
3.创建全局配置文件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">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/><!-- 单独使用时配置成MANAGED没有事务 -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis?useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="password"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="EmployeeMapper.xml"/>
</mappers>
</configuration>
4.创建SQL映射文件EmployeeMapper.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.xiang.dao.EmployeeMapper">
<select id="selectEmp" resultType="com.xiang.bean.Employee">
select id, last_name as lastName, gender, email from tbl_employee where id = #{id}
</select>
</mapper>
5.根据mybatis-config.xml创建SqlSessionFactory对象
//创建SqlSessionFactory
private SqlSessionFactory getSqlSessionFactory() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
return new SqlSessionFactoryBuilder().build(inputStream);
}
6.创建SqlSession对象
//获取SqlSession
SqlSession openSession = sqlSessionFactory.openSession();
7.执行查询
方式一:根据SQL直接查询
@Test
public void test1() throws IOException {
//创建SqlSessionFactory
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
//获取SqlSession
try (SqlSession openSession = sqlSessionFactory.openSession()) {
//执行语句
Employee employee = openSession.selectOne("com.xiang.dao.EmployeeMapper.selectEmp", 1);
System.out.println(employee);
}
}
方式二:接口式编程
@Test
public void test2() throws IOException {
//创建SqlSessionFactory
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
//获取SqlSession
try (SqlSession openSession = sqlSessionFactory.openSession()) {
//执行语句
EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
Employee employee = mapper.selectEmp(1);
System.out.println(employee);
}
}
三、Mybatis全局配置文件
MyBatis 的配置文件包含了影响 MyBatis 行为甚深的设置( settings)和属性( properties)信息。文档的顶层结构如下:
- configuration 配置
- properties 属性
- settings 设置
- typeAliases 类型命名
- typeHandlers 类型处理器
- objectFactory 对象工厂
- plugins 插件
- environments 环境
- environment 环境变量
- transactionManager 事务管理器
- dataSource 数据源
- environment 环境变量
- databaseIdProvider 数据库厂商标识
- mappers 映射器
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">
<configuration>
<!--1.properties可引入外部properties配置文件-->
<!--resource引入项目文件 url引入网络文件-->
<properties resource="dbConfig.properties"/>
<!--2.settings 设置一些配置项值-->
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
<!--3.typeAliases 为Java类起别名-->
<typeAliases>
<!--指定类及别名-->
<typeAlias type="com.xiang.bean.Employee" alias="employee" />
<!--指定包下的别名,别名默认为类名小写-->
<package name="com.xiang.bean" />
<!--还可以通过@Alias注解为类起别名-->
</typeAliases>
<!--4.配置连接环境,通过default决定启用哪个环境-->
<environments default="development">
<environment id="development">
<!--transactionManager决定使用哪个事务管理器, JDBC是org.apache.ibatis.session.Configuration中配置的别名-->
<transactionManager type="JDBC"/><!-- 单独使用时配置成MANAGED没有事务 -->
<!--type决定使用哪种连接迟-->
<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>
<mappers>
<mapper resource="EmployeeMapper.xml"/>
</mappers>
</configuration>
3.1、properties 引入外部配置文件
mybatis-config.xml
<properties resource ="dbconfig.properties"></properties>
dbconfig.properties
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/mybatis?useSSL=false
jdbc.username=root
jdbc.password=password
如果属性在不只一个地方进行了配置,那么 MyBatis 将按照下面的顺序来加载:
- 在 properties 元素体内指定的属性首先被读取。
- 然后根据 properties 元素中的 resource 属性读取类路径下属性文件或根据 url 属性指定的路径读取属性文件,并覆盖已读取的同名属性。
- 最后读取作为方法参数传递的属性,并覆盖已读取的同名属性。
3.2、settings 设置属性值
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
可以设置的参数
设置参数 | 描述 | 有效值 | 默认值 |
---|---|---|---|
cacheEnabled | 该配置影响的所有映射器中配置的缓存的全局开关。 | true/false | TRUE |
lazyLoadingEnabled | 延迟加载的全局开关。当开启时。所有关联对象都会延迟加载。特定关联关系中可通过设置fetchType属性来覆盖该项的开关状态。 | true/false | FALSE |
useColumnLabel | 使用列标签代替列名。不同的驱动在这方面会有不同的表现,具体可参考相关驱动文档或通过测试这两种不同的模式来观察所用驱动的结果。 | true/false | TRUE |
defaultStatementTimeout | 设置超时时间,它决定驱动等待数据库响应的秒数。 | Any positive integer | Not Set (null) |
mapUnderscoreToCamelCase | 是否开启自动驼峰命名规则( camel case )映射即从经典数据库列名A_ COLUMN到经典Java属性名aColumn的类似映射 | true/false | FALSE |
3.3、typeAliases 类型别名
为了降低冗余的全限定类名,可为Java 类型设置一个缩写名字。
方式一:
为单个类设置别名
<configuration>
...
<typeAliases>
<typeAlias alias="author" type="domain.blog.Author"/>
<typeAlias alias="blog" type="domain.blog.Blog"/>
</typeAliases>
方式二:
为整个包的类设置别名。别名默认为类名小写。
<configuration>
...
<typeAliases>
<package name="domain.blog"/>
</typeAliases>
方式三:
直接通过@Alias注解为类起别名
@Alias("author")
public class Author {
...
}
值得注意的是, MyBatis已经为许多常见的 Java 类型内建了相应的类型别名。它们都是大小写不敏感的,起别名的时候千万不要占用已有的别名。
3.4、typeHandlers 类型处理器
类型处理器(typeHandlers)
无论是 MyBatis 在预处理语句( PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时,都会用类型处理器将获取的值以合适的方式转换成 Java 类型。
日期类型的处理
日期和时间的处理, JDK1.8以前一直是个头疼的问题。我们通常使用JSR310规范领导者Stephen Colebourne创建的Joda-Time来操作。
1.8已经实现全部的JSR310规范了。
MyBatis3.4以前的版本需要我们手动注册这些处理器,以后的版本都是自动注册的。
<typeHandlers>
<typeHandler handler="org.apache.ibatis.type.InstantTypeHandler" />
<typeHandler handler="org.apache.ibatis.type.LocalDateTimeTypeHandler" />
<typeHandler handler="org.apache.ibatis.type.LocalDateTypeHandler" />
<typeHandler handler="org.apache.ibatis.type.LocalTime TypeHandler" />
<typeHandler handler="org.apache.ibatis.type.0ffsetDateTimeTypeHandler" />
<typeHandler handler="org.apache.ibatis.type.OffsetTimeTypeHandler" />
<typeHandler handler="org.apache.ibatis.type.ZonedDateTimeTypeHandler" />
<typeHandler handler="org.apache.ibatis.type.YearTypeHandler" />
<typeHandler handler="org.apache.ibatis.type.MonthTypeHandler" />
<typeHandler handler="org.apache.ibatis.type.YearMonthTypeHandler" />
<typeHandler handler="org.apache.ibatis.type.JapaneseDateTypeHandler" />
</typeHandlers>
自定义类型处理器
我们可以重写类型处理器或创建自己的类型处理器来处理不支持的或非标准的类型。
- 实现org.apache.ibatis.type.TypeHandler接口或者继承org.apache.ibatis.type.BaseTypeHandler
- 指定其映射某个JDBC类型(可选操作)
- 在mybatis全局配置文件中注册
3.5、plugins 插件
插件是MyBatis提供的一个非常强大的机制,我们可以通过插件来修改MyBatis的一些核心行为。 插件通过动态代理机制,可以介入四大对象的任何一个方法的执行。
了解mybatis运行原理才能更好开发插件。
- Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
- ParameterHandler (getParameterObject, setParameters)
- ResultSetHandler (handleResultSets, handleOutputParameters)
- StatementHandler (prepare, parameterize, batch, update, query)
3.6、environments 环境
MyBatis可以配置多种环境,比如开发、测试和生产环境需要有不同的配置。
每种环境使用一个environment标签进行配置并指定唯一标识符
可以通过environments标签中的default属性指定一个环境的标识符来快速的切换环境
environment-指定具体环境
- id:指定当前环境的唯一标识
- transactionManager、和dataSource都必须有
<environments default="dev_mysql">
<environment id="dev_mysql">
<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>
...
transactionManager
- type: JDBC | MANAGED | 自定义
- JDBC:使用了 JDBC 的提交和回滚设置,依赖于从数据源得到的连接来管理事务范围。JdbcTransactionFactory
- MANAGED:不提交或回滚一个连接、让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。 ManagedTransactionFactory
- 自定义:实现TransactionFactory接口, type=全类名/别名
dataSource
- type: UNPOOLED | POOLED | JNDI | 自定义
- UNPOOLED:不使用连接池,
- UnpooledDataSourceFactory
- POOLED:使用连接池, PooledDataSourceFactory
- JNDI: 在EJB 或应用服务器这类容器中查找指定的数据源
- 自定义:实现DataSourceFactory接口,定义数据源的获取方式。
实际开发中我们使用Spring管理数据源,并进行事务控制的配置来覆盖上述配置。
3.7、databaseIdProvider 数据库厂商标志
- MyBatis 可以根据不同的数据库厂商执行不同的语句。
<databaseIdProvider type="DB_VENDOR">
<property name="SQL Server" value="sqlserver"/>
<property name="DB2" value="db2"/>
<property name="Oracle" value="oracle" />
<property name="MySQL" value="mysql" />
</databaseIdProvider>
- Type: DB_VENDOR 使用MyBatis提供的VendorDatabaseIdProvider解析数据库厂商标识。也可以实现DatabaseIdProvider接口来自定义。
- Property-name:数据库厂商标识
- Property-value:为标识起一个别名,方便SQL语句使用databaseId属性引用
DB_VENDOR -会通过 DatabaseMetaData#getDatabaseProductName() 返回的字符串进行设置。由于通常情况下这个字符串都非常长而且相同产品的不同版本会返回不同的值,所以最好通过设置属性别名来使其变短
- databaseId属性在映射xml使用
<select id="getEmpById" resultType="com.lun.c01.helloworld.bean.Employee"
databaseId="mysql">
select * from employee where id = #{id}
</select>
<select id="getEmpById" resultType="com.lun.c01.helloworld.bean.Employee"
databaseId="oracle">
select e.* from employee e where id = #{id}
</select>
- 通过切换数据库,便能切换SQL
MyBatis匹配规则如下:
- 如果没有配置databaseIdProvider标签,那么databaseId=null
- 如果配置了databaseIdProvider标签,使用标签配置的name去匹配数据库信息,匹配上设置databaseId=配置指定的值,否则依旧为null
- 如果databaseId不为null,他只会找到配置databaseId的sql语句
- MyBatis 会加载不带 databaseId 属性和带有匹配当前数据库 databaseId 属性的所有语句。如果同时找到带有 databaseId 和不带 databaseId 的相同语句, 则后者会被舍弃。
3.8、mapper 映射文件
在自动查找资源方面,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>
<!-- 使用映射器接口实现类的完全限定类名 -->
<!--
注册接口
class:引用(注册)接口,
1、有sql映射文件,映射文件名必须和接口同名,并且放在与接口同一目录下;
2、没有sql映射文件,所有的sql都是利用注解写在接口上;
推荐:
比较重要的,复杂的Dao接口我们来写sql映射文件
不重要,简单的Dao接口为了开发快速可以使用注解;
-->
<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>
这些配置会告诉 MyBatis 去哪里找映射文件
四、映射文件-增删改查
Mybatis映射文件
映射文件指导着MyBatis如何进行数据库增删改查,有着非常重要的意义;
- cache –命名空间的二级缓存配置
- cache-ref – 其他命名空间缓存配置的引用
- resultMap – 自定义结果集映射
- sql –抽取可重用语句块。
- insert – 映射插入语句
- update – 映射更新语句
- delete – 映射删除语句
- select – 映射查询语句
五、缓存
MyBatis官方文档
MyBatis 包含一个非常强大的查询缓存特性,它可以非常方便地配置和定制。缓存可以极大的提升查询效率。
MyBatis系统中默认定义了两级缓存,一级缓存和二级缓存。
- 默认情况下,只有一级缓存( SqlSession级别的缓存,也称为本地缓存)开启。
- 二级缓存需要手动开启和配置,它是基于namespace级别的缓存。
5.1 一级缓存
- 一级缓存(local cache),即本地缓存,作用域默认为sqlSession。当 Session flush 或 close 后, 该Session 中的所有 Cache 将被清空。
- 本地缓存不能被关闭, 但可以调用 clearCache() 来清空本地缓存, 或者改变缓存的作用域。
- 在mybatis3.1之后, 可以配置本地缓存的作用域localCacheScope。在 mybatis.xml 中配置。
示例:
/**
* 测试缓存命中
*/
@Test
public void test() throws IOException {
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
SqlSession sqlSession = sqlSessionFactory.openSession();
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
Employee employee1 = mapper.selectEmp(1);
System.out.println(employee1);
Employee employee2 = mapper.selectEmp(1);
System.out.println(employee2);
System.out.println(employee1 == employee2);
}
输出结果:
[DEBUG][2022-02-27 15:03:59 146][com.xiang.dao.EmployeeMapper.selectEmp]-[==> Preparing: select * from tbl_employee where id = ? ]
[DEBUG][2022-02-27 15:03:59 213][com.xiang.dao.EmployeeMapper.selectEmp]-[==> Parameters: 1(Integer)]
[DEBUG][2022-02-27 15:03:59 300][com.xiang.dao.EmployeeMapper.selectEmp]-[<== Total: 1]
Employee{id=1, lastName='tom', gender='1', email='test@qq.com', dept=null}
[DEBUG][2022-02-27 15:03:59 302][com.xiang.dao.EmployeeMapper]-[Cache Hit Ratio [com.xiang.dao.EmployeeMapper]: 0.0]
Employee{id=1, lastName='tom', gender='1', email='test@qq.com', dept=null}
true
5.2 一级缓存失效的情况
同一次会话期间只要查询过的数据都会保存在当前SqlSession的一个Map中
key = hashCode + 查询的SqlId + 编写的sql查询语句 + 参数
-1718802744:-1553564517:com.xiang.dao.EmployeeMapper.selectEmp:0:2147483647:select * from tbl_employee where id = ?:1:development
一级缓存失效的四种情况:
- 不同的SqlSession对应不同的一级缓存
- 同一个SqlSession但是查询条件不同
- 同一个SqlSession两次查询期间执行了任何一次增删改操作
- 同一个SqlSession两次查询期间手动清空了缓存
5.3 二级缓存介绍
二级缓存(second level cache),全局作用域缓存
- 二级缓存默认不开启,需要手动配置
- MyBatis提供二级缓存的接口以及实现,缓存实现要求 POJO实现Serializable接口
- 二级缓存在 SqlSession 关闭或提交之后才会生效
使用步骤
- 全局配置文件中开启二级缓存
- 需要使用二级缓存的映射文件处使用cache配置缓存
- 注意: POJO需要实现Serializable接口
标签的属性:
- eviction:缓存的回收策略:
- LRU – 最近最少使用的:移除最长时间不被使用的对象。
- FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
- SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
- WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
- 默认的是 LRU。
- flushInterval:缓存刷新间隔。缓存多长时间清空一次,默认不清空,设置一个毫秒值
- readOnly:是否只读:
- true:只读;mybatis认为所有从缓存中获取数据的操作都是只读操作,不会修改数据。mybatis为了加快获取速度,直接就会将数据在缓存中的引用交给用户。不安全,速度快
- false:非只读:mybatis觉得获取的数据可能会被修改。mybatis会利用序列化&反序列的技术克隆一份新的数据给你。安全,速度慢
- size:缓存存放多少元素;
- type="":指定自定义缓存的全类名;实现Cache接口即可;
5.4 二级缓存使用细节
1.mybatis-config.xml 开启二级缓存
2.需要使用二级缓存的namespace中使用标签
3.注意POJO需要实现序列化
示例
/**
* 测试缓存命中
*/
@Test
public void test() throws IOException {
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
SqlSession sqlSession = sqlSessionFactory.openSession();
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
Employee employee = mapper.selectEmp(1);
sqlSession.close();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
EmployeeMapper mapper2 = sqlSession2.getMapper(EmployeeMapper.class);
Employee employee2 = mapper2.selectEmp(1);
sqlSession2.close();
//虽然命中了缓存,但此处地址并不相等
System.out.println(employee == employee2);
}
结果:
[DEBUG][2022-02-27 15:27:21 973][com.xiang.dao.EmployeeMapper.selectEmp]-[==> Preparing: select * from tbl_employee where id = ? ]
[DEBUG][2022-02-27 15:27:22 044][com.xiang.dao.EmployeeMapper.selectEmp]-[==> Parameters: 1(Integer)]
[DEBUG][2022-02-27 15:27:22 089][com.xiang.dao.EmployeeMapper.selectEmp]-[<== Total: 1]
[DEBUG][2022-02-27 15:27:22 098][org.apache.ibatis.transaction.jdbc.JdbcTransaction]-[Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@4b0b0854]]
[DEBUG][2022-02-27 15:27:22 099][org.apache.ibatis.transaction.jdbc.JdbcTransaction]-[Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@4b0b0854]]
[DEBUG][2022-02-27 15:27:22 099][org.apache.ibatis.datasource.pooled.PooledDataSource]-[Returned connection 1259014228 to pool.]
[DEBUG][2022-02-27 15:27:22 106][com.xiang.dao.EmployeeMapper]-[Cache Hit Ratio [com.xiang.dao.EmployeeMapper]: 0.5]
false
5.5 缓存相关设置和属性
- 全局setting的cacheEnable:配置二级缓存的开关。一级缓存一直是打开的。
- select标签的useCache属性:配置这个select是否使用二级缓存。一级缓存一直是使用的
- 每个增删改标签的flushCache属性: 增删改默认flushCache=true。sql执行以后,会同时清空一级和二级缓存。查询默认flushCache=false。
- sqlSession.clearCache(): 只是用来清除一级缓存。
- 全局setting的localCacheScope:本地缓存作用域:(一级缓存SESSION),当前会话的所有数据保存在会话缓存中;STATEMENT:可以禁用一级缓存。
5.6 缓存原理图示
5.7 第三方缓存整合原理
1.MyBatis定义了Cache接口方便我们进行自定义扩展。
package org.apache.ibatis.cache;
import java.util.concurrent.locks.ReadWriteLock;
public interface Cache {
String getId();
void putObject(Object key, Object value);
Object getObject(Object key);
Object removeObject(Object key);
void clear();
int getSize();
ReadWriteLock getReadWriteLock();
}
2.实现Cache类
3.在Mapper中配置此实现类
<cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache>
六、整合Spring
6.1 引入依赖
pom.xml
<dependencies>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.1</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring</artifactId>
<version>5.2.0.RELEASE</version>
<type>pom</type>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.2.0.RELEASE</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.2</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.4</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.12.4</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.26</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.5</version>
</dependency>
</dependencies>
6.2 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">
<configuration>
<!--2.settings 设置一些配置项值-->
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
<setting name="cacheEnabled" value="false"/>
</settings>
</configuration>
EmployeeMapper.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.xiang.dao.EmployeeMapper">
<select id="selectEmp" resultType="com.xiang.bean.Employee">
select * from tbl_employee where id = #{id}
</select>
</mapper>
EmployeMapper.java
@Mapper
public interface EmployeeMapper {
Employee selectEmp(int id);
}
6.3 SpringMVC配置文件
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1">
<display-name>Mybatis_SSM</display-name>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!--SpringMVC-->
<servlet>
<servlet-name>spring</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>spring</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
spring-servlet.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.xiang" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/pages/" />
<property name="suffix" value=".jsp" />
</bean>
<mvc:annotation-driven />
<mvc:default-servlet-handler />
</beans>
6.4 Spring配置文件
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:mybatis-spring="http://mybatis.org/schema/mybatis-spring"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-2.5.xsd http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring.xsd">
<context:component-scan base-package="com.xiang">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<context:property-placeholder location="classpath:dbConfig.properties"/>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="driverClass" value="${jdbc.driver}" />
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<tx:annotation-driven transaction-manager="dataSourceTransactionManager" />
<bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean" >
<property name="dataSource" ref="dataSource"/>
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<property name="mapperLocations" value="classpath:mapper/*.xml"/>
</bean>
<mybatis-spring:scan base-package="com.xiang.dao"/>
</beans>
6.5 整合测试
Controller
@Controller
public class EmpController {
@Autowired
private EmployeeMapper mapper;
@GetMapping("getEmp")
@ResponseBody
public Employee getEmp(){
Employee employee = mapper.selectEmp(1);
return employee;
}
}
index.html
<html xmlns="http://www.w3.org/1999/html">
<body>
<a href="getEmp">getEmp</a><br/>
</body>
</html>
启动后点击getEmp访问。
七、逆向代码生成器
是一个专门为MyBatis框架使用者定制的代码生成器,可以快速的根据表生成对应的映射文件,接口,以及bean类。
官方文档
7.1 使用步骤
- 引入插件
- 编写MBG配置文件
- 执行
7.2 插件引入
<build>
<plugins>
<plugin>
<!--mybatis自动生成插件-->
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.2</version>
<configuration>
<configurationFile>src/main/resources/mybatis-generator.xml</configurationFile>
<verbose>true</verbose>
<overwrite>true</overwrite>
</configuration>
<dependencies>
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.3.2</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
7.3 配置文件
mybatis-generator.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<properties resource="dbConfig.properties"/>
<classPathEntry location="/Users/xiang/software/maven/repository/mysql/mysql-connector-java/8.0.26/mysql-connector-java-8.0.26.jar"/>
<context id="generate" targetRuntime="MyBatis3">
<jdbcConnection driverClass="${jdbc.driver}"
connectionURL="${jdbc.url}"
userId="${jdbc.username}"
password="${jdbc.password}">
</jdbcConnection>
<javaModelGenerator targetPackage="com.xiang.bean"
targetProject="/Users/xiang/work/workspace/mybatis_SSM/src/main/java">
<property name="enableSubPackages" value="true"/>
<property name="trimStrings" value="true"/>
</javaModelGenerator>
<!-- sqlMapGenerator:sql映射生成策略: -->
<sqlMapGenerator targetPackage="com.xiang.dao"
targetProject="/Users/xiang/work/workspace/mybatis_SSM/src/main/java">
<property name="enableSubPackages" value="true"/>
</sqlMapGenerator>
<!-- javaClientGenerator:指定mapper接口所在的位置 -->
<javaClientGenerator type="XMLMAPPER" targetPackage="com.xiang.dao"
targetProject="/Users/xiang/work/workspace/mybatis_SSM/src/main/java">
<property name="enableSubPackages" value="true"/>
</javaClientGenerator>
<!-- 指定要逆向分析哪些表:根据表要创建javaBean -->
<table tableName="tbl_dept" domainObjectName="Department"
enableCountByExample="false" enableUpdateByExample="false"
enableDeleteByExample="false" enableSelectByExample="false"
selectByExampleQueryId="false"/>
</context>
</generatorConfiguration>
7.4 执行
直接双击执行
八、工作原理
8.1 整体流程
8.2 创建SqlSessionFactory流程
Configuration封装了所有配置文件的详细信息
把配置文件的信息解析并保存在Configuration对象中,返回包含了Configuration的DefaultSqlSession对象。
8.3 创建SqlSession流程
返回SqlSession的实现类DefaultSqlSession对象。里面包含了Executor和Configuration。
8.4 创建Mapper
创建Mapper的代理对象。
8.5 执行查询
九、插件开发
插件开发步骤:
1.编写插件实现Interceptor接口,并使用 @Intercepts注解完成插件签名
2.在全局配置文件中注册插件
9.1 自定义插件
@Intercepts(value = {
@Signature(type = StatementHandler.class, method = "parameterize", args = java.sql.Statement.class)
})
public class MyFirstPlugin implements Interceptor {
//拦截对象的
@Override
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("MyFirstPlugin---intercept:" + invocation.getMethod());
Object proceed = invocation.proceed();
return proceed;
}
@Override
public Object plugin(Object target) {
System.out.println("MyFirstPlugin---plugin:" + target);
Object wrap = Plugin.wrap(target, this);
return wrap;
}
@Override
public void setProperties(Properties properties) {
System.out.println("MyFirstPlugin--插件属性信息:" + properties);
}
}
9.2 注册自定义插件
<?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>
<!--......>
<plugins>
<plugin interceptor="com.xiang.plugin.MyFirstPlugin">
<property name="testProp" value="testPlugin"/>
</plugin>
</plugins>
<!--......>
</configuration>
9.3 插件原理
按照插件注解声明,按照插件配置顺序调用插件plugin方法,生成被拦截对象的动态代理
多个插件依次生成目标对象的代理对象,层层包裹,先声明的先包裹;形成代理链
目标方法执行时依次从外到内执行插件的intercept方法。
多个插件情况下,我们往往需要在某个插件中分离出目标对象。可以借助MyBatis提供的SystemMetaObject类来进行获取最后一层的h以及target属性的值
Interceptor接口
Intercept:拦截目标方法执行
plugin:生成动态代理对象,可以使用MyBatis提供的Plugin类的wrap方法
setProperties:注入插件配置时设置的属性
十、扩展
9.1 PageHelper插件
1、引入依赖
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.2</version>
</dependency>
2、注册插件
<?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>
...
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<property name="pageSizeZero" value="true"/>
<property name="autoRuntimeDialect" value="true"/>
</plugin>
</plugins>
...
</configuration>
3、案例
public PageInfo<Employee> page(){
PageHelper.startPage(1, 5);
List<Employee> employees = employeeMapper.selectAll();
return new PageInfo<>(employees);
}
4、原理
1、PageHelper实现了Dialect类
2、PageHelper设置分页字段
3、PageInterceptor拦截Excutor的query方法,判断是否有分页字段,有的话则先查count,再分页。没有的话直接查询。
4、包装返回结果
9.2 批量操作
默认的 openSession() 方法没有参数,它会创建有如下特性的
- 会开启一个事务(也就是不自动提交)
- 连接对象会从由活动环境配置的数据源实例得到。
- 事务隔离级别将会使用驱动或数据源的默认设置。
- 预处理语句不会被复用,也不会批量处理更新。
openSession 方法的 ExecutorType 类型的参数,枚举类型:
- ExecutorType.SIMPLE: 这个执行器类型不做特殊的事情(这是默认装配的)。它为每个语句的执行创建一个新的预处理语句。
- ExecutorType.REUSE: 这个执行器类型会复用预处理语句。
- ExecutorType.BATCH: 这个执行器会批量执行所有更新语句
批量操作我们是使用MyBatis提供的BatchExecutor进行的,它的底层就是通过jdbc攒sql的方式进行的。我们可以让他攒够一定数量后发给数据库一次。
BatchTest.java
@Test
public void test1() throws IOException {
long start = System.currentTimeMillis();
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
for (int i = 0; i < 10000; i++) {
String name = UUID.randomUUID().toString().substring(0, 5);
mapper.addEmp(new Employee(name , "1", name + "@qq.com"));
}
sqlSession.commit();
sqlSession.close();
long end = System.currentTimeMillis();
System.out.println("花费时间:" + (end - start) / 1000.0);
}
与Spring整合中,我们推荐,额外的配置一个可以专门用来执行批量操作的SqlSession
<bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean" >
<property name="dataSource" ref="dataSource"/>
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<property name="mapperLocations" value="classpath:mapper/*.xml"/>
</bean>
<!-- sqlSession -->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg name="sqlSessionFactory" ref="sqlSessionFactoryBean"/>
<constructor-arg name="executorType" value="BATCH"/>
</bean>
需要用到批量操作的时候,我们可以注入配置的这个批量SqlSession。通过他获取到mapper映射器进行操作。
@Service
public class EmployeeService {
@Autowired
private SqlSession sqlSession;
...
注意
- 批量操作是在session.commit()以后才发送sql语句给数据库进行执行的
- 如果我们想让其提前执行,以方便后续可能的查询操作获取数据,我们可以使用sqlSession.flushStatements()方法,让其直接冲刷到数据库进行执行。
9.3 存储过程调用
实际开发中,我们通常也会写一些存储过程,MyBatis也支持对存储过程的调用
1、定义存储过程
drop procedure queryEmp;
create procedure queryEmp(in empId int, out empCount int, out record )
begin
select count(*) from tbl_employee where id >= empId into empCount;
select * from tbl_employee limit 10;
end;
2、mysql中直接调用存储过程
call queryEmp(100000, @empCount);
select @empCount;
3、mybatis调用存储过程
select标签中statementType=“CALLABLE”
标签体中调用语法:{call procedure_name(#{param1_info},#{param2_info})}
mapper.xml文件
<select id="countByProcedure" statementType="CALLABLE" useCache="false" resultMap="simpleMap">
call queryEmp(
#{empId,mode=IN,jdbcType=INTEGER},
#{empCount,mode=OUT,jdbcType=INTEGER}
)
</select>
<resultMap id="simpleMap" type="com.xiang.bean.Employee" >
<id column="id" property="id" jdbcType="INTEGER" />
<result column="last_name" property="lastName" jdbcType="VARCHAR" />
<result column="gender" property="gender" jdbcType="VARCHAR" />
<result column="email" property="email" jdbcType="VARCHAR" />
</resultMap>
mapper.java文件
@Mapper
public interface EmployeeMapper {
List<Employee> countByProcedure(EmpProcedure empProcedure);
}
测试
public List<Employee> countByProcedure(int id){
EmpProcedure emp = new EmpProcedure();
emp.setEmpId(id);
List<Employee> employees = employeeMapper.countByProcedure(emp);
System.out.println(emp);
System.out.println(employees);
return employees;
}
9.4 自定义TypeHandler
我们可以通过自定义TypeHandler的形式来在设置参数或者取出结果集的时候自定义参数封装策略。
步骤:
实现TypeHandler接口或者继承BaseTypeHandler
使用@MappedTypes定义处理的java类型,使用@MappedJdbcTypes定义jdbcType类型
在自定义结果集标签或者参数处理的时候声明使用自定义TypeHandler进行处理或者在全局配置TypeHandler要处理的javaType。
注册自定义TypeHandler
1、全局配置文件中注册
<configuration>
<typeHandlers>
<typeHandler handler="com.xiang.MyTypeHandler"
javaType="com.lun.c09.other.bean.EmpStatus"/>
</typeHandlers>
...
2、处理某个字段时限定
<resultMap type="com.xiang.Employee" id="MyEmp">
<id column="id" property="id"/>
<result column="empStatus" property="empStatus" typeHandler="com.xiang.MyTypeHandler"/>
</resultMap>