当前位置:首页 > 科技  > 软件

为什么说MyBatis默认的DefaultSqlSession是线程不安全?

来源: 责编: 时间:2023-09-18 21:40:26 210观看
导读1 环境准备mybatis-config.xml<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"><configuration> <

1 环境准备

mybatis-config.xml4bh28资讯网——每日最新资讯28at.com

<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"><configuration> <environments default="development">   <environment id="development">     <transactionManager type="JDBC"/>     <dataSource type="POOLED">       <property name="driver" value="com.mysql.cj.jdbc.Driver"/>       <property name="url" value="jdbc:mysql://localhost:3306/testjpa?serverTimezone=GMT%2B8"/>       <property name="username" value="root"/>       <property name="password" value="123123"/>     </dataSource>   </environment> </environments> <mappers>   <mapper resource="mappers/UsersMapper.xml"/> </mappers></configuration>

UsersMapper.xml4bh28资讯网——每日最新资讯28at.com

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.pack.mapper.UsersMapper"> <select id="selectList" resultType="com.pack.domain.Users">  select * from t_users </select></mapper>

UsersMapper.java4bh28资讯网——每日最新资讯28at.com

package com.pack.mapper;import java.util.List;import com.pack.domain.Users;public interface UsersMapper { List<Users> selectList() ;}

Users.java4bh28资讯网——每日最新资讯28at.com

public class Users{ private String id ; private String username ; private String password ;}

UsersMapperTest.java测试类4bh28资讯网——每日最新资讯28at.com

public class UsersMapperTest { private static final int MAX = 100 ; private SqlSessionFactory sqlSessionFactory ; private Thread[] threads = new Thread[MAX] ; private CountDownLatch cdl = new CountDownLatch(MAX) ; @Before public void init() throws Exception {   String resource = "mybatis-config.xml";   InputStream inputStream = Resources.getResourceAsStream(resource);   sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);} @Test public void testSelectList() throws Exception {   SqlSession session = sqlSessionFactory.openSession() ;   UsersMapper mapper = session.getMapper(UsersMapper.class) ;   for (int i = 0; i < MAX; i++) {     threads[i] = new Thread(() -> {       try {         cdl.await() ;         System.out.println(mapper.selectList()) ;      } catch (InterruptedException e) {         e.printStackTrace();      }    }) ;  }   for (int i = 0; i < MAX; i++) {     threads[i].start() ;     cdl.countDown() ;  }   System.in.read() ;}}

启动100个线程同时查询,结果如下:4bh28资讯网——每日最新资讯28at.com

### Cause: java.lang.ClassCastException: org.apache.ibatis.executor.ExecutionPlaceholder cannot be cast to java.util.Listat org.apache.ibatis.exceptions.ExceptionFactory.wrapException(ExceptionFactory.java:30)at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:153)at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:145)at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:140)at org.apache.ibatis.binding.MapperMethod.executeForMany(MapperMethod.java:147)at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:80)at org.apache.ibatis.binding.MapperProxy$PlainMethodInvoker.invoke(MapperProxy.java:145)at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:86)at com.sun.proxy.$Proxy8.selectList(Unknown Source)at test.UsersMapperTest.lambda$0(UsersMapperTest.java:39)at java.lang.Thread.run(Thread.java:745)Caused by: java.lang.ClassCastException: org.apache.ibatis.executor.ExecutionPlaceholder cannot be cast to java.util.Listat org.apache.ibatis.executor.BaseExecutor.query(BaseExecutor.java:152)at org.apache.ibatis.executor.CachingExecutor.query(CachingExecutor.java:109)at org.apache.ibatis.executor.CachingExecutor.query(CachingExecutor.java:89)at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:151)... 9 more[Users [id=1, username=admin, password=123123], Users [id=2, username=guest, password=111111]][Users [id=1, username=admin, password=123123], Users [id=2, username=guest, password=111111]]

程序抛出了异常ClassCastException类型转换异常。也就是在多个线程同时使用SqlSession时出现了类型转换错误。4bh28资讯网——每日最新资讯28at.com

2 错误分析

根据错误信息,把错误定位到DefaultSqlSession.java:1534bh28资讯网——每日最新资讯28at.com

