
由于做数据脱敏时,其中一个方案为使用Mybatis Interceptor,因此本篇介绍下Mybatis的执行流程,看下有哪些核心的类和方法,以及拦截器是如何切入SQL的执行流程的;
1. spring通过sqlSessionFactoryBean创建sqlSessionFactory,在使用sqlSessionFactoryBean时,我们通常会指定configLocation和mapperLocations,来告诉sqlSessionFactoryBean去哪里读取配置文件以及去哪里读取mapper文件。
2. 得到配置文件和mapper文件的位置后,分别调用XmlConfigBuilder.parse()和XmlMapperBuilder.parse()创建Configuration和MappedStatement,Configuration类顾名思义,存放的是Mybatis所有的配置,而MappedStatement类存放的是每条sql语句的封装,MappedStatement以map的形式存放到Configuration对象中,key为对应方法的全路径。
3. spring通过ClassPathMapperScanner扫描所有的Mapper接口,为其创建BeanDefinition对象,但由于他们本质上都是没有被实现的接口,所以spring会将他们的BeanDefinition的beanClass属性修改为MapperFactorybean。
4. MapperFactoryBean也实现了FactoryBean接口,spring在创建Bean时会调用Object()方法获取Bean,最终是通过mapperProxyFactory的newInstance方法为mapper接口创建代理,创建代理的方式是JDK,最终生成的代理对象是MapperProxy。
5. 调用mapper的所有接口本质上调用的都是MapperProxy.invoke方法,内部调用sqlSession的insert/update/delete等各种方法。
// MapperMethod.java
public Object execute(SqlSession sqlSession, Object[] args) {Object result;if (SqlCommandType.INSERT == Type()) {Object param = vertArgsToSqlCommandParam(args);result = rowCountResult(sqlSession.Name(), param));} else if (SqlCommandType.UPDATE == Type()) {Object param = vertArgsToSqlCommandParam(args);result = rowCountResult(sqlSession.Name(), param));} else if (SqlCommandType.DELETE == Type()) {Object param = vertArgsToSqlCommandParam(args);result = rowCountResult(sqlSession.Name(), param));} else if (SqlCommandType.SELECT == Type()) {if (urnsVoid() && method.hasResultHandler()) {executeWithResultHandler(sqlSession, args);result = null;} else if (urnsMany()) {result = executeForMany(sqlSession, args);} else if (urnsMap()) {result = executeForMap(sqlSession, args);} else {Object param = vertArgsToSqlCommandParam(args);result = sqlSession.Name(), param);}} else if (SqlCommandType.FLUSH == Type()) {result = sqlSession.flushStatements();} else {throw new BindingException("Unknown execution method for: " + Name());}if (result == null && ReturnType().isPrimitive() && !urnsVoid()) {throw new BindingException("Mapper method '" + Name()+ " attempted to return null from a method with a primitive return type (" + ReturnType() + ").");}return result;
}
6. SqlSession可以理解为一次会话,SqlSession会从Configuration中获取对应的MappedStatement,交给Executor执行。
// DefaultSqlSession.java
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {try {// 从configuration对象中使用被调用方法的全路径,获取对应的MappedStatementMappedStatement ms = MappedStatement(statement);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();}
}
7. Executor会先创建StatementHandler,StatementHandler可以理解为是一次语句的执行。
8. 然后Executor会获取连接,具体获取连接的方式取决于Datasource的实现,可以使用连接池等方式获取连接。
9. 之后调用StatementHandler.prepare方法,对应到jdbc执行流程中的Connection.prepareStatement这一步。
10. Executor再调用StatementHandler的parameterize方法,设置参数,对应到jdbc执行流程的StatementHandler.setXXX()设置参数,内部会创建ParameterHandler方法。
// SimpleExecutor.java
@Override
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();// 创建StatementHandler,对应第7步StatementHandler handler = wStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);// 获取连接,再调用conncetion.prepareStatement创建prepareStatement,设置参数stmt = prepareStatement(handler, ms.getStatementLog());// 执行prepareStatementreturn handler.<E>query(stmt, resultHandler);} finally {closeStatement(stmt);}
}
11. 再由ResultSetHandler处理返回结果,处理jdbc的返回值,将其转换为java的对象。
这篇文章纯粹是为了更好的理顺mybatis的执行流程,执行流程具体可参考下面的流程图,mybatis的代码基于3.4.6;
先给结论,将整体流程及涉及的类、类之间的调用关系整理为流程图:
通过上面的图,可以知道:
- 核心流程一是指mapperMethod的执行流程,这个过程主要是指对Mapper接口的方法参数的解析;
- 核心流程二是指executor的执行流程,这个过程主要是指StatementHandler的创建,包含:BoundSql 的创建(返回SQL语句)、parameterHandler(执行SQL前的SQL参数处理器)和ResultSetHandler(SQL结果赋值到ResutSets时的值处理)的创建;
- StatementHandler执行流程,这部分主要执行 SQL 并对结果集进行处理;
- 通过上面的流程,了解了一些关键操作的执行顺序:(1)Mapper接口参数的组装在 exector 之前前,同样在所有的拦截器执行前;(2)BoundSql生成SQL语句的时机在于 statementHandler 的创建,早于parameterHandler和ResultSetHandler的时机;
下面从MapperProxyFactory开始走读代码;
(1)Mapper接口代理的工厂——MapperProxyFactory
(2)Mapper接口代理类——MapperProxy
(3)Mapper接口方法——MapperMethod
(4)Mapper接口方法签名——MethodSignature
(5)Mapper接口方法参数名解析——ParamNameResolver
Mybatis的参数传递情况分为:一个参数、Map参数、javaBean参数、多个参数、Collection参数、List参数、Array数组参数;这个类负责解析Mapper接口的方法参数;(解析的规则示例详细可参考:Mybatis第三篇:参数解析)
ParamNameResolver的getNameParams方法:
通过上述代码可知:
实际上Mapper方法参数的解析除了根据注解别名来构建参数名和参数对象的映射关系外,针对集合类也做了特殊处理(org.apache.ibatis.session.defaults.DefaultSqlSession#wrapCollection),在后面介绍相关代码;
这里补充参数解析的示例:
场景一:
List<Object> batchSelectByXxx(@Param("xxx") List<String> xxxList)
参数映射:构建以xxx为key,xxxList为value的Map对象。场景二:
int batchInsert(List<Object> xxxList)
参数映射:构建以list/collection为key,xxxList为value的Map对象场景三:
int update(Object record)
参数映射:构建以Object为参数对象,没有Map。场景四:
List<Object> listByParam(@Param("paramA") String strA, @Param("paramB") String strB)
参数映射:构建以paramA为key,strA为value;以paramB为key,strB为value的Map对象。场景五:
List<Object> listByParam(String strA, String strB)
参数映射,构建以param0为key,strA为value;以param1为key,strB为value的Map对象。
(6)MapperMethod中执行SQL的对象——SqlSession
上面分析完了MapperMethod的invoke方法的第1步:convertArgsToSqlCommandParam方法的实现,现在回到invoke方法的第2步:通过SqlSession的方法来执行SQL;
DefaultSqlSession是SqlSession的默认实现类,以下是SqlSession的select和update方法:
通过上述代码可知:
(7)DefaultSqlSession执行前获取的MappedStatement对象
MappedStatement维护了Mapper接口对应的l中的一条<select|update|delete|insert>节点的封装,MappedStatement类的成员变量也对应<select|update|delete|insert>节点上的标签属性,如下:
MappedStatement的核心方法——getBoundSql
(8)DefaultSqlSession通过Executor执行增删改查
执行Executor的方法前先做一步专门针对集合类的参数解析:
集合类的参数解析代码如下:
Executor从CachingExecutor到SimpleExecutor的顺序进行执行,核心在SimpleExecutor当中:
(9)获取StatementHandler——拦截器的织入
可以看到,拦截器就是这里织入的,这也是走读Mybatis执行过程需要了解的一个点,也就是我们可以通过注册拦截器,在真正执行SQL之前,做一些自定义的操作;
(10)再回到handler执行增删改查的位置——根据类型路由RoutingStatementHandler
这里switch进入PreparedStatementHandler:
PreparedStatementHandler的父类——BaseStatementHandler
通过上面的代码可知:
PreparedStatementHandler的执行后针对 update 操作后会进行主键的设置(通过KeyGenerator设置);针对 select 操作后执行resultSetHandler的执行动作;
至此Mybatis的执行过程的源码走读已经结束;
参考:
mybatis涉及的核心类Mappedstatement
mybatis笔记-MappedStatement
详述mybatis执行流程 - 简书
本文发布于:2024-03-11 15:33:39,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/1710553941142190.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
| 留言与评论(共有 0 条评论) |