国产精品嫩草99av在线_一区在线视频观看_欧美高清一区_欧美 日韩 国产 一区_99精品欧美一区二区三区_久久大香伊蕉在人线观看热2_一色屋精品视频在线观看网站_在线亚洲国产精品网站_亚洲区一区二区三区_你懂的视频一区二区

當前位置:首頁 > 科技  > 軟件

為什么說MyBatis默認的DefaultSqlSession是線程不安全?

來源: 責編: 時間:2023-09-18 21:40:26 394觀看
導(dǎo)讀1 環(huán)境準備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 環(huán)境準備

mybatis-config.xmlukN28資訊網(wǎng)——每日最新資訊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.xmlukN28資訊網(wǎng)——每日最新資訊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.javaukN28資訊網(wǎng)——每日最新資訊28at.com

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

Users.javaukN28資訊網(wǎng)——每日最新資訊28at.com

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

UsersMapperTest.java測試類ukN28資訊網(wǎng)——每日最新資訊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個線程同時查詢,結(jié)果如下:ukN28資訊網(wǎng)——每日最新資訊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類型轉(zhuǎn)換異常。也就是在多個線程同時使用SqlSession時出現(xiàn)了類型轉(zhuǎn)換錯誤。ukN28資訊網(wǎng)——每日最新資訊28at.com

2 錯誤分析

根據(jù)錯誤信息,把錯誤定位到DefaultSqlSession.java:153ukN28資訊網(wǎng)——每日最新資訊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();  }}    }

繼續(xù)根據(jù)錯誤日志,確定是執(zhí)行下面這行代碼出現(xiàn)錯誤ukN28資訊網(wǎng)——每日最新資訊28at.com

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

而executor根據(jù)錯誤日志確定為BaseExecutor類ukN28資訊網(wǎng)——每日最新資訊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++;     // 從本地緩存中獲取數(shù)據(jù),如果有會強制轉(zhuǎn)換為List對象     // 位置1:     list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;     if (list != null) {       handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);    } else {       // 如果緩存中沒有,則會進入該方法執(zhí)行       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);  }   // 將查詢出來的數(shù)據(jù)緩存起來   localCache.putObject(key, list);   // 位置3   if (ms.getStatementType() == StatementType.CALLABLE) {     localOutputParameterCache.putObject(key, parameter);  }   return list;}}public enum ExecutionPlaceholder { EXECUTION_PLACEHOLDER}

分析:當線程1執(zhí)行到‘位置2’時,此時緩存中緩存了ExecutionPlaceholder枚舉值,這是線程2開始執(zhí)行‘位置1’此時線程2從緩存中是能獲取值,此值是ExecutionPlaceholder枚舉值,該值怎么可能轉(zhuǎn)換為List,所以這里就會拋出類型轉(zhuǎn)換異常了。ukN28資訊網(wǎng)——每日最新資訊28at.com

如果想正確執(zhí)行,只能是每個線程創(chuàng)建一個新的SqlSession對象。ukN28資訊網(wǎng)——每日最新資訊28at.com

3 默認SqlSession實現(xiàn)

// 獲取SqlSession對象// SqlSessionFactory的實現(xiàn)是DefaultSqlSessionFactory對象sqlSessionFactory.openSession()

進入openSession()方法ukN28資訊網(wǎng)——每日最新資訊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默認實現(xiàn)使用的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.javaukN28資訊網(wǎng)——每日最新資訊28at.com

