经验整理-30-mysql缓存-Mybatis一级缓存SqlSession、二级缓存namespace

-------------   mysql缓存-------------   

mysql缓存淘汰策略

默认采用LRU算法(Least recently used,最近最少使用的被淘汰)

------------- Mybatis新请求的调用顺序-------------   

查询的流程是二级缓存>一级缓存>数据库

------------- Mybatis对比一级和二级缓存-------------   

参考:https://www.pianshen.com/article/16399265/

1)作用域不同

1.1、(非事物情况下的) 一级缓存是基于sqlsession的,当一次请求结束之后,意味着session执行结束,所以sqlsession也就关闭了,所以缓存也就清空了,因此,对于多次的请求,肯定不存在一级缓存
(事物情况下的) 多个请求,都是同一个sqlsession,共享一级缓存;
 1.2、二级缓存作用域是对于namespace(你当然可把多个mapper写成相同的namespace,那就共享了,一般一对一)的,只要相同的查询sql,都会优先从二级缓存区域查找

2)结果结构类似但不同

2.1、一级缓存在sqlsession中有一个数据区域,是map结构,一级缓存中的key是由sql语句、条件、statement等信息组成一个唯一值,,稍微有一点不同,比如Limit不同,都会是不同的缓存key。一级缓存中的value,缓存就是查询出的结果对象
Map<String,Object>;
2.2、二级缓存在namespace(对应一个mapper)中有一个数据区域,是map结构。可以理解为每次一级缓存成功,都会往二级里写一份(但是其中缓存的是数据而不是对象)一个namespace是可以被多个sqlsession来请求的。
区别点理解:
二级缓存查询前,先查一下外层namespace
map结构获取到当前namespace对应的数据map。
二级缓存出来的数据是会再经过一次序列化转成对象的;

2)刷新缓存效率不同

对于缓存中当前sql结果的value的每个字段的变更,都会销毁缓存;
但由于作用域不同,一级缓存只销毁刷新包含当前sql每个sqlsession;二级缓存会销毁刷新当前namespace下所有缓存,效率低


2)使用开启开关不同

一级缓存是默认使用的。
二级缓存需要手动开启。

3)缓存内容不同

mybatis的的一级缓存是SqlSession级别的缓存,一级缓存缓存的是对象,当SqlSession提交、关闭以及其他的更新数据库的操作发生后,一级缓存就会清空。
二级缓存是SqlSessionFactory级别的缓存,同一个SqlSessionFactory产生的SqlSession都共享一个
二级缓存,二级缓存中存储的是数据,当命中二级缓存时,通过存储的数据构造对象返回。查询数据的时候,
查询的流程是二级缓存>一级缓存>数据库

 

4)优先级

一级缓存和二级缓存同时存在的时候优先使用二级缓存

5)推荐使用及场景不同

一级缓存作用于sqlsession默认是开启的,但在spring环境需要开启事务才能使用,开启事务后执行到第二个dao的时候不会新建sqlsession,不开启事务及时是同一个方法中连续调用两次同一个dao同样的参数都不会触发一级缓存的,每次请求是新的sqlsession.
以下是直接用原生的sqlsession(非spring管理的)来测不同会话的BUG:

二级缓存作用于namespase,默认是关闭的,并且不建议开启,集群环境会有bug的哦,单机环境使用也有很多限制的
因为mybaits的二级缓存区域以mapper为单位划分当一个商品信息变化会将所有商品信息的缓存数据全部清空。解决此类问题需要在业务层根据需求对数据有针对性缓存。
更新少,实时性要求不高的场景才能用二级缓存。参考:https://blog.csdn.net/gg12365gg/article/details/52048266

-------------------spring事务管理下的共享sqlsession(一级缓存)-------------

而关于spring事务管理的多个service只会一个创建sqlsession,这是因为事务管理下的sql执行方式是BATCH,只会与数据库交互一次,一次执行完所有的sql,所以只会创建一个sqlsession;(多个service在事物期间发生多个查询,自然是在同一个sqlsession里共享一级缓存);
 

