MyBatis集成到Spring

2019-08-19 00:00:00 spring mybatis 集成

使用 MyBatis 的 SqlSession

MyBatis 的 提供了执行 SQL 语句、提交或回滚事务和获取映射器实例的方法。SqlSession 由工厂类 SqlSessionFactory 来创建,SqlSessionFactory 又是构造器类 SqlSessionFactoryBuilder 创建的。

InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();

使用 mybatis-spring 的 SqlSession

使用 mybatis-spring 集成 Spring 时 ,SqlSessionFactory 使用了 Spring 的 FactoryBean 的实现类 SqlSessionFactoryBean 间接地调用 SqlSessionFactoryBuilder 来创建。 SqlSession 由 它的线程安全的实现类 SqlSessionTemplate 替代,它能基于 Spring 的事务机制自动提交、回滚、关闭 session。要在 Spring 容器中使用 SqlSessionTemplate,就要将其注入到容器中。

// 注入 SqlSessionTemplate
@Bean
public SqlSessionTemplate sqlSession() throws Exception {
    return new SqlSessionTemplate(sqlSessionFactory());
}

public SqlSessionFactory sqlSessionFactory() throws Exception {
    SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
    // 指定数据源连接信息
    factoryBean.setDataSource(dataSource());
    // 指定 mapper 文件路径
    InputStream inputStream = Resources.getResourceAsStream("mapper/UserSpringMapper.xml");
    factoryBean.setMapperLocations(new InputStreamResource(inputStream));
    return factoryBean.getObject();
}

// 使用 Spring 事务机制
@Bean
PlatformTransactionManager getTransactionManager() {
    return new DataSourceTransactionManager(dataSource());
}

使用 mybatis-spring-boot-starter 自动注入

如果使用 Springboot,可以通过引入mybatis-spring-boot-starter,将 MyBatis 的组件自动注入到 Spring 容器中,这个 starter 会引入mybatis-spring-boot-autoconfigure(查看如何开发自己的 Springboot starter),这个包里面有一个重要的配置类MybatisAutoConfiguration,通过查看其源码可知,它还有两个静态内部类MapperScannerRegistrarNotFoundConfigurationAutoConfiguredMapperScannerRegistrar,其中,MybatisAutoConfigurationMapperScannerRegistrarNotFoundConfiguration都加了 Spring 的 @Configuration 注解,所以 Spring 启动时会将它们都加载到容器中,而AutoConfiguredMapperScannerRegistrar是通过MapperScannerRegistrarNotFoundConfiguration的注解 @Import 间接地注入容器的。

AutoConfiguredMapperScannerRegistrar实现了 ImportBeanDefinitionRegistrar,所以其方法 registerBeanDefinitions() 会在容器启动时执行,主要有如下两个作用:

  1. 从 BeanFactory 获取包扫描的路径
  2. 初始化和配置 MapperScannerConfigurer (指定注解类型为 @Mapper、指定包路径等),注册到 BeanFactory

MapperScannerConfigurer 实现了 BeanDefinitionRegistryPostProcessor,所以其方法 postProcessBeanDefinitionRegistry() 会在容器启动时执行,通过这个方法初始化 ClassPathBeanDefinitionScanner 的子类 ClassPathMapperScanner,调用 scan(String… basePackages),扫描包路径下 @Mapper 注解的所有接口,注册到 BeanFactory,接着进行后置处理:

  1. 将 BeanDefinition 的类型修改为 MapperFactoryBean
  2. 指定 MapperFactoryBean 的构造器参数为 @Mapper 接口类的全类名
  3. 设置 sqlSessionFactory、sqlSessionTemplate、按照类型自动装配等
  4. 利用反射创建 MapperFactoryBean 实例,调用其有参构造器,将 @Mapper 接口传入,缓存到 Class mapperInterface

如下图: MapperFactoryBean 的继承关系

《MyBatis集成到Spring》

初始化和配置解析

DaoSupport 实现了 InitializingBean.afterPropertiesSet(),通过这个方法,将 Mapper 缓存到 MapperRegistryMap<Class<?>, MapperProxyFactory<?>> knownMappers,key 为 Mapper 接口,value 为 Mapper 代理工厂类 MapperProxyFactory;最后,使用 MapperAnnotationBuilder.parse() 来解析 XML 配置文件或者方法注解,缓存到 ConfigurationMap<String, MappedStatement> mappedStatements,源码流程如下:

DaoSupport.afterPropertiesSet()
->MapperFactoryBean.checkDaoConfig()
->Configuration.addMapper(this.mapperInterface)
->MapperRegistry.addMapper(type)
->knownMappers.put(type, new MapperProxyFactory<>(type))
// 解析 SQL 配置
->MapperAnnotationBuilder.parse()
-->configuration.addMappedStatement(statement)

生成代理对象

MapperFactoryBean 实现了 FactoryBean.getObject(),从 knownMappers 缓存取出 Mapper 接口映射的 MapperProxyFactory,使用这个工厂类来创建 MapperProxy 代理类,从 MapperProxy<T> implements InvocationHandler 可知是使用了 JDK 的动态代理,源码流程如下:

MapperFactoryBean.getObject()
->SqlSessionTemplate.getMapper(mapperInterface)
->Configuration.getMapper(mapperInterface, this)
->MapperRegistry.getMapper(mapperInterface, sqlSession)
->MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
->mapperProxyFactory.newInstance(sqlSession)

public T newInstance(SqlSession sqlSession) {
  final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
  return newInstance(mapperProxy);
}
protected T newInstance(MapperProxy<T> mapperProxy) {
  return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

到这里,代理对象就生成了,在 Springboot 应用中就可以简单的通过 @Autowired 的注解方便的从容器中获取 Mapper 接口的代理对象(MapperProxy)了。

执行流程

假设存在 @Mapper 注解的类 UserDao。

@Mapper
public interface UserDao {
  @Select("select * from t_user where id = #{id}")
  Optional<UserEntity> findOne(String id);
}

通过 @Autowired 获取 Bean。由上面可知,实际获取到的是代理对象 MapperProxy。

@Autowired
UserDao userDao;

调用 UserDao 的方法实际上执行的是代理对象 MapperProxy 的 invoke() 方法。

// 调用 findOne
userDao.findOne(id);

// 实际执行的方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  final MapperMethod mapperMethod = cachedMapperMethod(method);
  return mapperMethod.execute(sqlSession, args);
}

invoke() 方法大致的源码执行流程如下:

MapperMethod.execute(sqlSession, args)
sqlSessionProxy.selectOne(statement, parameter)

需要注意 SqlSession在 SqlSessionTemplate 的有参构造器中初始化,并且它也是个代理类,被 SqlSessionInterceptor 代理

this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),
    new Class[] { SqlSession.class }, new SqlSessionInterceptor());

所以 selectOne 方法会被 SqlSessionInterceptor.invoke() 拦截,反射执行 SqlSession.selectOne() 方法,源码流程如下:

private class SqlSessionInterceptor implements InvocationHandler {
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    // 打开获取 DefaultSqlSession;
    SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
        SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
    try {
      // 反射执行 SqlSession 的方法 selectOne(String statement, Object parameter) 进行查询
      Object result = method.invoke(sqlSession, args);
      if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
        // 提交
        sqlSession.commit(true);
      }
      // 返回查询结果
      return result;
    } catch (Throwable t) {
      // 异常时释放连接
      closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
    } finally {
      if (sqlSession != null) {
        // 释放连接
        closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
      }
    }
  }
}

注解和配置文件

Springboot 应用同样可以选择使用注解,或者配置文件的方式使用 MyBatis,一般简单的增删改查直接使用注解的方式(比如 @Select、@SelectProvider)即可,可以减少很多配置文件;比较复杂的 SQL 可能还是使用配置文件的方式操作起来更加方便一些,具体还是得看实际情况来选择,需要注意的是,每个 DAO 可以同时存在注解和配置的方式,但是同一个方法不能同时存在注解和配置的方式。

如果是通过配置文件的方式,可以在 application.yml 配置文件指定 DAO 的配置文件所在位置:

# 使用基于配置文件的 MyBatis 时指定 Mapper 配置的路径
mybatis:
  mapper-locations: mapper/*Dao.xml
    原文作者:O'Neal
    原文地址: https://www.cnblogs.com/bigshark/p/11374857.html
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。

相关文章