mysql中session和connection的关系:
mysql中一个session就是一个connection,区别在于,connection是对象池中的一个可复用对象,所以它就是一个物理连接,而session就是connection从对象池中被取出后做的一系列事情,直到connection再次被施加对象池(连接池)中时,发生的所有事情(这里指数据库交互),叫做一个session。
跟踪代码分析:
事务拦截类 org.springframework.transaction.interceptor.TransactionInterceptor extends org.springframework.transaction.interceptor.TransactionAspectSupport implements org.aopalliance.intercept.MethodInterceptor, java.io.Serializable
MethodInterceptor 接口同cglib代理接口,只有一个方法Object invoke(MethodInvocation invocation) throws Throwable; 实现该方法则会在代理类的方法执行时先执行此方法。这就是入口。cglib的原理有兴趣的同学可以自行研究。
TransactionInterceptor 的实现则是
@Override
public Object invoke(final MethodInvocation invocation) throws Throwable {
// Work out the target class: may be {@code null}.
// The TransactionAttributeSource should be passed the target class
// as well as the method, which may be from an interface.
Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
// Adapt to TransactionAspectSupport's invokeWithinTransaction...
return invokeWithinTransaction(invocation.getMethod(), targetClass, new InvocationCallback() {
@Override
public Object proceedWithInvocation() throws Throwable {
return invocation.proceed();
}
});
}
其中调用了 invokeWithinTransaction(Method, Class, InvocationCallback) 方法,这个方法是其父类 TransactionAspectSupport 的。
其中的实现比较长,有兴趣的同学可以自行研究。
重点来了,
invokeWithinTransaction(Method, Class, InvocationCallback)
方法调用了
protected TransactionInfo createTransactionIfNecessary(PlatformTransactionManager, TransactionAttribute, String) 方法,其含义是在有必要的情况下创建一个新的事务,那么就是说没必要的时候就不要创建新事务了。
这其中则调用org.springframework.transaction.PlatformTransactionManager.getTransaction(TransactionDefinition)来创建新的事务。
这个接口的实现类就是mybatis事务控制使用的org.springframework.jdbc.datasource.DataSourceTransactionManager extends org.springframework.transaction.support.AbstractPlatformTransactionManager
AbstractPlatformTransactionManager.getTransaction(TransactionDefinition)
首先会调用doGetTransaction()获取一个新的事务对象,然而在这里,这个事务对象可能并不完全是一个新的。因为存在多个事务嵌套的情况存在(propagation使用默认值require),此处会使用数据源从线程中获取(ThreadLocal),以确定是否是嵌套的事务。事务对象中都会有一个属性connectionHolder,每一个事务都会重新生成一个,并不一样,但是只会把第一个保存在线程 ThreadLocal中,因为它们里面包含的Connection是一样的。同时这也是其它位置的代码获取同一个connetion的唯一方式。
如果是嵌套的事务,则不会重新创建新的事务,而是在此基础上处理。handleExistingTransaction(TransactionDefinition, Object, boolean)
处理方式则是根据事务配置中propagation的配置内容分别处理,有兴趣的同学可以自行研究。这里只讨论TransactionDefinition.PROPAGATION_REQUIRED。因此嵌套的事务只是创建了一个新的事务状态信息TransactionInfo,并没有再次创建数据库事务,此TransactionInfo包含它的父级,也就是上一个事务信息用于回溯(此时回到invokeWithinTransaction(Method, Class, InvocationCallback)方法)。
注意DataSourceTransactionManager.doBegin(Object transaction, TransactionDefinition definition)方法,第一个事务过来时,走的是这里,但是只走一次,后面的都不会再执行这里的代码,因为事务只需要开启一次,如果事务隔离级别需要修改,则是在这个方法中调用org.springframework.jdbc.datasource.DataSourceUtils.prepareConnectionForTransaction(con, definition)处理的,如果不需要修改,则需要等到第一次需要执行sql时,由sqlSessionFactory.openSession(ExecutorType execType)开启事务。
然后就会通过InvocationCallback.proceedWithInvocation()回调到最开始的入口方法处,进行业务处理。
接着走过层层代理之后,来到 org.apache.ibatis.binding.MapperProxy.invoke(Object proxy, Method method, Object[] args) 方法中。它会回调真正的执行方法中。
其中的
sqlSession.insert(command.getName(), param)
会进入到
org.mybatis.spring.SqlSessionTemplate
的下面方法
@Override
public int insert(String statement, Object parameter) {
return this.sqlSessionProxy.insert(statement, parameter);
}
这里需要注意,这已经从mybatis的源码跳转到mybatis-spring源码了,原因是,在配置mybatis的mapper和java接口时都是使用的mybatis-spring的配置,所以sqlSession也在内部被替换成spring的了。
而这里的sqlSessionProxy是在SqlSessionTemplate构造函数中生成的代理类
this.sqlSessionProxy = (SqlSession) newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class },
new SqlSessionInterceptor());
下面是这个内部代理类的信息:
位置:org.mybatis.spring.SqlSessionTemplate.SqlSessionInterceptor
源码:
/**
* Proxy needed to route MyBatis method calls to the proper SqlSession got
* from Spring's Transaction Manager
* It also unwraps exceptions thrown by {@code Method#invoke(Object, Object...)} to
* pass a {@code PersistenceException} to the {@code PersistenceExceptionTranslator}.
*/
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
SqlSession sqlSession = getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType,
SqlSessionTemplate.this.exceptionTranslator);
try {
Object result = method.invoke(sqlSession, args);
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
// force commit even on non-dirty sessions because some databases require
// a commit/rollback before calling close()
sqlSession.commit(true);
}
return result;
} catch (Throwable t) {
Throwable unwrapped = unwrapThrowable(t);
if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
// release the connection to avoid a deadlock if the translator is no loaded. See issue #22
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
sqlSession = null;
Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
if (translated != null) {
unwrapped = translated;
}
}
throw unwrapped;
} finally {
if (sqlSession != null) {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
}
从注释可以看出,这就是为了把mybatis的方法调用转到spring方法调用,从而可以替换掉sqlSession,而达到管理事务的目的。(是在MapperFactoryBean中替换的,如果使用自动扫描,则是在ClassPathMapperScanner类中会有相关类创建MapperFactoryBean,从而在MapperFactoryBean的父类SqlSessionDaoSupport中。)
通过org.mybatis.spring.SqlSessionUtils.getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) 这个方法,回调后会到mybatis中进行sql执行,而此时有sqlSession也有dataSource,但是却没有connetion(因为前两者本地创建,而后者需要连接),这时mybatis的SimpleExecutor.prepareStatement() 会调用 getConnection()获取连接,而这个连接是之前用SqlSessionUtils.getSqlSession()创建的,里面包含有sqlSessionFactory,也就是会有 SpringManagedTransaction 进行事务连接创建。创建连接就在 SpringManagedTransaction.getConnection(),它是通过 DataSourceUtils.getConnection(this.dataSource) 调用 doGetConnection(DataSource dataSource) 来获取连接的,所以通过和之前事务管理中一样的dataSource,就能获取到spring事务管理中的connectionHoler,再获取其connection即可。然,如果spring事务和mybatis数据源配置的不是同一个(即配置出错),则无法获取到spring事务中的connectionHolder,这时,doGetConnection(DataSource dataSource) 会调用传来的dataSource创建一份新的connection,也就是不受事务控制的一个connection,返回给myabatis使用,所以一定需要保证spring和mybatis使用的是同一个数据源(spring中的是真实生效的,mybatis中的只是一个引用,作为key用来获取spring中相应的数据。)。
再转回到一开始定义的ConnectionHolder中,从ThreadLocal中获取出相应的connection,然后直接method.invoke运行程序中的sql。结束。
为什么放在threadLocal中?
因为一个任务是一个线程执行,所以不管到了哪一行代码,都能获取到。而datasource和sqlSessionFactory都是spring管理,所以可以直接作为key来使用。
到此为止:
今日分析的目标已经达到,这其中的代码,思想都是非常值得参考,学习的。学到自己的思想里,才能运用自如。
2016-09-16 06:29:16
一夜,又过去了。