IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> MybatisPlus 原理学习:springboot 中 mybatisplus 初始化流程 -> 正文阅读

[Java知识库]MybatisPlus 原理学习:springboot 中 mybatisplus 初始化流程

之前一直在用 mybatis-plus(mybatis 的增强版, 简化开发),只觉得 mybatis-plus 用起来很方便,但一直没了解其实现原理。于是最近开始学习一下。
平时使用 mybatisplus 时都是定义一个 Mapper 接口继承下 BaseMapper 就直接使用,并没有实现类。

那么现在就开始探究一下 mybatisPlus 是怎么实现的, 首先来了解 mybatisPlus 的加载流程。

springboot 中 mybatis-plus 加载流程

首先是了解 mybatis-plus 的加载流程。

从 mybatis-plus 源码中的 spring.factories 文件中我们可以了解到,其加载入口为 MybatisPlusAutoConfiguration
点进去这个类可以发现里面有几个核心的 bean:

  • SqlSessionFactory
  • SqlSessionTemplate
  • MapperScannerConfigurer
/*
 * MybatisPlusAutoConfiguration.java
 */

@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
    // mybatisplus 重写的 SqlSessionFactoryBean
    MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();
    factory.setDataSource(dataSource);
    applyConfiguration(factory); // setConfiguration
    // set :interceptors,typeHandlers,
    //      mapperLocations,typeEnumsPackage,globalConfig 等
    // ...
    return factory.getObject();
}


@Bean
@ConditionalOnMissingBean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
    ExecutorType executorType = this.properties.getExecutorType();
    if (executorType != null) {
        return new SqlSessionTemplate(sqlSessionFactory, executorType);
    } else {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

// 如果不存在 MapperScannerConfigurer bean (对应没有使用 @MapperScan 注解) 则自动注册一个
@Configuration
@Import(AutoConfiguredMapperScannerRegistrar.class)
@ConditionalOnMissingBean({MapperFactoryBean.class, MapperScannerConfigurer.class})
public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean {
    //....
}

// 自动注册一个 MapperScannerConfigurer beanDefinition
public static class AutoConfiguredMapperScannerRegistrar implements BeanFactoryAware, ImportBeanDefinitionRegistrar {
    // ...
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        // 定义 MapperScannerConfigurer
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
        // addPropertyValue: basePackage等
        // ...
        registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition());
    }
}

MapperScannerConfigurer

首先看 MapperScannerConfigurer 这个 bean,这是一个扫描 mapper 的配置类,内部使用 ClassPathMapperScanner 根据配置扫描 mapper 接口并将接口注册为 MapperFactoryBean。

创建 MapperScannerConfigurer bean 的方式一般有两种:(当然也可以自己手动声明创建)

  • 一种是默认的使用 AutoConfiguredMapperScannerRegistrar (如上 AutoConfig 中手动注册的 beanDefinition)扫描 spring 默认包下的带 @Mapper 注解的接口。
  • 一种是 MapperScannerRegistrar 通过解析 @MapperScan 注解的属性在 spring 中注册 MapperScannerConfigurer 的 beanDefinition。
    • 关于 @MapperScan 注解:默认扫描注解的类所在的包下所有接口。主要提供一下功能
      • 配置扫描规则:指定扫描的包,指定扫描携带的注解,指定继承的接口等。
      • 指定 sqlSessionFactory 等
      • 指定 自定义的 MapperFactoryBean

