MyBatisBatchItemWriter Cannot change the ExecutorType when there is an existing transaction
Posted ppjj
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MyBatisBatchItemWriter Cannot change the ExecutorType when there is an existing transaction相关的知识,希望对你有一定的参考价值。
但凡使用mybatis,同时与spring集成使用时,接下来要说的这个问题是躲不了的。众所周知,mybatis的SqlSessionFactory在获取一个SqlSession时使用默认Executor或必须要指定一个Executor,这样一来,在同一个SqlSession的生命周期中,要想切换Executor是不可能的,比如在一个复杂业务中:
sqlSession.insert("insertMainOrder", mainOrder); // -----(1) for(OrderInfo childOrder : childOrderList){ // -----(2) sqlSession.insert("insertChildOrder", childOrder); }
如果sqlSession使用ExecutorType.SIMPLE open出来的话,(2)处如果是用Jdbc batch操作将是不可能的,当然(2)处如果你再新open一个ExecutorType.BATCH的新的SqlSession的话:A、如果整个业务在无事务环境下运行的话,则不会报错,但是底层会使用多个不同的Connection,浪费资源,最重要的是无法保持在同一个事务中。B、如果整个业务在一个事务中运行的话(如propagation=Propagation.REQUIRED),则会在mybatis-spring框架中报错:TransientDataAccessResourceException("Cannot change the ExecutorType when there is an existing transaction"),也就是标题中的错误,究其原因是因为在mybatis-spring框架中在有事务情况下SqlSession是通过sessionFactory与当前线程绑定的,新open出来的SqlSession会与上一个使用的SqlSession的ExecutorType进行比较,如果ExecutorType改变了,则直接报错。
首先了解下相关知识,mybatis的执行器有三种类型:
- ExecutorType.SIMPLE
这个类型不做特殊的事情,它只为每个语句创建一个PreparedStatement。
- ExecutorType.REUSE
这种类型将重复使用PreparedStatements。
- ExecutorType.BATCH
这个类型批量更新,性能更优,但batch模式也有自己的问题,比如在Insert操作时,在事务没有提交之前,是没有办法获取到自增的id,这在某型情形下是不符合业务要求的。
使用方式:
java代码中,创建模板的时候:
new SqlSessionTemplate(sqlSessionFactory, ExecutorType.BATCH);
或者
SqlSession session = getSqlSessionFactory().openSession(ExecutorType.BATCH);
xml文件配置:
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSessionFactory"/>
<constructor-arg index="1" value="BATCH"/>
</bean>
下面是具体执行方法:
1,ExecutorType.SIMPLE:可以返回自增键,自增键会在事务提交后,自动设置到传入的user对象中,只需要在mapper文件中,增加属性: useGeneratedKeys="true" keyProperty="productId":
<!-- 插入一个user --> <insert id="insertUser" parameterType="User" statementType="PREPARED" useGeneratedKeys="true" keyProperty="userId"> INSERT INTO user ( <include refid="userColumns" /> , create_time, update_time) VALUES (#{email}, #{pwd},#{nickname}, #{phone}, #{sign}, #{age}, #{birthday}, #{createTime}, now()) </insert>
2,ExecutorType.SIMPLE,借助foreach动态sql语句,使用Insert values(...),(...),(...) 的方式,这种方式无法取到自增键:
<!-- 批量插入user --> <insert id="insertUsers" parameterType="map" useGeneratedKeys="true" keyProperty="userId"> INSERT INTO user ( <include refid="userColumns" /> , create_time, update_time) VALUES <foreach collection="users" item="userCommand" index="index" separator=","> (#{userCommand.email}, #{userCommand.pwd},#{userCommand.nickname}, #{userCommand.phone}, #{userCommand.sign}, #{userCommand.age}, #{userCommand.birthday}, #{userCommand.sex}, #{userCommand.createTime}, now()) </foreach> </insert>
3,ExecutorType.BATCH,但是SqlSession的执行器类型一旦设置就无法动态修改,因为这个方法仍然需要包在事务中。所以如果在配置文件中设置了执行器为SIMPLE,当要使用BATCH执行器时,需要临时获取:
SqlSession session = sqlSessionTemplate.getSqlSessionFactory() .openSession(ExecutorType.BATCH, false); try { UserDao batchUserDao = session.getMapper(UserDao.class); for (UserCommand user : users) { batchUserDao.insertUser(user); } session.commit(); // 清理缓存,防止溢出 session.clearCache(); // 添加位置信息 userLbsDao.insertUserLbses(users); } finally { session.close(); }
4,ExecutorType.BATCH,全部改成batch模式。但是没有办法获取到自增的id。
以上是关于MyBatisBatchItemWriter Cannot change the ExecutorType when there is an existing transaction的主要内容,如果未能解决你的问题,请参考以下文章