Mybatis 源码分析

 一 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。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值