扫描并注册 Mapper BeanDefinition

  1. MapperScannerConfigurer#postProcessBeanDefinitionRegistry
    • 创建 ClassPathMapperScanner 对象(继承 ClassPathBeanDefinitionScanner),设置相关属性(参考MapperScan属性),根据设置的属性注册扫描过滤器,开始执行扫描
    // MapperScannerConfigurer.java
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
        if (this.processPropertyPlaceHolders) {
          processPropertyPlaceHolders();
        }
        
        ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
        // set 相关属性  扫描过滤规则、指定 sqlSessionxxx。。
        ...
        // 根据 set 的属性,注册扫描过滤器
        scanner.registerFilters();
        // 扫描 mapper 接口,并注册 beanDefinition, 底层是 doScan
        scanner.scan(
            StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
    }
    
  2. ClassPathMapperScanner#doScan
    • 根据配置的过滤器扫描相关接口,并注册 beanDifinition
    • 修改扫描到的 beanDefinition 类型为 MapperFactoryBean (或@MapperScan 指定的自定义类型)
    // ClassPathMapperScanner.java
    @Override
    public Set<BeanDefinitionHolder> doScan(String... basePackages) {
        // 调用父类 ClassPathBeanDefinitionScanner 方法扫描并注册 beanDifinition
        Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
        
        if (beanDefinitions.isEmpty()) {
          LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages)
              + "' package. Please check your configuration.");
        } else {
          // 对 mapper 的 beanDefinition 进行处理: 修改 beanClass 类型等。
          processBeanDefinitions(beanDefinitions);
        }
        
        return beanDefinitions;
    }
    
    private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
        GenericBeanDefinition definition;
        for (BeanDefinitionHolder holder : beanDefinitions) {
          definition = (GenericBeanDefinition) holder.getBeanDefinition();
          String beanClassName = definition.getBeanClassName();
          ...
          // 构造方法中传入原 mapper bean 类型
          definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
          // 修改 bean 类型为 MapperFactoryBean
          definition.setBeanClass(this.mapperFactoryBeanClass);
          ... // set sqlSessionFactory, sqlSessionTemplate (如果存在的话)
          // 设置懒加载
          definition.setLazyInit(lazyInitialization);
         }
    }
    

MapperFactoryBean

前面可知,扫描 Mapper 后会将其 beanDefinition.beanClass 修改为 MapperFactoryBean. 所以在后续 spring 将 mapper bean 初始化时,会通过调用 MapperFactoryBean.getObject 获取其对象。

查看源码可以发现,最终是调用 sqlSession 的 getMapper 方法获取 mapper 对象。具体实现后面再看。

  public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }

SqlSessionFactory

这里的 sqlSessionFactory 是 mybatis 的用来获取 sqlSession (用来执行 sql 管理事务的对象)的工厂类. 里面主要提供 openSession 的一些列重载方法。

创建 sqlSessionFactory 的逻辑在 MybatisSqlSessionFactoryBean#buildSqlSessionFactory 方法中,里面主要是

  • 设置前面配置的属性:globalConfig,typeHandlers,interceptor…
  • 解析 mapper.xml 并保存到 configuration 中
  • 创建 sqlSessionFactory
protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
    final Configuration targetConfiguration;
    if (this.configuration != null) {  // AutoConfig  里面 set 的 configuration
        targetConfiguration = this.configuration;
        ...
    } 
    ...
    // 加载前面的配置 并 set 到 targetConfiguration 中, 
    //      如:set globalConfig, register typeHandlers, addInterceptor, parse xmlMapper
    ...
    // 解析 mapper.xml, 并将解析的结果存在 targetConfiguration 中
    for (Resource mapperLocation : this.mapperLocations) {
        ...
        Builder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
                            targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
        xmlMapperBuilder.parse();
        ...
    }
    ...
    // 构建 SqlSessionFactory, 实现类为 DefaultSqlSessionFactory
    SqlSessionFactory sqlSessionFactory = new MybatisSqlSessionFactoryBuilder().build(targetConfiguration);return sqlSessionFactory;
}

解析 mapper.xml

XMLMapperBuilder 主要用于解析 mapper.xml 文件,入口为 XMLMapperBuilder#parse, XMLMapperBuilder 会解析 mapper.xml 文件中配置的 statements、resultMap、parameter 等信息, 并将其存放于 Configuration 的 对应 map 中。

// XMLMapperBuilder.java

