MyBatis框架原理2:SqlSession运行过程
获取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其他组件的原理进行探究。
MyBatis框架原理2:SqlSession运行过程的更多相关文章
- Mybatis的SqlSession运行原理
前言 SqlSession是Mybatis最重要的构建之一,可以简单的认为Mybatis一系列的配置目的是生成类似 JDBC生成的Connection对象的SqlSession对象,这样才能与数据库开 ...
- MyBatis框架原理4:插件
插件的定义和作用 首先引用MyBatis文档对插件(plugins)的定义: MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用.默认情况下,MyBatis 允许使用插件来拦截的方法调用 ...
- MyBatis框架原理3:缓存
上一篇[MyBatis框架原理2:SqlSession运行过程][1]介绍了MyBatis的工作流程,其中涉及到了MyBatis缓存的使用,首先回顾一下工作流程图: 如果开启了二级缓存,数据查询执行过 ...
- 互联网轻量级框架SSM-查缺补漏第七天(MyBatis的解析和运行原理)
第七章MyBatis的解析和运行原理 SqlSessionFactory是MyBatis的核心类之一,其最重要的功能就是提供创建MyBatis的核心借口SqlSession,所以要先创建SqlSess ...
- 《深入浅出MyBatis技术原理与实战》——6. MyBatis的解析和运行原理
MyBatis的运行分为两大部分,第一部分是读取配置文件缓存到Configuration对象,用以创建SqlSessionFactory,第二部分是SqlSession的执行过程. 6.1 涉及的技术 ...
- mybatis解析和基本运行原理
Mybatis的运行过程分为两大步: 第1步,读取配置文件缓存到Configuration对象,用于创建SqlSessionFactory: 第2步,SqlSession的执行过程.相对而言,SqlS ...
- Mybatis的解析和运行原理
Mybatis的解析和运行原理 Mybatis的运行过程大致分为两大步:第一步,读取配置文件缓存到Configuration对象,用以创建 SqlSessionFactory:第二步,SqlSessi ...
- MyBatis源码解析【6】SqlSession运行
前言 这个分类比较连续,如果这里看不懂,或者第一次看,请回顾之前的博客 http://www.cnblogs.com/linkstar/category/1027239.html 经过之前的学习我们知 ...
- mybatis学习笔记(五) -- maven+spring+mybatis从零开始搭建整合详细过程(附demo和搭建过程遇到的问题解决方法)
文章介绍结构一览 一.使用maven创建web项目 1.新建maven项目 2.修改jre版本 3.修改Project Facts,生成WebContent文件夾 4.将WebContent下的两个文 ...
随机推荐
- pikachu-file
1.不安全的文件下载 1.1.概述 文件下载功能在很多web系统上都会出现,一般我们当点击下载链接,便会向后台发送一个下载请求,一般这个请求会包含一个需要下载的文件名称,后台在收到请求后 会开始执行下 ...
- java 学习笔记(四) java连接ZooKeeper
public class Demo2 { public static void main(String[] args) { String connectString = "192.168.1 ...
- 权限和ACL访问控制 -01-权限
权限位 rwxrwrwx:左三位:定义user(owner)的权限,属主权限中三位:定义group的权限,属组权限有三位:定义other的权限,其他的权限 进程对文件的访问权限应用模型:进程的属主与文 ...
- 【银川网络赛G】Factories
题目大意:给定一棵 N 个节点的树,边有边权,选定 M 个叶子节点,使得任意两个叶子节点的树上距离之和最小,求最小值是多少. 题解:任意两点的树上距离和问题应从边的贡献角度考虑. 设 \(f[u][i ...
- 简述Hibernate常见优化策略
①制定合理的缓存策略 ② 采用合理的Session管理机制 ③ 尽量使用延迟加载特性 ④如果可以, 选用基于version的乐观锁替代悲观锁 ⑤在开发过程中, 开启hibernate.show_sql ...
- promise,await,async小论
Promise: Promise为了解决异步回调地狱而生的,其解决方法就是链式调用promise.then()或promise.all(); Promise有两个参数,resolve和reject,第 ...
- 处理离散型特征和连续型特征共存的情况 归一化 论述了对离散特征进行one-hot编码的意义
转发:https://blog.csdn.net/lujiandong1/article/details/49448051 处理离散型特征和连续型特征并存的情况,如何做归一化.参考博客进行了总结:ht ...
- MySQL8.0.18通用版本安装
环境说明: 系统版本:CentOS release 6.8 (Final) MySQL版本:mysql-8.0.18 内存:63G 空间:8T 1 配置本地yum仓库 这个只需要拷贝一个镜像,然后挂载 ...
- ie文件断点续传
一.概述 所谓断点续传,其实只是指下载,也就是要从文件已经下载的地方开始继续下载.在以前版本的HTTP协议是不支持断点的,HTTP/1.1开始就支持了.一般断点下载时才用到Range和Content- ...
- Markdown 标记语言指北 - 源码
这是上一篇博客的源代码. 这是班刊约稿的一篇文章. 全文约6000字, 预计需要 60 分钟读完. # Markdown 标记语言指北 #### TOC 1. [什么是 Markdown?](#%E4 ...