开启一个新的事务并且在新的事务中首次执行 mybatis 操作时会开启新的 mybatis Session,因此在 REQUIRES_NEW 中执行 mybatis 操作一定会开启新的 Session .参考:https://suncle.me/2019/12/19/record-a-troubleshooting-process-of-ut-hanging-caused-by-mybatis-cache-and-transaction-propagation/

*------spring事务管理下的共享sqlsession(一级缓存)f示例解释-------------

如下,每个sql片段都是拿的同一个sqlsession,所以修改sqlsession里的值会直接影响下一个查询

(mapper真接返回的对象就是sqlsession里的值,修改这个对象就是在修改一级缓存的值)

-------------------附g事务管理下的共享sqlsession(一级缓存)的原理-------------

事务管理的原理参考:https://www.cnblogs.com/chihirotan/p/6592759.html

、在前面讲解到,sqlSessionTemplate 操作数据库实际操作是对于代理对象 目标方法的执行。

  •  代理对象是如何获取defaultSqlSession ,在代理方法中通过SqlSessionUtils 的方法获取SqlSession
  •   它会首先获取SqlSessionHolder,SqlSessionHolder用于在TransactionSynchronizationManager中保持当前的SqlSession。
  •   如果holder不为空,并且holder被事务锁定,则可以通过holder.getSqlSession()方法,从当前事务中获取sqlSession,即 Fetched SqlSession from current transaction。
  •   如果不存在holder或没有被事务锁定,则会创建新的sqlSession,即 Creating a new SqlSession,通过sessionFactory.openSession()方法。
  •   如果当前线程的事务是活跃的,将会为SqlSession注册事务同步,即 Registering transaction synchronization for SqlSession。

获取session源码

释放session源码

-------------------无spring事务管理下的sqlsession(一级缓存)会自动关闭-------------

sqlsession默认执行完一段的sql片段后就会close掉sqlsession,即销毁sqlsession;而下一次对数据库的操作的又会重新建立新的sqlsession,所以这就和前一次的执行sql的sqlsession属于不同的SQL session了,也就这两个sqlsession就不存在一级缓存关系了;代码分析如下:

一级缓存不需要任何配置,mybatis默认开启的,所以这里直接写service,没有其他额外配置;代码内容是对同一个sql代码片段连续调用两次,控制台日志打印如下:

-------------------任何二级缓存(有无事物一样)-------------

 二级缓存是基于namespace和mapper的作用域.
开启缓存,具体如下:

 1)       在mybatis-config.xml文件中的<settings>标签配置开启缓存,代码如下:

   <!--开启缓存,此时配置的是mybatis的二级缓存-->

       <setting name="cacheEnabled" value="true"/>   

   2)   单单配置这个还是不够的,还需要在我们的mapper的xml文件下开启缓存,即加入该标签:


然后可以开始试着运行我们的代码如下:   

@Override
public CourseDto getCourse(String courseId) {
    log.info("测试mybatis默认开启的二级缓存");
    Course course = courseDao.getSqlSession().selectOne("listCourseByAutoMapping", courseId);
    log.info("第一次查询结果:{}",course.toString());
    Course course1 = courseDao.getSqlSession().selectOne("listCourseByAutoMapping", courseId);
    log.info("第二次执行查询完毕:{}",course1.toString());
    CourseDto courseDto = mapper.map(course, CourseDto.class);
 
    return courseDto;
}
执行结果如下:              

                                                                    

-----------------任何二级缓存的开启和部分关闭及redis缓存的引入------------------

官方提供了一些第三方缓存集成方式,比如ehcache和redis

-------------------openSession()获取SqlSession源码分析-------------

可以看到openSession主要要以下几个入参

