Spring整合MyBatis原理之MapperScannerConfigurer(一)

前言

springmybatis 进行整合时,需要使用以下这个依赖包

<dependency>
	<groupId>org.mybatis</groupId>
	<artifactId>mybatis</artifactId>
	<version>3.5.0</version>
</dependency>
<dependency>
	<groupId>org.mybatis</groupId>
	<artifactId>mybatis-spring</artifactId>
	<version>2.0.2</version>
</dependency>

spring 容器启动的时候需要使用一个 xml 配置文件

ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext-spring.xml");

xml 文件中就涉及到整合的配置信息

<!-- dataSource -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
    <property name="driverClassName" value="${driver}"/>
    <property name="url" value="${url}"/>
    <property name="username" value="${username}"/>
    <property name="password" value="${password}"/>
</bean>

<!-- spring 和 mybatis 整合 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
	<!-- 指定 mybatis 要连接的数据源 -->
	<property name="dataSource" ref="dataSource"/>
	<!-- mybatis 配置文件的地址 -->
    <property name="configLocation" value="classpath:mybatis-config.xml"/>
	<!-- mybatis 的 mapper 文件的地址 -->
	<property name="mapperLocations" value="classpath:com/atguigu/mapper/*.xml"/>
</bean>

<!-- 配置 Mapper 接口扫描 -->
<bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
	<property name="basePackage" value="com.atguigu.mapper"/>
	<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>

dao 层接口

public interface ProductInfoMapper {
    int deleteByPrimaryKey(Integer productId);

    int insert(ProductInfo record);

    int insertSelective(ProductInfo record);

    ProductInfo selectByPrimaryKey(Integer productId);

    int updateByPrimaryKeySelective(ProductInfo record);

    int updateByPrimaryKey(ProductInfo record);
}

对应的 mapper.xml 文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.joonwhee.open.mapper.UserPOMapper" >
    <resultMap id="BaseResultMap" type="com.joonwhee.open.po.UserPO">
        <result column="id" property="id" jdbcType="INTEGER" />
        <result column="name" property="name" jdbcType="VARCHAR" />
    </resultMap>
 
    <select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap">
        select
        <include refid="Base_Column_List"/>
        from product_info
        where product_id = #{productId,jdbcType=INTEGER}
    </select>
</mapper>

spring 整合 mybatis 原理

MapperScannerConfigurer

MapperScannerConfigurer 这个类实现了接口 BeanDefinitionRegistryPostProcessor,会在 spring 构建 IOC 容器的早期被调用重写的 postProcessBeanDefinitionRegistry 方法

参考:spring IOC:invokeBeanFactoryPostProcessors 详解

@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
  	if (this.processPropertyPlaceHolders) {
    	processPropertyPlaceHolders();
  	}
 
  	// 1.新建一个ClassPathMapperScanner并填充相应属性
  	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)) {
    	// 2.设置mapper bean是否需要懒加载
    	scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
  	}
  	// 3.注册Filter,因为上面构造函数我们没有使用默认的Filter,
  	// 有两种Filter,includeFilters:要扫描的;excludeFilters:要排除的
  	scanner.registerFilters();
  	// 4.扫描basePackage,basePackage可通过",; \t\n"来填写多个,
  	// ClassPathMapperScanner重写了 doScan 方法
  	scanner.scan(
      	StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
	}
}

ClassPathMapperScanner 这个类是 spring-mybatis.jar中的的类,它 extendsClassPathBeanDefinitionScanner,重写了 doScan 方法

ClassPathMapperScanner 中的 doScan()

@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {

  	// 1.找出指定包名下符合bean定义的BeanDefinition并注册到容器中
  	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 {
    	// 2.对扫描到的beanDefinitions进行处理,主要4件事:
    	// 1)将bean的真正接口类添加到通用构造函数参数中
    	// 2)将beanClass直接设置为MapperFactoryBean.class,
    	//  	结合1,相当于要使用的构造函数是MapperFactoryBean(java.lang.Class<T>)
    	// 3)添加sqlSessionFactory属性,sqlSessionFactoryBeanName和
    	//  	sqlSessionFactory中,优先使用sqlSessionFactoryBeanName
    	// 4)添加sqlSessionTemplate属性,同样的,sqlSessionTemplateBeanName
    	//  	优先于sqlSessionTemplate,
    	processBeanDefinitions(beanDefinitions);
  	}
 
  	return beanDefinitions;
}

processBeanDefinitions()

private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {

	GenericBeanDefinition definition;
    for (BeanDefinitionHolder holder : beanDefinitions) {
    	definition = (GenericBeanDefinition) holder.getBeanDefinition();
      	String beanClassName = definition.getBeanClassName();
      	LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName
          + "' mapperInterface");
		
		// 设置 definition 的构造函数参数值为:映射接口的类名
      	definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); 
      	// 改变 definition 的beanClass为MapperFactoryBean.class
      	definition.setBeanClass(this.mapperFactoryBeanClass);
		// 添加属性addToConfig为true
      	definition.getPropertyValues().add("addToConfig", this.addToConfig);

		// 分别为 definition 添加 sqlSessionFactory 和 sqlSessionTemplate属性
      	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;
      	}

      	if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
        	if (explicitFactoryUsed) {
          		LOGGER.warn(
              		() -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
        }
        	definition.getPropertyValues().add("sqlSessionTemplate",
            	new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
        	explicitFactoryUsed = true;
      	} else if (this.sqlSessionTemplate != null) {
        	if (explicitFactoryUsed) {
          		LOGGER.warn(
              		() -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
        	}
        	definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
        	explicitFactoryUsed = true;
		}

		if (!explicitFactoryUsed) {
        	LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
        	definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
		}
		
		// 设置属性按类型注入
		definition.setLazyInit(lazyInitialization);
	}
}

MapperScannerConfigurer 小结

  1. 创建扫描器 ClassPathMapperScanner
  2. 使用 ClassPathMapperScanner 扫描注册 basePackage 包下的所有的 mapper 接口类,将 mapper 接口类封装成为 BeanDefinition 对象,注册到 Spring 容器中
  3. 同时会将 basePackage 包下的所有 bean 进行一些特殊处理:beanClass 设置为 MapperFactoryBean、bean 的真正接口类作为构造函数参数传入 MapperFactoryBean

在这里插入图片描述


版权声明:本文为weixin_38192427原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。