背景
我想用Spring和MyBatis实现以下过程:
- 从CSV文件中读取一行
- 使用从CSV文件中读取的值查询表(表
ACCOUNT
) - 将CSV文件和表
ACCOUNT
中的值合并到一条记录中 - 将合并后的记录插入另一个表(
REGISTRATION
)
我想使用批处理执行器(<setting name="defaultExecutorType" value="BATCH"/>
),因为此过程插入了大量记录。
问题
我已经实现了这样的程序:
AccountDao accountDao = applicationContext.getBean("accountDao", AccountDao.class);
RegistrationDao registrationDao = applicationContext.getBean("registrationDao", RegistrationDao.class);
List<Registration> list = null;
try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(path), "UTF-8"))) {
CsvToBeanBuilder<Registration> builder = new CsvToBeanBuilder<Registration>(reader);
builder.withType(Registration.class);
list = builder.build().parse();
for (Registration reg : list) {
Account account = accountDao.findAccount(reg.getAccountId());
reg.setName(account.getName());
...
registrationDao.insertRegistration(reg);
}
}
我期望INSERT
SQL与JDBCStatement#addBatch()
一起排队,并在事务结束时以批处理方式执行。
但实际上,当我用AccountDao#findAccount()
查询ACCOUNT
表时,MyBatis会自动用BatchExecutor#doFlushStatements()
刷新每条语句,所以所有的INSERT
SQL都是单独执行的。
在这种情况下,ACCOUNT
与REGISTRATION
没有关系,因此您可以安全地将更新语句排队并批量执行。
问题
当开发人员知道查询的表与更新的表没有关系时,是否有任何方法可以抑制排队语句的自动刷新?
行为解释
批处理不起作用,因为只有在执行相同查询时才能执行批处理。但在您的代码中,有两个查询相继执行:select
和insert
因此,每个select
都会中断批处理,因为MyBatis对您试图查询的内容一无所知。它能做的最好的事情是刷新所有以前的插入,这样查询就可以在实际的数据库状态上执行
解决方案
只有一种方法可以选择:在循环中选择一批帐户,在嵌套循环中插入
AccountDao accountDao = applicationContext.getBean("accountDao", AccountDao.class);
RegistrationDao registrationDao = applicationContext.getBean("registrationDao", RegistrationDao.class);
try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(path), "UTF-8"))) {
List<Registration> registrations = new CsvToBeanBuilder<Registration>(reader)
.withType(Registration.class)
.build()
.parse();
for (List<Registration> batch : Lists.partition(registrations, 500)) {
Map<Integer, Account> accounts = Maps.uniqueIndex(
accountDao.getAccount(batch.stream()
.map(Registration::getAccountId)
.toList()),
Account::getId
);
for (Registration registration : batch) {
Account account = accounts.get(reg.getAccountId());
reg.setName(account.getName());
...
registrationDao.insertRegistration(reg);
}
}
}
为什么没有这样的机制
IMO,这是非常危险的,因为作为一个原始开发人员,你可能肯定知道它是安全的,但另一个开发人员可能不知道。他甚至可能没有注意到这种配置。然后添加一些代码,打破你的不变量。而且很难意识到错误在哪里