我们知道事务是针对同一个connection来说的,先将connection自动提交设置为false,再和数据库交互结束后commit提交事务,spring中提供了简便的注解方式实现事务,事务的源码可以找到connection的自动提交设置为false,但是mybatis真正执行sql语句的时候,如何保证和事务的连接是同一个connection的,如果不是同一个,则不能保证事务.
事务源码
org.springframework.transaction.support.AbstractPlatformTransactionManager#getTransaction
image.png
org.springframework.transaction.support.TransactionSynchronizationManager#bindResource
--org.springframework.jdbc.datasource.DataSourceTransactionManager#doBegin
开始事务的时候会创建connection,将connection自动提交false,后续的bindResource方法中将ConnectionHolder存放到ThreadLocal中
image.png
将ConnectionHolder对象,放到TransactionSynchronizationManager的resources成员变量中
org.springframework.transaction.support.TransactionSynchronizationManager#bindResource
public static void bindResource(Object key, Object value) throws IllegalStateException {
Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
Assert.notNull(value, "Value must not be null");
Map<Object, Object> map = resources.get();
// set ThreadLocal Map if none found
if (map == null) {
map = new HashMap<>();
resources.set(map);
}
//存储ConnectionHolder
Object oldValue = map.put(actualKey, value);
// Transparently suppress a ResourceHolder that was marked as void...
if (oldValue instanceof ResourceHolder && ((ResourceHolder) oldValue).isVoid()) {
oldValue = null;
}
if (oldValue != null) {
throw new IllegalStateException(
"Already value [" + oldValue + "] for key [" + actualKey + "] bound to thread");
}
}
在TransactionSynchronizationManager#doGetResource方法获取ConnectionHolder
private static Object doGetResource(Object actualKey) {
//private static final ThreadLocal<Map<Object, Object>> resources =new NamedThreadLocal<>("Transactional resources");
//resources是ThreadLocal
Map<Object, Object> map = resources.get();
if (map == null) {
return null;
}
Object value = map.get(actualKey);
// Transparently remove ResourceHolder that was marked as void...
if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {
map.remove(actualKey);
// Remove entire ThreadLocal if empty...
if (map.isEmpty()) {
resources.remove();
}
value = null;
}
return value;
}
执行mybatis执行sql的时候是如何调用TransactionSynchronizationManager#doGetResource方法,从TheadLocal中获取ConnectionHolder的
image.png
这里不讲解mybatis的源码,贴出来调用链,可以看到如何从ThreadLocal获取到了ConnectionHolder的
分析到这可知,从开启事务到sql的执行需要同一个线程,如果执行sql的代码放在多线程中,该sql不能保证在事务里.
网友评论