Mybatis-SpringBoot源码解析之三:执行sql流程
文章目录
- Mybatis-SpringBoot源码解析之三:执行sql流程
- 1、前言
- 2、@MapperScanner流程简析
- 3、getObject方法具体调用逻辑
- 4、newInstance创建代理对象
- 5、小总结
- 6、sql执行流程
- 6.1、首先进入MapperProxy的invoke方法
- 6.2、executeForMany查询返回多条
- 7、总结
1、前言
前面已经介绍了SqlSessionFactory
以及SqlSessionTemplate
的构建流程,本章内容就介绍sql的具体解析流程,不过在此之前需要先浅分析一个重要注解@MapperScan
,在使用中我们都知道他会扫描所有的xxxdao.java,也就是xxx.xml的namespace的对应的dao的位置,在构建SqlSessionFactory中已经解析了会将namespace封装到MapperRegistry
的knownMappers
属性上,在这儿就发挥着巨大的作用。
PS:这儿不对@MapperScan
做详细分析,以后有时间的话在写一下,这儿只做浅析。
2、@MapperScanner流程简析
@Import(MapperScannerRegistrar.class)
public @interface MapperScan {}
可以看到@Import导入了MapperScannerRegistrar类。
流程如下:
- 通过registerBeanDefinitions方法解析
@MapperScanner
的所有属性以及注册了MapperScannerConfigurer
类,后续通过MapperScannerConfigurer
类进行进一步的解析操作。 MapperScannerConfigurer
实现了BeanDefinitionRegistryPostProcessor
所以会进一步的进入到postProcessBeanDefinitionRegistry
方法中,然后调用scan方法。- doScan方法简要代码如下所示:
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
1、扫描该包下面所有的的符合条件的对象也就是获取我们的dao类
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
2、在这儿会将dao进一步的封装成MapperFactoryBean
processBeanDefinitions(beanDefinitions);
return beanDefinitions;
}
这儿比较重要,会将dao封装成MapperFactoryBean
,所以他是个FactoryBean
实现了getObject
方法。
3、getObject方法具体调用逻辑
1、位于MapperFactoryBean类中
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
2、位于DefaultSqlSession类中
public <T> T getMapper(Class<T> type) {
return configuration.getMapper(type, this);
}
3、位于Configuration类中,老朋友了,保存在sqlSessionFactory中的重要属性
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
3.1、mapperRegistry类中内部有knownMappers属性保存的就是namespace指向的dao也就是当前解析的dao
是不是有种恍然大悟流程穿起来的感觉。
return mapperRegistry.getMapper(type, sqlSession);
}
4、创建代理对象MapperProxy
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
return mapperProxyFactory.newInstance(sqlSession);
}
4、newInstance创建代理对象
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
protected T newInstance(MapperProxy<T> mapperProxy) {
1、创建代理对象,最终返回的是该代理对象MapperProxy,保存到了spring的factoryBeanObjectCache中。
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
5、小总结
在这儿做一个小总结吧
-
@MapperScanner
会扫描所有的dao接口,然后将他们封装成MapperFactoryBean
-
MapperFactoryBean
的getObect
方法去Configuration
中获取MapperProxyFactory
属性。 -
MapperProxyFactory
最终通过反射获取具体的代理类MapperProxy
,保存到spring容器中。
6、sql执行流程
前面已经做好了所有的准备工作下面就开始正式进入执行sql吧。
例子:UserDao.java接口
public interface UserDao {
List<User> getListById(@Param(value = "id") Long id);
List<User> getList();
}
UserMapper.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zgf.study.mybatis.mapper.UserDao">
<select id="getListById" resultType="com.zgf.study.mybatis.entity.User">
select uid,my_name myName,age,email,sex from t_user where uid =#{id}
</select>
<select id="getList" resultType="com.zgf.study.mybatis.entity.User">
select uid,my_name myName,age,email,sex from t_user
</select>
</mapper>
6.1、首先进入MapperProxy的invoke方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
1、上面的代码就省略了,只看重点。
return mapperMethod.execute(sqlSession, args);
}
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
1、获取sql类型 (select update delete 还是 insert)
switch (command.getType()) {
2、insert语句
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
3、update语句
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
4、delete语句
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
5、select语句
case SELECT:
这儿根据返回值,调用不同的方法解析。
5.1、如果返回结果为void 并且 有结果处理程序 处理结构 之后 返回null
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
5.2、返回多条数据
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
5.3、返回map
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
5.4、返回的是Cursor
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
5.5、单条查询
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() + ").");
}
6、返回最终结果
return result;
}
至此,走到了真正解析sql的地方,这儿还可以再往下稍微探索一点。
6.2、executeForMany查询返回多条
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
List<E> result;
1、这儿就是获取入参
Object param = method.convertArgsToSqlCommandParam(args);
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;
}
public <E> List<E> selectList(String statement, Object parameter) {
1、sqlSessionProxy就是SqlSessionTemplate前文提到的执行sql的代理类,所以会再次执行invoke方法。
return this.sqlSessionProxy.selectList(statement, parameter);
}
已经很深入了,就解析到这儿吧。
7、总结
在这儿总结一下mybatis与springBoot整合的整个流程步骤如下所示:
- 首先mybatis是通过
MybatisAutoConfiguration
自动配置的方式进行注入到spring中去的,在该类中同样注册了SqlSessionFactory
以及SqlSessionTemplate
。 - 构建
SqlSessionFactory
,主要是构建内部的Configuration
属性,在这儿得到两个重要的参数。
- MappedStatement: 解析
mapper.xml
后封装成的对象。 - MapperRegistry类内的
knownMappers
缓存: key为namespace对应的dao的class,value为MapperProxyFactory
。
- 通过
@MapperScanner
注解扫描所有的dao,将dao封装成MapperFactoryBean
,后续通过getObject
方法获取对应的MapperProxyFactory
,以此创建对应的dao的代理类MapperProxy
,在该代理类中封装了mapperInterface (是被代理类的class)和SqlSessionTemplate 两个重要属性。 - 执行sql,调用
MapperProxy
的invoke
方法 - 最终根据sql类型如select、insert、update、delete进入SqlSessionTemplate执行真正的sql。
- 在
SqlSessionTemplate
内部通过mapperInterface
从SqlSessionFactory
内的mappedStatements
属性缓存中获取对应的MappedStatement
。进而执行相应的操作。