Mybatis⼤数据量的批量insert解决⽅案
前⾔
通过Mybatis做7000+数据量的批量插⼊的时候报错了,error log如下:
,
('G6*******',
'610103************',
'学⽣52',
'G6*******',
'610103199109920192',
'学⽣50',
'07',
'01',
'0104',
' ',
0,
' ',
' ',
current_timestamp,
current_timestamp
) 被中⽌,呼叫 getNextException 以取得原因。
at org.postgresql.jdbc2.AbstractJdbc2Statement$BatchResultHandler.handleError(AbstractJdbc2Statement.java:2743)
at ute(QueryExecutorImpl.java:411)
at org.postgresql.uteBatch(AbstractJdbc2Statement.java:2892)
at com.alibaba.druid.filter.FilterChainImpl.statement_executeBatch(FilterChainImpl.java:2596)
at com.alibaba.druid.wall.WallFilter.statement_executeBatch(WallFilter.java:473)
at com.alibaba.druid.filter.FilterChainImpl.statement_executeBatch(FilterChainImpl.java:2594)
at com.alibaba.druid.filter.FilterAdapter.statement_executeBatch(FilterAdapter.java:2474)
at com.alibaba.druid.filter.FilterEventAdapter.statement_executeBatch(FilterEventAdapter.java:279)
at com.alibaba.druid.filter.FilterChainImpl.statement_executeBatch(FilterChainImpl.java:2594)
at com.alibaba.druid.proxy.uteBatch(StatementProxyImpl.java:192)
at com.alibaba.druid.uteBatch(DruidPooledPreparedStatement.java:559)
at org.utor.BatchExecutor.doFlushStatements(BatchExecutor.java:108)
at org.utor.BaseExecutor.flushStatements(BaseExecutor.java:127)
at org.utor.BaseExecutor.flushStatements(BaseExecutor.java:120)
at org.utor.BaseExecutormit(BaseExecutor.java:235)
at org.utor.CachingExecutormit(CachingExecutor.java:112)
at org.apache.ibatis.session.defaults.DefaultSqlSessionmit(DefaultSqlSession.java:196)
batis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:390)
... 39 more
可以看到这种异常⽆法捕捉,仅能看到异常指向了druid和ibatis的原码处,初步猜测是由于默认的SqlSession⽆法⽀持这个数量级的批量操作,下⾯就结合源码和官⽅⽂档具体看⼀看。
源码分析
项⽬使⽤的是Spring+Mybatis,在Dao层是通过Spring提供的SqlSessionTemplate来获取SqlSession的:
@Resource(name = "sqlSessionTemplate")
private SqlSessionTemplate sqlSessionTemplate;
public SqlSessionTemplate getSqlSessionTemplate()
{
return sqlSessionTemplate;
}
为了验证,接下看⼀下它是如何提供SqlSesion的,打开SqlSessionTemplate的源码,看⼀下它的构造⽅法:
/**
* Constructs a Spring managed SqlSession with the {@code SqlSessionFactory}
* provided as an argument.
*
* @param sqlSessionFactory
*/
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
this(sqlSessionFactory, Configuration().getDefaultExecutorType());
}
接下来再点开getDefaultExecutorType这个⽅法:
public ExecutorType getDefaultExecutorType() {
return defaultExecutorType;
}
可以看到它直接返回了类中的全局变量defaultExecutorType,我们再在类的头部寻⼀下这个变量:
protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
到了,Spring为我们提供的默认执⾏器类型为Simple,它的类型⼀共有三种:
/**
* @author Clinton Begin
*/
public enum ExecutorType {
SIMPLE, REUSE, BATCH
}
仔细观察⼀下,发现有3个枚举类型,其中有⼀个BATCH是否和批量操作有关呢?我们看⼀下mybatis官⽅⽂档中对这三个值的描述:
- ExecutorType.SIMPLE: 这个执⾏器类型不做特殊的事情。它为每个语句的执⾏创建⼀个新的预处理语句。
- ExecutorType.REUSE: 这个执⾏器类型会复⽤预处理语句。
- ExecutorType.BATCH:这个执⾏器会批量执⾏所有更新语句,如果 SELECT 在它们中间执⾏还会标定它们是 必须的,来保证⼀个简单并易于理解的⾏为。
可以看到我的使⽤的SIMPLE会为每个语句创建⼀个新的预处理语句,也就是创建⼀个PreparedStatement对象,即便我们使⽤druid连接池进⾏处理,依然是每次都会向池中put⼀次并加⼊druid的cache中。这个效率可想⽽知,所以那个异常也有可能是insert timeout导致等待时间超过数据库驱动的最⼤等待值。
好了,已解决问题为主,根据分析我们选择通过BATCH的⽅式来创建SqlSession,官⽅也提供了⼀系列重载⽅法:
SqlSession openSession()
SqlSession openSession(boolean autoCommit)
SqlSession openSession(Connection connection)
SqlSession openSession(TransactionIsolationLevel level)
SqlSession openSession(ExecutorType execType,TransactionIsolationLevel level)
SqlSession openSession(ExecutorType execType)
SqlSession openSession(ExecutorType execType, boolean autoCommit)
SqlSession openSession(ExecutorType execType, Connection connection)
可以观察到主要有四种参数类型,分别是
- Connection connection
- ExecutorType execType
- TransactionIsolationLevel level
- boolean autoCommit
官⽅⽂档中对这些参数也有详细的解释:
SqlSessionFactory 有六个⽅法可以⽤来创建 SqlSession 实例。通常来说,如何决定是你 选择下⾯这些⽅法时:
Transaction (事务): 你想为 session 使⽤事务或者使⽤⾃动提交(通常意味着很多 数据库和/或 JDBC 驱动没有事务)?
Connection (连接): 你想 MyBatis 获得来⾃配置的数据源的连接还是提供你⾃⼰
Execution (执⾏): 你想 MyBatis 复⽤预处理语句和/或批量更新语句(包括插⼊和 删除)?
所以根据需求选择即可,由于我们要做的事情是批量insert,所以我们选择SqlSession openSession(ExecutorType execType, boolean autoCommit)
顺带⼀提关于TransactionIsolationLevel也就是我们经常提起的事务隔离级别,官⽅⽂档中也介绍的很到位:
MyBatis 为事务隔离级别调⽤使⽤⼀个 Java 枚举包装器, 称为 TransactionIsolationLevel,
否则它们按预期的⽅式来⼯作,并有 JDBC ⽀持的 5 级 (
NONE,
READ_UNCOMMITTED
READ_COMMITTED,
REPEATABLE_READ,
SERIALIZA BLE)
解决问题
回归正题,初步到了问题原因,那我们换⼀中SqlSession的获取⽅式再试试看。
testing… 2minutes later…
不幸的是,依旧报相同的错误,看来不仅仅是ExecutorType的问题,那会不会是⼀次commit的数据量过⼤导致响应时间过长呢?上⾯我也提到了这种可能性,那么就再分批次处理试试,也就是说,在同⼀事务范围内,分批commit insert batch。具体看⼀下Dao层的代码实现:批量更新sql语句
再次测试,程序没有报异常,总共7728条数据 insert的时间⼤约为10s左右,如下图所⽰,
总结简单记录⼀下Mybatis批量insert⼤数据量数据的解决⽅案,仅供参考,Tne End。 @Override
public boolean insertCrossEvaluation(List<CrossEvaluation> members)
throws Exception {
// TODO Auto-generated method stub
int result = 1;
SqlSession batchSqlSession = null ;
try {
batchSqlSession = this .getSqlSessionTemplate()
.getSqlSessionFactory()
.openSession(ExecutorType.BATCH, false );// 获取批量⽅式的sqlsession
int batchCount = 1000;// 每批commit 的个数
int batchLastIndex = batchCount;// 每批最后⼀个的下标
for (int index = 0; index < members.size();) {
if (batchLastIndex >= members.size()) {
batchLastIndex = members.size();
result = result * batchSqlSession.insert("MutualEvaluationMapper.insertCrossEvaluation",members.subList(index , batchLastIndex)); batchSqlSessionmit();
System.out.println("index:" + index + " batchLastIndex:" + batchLastIndex);
break ;// 数据插⼊完毕,退出循环
} else {
result = result * batchSqlSession.insert("MutualEvaluationMapper.insertCrossEvaluation",members.s
ubList(index , batchLastIndex)); batchSqlSessionmit();
System.out.println("index:" + index + " batchLastIndex:" + batchLastIndex);
index = batchLastIndex;// 设置下⼀批下标
batchLastIndex = index + (batchCount - 1);
}
}
batchSqlSessionmit();
}
finally {
batchSqlSession.close();
}
return Boolean(result);
}
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论