mybatis-config.xml
UsersMapper.xml
UsersMapper.java
package com.pack.mapper;
import java.util.List;
import com.pack.domain.Users;
public interface UsersMapper {
List selectList() ;
}
Users.java
public class Users{
private String id ;
private String username ;
private String password ;
}
UsersMapperTest.java测试类
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个线程同时查询,结果如下:
### Cause: java.lang.ClassCastException: org.apache.ibatis.executor.ExecutionPlaceholder cannot be cast to java.util.List
at 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.List
at 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时出现了类型转换错误。
根据错误信息,把错误定位到DefaultSqlSession.java:153
public class DefaultSqlSession implements SqlSession {
private List 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();
}
}
}
继续根据错误日志,确定是执行下面这行代码出现错误
executor.query(ms, wrapCollection(parameter), rowBounds, handler);
而executor根据错误日志确定为BaseExecutor类
public abstract class BaseExecutor implements Executor {
public List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
// ...
List list;
try {
queryStack++;
// 从本地缓存中获取数据,如果有会强制转换为List对象
// 位置1:
list = resultHandler == null ? (List) 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 List queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List 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,所以这里就会抛出类型转换异常了。
如果想正确执行,只能是每个线程创建一个新的SqlSession对象。
// 获取SqlSession对象
// SqlSessionFactory的实现是DefaultSqlSessionFactory对象
sqlSessionFactory.openSession()
进入openSession()方法
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.java
/**
* 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.
在Springboot中是如何保证线程安全的呢?
org.mybatis.spring.boot
mybatis-spring-boot-starter
2.1.4
@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 {
}
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)
在这Import类中会注册一个MapperScannerConfigurer配置类
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 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。
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 doScan(String... basePackages) {
// 调用父类的doSan方法进行查找所有符合条件的类,并将其注册到容器中
Set beanDefinitions = super.doScan(basePackages);
// 对找到的BeanDefinition对象进行处理
processBeanDefinitions(beanDefinitions);
return beanDefinitions;
}
private void processBeanDefinitions(Set 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的返回值类型。
通过上面知道了所有的Mapper都是通过FactoryBean来构建的。
public class MapperFactoryBean extends SqlSessionDaoSupport implements FactoryBean {
public T getObject() throws Exception {
// getSqlSession()方法返回的是SqlSessionTemplate对象
return getSqlSession().getMapper(this.mapperInterface);
}
}
MapperFactoryBean类继承了SqlSessionDaoSupport对象
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两个对象。
SqlSessionTemplate对象实现了SqlSession接口。
到这里你应该知道了,在Spring环境下使用的SqlSession对象实际是SqlSessionTemplate对象。
接下来查看SqlSessionTemplate是如何保证线程安全的。
在Spring环境下使用的SqlSessionTemplate对象。
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 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方法
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
通过上面的源码分析清楚的知道,在Spring中SqlSession的线程安全是通过ThreadLocal来保证的,通过Spring提供的事务通过管理器来保存SqlSession对象,这样就使得同一个线程获取的是同一个SqlSession。
在事务管理方法在Spring环境下使用的是SpringManagedTransactionFactory事务管理器工厂
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) {
}
}
事务对象
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方法参数如何传)。
分享文章:为什么说MyBatis默认的DefaultSqlSession是线程不安全?
转载来于:http://www.shufengxianlan.com/qtweb/news1/313851.html
网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联