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去完成数据库交互的功能