在SpringBoot集成下,Mybatis的mapper代理对象究竟是如何生成的

简介: 在SpringBoot集成下,Mybatis的mapper代理对象究竟是如何生成的

前情回顾

 

之前我们讲到了mybatis操作数据库的流程:先创建SqlSessionFactory,然后创建SqlSession,然后再创建获取mapper代理对象,最后利用mapper代理对象完成数据库的操作;Mapper代理对象的创建,利用的是JDK的动态代理,InvocationHandler是MapperProxy,后续Mapper代理对象方法的执行都会先经过MapperProxy的invoke方法。

 

但是,此时SqlSessionFactory的创建、SqlSession的创建以及mapper代理对象的获取都是我们手动操作的,实际应用中,mybatis往往也不会单独使用,绝大多数都是集成在spring中,也就是说我们无需手动去管理mybatis相关对象的生命周期,全部都由spring容器统一管理,那么spring是什么时候在哪创建的mybatis的相关对象的呢?

 

尤其是mapper代理对象MapperProxy的创建

 

Springboot集成mybatis

 

当springboot(其实还是spring)集成mybatis后,mybatis的对象是交给spring容器管理的,只会实例化一次,然后伴随着spring容器一直存在,直到spring容器销毁

 

自动配置:MybatisAutoConfiguration

 

 

 

Mybatis的自动配置类:MybatisAutoConfiguration,至于如何加载此类,可参考:

https://www.cnblogs.com/youzhibing/p/9550343.html

 

MybatisAutoConfiguration会被当做配置类被spring解析,我们来看看spring容器会从此配置类中解析到什么

创建了SqlSessionFactory实例(实际类型:

 

DefaultSqlSessionFactory),并注册到了spring容器;此时我们应该还注意到

 

@Import({ AutoConfiguredMapperScannerRegistrar.class })

 

AutoConfiguredMapperScannerRegistrar继承了ImportBeanDefinitionRegistrar(注意看类注释,有兴许的可以更深入的研究下),那么它的registerBeanDefinitions也会被调用

 

@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
 
  logger.debug("Searching for mappers annotated with @Mapper");
 
  ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
 
  try {
    if (this.resourceLoader != null) {
      scanner.setResourceLoader(this.resourceLoader);
    }
 
    // 获取启动类所在的包,如:com.lee.shiro,会作为扫描开始的base package,一般只会有一个,但支持多个
    List packages = AutoConfigurationPackages.get(this.beanFactory);
    if (logger.isDebugEnabled()) {
      for (String pkg : packages) {
        logger.debug("Using auto-configuration base package '{}'", pkg);
      }
    }
 
    scanner.setAnnotationClass(Mapper.class);                    // 设置扫谁,Mapper注解是被扫描对象
    scanner.registerFilters();
    scanner.doScan(StringUtils.toStringArray(packages));        // 扫描所有mapper,进行bean定义处理
  } catch (IllegalStateException ex) {
    logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled.", ex);
  }
}

 

以我们启动类所在的包(com.lee.shiro)为基包,扫描所有的mapper,然后修改所有mapper在spring容器中的bean定义,将mapper的beanClass全部指向了MapperFactoryBean

 

mapper代理对象的创建:MapperFactoryBean

 

MapperFactoryBean继承SqlSessionDaoSupport,SqlSessionDaoSupport有两个方法用来设置SqlSession

 

public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
    if (!this.externalSqlSession) {
      this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
    }
}
 
public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
    this.sqlSession = sqlSessionTemplate;
    this.externalSqlSession = true;
}

 

可以看到SqlSession的实际类型是:SqlSessionTemplate,SqlSessionTemplate在MybatisAutoConfiguration以@Bean方式创建的

 

Spring在创建Service实例:UserServiceImpl的时候,发现依赖mapper(可能还有其他的实例依赖mapper),那么就会去spring容器获取mapper实例,没有则进行创建,然后注入进来(依赖注入);

 

具体创建过程如下

 

