mybatis 动态代理 事务 初探

背景:

1 我们有一个自己的事务代理工厂

2 工厂内原先为connection.xxx()

3 mybatis怎么start transaction(set autocommit false),commit,rollback

 

https://blog.csdn.net/suifeng629/article/details/103232904  mybatis中的事务管理

public static void main(String[] args) {
        SqlSession sqlSession = MybatisSqlSessionFactory.openSession();
        try {
            StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);

            Student student = new Student();
            student.setName("yy");
            student.setEmail("email@email.com");
            student.setDob(new Date());
            student.setPhone(new PhoneNumber("123-2568-8947"));

            studentMapper.insertStudent(student);
            sqlSession.commit();
        } catch (Exception e) {
            sqlSession.rollback();
        } finally {
            sqlSession.close();
        }
    }

 解决了第3点,还提到了sqlSession.close中resetAutocommit,myorm【重点】中碰到过这个坑

面对上面这段代码,我们不禁好奇,connection.close()之前,居然调用了一个resetAutoCommit(),含义为重置autoCommit属性值。

connection.close()含义为销毁conn,既然要销毁conn,为何还多此一举的调用一个resetAutoCommit()呢?消失之前多喝口水,真的没有必要。

其实,原因是这样的,connection.close()不意味着真的要销毁conn,而是要把conn放回连接池,供下一次使用,既然还要使用,自然就需要重置AutoCommit属性了。

通过生成connection代理类,来实现重回连接池的功能。如果connection是普通的Connection实例,那么代码也是没有问题的,双重支持。

 

 

4 mapper被上层框架注入guice,获取时inject.get(xxxMapper.class),怎么样得到sqlSession

 

https://blog.csdn.net/rocklee/article/details/88762361  Mybatis从Mapper实例获取Sqlsession对象

在一个事务中,若想利用mapper实例同一个sqlsession来同时做其他事,因为mapper字面的定义为interface,没有任何其他可用属性,但在运行过程中发现其实这个mapper实例已经被动态代理实例化了,具体可参考这位大神的分析:https://www.bbsmax.com/A/nAJvPBw3dr/ 因为mapper实例的真实处理代理类是MapperProxy, 所以就写了一个function直接从MapperProxy中取得private final属性的sqlsession, 用了反射功能

/***
	 * 从Mapper实例获取Sqlsession对象
	 * @param pvMapper 
	 * @return
	 */
	private static SqlSession getSqlSessionFromMapperInstance(Object pvMapper) {
		if (!Proxy.isProxyClass(pvMapper.getClass()))return null;
		InvocationHandler lvInvHandler=Proxy.getInvocationHandler(pvMapper);
		if (!(lvInvHandler instanceof MapperProxy)) return null;
		MapperProxy lvT= (MapperProxy)lvInvHandler ;
		try {
			Field lvSessField=lvT.getClass().getDeclaredField("sqlSession");
			lvSessField.setAccessible(true);
			try {
				return (SqlSession) lvSessField.get(lvT);
			} catch (IllegalArgumentException | IllegalAccessException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		} catch (NoSuchFieldException | SecurityException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;
	}

 

 

5 我们要求同一个dbsource,所有mapper共用一个sqlsession?

https://blog.csdn.net/Message_lx/article/details/89533045

sqlSession调用Threadlocal确保每个线程一个连接,连接同样跟thread绑定

在6中,明确了,同一个source的mappers共用一个sqlSession(sqlSessionManager)

 

 

6 实践:

InvocationHandler lvInvHandler0 = Proxy.getInvocationHandler(odsTransactionMapper);  空mapper
ManagedMapperProvider managedMapperProvider0 = (ManagedMapperProvider)lvInvHandler0;
SqlSessionManager sqlSessionManager0 = managedMapperProvider0.getSqlSessionManager();
InvocationHandler lvInvHandler1 = Proxy.getInvocationHandler(auditTrailMapper);
ManagedMapperProvider managedMapperProvider1 = (ManagedMapperProvider)lvInvHandler1;
SqlSessionManager sqlSessionManager1 = managedMapperProvider1.getSqlSessionManager();
InvocationHandler lvInvHandler2 = Proxy.getInvocationHandler(communicationMapper);
ManagedMapperProvider managedMapperProvider2 = (ManagedMapperProvider)lvInvHandler2;
SqlSessionManager sqlSessionManager2 = managedMapperProvider2.getSqlSessionManager();
InvocationHandler lvInvHandler3 = Proxy.getInvocationHandler(xRefMapper);
ManagedMapperProvider managedMapperProvider3 = (ManagedMapperProvider)lvInvHandler3;
SqlSessionManager sqlSessionManager3 = managedMapperProvider3.getSqlSessionManager();
SqlSessionManager sqlSessionManager = sqlSessionManager0;
sqlSessionManager.startManagedSession(false);
communicationMapper.insert(communicationDto);  没有
sqlSessionManager.commit();  有
communicationMapper.insert(communicationDto);  connection is closed
sqlSessionManager.close();
communicationMapper.insert(communicationDto);  有
System.out.println();

 

测试:

1 managedMapperProvider,0 1 2 3 各不相同

2 SqlSessionManager 0 1 2 相同 3不同

3 这个方案并不能保证sqlSessionManager close之后,Connection如果池化的,不确定是否resetAutocommit

mybatis Guice 事务源码解析中,我们阅读源码后确定了,会经过Transaction调用

 

但这个方案最终失败了

整个失败的原因其中之一有:

框架使用

CoreMyBatisTransaction implements Transaction

CoreMyBatisTransactionFactory implements TransactionFactory

在CoreMyBatisTransaction中又使用一个类,该类实现了spring PlatfromTransactionManager

 

在这个过程中,transaction(CoreByBatisTransaction).rollback,不同于mybatis自己的JdbcTransaction.rollback直接操作Connection.rollback了,它继续走到了PlatformTransactionManager.rollback,绕啊绕啊,本身有bug,导致rollback上面有一个永远为false的判断

posted on 2020-08-03 22:47  silyvin  阅读(262)  评论(0编辑  收藏  举报