Spring整合MyBatis原理重要流程梳理

简介

最近重构项目,遇到一些关于MyBatis的奇奇怪怪的问题,还久没有看MyBatis源码都有点生疏了,这里重新梳理一下。

首先,先看一个简要的流程图:

MyBatis重要流程

在Spring中我们基本配置SqlSessionFactoryBean的DataSource,xml文件位置,配置文件位置就可以使用MyBatis了。

其中的细节,请看后面的内容。

SqlSessionFactoryBean

故事还得从SqlSessionFactoryBean说起,看名字,咱就知道是一个FactoryBean,就是一个生成工厂的类,生产什么的的工厂呢?

生成SqlSessionFactory的工厂。

其实在结合Spring使用MyBatis的时候,我们主要就是通过配置SqlSessionFactoryBean来获取SqlSessionFactory。

@Bean(name = "sqlSessionFactory")
public SqlSessionFactory sqlSessionFactory(@Qualifier("dataSource") DataSource dataSource) throws Exception {
    
    
    SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
    sessionFactory.setDataSource(dataSource);
    sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mybatis/mapping/*.xml"));
    sessionFactory.setConfigLocation(new DefaultResourceLoader().getResource("classpath:mybatis/mybatis-config.xml"));
    return sessionFactory.getObject();
}

当然处理设置Datasource、MyBatis自身配置文件的位置、包含SQL的xml文件位置,我们还可以通过SqlSessionFactoryBean配置一些其他东西:

  1. 类型的别名
  2. 拦截器(Interceptor)
  3. 类型处理器(TypeHandler)
  4. ……

如果对这些组件不太了解,可以看看下面这篇文章:

MyBatis配置与重要组件梳理

我们知道有那些组件之后,还需要了解Spring到底如何使用它们,所以我们还需要理解一个重要的组件Configuration

Configuration

一般我们不需要修改Configuration,SqlSessionFactoryBean会去读取Mybatis的配置文件解析,如果没有会自动创建一个。这些逻辑都在SqlSessionFactoryBean#buildSqlSessionFactory方法中。

其实在SqlSessionFactoryBean注册的类型别名、类型处理器也注册在了Configuration中,不过这里不是我们关注的重点,我们关注的重点是Configuration在实例化的时候,创建了一个重要的类MapperRegistry

MapperRegistry

看名字我们就能猜到MapperRegistry多半是用来存放Mapper的地方,实际上MapperRegistry维护着一个Map:

private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();

map的key是我们Mapper的class,value是MapperProxyFactory

MapperProxyFactory这玩意儿啊,看名字我们知道是一个生产MapperProxy的工厂

MapperProxy这个类看名字我们就可以猜是我们Mapper的代理类,实际上也正是如此,MapperProxy实现了InvocationHandler,Java标准代理模式,如果对Java的动态代理不太了解,可以看一下:Java动态代理细探Cglib、Javassist、JDK动态代理

MappeProxy将Mapper的每一个方法封装为MapperMethod

为什么我们调用一个空的Mapper接口,就获取到数据了?具体的怎么处理参数、怎么执行SQL、怎么处理返回值就可以在MapperMethod找。

详细的介绍,可以参考下面两篇文章:

MyBatis源码之:MapperProxyFactory与MapperProxy
MyBatis源码之:MapperMethod

MapperScan

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
public @interface MapperScan {
    
    
}

MapperScan注解有一个@Import(MapperScannerRegistrar.class)

MapperScannerRegistrar实现了ImportBeanDefinitionRegistrar,ImportBeanDefinitionRegistrar这个接口通过Import就会调用其接口方法。

MapperScannerRegistrar还实现了ResourceLoaderAware,就是为了获取ResourceLoader,看名字就知道是为了获取资源,例如,ClassPathXmlApplicationContext就实现了ResourceLoader。

接下来我们看一下MapperScannerRegistrar这个类

MapperScannerRegistrar

MapperScannerRegistrar实现了ImportBeanDefinitionRegistrar,通过@Import导入,会调用registerBeanDefinitions方法,所以直接看MapperScannerRegistrar#registerBeanDefinitions的方法就可以了。

不同版本的实现不一样,高版本的多封装了一个MapperScannerConfigurer类,不过实现都是使用ClassPathMapperScanner去扫描包。

注意:扫描包并不是直接创建了代理,而是创建了一个MapperFactoryBean,具体实现看ClassPathMapperScanner#processBeanDefinitions方法

关键代码:

definition.setBeanClass(this.mapperFactoryBeanClass);

MapperFactoryBean

MapperFactoryBean一看就知道是生产MapperFactory的类,是通过getObject获取MapperFactory。

MapperFactoryBean#checkDaoConfig方法中,把Mapper注册到了Configuration中,实际上通过前面的介绍,我们知道是注册到了MapperRegistry中。

@Override
protected void checkDaoConfig() {
    
    
super.checkDaoConfig();

notNull(this.mapperInterface, "Property 'mapperInterface' is required");

Configuration configuration = getSqlSession().getConfiguration();
if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
    
    
    try {
    
    
    configuration.addMapper(this.mapperInterface);
    } catch (Exception e) {
    
    
    logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
    throw new IllegalArgumentException(e);
    } finally {
    
    
    ErrorContext.instance().reset();
    }
}
}

但是checkDaoConfig是怎么调用的呢?

MapperFactoryBean继承了SqlSessionDaoSupport,SqlSessionDaoSupport继承了DaoSupport,DaoSupport实现了InitializingBean:

InitializingBean这玩意儿咱应该比较熟悉,Spring在执行完setter方法之后,就会调用它的afterPropertiesSet方法。

public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
    
    
this.checkDaoConfig();

try {
    
    
    this.initDao();
} catch (Exception var2) {
    
    
    throw new BeanInitializationException("Initialization of DAO failed", var2);
}
}

如何调用

我们使用Mapper时候,Spring实际注入的类是MapperProxy,MapperProxy最终会通过MapperMethod来执行对应的方法。

最后

流程中有一个可能常遇到的问题因为在MapperMethod中没有展开细说,如经常遇到:

Invalid bound statement (not found): 
Unknown execution method for: 

类的错误,定位到MapperMethod的SqlCommand类,debug。

猜你喜欢

转载自blog.csdn.net/trayvontang/article/details/109103042
今日推荐