1. 程式人生 > >基於MyBatis的批量插入更新實現

基於MyBatis的批量插入更新實現

我的問題:如何在MyBatis下實現批量插入和更新操作?

我的困惑:

1、貌似MyBatis不支援在XML配置的SQL中帶有分號“;”,這就要求儘可能在配置中通過一條SQL語句實現;

2、不同資料庫可支援的批量插入SQL語句的寫法有差別,Oracle批量插入SQL語句如下:

寫法:"INSERT ALL INTO  a表 VALUES(各個值)  INTO a表 VALUES (其它值) INTO a表 VALUES(其它值) ....再跟一個SELECT 語句"

參考文章:http://blog.csdn.net/chenleixing/article/details/45165761/

3、在SQL語句中拼湊批量插入語句,資料量大了終究是不妥,且不方便獲取插入資料的ID值。

4、如果排除SQL語句實現批量插入的方案,則可以考慮採用java程式碼,jdbc或mybatis執行分批提交實現。

參考文章:http://www.cnblogs.com/robinjava77/p/5530681.html

我的實現:

權衡再三,決定採用MyBatis分批提交以實現批量操作,同時這也會使程式碼更具通用性。

批量執行器:

public abstract class BatchExecutor<T> {

	private List<T> list = null;

	public BatchExecutor(List<T> list) {
		this.list = list;
	}

	public void execute() throws Exception {
		if (CollectionUtils.isEmpty(list)) {
			return;
		}

		SqlSessionFactoryBean sqlSessionFactory = (SqlSessionFactoryBean) ContextUtils.getBean("sqlSessionFactory");
		SqlSession batchSqlSession = null;
		try {
			batchSqlSession = sqlSessionFactory.getObject().openSession(ExecutorType.BATCH, false);
			BaseMapper baseMapper = batchSqlSession.getMapper(BaseMapper.class);
			int batchCount = 500;
			for (int index = 0; index < list.size(); index++) {
				T object = list.get(index);
				doExecute(object, baseMapper);
				if (index != 0 && index % batchCount == 0) {
					batchSqlSession.commit();
				}
			}
			batchSqlSession.commit();
		} catch (Exception e) {
			batchSqlSession.rollback();
			throw e;
		} finally {
			if (batchSqlSession != null) {
				batchSqlSession.close();
			}
		}
	}

	protected abstract void doExecute(T object, BaseMapper baseMapper) throws Exception;
}

業務方法:

	@Override
	public void batchAddNew(List<T> list) throws Exception {

		BatchExecutor<T> executor = new BatchExecutor<T>(list) {
			@Override
			protected void doExecute(T object, BaseMapper baseMapper) throws Exception {
				addNew(object, baseMapper);
			}
		};

		executor.execute();
	}


這篇文章總結寫得挺不錯的,摘錄參考如下:

----------------------------------------------------------------------------

前言:做一個數據同步專案,要求:同步資料不丟失的情況下,提高插入效能。

專案DB框架:Mybatis。DataBase:Oracle。

----------------------------------------------------------------------------

批量插入資料方式:

一、Mybatis 全域性設定批處理;

二、Mybatis 區域性設定批處理;

三、Mybatis foreach批量插入:

①SELECT UNION ALL;

②BEGIN INSERT INTO ...;INSERT INTO...;...;END;

四、java自帶的批處理插入;

五、其他方式

-----------------------------------------------------------------------------

先說結論:Mybatis(全域性/區域性)批處理和java自帶的批處理 效能上差不多,屬於最優處理辦法,我這邊各種測試後,最後採用Mybatis區域性批處理方式。

一、Mybatis 全域性設定批處理

先上Spring-Mybatis.xml 配置資訊

複製程式碼
 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <beans xmlns="http://www.springframework.org/schema/beans"
 3        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 4        xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
 5        xsi:schemaLocation="http://www.springframework.org/schema/beans
 6                            http://www.springframework.org/schema/beans/spring-beans.xsd
 7                            http://www.springframework.org/schema/context
 8                            http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
 9 