if (mbd.isSingleton()) {
    sharedInstance = getSingleton(beanName, () -> {
        try {
            // 创建mapper对象,beanName:com.lee.shiro.mapper.UserMapper,创建出来的实例实际上是MapperFactoryBean类型
            return createBean(beanName, mbd, args);        
        }
        catch (BeansException ex) {
            // Explicitly remove instance from singleton cache: It might have been put there
            // eagerly by the creation process, to allow for circular reference resolution.
            // Also remove any beans that received a temporary reference to the bean.
            destroySingleton(beanName);
            throw ex;
        }
    });
    // 获取给定bean实例的对象,如果是FactoryBean,则获取bean实例本身或其创建的对象
    bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}

 

因为Spring在mapper扫描的时候,将所有mapper bean定义中的beanClass设置成了MapperFactoryBean(继承了FactoryBean),所以通过createBean方法创建的mapper实例实际上是MapperFactoryBean对象,然后通过

 

bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
 
protected Object getObjectForBeanInstance(
        Object beanInstance, String name, String beanName, @Nullable RootBeanDefinition mbd) {
 
    // Don't let calling code try to dereference the factory if the bean isn't a factory.
    // isFactoryDereference方法判断name中是否有&字符
    if (BeanFactoryUtils.isFactoryDereference(name)) {
        if (beanInstance instanceof NullBean) {
            return beanInstance;
        }
        if (!(beanInstance instanceof FactoryBean)) {
            throw new BeanIsNotAFactoryException(transformedBeanName(name), beanInstance.getClass());
        }
    }
 
    // Now we have the bean instance, which may be a normal bean or a FactoryBean.
    // If it's a FactoryBean, we use it to create a bean instance, unless the
    // caller actually wants a reference to the factory.
    // 此时的beanInstance可能是一个普通bean,也可能是一个FactoryBean
    // 如果是一个FactoryBean,那么就用它创建想要的bean实例
    // 此if表示,如果beanInstance是普通bean,或者本来就想要FactoryBean实例,则直接返回beanInstance
    if (!(beanInstance instanceof FactoryBean) || BeanFactoryUtils.isFactoryDereference(name)) {
        return beanInstance;
    }
 
    Object object = null;
    if (mbd == null) {
        object = getCachedObjectForFactoryBean(beanName);
    }
    // 此时表明beanInstance是一个FactoryBean,并且不是想要FactoryBean实例
    if (object == null) {
        // Return bean instance from factory.
        FactoryBean factory = (FactoryBean) beanInstance;
        // Caches object obtained from FactoryBean if it is a singleton.
        if (mbd == null && containsBeanDefinition(beanName)) {
            mbd = getMergedLocalBeanDefinition(beanName);
        }
        boolean synthetic = (mbd != null && mbd.isSynthetic());
        // 通过FactoryBean实例创建我们想要的实例
        object = getObjectFromFactoryBean(factory, beanName, !synthetic);
    }
    return object;
}

 

获取真正想要的bean实例,如果beanInstance是普通bean,或者本来就想要FactoryBean实例(beanName中有&),那么直接返回beanInstance,否则用FactoryBean实例来创建我们想要的实例对象。

 

说回来就是会调用MapperFactoryBean的getObject()方法来获取Mapper的代理对象

 

至此,前情回顾中的问题也就清晰了

 

总结

 

1、自动配置的过程中,spring会扫描所有的mapper,并将所有mapper bean定义中的beanClass指向MapperFactoryBean;

 

2、创建mapper实例的时候,根据bean定义创建的实例实际上是MapperFactoryBean实例,然后再利用MapperFactoryBean获取mapper实例(调用MapperFactoryBean的getObject方法,mybatis会利用jdk的动态代理创建mapper代理对象);

 

3、对比Mybatis源码解析 - mapper代理对象的生成,你有想过吗,其实就是将我们手动创建的过程通过自动配置,将创建过程交给了spring;

 

补充扩展

 

Mybatis源码解析 - mapper代理对象的生成,你有想过吗

 

spring-boot-2.0.3启动源码篇一 - SpringApplication构造方法

Mybatis源码解析 - mapper代理对象的生成,你有想过吗

Spring拓展接口之FactoryBean,我们来看看其源码实现

