Mybatis的使用(三)

Mybatis中三种ExecutorType方式

ExecutorType是一个枚举类,提供了三种执行模式,分别为:

  • SIMPLE:字面意思为基本的,简单的,简易的
  • REUSE:字面意思为可重用的
  • BATCH:字面意思为批量处理的

其实从字面意思来看,多多少少还是能猜测到其各自的功能,其中默认值为SIMPLE。这三种模式分别对应三种执行器,分别为SimpleExecutor,ReuseExecutor,BatchExecutor。

SimpleExecutor中进行增删改的源码为:

1
2
3
4
5
6
7
8
9
10
11
12
@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null); // 创建一个statement
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.update(stmt); // 执行更新操作
} finally {
closeStatement(stmt); // 最后会关闭这个statement
}
}

从源码可知,SimpleExecutor这种方式每次都会创建一个statement,执行完毕后就关闭。也就是说,我在代码中遍历执行更新操作,每循环一次都是一次statement创建 — 执行 — 关闭操作。

ReuseExecutor中进行增删改的源码为:

1
2
3
4
5
6
7
8
9
10
// 用于存储statement的map
private final Map<String, Statement> statementMap = new HashMap<String, Statement>();

@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null); // 创建一个statement
Statement stmt = prepareStatement(handler, ms.getStatementLog());
return handler.update(stmt); // 执行了更新操作后并没有像上面进行closeStatement(stmt),说明并没有关闭statement
}

从源码可知,ReuseExecutor不会关闭statement,而是把statement放到map中,其中key为sql语句,value为对应的statemen。也就是说这种模式不会为每句sql创建一个statement,如果sql相同(其中的插入的数据可以不同)就会重复使用以前创建过的statement,节约了重复创建statement的时间,直接通过key去缓存中获取statemet.

BatchExecutor中进行增删改的源码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 这里就解释了为什么使用这种模式返回值是-2147482646
public static final int BATCH_UPDATE_RETURN_VALUE = Integer.MIN_VALUE + 1002;

private final List<Statement> statementList = new ArrayList<Statement>();
private final List<BatchResult> batchResultList = new ArrayList<BatchResult>();
private String currentSql;
private MappedStatement currentStatement;

@Override
public int doUpdate(MappedStatement ms, Object parameterObject) throws SQLException {
final Configuration configuration = ms.getConfiguration();
final StatementHandler handler = configuration.newStatementHandler(this, ms, parameterObject, RowBounds.DEFAULT, null, null);
final BoundSql boundSql = handler.getBoundSql();
final String sql = boundSql.getSql();
final Statement stmt;
// 判断当前sql是否为同一条sql和同一个statement
if (sql.equals(currentSql) && ms.equals(currentStatement)) {
int last = statementList.size() - 1;
stmt = statementList.get(last);
applyTransactionTimeout(stmt);
handler.parameterize(stmt);//fix Issues 322
BatchResult batchResult = batchResultList.get(last);
batchResult.addParameterObject(parameterObject);
} else {
Connection connection = getConnection(ms.getStatementLog());
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt); //fix Issues 322
currentSql = sql;
currentStatement = ms;
statementList.add(stmt);
batchResultList.add(new BatchResult(ms, sql, parameterObject));
}
// handler.parameterize(stmt);
handler.batch(stmt); // 将statement存入但未执行
return BATCH_UPDATE_RETURN_VALUE; // 直接返回-2147482646
}

这里源码看的不是太懂,只看出了其每次执行更新操作都会返回一个固定值-2147482646,也就是上一篇不能用变量去接收其返回参数的原因。剩余不懂的部分,经查询百度可知,BatchExecutor在执行时会将statement加入到batchResultList中,然后在doFlushStatements方法中进行批量执行。

三种模式以及foreach的性能对比

这里来比较三种模式之间的性能,从上面分析大概可知,SIMPLE模式应该是性能最差的,foreach和REUSE性能可能相差不大,且高于SIMPLE,而BATCH模式应该是性能最好。

附上junit测试代码,第一个为三种模式的测试方式,第二个为foreach的测试方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Test
public void addManyHusbandMethodTwo(){
Husband husband = new Husband().setHid(1005).setHname("john");
int effectRows = 0;
long begin = System.currentTimeMillis();
for (int i=0;i<2000;i++){
husbandDao.addManyHusbandMethodTwo(husband);
sqlSession.clearCache();
effectRows += 1;

}
long end = System.currentTimeMillis();
System.out.println(end-begin);
System.out.println(effectRows);
}

@Test
public void addManyHusbandMethodThree(){
Husband husband = new Husband().setHid(1005).setHname("john");
List<Husband> husbands = new ArrayList<>();
for (int i=0;i<2000;i++){
husbands.add(husband);
}
long begin = System.currentTimeMillis();
int effectRow = husbandDao.addManyHusband(husbands);
long end = System.currentTimeMillis();
System.out.println(end - begin);
System.out.println(effectRow);
}

先使用这三种模式循环插入2000条数据然后再使用foreach方式插入2000条,单位为毫秒值:

SIMPLE REUSE BATCH foreach
2867 2497 798 900

多次测试数值如上,可能不太精确,但是大体结果还是很明显的,BATCH和foreach这两种方式性能都很好,我上面的猜测还是不太准确的,所以在开发中批量数据插入应该是首选这两种方式。

注意事项

由于REUSE和BATCH都需要在创建sqlSession的时候进行设置,所以如果项目中以前没有进行设置的话,需要重新生成一个sqlSession设置相应模式才能进行批量操作。