Mybatis-Spring源码解析 —— mybatis与Spring是如何整合的?

1.原生创建mapper的方式

String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession= sqlSessionFactory.openSession();
UserMapper userMapper= sqlSession.getMapper(UserMapper.class);
User user = mapper.selectById(1); 

前面是学习mybatis常看到的一种代码,但缺点也很明显: 每次请求都得创建SqlSession,并且Mapper的代理类是通过SqlSession获取,也就意味着每次请求都得创建一个新的Mapper代理类。在下面这种场景会遇到问题:

@Service
public class UserServiceImpl implements UserService {
	private UserMapper userMapper=MybatisUtils.getMapper(UserMapper.class);
	}

当我们第一次发起请求调用service的时候程序可以正常执行,但是第二次执行的时候程序会报错。
在这里插入图片描述

问题出现的原因是servlet是单例模式的,当第一次发起请求时,创建一个servlet实例,同时创建一个service实例,并且service也是单例,这个时候是通过当前线程的SqlSession的getMapper()获取代理对象mapper,mapper是和当前线程的SqlSession绑定的,第一次请求结束后我们会关闭SqlSession
在这里插入图片描述
第二次发起请求的时候,mapper还是成员的那个mapper,但是sqlSession已经关闭了,所以会报错

但是用这种方式

@Service
public class UserServiceImpl implements UserService {
	@Autowired
	private UserMapper userMapper;
	}

却没有问题,这就是Mybatis-Spring的作用。
创建mapper 和 SqlSession 并注入到 bean中 是我们本次解析的关键点。那么开始分析吧!

2.SqlSessionFactoryBean : 加载xml及build SqlSessionFactory对象

在Spring项目中应用了Mybatis都会有下面的2个bean配置,这2个配置就是实现xml加载、mapper和SqlSession注入的起始配置。

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

    <!-- DAO接口所在包名,Spring会自动查找其下的类 -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="xxx.dao"/>
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
    </bean>

从配置中我们可以看到 SqlSessionFactoryBean 配置了数据源、mapper的xml路径、mybatis-config的xml路径。因此,不难想象,SqlSessionFactoryBean 内部实现了xml配置文件的加载及SqlSessionFactory对象的创建。我们先来看下 SqlSessionFactoryBean继承关系图形:

在这里插入图片描述

@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
	void onApplicationEvent(E event);
	static <T> ApplicationListener<PayloadApplicationEvent<T>> forPayload(Consumer<T> consumer) {
		return event -> consumer.accept(event.getPayload());
	}
}


public interface InitializingBean {
	void afterPropertiesSet() throws Exception;
}


public interface FactoryBean<T> {
	String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";
	@Nullable
	T getObject() throws Exception;
	@Nullable
	Class<?> getObjectType();
	default boolean isSingleton() {
		return true;
	}

}

实现的三个接口的源码如上,先不管监听器,我们可以看到SqlSessionFactoryBean是通过afterPropertiesSet() 来创建 SqlSessionFactory 对象 和 getObject() 来获取 SqlSessionFactory 对象

先看看getObject()的实现

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

    return this.sqlSessionFactory;
  }

getObject()相对简单,我们都知道FactoryBean子类都是通过getObject()来获取到实际的Bean对象,这里也就是SqlSessionFactory。从源码中我们看到当 sqlSessionFactory为null会去调用 afterPropertiesSet(),所以 SqlSessionFactory 肯定是由 afterPropertiesSet() 来实现创建的。继续看afterPropertiesSet()实现:

  public void afterPropertiesSet() throws Exception {
    notNull(dataSource, "Property 'dataSource' is required");
    notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");

    this.sqlSessionFactory = buildSqlSessionFactory();
  }

afterPropertiesSet() 内部首先 验证了 dataSource 和 sqlSessionFactoryBuilder 部位null,最后调用 buildSqlSessionFactory()方法获取到 SqlSessionFactory 对象,并赋值到类字段属性 sqlSessionFactory 。 继续查看buildSqlSessionFactory()源码:

