知识点
- Myabtis有哪些Executor执行器,区别是什么?
- Mybatis如何配置使用哪种Executor?
- Mybatis如何实现批处理?
- Mybatis的主键策略,批量写入,能返回数据库主键么?
Executor包的主要结构及其作用
Executor 接口及其实现类,使用场景及其调用
BaseExecutor
BaseExecutor在创建的时候new了一个本地缓存,每次执行query的时候先从缓存中获取,如果缓存中没有,则从数据库中获取并放到缓存中。代码如下:
public <e> List<e> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
//如果已经关闭,报错
if (closed) {
throw new ExecutorException("Executor was closed.");
}
//先清局部缓存,再查询.但仅查询堆栈为0,才清。为了处理递归调用
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<e> list;
try {
//加一,这样递归调用到上面的时候就不会再清局部缓存了
queryStack++;
//先根据cachekey从localCache去查
list = resultHandler == null ? (List<e>) localCache.getObject(key) : null;
if (list != null) {
//若查到localCache缓存,处理localOutputParameterCache
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
//从数据库查
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
//清空堆栈
queryStack--;
}
if (queryStack == 0) {
//延迟加载队列中所有元素
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
//清空延迟加载队列
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
//如果是STATEMENT,清本地缓存
clearLocalCache();
}
}
return list;
}
可以看到如果LocalCacheScope为STATEMENT,则每次调用后就清空缓存。那么,LocalCacheScope的值如何配置呢?
Configuration的settings节点配置:SESSION,STATEMENT
从数据库中获取数据:
public <e> List<e> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.<e> handleResultSets(ps);
}
SimpleExecutor:
每次获取一个新的statement
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
//调用StatementHandler.prepare
stmt = handler.prepare(connection);
//调用StatementHandler.parameterize
handler.parameterize(stmt);
return stmt;
}
ReuseExecutor:
从hashmap中获取statement
//ReuseExecutor中声明了可重用的一个map,用来缓存SQL语句对应的Statement
private final Map<string, statement> statementMap = new HashMap<string, statement>();
...
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
//得到绑定的SQL语句
BoundSql boundSql = handler.getBoundSql();
String sql = boundSql.getSql();
//如果缓存中已经有了,直接得到Statement
if (hasStatementFor(sql)) {
stmt = getStatement(sql);
} else {
//如果缓存没有找到,则和SimpleExecutor处理完全一样,然后加入缓存
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection);
putStatement(sql, stmt);
}
handler.parameterize(stmt);
return stmt;
}
...
private Statement getStatement(String s) {
return statementMap.get(s);
}
BatchExecutor:可以批量更新。
将多次执行的statement存起来一次执行,核心代码如下:
@Override
public void batch(Statement statement) throws SQLException {
String sql = boundSql.getSql();
//调用Statement.addBatch
statement.addBatch(sql);
}
如何配置默认的Excutor?
Configuration的settings节点配置:defaultExecutorType,值为SIMPLE,REUSE,BATCH
CachingExecutor
keygen
keygen为主键生成策略,提供了一下几种策略:
- Jdbc3KeyGenerator:JDBC3键值生成器,核心是使用JDBC3的Statement.getGeneratedKeys,执行后生成主键。核心代码如下:
//获取主键集合...
ResultSet resultSet = statement.getGeneratedKeys();
//回填到对象...
...
- SelectKeyGenerator:根据sql生成主键,支持执行前生成主键和执行后生成主键。
//获取主键集合...
Executor keyExecutor = configuration.newExecutor(executor.getTransaction(), ExecutorType.SIMPLE);
List<object> values = keyExecutor.query(keyStatement, parameter, RowBounds.DEFAULT, Executor.NO_RESULT_HANDLER);
//回填到对象...
-
NoKeyGenerator:不生成主键
-
注意:
-
- XML配置:如果配置的useGeneratedKeys为true并且执行的insert语句,则默认使用Jdbc3KeyGenerator,否则为NoKeyGenerator
-
- 注解方式:如果是insert或者update语句,首选判断@SelectKey注解,其次判断@Options注解,如果没有,则判断默认配置中的useGeneratedKeys,否则根据@Option注解执行。所以,如果配置了全局的useGeneratedKeys为true,则update的时候需要设置 ++@Option(useGeneratedKeys=false)++
if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
// first check for SelectKey annotation - that overrides everything else
SelectKey selectKey = method.getAnnotation(SelectKey.class);
if (selectKey != null) {
keyGenerator = handleSelectKeyAnnotation(selectKey, mappedStatementId, getParameterType(method), languageDriver);
keyProperty = selectKey.keyProperty();
} else if (options == null) {
keyGenerator = configuration.isUseGeneratedKeys() ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
} else {
keyGenerator = options.useGeneratedKeys() ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
keyProperty = options.keyProperty();
keyColumn = options.keyColumn();
}
} else {
keyGenerator = new NoKeyGenerator();
}
//Test1
@Insert("insert into table2 (name) values(#{name})")
@SelectKey(statement="select id, name_fred from table2 where id = identity()", keyProperty="nameId,generatedName", keyColumn="ID,NAME_FRED", before=false, resultType=Map.class)
int insertTable2WithSelectKeyWithKeyMap(Name name);
//Test2
@Insert("insert into table2 (name) values(#{name})")
@Options(useGeneratedKeys=true, keyProperty="nameId,generatedName", keyColumn="ID,NAME_FRED")
int insertTable2WithGeneratedKey(Name name);
```</object></string,></string,></e></e></e></e></e></e></e>