获取SqlSession对象

SqlSession session = sqlSessionFactory.openSession();

首先通过SqlSessionFactory的openSession方法获取SqlSession接口的实现类DefaultSqlSession对象。

public interface SqlSessionFactory {

  SqlSession openSession();

  SqlSession openSession(boolean autoCommit);
SqlSession openSession(Connection connection);
SqlSession openSession(TransactionIsolationLevel level); SqlSession openSession(ExecutorType execType);
SqlSession openSession(ExecutorType execType, boolean autoCommit);
SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);
SqlSession openSession(ExecutorType execType, Connection connection); Configuration getConfiguration(); }

SqlSessionFactory接口提供一系列重载的openSession方法,其参数如下:

  • boolean autoCommit:是否开启JDBC事务的自动提交,默认为false。
  • Connection:提供连接。
  • TransactionIsolationLevel:定义事务隔离级别。
  • ExecutorType:定义执行器类型。

DefaultSqlSessionFactory对象调用覆写的openSession方法:

public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}

得到一个定义了ExecutorType为configuration的默认执行器SIMPLE,事务隔离级别为null,JDBC事务自动提交为false的DefaultSqlSession对象。

获取MapperProxy代理对象

有了DefaultSqlSession对象,以查询一条数据为例,来看一下整个处理过程。

For example:

SqlSession session = sqlSessionFactory.openSession();
try {
BlogMapper mapper = session.getMapper(BlogMapper.class);
Blog blog = mapper.selectBlog(101);
} finally {
session.close();
}

MyBatis时序图:

根据MyBatis文档推荐的方法,调用Mapper接口中的方法实现对数据库的操作,上述例子中根据blog ID获取Blog对象。

通过DefaultSqlSession对象的getMapper方法获取的是一个MapperProxy代理对象,这也是Mapper接口不用实现类的原因。当调用BlogMapper中的方法时,由于BlogMapper是一个JDK动态代理对象,它会运行invoke方法,代码如下:

  @Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
//判断代理对象是否是一个类
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
//生成MapperMethod对象
final MapperMethod mapperMethod = cachedMapperMethod(method);
//执行execute方法
return mapperMethod.execute(sqlSession, args);
} private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
...

invoke方法判断代理的对象是否是一个类,由于代理对象是一个接口,所以通过cachedMapperMethod生成一个MappedMethod对象,然后执行execute方法,execute方法代码如下:

public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}

因为这里是根据ID查询一个对象,所以最终调用了DefaultSqlSession的selectOne方法,selectOne方法又调用自身selectList方法,最终将查询操作委托给Executor:

@Override
public <T> T selectOne(String statement, Object parameter) {
// Popular vote was to return null on 0 results and throw exception on too many.
List<T> list = this.<T>selectList(statement, parameter);
if (list.size() == 1) {
return list.get(0);
} else if (list.size() > 1) {
throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
} public <E> List<E> selectList(String statement, Object parameter) {
return this.selectList(statement, parameter, RowBounds.DEFAULT);
} @Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
//根据id获取MappedStatement对象
MappedStatement ms = configuration.getMappedStatement(statement);
//wrapCollection方法处理集合参数
//委托Exector执行SQL
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}

Executor



Executor在MyBatis加载全局配置文件时初始化,我们可以在全局配置文件settings元素中配置Executor类型,MyBatis默认使用SimpleExecutor,如果开启了二级缓存,则再用CachingExecutor进行包装。

如果开启了二级缓存,SqlSession调用CachingExecutor执行器的query方法,先从二级缓存获取数据,当无法从二级缓存获取数据时,则委托给BaseExecutor的子类进行操作,CachingExecutor执行过程代码如下:

 public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
//判断是否有二级缓存
Cache cache = ms.getCache();
if (cache != null) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
//从二级缓存获取数据
List<E> list = (List<E>) tcm.getObject(cache, key);
//如果二级缓存没有数据则委托给BaseExcutor的子类进行操作
if (list == null) {
list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
//如果没有二级缓存则委托给BaseExcutor的子类进行操作
return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

MyBatis默认使用SimpleExecutor,调用父类BaseExecutor的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.");
}
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
queryStack++;
//从一级缓存获取数据
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
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
clearLocalCache();
}
}
return list;
}

最后,queryFromDatabase方法则调用SimpleExecutor的doQuery方法,通过Configuration构建StatementHandler对SQL和参数编译,parameterize()方法通过ParameterHandler对参数进行设置,使用TypeHandler转换参数类型,执行查询后再通过ResultSetHandler封装结果并返回,其方法代码如下:

 public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
//根据Configuration构建StatementHandler
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
//编译SQL
stmt = prepareStatement(handler, ms.getStatementLog());
//ResultSetHandler处理结果,并返回处理后的结果
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
} private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
//处理SQL参数
handler.parameterize(stmt);
return stmt;
}

通过以上流程发现,MyBatis核心工作实际上是由Executor、StatementHandler、ParameterHandler和ResultSetHandler四个接口完成的,掌握这四个接口的工作原理,对理解MyBatis底层工作原理有很大帮助。

StatementHandler



StatementHandler接口设计采用了适配器模式, 实现类RoutingStatementHandler根据上下文来选择适配器生成相应的StatementHandler。三个适配器分别是SimpleStatementHandler、PreparedStatementHandler和CallableStatementHandler。StatementHandler初始化过程如下:

//在Configuration中构建StatementHandler
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}

RoutingStatementHandler构建过程:

public class RoutingStatementHandler implements StatementHandler {

  private final StatementHandler delegate;

