Aop与jdbc事务与mybatis,配合工作源码解析
[.(spring和mybatis配合出现mybatis一级缓存失效的问题)]:
1.主要分析的内容
1.spring的jdbc的事务设计(简单的设想)
2.mybatis的一级缓存,二级缓存.(源码大致讲述,后期会着重使用以及与其他工具的配合)
3.aop工作和mybatis配合工作,mybatis一级缓存失效问题.
2.spring的jdbc的事务设计+mybatis
2.1如果自己实现这部分,需要考虑的点.
(在查看源码的时候,可以一一验证自己的猜想,如果确实猜想正确,看看对方是如何设计的)
●满足数据库设计的acid.
●连接connection,实现一个事务隔离,数据库通过这个连接,也实现库内对数据进行的事务的控制.(一个connection,线程级别)
●如果是mybatis(一定有他的连接池),来操控jdbc,他应该实现什么.
●连接池中的连接,在连接使用的使用,必须是单独使用,和其他的事务进行隔离,只有事务结束的时候,连接才能复用(清空事务,等其他连接数据).--一个连接,一个事务.
●考虑一个问题,在多线程环境中,进行连接的复用,是否会出现线程安全的问题.相当于 getConnection()这个阶段,(只要这个方法,不被同时获得,就可以了).
●考虑,sqlsession和连接(connection)的关系,和事务的关系-------首先,connection和事务应该一一对应.sqlsession是一个mybatis的一个特殊载体工具.
==>他提供,缓存,连接,配置,mapper等一系列信息.
==>发现他的缓存功能,在excutor里面的PerpetualCache一个map来实现的,他不是线程安全的.
-->这里有一个问题,为什么不把PerpetualCache一级缓存设计成线程安全的,加threadLocal那种.
-->设计sqlsession,单例,那么配置,连接,mapper等等信息,都变成了多线程环境的共享数据了,
-->所以不仅仅是缓存有线程安全问题,其他数据也有线程安全的问题
==>所以在使用sqlsession的时候,需要考虑他的线程安全性,所以sqlsession的使用就是和线程绑定的.
-->这里看到他的两种设计方案,思路都是和线程绑定.只不过用了不同的事务管理器.
-->spring使用trasanctionSynchronizeManager(提供线程安全)+
-->defaultSqlSessoin(本身线程不安全),
-->单独使用mybatis的话,使用SqlSessionManager(线程安全)
==>在翻看代码的时候,并没有发现sqlsession和connection有什么直接关系
==>有待解决==>暂时看到的.sqlsession将事务交给事务管理器,根据创建的事务管理器而定,他自己本身就不处理事务了
●那么连接池,应该和sqlsession进行解耦.由连接池,单独控制connection的获得,而sqlsession只是单独调用连接池.
==>这部分,和aop进行配合的时候,拦截器进行打开连接,并且将事务信息,连接信息保存在了事务管理器中transactionsynchronizeManager中.连接池怎么交互,没仔细看
●事务和线程关系.
一个线程调用里面,可能有多个事务,也就是切面套切面,那么在处理各个切面关系之间的关系(事务传播),
就要根据具体情况做相应的措施.(比如挂起上一个事务,提交的时机,是否创建新事务等等)
●事务和connection的关系:一一对应,如果是一个线程里面有多个事务,那就是切面代码,来解决这期间的关系.(配合线程,因为线程相当于一个人为"大事务")
●事务和sqlsession的关系:
由spring的aop和mybatis的工作原理可知,
==>我在执行器中看到了一对一的事务,并且看到执行之后,清空执行器(清空sqlsession绑定的连接)
==>或者在aop开始的时候就获得transaction,然后进行管理.后续创新sqlsession,也用的一个事务.
==>所以,代码设计到最后,就是解决,各个组件中的配合关系,先搞清楚各个组件的工作原理,然后设计逻辑(易于管理,开发,代码扩展),代码设计.
2.2.具体代码走的过程.
写一段异常代码,通过堆栈信息,可以看出,先整体通过拦截器链,第一层是trasactionIntercept (通过判断指针(索引)控制他第一个(切面)拦截器) 开启事务,在依次执行其他拦截器链,方法,在依次返回到最外面.
执行过程如下 :
事务拦截器 TransactionInterceptor;保存了事务属性信息,事务管理器; 同时它是一个MethodInterceptor;
在目标方法执行的时候;
执行拦截器链invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
事务拦截器:
1.先获取事务相关属性
2.再获取PlatformTranactionManager,如果没有先添加指定任何PlatformTranactionManager,最终会从容器中按照类型获取一个PlatformTranactionManager
3.开启事务
4.执行目标方法(执行其他拦截器==>执行目标代码(你的service内容))invocation.proceedWithInvocation
5. 如果异常,获取到事务管理器,利用事务管理器回滚操作;
如果正常,利用事务提交,进行提交.
1.以上是老版本的spring-mybatis 包下的,事务工作的大致时序图.
2.我看的springboot 2.0 以上版本,加上1.3.2的mybatis.具体代码中的属性内容和上面图可能有出入.
我不太会画uml图,这是别人的图,基本可以知道大致的内容
2.3图解,mybatis,spring事务,aop的关系
在这里插入图片描述
1.这里需要解释一下,这只是spring业务代码,抽象出来的一个图.以后如果有条件了,理解了更深的业务,会把他变成一个更容易理解的时序图.
3.mybatis的一级缓存,二级缓存.(源码大致讲述,后期会着重使用以及与其他工具的配合)
关于mybatis的一级缓存二级缓存
功能:实现缓存.
工作原理:当都开启的时候,二者的关系类似委托关系,二级没有就找一,一没有查数据库.(委托机制,类似于类加载器的双亲委托机制?)
●一级缓存设计
实现原理:mybatis是通过执行器Excutor中的hashmap 来实现缓存. 既然是缓存数据就得考虑到线程安全问题.
一级缓存的线程安全问题:
●使用defaultsqlsession(单独使用这个回有线程安全问题),配合spring使用,sqlsession绑定在一个sqlsessionholder对象里,这个对象与threadlocal绑定==>
所以也是线程安全的
●使用sqlsessionManager,里面通过threadlocal对象控制sqlsession线程安全(与线程绑定,所以不会出现线程安全问题)
二级缓存的线程安全问题:
不同于一级缓存,他是sqlsesison级别的缓存,所以就是线程级别的缓存.二级缓存相当于全局缓存,以namespace为存储单元.
●二级缓存设计
●所以只要控制在namespace级别的cache,线程安全即可.因为cache数据的特殊性.所以使用readwirteLock 锁.来实现 get put clear 之间的线程安全
●锁的范围是cache对象(cache和nammespace对应).
●缓存处理器(执行器ExcutorExcutor)来统一控制锁,控制数据的安全性(cache当参数传进来,统一管理即可.)
●在加载的时候,根据参数,构造合适excutor 构建者模式ok.构建缓存,各种参数,以及构建各种委托类.(拼装)
注:
●cache的处理算法,有好多种,默认的是lru. 当达到0.75*(设定的size)的时候,删除使用次数最少的key 这是他的数据结构 LinkedHashMap 好你妹复杂啊
4.aop工作和mybatis配合工作,mybatis一级缓存失效问题.
1.查看springboot2.0+mybatis1.3 的源码实现后.发现了mybatis一级缓存失效.
●原因:在aop执行完代码之后,会清理transactionSynchronizeMnager里面的一些属性信息,其中就包括了,持有sqlSession的 Transactional resources.==>就相当于,清除了该线程内的sqlsession对象.==>那么下一轮(nio模式)在执行mybatis的代码的时候,获取不到sqlsesion,那么就得重新创建.==>有因为,一级缓存是存放在excutor里面,所以一级缓存失效.
解决办法: 不详!!!
原因分析:
可能的原因:
●因为aop工作的代码,需要处理太多的事务,如果这个resources,不清理,就会影响下一轮该线程下的事务信息.所以选择清理.
●如果能在sqlsesison使用后,清除干净掉sqlsession绑定的事务信息(里面有一些参数属性).每次都相当于有事务提交,而单独使用sqlsession,如果出现update,insert等都会出现清空缓存的问题,如果这里,spring提交的时候,在判断一下,是否需要清空缓存,会不会有一些小题大做???
●如果挑好合适的jar包,能让sqlsessoin使用的时候,找到事务信息,并且在aop结束的时候,能够准确判断,是否需要清空缓存??
缓存问题的解决办法:
●查询的问题,api,单独交给sqlsession,貌似不可取,因为在spring的切面内,除非配置一下事务传播特性.
就是单独使用线程安全的mybatis,那该线程内,还是会出现事务信息,还是和spring的aop 有耦合. aop执行之后,还是可能清空一些数据.......我头疼.(sqlsession,不委托spring处理事务????)
●看看有没有其他工具,提供实现缓存的思路,并且和spring的aop能够配合工作.
●那么这里还有其他办法,解决缓存的问题吗??? springboot,使用的时候,推荐配合使用springDataJpa,里面有提供缓存. 这里没有去测试.也没有去看源码,有空继续研究.