念奴娇 赤壁怀古
     [北宋]苏轼
大江东去,浪淘尽,千古风流人物。
故垒西边,人道是,三国周郎赤壁。
乱石穿空,惊涛拍岸,卷起千堆雪。
江山如画,一时多少豪杰。

遥想公瑾当年,小乔初嫁了,雄姿英发。
羽扇纶巾,谈笑间,樯橹灰飞烟灭。
故国神游,多情应笑我,早生华发。
人生如梦,一樽还酹江月。

Java EE开发平台随手记4——Mybatis扩展3

  接着昨天的Mybatis扩展——IDaoTemplate接口。

  扩展9:批量执行

1、明确什么是批量执行

  首先说明一下,这里的批量执行不是利用<foreach>标签生成一长串的sql字符串——这并不是真正的JDBC批量执行,我这里所说的批量是指在内核调用JDBC的addBatch、executeBatch等方法来实现的。类似下面的示例:

 1 private void batchUpdate(Connection conn) throws SQLException {
 2     PreparedStatement ps = null;
 3     try{
 4         String sql = " INSERT INTO BF_PARAM_ENUM_DATA "
 5                    + " (PARAM_CODE,DATA_CODE,DATA_TEXT,DATA_PARAM,SEQNO,DES) "
 6                    + " VALUES(?,?,?,?,?,?) ";
 7         ps = conn.prepareStatement(sql);
 8         for(int i = 0; i < 10; i++){
 9             String random = RandomStringUtils.randomAlphabetic(8);
10             ps.setString(1, "TEST");//PARAM_CODE
11             ps.setString(2, random);//DATA_CODE
12             ps.setString(3, "数据" + random);//DATA_TEXT
13             ps.setString(4, "参数" + random);//DATA_PARAM
14             ps.setInt(5, i);//SEQNO
15             ps.setString(6, "");//MEMO
16             ps.addBatch();
17         }
18         int[] rs = ps.executeBatch();
19         System.out.print("batch insert : ");
20         for(int r : rs){
21             System.out.print(r+",");
22         }
23         System.out.println();
24     }finally{
25         try{
26             ps.close();
27         }catch(Exception e){}
28     }
29 }
View Code

2、批量执行的API  

  明白了批量执行的含义,接着看批量执行的API,再看一次IDaoTemplate接口中批量执行相关方法:

/**
 * 执行批量:一个SQL执行多次
 * @param sqlId      SQL-ID
 * @param parameters 参数对象数组
 * @return 批量执行的影响记录数组
 */
public int[] executeBatch(String sqlId, List<?> parameters);

/**
 * 执行批量:一次执行多个SQL
 * @param sqlIds  要执行的一组SQL-ID
 * @return 批量执行的影响记录数组
 */
public int[] executeBatch(List<String> sqlIds);

/**
 * 执行批量:一次执行多个SQL
 * @param sqlIds     要执行的一组SQL-ID
 * @param parameters 参数对象数组
 * @return 批量执行的影响记录数组
 */
public int[] executeBatch(List<String> sqlIds, List<?> parameters);

  简单的理解,批量执行就是需要执行一组操作,要么是相同SQL不同的参数,要么就是不同的SQL(参数相同或不同都可以)。至于这些接口怎么实现,都比较简单,根据SqlSessionFactory,创建批量执行的会话,大概如下:

this.batchSqlSession = new SqlSessionTemplate(sqlSessionFactory, ExecutorType.BATCH);

然后和普通会话一样去执行update/insert/delete等操作,完了再调用批量会话的flushBatch方法返回结果即可。

