前言
本文主要讲解 Mybatis 的以下知识点:
Mybatis 缓存
一级缓存
二级缓存
与 Ehcache 整合
Mapper 代理
使用 Mapper 代理就不用写实现类了
逆向工程
自动生成代码
Mybatis 缓存
缓存的意义
将用户经常查询的数据放在缓存 (内存) 中, 用户去查询数据就不用从磁盘上 (关系型数据库数据文件) 查询, 从缓存中查询, 从而提高查询效率, 解决了高并发系统的性能问题
mybatis 提供一级缓存和二级缓存
mybatis 一级缓存是一个 SqlSession 级别, sqlsession 只能访问自己的一级缓存的数据
二级缓存是跨 sqlSession, 是 mapper 级别的缓存, 对于 mapper 级别的缓存不同的 sqlsession 是可以共享的
看完上面对 Mybatis 的缓存的解释, 我们发现 Mybatis 的缓存和 Hibernate 的缓存是极为相似的..
Mybatis 一级缓存
Mybatis 的一级缓存原理:
第一次发出一个查询 sql,sql 查询结果写入 sqlsession 的一级缓存中, 缓存使用的数据结构是一个 map<key,value>
key:hashcode+sql+sql 输入参数 + 输出参数(sql 的唯一标识)
value: 用户信息
同一个 sqlsession 再次发出相同的 sql, 就从缓存中取不走数据库如果两次中间出现 commit 操作(修改添加删除), 本 sqlsession 中的一级缓存区域全部清空, 下次再去缓存中查询不到所以要从数据库查询, 从数据库查询到再写入缓存
Mybatis 一级缓存值得注意的地方:
Mybatis 默认就是支持一级缓存的, 并不需要我们配置.
mybatis 和 spring 整合后进行 mapper 代理开发, 不支持一级缓存, mybatis 和 spring 整合, spring 按照 mapper 的模板去生成 mapper 代理对象, 模板中在最后统一关闭 sqlsession
Mybatis 二级缓存
二级缓存原理:
二级缓存的范围是 mapper 级别(mapper 同一个命名空间),mapper 以命名空间为单位创建缓存数据结构, 结构是 map<keyvalue>
Mybatis 二级缓存配置
需要我们在 Mybatis 的配置文件中配置二级缓存
- <!-- 全局配置参数 -->
- <settings>
- <!-- 开启二级缓存 -->
- <setting name="cacheEnabled" value="true"/>
- </settings>
上面已经说了, 二级缓存的范围是 mapper 级别的, 因此我们的 Mapper 如果要使用二级缓存, 还需要在对应的映射文件中配置..
<cache/>
查询结果映射的 pojo 序列化
mybatis 二级缓存需要将查询结果映射的 pojo 实现 java.io.serializable 接口, 如果不实现则抛出异常:
org.apache.ibatis.cache.CacheException: Error serializing object. Cause: java.io.NotSerializableException: cn.itcast.mybatis.po.User
二级缓存可以将内存的数据写到磁盘, 存在对象的序列化和反序列化, 所以要实现 java.io.serializable 接口 如果结果映射的 pojo 中还包括了 pojo, 都要实现 java.io.serializable 接口
禁用二级缓存
对于变化频率较高的 sql, 需要禁用二级缓存:
在 statement 中设置 useCache=false 可以禁用当前 select 语句的二级缓存, 即每次查询都会发出 sql 去查询,** 默认情况是 true,** 即该 sql 使用二级缓存
<select id="findOrderListResultMap" resultMap="ordersUserMap" useCache="false">
刷新缓存
有的同学到这里可能会有一个疑问: 为什么缓存我们都是在查询语句中配置?? 而使用增删改的时候, 缓存默认就会被清空刷新了???
缓存其实就是为我们的查询服务的, 对于增删改而言, 如果我们的缓存保存了增删改后的数据, 那么再次读取时就会读到脏数据了!
我们在特定的情况下, 还可以单独配置刷新缓存但不建议使用 flushCache, 默认是的 true
- <update id="updateUser" parameterType="cn.itcast.mybatis.po.User" flushCache="false">
- update user set username=#{username},birthday=#{birthday},sex=#{sex},address=#{address} where id=#{id}
- </update>
了解 Mybatis 缓存的一些参数
mybatis 的 cache 参数只适用于 mybatis 维护缓存
flushInterval(刷新间隔)可以被设置为任意的正整数, 而且它们代表一个合理的毫秒形式的时间段默认情况是不设置, 也就是没有刷新间隔, 缓存仅仅调用语句时刷新
size(引用数目)可以被设置为任意正整数, 要记住你缓存的对象数目和你运行环境的可用内存资源数目默认值是 1024
readOnly(只读)属性可以被设置为 true 或 false 只读的缓存会给所有调用者返回缓存对象的相同实例因此这些对象不能被修改这提供了很重要的性能优势可读写的缓存会返回缓存对象的拷贝 (通过序列化) 这会慢一些, 但是安全, 因此默认是 false
如下例子:
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
这个更高级的配置创建了一个 FIFO 缓存, 并每隔 60 秒刷新, 存数结果对象或列表的 512 个引用, 而且返回的对象被认为是只读的, 因此在不同线程中的调用者之间修改它们会导致冲突可用的收回策略有, 默认的是 LRU:
1.LRU 最近最少使用的: 移除最长时间不被使用的对象
2.FIFO 先进先出: 按对象进入缓存的顺序来移除它们
3.SOFT 软引用: 移除基于垃圾回收器状态和软引用规则的对象
4.WEAK 弱引用: 更积极地移除基于垃圾收集器状态和弱引用规则的对象
mybatis 和 ehcache 缓存框架整合
ehcache 是专门用于管理缓存的, Mybatis 的缓存交由 ehcache 管理会更加得当..
在 mybatis 中提供一个 cache 接口, 只要实现 cache 接口就可以把缓存数据灵活的管理起来
整合 jar 包
- mybatis-ehcache-1.0.2.jar
- ehcache-core-2.6.5.jar
ehcache 对 cache 接口的实现类:
ehcache.xml 配置信息
这个 xml 配置文件是配置全局的缓存管理方案
- <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
- <!--diskStore: 缓存数据持久化的目录 地址 -->
- <diskStore path="F:\develop\ehcache" />
- <defaultCache
- maxElementsInMemory="1000"
- maxElementsOnDisk="10000000"
- eternal="false"
- overflowToDisk="false"
- diskPersistent="true"
- timeToIdleSeconds="120"
- timeToLiveSeconds="120"
- diskExpiryThreadIntervalSeconds="120"
- memoryStoreEvictionPolicy="LRU">
- </defaultCache>
- </ehcache>
如果我们 Mapper 想单独拥有一些特性, 需要在 mapper.xml 中单独配置
- <!-- 单位: 毫秒 -->
- <cache type="org.mybatis.caches.ehcache.EhcacheCache">
- <property name="timeToIdleSeconds" value="12000"/>
- <property name="timeToLiveSeconds" value="3600"/>
- <!-- 同 ehcache 参数 maxElementsInMemory -->
- <property name="maxEntriesLocalHeap" value="1000"/>
- <!-- 同 ehcache 参数 maxElementsOnDisk -->
- <property name="maxEntriesLocalDisk" value="10000000"/>
- <property name="memoryStoreEvictionPolicy" value="LRU"/>
- </cache>
应用场景与局限性
应用场景
对查询频率高, 变化频率低的数据建议使用二级缓存
对于访问多的查询请求且用户对查询结果实时性要求不高, 此时可采用 mybatis 二级缓存技术降低数据库访问量, 提高访问速度
业务场景比如:
耗时较高的统计分析 sql
电话账单查询 sql 等
实现方法如下: 通过设置刷新间隔时间, 由 mybatis 每隔一段时间自动清空缓存, 根据数据变化频率设置缓存刷新间隔 flushInterval, 比如设置为 30 分钟 60 分钟 24 小时等, 根据需求而定
局限性
mybatis 局限性
mybatis 二级缓存对细粒度的数据级别的缓存实现不好, 比如如下需求: 对商品信息进行缓存, 由于商品信息查询访问量大, 但是要求用户每次都能查询最新的商品信息, 此时如果使用 mybatis 的二级缓存就无法实现当一个商品变化时只刷新该商品的缓存信息而不刷新其它商品的信息, 因为 mybaits 的二级缓存区域以 mapper 为单位划分, 当一个商品信息变化会将所有商品信息的缓存数据全部清空解决此类问题需要在业务层根据需求对数据有针对性缓存
Mapper 代理方式
Mapper 代理方式的意思就是: 程序员只需要写 dao 接口, dao 接口实现对象由 mybatis 自动生成代理对象
经过我们上面的几篇博文, 我们可以发现我们的 DaoImpl 是十分重复的...
1 dao 的实现类中存在重复代码, 整个 mybatis 操作的过程代码模板重复(先创建 sqlsession 调用 sqlsession 的方法关闭 sqlsession)
2dao 的实现 类中存在硬编码, 调用 sqlsession 方法时将 statement 的 id 硬编码
以前的重复代码和硬编码如下:
- public class StudentDao {
- public void add(Student student) throws Exception {
- // 得到连接对象
- SqlSession sqlSession = MybatisUtil.getSqlSession();
- try{
- // 映射文件的命名空间. SQL 片段的 ID, 就可以调用对应的映射文件中的 SQL
- sqlSession.insert("StudentID.add", student);
- sqlSession.commit();
- }catch(Exception e){
- e.printStackTrace();
- sqlSession.rollback();
- throw e;
- }finally{
- MybatisUtil.closeSqlSession();
- }
- }
- public static void main(String[] args) throws Exception {
- StudentDao studentDao = new StudentDao();
- Student student = new Student(3, "zhong3", 10000D);
- studentDao.add(student);
- }
- }
Mapper 开发规范
想要 Mybatis 帮我们自动生成 Mapper 代理的话, 我们需要遵循以下的规范:
1mapper.xml 中 namespace 指定为 mapper 接口的全限定名
此步骤目的: 通过 mapper.xml 和 mapper.java 进行关联
2mapper.xml 中 statement 的 id 就是 mapper.java 中方法名
3mapper.xml 中 statement 的 parameterType 和 mapper.java 中方法输入参数类型一致
4mapper.xml 中 statement 的 resultType 和 mapper.java 中方法返回值类型一致.
再次说明: statement 就是我们在 mapper.xml 文件中命名空间 + sql 指定的 id
Mapper 代理返回值问题
mapper 接口方法返回值:
如果是返回的单个对象, 返回值类型是 pojo 类型, 生成的代理对象内部通过 selectOne 获取记录
如果返回值类型是集合对象, 生成的代理对象内部通过 selectList 获取记录
Mybatis 解决 JDBC 编程的问题
1 数据库链接创建释放频繁造成系统资源浪费从而影响系统性能, 如果使用数据库链接池可解决此问题
解决: 在 SqlMapConfig.xml 中配置数据链接池, 使用连接池管理数据库链接
2Sql 语句写在代码中造成代码不易维护, 实际应用 sql 变化的可能较大, sql 变动需要改变 java 代码
解决: 将 Sql 语句配置在 XXXXmapper.xml 文件中与 java 代码分离
3 向 sql 语句传参数麻烦, 因为 sql 语句的 where 条件不一定, 可能多也可能少, 占位符需要和参数一一对应
解决: Mybatis 自动将 java 对象映射至 sql 语句, 通过 statement 中的 parameterType 定义输入参数的类型
4 对结果集解析麻烦, sql 变化导致解析代码变化, 且解析前需要遍历, 如果能将数据库记录封装成 pojo 对象解析比较方便
解决: Mybatis 自动将 sql 执行结果映射至 java 对象, 通过 statement 中的 resultType 定义输出结果的类型
Mybatis 逆向工程
在 Intellij idea 下, 没有学习 Maven 的情况下使用 Mybatis 的逆向工程好像有点复杂, 资料太少了... 找到的资料好像也行不通...
于是学完 Maven 之后, 我就再来更新 Idea 下使用 Mybatis 的逆向工程配置...
借鉴博文: blog.csdn.net/for_my_life
修改 pom.xml 文件
向该工程添加逆向工程插件..
- <?xml version="1.0" encoding="UTF-8"?>
- <project xmlns="http://maven.apache.org/POM/4.0.0"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
- <modelVersion>4.0.0</modelVersion>
- <groupId>asdf</groupId>
- <artifactId>asdf</artifactId>
- <version>1.0-SNAPSHOT</version>
- <build>
- <finalName>zhongfucheng</finalName>
- <plugins>
- <plugin>
- <groupId>org.mybatis.generator</groupId>
- <artifactId>mybatis-generator-maven-plugin</artifactId>
- <version>1.3.2</version>
- <configuration>
- <verbose>true</verbose>
- <overwrite>true</overwrite>
- </configuration>
- </plugin>
- </plugins>
- </build>
- </project>
generatorConfig.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="conn.properties" />
- -->
- <!-- 处理 1, 这里的 jar 包位置可能需要修改 -->
- <classPathEntry location="C:\mybatisMaven\lib\mysql-connector-java-5.1.7-bin.jar"/>
- <!-- 指定运行环境是 mybatis3 的版本 -->
- <context id="testTables" targetRuntime="MyBatis3">
- <commentGenerator>
- <!-- 是否取消注释 -->
- <property name="suppressAllComments" value="true" />
- <!-- 是否生成注释代时间戳 -->
- <property name="suppressDate" value="true" />
- </commentGenerator>
- <!-- 处理 2 jdbc 连接信息, 看看库是否存在 -->
- <jdbcConnection driverClass="com.mysql.jdbc.Driver"
- connectionURL="jdbc:mysql://localhost:3306/scm?useUnicode=true&characterEncoding=UTF-8" userId="root" password="root">
- </jdbcConnection>
- <!-- 处理 3 targetPackage 指定模型在生成在哪个包 ,targetProject 指定项目的 src,-->
- <javaModelGenerator targetPackage="zhongfucheng.entity"
- targetProject="src/main/java">
- <!-- 去除字段前后空格 -->
- <property name="trimStrings" value="false" />
- </javaModelGenerator>
- <!-- 处理 4 配置 SQL 映射文件生成信息 -->
- <sqlMapGenerator targetPackage="zhongfucheng.dao"
- targetProject="src/main/java" />
- <!-- 处理 5 配置 dao 接口生成信息 -->
- <javaClientGenerator type="XMLMAPPER" targetPackage="zhongfucheng.dao" targetProject="src/main/java" />
- <table tableName="account" domainObjectName="Account"/>
- <table tableName="supplier" domainObjectName="Supplier"/>
- </context>
- </generatorConfiguration>
使用插件步骤
最后生成代码
如果对我们上面 generatorConfig.xml 配置的包信息不清楚的话, 那么可以看一下我们的完整项目结构图...
因为我们在 Idea 下是不用写对应的工程名字的, 而在 eclipse 是有工程名字的
总结
Mybatis 的一级缓存是 sqlSession 级别的只能访问自己的 sqlSession 内的缓存如果 Mybatis 与 Spring 整合了, Spring 会自动关闭 sqlSession 的所以一级缓存会失效的
一级缓存的原理是 map 集合, Mybatis 默认就支持一级缓存
二级缓存是 Mapper 级别的只要在 Mapper 命名空间下都可以使用二级缓存需要我们自己手动去配置二级缓存
Mybatis 的缓存我们可以使用 Ehcache 框架来进行管理, Ehcache 实现 Cache 接口就代表使用 Ehcache 来环境 Mybatis 缓存
由于之前写的 DaoImpl 是有非常多的硬编码的可以使用 Mapper 代理的方式来简化开发
命名空间要与 JavaBean 的全类名相同
sql 片段语句的 id 要与 Dao 接口的方法名相同
方法的参数和返回值要与 SQL 片段的接收参数类型和返回类型相同
来源: https://juejin.im/post/5aa655d6518825556020924e