Mybatisi和Spring整合源码分析()

  本篇文章为你整理了Mybatisi和Spring整合源码分析()的详细内容,包含有 Mybatisi和Spring整合源码分析,希望能帮助你了解 Mybatisi和Spring整合源码分析。

   PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"

   "http://mybatis.org/dtd/mybatis-3-mapper.dtd"

   mapper namespace="com.ybe.mapper.BookMapper"

   cache /cache

   select id="getBook" resultType="com.ybe.entity.Book"

   select * from book where id = #{id}

   /select

   /mapper

  

 

 

  5.添加 BookService 和 BookServiceImpl代码如下,

  

package com.ybe.service;

 

  import com.ybe.entity.Book;

  public interface BookSerivce {

   Book getBook(int id);

  package com.ybe.service.impl;

  import com.ybe.entity.Book;

  import com.ybe.mapper.BookMapper;

  import com.ybe.service.BookSerivce;

  import org.springframework.beans.factory.annotation.Autowired;

  import org.springframework.stereotype.Component;

  @Component

  public class BookServiceImpl implements BookSerivce {

   @Autowired

   BookMapper bookMapper;

   public Book getBook(int id) {

   return bookMapper.getBook(id);

  

 

  6.添加配置类,代码如下

  

package com.ybe.config;

 

  @Configuration

  @MapperScan(basePackages = {"com.ybe.mapper"})

  @ComponentScan(basePackages = {"com.ybe"})

  public class MyBatisConfig {

   @Bean

   public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) throws IOException {

   SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();

   factoryBean.setDataSource(dataSource);

   factoryBean.setConfigLocation(new ClassPathResource("mybatis.xml"));

   factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()

   .getResources("classpath:com/ybe/mapper/*.xml"));

   factoryBean.setTypeAliases(Book.class);

   return factoryBean;

   @Bean

   public DataSource dataSource(){

   DruidDataSource dataSource = new DruidDataSource();

   dataSource.setUsername("xxx");

   dataSource.setPassword("xxx");

   dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");

   dataSource.setUrl("jdbc:mysql://localhost:3306/aopTest?useUnicode=true characterEncoding=UTF-8 serverTimezone=GMT%2B8");

   return dataSource;

   @Bean

   public DataSourceTransactionManager transactionManager(DataSource dataSource) {

   return new DataSourceTransactionManager(dataSource);

  

 

  7.添加主类代码,代码如下

  

AnnotationConfigApplicationContext configApplicationContext = new AnnotationConfigApplicationContext(MyBatisConfig.class);

 

  BookSerivce bookSerivce = configApplicationContext.getBean(BookSerivce.class);

  Book book = bookSerivce.getBook(1);

  System.out.println(book.getId());

  

 

  二、Mybatis和Spring的整合

  ​ 因为Mybatis中使用的是Mapper.class 接口来找到数据库sql语句,并且是通过SqlSessionFactory的SqlSession来连接数据库和执行Sql语句的。所以Mybatis和Spring的整合,其实就是把Mybatis的SqlSessionFactory类和Mapper.Class接口注入到SpringIOC中。并且SqlSessionFactory类中的事务管理对象(SpringManagedTransactionFactory )会集成Spring的事务。

  ​ 整个整合的过程分为两部分,第一部分 Mapper接口注入;第二部分 SqlSessionFactoryBean 注入。

  2.1 Mapper 接口注入

  ​ 试想一下我们在写Mapper接口的时候并没有写实现类,只是写了Mapper.xml文件。那在注入到Spring容器中,具体的实现类是啥?我这里直接给答案,Mapper接口在Spring容器中对应的实现类是一个MapperFactoryBean的类,最终存在beanFactory.singletonObjects中。MapperFactoryBean实现了FactoryBean接口,在获取Mapper接口的时候,默认返回的是MapperFactoryBean的getObject()方法,其中具体返回xxxMapper接口的动态代理类(JDK代理方式)。动态代理类会进行缓存。

  ​ 通过配置@MapperScan(basePackages = {"com.ybe.mapper"}) 注解,向 BeanDefinitionRegistry 中添加类型为 MapperScannerConfigurer 的BeanDefinition对象并且初始化对象相关属性,在org.springframework.context.support.AbstractApplicationContext#invokeBeanFactoryPostProcessors 中会进行调用MapperScannerConfigurer 的postProcessBeanDefinitionRegistry 方法,该方法会扫描配置的包路径下的Mapper接口class文件,生成BeanClass为MapperFactoryBean的ScannedGenericBeanDefinition,注册到 BeanDefinitionRegistry 中。

  @MapperScan注解源码如下

  

@Retention(RetentionPolicy.RUNTIME)

 

  @Target(ElementType.TYPE)

  @Documented

  @Import(MapperScannerRegistrar.class)

  @Repeatable(MapperScans.class)

  

 

  在 org.springframework.context.support.AbstractApplicationContext#invokeBeanFactoryPostProcessors 方法中,会先执行 org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions的方法,其中会执行MapperScannerRegistrar类的registerBeanDefinitions方法,向 BeanDefinitionRegistry 注册了一个类型为 MapperScannerConfigurer的BeanDefinition对象。代码如下,

  

BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);

 

  registry.registerBeanDefinition(beanName, builder.getBeanDefinition());

  

 

  
MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor接口。MapperScannerConfigurer主要用来扫描具体的 Mapper接口class文件,生成BeanClass类型为MapperFactoryBean的ScannedGenericBeanDefinition对象后注入 BeanDefinitionRegistry 中。具体类图如下
 

  
在org.springframework.context.support.AbstractApplicationContext#invokeBeanFactoryPostProcessors中会接着执行,MapperScannerConfigurer的postProcessBeanDefinitionRegistry 方法。postProcessBeanDefinitionRegistry中大概逻辑为

  
scanner.scan(

   StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));

  

 

 

  生成ScannedGenericBeanDefinition,并以Mapper接口名称为BeanName注入到BeanDefinitionRegistry对象中。

  

BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);

 

  definitionHolder =

   AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);

  beanDefinitions.add(definitionHolder);

  registerBeanDefinition(definitionHolder, this.registry);

  

 

  然后再设置ScannedGenericBeanDefinition的BeanClass类型为MapperFactoryBean类。 关键代码代码如下

  

// 设置 definition 的 构造函数的参数值类型为 beanClassName,

 

  // 在创建 MapperFactoryBean 时,会根据beanClassName创建类,然后把类作为参数调用MapperFactoryBean带参数的构造方法

  definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59

  // 设置 definition 的 Bean 类型为 MapperFactoryBean.class

  definition.setBeanClass(this.mapperFactoryBeanClass);

  

 

  
每个Mapper接口文件对应的BeanDefinition为 ScannedGenericBeanDefinition,BeanDefinition的BeanClass实现类为 MapperFactoryBean 类。此时查看beanFactory的 beanDefinitionMap 中的值,如下图
 

  
MapperFactoryBean是一个泛型类,泛型用来表示不同接口类型。继承了SqlSessionDaoSupport类,该类中存储了Maybatis的SqlSession工厂类。MapperFactoryBean也是一个实现了FactoryBean的类,用来返回具体的类型以及根据类型来生成具体的Mapper接口代理类。

  
public T getObject() throws Exception {

   // 返回根据接口类型返回 SqlSession中的Mapper代理对象

   return getSqlSession().getMapper(this.mapperInterface);

   * {@inheritDoc}

  @Override

  public Class T getObjectType() {

   return this.mapperInterface;

  

 

 

  
在beanFactory.preInstantiateSingletons()方法中会把BeanDefinition生成具体的Bean对象,在创建 MapperFactoryBean 对象的时候会调用带参数的构造方法(上面有具体说明)。因为在配置类中我们注入了SqlSessionFactoryBean对象(具体解析过程在下面章节讲解),SqlSessionFactoryBean对象实现了FactoryBean接口,在Srping容器中会返回SqlSessionFactory类型对象。在给MapperFactoryBean属性赋值的时候会把SqlSessionFactoryBean的实际SqlSessionFactory对象赋值给sqlSessionTemplate属性。最终会在beanFactory.singletonObjects对象中添加以Mapper接口名称为key,以 MapperFactoryBean 类型为value的 记录。

  
在获取Mapper接口的Bean对象的时候,会调用getObject()方法,其中会调SqlSessionTemplate的getMapper(),代码如下

  


public T T getMapper(Class T type, SqlSession sqlSession) {

 

   // 根据Mapper接口类型获取已经注册的对象

   return mapperRegistry.getMapper(type, sqlSession);

  

 

  返回创建的动态代理对象,这里返回的动态代理对象之后不会更新beanFactory.singletonObjects的对象,并且会进行缓存处理。关键代码如下

  

@SuppressWarnings("unchecked")

 

  public T T getMapper(Class T type, SqlSession sqlSession) {

   // 根据 Mapper 接口类型获取已经注册的对象

   final MapperProxyFactory T mapperProxyFactory = (MapperProxyFactory T ) knownMappers.get(type);

   if (mapperProxyFactory == null) {

   throw new BindingException("Type " + type + " is not known to the MapperRegistry.");

   try {

   // 创建动态代理对象

   return mapperProxyFactory.newInstance(sqlSession);

   } catch (Exception e) {

   throw new BindingException("Error getting mapper instance. Cause: " + e, e);

  

 

  总结一下,beanFactory.singletonObjects关于Mapper接口存储的Bean对象是泛型的MapperFactoryBean对象。但是根据Mapper接口获取Bean对象会返回上面的动态代理对象,并且动态代理对象会缓存在beanFactory.factoryBeanObjectCache中,这是因为MapperFactoryBean实现的FactoryBean接口。

  2.1 SqlSessionFactoryBean 注入

  SqlSessionFactoryBean介绍

  通过@Bean方式可以将SqlSessionFactoryBean对象注入到Spring容器,SqlSessionFactoryBean对象在MapperFactoryBean对象中会用到。注入代码如下,

  

@Bean

 

  public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) throws IOException {

   // 创建 SqlSessionFactoryBean 的类

   SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();

   // 设置数据源

   factoryBean.setDataSource(dataSource);

   // 设置配置文件路径

   factoryBean.setConfigLocation(new ClassPathResource("mybatis.xml"));

   // 设置 mybaits.xml 文件

   factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()

   .getResources("classpath:com/ybe/mapper/*.xml"));

   // 设置别名

   factoryBean.setTypeAliases(Book.class);

   return factoryBean;

  

 

  SqlSessionFactoryBean的类继承关系图如下,
 

  SqlSessionFactoryBean实现了InitializingBean接口 ,会在初始化后会执行afterPropertiesSet方法,其中会调用buildSqlSessionFactory()方法进行SqlSessionFactory的创建。SqlSessionFactoryBean也实现了FactoryBean接口,在容器中如果获取SqlSessionFactoryBean对象会返回SqlSessionFactory对象,并且也会在beanFactory.factoryBeanObjectCache进行缓存。getObject源码如下,

  

@Override

 

  public SqlSessionFactory getObject() throws Exception {

   if (this.sqlSessionFactory == null) {

   afterPropertiesSet();

   return this.sqlSessionFactory;

  

 

  buildSqlSessionFactory()具体逻辑

  ​ 1. 根据 configLocation 创建 XMLConfigBuilder 以及 Configuration对象

  

xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);

 

  targetConfiguration = xmlConfigBuilder.getConfiguration();

  

 

  ​ 2. 读取SqlSessionFactoryBean的属性对象给 targetConfiguration 赋值

  

Optional.ofNullable(this.objectFactory).ifPresent(targetConfiguration::setObjectFactory);

 

  Optional.ofNullable(this.objectWrapperFactory).ifPresent(targetConfiguration::setObjectWrapperFactory);

  Optional.ofNullable(this.vfs).ifPresent(targetConfiguration::setVfsImpl);

  if (hasLength(this.typeAliasesPackage)) {

   scanClasses(this.typeAliasesPackage, this.typeAliasesSuperType).stream()

   .filter(clazz - !clazz.isAnonymousClass()).filter(clazz - !clazz.isInterface())

   .filter(clazz - !clazz.isMemberClass()).forEach(targetConfiguration.getTypeAliasRegistry()::registerAlias);

  ......

  

 

  ​ 3. 解析主配置文件

  

xmlConfigBuilder.parse();

 

  

 

  ​ 4. targetConfiguration设置环境变量,如果配置的transactionFactory 事务工厂类为 null,则创建 SpringManagedTransactionFactory 事务工厂类,该事务工厂会直接调用org.springframework.jdbc.datasource.DataSourceUtils去获取数据库连接对象,所以和Spring的事务进行了集成。代码如下,

  

// 设置环境变量,如果事务工程类为 null,则创建 SpringManagedTransactionFactory 事务工厂类

 

  targetConfiguration.setEnvironment(new Environment(this.environment,

  this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory,

  this.dataSource));

  

 

  ​ 5. 如果 mapperLocations 不为null ,则循环遍历 xxxMapper.xml 文件流,解析之后给 targetConfiguration 的相关对象赋值。代码如下,

  

for (Resource mapperLocation : this.mapperLocations) {

 

   if (mapperLocation == null) {

   continue;

   try {

   // 构建 XMLMapperBuilder 对象,进行mapper.xml 文件资源解析

   XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),

   targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());

   xmlMapperBuilder.parse();

   } catch (Exception e) {

   throw new NestedIOException("Failed to parse mapping resource: " + mapperLocation + "", e);

   } finally {

   ErrorContext.instance().reset();

   LOGGER.debug(() - "Parsed mapper file: " + mapperLocation + "");

  

 

  总结一下,整个过程比较简单,其主要就是Mybaits初始化的过程,更详细Mybatis初始化过程请参考上一篇文章。

  以上就是Mybatisi和Spring整合源码分析()的详细内容,想要了解更多 Mybatisi和Spring整合源码分析的内容,请持续关注盛行IT软件开发工作室。

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: