当前位置: 首页 > news >正文

【MyBatis】MyBatis与Spring和Spring Boot整合原理

目录

一、前言

二、Spring整合MyBatis

2.1 在Spring中使用MyBatis

2.1.1 引入依赖

2.1.2 完成初始化

2.1.3 测试代码

2.1.4 小结

2.2 源码分析

2.2.1 SqlSessionFactoryBean

2.2.1.1 事务集成

2.2.2 Mapper接口注册

2.2.2.1 Spring是怎么管理Mapper接口的动态代理的?

2.2.2.1.1 Spring中Bean的产生过程

2.2.2.1.2 如何将Mapper的代理对象加入到Spring容器中

2.2.2.1.3 最终的解决方案

2.2.2.1.3.1 FactoryBean

2.2.2.1.3.2 Import

2.2.2.1.4 小结

2.2.2.2 创建Mapper接口的代理对象,并将其注册到Spring容器的源码分析

2.3 总结流程图

三、Spring Boot整合MyBatis


一、前言

在真实的项目我们几乎不会将MyBatis 单独运用到项目中,而是将其整合到Spring框架或者Spring Boot中,本文将通过讲解MyBatis 与Spring和Spring Boot的整合的方法和原理。

二、Spring整合MyBatis

Spring整合MyBatis的原理也是一道非常高频的面试题, Spring中集成Mybatis主要有两个难点,一是事务的集成,二是Mapper接口注册到Spring容器中,Spring中集成MyBatis需要引入mybatismybatis-spring依赖包。

在Spring中我们通过mybatis-spring 中间框架将MyBatis和Spring 两个完全不相关的框架整合起来

该框架一方面负责加载和解析MyBatis相关配置,另一方面,该框架还会通过Spring提供的扩展点,把各种Dao接口对应的对象放入IOC容器中。

2.1 Spring中使用MyBatis

2.1.1 引入依赖

<dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.3</version>
</dependency><dependency><groupId>org.mybatis</groupId><artifactId>mybatis-spring</artifactId><version>2.0.3</version>
</dependency>

2.1.2 完成初始化

JavaConfig + 注解完成初始化:

我们可以通过JavaConfig + 注解将SqlSessionFactoryBean注册到Spring容器中,以及通过@MapperScan@MapperScans确定将哪些Mapper接口注册到Spring容器中来完成初始化。

@Configuration
// 将这个包下面的所有Mapper接口注册到Spring容器中
@MapperScan(basePackages = {"com.eleven.icode.ispring.mapper"})
public class MyBatisConfig {@Beanpublic SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) throws IOException {// 创建SqlSessionFactoryBeanSqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();// 解析全局配置文件和SQL映射文件// 配置数据源factoryBean.setDataSource(dataSource);// 配置全局配置文件factoryBean.setConfigLocation(new ClassPathResource("mybatis-config.xml"));// 配置SQL映射文件factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml"));// 返回SqlSessionFactoryBean,将其注册到Spring容器中return factoryBean;}
}

XML配置文件完成初始化:

我们也可以通过在Spring的配置文件SpringContext.xml中配置这些Bean,这些Bean主要用于配置数据源,配置sqlSessionFactory用于生成SqlSession,配置MapperScannerConfigurer用于扫描接口,这种方式也可以将配置的组件注册到Spring容器中,来完成初始化。配置文件如下:

<context:property-placeholder  location="jdbc.properties"/>
<context:component-scan base-package="com.jay"/>
<!--配置数据源-->
<bean id="dataSource" class="org.apache.ibatis.datasource.pooled.PooledDataSource"><!-- 配置与数据库交互的4个必要属性 --><property name="driver" value="${jdbc.driver}"/><property name="url" value="${jdbc.url}"/><property name="username" value="${jdbc.username}"/><property name="password" value="${jdbc.password}"/>
</bean>
<!--配置sqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"><!-- 给 SqlSessionFactory 配置数据源,这里引用上面的数据源配置 --><property name="dataSource" ref="dataSource"/><!--配置MyBatis-cfg.xml的路径--><property name="configLocation" value="MyBatis-cfg.xml"/><!--配置映射文件--><property name="mapperLocations" value="mybatis/*.xml"/>
</bean>
<!--配置MapperScannerConfigurer-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"><!--配合Dao接口所在的包--><property name="basePackage" value="com.jay.mapper"/>
</bean>

不管用上面的哪种方法,我们都需要配置一下MyBatis的配置文件MyBatis-cfg.xml(具体内容在之前的笔记中已经讲过了:MyBatis的配置文件详解),配置如下:

<configuration><settings><setting name="cacheEnabled" value="true"/></settings><typeAliases><typeAlias type="com.jay.entity.ClassRoom" alias="ClassRoom"/><typeAlias type="com.jay.entity.Student" alias="Student"/></typeAliases>
</configuration>

需要注意的是,MyBatis-cfg.xml 中的配置除了settings元素是必须配置在MyBatis的配置文件中,其余元素都可以配置到Spring的配置文件中。

2.1.3 测试代码

配置文件处理完成之后,接着我们来新建一个映射文件以及Dao 接口测试下。此处我新建了一个名为StudentMapper.xml的映射文件:

<mapper namespace="com.jay.mapper.StudentMapper"><resultMap id="studentResult" type="Student"><id property="id" column="id"/><result property="name" column="name"/><result property="age" column="age"/><result property="sexEnum" column="sex"typeHandler="com.jay.Handler.GeneralEnumHandler"/><association property="classRoom" javaType="ClassRoom"><id property="id" column="id"/><result property="name" column="name"/></association></resultMap><resultMap id="classRoomResult" type="ClassRoom"><id property="id" column="id"/><result property="name" column="name"/></resultMap><select id="selectStudentById" resultMap="studentResult">SELECT s.id,s.`name`,s.age,s.sex,c.id,c.`name` FROM student s, class cWHERE s.class_id=c.id AND s.id=#{id}</select>
</mapper>

对应的Dao接口如下:

@Repository
public interface StudentMapper {/*** 根据id查询学生* @param id* @return*/Student selectStudentById(Integer id);
}

最后我们新建一个测试类,测试下结果:

@RunWith(SpringJUnit4ClassRunner.class)
// 配置Spring配置
@ContextConfiguration("classpath:SpringContext.xml")
public class StudentMapperTest {@Autowiredprivate StudentMapper studentMapper;@Testpublic void testSelectStudentById() {Student student = studentMapper.selectStudentById(1);System.out.println(student.toString());}
}

