源码解读 | Mybatis和Spring是怎么整合的

如果你觉得内容对你有帮助的话,不如给个赞,鼓励一下更新:joy:

前言

注意:阅读本文需要有一定的Spring和SpringBoot基础

先上一个Mybatis-Spring官网链接,打开一个SSM整合的案例项目一起食用本文,效果更佳哦。

官网上说的很清楚,要和 Spring 一起使用 MyBatis,需要在 Spring 应用上下文中定义至少两样东西:一个SqlSessionFactory至少一个数据映射器类
在 MyBatis-Spring 中,使用 SqlSessionFactoryBean来创建 SqlSessionFactory。

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
  <property name="dataSource" ref="dataSource" />
</bean>

至于在SpringBoot中SqlSessionFactoryBean是怎么被创建并且被执行的,就不赘述了,感兴趣的可以去看 org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration#sqlSessionFactory 方法,简单来说是利用的SpringBoot的自动装配机制

SqlSessionFactoryBean是怎么创建SqlSessionFactory的?

SqlSessionFactory是MyBatis中的一个重要的对象,它是用来创建SqlSession对象的,而SqlSession用来操作数据库的。
我们通过SqlSessionFactoryBean的继承体系可以看出,它实现了FactoryBean和InitialzingBean,这两个类的作用我也不赘述了
image.png

我们直接来看重要的代码

public SqlSessionFactory getObject() throws Exception {
    if (this.sqlSessionFactory == null) {
      // afterPropertiesSet方法在Bean的生命周期中会被调用,这里为什么会手动调用一次我也不知道
      // 或许为了防止getObject被提前调用,也可能是兼容SpringBoot和Spring整合的区别
      afterPropertiesSet();
    }
    return this.sqlSessionFactory;
}

public void afterPropertiesSet() throws Exception {
    notNull(dataSource, "Property 'dataSource' is required");
    notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
    // 一直往下追就是下面的build方法
    this.sqlSessionFactory = buildSqlSessionFactory();
}
public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
}

org.mybatis.spring.SqlSessionFactoryBean#buildSqlSessionFactory
走到buildSqlSessionFactory方法,最后还是通过 SqlSessionFactoryBuilder#build 去构建的DefaultSqlSessionFactory

@MapperScan注解到底做了什么?

@MapperScan负责Spring和Mybatis整合时mapper的扫描和注册

@Import(MapperScannerRegistrar.class)

@Import注解也是spring Framework提供的将普通javaBean注册到容器中,该注解导入了MapperScannerRegistrar 类来实现功能,我们看这个类的继承结构

public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware

它实现了ImportBeanDefinitionRegistrar,Spring中手动注册bean的两种方式:

  1. 实现ImportBeanDefinitionRegistrar
  2. 实现BeanDefinitionRegistryPostProcessor

需要注意的是,如果某一个Bean实现了BeanDefinitionRegistryPostProcessor或者ImportBeanDefinitionRegistrar接口,那我们在这个类中使用@Autowired或者@Value注解,我们会发现失效了。原因是,spring容器执行接口的方法时,此时还没有去解析@Autowired或者@Value注解。如果我们要使用获取配置文件属性,可以通过原始方式,直接用IO读取配置文件,然后得到Properties对象,然后再获取配置值。

MapperScannerRegistrar是何方神圣?

这里会调用registerBeanDefinitions方法
ImportBeanDefinitionRegistrar在ConfigurationClassPostProcessor处理Configuration类期间被调用,用来生成该Configuration类所需要的BeanDefinition。而ConfigurationClassPostProcessor正实现了BeanDefinitionRegistryPostProcessor接口(所以支持mapper注册成bean,并注入到spring容器中)。

  @Override
  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    AnnotationAttributes mapperScanAttrs = AnnotationAttributes
        .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
    if (mapperScanAttrs != null) {
      registerBeanDefinitions(mapperScanAttrs, registry);
    }
  }

void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry) {

    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
	...
    /**
     * 这里会将basePackages下的mapper全部扫描成bd并实例化成bean
     * 注意这里注册的所有bean实际均为MapperFactoryBean[代理]
     */
    scanner.doScan(StringUtils.toStringArray(basePackages));
  }

我们接着看doScan实现的细节:

 @Override
  public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    // 扫描出所有mybatis的mapper,此时的bd里的class还是自己
    // 假设UserMapper,那么此时bd中的beanclass = UserMapper.class
    Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);

    if (beanDefinitions.isEmpty()) {
	  ...
    } else {
      // 对bd进行处理
      processBeanDefinitions(beanDefinitions);
    }

    return beanDefinitions;
  }

  private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    GenericBeanDefinition definition;
    for (BeanDefinitionHolder holder : beanDefinitions) {
      definition = (GenericBeanDefinition) holder.getBeanDefinition();
      String beanClassName = definition.getBeanClassName();
	  ...

      definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); 
      // bean的实际类是MapperFactoryBean
      definition.setBeanClass(this.mapperFactoryBean.getClass());
      ...
      if (!explicitFactoryUsed) {
        LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
        definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
      }
    }
  }

这里特别要注意最后一行setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);Spring默认情况下的自动装配级别为AUTOWIRE_NO,需要修改为AUTOWIRE_BY_TYPE按类型注入。这样Spring就会通过set方法,并且再根据bean的类型帮我们注入属性,比如后会说到的MapperFactoryBean中的sqlSessionFactory和sqlSessionTemplate

Spring中自动装配有四种