/*** The default implementation for {@link SqlSession}.* Note that this class is not Thread-Safe.** @author Clinton Begin*/public class DefaultSqlSession implements SqlSession { // ...    }// 在這注釋中已經(jīng)提到了該類is not Thread-Safe.

4 Spring如何處理

在Springboot中是如何保證線程安全的呢?ukN28資訊網(wǎng)——每日最新資訊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注解中應(yīng)用了@Import(MapperScannerRegistrar.class)ukN28資訊網(wǎng)——每日最新資訊28at.com

在這Import類中會注冊一個MapperScannerConfigurer配置類ukN28資訊網(wǎng)——每日最新資訊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。ukN28資訊網(wǎng)——每日最新資訊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) {    // 調(diào)用父類的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的時候?qū)嶋H注入的是FactoryBean#getObject的返回值類型。ukN28資訊網(wǎng)——每日最新資訊28at.com

4.4 Mapper實例化

通過上面知道了所有的Mapper都是通過FactoryBean來構(gòu)建的。ukN28資訊網(wǎng)——每日最新資訊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對象ukN28資訊網(wǎng)——每日最新資訊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兩個對象。ukN28資訊網(wǎng)——每日最新資訊28at.com

SqlSessionTemplate對象實現(xiàn)了SqlSession接口。ukN28資訊網(wǎng)——每日最新資訊28at.com

到這里你應(yīng)該知道了,在Spring環(huán)境下使用的SqlSession對象實際是SqlSessionTemplate對象。ukN28資訊網(wǎng)——每日最新資訊28at.com

接下來查看SqlSessionTemplate是如何保證線程安全的。ukN28資訊網(wǎng)——每日最新資訊28at.com

4.5 線程安全的SqlSession

在Spring環(huán)境下使用的SqlSessionTemplate對象。ukN28資訊網(wǎng)——每日最新資訊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;    // 實際的執(zhí)行是InvocationHandler#invoke方法    this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[] { SqlSession.class }, new SqlSessionInterceptor());  }  // 這里隨便列出一個方法  // 實現(xiàn)的SqlSession接口中的所有方法,實際都是有一個Proxy代理對象執(zhí)行的  // 該代理對象在構(gòu)造方法中被創(chuàng)建  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方法了      // 該方法是調(diào)用SqlSessionUtils#getSqlSession(這里靜態(tài)導(dǎo)入)      SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);      try {        Object result = method.invoke(sqlSession, args);        // ...        return result;      }    }  }}

SqlSessionUtils#getSqlSession方法ukN28資訊網(wǎng)——每日最新資訊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);    // 如果存在執(zhí)行返回(保證同一個線程使用同一個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) {        // 創(chuàng)建SqlSessionHolder對象,將創(chuàng)建的SqlSession對象保存        holder = new SqlSessionHolder(session, executorType, exceptionTranslator);        // 將當前的SqlSessionHolder對象綁定到ThreadLocal中        TransactionSynchronizationManager.bindResource(sessionFactory, holder);        // 注冊事務(wù)回調(diào)事件        TransactionSynchronizationManager.registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));        // 將資源標記為與事務(wù)同步。        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提供的事務(wù)通過管理器來保存SqlSession對象,這樣就使得同一個線程獲取的是同一個SqlSession。ukN28資訊網(wǎng)——每日最新資訊28at.com

4.6 事務(wù)管理

在事務(wù)管理方法在Spring環(huán)境下使用的是SpringManagedTransactionFactory事務(wù)管理器工廠ukN28資訊網(wǎng)——每日最新資訊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) {  }    }

事務(wù)對象ukN28資訊網(wǎng)——每日最新資訊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環(huán)境下,事務(wù)由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管理(也就是事務(wù)是自行處理沒有用Spring的事務(wù)管理@Transactional)那么Spring會強制提交事務(wù)。如果沒有在Spring環(huán)境下,Mybatis事務(wù)是不會自動提交的(的看你openSession方法參數(shù)如何傳)。ukN28資訊網(wǎng)——每日最新資訊28at.com

ukN28資訊網(wǎng)——每日最新資訊28at.com

本文鏈接:http://m.rrqrq.com/showinfo-26-10419-0.html為什么說MyBatis默認的DefaultSqlSession是線程不安全?

聲明:本網(wǎng)頁內(nèi)容旨在傳播知識,若有侵權(quán)等問題請及時與本網(wǎng)聯(lián)系,我們將在第一時間刪除處理。郵件:2376512515@qq.com

上一篇: 一文帶你弄懂 CSS 布局知識

下一篇: 事務(wù)提交之后異步執(zhí)行工具類封裝