public void parse() {
    // resource: file /../xxxMapper.xml 判断是否加载过
    if (!configuration.isResourceLoaded(resource)) {
        // 1 解析 mapper.xml 文件
        configurationElement(parser.evalNode("/mapper"));
        configuration.addLoadedResource(resource);
        // 2  注册 Mapper 
        // 底层调用 MybatisMapperRegistry#addMapper 注册 Mapper
        bindMapperForNamespace();
    }
    
    // 前面没解析完成的 继续解析, 看代码主要是针对前面解析出错的
    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
}

解析 mapper.xml 后 Configuration 对象示例:
在这里插入图片描述

当前只包含 mapper.xml 中的 sql 语句。

<–, 里面解析 注解 sql 和 mybatisPlus 的默认 sql–>

MybatisMapperRegistry 中注册 mapper 代理工厂

前面 XMLMapperBuilder 解析完 xml 文件后,就会在 MybatisMapperRegistry 中添加对应的 Mapper 代理工厂 MybatisMapperProxyFactory。同时使用 MybatisMapperAnnotationBuilder 解析 mapper 接口中使用注解写的 sql 语句。

这里的 MybatisMapperProxyFactory 就是用来获取 Mapper 代理(MybatisMapperProxy)的工厂类。 在调用 Mapper 中方法的时候其实就是调用的 MybatisMapperProxy#invoke 方法。

备注:MybatisMapperRegistry 同时还有另一个方法 getMapper(Class<T> type, SqlSession sqlSession), 用来获取 Mapper 代理,后面讲。

// MybatisMapperRegistry.java

// type 为 mapper 接口
public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {
        if (hasMapper(type)) {
            // TODO 如果之前注入 直接返回
            return;
        }
        boolean loadCompleted = false;
        try {
            // 注册 Mapper 的代理工厂
            knownMappers.put(type, new MybatisMapperProxyFactory<>(type));
            // 再解析 Mapper 接口中使用注解写的 sql 语句
            MybatisMapperAnnotationBuilder parser = new MybatisMapperAnnotationBuilder(config, type);
            // 里面会继续调用 AbstractSqlInjector#inspectInject 注入 mybatisPlus 的动态 curd 方法
            parser.parse();
            loadCompleted = true;
        } finally {
            if (!loadCompleted) {
                knownMappers.remove(type);
            }
        }
    }
}

<–##### MybatisMapperAnnotationBuilder 解析注解 sql & 注入 动态 sql–>

注入 curd 动态 sql

MybatisMapperAnnotationBuilder 在解析完注解 sql 后,会注入 mybatis-plus 的的 curd 动态 sql。平时调用 mybatis-plus 的 BaseMapper 的方法就是使用的这里注入的动态 sql。

// MybatisMapperAnnotationBuilder.java

public void parse() {
    String resource = type.toString();
    if (!configuration.isResourceLoaded(resource)) {
        ...
        // 遍历Mapper 接口中的方法,解析方法上的注解 sql(如果存在的话)
        for (Method method : type.getMethods()) {
            ...
            parseStatement(method);
            ...
        }
        ...
        // 如果继承了 baseMapper,就注入 CURD 动态 SQL
        if (GlobalConfigUtils.isSupperMapperChildren(configuration, type)) {
            parserInjector();
        }
        ...
    }
    parsePendingMethods();
}

// 注入动态 sql, AbstractSqlInjector
void parserInjector() {
    GlobalConfigUtils.getSqlInjector(configuration).inspectInject(assistant, type);
}

AbstractSqlInjector 注入的 sql 默认为 DefaultSqlInjector 中的方法(也就是 BaseMapper 中的方法)。 注入的方式为:

  • 根据不同方法的模板构建对应的 sql 脚本(同 xml),并填充对应的数据表和实体类的字段

备注:这里的数据表字段是通过 TableInfoHelper 根据实体类字段和配置推断出来的,如根据 @TableField/@TableId 注解指定字段名,或根据驼峰下划线转换规则推断。

注入的详细代码参考: AbstractSqlInjector#inspectInject