// 默认不注入,也是默认级别  = 0
public static final int AUTOWIRE_NO = AutowireCapableBeanFactory.AUTOWIRE_NO;
// 按name  = 1
public static final int AUTOWIRE_BY_NAME = AutowireCapableBeanFactory.AUTOWIRE_BY_NAME;
// 按type  = 2
public static final int AUTOWIRE_BY_TYPE = AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE;
// 按构造器  = 3
public static final int AUTOWIRE_CONSTRUCTOR = AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR;

// 还有一种已经不建议使用了,这里就不赘述自动装配的细节了

为什么mybatis不用@AutoWired呢?
主要是解耦吧,可以不依赖Spring进行编译,加了Spring的注解,那么就是强耦合了。

到了这一步,那么此时的mapper,我们可以认为都变成了bd加入到spring中了,那么下一步就是实例化了,那么我们就要来看看MapperFactoryBean了

MapperFactoryBean

因为MapperFactoryBean extends SqlSessionDaoSupport extends DaoSupportDaoSupport implements InitializingBean ,所以在mfb实例化之后会执行afterPropertiesSet

	@Override
	public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
		// Let abstract subclasses check their configuration.
		checkDaoConfig();

		// Let concrete implementations initialize themselves.
		try {b
			initDao();
		}
		catch (Exception ex) {
			throw new BeanInitializationException("Initialization of DAO failed", ex);
		}
	}

afterPropertiesSet其中checkDaoConfig()方法很重要,我们来看看,在每一个mfb实例化的时候,其实方法和sql是在实例化的时候就已经绑定并且放在Map<String, MappedStatement> mappedStatements当中,而不是在你调用的时候才去绑定。

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

    Configuration configuration = getSqlSession().getConfiguration();
    if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
      try {
        // 在这一步,最后会调用一个parse解析,将所有的方法和sql语句绑定
        // 放进一个map当中,所以实际上当你执行一个mapper的方法时,底层其实是去一个map当中get
        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();
      }
    }
  }

this.mapperInterface这个实际上就是指的当前类,比如UserMapper在实例化的时候,这个mfb里面的mapperInterface就是它自己,因为mfb实现了FactoryBean,所以当你使用注解注入mapper的时候,此时的mapper是由getObject()方法产生的

  @Override
  public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }

getSqlSession()在和Spring整合中,拿到的一直是SqlSessionTemplate

org.mybatis.spring.support.SqlSessionDaoSupport#getSqlSession
// 这个SqlSession是由Spring管理,它会自动注入
 public SqlSession getSqlSession() {
    return this.sqlSessionTemplate;
 }

最后通过SqlSession的getMapper去获得代理类

// 底层调用的是MapperRegistry的getMapper方法
 public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    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);
    }
  }

说到这里,我们大概知道了mybatis的dao接口是怎么变成代理类并且放入Spring容器中的过程了。

代理类执行逻辑

放入到Spring容器后,我们就可以注入代理对象进行数据库操作了。由于接口方法会被代理逻辑拦截,所以下面我们把目光聚焦在代理逻辑上面,看看代理逻辑会做哪些事情

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
        if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this, args);
        } else if (isDefaultMethod(method)) {
            return invokeDefaultMethod(proxy, method, args);
        }
    } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
    }
    // 从缓存中获取 MapperMethod 对象,若缓存未命中,则创建 MapperMethod 对象
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    // 这里的sqlSession在不同情况下不同,比如有defaultSqlSession 和 sqlSessionManger
    // 最后还是调用的sqlSession的方法,但是sqlSessionManger解决自动关闭问题和线程安全问题
    // 调用 execute 方法执行 SQL
    return mapperMethod.execute(sqlSession, args);
}

private MapperMethod cachedMapperMethod(Method method) {
    return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}


/**
  * 创建映射方法
  */
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
    // 创建 SqlCommand 对象,该对象包含一些和 SQL 相关的信息
    this.command = new SqlCommand(config, mapperInterface, method);
    // 创建 MethodSignature 对象,由类名可知,该对象包含了被拦截方法的一些信息
    this.method = new MethodSignature(config, mapperInterface, method);
}
 
/**
  * 该对象包含一些和 SQL 相关的信息
  * 通过它可以找到MappedStatement
  * 我们可以看到熟悉的 Invalid bound statement (not found) 异常
  */
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
    final String methodName = method.getName();
    final Class<?> declaringClass = method.getDeclaringClass();
    // 解析 MappedStatement
    MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
                                                configuration);
    // 检测当前方法是否有对应的 MappedStatement
    if (ms == null) {
        if(method.getAnnotation(Flush.class) != null){
            name = null;
            type = SqlCommandType.FLUSH;
        } else {
            // 熟悉的异常
            throw new BindingException("Invalid bound statement (not found): "
                                       + mapperInterface.getName() + "." + methodName);
        }
    } else {
        name = ms.getId();
        type = ms.getSqlCommandType();
        if (type == SqlCommandType.UNKNOWN) {
            throw new BindingException("Unknown execution method for: " + name);
        }
    }
}

前面已经分析了 MapperMethod 的初始化过程,现在 MapperMethod 创建好了。那么,接下来要做的事情是调用 MapperMethod 的 execute 方法,执行 SQL,后面的就不分析了,看到这,你应该知道Mybatis是怎么实现的和Spring的整合流程了。

这篇文章到这就结束啦,喜欢的话就给个赞 + 收藏 + 关注吧!:nerd_face: 有什么想看的欢迎留言!!!

1 个赞