Mybatis源码分析(十五)Spring-Mybatis整理

目录

  • 一 Spring与Mybatis整合
  • 二 Spring中的一些概念
    • 2.1 BeanDefinition
    • 2.2 BeanDefinitionRegistry
    • 2.3 BeanFactory
    • 2.4 BeanFactoryPostProcessor
    • 2.5 ImportBeanDefinitionRegistrar
    • 2.6 BeanPostProcessor
    • 2.7 ClassPathBeanDefinitionScanner
    • 2.8 FactoryBean
    • 2.9 Spring容器启动过程
  • 三 SqlSessionFactoryBean
    • 3.2 InitializingBean接口
    • 3.2 FactoryBean

Mybatis源码分析(十五)Spring-Mybatis整理_第1张图片

文章 状态 时间 描述
(一)Mybatis 基本使用 已复习 2022-12-14 对Mybtais的基本使用,能够开发
(二)Mybatis-config.xml的初始化 已复习 2023-02-10 对我们编写的mybatis配置文件的解析
(三)SqlSessionFactory的初始化 已复习 2023-02-11 SqlSession会话工厂的初始化
(四)Mapper文件的解析 已复习 2023-02-12 主要对我们编写的Mapper.xml进行解析
(五)SqlSession的创建 已复习 2023-02-13 主要介绍构建DefaultSqlSessionFactory
(六)Mapper的接口代理 已复习 2023-02-14 如何通过动态代理来执行我们编写的方法
(七)MapperMethod的INSERT分析 已复习 2023-02-15 通过代理对象来执行Insert语句,返回结果
(八)MapperMethod的Select分析 已复习 2023-02-16 通过代理对象来执行Select语句,返回结果
(九)Mybatis的PreparedStatement 已复习 2023-02-17 预处理语句的常见,以及与数据库打交道
(十)Mybatis的结果隐射 已复习 2023-02-18 数据库结果与实体类对象的转换
(十一)Mybatis的一级缓存与二级缓存 已复习 2023-02-24 Mybatis中一级缓存与二级缓存
(十二)Mybatis的插件开发及原理分析 已复习 2023-02-25 Mybatis中的插件运行机制与开发
(十三)Mybatis中的四大组件梳理 计划中 Mybatis中的四大组件的梳理
(十四)Mybatis中的设计模式梳理 计划中 Mybatis中设计模式的整理
(十五)Spring-Mybatis整理 计划中 Spring与Mybatis整合
(十六)Mybatis疑惑总结 计划中 我遇到的疑惑与问题
  • 官网:mybatis – MyBatis 3 | 简介

一 Spring与Mybatis整合

详细参考:Spring整合MyBatis——超详细_spring mybatis整合

  • 将 MyBatis 与 Spring 进行整合,主要解决的问题就是SqlSessionFactory 对象交由 Spring来管理。
  • 该整合,只需要将 SqlSessionFactory 的对象生成SqlSessionFactoryBean 注册在 Spring 容器中,再将其注入给 Dao 的实现类即可完成整合。

依赖导入

<dependencies>
        <dependency>
            <groupId>junitgroupId>
            <artifactId>junitartifactId>
            <version>4.11version>
            <scope>testscope>
        dependency>

        
        <dependency>
            <groupId>junitgroupId>
            <artifactId>junitartifactId>
            <version>4.12version>
            <scope>testscope>
        dependency>

        
        <dependency>
            <groupId>org.mybatisgroupId>
            <artifactId>mybatisartifactId>
            <version>3.4.6version>
        dependency>

        
        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
            <version>8.0.22version>
        dependency>

        
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
            <version>1.18.12version>
            <scope>providedscope>
        dependency>




        
        <dependency>
            <groupId>org.springframeworkgroupId>
            <artifactId>spring-contextartifactId>
            <version>5.3.3version>
        dependency>

        
        <dependency>
            <groupId>org.springframeworkgroupId>
            <artifactId>spring-testartifactId>
            <version>5.3.3version>
            <scope>testscope>
        dependency>

        
        <dependency>
            <groupId>org.slf4jgroupId>
            <artifactId>slf4j-log4j12artifactId>
            <version>1.7.25version>
        dependency>


        
        <dependency>
            <groupId>com.alibabagroupId>
            <artifactId>druidartifactId>
            <version>1.1.10version>
        dependency>


        
        <dependency>
            <groupId>org.springframeworkgroupId>
            <artifactId>spring-ormartifactId>
            <version>5.3.3version>
        dependency>

        
        <dependency>
            <groupId>org.mybatisgroupId>
            <artifactId>mybatis-springartifactId>
            <version>1.3.2version>
        dependency>



    dependencies>

实体类

package com.shu.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
* @author SHU
*/
@Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class User {
        private Integer id;
        private String name;
        private String email;
        private Integer age;
        private Integer sex;
        private String schoolname;
    }

编写Mapper文件

package com.shu.mapper;

import com.shu.pojo.User;
import org.springframework.stereotype.Repository;

import java.util.List;

/**
 * @description:
 * @author: shu
 * @createDate: 2023/3/18 19:46
 * @version: 1.0
 */
@Repository
public interface UserMapper {
    /**
     * 查询所有用户
     * @return
     */
     List<User> getAllUser();
}

DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.shu.mapper.UserMapper">

  
  <select id="getAllUser" resultType="com.shu.pojo.User">
    select * from user
  select>
mapper>

编写配置文件


<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">




    
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver">property>
        <property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true">property>
        <property name="username" value="root">property>
        <property name="password" value="123456">property>
    bean>


    
    <bean id="sessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        
        <property name="dataSource" ref="dataSource"/>
        
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
        
        <property name="mapperLocations" value="classpath:mapper/*.xml"/>
    bean>


    
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        
        <property name="basePackage" value="com.shu.mapper"/>
    bean>


beans>

测试类

package com.test;

import com.shu.mapper.UserMapper;
import com.shu.pojo.User;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.util.List;

/**
* @description:
* @author: shu
* @createDate: 2023/3/18 20:49
* @version: 1.0
*/
@RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(locations = "classpath:application.xml")
    public class SpringMyBatisTest {
        @Autowired
        UserMapper mapper;

        @Test
        public void testFindUserList(){
            List<User> userList = mapper.getAllUser();
            for (User user : userList) {
                System.out.println(user);
            }
        }

    }

Mybatis源码分析(十五)Spring-Mybatis整理_第2张图片
上面我们把一个测试环境搭建完毕,方便我们自己更好的理解源码,根据我们上面的重点,我们可以看到一个关键类:SqlSessionFactoryBean,下面我们来看看

二 Spring中的一些概念

Spring中的概念可以帮助我们理解,但是这里不会详细讲解Sring的分析,后面在详细介绍

2.1 BeanDefinition

Spring容器在启动时,首先会对Bean的配置信息进行解析,把Bean的配置信息转换为BeanDefinition对象,BeanDefinition是一个接口,通过不同的实现类来描述不同方式配置的Bean信息。
BeanDefinition用于描述Spring Bean的配置信息,Spring配置Bean的方式通常有3种

  • XML配置文件。
  • Java注解,例如@Service、@Component等注解。
  • Java Config方式,Spring从3.0版本开始支持使用@Configuration注解

Mybatis源码分析(十五)Spring-Mybatis整理_第3张图片

2.2 BeanDefinitionRegistry

BeanDefinitionRegistry是BeanDefinition容器,所有的Bean配置解析后生成的BeanDefinition对象都会注册到BeanDefinitionRegistry对象中,Spring提供了扩展机制,允许用户在Spring框架启动时,动态地往BeanDefinitionRegistry容器中注册BeanDefinition对象。

2.3 BeanFactory

