Springboot中mybatis执行逻辑源码分析
Springboot中mybatis执行逻辑源码分析
在上一篇springboot整合mybatis源码分析已经讲了我们的Mapper接口,userMapper是通过MapperProxy实现的一个动态代理,所有调用userMapper的方法,最终都会代理到MapperProxy的invoke方法上,我们这次就来看看mybatis具体的执行流程。为了简单易懂,本次的示例用的是最简单的查询语句且不包含事务。
本篇文档的源码路径https://github.com/wbo112/blogdemo/tree/main/springbootdemo/springboot-mybatis
我们在业务代码中调用
userMapper.findAll()
会调用到MapperProxy的invoke方法,我就从这里开始吧//MapperProxy类的方法 //proxy就是我们userMapper生成的代理类,method当前是findAll(),args当前是个map "{id=1, table=user}"
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
//如果是Object的方法,就直接通过反射执行方法
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {
//当前的方法findAll不是Object的,所以会走到这里
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
//MapperProxy类的方法
private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
try {
//这里会将方法进行缓存。当method不存在于methodCache中时,创建一个MapperMethodInvoker,添加到methodCache中的
//methodCache你在MapperProxyFactory.newInstance方法的时候,从MapperProxyFactory类中传递过来的
//而MapperProxyFactory这个类是在添加mapper类的时候,MapperRegistry.addMapper方法中构造出来的, knownMappers.put(type, new MapperProxyFactory<>(type));所以我们这里的methodCache是每个mapper类都会有一个
return MapUtil.computeIfAbsent(methodCache, method, m -> {
//如果方法的修饰符是public,没有abstract,static修饰的话就会走这里,由于我们的mapper是接口,我们的方法也是abstract的,所以不会走到这个分支
if (m.isDefault()) {
try {
if (privateLookupInMethod == null) {
return new DefaultMethodInvoker(getMethodHandleJava8(method));
} else {
return new DefaultMethodInvoker(getMethodHandleJava9(method));
}
} catch (IllegalAccessException | InstantiationException | InvocationTargetException
| NoSuchMethodException e) {
throw new RuntimeException(e);
}
} else {
//所以我们这里最终会创建一个PlainMethodInvoker,添加到methodCache中,我们先看看MapperMethod的构造方法
return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
});
} catch (RuntimeException re) {
Throwable cause = re.getCause();
throw cause == null ? re : cause;
}
}
//MapperMethod类的方法
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
//command主要是用mapperInterface和method在config中查找对应sql的定义,返回sql id和 sqlCommandType
this.command = new SqlCommand(config, mapperInterface, method);
//method主要是通过解析我们的method,得到方法的返回类型,参数类型,分页等等相关信息
this.method = new MethodSignature(config, mapperInterface, method);
}
构造完 new PlainMethodInvoker之后就会调到它的invoke方法去处理,继续调用到上面MapperMethod的execute方法,我们进去看看
//所有的增删改查都是在这里走不同的分支来处理的,我们当前的是查询,我们看看select分支
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;
//我们这里的findAll()方法返回值是个List,所以会走到method.returnsMany()这个分支,我们进去看看
} 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);
if (method.returnsOptional()
&& (result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
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;
}
//MapperMethod的方法
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
List<E> result;
//这里会对参数做个处理,如果mapper方法有多个参数的话,会封装到一个map里面,我们这里只有一个参数,已经是map了,所以我们这里只是从Object[]返回args[0]
Object param = method.convertArgsToSqlCommandParam(args);
//通过mapper方法上有没有 RowBounds.class参数来判断是否有分页,我们这里没有
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.selectList(command.getName(), param, rowBounds);
} else {
//所以我们这里会走到这个分支中
result = sqlSession.selectList(command.getName(), param);
}
// issue #510 Collections & arrays support
if (!method.getReturnType().isAssignableFrom(result.getClass())) {
if (method.getReturnType().isArray()) {
return convertToArray(result);
} else {
return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
}
}
return result;
}
会调用到SqlSessionTemplate的selectList方法,继续调用到sqlSessionProxy的selectList方法,这个sqlSessionProxy也是个动态代理,是在SqlSessionTemplate构造方法中初始化的
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) { notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
notNull(executorType, "Property 'executorType' is required"); this.sqlSessionFactory = sqlSessionFactory;
this.executorType = executorType;
this.exceptionTranslator = exceptionTranslator;
this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class }, new SqlSessionInterceptor());
}
调用sqlSessionProxy的方法,最终都会调用到SqlSessionInterceptor(这是个内部类,在SqlSessionTemplate类中)的invoke方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//这里会返回一个真正执行sql的SqlSession,我们进 getSqlSession方法去看看
SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
......
}
//SqlSessionUtils的方法 public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) { notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
//如果开启了事务,在同一个事务中,第一次返回的holder是空的,后面返回的holder都不为空,直接holder中返回SqlSession
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
//如果holder不为空,根据executorType相同,返回之前缓存的SqlSession
SqlSession session = sessionHolder(executorType, holder);
if (session != null) {
return session;
} LOGGER.debug(() -> "Creating a new SqlSession");
//开启了事务,在同一个事务中,第一次执行,或者没有开启事务,就会走到这里
session = sessionFactory.openSession(executorType);
//如果开启了事务,就会将创建出来的session进行缓存
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session); return session;
}
我们看看session = sessionFactory.openSession(executorType)这句,最终会调用到DefaultSqlSessionFactory.openSessionFromDataSource中
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
//这里会创建一个SpringManagedTransaction
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
//在这里根据execType创建Executor,当前的execType是一个SIMPLE,会创建SimpleExecutor, 如果开启了缓存,就会再创建CachingExecutor,包装SimpleExecutor
final Executor executor = configuration.newExecutor(tx, execType);
//最终返回DefaultSqlSession对象
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
我们再回头看SqlSessionInterceptor.invoke后面的执行,创建完了SqlSession,就会去调用Object result = method.invoke(sqlSession, args),我们去看看这里的执行,这句是通过反射来执行的,这里的sqlSession是上面创建的DefaultSqlSession类来完成的
我们来看看DefaultSqlSession的方法,所有的增删改查方法都有具体的实现,我们最终mapper的方法都是通过这个类来实现的。
我们看看本次调用的selectList方法
//最终会调用到这个
private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
try {
//根据sql id获取到 MappedStatement(这个包含了我们sql语句的定义的详细信息)
MappedStatement ms = configuration.getMappedStatement(statement);
//这里会继续去调用CachingExecutor的query方法
return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
//CachingExecutor的方法 @Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
//这里主要是会对我们的sql语句中"${"、"}"和"#{"、"}" 及之间的内容进行替换。
//将"${"、"}"根据里面的内容直接进行字符串替换
//将"#{"、"}"替换成?,根据参数顺序将参数封装到parameterMappings中
//我们的sql语句是" SELECT * FROM ${table} where id =#{id}",我们的入参map中有table=user,这里就会进行替换,替换之后的sql就是 "SELECT * FROM user where id =?"
BoundSql boundSql = ms.getBoundSql(parameterObject);
//这里会生成一个查询缓存的key
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
//继续走到这里去看看
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
//CachingExecutor的方法
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
//会在这里获取缓存,这里的缓存也就是我们常说的二级缓存了,由于我们的mapper.xml文件中配置了<cache/>,所以这里的cache也就不会为空,这个cache在<mapper>标签下面,所以每个mapper的cache都是各自来控制的。
//这里的缓存最终是LruCache,看名字就知道这是一种LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,
//Cache有多种实现,具体要那种,我们是可以指定的
//在其他的update,delete等等方法会调用 flushCacheIfRequired(ms);将缓存清空
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);
if (list == null) {
//继续会调用到这里,这里的delegate是SimpleExecutor ,这个方法在它的父类,BaseExecutor中
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
//在这里会将查村出来的结果缓存到TransactionalCache.entriesToAddOnCommit中,
//注意:这里并没有缓存到cache里面
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
} return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
//BaseExecutor的方法
@SuppressWarnings("unchecked")
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
......
try {
queryStack++;
//在这里会从 localCache中查找,这个其实就我们说的一级缓存真正存放的位置。这个localCache是当前类的一个属性,而在没有开启事务的时候,我们每次都会新创建一个SimpleExecutor,所以这个localCache也就都是空的
//如果开启事务,在同一个事务中,第一次请求会创建 SimpleExecutor,之后都是重用同一个SimpleExecutor
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();
//这里的LocalCacheScope默认是LocalCacheScope.SESSION,如果是LocalCacheScope.STATEMENT的话就会清空缓存
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
//BaseExecutor的方法
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
//在这里会获取connection,执行数据库的查询,并返回我们需要的类型
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);
}
//在这里将结果添加到一级缓存中
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
我们现在退回到SqlSessionTemplate.invoke方法看获取到结果后的处理
//SqlSessionTemplate的方法
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
try {
//获取到执行结果,在这里返回
Object result = method.invoke(sqlSession, args);
//由于我们本次不涉及事务,所以会从这个分支返回
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
// force commit even on non-dirty sessions because some databases require
// a commit/rollback before calling close()
//在这里会将我们之前缓存到TransactionalCache.entriesToAddOnCommit中的返回结果,存储到MappedStatement.cache中
//由于在添加到cache中会调用,serialize((Serializable) object),通过序列化返回byte[]数组,所以如果我们xml文件中开启了缓存,那我们返回结果包含的类就需要实现Serializable接口
sqlSession.commit(true);
}
return result;
} catch (Throwable t) {
......
} finally {
if (sqlSession != null) {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
}
上面就是整个springboot中mybatis调用查询的整个流程了 。
Springboot中mybatis执行逻辑源码分析的更多相关文章
- Spring JPA实现逻辑源码分析总结
1.SharedEntityManagerCreator: entitymanager的创建入口 该类被EntityManagerBeanDefinitionRegistrarPostProcesso ...
- RocketMQ中Broker的启动源码分析(二)
接着上一篇博客 [RocketMQ中Broker的启动源码分析(一)] 在完成准备工作后,调用start方法: public static BrokerController start(Broker ...
- Netty中NioEventLoopGroup的创建源码分析
NioEventLoopGroup的无参构造: public NioEventLoopGroup() { this(0); } 调用了单参的构造: public NioEventLoopGroup(i ...
- RocketMQ中Broker的消息存储源码分析
Broker和前面分析过的NameServer类似,需要在Pipeline责任链上通过NettyServerHandler来处理消息 [RocketMQ中NameServer的启动源码分析] 实际上就 ...
- RocketMQ中PullConsumer的启动源码分析
通过DefaultMQPullConsumer作为默认实现,这里的启动过程和Producer很相似,但相比复杂一些 [RocketMQ中Producer的启动源码分析] DefaultMQPullCo ...
- RocketMQ中Broker的启动源码分析(一)
在RocketMQ中,使用BrokerStartup作为启动类,相较于NameServer的启动,Broker作为RocketMQ的核心可复杂得多 [RocketMQ中NameServer的启动源码分 ...
- JDK中String类的源码分析(二)
1.startsWith(String prefix, int toffset)方法 包括startsWith(*),endsWith(*)方法,都是调用上述一个方法 public boolean s ...
- (五)myBatis架构以及SQlSessionFactory,SqlSession,通过代理执行crud源码分析---待更
MyBatis架构 首先MyBatis大致上可以分为四层: 1.接口层:这个比较容易理解,就是指MyBatis暴露给我们的各种方法,配置,可以理解为你import进来的各种类.,告诉用户你可以干什么 ...
- SpringBoot事件监听机制源码分析(上) SpringBoot源码(九)
SpringBoot中文注释项目Github地址: https://github.com/yuanmabiji/spring-boot-2.1.0.RELEASE 本篇接 SpringApplicat ...
随机推荐
- Binding(三):资源和ValueConverter
这节讲资源和值转换器(ValueConverter). 资源 在XAML中,我们想要使用外部的数据或者类,需要引入其命名空间,然后将其定义为XAML页面的资源,供给控件使用,或者我们需要封装一个共用的 ...
- ArcGIS连接Postgres 数据库
ArcGIS连接Postgres 数据库 此前在使用ArcGIS的过程中,一般使用文件方式对数据进行管理,后面也有使用 GeoDatabase 数据库对数据进行管理,但是这种管理方式也存在一些弊端,特 ...
- Linux中Crontab的用法
1.crontab的概念: crontab命令用于设置周期性被执行的指令.该命令从标准输入设备读取指令,并将其存放于"crontab"文件中,以供之后读取和执行.可以使用它在每天的 ...
- Spring中这么重要的AnnotationAwareAspectJAutoProxyCreator类是干嘛的?
大家好,我是冰河~~ 停更了很久的[Spring注解系列]专题,终于重新更新了,我们还是接着之前的文章继续往下更新.在<[Spring注解驱动开发]二狗子让我给他讲讲@EnableAspectJ ...
- Java:TreeMap中LinkedHashMap和Map中HashMap的区别
一般情况下,我们用的最多的是HashMap,在Map 中插入.删除和定位元素,HashMap 是最好的选择. 但如果您要bai按自然顺序或自定义顺序遍历键,那么TreeMap会更好.如果需要输出的顺序 ...
- SpringCloud:feign默认jackson解析'yyyy-MM-ddTHH:mm:ssZ'时间格式报错
Feign默认的使用jackson解析,所以时间传值时会报错,时间格式错误 解决办法: 修改feign解析方式为fastjson方式: @Configuration public class CxfC ...
- Java实验项目三——递归实现字符串查找和替换操作
Program:按照下面要求实现字符串的操作: (1)设计一个提供下面字符串操作的类 1)编写一个方法,查找在一个字符串中指定字符串出现的次数. 2)编写一个方法,参数(母字符串,目标字符串,替换字符 ...
- 羊城杯wp babyre
肝了好久,没爆破出来,就很难受,就差这题没写了,其他三题感觉挺简单的,这题其实也不是很难,我感觉是在考算法. 在输入之前有个smc的函数,先动调,attach上去,ida打开那个关键函数. 代码逻辑还 ...
- linux学习之路第八天(linux文件权限详解)
建议和我上一篇博客一起通读,效果更加 1.权限的基本介绍 通过一张图片解决疑惑(重点) rwx权限详解 rwx作用到文件 1)[r]代表可读(read) :可以读取,查看 2)[w]代表可写(writ ...
- 全网唯一开源java开发的支持高扩展,高性能的Mqtt集群broker!
SMQTT是一款开源的MQTT消息代理Broker, SMQTT基于Netty开发,底层采用Reactor3反应堆模型,支持单机部署,支持容器化部署,具备低延迟,高吞吐量,支持百万TCP连接,同时支持 ...