JAVA mybatis batch取代foreach & 原理

1. 开发中遇到批处理问题

Ⅰ、官方貌似不推荐长期持有Sqlsession

而org.mybatis.spring.SqlSessionUtils.getSqlSession API又内部缓存化。。可能始终持有同一个session???

Ⅱ 、遇到一个SqlSession执行批量插入sql语句报错的问题:

场景是foreach改成了调用Sqlsession内置的jdbc操作来直接执行values批量插入的sql操作

SqlSession session = SqlSessionUtils.getSqlSession(sqlSessionTemplate.getSqlSessionFactory(),
                sqlSessionTemplate.getExecutorType(), sqlSessionTemplate.getPersistenceExceptionTranslator());
session.getConnection().setAutoCommit(false).prepareStatement("batch sql insert values");

conn.commit().close();sqlSession.close();

实际情况:触发多次频繁的插入后,就会自动莫名的sqlsession的connection连接被诡异断开。。。

测试了多次未发现真实原因

最后只能怀疑现有的问题,1.共用一个sqlsession并且没完全释放回连接池,对其进行优化 2.也可能没有去指定需要批处理batch,然后直接执行批处理SQL容易造成jdbc.conn出错

2. 解决批量插入问题,取代mybatis.foreach

优化后再无出现该问题.......

/**
       * 批量处理修改或者插入
    * @param SqlSession
    * @param list     需要被处理的数据
    * @param mapperClass  Mybatis的Mapper类
    * @param function 自定义处理逻辑
    * @return int 影响的总行数
     * @throws Exception 
    */
    public  <T,R> int batchUpdateOrInsert(SqlSession batchSqlSession, List<T> list, Function<T,R> function) throws SQLException {

        int size = list.size();
        
        try {
            
            for (int n = 1; n <= size; n++) {
                //执行同sqlSession下的单条记录插入
                function.apply(list.get(n - 1));
                
                if ((n % BATCH_SIZE == 0) || n == size) {
                    batchSqlSession.commit();
                }
            }
            
            // 非事务环境下强制commit,事务情况下该commit相当于无效
            batchSqlSession.commit(!TransactionSynchronizationManager.isSynchronizationActive());
            
        } catch (Exception e) {
            batchSqlSession.rollback();
            throw e;
        } finally {
            batchSqlSession.close();
        }
        
        return size;
     }

3. 二次改造优化_批量插入

你以为就这么OK了吗?但是诡异的事情发生了。。百度推荐用jdbc自带的批处理。。性能更优

使用SqlSession的batch方法更为简单,但是可能会有性能问题。使用PreparedStatement的addBatch方法可以更精细地控制批处理,但是代码更为复杂。

在实际应用中,你可能需要根据你的具体需求和数据量来选择最合适的方法。对于大批量数据的插入,通常推荐使用PreparedStatement的addBatch方法,因为它可以更有效地利用数据库资源,并减少数据库的访问次数

对数据量大的场景来说,性能是上帝,复杂也是暂时的。。。。

改造成了类似的写法!此写法绕开了Sqlsession,直接使用的Connection操作JDBC完成的原生批量插入

Connection connection = sqlSessionFactory.getConfiguration().getEnvironment().getDataSource().getConnection();

try {

     connection.setAutoCommit(false);

     String sql = "INSERT INTO YOUR_TABLE (COLUMN1, COLUMN2) VALUES (?, ?)";

     PreparedStatement statement = connection.prepareStatement(sql);

     int n = 1;

     for (YourObject object : objects) {

          statement.setString(1, object.getColumn1());

          statement.setString(2, object.getColumn2());

          statement.addBatch();

         if ((n % BATCH_SIZE == 0) || n == size) {

              statement.executeBatch();

              connection.commit();

         }

         n++;     

}

} finally {

     connection.close();

}

4. 顺带回顾一下mybatis知识点

① 存在二级缓存,第一级:sqlsession级别的缓存,第二级:为了解决一级缓存不能跨会话共享的问题,范围是namespace级别的,可以被多个SqlSession共享(只要是同一个接口里面的相同方法,都给可以共享)

② #和$区别?#会加引号,防止sql注入(推荐);$直接显示

③ 源码中的StatementHandler:负责和JDBC业务逻辑交互(相当于mybatis写了一套jdbc调用的通用代码,用了反射之类的技术)

Executor执行器:属于Mybatis用来拓展功能(查询缓存之类)

SqlSession:对增删改查JDBC_API封装,内部还是得靠执行器Executor->StatementHandler

SqlSessionFactory:属于Mybatis架构中的组件(读取xml数据连接配置或连接配置类等),连接池也是apache.dbcp写的和mybatis没关系

④ 当你调用UserMapper 中的insertUser()updateUser(), 或 deleteUser() 方法时,MyBatis会使用对应的StatementHandler执行SQL语句。这些方法会进行数据库的增删改操作。(调用JDBC原生方法处理增删改查)

可以看到,关于查询的方法也是直接调用PrepareStatement的execute方法去执行,然后使用ResultSetHandler来进行处理结果集映射而已

可以看到,绑定实参就是调用ParameterHandler去给PrepareStatement去绑定实参

1)获取前面解析SQL的#{}占位符时的ParameterMappings集合
2)遍历ParameterMappings集合
3)判断传进来的参数对象是否为空,如果为空,那么实参也为Null
4)判断传进来的参数对象是不是已经注册在类型转换中心了,如果已经注册,代表参数对象是基础类型,也就是基本数据类型、包装类、String和日期等类型,参数对象本身就是实参,不需要继续解析其成员属性
5)如果参数对象不为空,而且不是基础类型,那么代表参数对象是一个自定义的引用类型,真正的实参是参数对象内部的成员属性,将参数对象转换为MetaObject
6)取出#{}占位符里面的token,参数对象的MetaObject通过token来调用参数对象的get方法(反射),从而获取占位符对应的实参
7)然后会根据ParameterMapping对象里面的JdbcType和TypeHandler
8)JdbcType就是确认的JDBC类型,如果前面#{}没有配置的话,会为OTHER类型
9)使用TypeHandler来给PrepareStatement按照当前位置去注入实参

CallableStatementHandler是用来执行存储过程的,底层依赖CallableStatement去完成数据库交互的功能

  • 11
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值