BeanFactory是Spring的Bean工厂,负责Bean的创建及属性注入,它同时是一个Bean容器,Spring框架启动后,会根据BeanDefinition对象创建Bean实例,所有的单例Bean都会注册到BeanFactory容器中。

2.4 BeanFactoryPostProcessor

BeanFactoryPostProcessor是Spring提供的扩展机制,用于在所有的Bean配置信息解析完成后修改Bean工厂信息。

2.5 ImportBeanDefinitionRegistrar

ImportBeanDefinitionRegistrar是一个接口,该接口的实现类作用于Spring解析Bean的配置阶段,当解析@Configuration注解时,可以通过ImportBeanDefinitionRegistrar接口的实现类向BeanDefinitionRegistry容器中添加额外的BeanDefinition对象。

2.6 BeanPostProcessor

Bean的后置处理器,在Bean初始化方法(init-method属性指定的方法或afterPropertiesSet()方法)调用前后,会执行BeanPostProcessor中定义的拦截逻辑。

2.7 ClassPathBeanDefinitionScanner

ClassPathBeanDefinitionScanner是BeanDefinition扫描器,能够对指定包下的Class进行扫描,将Class信息转换为BeanDefinition对象注册到BeanDefinitionRegistry容器中。

2.8 FactoryBean

FactoryBean是Spring中的工厂Bean,通常用于处理Spring中配置较为复杂或者由动态代理生成的Bean实例。
SqlSessionFactoryBean是一个FactoryBean,通过名称sqlSessionFactory从Spring容器中获取Bean时,获取到的实际上是SqlSessionFactoryBean对象的getObject()方法返回的对象。

2.9 Spring容器启动过程

  • 对所有Bean的配置信息进行解析,其中包括XML配置文件、Java注解以及Java Config方式配置的Bean,将Bean的配置信息转换为BeanDefinition对象,注册到BeanDefinitionRegistry容器中。
  • 从BeanDefinitionRegistry容器中获取实现了BeanFactoryPostProcessor接口的Bean定义,然后实例化Bean,调用所有BeanFactoryPostProcessor对象的postProcessBeanFactory()方法,在postProcessBeanFactory()方法中可以对Bean工厂的信息进行修改。
  • 根据BeanDefinitionRegistry容器中的BeanDefinition对象实例化所有的单例Bean,并对Bean的属性进行填充。
  • 执行所有实现了BeanPostProcessor接口的Bean的postProcessBeforeInitialization()方法,该方法中可以对原始的Bean进行包装。
  • 执行Bean的初始化方法,初始化方法包括配置Bean时通过init-method属性指定的方法,或者通过实现InitializingBean接口重写的afterPropertiesSet()方法。
  • 执行所有实现了BeanPostProcessor接口的Bean的 postProcessAfterInitialization()方法。

Mybatis源码分析(十五)Spring-Mybatis整理_第4张图片

三 SqlSessionFactoryBean

首先看看我们的配置文件,我们配置的SessionFactory的Bean,我们再来看看这个接口信息

    <!-- 配置SessionFactoryBean -->
    <bean id="sessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!-- 注入数据源 -->
        <property name="dataSource" ref="dataSource"/>
        <!-- 指定MyBatis配置文件的位置 -->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
        <!-- 指定MyBatisMapper文件的位置 -->
        <property name="mapperLocations" value="classpath:mapper/*.xml"/>
    </bean>

SqlSessionFactoryBean

