MyBatis的缓存机制
MyBatis的一级缓存
MyBatis的一级缓存是默认开启的,是基于SqlSession级别的缓存,也就是说,只要是同一个sqlSession,只要执行的语句相同,则不会去数据库中进行查找,而是会从缓存中找到对应的结果。
MyBatis一级缓存失效的四种情况
- 使用了不同的sqlsession对象
- 同一个sqlsession对象,但查询条件不同
- 两次查询之间进行了增删改操作(增删改操作会清空缓存)
- 手动清空缓存(sqlsession.clearCache())
MyBatis二级缓存
MyBatis的二级缓存是SqlSessionFactory级别的,也就是说,通过同一个SqlSessionFactory创建的SqlSession对象所查询的结果都会被缓存,而在查询时也都会优先从缓存中获取结果。
二级缓存的开启
MyBatis的二级缓存默认是不开启的
-
在核心配置文件(mybatis-config.xml)中配置属性cacheEnable="true",默认为true不用刻意配置
-
在映射文件中设置标签:
xml<mapper namespace="com.qinghe.mybatis.mapper.DynamicSQLMapper"> <!-- 在命名空间下写一个这个就可以了 --> <cache /> <sql id="empColumn">eid, emp_name, age, sex, email</sql>
-
对应的实体类必须序列化
javapublic class Emp implements Serializable {
注意:只有在sqlSession关闭或提交之后,信息才会被提交到二级缓存中。
java
@Test
public void testTwoCache() {
try {
// 获取mybatis配置文件的字节输入流
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
// 工厂模式,创建一个创建sqlSession的工厂,(通过刚刚获取到的输入流)
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
// 创建一个sqlSession对象,开启自动提交
SqlSession sqlSession1 = sqlSessionFactory.openSession(true);
CacheMapper mapper1 = sqlSession1.getMapper(CacheMapper.class);
System.out.println(mapper1.getEmpByEid(1));
// 只有sqlsession关闭或提交之后二级缓存才生效
sqlSession1.commit();
SqlSession sqlSession2 = sqlSessionFactory.openSession(true);
CacheMapper mapper2 = sqlSession2.getMapper(CacheMapper.class);
System.out.println(mapper2.getEmpByEid(1));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
注意:在两次查询之间进行了增删改操作的话,会使缓存失效
手动清空缓存不会清空二级缓存
二级缓存的相关信息
-
eviction属性:缓存回收策略
- LRU(默认):最近最少使用策略
- FIFO:先进先出
- SOFT-软引用:移除基于垃圾回收器状态和软引用规则的对象
- WEAK-弱引用:更积极的移除基于垃圾回收器状态和弱引用规则的对象
-
flushInterval属性:刷新间隔(二级缓存的清空间隔)
默认为不刷新,只在进行了增删改之后刷新。
-
size属性:引用数目,正整数
代表缓存中可以存储多少个对象,不要设置太大,容易造成内存溢出
-
readOnly属性:默认为false
- true:缓存返回的对象为只读缓存,其会给调用者返回缓存对象相同的实例,因此这些对象不能修改,这提供了很大的性能优势
- false:缓存返回的对象为读写缓存(通过序列化),这会慢一些但是保证了数据的安全性。
MyBatis的缓存查询顺序
- 先查询二级缓存,因为二级缓存中可能会存在其他程序已经查询出来的数据,其可以直接拿来使用。
- 如果二级缓存没有命中,会去查询一级缓存(还未提交到二级缓存的数据可能会存在于这里)
- 如果一级缓存也没有命中,查询数据库
- SqlSession关闭之后,将一级缓存中的内容存储到二级缓存
MyBatis整合EHCache
MyBatis作为持久层框架,其也提供了整合第三方缓存机制的接口,例如可以整合EHCache:
添加依赖:
xml
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.2.1</version>
</dependency>
<!-- slf4j的一个具体实现(logback)-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
添加encache的配置文件:ehcache.xml(文件名固定)
xml
<?xml version="1.0" encoding="utf-8" ?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
<!-- 磁盘保存路径 -->
<diskStore path="D:\atguigu\ehcache"/>
<defaultCache
maxElementsInMemory="1000"
maxElementsOnDisk="10000000"
eternal="false"
overflowToDisk="true"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
</defaultCache>
</ehcache>
设置二级缓存的类型
- 在xxxMapper.xml文件中设置二级缓存类型
xml
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
加入logback日志
- 存在SLF4J时,作为简易日志的log4j将失效,此时我们需要借助SLF4J的具体实现logback来打印日志。创建logback的配置文件
logback.xml
,名字固定,不可改变
xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="true">
<!-- 指定日志输出的位置 -->
<appender name="STDOUT"
class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<!-- 日志输出的格式 -->
<!-- 按照顺序分别是:时间、日志级别、线程名称、打印日志的类、日志主体内容、换行 -->
<pattern>[%d{HH:mm:ss.SSS}] [%-5level] [%thread] [%logger] [%msg]%n</pattern>
</encoder>
</appender>
<!-- 设置全局日志级别。日志级别按顺序分别是:DEBUG、INFO、WARN、ERROR -->
<!-- 指定任何一个日志级别都只打印当前级别和后面级别的日志。 -->
<root level="DEBUG">
<!-- 指定打印日志的appender,这里通过"STDOUT"引用了前面配置的appender -->
<appender-ref ref="STDOUT" />
</root>
<!-- 根据特殊需求指定局部日志级别 -->
<logger name="com.atguigu.crowd.mapper" level="DEBUG"/>
</configuration>
EHCache配置文件说明
属性名 | 是否必须 | 作用 |
---|---|---|
maxElementsInMemory | 是 | 在内存中缓存的element的最大数目 |
maxElementsOnDisk | 是 | 在磁盘上缓存的element的最大数目,若是0表示无穷大 |
eternal | 是 | 设定缓存的elements是否永远不过期。 如果为true,则缓存的数据始终有效, 如果为false那么还要根据timeToIdleSeconds、timeToLiveSeconds判断 |
overflowToDisk | 是 | 设定当内存缓存溢出的时候是否将过期的element缓存到磁盘上 |
timeToIdleSeconds | 否 | 当缓存在EhCache中的数据前后两次访问的时间超过timeToIdleSeconds的属性取值时, 这些数据便会删除,默认值是0,也就是可闲置时间无穷大 |
timeToLiveSeconds | 否 | 缓存element的有效生命期,默认是0.,也就是element存活时间无穷大 |
diskSpoolBufferSizeMB | 否 | DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区 |
diskPersistent | 否 | 在VM重启的时候是否启用磁盘保存EhCache中的数据,默认是false |
diskExpiryThreadIntervalSeconds | 否 | 磁盘缓存的清理线程运行间隔,默认是120秒。每个120s, 相应的线程会进行一次EhCache中数据的清理工作 |
memoryStoreEvictionPolicy | 否 | 当内存缓存达到最大,有新的element加入的时候, 移除缓存中element的策略。 默认是LRU(最近最少使用),可选的有LFU(最不常使用)和FIFO(先进先出 |
MyBatis逆向工程
引入依赖
xml
<dependencies>
<!-- MyBatis核心依赖包 -->
<!-- Mybatis核心 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
<!-- junit测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- MySQL驱动 -->
<!-- https://mvnrepository.com/artifact/com.mysql/mysql-connector-j -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.0.32</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
</dependencies>
<!-- 控制Maven在构建过程中相关配置 -->
<build>
<!-- 构建过程中用到的插件 -->
<plugins>
<!-- 具体插件,逆向工程的操作是以构建过程中插件形式出现的 -->
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.0</version>
<!-- 插件的依赖 -->
<dependencies>
<!-- 逆向工程的核心依赖 -->
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.3.2</version>
</dependency>
<!-- 数据库连接池 -->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.2</version>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.27</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
创建mybatis核心配置文件
mybatis-config.xml:
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>
<!-- 配置jdbc.properties文件,使数据库的连接属性们从jdbc.properties读取-->
<properties resource="jdbc.properties"/>
<!-- typeAliases是设置别名的意思,其将全类名设置别名为最后的类名-->
<typeAliases>
<!-- type属性是要设置别名的类,alias属性是设置的类名
alias属性可以不进行设置,若不进行设置则其默认的别名就是类名,且不区分大小写,若设置了alias就区分大小写了,严格按照设置的alias属性进行匹配
-->
<!-- 也可以以包为单位,将包中的所有类都设置为默认的类名的别名(不区分大小写)-->
<package name="com.qinghe.mybatis.pojo"></package>
</typeAliases>
<!--设置连接数据库的环境-->
<environments default="development">
<environment id="development">
<!--
transactionManager标签
JDBC表示使用原生的JDBC事务管理方式,例如commit和rollback
MANAGED表示被管理的事务管理方式,例如被Spring管理等
-->
<transactionManager type="JDBC"/>
<!--
设置数据源
POOLED表示使用数据库连接池缓存进行连接
UNPOLLED表示不使用数据库连接池
JNDI表示使用上下文中的数据源
-->
<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="mappers/UserMapper.xml"/>-->
<!-- 使用package标签可以做到引入一个包下的所有映射文件的效果-->
<!--
***************************
注意mapper接口所在的包的包名必须也映射配置文件所在的包名一致
mapper接口的名字也必须与xml文件的名字保持一致
*****************************
-->
<package name=""></package>
</mappers>
</configuration>
创建逆向工程的配置文件
generatorConfig.xml(不得改名)
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>
<!--
targetRuntime: 执行生成的逆向工程的版本
MyBatis3Simple: 生成基本的CRUD(清新简洁版)
MyBatis3: 生成带条件的CRUD(奢华尊享版)
-->
<context id="DB2Tables" targetRuntime="MyBatis3Simple">
<!-- 数据库的连接信息 -->
<jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/mybatis"
userId="root"
password="123456">
</jdbcConnection>
<!-- javaBean的生成策略-->
<!-- enableSubPackages为是否在子包,trimStrings为是否去除数据库字段前后的空格-->
<javaModelGenerator targetPackage="com.qinghe.mybatis.pojo" targetProject=".\src\main\java">
<property name="enableSubPackages" value="true" />
<property name="trimStrings" value="true" />
</javaModelGenerator>
<!-- SQL映射文件的生成策略 -->
<sqlMapGenerator targetPackage="com.qinghe.mybatis.mapper"
targetProject=".\src\main\resources">
<property name="enableSubPackages" value="true" />
</sqlMapGenerator>
<!-- Mapper接口的生成策略 -->
<javaClientGenerator type="XMLMAPPER"
targetPackage="com.qinghe.mybatis.mapper" targetProject=".\src\main\java">
<property name="enableSubPackages" value="true" />
</javaClientGenerator>
<!-- 逆向分析的表 -->
<!-- tableName设置为*号,可以对应所有表,此时不写domainObjectName -->
<!-- domainObjectName属性指定生成出来的实体类的类名 -->
<table tableName="t_emp" domainObjectName="Emp"/>
<table tableName="t_dept" domainObjectName="Dept"/>
</context>
</generatorConfiguration>
之后在右侧的plugins中执行mybatis-generator就可以生成文件了,注意这样会将数据库中的下划线字段映射为驼峰,这样的生成方式只有基础的CRUD功能
MyBatis逆向工程奢华尊享版
在高级的MyBatis生成文件中,我们会生成类似于Mybatis-plus的sql映射,以以下方式使用:
注意xxxSeletive方法在操作时会不插入值为null的数据
java
@Test
public void test1() {
try {
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession = sqlSessionFactory.openSession(true);
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
// List<Emp> list = mapper.selectByExample(null);
// 创建条件
EmpExample example = new EmpExample();
// and条件这样添加
example.createCriteria().andEmpNameEqualTo("张三").andAgeBetween(20, 90);
// or条件这样添加
example.or().andDidIsNotNull();
List<Emp> list = mapper.selectByExample(example);
list.forEach(item -> {
System.out.println(item);
});
} catch (IOException e) {
throw new RuntimeException(e);
}
}
MyBatis分页插件
配置依赖
xml
<!-- https://mvnrepository.com/artifact/com.github.pagehelper/pagehelper -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.2.0</version>
</dependency>
在mybatis-config中配置分页插件,注意其位置顺序:
properties、settings、typeAliases、typeHandlers、objectFactory、objectWrapperFactory、reflectorFactory、plugins、environments、databaseIdProvider、mappers
xml
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>
使用:
java
@Test
public void testPageHelper() {
try {
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession = sqlSessionFactory.openSession(true);
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
// 开启PageHelper分页
// 可以使用Page对象接收分页,page对象中会存储一些基本信息
Page<Object> page = PageHelper.startPage(3, 2);
List<Emp> list = mapper.selectByExample(null);
// 也可以在这里使用PageInfo对象接收分页信息,这之中有全部的详细信息
// 第二个形参代表导航的数量:......4、5、6、7、8、9......(例如这种就是有五个)
PageInfo<Emp> pageInfo = new PageInfo<>(list, 5);
System.out.println(page);
System.out.println(pageInfo);
/**
* limit index, pageSize
*
*/
} catch (IOException e) {
throw new RuntimeException(e);
}
}
Page对象中的信息:
Page{count=true, pageNum=3, pageSize=2, startRow=4, endRow=6, total=10, pages=5, reasonable=false, pageSizeZero=false}
[Emp{eid=5, empName='田七', age=22, sex='男', email='345@qq.com', did=2}, Emp{eid=6, empName='a', age=null, sex='null', email='null', did=null}]
PageInfo对象中的信息:
PageInfo{pageNum=3, pageSize=2, size=2, startRow=5, endRow=6, total=10, pages=5, list=Page{count=true, pageNum=3, pageSize=2, startRow=4, endRow=6, total=10, pages=5, reasonable=false, pageSizeZero=false}
[Emp{eid=5, empName='田七', age=22, sex='男', email='345@qq.com', did=2}, Emp{eid=6, empName='a', age=null, sex='null', email='null', did=null}],
prePage=2, nextPage=4, isFirstPage=false, isLastPage=false, hasPreviousPage=true, hasNextPage=true, navigatePages=5, navigateFirstPage=1, navigateLastPage=5, navigatepageNums=[1, 2, 3, 4, 5]}