• Spring(八)- Spring整合MyBatis框架原理剖析


    一、Spring xml方式整合第三方框架

    xml整合第三方框架有两种整合方案:
    ⚫ 不需要自定义命名空间,不需要使用Spring的配置文件配置第三方框架本身内容,例如:MyBatis
    ⚫ 需要引入第三方框架命名空间,需要使用Spring的配置文件配置第三方框架本身内容,例如:Dubbo

    在这里插入图片描述

    1. Spring整合MyBatis

    (1)原始方式

    需要mybatis-config.xml 配置文件

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration
            PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
            "https://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
        <environments default="development">
            <environment id="development">
                <transactionManager type="JDBC"/>
                <dataSource type="POOLED">
                    <property name="driver" value="com.mysql.jdbc.Driver"/>
                    <property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
                    <property name="username" value="root"/>
                    <property name="password" value="root"/>
                </dataSource>
            </environment>
        </environments>
    
        <mappers>
            <package name="com.itheima.mapper"></package>
        </mappers>
    
    </configuration>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    编写Mapper和Mapper.xml

    public interface UserMapper {
        List<User> findAll();
    }
    
    • 1
    • 2
    • 3
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.itheima.mapper.UserMapper">
        <select id="findAll" resultType="com.itheima.pojo.User">
            select * from tb_user
        </select>
    </mapper>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    测试方法

    public class MyBatisTest {
        public static void main(String[] args) throws Exception {
            InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
            SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
            SqlSessionFactory sqlSessionFactory = builder.build(in);
            SqlSession sqlSession = sqlSessionFactory.openSession();
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            List<User> all = mapper.findAll();
            for (User user : all) {
                System.out.println(user);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    (2)引入mybatis-spring.jar方

    不再需要mybatis-config.xml文件
    MyBatis提供了mybatis-spring.jar专门用于两大框架的整合。
    Spring整合MyBatis的步骤如下:
    ⚫ 导入MyBatis整合Spring的相关坐标;
    ⚫ 编写Mapper和Mapper.xml;
    ⚫ 配置SqlSessionFactoryBean和MapperScannerConfigurer;
    ⚫ 编写测试代码

    ①导入MyBatis整合Spring的相关坐标

    <dependency>
       <groupId>org.mybatisgroupId>
        <artifactId>mybatisartifactId>
        <version>3.5.5version>
    dependency>
    <dependency>
        <groupId>org.mybatisgroupId>
        <artifactId>mybatis-springartifactId>
        <version>2.0.5version>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    ②编写Mapper和Mapper.xml

    public interface UserMapper {
        List<User> findAll();
    }
    
    • 1
    • 2
    • 3
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.itheima.mapper.UserMapper">
        <select id="findAll" resultType="com.itheima.pojo.User">
            select * from tb_user
        </select>
    </mapper>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    ③在applicationContext.xml中配置SqlSessionFactoryBean和MapperScannerConfigurer

     
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> 
    	<property name="driverClassName" value="com.mysql.jdbc.Driver">property>
    	<property name="url" value="jdbc:mysql://localhost:3306/mybatis">property> 
    	<property name="username" value="root">property> 
    	<property name="password" value="root">property>
    bean>
    
    <bean class="org.mybatis.spring.SqlSessionFactoryBean"> 
    	<property name="dataSource" ref="dataSource">property>
    bean>
    
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> 
    	<property name="basePackage" value="com.itheima.dao">property>
    bean>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    ④编写测试代码

    public static void main(String[] args) {
    	ClassPathxmlApplicationContext applicationContext = new ClassPathxmlApplicationContext("applicationContext.xml");
    	UserMapper userMapper = applicationContext.getBean(UserMapper.class);
    	List<User> all = userMapper.findAll();
    	System.out.println(all);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    2. Spring整合MyBatis的原理剖析

    整合包里提供了一个SqlSessionFactoryBean和一个扫描Mapper的配置对象(MapperScannerConfigurer ),SqlSessionFactoryBean一旦被实例化,就开始扫描Mapper并通过动态代理产生Mapper的实现类存储到Spring容器中。相关的有如下四个类:
    ⚫ SqlSessionFactoryBean:需要进行配置,用于提供SqlSessionFactory;
    ⚫ MapperScannerConfigurer:需要进行配置,用于扫描指定mapper注册BeanDefinition;
    ⚫ MapperFactoryBean:Mapper的FactoryBean,调用getObject方法创建指定的Mapper;
    ⚫ ClassPathMapperScanner:definition.setAutowireMode(2) 修改了自动注入状态,所以MapperFactoryBean中的setSqlSessionFactory会自动注入进去。

    SqlSessionFactoryBean源码:
    配置SqlSessionFactoryBean作用是向容器中提供SqlSessionFactory,SqlSessionFactoryBean实现了FactoryBean和InitializingBean两个接口,所以会自动执行getObject() 和afterPropertiesSet()方法

    public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
      // spring生命周期中,属性填充后,会执行InitializingBean的afterPropertiesSet方法
      // SqlSessionFactoryBean重写了afterPropertiesSet方法,用于创建SqlSessionFactory对象存入容器中
      @Override
      public void afterPropertiesSet() throws Exception {
        this.sqlSessionFactory = buildSqlSessionFactory();
      }
      // 其他对象要引用SqlSessionFactory对象时,可以通过工厂方法getObject()从容器中获取SqlSessionFactory对象
      @Override
      public SqlSessionFactory getObject() throws Exception {
        if (this.sqlSessionFactory == null) {
          afterPropertiesSet();
        }
        return this.sqlSessionFactory;
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    MapperScannerConfigurer源码:
    配置MapperScannerConfigurer作用是扫描Mapper,向容器中注册Mapper对应的MapperFactoryBean,MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor接口,会在postProcessBeanDefinitionRegistry方法中向容器中注册MapperFactoryBean

    public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
      @Override
      public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
        if (this.processPropertyPlaceHolders) {
          processPropertyPlaceHolders();
        }
        // 类加载路径下的Mapper扫描器
        ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
        。。。
        // ClassPathMapperScanner中没有scan方法,所以调的是父类ClassPathBeanDefinitionScanner的scan方法
        scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ",; \t\n"));
      }
      
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    ClassPathBeanDefinitionScanner 源码:

    public class ClassPathBeanDefinitionScanner {
    	// 步骤一:父类scan方法
    	public int scan(String... basePackages) {
    		int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
    		// 步骤二:这里会先执行子类的doScan方法
    		doScan(basePackages);
    		。。。
    		return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
    	}
    	// 步骤四:父类doScan方法
    	protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
    		Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
    		for (String basePackage : basePackages) {
    			Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
    			for (BeanDefinition candidate : candidates) {
    				。。。
    				if (checkCandidate(beanName, candidate)) {
    					BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
    					definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
    					beanDefinitions.add(definitionHolder);
    					// 步骤五:注册beanDefinition,definitionHolder其实就是将beanDefinition包装了一层
    					// 将扫描到的类注册到beanDefinitionMap中,此时beanClass是当前类全限定名
    					registerBeanDefinition(definitionHolder, this.registry);
    				}
    			}
    		}
    		return beanDefinitions;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    步骤五:definitionHolder其实就是将beanDefinition包装了一层
    在这里插入图片描述
    此时注册的是UserMapper接口的beanDefinition信息,然后将beanDefinition存入beanDefinitionMap中后续就可以通过spring创建对象了
    在这里插入图片描述

    ClassPathMapperScanner 源码:

    public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
    	// 步骤三:子类doScan方法
    	public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    			// 步骤四:从子类中去调用父类doScan方法,获取BeanDefinitionHolder集合
    			Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
    			if (beanDefinitions.isEmpty()) {
    				。。。
    			} else {
    				// 步骤六:处理扫描后的所有beanDefinitions定义信息
    				this.processBeanDefinitions(beanDefinitions);
    			}
    	}
    	// 步骤六:处理扫描后的所有Mapper接口的beanDefinitions定义信息
    	private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    		 。。。
    		// 设置Mapper的beanClass是org.mybatis.spring.mapper.MapperFactoryBean
    		definition.setBeanClass(this.mapperFactoryBeanClass);
    		// 步骤七:
    		// autowireMode取值:1是根据名称自动装配,2是根据类型自动装配
    		definition.setAutowireMode(2); 
    		。。。
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    步骤六:处理扫描后的所有Mapper接口的beanDefinitions定义信息
    此时的beanClass=“com.itheima.mapper.UserMapper”,由于接口不能通过反射创建对象,所以需要通definition.setBeanClass(this.mapperFactoryBeanClass);将原有的"com.itheima.mapper.UserMapper"覆盖,设置为this.mapperFactoryBeanClass(mapperFactoryBeanClass实现了FactoryBean接口,所以会重写getObject方法)
    @Override
    public T getObject() throws Exception {
    // 步骤八:底层调用的是mybatis源码通过sqlSession去创建Mapper对象
    return getSqlSession().getMapper(this.mapperInterface);
    }
    // 步骤八:mybatis源码通过sqlSession去创建Mapper对象
    public < T > T getMapper(Class< T> type, SqlSession sqlSession) {
    MapperProxyFactory< T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
    return mapperProxyFactory.newInstance(sqlSession);
    }

    在这里插入图片描述

    步骤七:覆盖完beanClass后,此时被扫描的Mapperbean类型就是MapperFactoryBean类型,所以接下来上面的步骤八会去调用getObject方法创建Mapper对象,由于创建Mapper对象会用到sqlSession,所以需要先注入sqlSession对象,因此这里definition.setAutowireMode(2);是为了通过类型进行注入sqlSession对象
    在这里插入图片描述

  • 相关阅读:
    鲜花在线销售平台的设计与实现/鲜花商城/网上花店管理系统
    aws的alb,多个域名绑定多个网站实践
    Jmeter性能测试
    Linux —— 文件操作
    C++11智能指针
    [网鼎杯 2020 白虎组]PicDown
    Android 应用开发-实现将公共存储空间内的文件复制到应用的私用存储空间中
    C语言矩阵求逆
    薛定谔的猫重出江湖?法国初创公司Alice&Bob研发猫态量子比特
    Oracle OCM考试(史上最详细的介绍,需要19c OCP的证书)
  • 原文地址:https://blog.csdn.net/qq_36602071/article/details/127837575