10     <!-- 自動掃描(自動注入) -->
11     <context:annotation-config/>
12     <context:component-scan base-package="com.company.dao"/>
13 
14     <!-- 動態資料來源 -->
15     <bean id="dataSource" class="com.company.dao.datasource.DataSource">
16         <property name="myConfigFile" value="mySource.xml"/>
17     </bean>
18 
19     <!-- mybatis配置 -->
20     <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
21         <property name="dataSource" ref="dataSource"/>
22         <property name="mapperLocations" value="classpath*:mapper/*/*/*.xml"/>
23         <property name="configLocation" value="classpath:/mybatisConfig.xml"/>
24     </bean>
25 
26     <!-- 自動建立對映器,不用單獨為每個 mapper對映-->
27     <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
28         <property name="basePackage" value="com.company.dao.mapper"/>
29         <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
30     </bean>
31 
32     <!-- 事務管理器配置,單資料來源事務 -->
33     <bean id="transactionManager"
34           class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
35         <property name="dataSource" ref="dataSource"/>
36     </bean>
37 
38     <tx:annotation-driven transaction-manager="transactionManager"/>
39 
40 </beans>
複製程式碼

再上mybatisConfig.xml(在本專案中,我沒有設定setting。最終採用的區域性批處理,因此未設定全域性批處理,具體原因後面再說。)

複製程式碼
 1 <?mapper.xml version="1.0" encoding="UTF-8" ?>
 2 <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
 3 <configuration>
 4     
 5     <settings>
 6         <!-- 配置預設的執行器。SIMPLE 就是普通的執行器;REUSE 執行器會重用預處理語句(prepared statements); BATCH 執行器將重用語句並執行批量更新。-->
 7         <setting name="defaultExecutorType" value="BATCH"/>
 8         <!--詳見:http://www.mybatis.org/mybatis-3/zh/configuration.html-->
 9     </settings>
10     
11     <!-- 別名列表 -->
12     <typeAliases>
13        <!-- typeAliases 中的配置都是配置別名,在此就不貼出來了 -->
14     </typeAliases>
15 
16 </configuration>
複製程式碼

這樣子設定好後,在BaseService開放saveBatch(List<T> list)方法

複製程式碼
 1 @Override
 2     public void save(List<T> list) {
 3         for (int i = 0;i < list.size();i++){
 4             mapper.insert(list.get(i));
 5         }
 6     }
 7 
 8     @Override
 9     public void saveBatch(List<T> list) {
10         int size = list.size();
11         int unitNum = 500;
12         int startIndex = 0;
13         int endIndex = 0;
14         while (size > 0){
15             if(size > unitNum){
16                 endIndex = startIndex+unitNum;
17             }else {
18                 endIndex = startIndex+size;
19             }
20             List<T> insertData = list.subList(startIndex,endIndex);
21             save(insertData);
22             size = size - unitNum;
23             startIndex = endIndex;
24         }
25     }
複製程式碼

雖然看上去是500條記錄,一次次INSERT INTO,但由於在全域性已經設定Mybatis是批處理執行器,所以這500條INSERT INTO只會與Oracle資料庫通訊一次。

全域性設定批處理的侷限性在哪裡呢?

先附上mybatis官方的討論列表中最很關鍵的一句:“If the BATCH executor is in use, the update counts are being lost. ”

設定全域性批處理後,DB裡的insert、Update和delete方法,都無法返回進行DML影響DB_TABLE的行數。

1.insert 無法返回影響的行數,這個好解決,一個批處理放在一個事務裡,記錄批處理失敗次數,總數-批處理失敗次數*單位批處理資料量,就能得到insert 影響DB_TABLE的行數;

2.但是update和delete就無法很簡單的去統計影響行數了,如果做反覆查詢,反而降低了效率,得不償失。

雖現在的專案尚未有需要反饋影響DB_TABLE行數的需求,但是為了更靈活,我們放棄了全域性批處理的方式。

!這裡提個疑問:為什麼Mybatis官方,不將批處理的選擇方式下沉到方法級別?方便開發者根據實際情況,靈活選擇。我覺得這是個可以改進的地方,如有機會,可看原始碼去進行改進。

---------------------------------------------------------------------------------------------------------

二、Mybatis區域性批處理方式

由於領導說全域性批處理方式,不夠靈活,不適宜專案所需,要另想辦法支援。但是java自帶的批處理,因為專案程式碼管理的要求,也不能採用。因此,在仔細閱讀官方文件後,設想自己能否獲取SQLSession後openSession,將這個會話設定為批處理呢?

複製程式碼
1 SqlSession session = sqlSessionFactory.openSession();
2 try {
3   BlogMapper mapper = session.getMapper(BlogMapper.class);
4   // do work
5 } finally {
6   session.close();
7 }
複製程式碼

現在你有一個 SqlSessionFactory,可以用來建立 SqlSession 例項。

SqlSessionFactory

SqlSessionFactory 有六個方法可以用來建立 SqlSession 例項。通常來說,如何決定是你 選擇下面這些方法時:

  • Transaction (事務): 你想為 session 使用事務或者使用自動提交(通常意味著很多 資料庫和/或 JDBC 驅動沒有事務)?
  • Connection (連線): 你想 MyBatis 獲得來自配置的資料來源的連線還是提供你自己
  • Execution (執行): 你想 MyBatis 複用預處理語句和/或批量更新語句(包括插入和 刪除)

過載的 openSession()方法簽名設定允許你選擇這些可選中的任何一個組合。

複製程式碼
1 SqlSession openSession()
2 SqlSession openSession(boolean autoCommit)
3 SqlSession openSession(Connection connection)
4 SqlSession openSession(TransactionIsolationLevel level)
5 SqlSession openSession(ExecutorType execType,TransactionIsolationLevel level)
6 SqlSession openSession(ExecutorType execType)
7 SqlSession openSession(ExecutorType execType, boolean autoCommit)
8 SqlSession openSession(ExecutorType execType, Connection connection)
9 Configuration getConfiguration();
複製程式碼

因此出來了局部批處理第一套程式碼實現方式:

複製程式碼
 1 public static void sqlSession(List<Student> data) throws IOException {
 2         String resource = "mybatis-dataSource.xml";
 3         InputStream inputStream = null;
 4         SqlSession batchSqlSession = null;
 5         try{
 6             inputStream = Resources.getResourceAsStream(resource);
 7             SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
 8             batchSqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH, false);
 9             int batchCount = 500;//每批commit的個數
10             for(int index = 0; index < data.size();index++){
11                 Student stu = data.get(index);
12                 batchSqlSession.getMapper(Student.class).insert(stu);
13                 if(index !=0 && index%batchCount == 0){
14                     batchSqlSession.commit();
15                 }
16             }
17             batchSqlSession.commit();
18         }catch (Exception e){
19             e.printStackTrace();
20         }finally {
21             if(batchSqlSession != null){
22                 batchSqlSession.close();
23             }
24             if(inputStream != null){
25                 inputStream.close();
26             }
27         }
28     }
複製程式碼 複製程式碼
 1 <?xml version="1.0" encoding="UTF-8" ?>
 2 <!DOCTYPE configuration
 3   PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
 4   "http://mybatis.org/dtd/mybatis-3-config.dtd">
 5 <configuration>
 6   <environments default="development">
 7     <environment id="development">
 8       <transactionManager type="JDBC"/>
 9       <dataSource type="POOLED">
10         <property name="driver" value="${driver}"/>
11         <property name="url" value="${url}"/>
12         <property name="username" value="${username}"/>
13         <property name="password" value="${password}"/>
14       </dataSource>
15     </environment>
16   </environments>
17   <mappers>
18     <mapper resource="org/mybatis/example/Student.xml"/>
19   </mappers>
20 </configuration>
複製程式碼

已經在Spring-Mybatis.xml 中配置了SQLSessionFactory,那我為何還要自己去建立SQLSessionFactory呢?因此繼續改良程式碼

複製程式碼
 1 public static void mybatisSqlSession(List<Student> data){
 2         DefaultSqlSessionFactory sqlSessionFactory = (DefaultSqlSessionFactory) ServiceBeanConstant.CTX.getBean("sqlSessionFactory");
 3         SqlSession batchSqlSession = null;
 4         try{
 5             batchSqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH, false);
 6             int batchCount = 500;//每批commit的個數
 7             for(int index = 0; index < data.size();index++){
 8                 Student stu = data.get(index);
 9                 batchSqlSession.getMapper(StudentMapper.class).insert(stu);
10                 if(index !=0 && index%batchCount == 0){
11                     batchSqlSession.commit();
12                 }
13             }
14             batchSqlSession.commit();
15         }catch (Exception e){
16             e.printStackTrace();
17         }finally {
18             if(batchSqlSession != null){
19                 batchSqlSession.close();
20             }
21         }
22     }
複製程式碼

這個版本的區域性批處理插入是比較滿意的,最終採用的方式也是