• 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);

      今天到此为止。

  • 相关阅读:
    ConcurrentHashMap的使用和原理
    记录下项目中常用到的JavaScript/JQuery代码一(大量实例)
    layer ui插件显示tips时,修改字体颜色
    flash上传文件,如何解决跨域问题
    ubuntu下的mv命令
    Semantic 3D
    shellnet运行train_val_seg.py
    Tensorflow的不足之处
    用pip命令把python包安装到指定目录
    ubuntu建立文件或者文件夹软链接
  • 原文地址:https://www.cnblogs.com/linjisong/p/5551432.html
Copyright © 2020-2023  润新知