测试结果如下:

2.1.4 小结

至此我们MyBatis与Spring的整合就完成了,当然这是一个很小的示范demo,但是其包括了Spring与MyBatis整合的要点。其流程无非就是:

  1. 先引入相关的依赖,Spring的依赖和MyBatis的依赖等。
  2. 接着就是对数据源,SqlSessionFactory等信息进行配置。
  3. 最后就是编写映射文件和Dao接口。

下面我们就深入源码,讲解Spring整合MyBatis的底层原理。

我们就以Spring初始化MyBatis为讲解入口,也就是上面讲过的代码2.1.2 完成初始化中:1、构造SqlSessionFactoryBean将其注册到Spring容器;2、将Mapper接口注册到Spring容器 这两个步骤来进行源码分析。

2.2 源码分析

2.2.1 SqlSessionFactoryBean

SqlSessionFactoryBean是一个FactoryBean,且重写了getObjectType方法将代理的Bean的类型设置为了原始的SqlSessionFactory类型,SqlSessionFactoryBean实现了InitializingBean接口,主要逻辑都在其实例化完成时调用的afterPropertiesSet()(实现了InitializingBean接口的类,可以实现这个接口的afterPropertiesSet()方法,这个方法会在Bean初始化的时候被调用),其作用就是创建Mybatis的SqlSessionFactory,其可以代替全局配置文件,若设置了配置文件可对配置文件内容做一个补充

// SqlSessionFactoryBean是Spring整合MyBatis时使用的类,如果只是单独MyBatis框架的话,是不用这个类的。所以前面单独讲MyBatis源码的时候并没有出现这个类,毕竟这个类都实现了Spring提供的InitializingBean接口,这也进一步说明了这个类是和Spring有关的。
package org.mybatis.spring;
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {// 这个是MyBatis提供的构造类,在前面也已经讲过了:SqlSessionFactoryBuilder类,Spring也要通过它来初始化框架private SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();// SqlSessionFactoryBean实现InitializingBean接口,需要实现其afterPropertiesSet()。SqlSessionFactoryBean 初始化的时候Spring会自动调用这个方法public void afterPropertiesSet() throws Exception {notNull(dataSource, "Property 'dataSource' is required");notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),"Property 'configuration' and 'configLocation' can not specified with together");// 在方法buildSqlSessionFactory()中通过SqlSessionFactoryBean的成员属性sqlSessionFactoryBuilder对象来构建我们的sqlSessionFactorythis.sqlSessionFactory = buildSqlSessionFactory();}protected SqlSessionFactory buildSqlSessionFactory() throws Exception {// 声明一个Configuration对象用于保存mybatis的所有的配置信息final Configuration targetConfiguration; // 声明一个XMLConfigBuilder对象,用于解析XML配置文件,这个类前面也讲过了:XMLConfigBuilderXMLConfigBuilder xmlConfigBuilder = null;// 初始化configuration对象,和设置其 `configuration.variables` 属性if (this.configuration != null) {// 把配置的SqlSessionFactoryBean配置的configuration赋值给targetConfigurationtargetConfiguration = this.configuration;if (targetConfiguration.getVariables() == null) {targetConfiguration.setVariables(this.configurationProperties);} else if (this.configurationProperties != null) {targetConfiguration.getVariables().putAll(this.configurationProperties);}}// 创建xml配置构建器对象(xmlConfigBuilder),对全局配置文件mybatis-config.xml配置文件进行解析else if (this.configLocation != null) { xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);targetConfiguration = xmlConfigBuilder.getConfiguration();} else {targetConfiguration = new Configuration();// 判断configurationProperties不为空,则调用targetConfiguration.set方法把configurationProperties注入到Configuration对象中Optional.ofNullable(this.configurationProperties).ifPresent(targetConfiguration::setVariables);}Optional.ofNullable(this.objectFactory).ifPresent(targetConfiguration::setObjectFactory);Optional.ofNullable(this.objectWrapperFactory).ifPresent(targetConfiguration::setObjectWrapperFactory);Optional.ofNullable(this.vfs).ifPresent(targetConfiguration::setVfsImpl);// typeAliasesPackage配置情况分为二种:在mybaits-config.xml中配置,在配置SqlSessionFactoryBean时配置if (hasLength(this.typeAliasesPackage)) {// 扫描typeAliasesPackage包路径下所有实体类class类型进行过滤注册到Configuration的别名映射器中scanClasses(this.typeAliasesPackage, this.typeAliasesSuperType).stream().filter(clazz -> !clazz.isAnonymousClass()).filter(clazz -> !clazz.isInterface()).filter(clazz -> !clazz.isMemberClass()).forEach(targetConfiguration.getTypeAliasRegistry()::registerAlias);}// 判断SqlSessionFactory是否配置了typeAliases,一般typeAliasesPackage配置了就没有必要配置typeAliases,注册到Configuration的别名映射器中if (!isEmpty(this.typeAliases)) {Stream.of(this.typeAliases).forEach(typeAlias -> { targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias);});}// 把自定义的插件注册到的mybatis的配置类上if (!isEmpty(this.plugins)) {Stream.of(this.plugins).forEach(plugin -> { targetConfiguration.addInterceptor(plugin);});}// 扫描自定义的类型处理器(用来处理的java类型和数据库类型的转化) 并且注册到的 targetConfiguration(批量注册)if (hasLength(this.typeHandlersPackage)) {scanClasses(this.typeHandlersPackage, TypeHandler.class).stream().filter(clazz -> !clazz.isAnonymousClass()).filter(clazz -> !clazz.isInterface()).filter(clazz -> !Modifier.isAbstract(clazz.getModifiers())).forEach(targetConfiguration.getTypeHandlerRegistry()::register);}// 通过配置<TypeHandlers></TypeHandlers>的形式来注册的类型处理器对象if (!isEmpty(this.typeHandlers)) { Stream.of(this.typeHandlers).forEach(typeHandler -> { targetConfiguration.getTypeHandlerRegistry().register(typeHandler);});}// Mybatis从3.2开始支持可插拔的脚本语言,因此可插入一种语言的驱动来写基于这种语言的动态SQL查询if (!isEmpty(this.scriptingLanguageDrivers)) {Stream.of(this.scriptingLanguageDrivers).forEach(languageDriver -> { targetConfiguration.getLanguageRegistry().register(languageDriver);});}Optional.ofNullable(this.defaultScriptingLanguageDriver).ifPresent(targetConfiguration::setDefaultScriptingLanguage);// 设置数据库厂商if (this.databaseIdProvider != null) { try {targetConfiguration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));} catch (SQLException e) {throw new NestedIOException("Failed getting a databaseId", e);}}// 若二级缓存配置不为空,注册二级缓存Optional.ofNullable(this.cache).ifPresent(targetConfiguration::addCache); if (xmlConfigBuilder != null) {try {// 真正的解析的配置(mybatis-config.xml)的document对象xmlConfigBuilder.parse();} catch (Exception ex) {throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);} finally {ErrorContext.instance().reset();}}// 为的configuration设置一个环境变量targetConfiguration.setEnvironment(new Environment(this.environment, this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory, this.dataSource));// 循环遍历解析mapper.xml文件if (this.mapperLocations != null) {if (this.mapperLocations.length == 0) {} else {for (Resource mapperLocation : this.mapperLocations) {if (mapperLocation == null) {continue;}try {XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());// 真正的解析mapper.xml文件。这个过程我们在前面也已经讲过了:解析SQL映射文件(解析XML映射文件)xmlMapperBuilder.parse(); } catch (Exception e) {throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);} finally {ErrorContext.instance().reset();}}}}// 通过建造者模式构建的SqlSessionFactory对象 默认是DefaultSqlSessionFactory。整个流程前面都已经讲过:SqlSessionFactoryBuilder类return this.sqlSessionFactoryBuilder.build(targetConfiguration); }private Set<Class<?>> scanClasses(String packagePatterns, Class<?> assignableType) throws IOException {Set<Class<?>> classes = new HashSet<>();// 把的多个配置报路径转换成字符串数组String[] packagePatternArray = tokenizeToStringArray(packagePatterns, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);// 循环的包路径for (String packagePattern : packagePatternArray) { // 把包路径下的class解析成的Resouce数组Resource[] resources = RESOURCE_PATTERN_RESOLVER.getResources(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + ClassUtils.convertClassNameToResourcePath(packagePattern) + "/**/*.class");for (Resource resource : resources) {try {// 挨个解析成的class类型ClassMetadata classMetadata = METADATA_READER_FACTORY.getMetadataReader(resource).getClassMetadata();Class<?> clazz = Resources.classForName(classMetadata.getClassName());// 判断当前的class类型是不是被支持if (assignableType == null || assignableType.isAssignableFrom(clazz)) {// 加入到结合中classes.add(clazz); }} catch (Throwable e) {LOGGER.warn(() -> "Cannot load the '" + resource + "'. Cause by " + e.toString());}}}return classes;}// 重写了getObjectType方法将代理的Bean的类型设置为了原始的SqlSessionFactory类型public Class<? extends SqlSessionFactory> getObjectType() {return this.sqlSessionFactory == null ? SqlSessionFactory.class : this.sqlSessionFactory.getClass();}/*** 实现FactoryBean接口的getObject方法* 将SqlSessionFactory对象注入spring容器* {@inheritDoc}*/@Overridepublic SqlSessionFactory getObject() throws Exception {if (this.sqlSessionFactory == null) {afterPropertiesSet();}return this.sqlSessionFactory;}}

可知SqlSessionFactoryBean主要通过对applicationContext.xml解析来完成对Configuration的实例化以及对映射文件*mapper.xml的解析。

关键点:

  1. *XMLConfigBuilder:在MyBatis中主要负责解释mybatis-config.xml
  2. 解析完后,如果我们自己设置了则使用我们的设置的进行覆盖,不做一一介绍了
  3. XMLMapperBuilder:负责解析映射配置文件
  4. targetConfiguration.setEnvironment 这里注意一下,事务工厂会使用一个新的new SpringManagedTransactionFactory(),而不是MyBatis之前的ManagedTransactionFactory。这个SpringManagedTransactionFactory会使用Spring事务中的dataSource,从而达到跟事务集成。

 

2.2.1.1 事务集成

事务集成是在SqlSessionFactoryBean中完成的,在为configuration设置环境变量时创建传入SpringManagedTransactionFactory,在MyBatis中是使用的ManagedTransactionFactory创建一个ManagedTransaction,其是直接从DataSource中获取的Connection

public class DefaultSqlSessionFactory implements SqlSessionFactory {private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) {if (environment == null || environment.getTransactionFactory() == null) {return new ManagedTransactionFactory();}return environment.getTransactionFactory();}
}public class ManagedTransactionFactory implements TransactionFactory {public Transaction newTransaction(Connection conn) {return new ManagedTransaction(conn, closeConnection);}public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) {return new ManagedTransaction(ds, level, closeConnection);}
}public class ManagedTransaction implements Transaction {public Connection getConnection() throws SQLException {if (this.connection == null) {openConnection();}return this.connection;}protected void openConnection() throws SQLException {this.connection = this.dataSource.getConnection();if (this.level != null) {this.connection.setTransactionIsolation(this.level.getLevel());}}
}

 SpringManagedTransactionFactory中创建的是SpringManagedTransaction,其获取的数据库连接是通过DataSourceUtils工具类从事务同步管理器TransactionSynchronizationManager中获取的连接,从而达到与Spring事务集成的目的。

public class SpringManagedTransactionFactory implements TransactionFactory {public Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit) {return new SpringManagedTransaction(dataSource);}
}public class SpringManagedTransaction implements Transaction {public Connection getConnection() throws SQLException {if (this.connection == null) {openConnection();}return this.connection;}private void openConnection() throws SQLException {this.connection = DataSourceUtils.getConnection(this.dataSource);this.autoCommit = this.connection.getAutoCommit();this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);}
}

2.2.2 Mapper接口注册

2.2.2.1 Spring是怎么管理Mapper接口的动态代理的?

面试的时候会经常问到Spring整合MyBatis的问题,而在这个问题上,我们重点要关注的就是这个代理对象。因为Spring整合MyBatis的目的就是:把某个Mapper的代理对象作为一个bean放入Spring容器中,使得能够像使用一个普通bean一样去使用这个代理对象,比如能被@Autowire自动注入。

比如当Spring和Mybatis整合之后,我们就可以使用如下的代码来使用MyBatis中的代理对象了:

@Component
public class UserService {// 自动注入@Autowiredprivate UserMapper userMapper;public User getUserById(Integer id) {return userMapper.selectById(id);}
}