这里有几个问题:

  • 批量执行的事务:批量执行怎么保证事务,是否通过添加Transactional注解即可?事实上,事务注解也只是一个注解,它本身不会对程序运行起任何影响,只有解析这个注解的程序,根据注解属性执行不同的业务逻辑,才会真正体现注解的作用。而另外一方面,Spring注解必须要求调用方法是容器管理的Bean方法,而IDaoTemplate接口的实现类,并不一定是Spring容器中的bean。因此,这里通过事务注解并不能生效,而需要使用编程式事务。相关代码大致如下:
 1 SqlSession batchSqlSession = getExecuteSqlSession();
 2 PlatformTransactionManager txManager = this.getTransactinManager(dataSource);
 3 if(null == txManager){
 4     for(int i = 0, s = sqlIds.size(); i < s; i++){
 5         batchSqlSession.update(SqlManager.getExecuteSqlId(sqlIds.get(i)), parameters==null?null : parameters.get(i));
 6     }
 7     List<BatchResult> r = batchSqlSession.flushStatements();
 8     return resolveBatchResult(r);
 9 }else{
10     DefaultTransactionDefinition def = new DefaultTransactionDefinition();  
11     def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);  
12     def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); 
13     TransactionStatus status = txManager.getTransaction(def); 
14     try {  
15         for(int i = 0, s = sqlIds.size(); i < s; i++){
16             batchSqlSession.update(SqlManager.getExecuteSqlId(sqlIds.get(i)), parameters==null?null : parameters.get(i));
17         }
18         List<BatchResult> r = batchSqlSession.flushStatements();
19         txManager.commit(status); 
20         return resolveBatchResult(r);
21     } catch (Throwable e) {  
22         txManager.rollback(status); 
23         throw Throw.createRuntimeException(e);
24     }  
25 }
View Code
  • 执行方式的混合:如果在一个服务方法中,既有一般方式的dao访问,也有批量执行的dao访问,同时还处于一个事务中。由于Mybatis自身的限制,不能在同一个事务中切换执行方法,因此会抛出异常。那怎么办呢?我是利用ThreadLocal变量保存当前线程是否开启了批量执行模式,然后提供了如下三个管理方法作为批量执行的支持接口:

3、批量执行模式管理API

/**
 * 打开批量执行模式
 */
public void openBatchType();

/**
 * 恢复打开批量执行模式之前的执行模式
 */
public void resetBatchType();

/**
 * 获取批量执行结果
 * @return
 */
public int[] flushBatch();

这样,在前面提到的情况下,可以在服务方法开始,就调用openBatchType打开批量执行方式,然后依次执行各sql脚本,调用flushBatch()获取批量执行结果,在finally快再将当前线程的执行方式切换为调用openBatchType方法之前的执行方式。

  批量执行先说到这里,下一次再结合Dao接口的方法签名、Dao接口的自动代理子类等详细说明怎么使用自动代理调用批量执行。

    扩展10:merge

  在实际业务中,常常会有存在更新、不存在插入的数据库操作——merge,这种操作在Oracle数据库中可以直接利用SQL语法去更新,但是这种方法并不通用,更好的方法就是在Java中添加这个接口方法,但是添加接口方法也面临一个问题:怎么保证事务性?

  其实在看过前面的批量执行的事务处理之后,就没什么秘密可言了,类似的使用编程式事务就可以了。

  遗憾的是——由于平台组评审过于谨慎,这个接口并没有添加到IDaoTemplate中:

/**
 * 存在更新、不存在插入
 * @param insertSqlId 插入的SQL-ID
 * @param updateSqlId 更新的SQL-ID
 * @param parameter   参数对象
 * @return 影响的记录条数
 */
public int merge(String insertSqlId, String updateSqlId, Object parameter);

  类似于merge,还可进一步做更多的扩展,只是通用性比较低,这里举一个例子来说明一下:

/**
 * 根据条件执行不同sql
 * <p>
 *     <ul>
 *         <li> 将参数对象parameter作为根对象,对条件表达式caseExpression求值
 *         <li> 根据求值结果,从sqlId映射中查找对应的sqlId,如果没有找到,就返回默认的sqlId
 *         <li> 根据计算的sqlId和参数对象,执行数据库操作,并返回结果
 *     </ul>
 * </p>
 * @param caseExpression 条件表达式,可为OGNL/SpEL
 * @param sqlIdMapping   sqlId映射
 * @param defaultSqlId   默认的sqlId
 * @param parameter         参数对象
 * @return 影响的记录条数
 */
public int switchExecute(String caseExpression, Map<String, String> sqlIdMapping, String defaultSqlId, Object parameter);

  今天到此为止。

posted @ 2016-06-01 23:30  linjisong  阅读(742)  评论(3编辑  收藏  举报