protected SqlSessionFactory buildSqlSessionFactory() throws IOException {

    // 省略了 SqlSessionFactoryBean 的属性(比如:ObjectFactory )赋值到 Configuration 对象中的操作
    //  1 Configuration : Mybatis的核心类之一,主要存放读取到的xml数据,包括mapper.xml 
    Configuration configuration;

    XMLConfigBuilder xmlConfigBuilder = null;
    if (this.configLocation != null) {
      //  2 创建  xmlConfigBuilder 对象 : 用于解析 mybatis-config.xml 数据
      xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
      configuration = xmlConfigBuilder.getConfiguration();
    } else {
      if (logger.isDebugEnabled()) {
        logger.debug("Property 'configLocation' not specified, using default MyBatis Configuration");
      }
      configuration = new Configuration();
      configuration.setVariables(this.configurationProperties);
    }

    if (xmlConfigBuilder != null) {
      try {
        //  3  XmlConfigBuilder 解析方法执行 
        xmlConfigBuilder.parse();
      } catch (Exception ex) {
        throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
      } finally {
        ErrorContext.instance().reset();
      }
    }
    
    if (!isEmpty(this.mapperLocations)) {
      for (Resource mapperLocation : this.mapperLocations) {
        if (mapperLocation == null) {
          continue;
        }
        try {
          //  4 创建  XMLMapperBuilder 对象 : 用于解析 mapper.xml 数据
          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();
        }
      }
    } 
    // 5 通过 SqlSessionFactoryBuilder bulid  SqlSessionFactory 对象
    return this.sqlSessionFactoryBuilder.build(configuration);
  }

整个 buildSqlSessionFactory() 源码主要有以下几个重要的点:

1、 XMLConfigBuilder ,通过调用其 parse() 方法来 解析 mybatis-config.xml 配置(如果 配置有 mapper.xml ,其会通过 XMLMapperBuilder 进行解析加载),并将解析的数据赋值到 Configuration(Mybatis的核心类之一,主要存放读取到的xml数据,包括mapper.xml,该类贯穿整个mybatis,足以见得其重要性)

2、 XMLMapperBuilder : 通过调用其 parse() 方法来 解析 mapper.xml 配置, 并将解析的数据赋值到 Configuration

3、 将存放有解析数据的 Configuration 作为 sqlSessionFactoryBuilder.build() 参数,创建 sqlSessionFactory 对象。

3.MapperScannerConfigurer

MapperScannerConfigurer 是 mybatis-spring 项目中为了实现方便加载Mapper接口,以及将 Mapper 偷梁换柱成 MapperFactoryBean。查看 MapperScannerConfigurer 源码,先看下其继承关系图:
在这里插入图片描述
常见的三个接口ApplicationContextAware、BeanNameAware和InitializingBean的作用相信大家都已经很熟悉了,暂且不表,我们来看看 MapperScannerConfigurer 是如何实现 BeanDefinitionRegistryPostProcessor 的 postProcessBeanDefinitionRegistry 方法:

public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
    if (this.processPropertyPlaceHolders) {
      processPropertyPlaceHolders();
    }

    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.registerFilters();
    scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }

我们可以发现整个方法内部其实就是通过 ClassPathMapperScanner 的 scan() 方法,查看 scan() 实现,发现其内部调用了关键方法 doScan(),那么我们来看下 doScan() 方法实现:

@Override
 public Set<BeanDefinitionHolder> doScan(String... basePackages) {
   // 1、调用父类 ClassPathBeanDefinitionScanner的 doScan方法 加载路径下所有的mapper接口生成对应的 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 {
     for (BeanDefinitionHolder holder : beanDefinitions) {
       GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition();

       // 2、 设置 被代理的 Bean(也就是Mapper) 的class信息
       definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName());
       // 3、 偷梁换柱成 MapperFactoryBean 
       definition.setBeanClass(MapperFactoryBean.class);

       definition.getPropertyValues().add("addToConfig", this.addToConfig);

       boolean explicitFactoryUsed = false;
       // 4、 设置 sqlSessionFactory 
       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;
       }
       
       // 5、 设置 sqlSessionTemplate
       if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
         definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
         explicitFactoryUsed = true;
       } else if (this.sqlSessionTemplate != null) {
         definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
         explicitFactoryUsed = true;
       }
       
       if (!explicitFactoryUsed) {
         definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
       }
     }
   }

   return beanDefinitions;
 }

整个方法分为3个部分:

1、 调用父类 ClassPathBeanDefinitionScanner的 doScan()方法 加载路径下所有的mapper接口生成对应的 BeanDefinition

2、 通过definition.setBeanClass(MapperFactoryBean.class) 偷梁换柱成 MapperFactoryBean

3、 通过 definition.getPropertyValues().add() 添加 MapperFactoryBean 所需的 字段或者方法参数信息 : sqlSessionFactory 、 mapperInterface等

至此 MapperScannerConfigurer 的使命已经完成, 至于 MapperFactoryBean 的创建就完全交给Spring来完成了。

4.MapperFactoryBean

正如前面我们所看到的一样,MapperFactoryBean 其实可以理解为 Mapper的代理工厂Bean,我们可以通过 MapperFactoryBean 的getObject()方法获取到 Mapper的代理对象。先来看下 MapperFactoryBean继承关系 :
在这里插入图片描述
我们熟悉的FactoryBean就无需多讲,重点关注其父类SqlSessionDaoSupport ,源码如下:

public abstract class SqlSessionDaoSupport extends DaoSupport {

  private SqlSession sqlSession;

  private boolean externalSqlSession;

  //  创建 SqlSession子类 SqlSessionTemplate 
  public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
    if (!this.externalSqlSession) {
      this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
    }
  }

  public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
    this.sqlSession = sqlSessionTemplate;
    this.externalSqlSession = true;
  }

  
  public SqlSession getSqlSession() {
    return this.sqlSession;
  }

  ....

}

我们发现我们获取到的SqlSession其实是其子类SqlSessionTemplate, 我们查看其构造方法源码:

public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
     PersistenceExceptionTranslator exceptionTranslator) {

   notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
   notNull(executorType, "Property 'executorType' is required");

   this.sqlSessionFactory = sqlSessionFactory;
   this.executorType = executorType;
   this.exceptionTranslator = exceptionTranslator;
   // 维护了一个 SqlSession的代理对象
   this.sqlSessionProxy = (SqlSession) newProxyInstance(
       SqlSessionFactory.class.getClassLoader(),
       new Class[] { SqlSession.class },
       new SqlSessionInterceptor());
 }
 

我们可以清楚的发现,其内部维护了一个 SqlSession的字段 sqlSessionProxy ,其赋值的是代理对象 SqlSessionInterceptor。 我们再来看下 SqlSessionInterceptor 的源码:

 private class SqlSessionInterceptor implements InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      // 通过getSqlSession() 获取一个 SqlSession
      SqlSession sqlSession = getSqlSession(
          SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType,
          SqlSessionTemplate.this.exceptionTranslator);
      try {
        Object result = method.invoke(sqlSession, args);
        if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
          // force commit even on non-dirty sessions because some databases require
          // a commit/rollback before calling close()
          sqlSession.commit(true);
        }
        return result;
      } catch (Throwable t) {
        Throwable unwrapped = unwrapThrowable(t);
        if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
          // release the connection to avoid a deadlock if the translator is no loaded. See issue #22
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
          sqlSession = null;
          Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
          if (translated != null) {
            unwrapped = translated;
          }
        }
        throw unwrapped;
      } finally {
        if (sqlSession != null) {
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        }
      }
    }
  }
  
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值