相关文章
|
7月前
|
SQL Java 数据库连接
【YashanDB知识库】解决mybatis的mapper文件sql语句结尾加分号";"报错
【YashanDB知识库】解决mybatis的mapper文件sql语句结尾加分号";"报错
|
3月前
|
SQL XML Java
MyBatis Mapper中使用limit参数的查询问题
总结而言,MyBatis中使用 `limit`参数的查询可以高度定制并且灵活,基于方法签名和XML映射文件的组合来达成多样化的查询需求。通过参数化查询和动态SQL,MyBatis可以有效地处理各种复杂情境下的数据库操作,并且将SQL语句的维护与业务代码的编写相分离,提升代码的可维护性和可阅读性。
335 13
|
7月前
|
XML Java 数据库连接
微服务——SpringBoot使用归纳——Spring Boot集成MyBatis——基于 xml 的整合
本教程介绍了基于XML的MyBatis整合方式。首先在`application.yml`中配置XML路径,如`classpath:mapper/*.xml`,然后创建`UserMapper.xml`文件定义SQL映射,包括`resultMap`和查询语句。通过设置`namespace`关联Mapper接口,实现如`getUserByName`的方法。Controller层调用Service完成测试,访问`/getUserByName/{name}`即可返回用户信息。为简化Mapper扫描,推荐在Spring Boot启动类用`@MapperScan`注解指定包路径避免逐个添加`@Mapper`
314 0
|
4月前
|
Java 数据库连接 数据库
Spring boot 使用mybatis generator 自动生成代码插件
本文介绍了在Spring Boot项目中使用MyBatis Generator插件自动生成代码的详细步骤。首先创建一个新的Spring Boot项目,接着引入MyBatis Generator插件并配置`pom.xml`文件。然后删除默认的`application.properties`文件,创建`application.yml`进行相关配置,如设置Mapper路径和实体类包名。重点在于配置`generatorConfig.xml`文件,包括数据库驱动、连接信息、生成模型、映射文件及DAO的包名和位置。最后通过IDE配置运行插件生成代码,并在主类添加`@MapperScan`注解完成整合
634 1
Spring boot 使用mybatis generator 自动生成代码插件
|
7月前
|
SQL Java 数据库连接
【YashanDB 知识库】解决 mybatis 的 mapper 文件 sql 语句结尾加分号";"报错
【YashanDB 知识库】解决 mybatis 的 mapper 文件 sql 语句结尾加分号";"报错
|
7月前
|
XML Java 数据库连接
微服务——SpringBoot使用归纳——Spring Boot集成MyBatis——基于注解的整合
本文介绍了Spring Boot集成MyBatis的两种方式:基于XML和注解的形式。重点讲解了注解方式,包括@Select、@Insert、@Update、@Delete等常用注解的使用方法,以及多参数时@Param注解的应用。同时,针对字段映射不一致的问题,提供了@Results和@ResultMap的解决方案。文章还提到实际项目中常结合XML与注解的优点,灵活使用两者以提高开发效率,并附带课程源码供下载学习。
552 0
|
7月前
|
Java 数据库连接 数据库
微服务——SpringBoot使用归纳——Spring Boot集成MyBatis——MyBatis 介绍和配置
本文介绍了Spring Boot集成MyBatis的方法,重点讲解基于注解的方式。首先简述MyBatis作为持久层框架的特点,接着说明集成时的依赖导入,包括`mybatis-spring-boot-starter`和MySQL连接器。随后详细展示了`properties.yml`配置文件的内容,涵盖数据库连接、驼峰命名规范及Mapper文件路径等关键设置,帮助开发者快速上手Spring Boot与MyBatis的整合开发。
877 0
|
9月前
|
SQL Java 数据库连接
【潜意识Java】深入理解MyBatis的Mapper层,以及让数据访问更高效的详细分析
深入理解MyBatis的Mapper层,以及让数据访问更高效的详细分析
1113 1
|
12月前
|
Java Maven Docker
gitlab-ci 集成 k3s 部署spring boot 应用
gitlab-ci 集成 k3s 部署spring boot 应用
|
11月前
|
消息中间件 监控 Java
您是否已集成 Spring Boot 与 ActiveMQ?
您是否已集成 Spring Boot 与 ActiveMQ?
322 0