// 解析mybatisConfig.xml文件和mapper.xml,设置数据源和所使用的事务管理机制,将这些封装到Configuration对象
// 使用Configuration对象作为构造参数,创建SqlSessionFactory对象,其中SqlSessionFactory为单例bean,最后将SqlSessionFactory单例对象注册到spring容器。
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {

  private static final Logger LOGGER = LoggerFactory.getLogger(SqlSessionFactoryBean.class);

  // mybatis配置mybatisConfig.xml的资源文件
  private Resource configLocation;

  //解析完mybatisConfig.xml后生成Configuration对象
  private Configuration configuration;

  // mapper.xml的资源文件
  private Resource[] mapperLocations;

  // 数据源
  private DataSource dataSource;

  // 事务管理,mybatis接入spring的一个重要原因也是可以直接使用spring提供的事务管理
  private TransactionFactory transactionFactory;

  private Properties configurationProperties;

  // mybatis的SqlSessionFactoryBuidler和SqlSessionFactory
  private SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();

  private SqlSessionFactory sqlSessionFactory;
  
  
  // 实现FactoryBean的getObject方法
  @Override
  public SqlSessionFactory getObject() throws Exception {
  
    //...

  }
  
  // 实现InitializingBean的
  @Override
  public void afterPropertiesSet() throws Exception {
  
    //...
    
  }
  // 为单例
  public boolean isSingleton() {
    return true;
  }
}

我们可以 看到SqlSessionFactory的接口设计如下:实现了spring提供的FactoryBean,InitializingBean和ApplicationListener这三个接口,在内部封装了mybatis的相关组件作为内部属性,如mybatisConfig.xml配置资源文件引用,mapper.xml配置资源文件引用,以及SqlSessionFactoryBuilder构造器和SqlSessionFactory引用。

3.2 InitializingBean接口

bean实现的接口,这些bean需要在BeanFactory设置其所有属性后做出反应:例如,执行自定义初始化,或者仅仅检查是否已设置了所有强制属性。

public interface InitializingBean {
	/**
	 * Invoked by the containing {@code BeanFactory} after it has set all bean properties
	 * and satisfied {@link BeanFactoryAware}, {@code ApplicationContextAware} etc.
	 * 

This method allows the bean instance to perform validation of its overall * configuration and final initialization when all bean properties have been set. * @throws Exception in the event of misconfiguration (such as failure to set an * essential property) or if initialization fails for any other reason */ void afterPropertiesSet() throws Exception; }

我们来看看SqlSessionFactoryBean组件的初始化代码,其实就是SqlSessionFactory的初始化构建,当然我们也可以自定义一个类,实现这个接口来完成一些组件类的初始化
SqlSessionFactoryBean

@Override
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");
    // 创建sqlSessionFactory
    this.sqlSessionFactory = buildSqlSessionFactory();
}
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
    // 配置类
   Configuration configuration;
    // 解析mybatis-Config.xml文件,
    // 将相关配置信息保存到configuration
   XMLConfigBuilder xmlConfigBuilder = null;
   if (this.configuration != null) {
     configuration = this.configuration;
     if (configuration.getVariables() == null) {
       configuration.setVariables(this.configurationProperties);
     } else if (this.configurationProperties != null) {
       configuration.getVariables().putAll(this.configurationProperties);
     }
    //资源文件不为空
   } else if (this.configLocation != null) {
     //根据configLocation创建xmlConfigBuilder,XMLConfigBuilder构造器中会创建Configuration对象
     xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
     //将XMLConfigBuilder构造器中创建的Configuration对象直接赋值给configuration属性
     configuration = xmlConfigBuilder.getConfiguration();
   } 
   
    //略....

   if (xmlConfigBuilder != null) {
     try {
       //解析mybatis-Config.xml文件,并将相关配置信息保存到configuration
       xmlConfigBuilder.parse();
       if (LOGGER.isDebugEnabled()) {
         LOGGER.debug("Parsed configuration file: '" + this.configLocation + "'");
       }
     } catch (Exception ex) {
       throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
     }
   }
    
   if (this.transactionFactory == null) {
     //事务默认采用SpringManagedTransaction,这一块非常重要,我将在后买你单独写一篇文章讲解Mybatis和Spring事务的关系
     this.transactionFactory = new SpringManagedTransactionFactory();
   }
    // 为sqlSessionFactory绑定事务管理器和数据源
    // 这样sqlSessionFactory在创建sqlSession的时候可以通过该事务管理器获取jdbc连接,从而执行SQL
   configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));
    // 解析mapper.xml
   if (!isEmpty(this.mapperLocations)) {
     for (Resource mapperLocation : this.mapperLocations) {
       if (mapperLocation == null) {
         continue;
       }
       try {
         // 解析mapper.xml文件,并注册到configuration对象的mapperRegistry
         XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
             configuration, mapperLocation.toString(), configuration.getSqlFragments());
         xmlMapperBuilder.parse();
       } catch (Exception e) {
         throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
       } finally {
         ErrorContext.instance().reset();
       }

       if (LOGGER.isDebugEnabled()) {
         LOGGER.debug("Parsed mapper file: '" + mapperLocation + "'");
       }
     }
   } else {
     if (LOGGER.isDebugEnabled()) {
       LOGGER.debug("Property 'mapperLocations' was not specified or no matching resources found");
     }
   }

    // 将Configuration对象实例作为参数,
    // 调用sqlSessionFactoryBuilder创建sqlSessionFactory对象实例
   return this.sqlSessionFactoryBuilder.build(configuration);
}
  • 第一步我们可以看到configuration的解析,其实就是mybatis-config.xml的解析