这里贴一段注入的 update 的 sql script。 (其中 et 为更新的实体类,ew 为查询的条件。)

<script>
UPDATE user <set>
<if test="et != null">
<if test="et['name'] != null">name=#{et.name},</if>
<if test="et['phone'] != null">phone=#{et.phone},</if>
<if test="et['age'] != null">age=#{et.age},</if>
</if>
<if test="ew != null and ew.sqlSet != null">${ew.sqlSet}</if>
</set> 
<if test="ew != null">
<where>
<if test="ew.entity != null">
<if test="ew.entity.id != null">id=#{ew.entity.id}</if>
<if test="ew.entity['name'] != null"> AND name=#{ew.entity.name}</if>
<if test="ew.entity['phone'] != null"> AND phone=#{ew.entity.phone}</if>
<if test="ew.entity['age'] != null"> AND age=#{ew.entity.age}</if>
</if>
<if test="ew.sqlSegment != null and ew.sqlSegment != '' and ew.nonEmptyOfWhere">
<if test="ew.nonEmptyOfEntity and ew.nonEmptyOfNormal"> AND</if> ${ew.sqlSegment}
</if>
</where>
<if test="ew.sqlSegment != null and ew.sqlSegment != '' and ew.emptyOfWhere">
 ${ew.sqlSegment}
</if>
</if> <choose>
<when test="ew != null and ew.sqlComment != null">
${ew.sqlComment}
</when>
<otherwise></otherwise>
</choose>
</script>

SqlSessionTemplate

线程安全、Spring管理的、g,以确保实际使用的SqlSession是与当前Spring事务关联的。此外,它还管理会话生命周期,包括根据Spring事务配置在必要时关闭、提交或回滚会话。

sqlSession 代理

SqlSessionTemplate 实现了 SqlSession 接口,但是内部持有一个 sqlSessionProxy 代理对象,最后都是调用的代理对象的方法。 而代理对象中最终调用的 sqlSession 也是通过 sqlSessionFactory.openSession 来获取的。只不过在 openSession 前会从 spring 事务同步管理器中获取一遍,不存在才创建一个新的 sqlSession,并且再执行完成后关闭或释放(引用数量-1)。

这样就可以在需要使用 sqlSession 时直接使用 sqlSessionTemplate,而不是需要每次都通过 sqlSessinFactory 获取 sqlSession,也不需要考虑 sqlSesion 的关闭。同时保证了在同一个 spring 事务中使用同一个 sqlSession 对象。

相关代码如下:

public class SqlSessionTemplate implements SqlSession, DisposableBean {

    public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
          PersistenceExceptionTranslator exceptionTranslator) {
        // ...
        this.sqlSessionFactory = sqlSessionFactory;
        this.executorType = executorType;
        this.exceptionTranslator = exceptionTranslator;
        
        // 创建一个 sqlSession 的代理, 实现为一个内部类
        this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),
            new Class[] { SqlSession.class }, new SqlSessionInterceptor());
    }
    
    // 数据库的相关操作均由代理对象实现, 但是不支持 commit,因为代理对象中户实现
    @Override
    public <T> T selectOne(String statement) {
        return this.sqlSessionProxy.selectOne(statement);
    }
    ...
    
    // 前面提到的 getMapper 方法,最终到 Configuration 中获取
    @Override
    public <T> T getMapper(Class<T> type) {
        return getConfiguration().getMapper(type, this);
    }
    
    
    /*
     * sqlSession 的代理实现
     */
    private class SqlSessionInterceptor implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      
      // 获取真正的 sqlSession,逻辑参考:SqlSessionUtils#getSqlSession) 
      //    从 spring 的 TransactionSynchronizationManager 事务同步管理器 (中的 threadlocal) 获取 sqlSession
      //    不存在则通过 sqlSessionFactory.openSession 创建新的 sqlSession 对象,并保存到 spring 中
      
      SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
      try {
        // 使用 sqlSession 执行对应方法
        Object result = method.invoke(sqlSession, args);
        // ...
        return result;
      } catch (Throwable t) {
        // unwrapThrowable
        // SqlSessionUtils.closeSqlSession
        throw unwrapped;
      } finally {
        //  如果是 spring 管理的则 release(引用数量-1), 不是则 close
        // SqlSessionUtils.closeSqlSession
      }
    }
  }
}
  • SqlSessionUtils
    处理MyBatis SqlSession生命周期,可以在 spring (TransactionSynchronizationManager)注册和获取 sqlSession。

  • TransactionSynchronizationManager
    spring 管理每个线程的资源和事务同步的中央委托。里面使用维护了一些 ThreadLocal 用户保存想成相关的事务信息。

