美文网首页
MyBatis集成到Spring

MyBatis集成到Spring

作者: O_Neal | 来源:发表于2019-08-19 01:34 被阅读0次

    使用 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<T> mapperInterface

    如下图: MapperFactoryBean 的继承关系

    MapperFactoryBean.png

    初始化和配置解析

    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
    

    相关文章

      网友评论

          本文标题:MyBatis集成到Spring

          本文链接:https://www.haomeiwen.com/subject/gldjsctx.html