1)autoCommit :自动提交否
true:自动提交,无需再显示调用sqlSession.commit();进行提交。
false:不自动提交,需再显示调用sqlSession.commit();进行提交,否则更新删除插入操作无效
2)ExecutorType
配置默认的执行器
。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(prepared statements); BATCH 执行器将重用语句并执行批量更新。
3)TransactionIsolationLevel:事务相关的隔离等级
TransactionIsolationLevel事务相关的隔离等级,用于解决脏读,不可重复读等并发问题,后面会有文章进行相关介绍。
READ_COMMITTED:读提交
READ_UNCOMMITTED: 读未提交
REPEATABLE_READ:可重复读 (不传时,也会拿数所默认的可重复读级别)
SERIALIZABLE:可序列化

    public SqlSession openSession() {
        return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false);
    }

    public SqlSession openSession(boolean autoCommit) {
        return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, autoCommit);
    }

    public SqlSession openSession(ExecutorType execType) {
        return this.openSessionFromDataSource(execType, (TransactionIsolationLevel)null, false);
    }

    public SqlSession openSession(TransactionIsolationLevel level) {
        return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), level, false);
    }

    public SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level) {
        return this.openSessionFromDataSource(execType, level, false);
    }

    public SqlSession openSession(ExecutorType execType, boolean autoCommit) {
        return this.openSessionFromDataSource(execType, (TransactionIsolationLevel)null, autoCommit);
    }

    public SqlSession openSession(Connection connection) {
        return this.openSessionFromConnection(this.configuration.getDefaultExecutorType(), connection);
    }

    public SqlSession openSession(ExecutorType execType, Connection connection) {
        return this.openSessionFromConnection(execType, connection);
    }

-------------------openSession()调用测试分析出调用流程-------------

观察:org.mybatis.spring.SqlSessionUtils

1)执行   :@Transactional
"method":"org.springframework.jdbc.datasource.DataSourceTransactionManager.doBegin","level":"DEBUG","IP":"","thread":"--测试线程-3","body":"Acquired Connection [com.mysql.jdbc.JDBC4Connection@2943173e] for JDBC transaction";

"method":"org.springframework.jdbc.datasource.DataSourceTransactionManager.doBegin","level":"DEBUG","IP":"","thread":"--测试线程-3","body":"Switching JDBC Connection [com.mysql.jdbc.JDBC4Connection@2943173e] to manual commit"

"method":"org.springframework.beans.factory.support.DefaultListableBeanFactory.doGetBean","level":"DEBUG","IP":"","thread":"--测试线程-3","body":"Returning cached instance of singleton bean 'logBean'"

2)执行 :
"method":"org.mybatis.spring.SqlSessionUtils.getSqlSession","level":"DEBUG","IP":"","thread":"--测试线程-3","body":"Creating a new SqlSession",

"method":"org.mybatis.spring.SqlSessionUtils.getSqlSession","level":"DEBUG","IP":"","thread":"--测试线程-3","body":"Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7ba4846f]",


3)执行 :打开连接

"method":"org.mybatis.spring.transaction.SpringManagedTransaction.openConnection","level":"DEBUG","IP":"","thread":"--测试线程-3","body":"JDBC Connection [com.mysql.jdbc.JDBC4Connection@2943173e] will be managed by Spring"

4)执行 :使用连接执行命令

"method":"dao.com.smy.cif.dao.CustBasicInfoDao.selectByPrimaryKey.debug","level":"DEBUG","IP":"","thread":"--测试线程-3","body":"ooo Using Connection [com.mysql.jdbc.JDBC4Connection@2943173e]",

"method":"dao.com.smy.cif.dao.CustBasicInfoDao.selectByPrimaryKey.debug","level":"DEBUG","IP":"","thread":"--测试线程-3","body":"==>  Preparing: select CUST_NO, CUST_NAME, ID_TYPE, ID_NO, CUST_SEX, BIRTH_DATE, CUST_STATUS, CHANNEL_CODE, CREATE_DATETIME, UPDATE_DATETIME from cust_basic_info where CUST_NO = ? ",

,"method":"dao.com.smy.cif.dao.CustBasicInfoDao.selectByPrimaryKey.debug","level":"DEBUG","IP":"","thread":"--测试线程-3","body":"==> Parameters: 150000000113(String)",

5)执行 :释放关闭连接

"org.mybatis.spring.SqlSessionUtils.closeSqlSession","level":"DEBUG","IP":"","thread":"--测试线程-3","body":"Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7ba4846f]"

6)执行 :

期间再次使用过开和关SqlSession

"method":"org.springframework.jdbc.datasource.DataSourceTransactionManager.handleExistingTransaction","level":"DEBUG","IP":"","thread":"--测试线程-3","body":"Participating in existing transaction",

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

java_爱吃肉

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值