public abstract class TransactionSynchronizationManager {
    private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal<>("Transactional resources");
    private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations = new NamedThreadLocal<>("Transaction synchronizations");
    private static final ThreadLocal<String> currentTransactionName = new NamedThreadLocal<>("Current transaction name");
    private static final ThreadLocal<Boolean> currentTransactionReadOnly = new NamedThreadLocal<>("Current transaction read-only status");
    private static final ThreadLocal<Integer> currentTransactionIsolationLevel = new NamedThreadLocal<>("Current transaction isolation level");
    private static final ThreadLocal<Boolean> actualTransactionActive = new NamedThreadLocal<>("Actual transaction active");
    // ...
}

获取 Mapper 对象

源码中可以看到, 前面提到的 sqlSessionTempalte 的 getMapper 方法中也是调用 configuration.getMapper 方法来获取 mapper 对象的. 而configuration 里面又是通过 MybatisMapperRegistry 获取 mapper。

前面解析 mapper 时提到了 MybatisMapperRegistry 提供了一个 getMapper 方法用来获取 mapper 代理对象。

在这个 getMapper 方法中之前注册的代理工厂 MybatisMapperProxyFactory 使用 通过 MybatisMapperProxy 生成了 mapper 的代理对象。

    // sqlSessionTempalte
    @Override
    public <T> T getMapper(Class<T> type) {
        return getConfiguration().getMapper(type, this);
    }

    // MybatisConfiguration 
    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        return mybatisMapperRegistry.getMapper(type, sqlSession);
    }
    
    // MybatisMapperRegistry 
    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        // knownMappers 为前面解析 mapper 后注册的 mapper 代理工厂对象
        final MybatisMapperProxyFactory<T> mapperProxyFactory = (MybatisMapperProxyFactory<T>) knownMappers.get(type);
        if (mapperProxyFactory == null) {
            throw new BindingException("Type " + type + " is not known to the MybatisPlusMapperRegistry.");
        }
        try {
            // 传入 sqlSession 创建代理对象
            return mapperProxyFactory.newInstance(sqlSession);
        } catch (Exception e) {
            throw new BindingException("Error getting mapper instance. Cause: " + e, e);
        }
    }
    
    
    // MybatisMapperProxyFactory
    public T newInstance(SqlSession sqlSession) {
        final MybatisMapperProxy<T> mapperProxy = new MybatisMapperProxy<>(sqlSession, mapperInterface, methodCache);
        return newInstance(mapperProxy);
    }

总结

mybatisPlus 的整个加载过程概括如下:

  1. MapperScannerConfigurer 扫描 mapper 接口,并在 spring 中注册 deanDefinition,类型为 MapperFactoryBean
  2. SqlSessionFactory 解析 mapper.xml 和 mapper 接口 中的 sql 语句保存到 Configuration 中,同时加入 mybatisPlus 提供的动态 sql。 最后注册对应 mapper 的 MybatisMapperProxyFactory
  3. SqlSessionTemplate 使用 SqlSessionInterceptor 代理实现一个线程安全的 spring 管理的 SqlSession,并最终通过 MybatisMapperProxyFactory 获取 mapper 的代理对象 MybatisMapperProxy.
  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-06-25 17:56:45  更:2022-06-25 17:57:25 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 16:51:21-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码