UserService中的userMapper属性就会被自动注入MyBatis中的代理对象。如果你基于一个已经完成整合的项目去调试即可发现,userMapper的类型为:org.apache.ibatis.binding.MapperProxy@41a0aa7d。证明确实是MyBatis中的代理对象。(之前我们已经讲过了MyBatis给Mapper接口创建代理类的原理:3.1.1 为 Mapper 接口创建代理对象)

好,那么现在我们要解决的问题的就是:如何能够把MyBatis的代理对象作为一个bean放入Spring容器中?

要解决这个,我们需要对Spring的bean生成过程有一个了解。

2.2.2.1.1 SpringBean的产生过程

这一块的内容我们讲Spring源码的时候已经学过了(Spring IoC 容器源码分析——ClassPathXmlApplicationContext和Spring IoC 容器源码分析——AnnotationConfigApplicationContext),这里就简单复习一下。

Spring启动过程中,大致会经过如下步骤去生成bean:

  1. 扫描指定的包路径下的class文件
  2. 根据class信息生成对应的BeanDefinition
  3. 在此处,程序员可以利用某些机制去修改BeanDefinition
  4. 根据BeanDefinition生成bean实例
  5. 把生成的bean实例放入Spring容器中

假设有一个A类,假设有如下代码:

一个A类:

@Component
public class A {
}

一个B类,不存在@Component注解

public class B {
}

执行如下代码:

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
System.out.println(context.getBean("a"));

输出结果为:com.tulingxueyuan.beans.A@6acdbdf5

A类对应的bean对象类型仍然为A类。但是我们可以认为的改变这个结果,比如我们可以利用BeanFactory后置处理器来修改BeanDefinition,我们添加一个BeanFactory后置处理器:

@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {BeanDefinition beanDefinition = beanFactory.getBeanDefinition("a");beanDefinition.setBeanClassName(B.class.getName());}
}

这样就会导致,原本的A类对应的BeanDefiniton被修改了,被修改成了B类,那么后续正常生成的bean对象的类型就是B类。此时,调用如下代码会报错:

context.getBean(A.class);

但是调用如下代码不会报错,尽管B类上没有@Component注解:

context.getBean(B.class);

并且,下面代码返回的结果是:com.tulingxueyuan.beans.B@4b1c1ea0

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
System.out.println(context.getBean("a"));

之所以讲这个问题,是想说明一个问题:在Spring中,bean对象跟class没有直接关系,跟BeanDefinition才有直接关系

在Spring中,如果你想生成一个bean,那么得先生成一个BeanDefinition,就像你想new一个对象实例,得先有一个class。

那么回到我们要解决的问题:如何能够把MyBatis的代理对象作为一个bean放入Spring容器中?

2.2.2.1.2 如何将Mapper的代理对象加入到Spring容器中

继续回到我们的问题,我们现在想自己生成一个bean,那么得先生成一个BeanDefinition,只要有了BeanDefinition,通过在BeanDefinition中设置bean对象的类型,然后把BeanDefinition添加给Spring,Spring就会根据BeanDefinition自动帮我们生成一个类型对应的bean对象。

所以,现在我们要解决两个问题:

  1. MyBatis的代理对象的类型是什么?因为我们要设置给BeanDefinition
  2. 我们怎么把BeanDefinition添加给Spring容器?

注意:上文中我们使用的BeanFactory后置处理器,它只能修改BeanDefinition,并不能新增一个BeanDefinition。我们应该使用Import技术来添加一个BeanDefinition。后文再详细介绍如果使用Import技术来添加一个BeanDefinition,可以先看一下伪代码实现思路。

假设:我们有一个UserMapper接口,它的代理对象的类型为UserMapperProxy。

那么我们的思路就是这样的,伪代码如下:

// 创建BeanDefinitoin
BeanDefinitoin bd = new BeanDefinitoin();
// 将代理类设置给BeanDefinitoin
bd.setBeanClassName(UserMapperProxy.class.getName());
// 将设置好的BeanDefinitoin注入到Spring容器中
SpringContainer.addBd(bd);

但是,这里有一个严重的问题,就是上文中的UserMapperProxy是我们假设的,它表示一个代理类的类型,然而MyBatis中的代理对象是利用的JDK的动态代理技术实现的,也就是代理对象的代理类是动态生成的,我们根本无法确定代理对象的代理类到底是什么。

所以回到我们的第一个问题:MyBatis的代理对象的类型是什么?

 这个问题可能有两种答案:

  1. 代理对象对应的代理类
  2. 代理对象对应的接口

由上面的分析可知,答案1肯定是不可能了,因为代理类是动态生成的,那么我们来看答案2:代理对象对应的接口

如果我们采用答案2,那么我们的思路就是:

BeanDefinition bd = new BeanDefinitoin();
// 注意这里,设置的是UserMapper
bd.setBeanClassName(UserMapper.class.getName());
SpringContainer.addBd(bd);

但是,实际上给BeanDefinition对应的类型设置为一个接口是行不通的,因为Spring没有办法根据这个BeanDefinition去new出对应类型的实例,接口是没法直接new出实例的。

那么现在问题来了,我要解决的问题:MyBatis的代理对象的类型是什么?

两个答案都被我们否定了,所以这个问题是无解的,所以我们不能再沿着这个思路去思考了,只能回到最开始的问题:如何能够把MyBatis的代理对象作为一个bean放入Spring容器中?

总结上面的推理:我们想通过设置BeanDefinition的class类型,然后由Spring自动的帮助我们去生成对应的bean,但是这条路是行不通的

2.2.2.1.3 最终的解决方案

那么我们还有没有其他办法,可以去生成bean呢?并且生成bean的逻辑不能由Spring来帮我们做了,得由我们自己来做。

2.2.2.1.3.1 FactoryBean

还是有一个解决方案的,那就是Spring中的FactoryBean接口。我们可以利用FactoryBean接口实现一个工厂类的Bean,去自定义我们要生成的bean对象,比如:

@Component
public class MyFactoryBean implements FactoryBean {@Overridepublic Object getObject() throws Exception {// 通过JDK动态代理创建代理对象Object proxyInstance = Proxy.newProxyInstance(MyFactoryBean.class.getClassLoader(), new Class[]{UserMapper.class}, new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {if (Object.class.equals(method.getDeclaringClass())) {return method.invoke(this, args);} else {// 执行代理逻辑return null;}}});return proxyInstance;}@Overridepublic Class<?> getObjectType() {return UserMapper.class;}
}

我们定义了一个MyFactoryBean,它实现了FactoryBean接口,getObject方法就是用来自定义生成bean对象逻辑的。

执行如下代码:

public class Test {public static void main(String[] args) {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);System.out.println("myFactoryBean: " + context.getBean("myFactoryBean"));System.out.println("&myFactoryBean: " + context.getBean("&myFactoryBean"));System.out.println("myFactoryBean-class: " + context.getBean("myFactoryBean").getClass());}
}

