但凡使用mybatis,同時與spring集成使用時,接下來要說的這個問題是躲不了的。衆所周知,mybatis的SqlSessionFactory在獲取一個SqlSession時使用默認Executor或必需要指定一個Executor,這樣一來,在同一個SqlSession的生命週期中,要想切換Executor是不可能的,好比在一個複雜業務中:java
sqlSession.insert("insertMainOrder", mainOrder); // -----(1) for(OrderInfo childOrder : childOrderList){ // -----循環插入是能夠的 sqlSession.insert("insertChildOrder", childOrder); }
可是使用MyBatisBatchItemWriter是不行的,由於它使用了SqlSessionTemplate的batch屬性,官方解釋以下:spring
ItemWriter that uses the batching features from SqlSessionTemplate to execute a batch of statements for all itemsprovided.
如下是xml配置文件實現,也能夠代碼實現:sql
<!--結果寫入庫--> <bean id="pickUpWriter" class="org.mybatis.spring.batch.MyBatisBatchItemWriter" scope="step"> <property name="sqlSessionFactory" ref="sqlSessionFactory"/> <property name="statementId" value="com.cwenao.cc.basic.dao.NoticeInfoDao.insertSelective"/> </bean>
若是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改變了,則直接報錯。數組
下面是stackoverflow的解釋:緩存
Because it sais it: you can't change the executor type inside the transaction.
it looks like you've tried to batch-write something as the part of more broad transaction that includes other SQL operations, but that transaction was started with SIMPLE (default) or REUSE executor type.
It's obvious, that batch-write requires BATCH executor type, though once the transaction started, it's executor type can not be changed. So, perform your batch operations in separate transaction, or run nested transaction, if your RDBMS allows it.
首先了解下相關知識,mybatis的執行器有三種類型:session
這個類型不作特殊的事情,它只爲每一個語句建立一個PreparedStatement。mybatis
這種類型將重複使用PreparedStatements。app
這個類型批量更新,性能更優,但batch模式也有本身的問題,好比在Insert操做時,在事務沒有提交以前,是沒有辦法獲取到自增的id,這在某型情形下是不符合業務要求的,並且假若有一條sql語句報錯,則整個事務回滾,雖然這條sql語句不是過重要。注意:在同一事務中batch模式和simple模式之間沒法轉換。框架
使用方式:ide
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",在外部java代碼中添加循環語句,xml中就是單條數據插入:
<!-- 插入一個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(...),(...),(...) 的方式,這種方式沒法取到自增鍵,外部java代碼中就不須要循環,在xml中使用循環,可是須要注意的是,該SQL語句不能在實現ItemWriter接口的類中調用,否則會報異常:Cannot change the ExecutorType when there is an existing transaction:
<!-- 批量插入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>
或者這樣寫在代碼中,不須要xml配置文件:
@Component("ledgerWriter") public class LedgerWriter implements ItemWriter<Ledger> { @Resource private NamedParameterJdbcTemplate jdbcTemplate; private static final String sql = "insert into tableA(a,b) values (:col1,:col2)"; /** * 寫入數據 * * @param ledgers */ public void write(List<? extends Ledger> ledgers) throws Exception { //將userDtoList轉化成BeanPropertySqlParameterSource[]數組 List<BeanPropertySqlParameterSource> userSourceList = new ArrayList<BeanPropertySqlParameterSource>(); for (UserDto userDto : userDtoList) { userSourceList.add(new BeanPropertySqlParameterSource(userDto)); } BeanPropertySqlParameterSource[] beanSources = userSourceList.toArray(new BeanPropertySqlParameterSource[userSourceList.size()]); jdbcTemplate.batchUpdate(sql, beanSources); } }
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,spring事務一塊兒使用,將沒法回滾,必須注意,最好單獨使用。須要用到一個類:MyBatisBatchItemWriter,它是批量執行更新操做。