標簽:
  • 熱門焦點
  • 7月安卓手機性價比榜:努比亞+紅魔兩款新機入榜

    7月登場的新機有努比亞Z50S Pro和紅魔8S Pro,除了三星之外目前唯二的兩款搭載超頻版驍龍8Gen2處理器的產(chǎn)品,而且努比亞和紅魔也一貫有著不錯的性價比,所以在本次的性價比榜單
  • 5月iOS設(shè)備性能榜:M1 M2依舊是榜單前五

    和上個月一樣,沒有新品發(fā)布的iOS設(shè)備性能榜的上榜設(shè)備并沒有什么更替,僅僅只有跑分變化而產(chǎn)生的排名變動,剛剛開始的蘋果WWDC2023,推出的產(chǎn)品也依舊是新款Mac Pro、新款Mac Stu
  • 本地生活這塊肥肉,拼多多也想吃一口

    出品/壹覽商業(yè) 作者/李彥編輯/木魚拼多多也看上本地生活這塊蛋糕了。近期,拼多多在App首頁&ldquo;充值中心&rdquo;入口上線了本機生活界面。壹覽商業(yè)發(fā)現(xiàn),該界面目前主要
  • 大廠卷向扁平化

    來源:新熵作者丨南枝 編輯丨月見大廠職級不香了。俗話說,兵無常勢,水無常形,互聯(lián)網(wǎng)企業(yè)調(diào)整職級體系并不稀奇。7月13日,淘寶天貓集團啟動了近年來最大的人力制度改革,目前已形成一
  • 阿里大調(diào)整

    來源:產(chǎn)品劉有媒體報道稱,近期淘寶天貓集團啟動了近年來最大的人力制度改革,涉及員工績效、層級體系等多個核心事項,目前已形成一個初步的&ldquo;征求意見版&rdquo;:1、取消P序列
  • 馮提莫簽約抖音公會 前“斗魚一姐”消失在直播間

    來源:直播觀察提起&ldquo;馮提莫&rdquo;這個名字,很多網(wǎng)友或許聽過,但應(yīng)該不記得她是哪位主播了。其實,作為曾經(jīng)的&ldquo;斗魚一姐&rdquo;,馮提莫在游戲直播的年代影響力不輸于現(xiàn)
  • 蘋果公司要求三星和LG Display生產(chǎn)「無邊框」OLED iPhone顯示屏

    據(jù) The Elec 報道,蘋果已要求其供應(yīng)商為未來的 iPhone 型號開發(fā)「無邊框」OLED 顯示面板。蘋果顯然已要求三星和 LG Display 開發(fā)新的 OLED 顯示面
  • Windows 11發(fā)布,微軟一改往常對老機型開放的態(tài)度

    距離 Windows 11 發(fā)布已經(jīng)過去一周,在過去一周里,很多數(shù)碼愛好者圍繞其對 Android 應(yīng)用的支持、對老機型的升級問題展開了激烈討論。與以往不同的是,在這次大
  • 上海舉辦人工智能大會活動,建設(shè)人工智能新高地

    人工智能大會在上海浦江兩岸隆重拉開帷幕,人工智能新技術(shù)、新產(chǎn)品、新應(yīng)用、新理念集中亮相。8月30日晚,作為大會的特色活動之一的上海人工智能發(fā)展盛典人工