Mybatis源码分析(十五)Spring-Mybatis整理_第5张图片

  • 第二步为configuration设置属性,比如objectFactory,objectWrapperFactory,vfs,typeAliasesPackage等等信息
  • 第三步开始解析配置文件,其实就是xmlConfigBuilder对configuration 文件的解析,这里与Mybatis单个中,对configuration的解析是一样的
  • 第四步为configuration配置环境

Mybatis源码分析(十五)Spring-Mybatis整理_第6张图片

  • 第五步我们编写的mapperLocations所在的位置对我们编写的Mapper文件进行解析,遍历我们编写的配置文件,通过XMLMapperBuilder进行解析,这里可以参考前面的代码分析,到这返回一个DefaultSqlSessionFactory对象

Mybatis源码分析(十五)Spring-Mybatis整理_第7张图片

总结

buildSqlSessionFactory的核心逻辑:解析mybatis配置文件mybatisConfig.xml和mapper配置文件mapper.xml并封装到Configuration对象中,最后调用mybatis的sqlSessionFactoryBuilder来创建SqlSessionFactory对象。这一点相当于前面介绍的原生的mybatis的初始化过程。另外,当配置中未指定事务时,mybatis-spring默认采用SpringManagedTransaction,这一点非常重要,请大家先在心里做好准备。此时SqlSessionFactory已经创建好了,并且赋值到了SqlSessionFactoryBean的sqlSessionFactory属性中。

3.2 FactoryBean

由BeanFactory中使用的对象实现的接口,这些对象本身就是单个对象的工厂,如果一个bean实现了这个接口,那么它将被用作要公开的对象的工厂,而不是直接用作将自己公开的bean实例。
spring的IOC容器在启动,创建好bean对象实例后,会检查这个bean对象是否实现了FactoryBean接口,如果是,则调用该bean对象的getObject方法,在getObject方法中实现创建并返回实际需要的bean对象实例,然后将该实际需要的bean对象实例注册到spring容器;如果不是则直接将该bean对象实例注册到spring容器。
我们来看看getObject()方法

 @Override
  public SqlSessionFactory getObject() throws Exception {
    if (this.sqlSessionFactory == null) {
      afterPropertiesSet();
    }
    return this.sqlSessionFactory;
  }

总结

SqlSessionFactoryBean的getObject方法实现如下:由于spring在创建SqlSessionFactoryBean自身的bean对象时,已经调用了InitializingBean的afterPropertiesSet方法创建了sqlSessionFactory对象,故可以直接返回sqlSessionFactory对象给spring的IOC容器,从而完成sqlSessionFactory的bean对象的注册,之后可以直接在应用代码注入或者spring在创建其他bean对象时,依赖注入sqlSessionFactory对象。

你可能感兴趣的:(#,Mybatis源码分析,mybatis,spring,java,源码分析,学习)