  public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
//根据我们映射配置文件中的SQL选择适配器,MyBatis默认使用PreparedStatement
switch (ms.getStatementType()) {
case STATEMENT:
//SimpleStatementHandler对应JDBC中的Statement
delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case PREPARED:
//PreparedStatementHandler对应JDBC中PreparedStatement
delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case CALLABLE:
//CallableStatementHandler对应JDBC中CallableStatement
delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
default:
throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
} }

通常我们使用PreparedStatementHandler,调用父类prepare方法,对SQL预编译:

 @Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
statement = instantiateStatement(connection);
setStatementTimeout(statement, transactionTimeout);
setFetchSize(statement);
return statement;
} catch (SQLException e) {
closeStatement(statement);
throw e;
} catch (Exception e) {
closeStatement(statement);
throw new ExecutorException("Error preparing statement. Cause: " + e, e);
}
}

通过PreparedStatementHandler的instantiateStatement方法可以看到,这里实际就是在调用JDBC中的prepareStatement方法进行SQL的预编译,query方法则是调用JDBC的execute方法来执行编译好的SQL和返回结果:

 @Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
String sql = boundSql.getSql();
if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
String[] keyColumnNames = mappedStatement.getKeyColumns();
//JDBC方法创建PrepareStatement对象
if (keyColumnNames == null) {
return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
} else {
return connection.prepareStatement(sql, keyColumnNames);
}
} else if (mappedStatement.getResultSetType() != null) {
return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
} else {
return connection.prepareStatement(sql);
}
} @Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
//JDBC中PreparedStatement对象的execute()方法执行SQL
ps.execute();
//ResultSetHandler对结果进行封装和返回
return resultSetHandler.<E> handleResultSets(ps);
}

ParameterHandler

ParameterHandler接口作用就是设置预编译SQL的参数:

public interface ParameterHandler {

  Object getParameterObject();

  void setParameters(PreparedStatement ps)
throws SQLException; }

接口提供getParameterObject和setParameters方法,前者作用获取参数对象,后者作用是设置预编译SQL的参数,由实现类DefaultParameterHandler执行,使用TypeHandler将参数对象类型转换成jdbcType,完成预编译SQL的参数设置。

  @Override
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
}
//验证参数类型
else if
(typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
//设置参数类型为jdbcType
try {
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
} catch (SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}

ResultSetHandler

ResultSetHandler接口根据配置文件中定义的规则将结果映射成相应对象,接口定义了三个方法,源码如下:

public interface ResultSetHandler {
// 处理结果集,映射成对应的对象集合
<E> List<e> handleResultSets(Statement stmt) throws SQLException;
// 处理结果集,返回相应的游标对象
<E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException;
// 处理储存过程输出参数
void handleOutputParameters(CallableStatement cs) throws SQLException;
}

ResultSetHandler接口的具体实现是DefaultResultSetHandler,我们通过SELECT语句执行得到的结果集由其handleResultSets方法处理,方法如下:

 @Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
// 生成ArrayList用于保存结果集映射的对象
final List<Object> multipleResults = new ArrayList<Object>(); int resultSetCount = 0;
// 获取第一个ResultSet对象
ResultSetWrapper rsw = getFirstResultSet(stmt);
// 获取MyBatis初始化时解析映射器配置文件中的resultMap节点生成的ResultMap对象
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
// 验证resultMap是否存在
validateResultMapsCount(rsw, resultMapCount);
while (rsw != null && resultMapCount > resultSetCount) {
ResultMap resultMap = resultMaps.get(resultSetCount);
// 根据映射规则将查询结果映射成ResultMap并放入multipleResults集合中
handleResultSet(rsw, resultMap, multipleResults, null);
// 获取下一个结果
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
//递增直到所有结果映射完成
resultSetCount++;
}
//此处省略resultSets多结果集处理方法
//...
return collapseSingleResultList(multipleResults);
}

通过对4个核心功能接口作用的简单分析,我们对MyBatis底层工作原理就有了初步认识,但是要进一步深入理解MyBatis框架原理还需要对MyBatis其他组件的原理进行探究。

最新文章

  1. commons configuration管理项目的配置文件
  2. JAVE not work in linux
  3. 简单而又复杂的jsp(小知识)
  4. redis 常用命令
  5. 《转》Spring4 Freemarker框架搭建学习
  6. 使用 github.io 免费建站
  7. php二叉树算法
  8. MariaDB5.5.32 绿色版下载安装一条龙
  9. memcache缓存命中深入理解转载
  10. poj1276--Cash Machine(多背包被判刑了)
  11. 嘿嘿。今天学习了AJAX的几个方法
  12. Hibernate 中Hql 查询中间表的用法
  13. Road of computer tec 01
  14. 2017软工实践K班总结
  15. as3:sprite作为容器使用时,最好不要指定width,height
  16. mysql并行执行--缩短主从同步时延
  17. java工具包一:日期处理
  18. 什么是Jenkins 以及如何使用?
  19. 含有ref out 参数 的方法反射 Emit 与 普通
  20. Spring jdbcTemplat 写入BLOB数据为空

热门文章

  1. Java 实现C#中的String.format效果 解决(&quot;我是{0},今年了&quot;,&quot;whaozl&quot;) bug
  2. spring ObjectFactory
  3. DevExpress ASP.NET v19.1版本亮点:Pivot Grid等控件
  4. 微信里关闭窗口 js
  5. DveOps路线指南
  6. 绑定与非绑定方法及反射,isinstance和issubclass内置函数
  7. [转] Linux环境变量配置文件以及启动顺序
  8. luogu小金明qwq x
  9. 【UOJ #46】 【清华集训2014】玄学
  10. Presto部署指南