将打印:

myFactoryBean: com.tulingxueyuan.beans.myFactoryBean$1@4d41cee

&myFactoryBean: com.tulingxueyuan.beans.myFactoryBean@3712b94

myFactoryBean-class: class com.sun.proxy.$Proxy20

从结果我们可以看到,从Spring容器中拿名字为"myFactoryBean"的bean对象,就是我们所自定义的jdk动态代理所生成的代理对象。

所以,我们可以通过FactoryBean来向Spring容器中添加一个自定义的bean对象。上文中所定义的MyFactoryBean对应的就是UserMapper,表示我们定义了一个MyFactoryBean,相当于把UserMapper对应的代理对象作为一个bean放入到了容器中。

但是作为程序员,我们不可能每定义了一个Mapper,还得去定义一个MyFactoryBean,这是很麻烦的事情,我们改造一下MyFactoryBean,让他变得更通用,比如:

@Component
public class MyFactoryBean implements FactoryBean {// 注意这里private Class mapperInterface;public MyFactoryBean(Class mapperInterface) {this.mapperInterface = mapperInterface;}@Overridepublic Object getObject() throws Exception {Object proxyInstance = Proxy.newProxyInstance(MyFactoryBean.class.getClassLoader(), new Class[]{mapperInterface}, new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {if (Object.class.equals(method.getDeclaringClass())) {return method.invoke(this, args);} else {// 执行代理逻辑return null;}}});return proxyInstance;}@Overridepublic Class<?> getObjectType() {return mapperInterface;}
}

改造MyFactoryBean之后,MyFactoryBean变得灵活了,可以在构造MyFactoryBean时,通过构造传入不同的Mapper接口。

实际上MyFactoryBean也是一个Bean,我们也可以通过生成一个BeanDefinition来生成一个MyFactoryBean,并给构造方法的参数设置不同的值,比如伪代码如下:

BeanDefinition bd = new BeanDefinitoin();
// 注意一:设置的是MyFactoryBean
bd.setBeanClassName(MyFactoryBean.class.getName());
// 注意二:表示当前BeanDefinition在生成bean对象时,会通过调用MyFactoryBean的构造方法来生成,并传入UserMapper
bd.getConstructorArgumentValues().addGenericArgumentValue(UserMapper.class.getName())
SpringContainer.addBd(bd);

特别说一下注意二,表示表示当前BeanDefinition在生成bean对象时,会通过调用MyFactoryBean的构造方法来生成,并传入UserMapper的Class对象。那么在生成MyFactoryBean时就会生成一个UserMapper接口对应的代理对象作为bean了。

到此为止,其实就完成了我们要解决的问题:把MyBatis中的代理对象作为一个bean放入Spring容器中。只是我们这里是用简单的JDK代理对象模拟的MyBatis中的代理对象,这是为了让我们快速了解Spring将MyBatis的Mapper代理对象加入Spring容器的原理,在后面的章节我们会针对实际的源码进行分析。

2.2.2.1.3.2 Import

到这里,我们还有一个事情没有做,就是怎么真正的定义一个BeanDefinition,并把它添加到Spring中,上文说到我们要利用Import技术,比如可以这么实现:

定义如下类:

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {// 创建BeanDefinition构造器对象BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();// 通过构造器创建一个BeanDefinitionAbstractBeanDefinition beanDefinition = builder.getBeanDefinition();beanDefinition.setBeanClass(MyFactoryBean.class);beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(UserMapper.class);// 添加beanDefinitionregistry.registerBeanDefinition("my"+UserMapper.class.getSimpleName(), beanDefinition);}
}

并且在JavaConfig类AppConfig上添加@Import注解:

@Import(MyImportBeanDefinitionRegistrar.class)
public class AppConfig {
}

这样在启动Spring时就会新增一个BeanDefinition,该BeanDefinition会生成一个MyFactoryBean对象,并且在生成MyFactoryBean对象时会传入UserMapper.class对象,通过MyFactoryBean内部的逻辑,相当于会自动生产一个UserMapper接口的代理对象作为一个bean。

2.2.2.1.4 小结

总结一下,通过我们的分析,我们要整合Spring和Mybatis,需要我们做的事情如下:

  1. 定义一个MyFactoryBean
  2. 定义一个MyImportBeanDefinitionRegistrar
  3. 在AppConfig上添加一个注解@Import(MyImportBeanDefinitionRegistrar.class)

这样就可以基本完成整合的需求了,当然还有两个点是可以优化的。

第一,单独再定义一个@MyScan的注解,如下:

@Retention(RetentionPolicy.RUNTIME)
@Import(MyImportBeanDefinitionRegistrar.class)
public @interface MyScan {
}

这样在AppConfig上直接使用@MyScan即可

第二,在MyImportBeanDefinitionRegistrar中,我们可以去扫描Mapper,在MyImportBeanDefinitionRegistrar我们可以通过AnnotationMetadata获取到对应的@MyScan注解,所以我们可以在@MyScan上设置一个value,用来指定待扫描的包路径。然后在MyImportBeanDefinitionRegistrar中获取所设置的包路径,然后扫描该路径下的所有Mapper,生成BeanDefinition,放入Spring容器中。

所以,到此为止,Spring整合Mybatis的核心原理就结束了,再次总结一下:

  1. 定义一个MyFactoryBean,用来将Mybatis的代理对象生成一个bean对象
  2. 定义一个MyImportBeanDefinitionRegistrar,用来生成不同Mapper对象的MyFactoryBean
  3. 定义一个@MynScan,用来在启动Spring时执行MyImportBeanDefinitionRegistrar的逻辑,并指定包路径

以上这个三个要素分别对象org.mybatis.spring中的:

  1. MapperFactoryBean
  2. MapperScannerRegistrar
  3. @MapperScan​

经过上面的分析,相信大家已经明白了将Mapper代理对象加入到Spring容器的原理,下面就带大家分析真实的源码。

2.2.2.2 创建Mapper接口的代理对象,并将其注册到Spring容器的源码分析

Mapper接口扫描是通过@MapperScan@MapperScans注解中导入了MapperScannerRegistrar来完成的,Mapper接口注册到Spring容器中是借助FactoryBean来完成的,注解中设置了factoryBean注册Mapper接口的默认值为MapperFactoryBean

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {String[] value() default {};String[] basePackages() default {};Class<?>[] basePackageClasses() default {};Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;Class<? extends Annotation> annotationClass() default Annotation.class;Class<?> markerInterface() default Class.class;String sqlSessionTemplateRef() default "";String sqlSessionFactoryRef() default "";Class<? extends MapperFactoryBean> factoryBean() default MapperFactoryBean.class;String lazyInitialization() default "";
}@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.RepeatingRegistrar.class)
public @interface MapperScans {MapperScan[] value();
}

MapperScannerRegistrar实现了ImportBeanDefinitionRegistrar接口,在容器扫描BeanDefinition时会调用registerBeanDefinitions。其作用其实就是为容器注册一个或多个MapperScannerConfigurer以及给该类设置一些从@MapperScan注解上解析出来的属性。@MapperScans注解上可以配置多个@MapperScan注解在导入时通过RepeatingRegistrar遍历注册多个MapperScannerConfigurer

public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {// 从传入的配置类中解析@MapperScan注解信息,把MapperScan注解的属性转化为AnnotationAttributes类型AnnotationAttributes mapperScanAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));// 若上一步解析出来的mapperScanAttrs不为空,说明配置类上加了@MapperScan注解if (mapperScanAttrs != null) { // 调用重写的方法registerBeanDefinitions#generateBaseBeanName(importingClassMetadata, 0)即将注册的bean定义的名称进行处理registerBeanDefinitions(mapperScanAttrs, registry, generateBaseBeanName(importingClassMetadata, 0));}}void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName) {// 创建bean定义构造器通过够构造器来构建出的bean定义<MapperScannerConfigurer>应用到的设计模式[建造者模式]BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);// 手动为MapperScannerConfigurer开启processPropertyPlaceHolders属性为true,需要着重研究下MapperScannerConfigurer类的继承结构builder.addPropertyValue("processPropertyPlaceHolders", true);// 为的MapperScannerConfigurer解析@MapperScanner指定扫描的的注解类型Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");if (!Annotation.class.equals(annotationClass)) {builder.addPropertyValue("annotationClass", annotationClass);}Class<?> markerInterface = annoAttrs.getClass("markerInterface");// 是否配置了标记接口if (!Class.class.equals(markerInterface)) { builder.addPropertyValue("markerInterface", markerInterface);}Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");// 是否设置了MapperScannerConfigurer的beanName生成器对象if (!BeanNameGenerator.class.equals(generatorClass)) { builder.addPropertyValue("nameGenerator", BeanUtils.instantiateClass(generatorClass));}// 解析@MapperScan注解属性MapperFactoryBean设置到MapperScannerConfigurer声明一个自定义的MapperFactoryBean返回一个代理对象Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {builder.addPropertyValue("mapperFactoryBeanClass", mapperFactoryBeanClass);}// 解析@MapperScan的sqlSessionTemplateRef到底使用是哪个sqlSessionTemplate设置到MapperScannerConfigurer多数据源的情况下需要指定String sqlSessionTemplateRef = annoAttrs.getString("sqlSessionTemplateRef");if (StringUtils.hasText(sqlSessionTemplateRef)) {builder.addPropertyValue("sqlSessionTemplateBeanName", annoAttrs.getString("sqlSessionTemplateRef"));}// 解析@MapperScan的sqlSessionFactoryRef属性 设置到 MapperScannerConfigurer 多数据情况下的话 ,需要指定使用哪个 sqlSessionFactoryString sqlSessionFactoryRef = annoAttrs.getString("sqlSessionFactoryRef");if (StringUtils.hasText(sqlSessionFactoryRef)) {builder.addPropertyValue("sqlSessionFactoryBeanName", annoAttrs.getString("sqlSessionFactoryRef"));}// 解析@MapperScan扫描的的包或者是class对象List<String> basePackages = new ArrayList<>();basePackages.addAll(Arrays.stream(annoAttrs.getStringArray("value")).filter(StringUtils::hasText).collect(Collectors.toList()));basePackages.addAll(Arrays.stream(annoAttrs.getStringArray("basePackages")).filter(StringUtils::hasText).collect(Collectors.toList()));basePackages.addAll(Arrays.stream(annoAttrs.getClassArray("basePackageClasses")).map(ClassUtils::getPackageName).collect(Collectors.toList()));String lazyInitialization = annoAttrs.getString("lazyInitialization");// 指定MapperScannerConfigurer是否为懒加载if (StringUtils.hasText(lazyInitialization)) {builder.addPropertyValue("lazyInitialization", lazyInitialization);}builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));// 为的容器中注册了MapperScannerConfigurer的接口registry.registerBeanDefinition(beanName, builder.getBeanDefinition()); }private static String generateBaseBeanName(AnnotationMetadata importingClassMetadata, int index) {return importingClassMetadata.getClassName() + "#" + MapperScannerRegistrar.class.getSimpleName() + "#" + index;}static class RepeatingRegistrar extends MapperScannerRegistrar {@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {AnnotationAttributes mapperScansAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScans.class.getName()));if (mapperScansAttrs != null) {AnnotationAttributes[] annotations = mapperScansAttrs.getAnnotationArray("value");for (int i = 0; i < annotations.length; i++) {registerBeanDefinitions(annotations[i], registry, generateBaseBeanName(importingClassMetadata, i));}}}}
}

MapperScannerConfigurer中显示创建了ClassPathMapperScanner包扫描器对象,其继承自ClassPathBeanDefinitionScanner,其作用是为了扫描@MapperScan注解中配置包路径下的目标类。