public class DefaultSqlSession implements SqlSession { private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {   try {     MappedStatement ms = configuration.getMappedStatement(statement);     return executor.query(ms, wrapCollection(parameter), rowBounds, handler);  } catch (Exception e) {     throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e); // 这里抛出的异常  } finally {     ErrorContext.instance().reset();  }}    }

继续根据错误日志,确定是执行下面这行代码出现错误4bh28资讯网——每日最新资讯28at.com

executor.query(ms, wrapCollection(parameter), rowBounds, handler);

而executor根据错误日志确定为BaseExecutor类4bh28资讯网——每日最新资讯28at.com

public abstract class BaseExecutor implements Executor {  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {   // ...   List<E> list;   try {     queryStack++;     // 从本地缓存中获取数据,如果有会强制转换为List对象     // 位置1:     list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;     if (list != null) {       handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);    } else {       // 如果缓存中没有,则会进入该方法执行       list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);    }  } finally {     queryStack--;  }   // ...   return list;} private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {   List<E> list;   // 先将一个枚举值存入到缓存中ExecutionPlaceholder   localCache.putObject(key, EXECUTION_PLACEHOLDER);   // 位置2   try {     // 做实际的查询     list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);  } finally {     // 删除上面存入的值     localCache.removeObject(key);  }   // 将查询出来的数据缓存起来   localCache.putObject(key, list);   // 位置3   if (ms.getStatementType() == StatementType.CALLABLE) {     localOutputParameterCache.putObject(key, parameter);  }   return list;}}public enum ExecutionPlaceholder { EXECUTION_PLACEHOLDER}

分析:当线程1执行到‘位置2’时,此时缓存中缓存了ExecutionPlaceholder枚举值,这是线程2开始执行‘位置1’此时线程2从缓存中是能获取值,此值是ExecutionPlaceholder枚举值,该值怎么可能转换为List,所以这里就会抛出类型转换异常了。4bh28资讯网——每日最新资讯28at.com

如果想正确执行,只能是每个线程创建一个新的SqlSession对象。4bh28资讯网——每日最新资讯28at.com

3 默认SqlSession实现

// 获取SqlSession对象// SqlSessionFactory的实现是DefaultSqlSessionFactory对象sqlSessionFactory.openSession()

进入openSession()方法4bh28资讯网——每日最新资讯28at.com

public class DefaultSqlSessionFactory implements SqlSessionFactory { @Override public SqlSession openSession() {   return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);} private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {   Transaction tx = null;   try {     final Environment environment = configuration.getEnvironment();     final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);     tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);     final Executor executor = configuration.newExecutor(tx, execType);     // SqlSession默认实现使用的DefaultSqlSession。     return new DefaultSqlSession(configuration, executor, autoCommit);  } catch (Exception e) {     closeTransaction(tx); // may have fetched a connection so lets call close()     throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);  } finally {     ErrorContext.instance().reset();  }}}

DefaultSqlSession.java4bh28资讯网——每日最新资讯28at.com

/*** The default implementation for {@link SqlSession}.* Note that this class is not Thread-Safe.** @author Clinton Begin*/public class DefaultSqlSession implements SqlSession { // ...    }// 在这注释中已经提到了该类is not Thread-Safe.

4 Spring如何处理

在Springboot中是如何保证线程安全的呢?4bh28资讯网——每日最新资讯28at.com

4.1 引入依赖

<dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.4</version></dependency>
@SpringBootApplication@MapperScan({"com.pack.mapper"})public class SpringBootTransactionalApplication { public static void main(String[] args) {   SpringApplication.run(SpringBootTransactionalApplication.class, args);}}// 重点在这@Import上@Import(MapperScannerRegistrar.class)public @interface MapperScan {}

4.2 自动配置

public class MybatisAutoConfiguration implements InitializingBean { @Bean @ConditionalOnMissingBean public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {   SqlSessionFactoryBean factory = new SqlSessionFactoryBean() ;   // ...         return factory.getObject() ;} @Bean @ConditionalOnMissingBean public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {   ExecutorType executorType = this.properties.getExecutorType();   if (executorType != null) {     return new SqlSessionTemplate(sqlSessionFactory, executorType);  } else {     return new SqlSessionTemplate(sqlSessionFactory);  }}}

@MapperScan注解中应用了@Import(MapperScannerRegistrar.class)4bh28资讯网——每日最新资讯28at.com

在这Import类中会注册一个MapperScannerConfigurer配置类4bh28资讯网——每日最新资讯28at.com

public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware { @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {   AnnotationAttributes mapperScanAttrs = AnnotationAttributes      .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));   if (mapperScanAttrs != null) {     registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry,         generateBaseBeanName(importingClassMetadata, 0));  }} void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs,     BeanDefinitionRegistry registry, String beanName) {   BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);   builder.addPropertyValue("processPropertyPlaceHolders", true);   Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");   if (!Annotation.class.equals(annotationClass)) {     builder.addPropertyValue("annotationClass", annotationClass);  }   Class<?> markerInterface = annoAttrs.getClass("markerInterface");   if (!Class.class.equals(markerInterface)) {     builder.addPropertyValue("markerInterface", markerInterface);  }   // ...   List<String> basePackages = new ArrayList<>();   basePackages.addAll(       Arrays.stream(annoAttrs.getStringArray("value")).filter(StringUtils::hasText).collect(Collectors.toList()));   basePackages.addAll(Arrays.stream(annoAttrs.getStringArray("basePackages")).filter(StringUtils::hasText)      .collect(Collectors.toList()));   basePackages.addAll(Arrays.stream(annoAttrs.getClassArray("basePackageClasses")).map(ClassUtils::getPackageName)      .collect(Collectors.toList()));   if (basePackages.isEmpty()) {     basePackages.add(getDefaultBasePackage(annoMeta));  }   // ...   builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));   registry.registerBeanDefinition(beanName, builder.getBeanDefinition()); }    }

这里注册了一个核心类MapperScannerConfigurer该类用来扫描Mapper接口,并注册为Bean。4bh28资讯网——每日最新资讯28at.com

4.3 扫描Mapper

public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {   // 实际Mapper接口注册的是MapperFactoryBean对象一个FactoryBean对象  private Class<? extends MapperFactoryBean> mapperFactoryBeanClass = MapperFactoryBean.class;  public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {    // ...    // 该类用来扫描指定包下的类,并如果符合条件(是接口类)将其注册为Bean(FactoryBean)    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);    scanner.setAddToConfig(this.addToConfig);    scanner.setAnnotationClass(this.annotationClass);    scanner.setMarkerInterface(this.markerInterface);    // 为null    scanner.setSqlSessionFactory(this.sqlSessionFactory);    // 为null    scanner.setSqlSessionTemplate(this.sqlSessionTemplate);    // 为null    scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);    // 为null    scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);    scanner.setResourceLoader(this.applicationContext);    scanner.setBeanNameGenerator(this.nameGenerator);    scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);    if (StringUtils.hasText(lazyInitialization)) {      scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));    }    if (StringUtils.hasText(defaultScope)) {      scanner.setDefaultScope(defaultScope);    }    scanner.registerFilters();    scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));  }  public Set<BeanDefinitionHolder> doScan(String... basePackages) {    // 调用父类的doSan方法进行查找所有符合条件的类,并将其注册到容器中    Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);    // 对找到的BeanDefinition对象进行处理    processBeanDefinitions(beanDefinitions);    return beanDefinitions;  }  private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {    AbstractBeanDefinition definition;    BeanDefinitionRegistry registry = getRegistry();    for (BeanDefinitionHolder holder : beanDefinitions) {      definition = (AbstractBeanDefinition) holder.getBeanDefinition();      boolean scopedProxy = false;      if (ScopedProxyFactoryBean.class.getName().equals(definition.getBeanClassName())) {        definition = (AbstractBeanDefinition) Optional          .ofNullable(((RootBeanDefinition) definition).getDecoratedDefinition())          .map(BeanDefinitionHolder::getBeanDefinition).orElseThrow(() -> new IllegalStateException(               "The target bean definition of scoped proxy bean not found. Root bean definition[" + holder + "]"));        scopedProxy = true;      }      String beanClassName = definition.getBeanClassName();      // the mapper interface is the original class of the bean      // but, the actual class of the bean is MapperFactoryBean      definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59      // 重点是这里指定BeanClass对象,一个FactoryBean工厂Bean。      definition.setBeanClass(this.mapperFactoryBeanClass);      definition.getPropertyValues().add("addToConfig", this.addToConfig);      // ...      if (ConfigurableBeanFactory.SCOPE_SINGLETON.equals(definition.getScope()) && defaultScope != null) {        definition.setScope(defaultScope);     }     // ...    }  }}

通过上面的源码可知,所有的Mapper接口都会通过MapperFactoryBean(是个FactoryBean)来注册的Bean对象,在注入Mapper Bean的时候实际注入的是FactoryBean#getObject的返回值类型。4bh28资讯网——每日最新资讯28at.com

4.4 Mapper实例化

通过上面知道了所有的Mapper都是通过FactoryBean来构建的。4bh28资讯网——每日最新资讯28at.com

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> { public T getObject() throws Exception {   // getSqlSession()方法返回的是SqlSessionTemplate对象   return getSqlSession().getMapper(this.mapperInterface); }    }

MapperFactoryBean类继承了SqlSessionDaoSupport对象4bh28资讯网——每日最新资讯28at.com

public abstract class SqlSessionDaoSupport extends DaoSupport { private SqlSessionTemplate sqlSessionTemplate; public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {   if (this.sqlSessionTemplate == null || sqlSessionFactory != this.sqlSessionTemplate.getSqlSessionFactory()) {     this.sqlSessionTemplate = createSqlSessionTemplate(sqlSessionFactory);  } } public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {   this.sqlSessionTemplate = sqlSessionTemplate; } public SqlSession getSqlSession() {   return this.sqlSessionTemplate; }}

在该类中提供了几个setter方法,当在注册当前MapperFactoryBean对象的时候就会注入在MybatisAutoConfiguration自动配置类中注册的SqlSessionFactory和SqlSessionTemplate两个对象。4bh28资讯网——每日最新资讯28at.com

SqlSessionTemplate对象实现了SqlSession接口。4bh28资讯网——每日最新资讯28at.com

到这里你应该知道了,在Spring环境下使用的SqlSession对象实际是SqlSessionTemplate对象。4bh28资讯网——每日最新资讯28at.com

接下来查看SqlSessionTemplate是如何保证线程安全的。4bh28资讯网——每日最新资讯28at.com

4.5 线程安全的SqlSession

在Spring环境下使用的SqlSessionTemplate对象。4bh28资讯网——每日最新资讯28at.com

public class SqlSessionTemplate implements SqlSession, DisposableBean {  private final SqlSession sqlSessionProxy;  // ...  public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {    this.sqlSessionFactory = sqlSessionFactory;    this.executorType = executorType;    this.exceptionTranslator = exceptionTranslator;    // 实际的执行是InvocationHandler#invoke方法    this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[] { SqlSession.class }, new SqlSessionInterceptor());  }  // 这里随便列出一个方法  // 实现的SqlSession接口中的所有方法,实际都是有一个Proxy代理对象执行的  // 该代理对象在构造方法中被创建  public <T> T selectOne(String statement) {    return this.sqlSessionProxy.selectOne(statement);  }  private class SqlSessionInterceptor implements InvocationHandler {    @Override    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {      // 重点是这里的getSqlSession方法了      // 该方法是调用SqlSessionUtils#getSqlSession(这里静态导入)      SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);      try {        Object result = method.invoke(sqlSession, args);        // ...        return result;      }    }  }}

SqlSessionUtils#getSqlSession方法4bh28资讯网——每日最新资讯28at.com

public final class SqlSessionUtils {  public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {    // ...    // 重点来了,先从同步事物管理器TransactionSynchronizationManager    // 中通过sessionFactory为key获取SqlSessionHolder对象    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);    // 如果存在执行返回(保证同一个线程使用同一个SqlSession对象)    SqlSession session = sessionHolder(executorType, holder);    if (session != null) {      return session;    }    // 通过SqlSessionFactory对象获取SqlSession对象    session = sessionFactory.openSession(executorType);    // 将获取的SqlSession对象保存到ThreadLocal中    registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);    return session;  }  private static void registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator, SqlSession session) {    SqlSessionHolder holder;    if (TransactionSynchronizationManager.isSynchronizationActive()) {      Environment environment = sessionFactory.getConfiguration().getEnvironment();      if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) {        // 创建SqlSessionHolder对象,将创建的SqlSession对象保存        holder = new SqlSessionHolder(session, executorType, exceptionTranslator);        // 将当前的SqlSessionHolder对象绑定到ThreadLocal中        TransactionSynchronizationManager.bindResource(sessionFactory, holder);        // 注册事务回调事件        TransactionSynchronizationManager.registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));        // 将资源标记为与事务同步。        holder.setSynchronizedWithTransaction(true);        holder.requested();      } else {        // ...      }    }    // ...  }}public abstract class TransactionSynchronizationManager {  private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal<>("Transactional resources");      // 将资源绑定到当前的线程对象中  public static void bindResource(Object key, Object value) throws IllegalStateException {    Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);    Map<Object, Object> map = resources.get();    // set ThreadLocal Map if none found    if (map == null) {      map = new HashMap<>();      resources.set(map);    }    Object oldValue = map.put(actualKey, value);    // Transparently suppress a ResourceHolder that was marked as void...    if (oldValue instanceof ResourceHolder && ((ResourceHolder) oldValue).isVoid()) {      oldValue = null;    }    // ...      }  public static Object getResource(Object key) {    Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);    Object value = doGetResource(actualKey);    return value;  }  @Nullable  private static Object doGetResource(Object actualKey) {    // 从当前的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;  }}

通过上面的源码分析清楚的知道,在Spring中SqlSession的线程安全是通过ThreadLocal来保证的,通过Spring提供的事务通过管理器来保存SqlSession对象,这样就使得同一个线程获取的是同一个SqlSession。4bh28资讯网——每日最新资讯28at.com

4.6 事务管理

在事务管理方法在Spring环境下使用的是SpringManagedTransactionFactory事务管理器工厂4bh28资讯网——每日最新资讯28at.com

public class SpringManagedTransactionFactory implements TransactionFactory {  @Override  public Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit) {    return new SpringManagedTransaction(dataSource);  }  @Override  public Transaction newTransaction(Connection conn) {    throw new UnsupportedOperationException("New Spring transactions require a DataSource");  }  @Override  public void setProperties(Properties props) {  }    }

事务对象4bh28资讯网——每日最新资讯28at.com

public class SpringManagedTransaction implements Transaction {  private static final Logger LOGGER = LoggerFactory.getLogger(SpringManagedTransaction.class);  private final DataSource dataSource;  private Connection connection;  private boolean isConnectionTransactional;  private boolean autoCommit;  public SpringManagedTransaction(DataSource dataSource) {    notNull(dataSource, "No DataSource specified");    this.dataSource = dataSource;  }  @Override  public Connection getConnection() throws SQLException {    if (this.connection == null) {      openConnection();    }    return this.connection;  }  private void openConnection() throws SQLException {    // 在Spring环境下,事务由Spring管理,所以这里先从Spring的ThreadLocal中获取连接对象    this.connection = DataSourceUtils.getConnection(this.dataSource);    this.autoCommit = this.connection.getAutoCommit();    this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);  }  @Override  public void commit() throws SQLException {    if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) {      this.connection.commit();    }  }  @Override  public void rollback() throws SQLException {    if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) {      this.connection.rollback();    }  }  @Override  public void close() throws SQLException {    DataSourceUtils.releaseConnection(this.connection, this.dataSource);  }  @Override  public Integer getTimeout() throws SQLException {    ConnectionHolder holder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);    if (holder != null && holder.hasTimeout()) {      return holder.getTimeToLiveInSeconds();    }    return null;  }}public abstract class DataSourceUtils {  public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException {    try {      return doGetConnection(dataSource);    }  }    public static Connection doGetConnection(DataSource dataSource) throws SQLException {    ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);    if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {      conHolder.requested();      if (!conHolder.hasConnection()) {        conHolder.setConnection(fetchConnection(dataSource));      }      return conHolder.getConnection();    }    Connection con = fetchConnection(dataSource);    if (TransactionSynchronizationManager.isSynchronizationActive()) {      try {        ConnectionHolder holderToUse = conHolder;        if (holderToUse == null) {          holderToUse = new ConnectionHolder(con);        } else {          holderToUse.setConnection(con);        }        holderToUse.requested();        TransactionSynchronizationManager.registerSynchronization(              new ConnectionSynchronization(holderToUse, dataSource));        holderToUse.setSynchronizedWithTransaction(true);        if (holderToUse != conHolder) {          TransactionSynchronizationManager.bindResource(dataSource, holderToUse);        }      }      //...catch    }    return con;  }}

如果SqlSession没有被Spring管理(也就是事务是自行处理没有用Spring的事务管理@Transactional)那么Spring会强制提交事务。如果没有在Spring环境下,Mybatis事务是不会自动提交的(的看你openSession方法参数如何传)。4bh28资讯网——每日最新资讯28at.com

4bh28资讯网——每日最新资讯28at.com

本文链接:http://www.28at.com/showinfo-26-10419-0.html为什么说MyBatis默认的DefaultSqlSession是线程不安全?

声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com

上一篇: 一文带你弄懂 CSS 布局知识

下一篇: 事务提交之后异步执行工具类封装

标签:
  • 热门焦点
Top