联系博主


你的名字:
Email:
建议:

MyBatis之Mapper运行机制及其与Spring整合的底层原理

编辑时间:2019-07-11      赞:2       踩:0

导言:

    本文主要从配置文件和底层源码的角度去解释MyBatis的Mapper的运行机制,同时也会对MyBatis与Spring整合的原理进行说明。在写作本文的时候,我是假定读者对Spring和MyBatis有一定的了解。(如果有空,我会写一些关于Spring和MyBatis基础知识的文章)

正文:

    在使用Spring整合MyBatis的时候我们只需要进行简单的配置被可以非常便利的地使用MyBatis。本文将从配置文件的角度去切入讲解Spring和MyBatis的交互流程。
    在使用SSM(Spring-SpringMVC-MyBatis)框架的时候我们常常会看见类似以下例子的代码片段:

ApplicationContext-MyBatis.xml

<!-- SqlSessionFactory mysql连接管理-->
	<!-- 让spring管理sqlsessionfactory 使用mybatis和spring整合包中的 -->
	<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
		<!-- 数据库连接池 -->
		<property name="dataSource" ref="dataSource" />
		<!-- 加载mybatis的全局配置文件 -->
		<property name="configLocation" value="classpath:mybatis/SqlMapConfig.xml" />
	</bean>
	<!-- Mapper映射文件的包扫描器 -->
    <!--basePackage 属性是让你为映射器接口文件设置基本的包路径。 你可以使用分号或逗号 作为分隔符设置多于一个的包路径。每个映射器将会在指定的包路径中递归地被搜索到。-->
	<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
		<property name="basePackage" value="com.kshop.mapper,com.kshop.search.mapper" />
	</bean>

TestMyBatisService.java

@Controller
public class TestMyBatisService{
	@Autowired
	private ItemMapper itemMapper;
	
	@RequestMapping("/test")
	@ResponseBody
	public String hello() {
		TbItem item = itemMapper.selectByPrimaryKey((long) 562379);
		return item.toString();
	}
}

    作为初学者,在编写上诉代码的时候总是会有一丝疑惑。为什么进行这么简单的配置,就可顺利的使用上MyBatis的Mapper?Spring IOC容器中的Mapper是从哪里来的?Mapper又是如何使用MyBatis的SqlSsession对数据库进行操作的?

    从配置文件中可以看出,为了使用Mapper,我往Spring的IOC容器注入了两个Bean。一个是SqlSessionFactoryBean一个是MapperScannerConfigurerMapperScannerConfigurer虽然比较陌生,但是SqlSessionFactoryBean对于使用MyBatis进行开发的开发者来说应该是很熟悉。使用Mybatis开发DAO时,通常有两个方法,即原始Dao开发方法和Mapper代理接口开发方法。在使用原始DAO开发方法时,开发者会通过MyBatis配置文件提供的配置信息构造SqlSessionFactory即会话工厂由会话工厂创建sqlSession即会话,操作数据库需要通过sqlSession进行。由此可知,配置了SqlSessionFactory后,Spring便可以通过该Bean获取sqlSession,从而使用MyBatis对数据库进行操作。

    上文已经解析了SqlSessionFactoryBean的用处,那么MapperScannerConfigurer又有什么用处呢?MapperScannerConfigurer是一个Mapper的包扫描器, 它将会查找basePackage属性所存储的类路径下的映射器并自动将它们创建成MapperFactoryBean。具体流程如下:MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor接口,该接口是Spring官方提供的接口,专门用于动态注册Bean,方法为postProcessBeanDefinitionRegistry(),代码如下:

public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
	//创建ClassPath扫描器,设置属性,然后调用扫描方法
	ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
	//设置scanner属性
    ···
	//如果配置了annotationClass,就将其添加到includeFilters
	scanner.registerFilters();
	scanner.scan(this.basePackage);
}
    MapperScannerConfigurer在该方法里面调用了ClassPathMapperScannerscan方法对包进行扫描。ClassPathMapperScanner继承自Spring中的类ClassPathBeanDefinitionScanner,所以scan方法会调用到父类的scan方法,而在父类的scan方法中又调用到子类的doScan方法。



