一 mybatis与spring整合
其整合思路也适用于其他框架,是Spring提供的通用开放式整合思路(例如@EnableFeignClients)。
流程:
@MapperScan -引入-> ImportBeanDefinitionRegistrar实现类 -调用-> BeanDefinitionBuilder -创建BD->
(BeanDefinition)MapperScannerConfigurer extend BeanDefinitionRegistryPostProcessor -调用->
ClassPathBeanDefinitionScanner.doScan(package)扫描包下所有类生成BeanDefinitionHolder --> 遍历Set<BeanDefinitionHolder> 修改每个BeanClass=MapperFactoryBean -->
其实现FactoryBean接口getObject() -调用-> Mybatis生成JDK代理对象 getSqlSession().getMapper(this.mapperInterface)
1、@MapperScan中@Import(MapperScannerRegistrar.class),MapperScannerRegistrar实现Spring的ImportBeanDefinitionRegistrar接口。
通过ImportBeanDefinitionRegistrar接口可以拿到环境的【BeanDefinitionRegistry registry(DefaultListableBeanFactory)】,
通过spring提供的BeanDefinitionBuilder构建了一个GenericBeanDefinition,类型为MapperScannerConfigurer,并注册到BeanFactory。
ImportBeanDefinitionRegistrar类只能通过其他类@Import的方式来加载,其接口方法拿到BeanFactory,
结合扫描器ClassPathMapperScanner,实现注册bean的能力。
https://blog.csdn.net/jiachunchun/article/details/94569246
BeanDefinitionBuilder使用样例部分源码:
//实例化GenericBeanDefinition,bean类型为MapperScannerConfigurer
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
//为BeanDefinition增加PropertyValue,name为MapperScannerConfigurer中的属性名,Object为bean初始化依赖注入的值。
//BeanFactory在Bean初始化时,依赖注入PropertyValue中属性。
//PropertyValue就是spring配置中<bean id="" class=""><property name="" value="">。
builder.addPropertyValue(String name, Object v);
//给类中userService变量注入名为userService的bean<property name="" ref="">
builder.addPropertyReference("userService", "userService");
//BF注册BD
beanFactory.registerBeanDefinition(beanName, builder.getBeanDefinition());
2、MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry方法。
方法中实例化mybatis自定义BeanDefinition扫描器:ClassPathMapperScanner ,其继承Spring的ClassPathBeanDefinitionScanner类。
重写了其doScan(),并调用super.doScan()扫描指定的Mapper包。registerFilters()为扫描器注册过滤机制到this.includeFilters。
附:spring框架中通过BeanDefinitionRegistryPostProcessor处理<property name="" value="${}">
3、自定义BeanDefinition扫描器:继承Spring的ClassPathBeanDefinitionScanner。
ClassPathBeanDefinitionScanner为子类提供了Set<BeanDefinitionHolder> doScan(String... basePackages)方法。
扫描指定路径下的Class并生成BeanDefinition,保存到Set中返回。可以为扫描器配置Filters过滤掉部分类,或对返回的集合手动过滤BD。
过滤后BeanDefinition注册到BeanFactory
https://jingyan.baidu.com/article/495ba841bb36e138b20ede49.html
4、扫描得到Set<BeanDefinitionHolder>并修改其配置。
执行org.mybatis.spring.mapper.ClassPathMapperScanner.processBeanDefinitions(Set<BeanDefinitionHolder>)方法。
遍历Set<BeanDefinitionHolder>:
将所有Mapper的BeanDefinition的Class设置为MapperFactoryBean.class,其实现了Spring的FactoryBean接口的getObject()。
部分源码:
definition.setBeanClass(this.mapperFactoryBeanClass);
//为MapperFactoryBean设置其构造器入参,这个就是mapperInterface,也就是包下实际的Mapper接口
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
SpringIOC在实例化MapperFactoryBean类型的Bean时,会直接调用其getObject()返回Bean对象。
部分源码:
public T getObject() throws Exception {
//通过mybatis的SqlSession获取Mapper接口jdk动态代理对象。
return getSqlSession().getMapper(this.mapperInterface);
}
二 框架初始化
初始化时扫描解析XML文件,将其封装成MappedStatement对象,保存在Configuration.map<方法名,MappedStatement>。
MappedStatement中包含XML中的所有信息,包括SqlSource,这个对象保存转化后预编译sql语句和参数映射。
SqlSource..... = "UPDATE user set user_name=? where id=? ";
解析sql样例:
SQL解析案例:
原文:https://www.cnblogs.com/fangjian0423/p/mybaits-dynamic-sql-analysis.html
<update id="update" parameterType="org.format.dynamicproxy.mybatis.bean.User">
UPDATE users
<trim prefix="SET" prefixOverrides=",">
<if test="name != null and name != ''">
name = #{name}
</if>
<if test="age != null and age != ''">
, age = #{age}
</if>
<if test="birthday != null and birthday != ''">
, birthday = #{birthday}
</if>
</trim>
where id = ${id}
</update>
此<update>是MixedSqlNode类型根节点,图中是其所有子节点List<SqlNode>,有三个子节点。其中trim节点下又有子节点。
<update>内部的<trim>也是MixedSqlNode类型节点,图中是其所有子节点List<SqlNode>:包含IfSqlNode、StaticTextSqlNode类型子节点。
核心组件:
Configuration MyBatis所有的配置信息都维持在统一的Configuration对象之中。
mappedStatements Configuration中一个Map<方法名,MappedStatement>,每个MappedStatement中的SqlSource保存xml解析后的sql语句。
一个Method -> 一个MappedStatement -> 一个SqlSource -> 一条xml-sql
InterceptorChain 拦截器链
MappedStatement 保存在Configuration,每一个MappedStatement对应一个XML中<>方法,维护了一条sql。
SqlSource 保存xml解析后的一条sql语句,对应一个XML中<>方法。负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中并返回。
SqlNode
BoundSql 表示动态生成的SQL语句以及相应的参数信息。
MapperRegistry 用Map保存所有的MapperProxy代理对象,key=Mapper接口类型。
SqlSessionFactory 整个环境统一的SqlSession工厂,创建SqlSession。
SqlSession 作为MyBatis工作的主要顶层API,表示和数据库交互的会话,完成必要数据库增删改查功能。
由SqlSessionFactory创建,线程不安全,每个线程要有独立的SqlSession。
Executor MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护
SqlSession线程不安全:1、SqlSession包含一个Executor,其中有一个Transaction,包含一个Connection。jdbcConnection是线程不安全的。
2、一级缓存
https://www.jianshu.com/p/b1d80ca8f300
MapperProxy Mapper接口的代理对象,框架对外暴露的执行者。
StatementHandler 封装了JDBC Statement操作,负责对JDBC statement 的操作,如设置参数、将Statement结果集转换成List集合
ParameterHandler 负责对用户传递的参数转换成JDBC Statement 所需要的参数
ResultSetHandler 负责将JDBC返回的ResultSet结果集对象转换成List类型的集合
TypeHandler 负责java数据类型和jdbc数据类型之间的映射和转换
SQL的解析保存与组装
三 执行Mapper接口方法
https://www.jianshu.com/p/937a797d5d11
https://blog.csdn.net/qq_36894974/article/details/104132876
框架为每个mapper接口生成JDK动态代理对象MapperProxy,对应一个Mapper-XML的<namespace>
MapperProxy{ SqlSession sqlSession; Class<T> mapperInterface; Map<Method, MapperMethod> methodCache;}
MapperProxy主要流程:
1 MapperProxy根据Method在methodCache中找MapperMethod,没有则创建放入缓存,key=Method。
流程图:https://img-blog.csdnimg.cn/20190612154305870.png
->mapperMethod.execute(this.sqlSession, args)。
->解析args
->根据mapperMethod中type(增删改查)
执行DefaultSqlSession.update/select/delete..。
【
注意:SqlSession是非线程安全的,但是MapperProxy是公用的,
与Spring整合时 MapperProxy中的SqlSession是Spring的SqlSessionTemplate。 (下面文章单独说明)
】
//从configuration找到SQL
->MappedStatement=Configuration.getMappedStatement(方法全名);
Configuration中有一个map<方法名,MappedStatement>,MappedStatement中有SqlSource
->取得SqlSource(DynamicSqlSource),其持有xml解析后的sql语句的根节点rootSqlNode
->拼接动态sql(见下"动态sql拼接")
->得到最终要执行的BoundSql
//处理参数
->wrapCollection(args) 将Collection、List、Array类型args,放入Map返回替换args。
//执行
->Executor.update/select/delete......
->实例化StatementHandler.update执行JDBC操作
2 动态sql拼接
一个SqlSource对应XML中一个SQL
DynamicSqlSource.getBoundSql(parameterObject)
->this.rootSqlNode.apply(context); 从根节点把sql拼接进入context。
rootSqlNode一般都是MixedSqlNode,存有一个List<SqlNode>,按顺序保存了xml中这句sql的所有动态节点
->遍历List<SqlNode>,执行apply(context)。拼接sql存入context。(context中带有参数parameterObject)
一个动态标签一个SqlNode。 StaticTextSqlNode、IfSqlNode、ForEachSqlNode....
->返回BoundSql,替换#{}为?
IfSqlNode{ExpressionEvaluator evaluator;String test;SqlNode contents;} 1、Ognl解析器 2、<if text="....."> 3、<if>中的节点/语句
IfSqlNode.apply(context):
String IfSqlNode.test记录<if text=".....">,通过IfSqlNode.test+parameterObject,解析Ognl表达式判断结果
->contents.apply(context)
3 最终通过jdbc执行
类似:
sql = "UPDATE user set user_name=? where id=? ";
PreparedStatement ptmt = conn.prepareStatement(sql); //预编译SQL,减少sql执行
ptmt.setString(1, g.getUser_name());//传参
ptmt.execute();//执行
4 结果映射
SqlSessionTemplate保证SqlSession的线程安全
SqlSession是非线程安全的(源码注释)。源码中SqlSessionTemplate中的SqlSession接口属性是JDK动态代理 ,其实现类是org.mybatis.spring.SqlSessionTemplate.SqlSessionInterceptor 。 调用了org.mybatis.spring.SqlSessionUtils#getSqlSession获取SqlSession。
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
SqlSession session = sessionHolder(executorType, holder);
if (session != null) {
return session;
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Creating a new SqlSession");
}
session = sessionFactory.openSession(executorType);
//注册SqlSession到TransactionSynchronizationManager的ThreadLocal
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
return session;
}
TransactionSynchronizationManager.getResource(sessionFactory) 向下调用doGetResource方法,从ThreadLocal<Map<Object, Object>> resources中取出SessionHolder。
private static Object doGetResource(Object actualKey) {
//ThreadLocal<Map<Object, Object>> resources
Map<Object, Object> map = resources.get();
if (map == null) {
return null;
}
Object value = map.get(actualKey);
// Transparently remove ResourceHolder that was marked as void...
if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {
map.remove(actualKey);
// Remove entire ThreadLocal if empty...
if (map.isEmpty()) {
resources.remove();
}
value = null;
}
return value;
}
总结:SqlSessionTemplate通过TransactionSynchronizationManager管理事务,ThreadLocal做SqlSession的线程隔离。SqlSession交给Spring事务管理,应该是保证同一个事物都使用一个SqlSession。