public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {if (this.processPropertyPlaceHolders) {processPropertyPlaceHolders(); // 若MapperScannerConfigurer属性的processPropertyPlaceHolders为ture时则执行processPropertyPlaceHolders(),解析动态参数}// 显示的new一个ClassPathMapperScanner包扫描器对象该对象继承了spring的ClassPathBeanDefinitionScanner为了扫描器指定@MapperScan属性ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);scanner.setAddToConfig(this.addToConfig);scanner.setAnnotationClass(this.annotationClass);scanner.setMarkerInterface(this.markerInterface);scanner.setSqlSessionFactory(this.sqlSessionFactory);scanner.setSqlSessionTemplate(this.sqlSessionTemplate);scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);scanner.setResourceLoader(this.applicationContext);scanner.setBeanNameGenerator(this.nameGenerator);scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);if (StringUtils.hasText(lazyInitialization)) {scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));}scanner.registerFilters(); // 注册扫描规则过滤器// 真正的去扫描@MapperScan指定的路径下的bean定义信息,先会去调用ClassPathMapperScanner.scan()方法scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));}private void processPropertyPlaceHolders() {// 因为postProcessBeanDefinitionRegistry是为注册bean定义的,但注册bean定义时需要解析${basepackaage},但PropertyResourceConfigurer类型的bean定义还没有实例化成bean对象 ,故不能提供解析${basepackaage}的能力// 故显示的设置processPropertyPlaceHolders为ture,即想通过applicationContext.getBeansOfType(PropertyResourceConfigurer.class),提前把PropertyResourceConfigurer组件实例化出来从而解析${basepackaage}Map<String, PropertyResourceConfigurer> prcs = applicationContext.getBeansOfType(PropertyResourceConfigurer.class);// 判断PropertyResourceConfigurer的集合不为空,且applicationContext是ConfigurableApplicationContextif (!prcs.isEmpty() && applicationContext instanceof ConfigurableApplicationContext) {// 通过名称去容器中获取MapperScannerConfigurer组件BeanDefinition mapperScannerBean = ((ConfigurableApplicationContext) applicationContext).getBeanFactory().getBeanDefinition(beanName);// PropertyResourceConfigurer类没有暴露任何的方法来处理的property placeholder substitution即${basepackaage},有且只有提供一个BeanFactoryPostProcessor接口来处理的bean定义// 但调用BeanFactoryPostProcessor.postProcessBeanFactory()方法需要一个beanFactory,若从外面传入一个Ioc的容器进来会提前破坏ioc容器,故这里创建了一个临时的ioc容器,然后把mapperScannerBean注册到该临时ioc容器中DefaultListableBeanFactory factory = new DefaultListableBeanFactory();factory.registerBeanDefinition(beanName, mapperScannerBean);for (PropertyResourceConfigurer prc : prcs.values()) {//这时就可以大胆放心的处理临时的ioc容器factory中的bean定义,即当前的mapperScannerBeanprc.postProcessBeanFactory(factory); }// 处理完之后重新获取通过PropertyResourceConfigurer解析后的mapperScannerBean的属性PropertyValues values = mapperScannerBean.getPropertyValues(); // 更新MapperScannerBean属性可能有${}包裹的字段this.basePackage = updatePropertyValue("basePackage", values); this.sqlSessionFactoryBeanName = updatePropertyValue("sqlSessionFactoryBeanName", values);this.sqlSessionTemplateBeanName = updatePropertyValue("sqlSessionTemplateBeanName", values);this.lazyInitialization = updatePropertyValue("lazyInitialization", values);}this.basePackage = Optional.ofNullable(this.basePackage).map(getEnvironment()::resolvePlaceholders).orElse(null);this.sqlSessionFactoryBeanName = Optional.ofNullable(this.sqlSessionFactoryBeanName).map(getEnvironment()::resolvePlaceholders).orElse(null);this.sqlSessionTemplateBeanName = Optional.ofNullable(this.sqlSessionTemplateBeanName).map(getEnvironment()::resolvePlaceholders).orElse(null);this.lazyInitialization = Optional.ofNullable(this.lazyInitialization).map(getEnvironment()::resolvePlaceholders).orElse(null);}
}

首先会根据在@MapperScan注解上设置的注解类annotationClass、以及markerInterface来添加过滤器,若未设置将会将包下的所有接口扫描出来。

在调用超类ClassPathBeanDefinitionScannerscan方法扫描包时会调用子类ClassPathMapperScanner中的doScan,而该类又去调用了超类doScan方法,但有一点不同的是这复写了isCandidateComponent方法只扫描接口,而Spring中默认恰好相反。

当将所有符合条件的Mapper接口BeanDefinition信息扫描出来后,进行遍历将beanClass设置成MapperFactoryBeanClass,从而达到偷天换日将Mapper接口通过MapperFactoryBean来创建生成Bean。因为接口是不能被实例化生成Bean的。

public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {// 注册扫描规则过滤器public void registerFilters() { boolean acceptAllInterfaces = true;// 若annotationClass不为空,表示用户设置了此属性,则根据此属性生成过滤器以保证达到用户想要的效果,而封装此属性的过滤器就是 AnnotationTypeFiter,其保证在扫描对应Java文件时只接受标记有注解为annotationClass的接口if (this.annotationClass != null) {addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));acceptAllInterfaces = false;}// 如果markerlnterface不为空,表示要根据此属性生成过滤器以保证达到用户想要的效果,而封装此属性的过滤器就是实现AssignableTypeFiter接口的局部类,扫描过程中只有实现markerIntrface接口的接口才会被接受if (this.markerInterface != null) {addIncludeFilter(new AssignableTypeFilter(this.markerInterface) {@Overrideprotected boolean matchClassName(String className) {return false;}});acceptAllInterfaces = false;}// 若接受所有接口,则添加自定义INCLUDE过滤器TypeFilter,全部返回trueif (acceptAllInterfaces) { // default include filter that accepts all classesaddIncludeFilter((metadataReader, metadataReaderFactory) -> true);}// 对于命名为package-info Java文件,默认不作为逻辑实现接口,将其排除掉,使用TypeFiltler接口的局部类实现match 法addExcludeFilter((metadataReader, metadataReaderFactory) -> {String className = metadataReader.getClassMetadata().getClassName();return className.endsWith("package-info");});}public Set<BeanDefinitionHolder> doScan(String... basePackages) {// 调用父类ClassPathBeanDefinitionScanner来进行扫描Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages); // 若扫描后mapper包下有接口类,则扫描bean定义就不会为空if (beanDefinitions.isEmpty()) { LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");} else {// 这里是将Mapper接口注册到Spring容器中的关键processBeanDefinitions(beanDefinitions); }return beanDefinitions;}private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {GenericBeanDefinition definition;// 循环所有扫描出mapper的bean定义出来for (BeanDefinitionHolder holder : beanDefinitions) { // 获取的bean定义definition = (GenericBeanDefinition) holder.getBeanDefinition(); // 获取的bean定义的名称String beanClassName = definition.getBeanClassName();  // 设置ConstructorArgumentValues会通过构造器初始化对象definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // 进行真的偷天换日操作,将当前Bean的Class设置成MapperFactoryBean的Classdefinition.setBeanClass(this.mapperFactoryBeanClass);definition.getPropertyValues().add("addToConfig", this.addToConfig);// 为的Mapper对象绑定sqlSessionFactory引用,实际上是为MapperFactoryBean添加一个sqlSessionFactory的属性// 然后SpringIoc在实例化MapperFactoryBean时为通过populate()为UserMapper(MapperFactoryBean)的sqlSessionFactory属性赋值,调用set方法boolean explicitFactoryUsed = false;if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));explicitFactoryUsed = true;} else if (this.sqlSessionFactory != null) {definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);explicitFactoryUsed = true;}// 同上sqlSessionFactoryif (StringUtils.hasText(this.sqlSessionTemplateBeanName)) { definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));explicitFactoryUsed = true;} else if (this.sqlSessionTemplate != null) {// 将sqlSessionTemplate通过AUTOWIRE_BY_TYPE自动装配definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate); explicitFactoryUsed = true;}// 设置UserMapper<MapperFactoryBean>定义的注入模型是通过包扫描进来的,所有默认注入模型为AutowireCapableBeanFactory.AUTOWIRE_NO=0标识通过@Autowire注解注入// 因为字段上是没有@AutoWired注解,则MapperFactoryBean的字段属性永远自动注入不了值,故需要把UserMapper<MapperFactoryBean>bean定义的注入模型给改成的AUTOWIRE_BY_TYPE=1表示根据类型自动装配if (!explicitFactoryUsed) {definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);}// 设置bean定义的加载模型(是否为懒加载)definition.setLazyInit(lazyInitialization); }}protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();}
}

对于MapperFactoryBean代码就比较简单了,上面在给BeanDefinition设置属性时,给构造函数参数列表中添加了一个参数即原始Mapper类名,故实例化MapperFactoryBean时会调用有参构造函数。

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {private Class<T> mapperInterface;public MapperFactoryBean(Class<T> mapperInterface) {this.mapperInterface = mapperInterface;}public T getObject() throws Exception {return getSqlSession().getMapper(this.mapperInterface);}public Class<T> getObjectType() {return this.mapperInterface;}
}public class SqlSessionTemplate implements SqlSession, DisposableBean {public <T> T getMapper(Class<T> type) {// 最终去sqlSessionFactory.Configuration.mapperRegistry去获取我们的Mapper对象return getConfiguration().getMapper(type, this);}
}

2.3 总结流程图

三、Spring Boot整合MyBatis

接下来我们学习下在SpringBoot 中整合MyBatis。其实基本原理和上面讲的Spring是一样的,就不重复讲了,这里我们就介绍一下Spring Boot整合MyBatis的使用方法。

首先我们需要新建一个Spring Boot项目,新建项目的细节再此处不详细说明。新建完项目之后之后我们首先需要添加依赖

<dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.0.1</version>
</dependency>
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope>
</dependency>
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope>
</dependency>

然后在application.yml中添加相关的数据源,MyBatis的相关信息

#DataSource config 配置数据源信息
spring:datasource:driver-class-name: com.mysql.jdbc.Driverurl: jdbc:mysql://localhost:3306/mybatisdemo?useUnicode=true&characterEncoding=utf-8username: rootpassword: admin
#配置MyBatis的配置文件地址和MyBatis的映射文件地址  
mybatis:config-location: MyBatis-cfg.xmlmapper-locations: mybatis/*.xml

接着我们还需要在启动类MybatisSpringbootDemoApplication中添加dao接口的扫描信息

@SpringBootApplication
@MapperScan(value = "com.jay.mapper")
public class MybatisSpringbootDemoApplication {public static void main(String[] args) {SpringApplication.run(MybatisSpringbootDemoApplication.class, args);}
}

最后我们看看测试类

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class StudentMapperTest {// 自动注入Mapper对象@Autowiredprivate StudentMapper studentMapper;@Testpublic void selectStudentById() throws Exception {Student student = studentMapper.selectStudentById(1);System.out.println("---->查询结果为={}"+student);}
}

测试结果如下:

通过上面的这个demo,我们能看出来Spring Boot的配置相对Spring较少,这与SpringBoot的理念相关,约定大于配置,注解多于配置。


相关文章:【MyBatis】MyBatis缓存原理详解-CSDN博客
                  【MyBatis】MyBatis的一级缓存和二级缓存简介-CSDN博客

http://www.lryc.cn/news/582109.html

相关文章:

  • 5种方法将联系人从iPhone转移到OnePlus
  • C++--map和set的使用
  • 仿mudou库one thread oneloop式并发服务器
  • 达梦数据库的信息查询
  • Redisson 分布式锁原理解析
  • Navicat Premium可视化工具使用查询控制台优化SQL语句
  • 商品中心—库存分桶高并发的优化文档
  • 力扣 3258 统计满足 K 约束的子字符串数量 I 题解
  • Java工具类,对象List提取某个属性为List,对象List转为对象Map其中某个属性作为Key值
  • RAG实战指南 Day 8:PDF、Word和HTML文档解析实战
  • UI自动化常见面试题
  • day08-Elasticsearch
  • 云计算领域“XaaS”是什么?
  • Python编译器(Pycharm Jupyter)
  • 第4.2节 Android App生成追溯关系
  • 【Mac 从 0 到 1 保姆级配置教程 19】- 英语学习篇-我的英语工作流分享(AI 辅助学习)
  • JavaWeb笔记07
  • 比亚迪6月销量38.25万辆,同比增长11.9%
  • window显示驱动开发—BGRA 扫描输出支持
  • 特伦斯T1节拍器,突出综合优势与用户体验
  • Python 包管理工具 uv
  • 【C语言进阶】数据是如何存储的?
  • Web后端开发-请求响应
  • 国产CAD皇冠CAD(CrownCAD)建模教程:哈雷摩托车发动机零件
  • [论文阅读] 人工智能 | 读懂Meta-Fair:让LLM摆脱偏见的自动化测试新方法
  • 【mini-spring】【更新中】第一章 IOC与Bean源码及思路解析
  • IT 与动环一体化运维的技术融合实践
  • MySQL Galera Cluster企业级部署
  • 力扣_链表(前后指针)_python版本
  • verilog中timescale指令的使用