//ClassPathBeanDefinitionScanner的scan,子类ClassPathMapperScanner并没有覆盖该方法
	public int scan(String... basePackages) {
		int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
		//子类ClassPathMapperScanner覆盖该方法,因此,会执行子类的doScan方法
		doScan(basePackages);

		// Register annotation config processors, if necessary.
		if (this.includeAnnotationConfig) {
			AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
		}

		return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
	}

//ClassPathBeanDefinitionScanner的doScan
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
	// 其他操作
	···
	//返回了basePackages里面复合特定条件的接口或类的BeanDefinition 
	return beanDefinitions;
}
//ClassPathMapperScanner的doScan
 @Override
 public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    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 {
      processBeanDefinitions(beanDefinitions);
    }

    return beanDefinitions;
  }

    经过上述操作,所有的Mapper接口,并将其注册为BeanDefinition对象。接下来调用processBeanDefinitions()要配置这些BeanDefinition对象(Bean的定义主要由BeanDefinition来描述的。作为Spring中用于包装Bean的数据结构,Spring解析Bean的配置文件后会生成将其配置信息存放在一个个BeanDefinition中),代码片段如下:

public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {

	private MapperFactoryBean<?> mapperFactoryBean = new MapperFactoryBean<Object>();
	
	private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    GenericBeanDefinition definition;
    for (BeanDefinitionHolder holder : beanDefinitions) {
      definition = (GenericBeanDefinition) holder.getBeanDefinition();
      String beanClassName = definition.getBeanClassName();
       ·······
      // the mapper interface is the original class of the bean
      // but, the actual class of the bean is MapperFactoryBean
      definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); 
        //设置BeanDefinition的class
      definition.setBeanClass(this.mapperFactoryBeanClass);
	//添加属性addToConfig
      definition.getPropertyValues().add("addToConfig", this.addToConfig);
		····
            //添加属性sqlSessionFactory,这个也会根据特定的判断条件改为添加sqlSessionTemplate
       definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
		······
    }
  }
}
    处理的过程很简单,就是往BeanDefinition对象中设置了一些属性。我们重点关注两个。
  •     设置beanClass

                设置BeanDefinition对象的BeanClass为MapperFactoryBean<?>。这意味着什么呢?以UserMapper为例,意味着当前的mapper接口在Spring容器中,beanName是userMapper,beanClass是MapperFactoryBean.class。那么在IOC初始化的时候,实例化的对象就是MapperFactoryBean对象

  •     设置sqlSessionFactory属性
                为BeanDefinition对象添加属性sqlSessionFactory,这就意味着,在为BeanDefinition对象设置PropertyValue的时候,会调用到setSqlSessionFactory()


    MapperFactoryBean实现了FactoryBean接口,俗称工厂Bean。那么,当我们通过@Autowired注入某一个Mapper(dao)接口的时候,返回的对象就是MapperFactoryBean这个工厂Bean中的getObject()方法返回的代理对象(调用链如下:MapperFactoryBean.getObject-->SqlSession.getMapper——>Configuration.getMapper——>MapperRegistry.getMapper——>MapperProxyFactory.newInstance)。该代理类实现了 xxxMapper 接口。 因为代理创建在运行时环境中(Runtime,译者注),那么指定的映射器必须是一个接口,而不是一个具体的实现类。简单来说,由于MapperFactoryBean实现了FactoryBean接口,开发使用@Autowired注入的Mapper是由MapperFactoryBean的getObject方法返回的一个代理bean。
    我们调用到Dao接口(Mapper)的方法时,则会调用到MapperProxy对象的invoke方法。MapperProxy对象的invoke方法底层就是调用SqlSession这些原始的Mybatis操作接口进行数据库操作。有兴趣的朋友可以去阅读MapperProxy中的invoke方法的源码。

    最后,为了加强理解,强烈建议阅读本文的读者自己搭建一个简单的Spring+MyBatis工程,然后写一些简单的代码进行单步调试。毕竟纸上得来终觉浅,绝知此事要躬行。


参考资料:

https://juejin.im/post/5c84b4395188257e342db6eb#comment

https://blog.csdn.net/jokemqc/article/details/79082571

https://www.cnblogs.com/yxfmp426756/p/8882852.html

https://juejin.im/post/5a62e99e6fb9a01c9950e667

https://zhuanlan.zhihu.com/p/30590254





转载请注明:
    本文转载自:www.kantblog.com/blog/WebDevelop/6

Kant©2016 All rights reserved 粤ICP备16014517号