Top 国产精品嫩草99av在线_一区在线视频观看_欧美高清一区_欧美 日韩 国产 一区_99精品欧美一区二区三区_久久大香伊蕉在人线观看热2_一色屋精品视频在线观看网站_在线亚洲国产精品网站_亚洲区一区二区三区_你懂的视频一区二区
亚洲欧洲精品成人久久奇米网| 亚洲精品成a人| av午夜一区麻豆| 欧美激情在线免费观看| 在线观看成人av| 亚洲国产乱码最新视频| 欧美亚洲自拍偷拍| 粉嫩欧美一区二区三区高清影视| 久久亚洲一级片| 欧美日韩mv| 亚洲福利视频导航| 在线播放/欧美激情| 色综合久久中文综合久久牛| 亚洲精品久久嫩草网站秘色| 一本久久a久久精品亚洲| 国产一区二区视频在线播放| 国产亚洲人成网站| 国产日韩欧美在线播放不卡| 精品一区二区免费视频| 精品国产乱码久久| 日韩午夜av| 六月丁香婷婷色狠狠久久| 精品成人在线观看| 一区二区久久| 国产高清不卡一区| 自拍视频在线观看一区二区| 色激情天天射综合网| 91伊人久久大香线蕉| 一区二区三区在线观看视频| 欧美日韩成人高清| eeuss鲁片一区二区三区 | 色噜噜夜夜夜综合网| 成人精品国产免费网站| 自拍偷在线精品自拍偷无码专区| 在线看日本不卡| 欧美成人嫩草网站| 奇米四色…亚洲| 国产精品天干天干在线综合| 91黄视频在线| 91视频免费观看| 蜜乳av一区二区| 亚洲国产精品t66y| 欧美人xxxx| 国产精品嫩草99av在线| av亚洲精华国产精华精| 日韩经典一区二区| 欧美国产精品v| 欧美日韩三级视频| 99xxxx成人网| 99久久精品免费看国产免费软件| 五月婷婷久久丁香| 国产午夜精品在线观看| 在线免费不卡视频| 1024成人| eeuss影院一区二区三区| 日本视频中文字幕一区二区三区| 亚洲国产成人一区二区三区| 91精品国产一区二区三区| 国产精品日韩高清| 欧美不卡高清| 亚洲成va人在线观看| 久久久精品动漫| 欧美日韩网址| 成年人国产精品| 精品在线视频一区| 亚洲丶国产丶欧美一区二区三区| 国产欧美日韩在线看| 日韩一区二区三区免费观看 | 欧美日韩天堂| 成人国产精品免费观看视频| 麻豆精品一二三| 亚洲午夜精品17c| 中文字幕一区三区| 久久综合久久久久88| 欧美日韩国产不卡| 久久人人九九| 国产精品亚洲产品| 在线高清一区| 国产精品v欧美精品v日韩精品| 丁香婷婷深情五月亚洲| 久久精品国产精品青草| 调教+趴+乳夹+国产+精品| 亚洲精选视频在线| 国产精品灌醉下药二区| 日本一区二区三区久久久久久久久不| 欧美一区二区人人喊爽| 欧美日韩一区二区在线观看 | 成人在线综合网站| 国产一二三精品| 韩国v欧美v亚洲v日本v| 奇米888四色在线精品| 天天做天天摸天天爽国产一区| 一区二区三区色| 亚洲激情中文1区| 亚洲精品日日夜夜| 亚洲柠檬福利资源导航| 亚洲色图在线播放| 亚洲素人一区二区| 亚洲色大成网站www久久九九| 国产精品不卡在线| 中文字幕一区二区三区不卡| 中文字幕人成不卡一区| 亚洲视频每日更新| 亚洲精品老司机| 亚洲女人的天堂| 一区二区三区不卡在线观看 | www.日韩精品| 欧美极品一区| 伊人影院久久| 国产日韩欧美一区| 鲁大师成人一区二区三区| 久久天堂国产精品| 色婷婷av一区二区| 欧美视频你懂的| 日韩亚洲欧美在线观看| 亚洲精品一区二区三区福利| 国产日韩一级二级三级| 中文字幕中文字幕一区| 亚洲黄色片在线观看| 亚洲不卡一区二区三区| 免费观看91视频大全| 国产精品夜夜爽| av电影在线观看完整版一区二区 | 99re6这里只有精品视频在线观看| 女同一区二区| 国产精品手机视频| 欧美午夜影院一区| 日韩免费视频一区二区| 亚洲国产精品成人综合| 亚洲精品一二三四区| 日韩一区精品视频| 高潮精品一区videoshd| 国内精品亚洲| 久久久99国产精品免费| 91精品麻豆日日躁夜夜躁| 久久婷婷色综合| 久久精品久久综合| 国产色产综合色产在线视频| 亚洲卡通动漫在线| 精品制服美女久久| 91在线观看视频| 中文一区二区| 欧美浪妇xxxx高跟鞋交| 欧美韩国日本一区| 视频一区中文字幕国产| 国产成人av影院| 在线精品亚洲一区二区| 色噜噜夜夜夜综合网| 欧美精品一区二| 夜夜爽夜夜爽精品视频| 国产真实精品久久二三区| 牛牛国产精品| 日本精品一区二区三区高清| 久久久久久久久久美女| 亚洲福利一二三区| 成人免费电影视频| 亚洲免费综合| 欧美第一区第二区| 一区二区欧美在线观看| 国产成人av资源| 亚洲一区精彩视频| 精品国产乱码久久久久久牛牛| 艳妇臀荡乳欲伦亚洲一区| 成人永久看片免费视频天堂| 夜夜夜久久久| www亚洲一区| 亚洲精品中文在线影院| 成人午夜精品在线| 亚洲免费黄色| 欧美精品一区二区蜜臀亚洲| 亚洲国产人成综合网站| 91免费国产视频网站| 在线免费精品视频| 日韩伦理免费电影| 粉嫩一区二区三区在线看| 亚洲一区二区三区在线观看视频| 26uuu精品一区二区| 蜜臀av一区二区在线免费观看| 欧美日韩综合另类| 在线不卡中文字幕| 亚洲电影一区二区三区| 色综合久久综合网| 在线播放国产精品二区一二区四区| 一区二区免费在线播放| 欧美在线观看天堂一区二区三区| 91国偷自产一区二区开放时间| 国产精品狼人久久影院观看方式| 国产精品资源站在线| 一本色道综合亚洲| 亚洲欧洲在线观看av| 成人激情免费网站| 欧美日韩免费在线视频| 亚洲国产美国国产综合一区二区| 欧美国产免费| 欧美成人a∨高清免费观看| 久久se精品一区精品二区| 免费永久网站黄欧美| 国产精品五月天| 99久久精品情趣| 欧美一区永久视频免费观看|