前言

目前,关系型数据库已经成为 Java 应用的标配,由于 JDBC 操作数据库的复杂性,我们通常会选择一款持久层框架,而作为半自动化 ORM 框架的 MyBatis 则成了我们的首选,而 Spring 又成了 Java 事实上的标准,它让我们开发 Java 应用更快、更容易、更安全,因此我们通常会将 MyBatis 与 Spring 整合在一起使用。

MyBatis 脱离 Spring 环境已经可以使用了,那为什么又要将它与 Spring 整合在一起呢?这里我总结出两点。

  1. 配置简化,和 Spring 整合在一起后复杂的 xml 配置可以忽略或少量配置。
  2. 加入 Spring 事务管理,将 MyBatis 异常转换为 Spring 中的异常。

快速上手

MyBatis 与 Spring 的整合可以分为两块。一块是直接与 Spring Framework 整合,整合后我们就可以在 Spring 的事务管理下使用 MyBatis,此时还需要少量的配置。另一块是与 SpringBoot 整合,整合后几乎可以忽略所有的 MyBatis 配置,极个别配置在 application.properties 添加即可。

这里假定屏幕前的小伙伴对 MyBatis 具备一定的认识,如果你认为还不够熟悉 MyBatis,可以参考我前面的几篇文章先进行了解。

Spring Framework 整合

在不考虑原理的情况下,我们先看如何在 Spring 环境中使用 MyBatis。

为了将 MyBatis 应用到 Spring,MyBatis 官方提出了一个子项目 mybatis-spring,需要先将其引入到项目中。

<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis-spring</artifactId>
  <version>2.0.6</version>
</dependency>

SqlSessionFactory 配置及使用

SqlSession 是 Mybatis 执行 SQL 的入口,它又非线程安全,因此我们可以在 Spring 环境下配置一个线程安全的 SqlSessionFactory 作为 bean。按照非 Spring 环境下的方式创建 SqlSessionFactory 也并无不可,然而为了简化配置及加入 Spring 的事务管理,mybatis-spring 项目中提供了一个 SqlSessionFactoryBean,这是一个 Spring 的 FactoryBean,最终 Spring 容器创建的是一个从 SqlSessionFactoryBean 获取到的 SqlSessionFactory,因此我们配置 SqlSessionFactoryBean 作为 bean 即可。

@Configuration
public class MyBatisConfiguration {

    @Bean
    public DataSource dataSource() {
        DataSource dataSource = new PooledDataSource();
        return dataSource;
    }

    @Bean
    public SqlSessionFactoryBean sqlSessionFactory() throws IOException {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();

        // 设置数据源
        sqlSessionFactoryBean.setDataSource(dataSource());

        // 设置 mapper xml 文件路径
        Resource[] mapperResources = new PathMatchingResourcePatternResolver().getResources("classpath*:/mapper/*Mapper.xml");
        sqlSessionFactoryBean.setMapperLocations(mapperResources);

        // 设置配置
        org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
        // 字段映射时先将数据库表字段下划线转驼峰
        configuration.setMapUnderscoreToCamelCase(true);
        // 关闭缓存
        configuration.setLocalCacheScope(LocalCacheScope.STATEMENT);
        configuration.setCacheEnabled(false);
        sqlSessionFactoryBean.setConfiguration(configuration);
        
        return sqlSessionFactoryBean;
    }

}

SqlSessionFactoryBean 只有一个必要的 dataSource 属性需要设置,除此之外,我们还配置了 mapper xml 配置文件的路径,以及常用的关闭缓存及下划线转驼峰的字段映射方式,此时我们已经可以使用如下的方式使用 MyBatis。

@Service
public class UserService {

    @Autowired
    private SqlSessionFactory sqlSessionFactory;

    public User getById(Integer id) {
        return sqlSessionFactory.openSession().selectOne("com.zzuhkp.blog.mybatis.mapper.UserMapper.getById", id);
    }
}

SqlSessionTemplate 配置及使用

通过上面对 SqlSessionFactoryBean 的配置我们已经可以获取到 SqlSession 来执行 SQL,但是这个 SqlSession 不是线程安全的,因此 MyBatis 又提供了一个线程安全的 SqlSession,为 SqlSessionTempalte,将其配置为 Spring 的 bean 即可方便的使用。

@Configuration
public class MyBatisConfiguration {

    @Bean
    public DataSource dataSource() {
        DataSource dataSource = new PooledDataSource();
        return dataSource;
    }

    @Bean
    public SqlSessionFactoryBean sqlSessionFactory() throws IOException {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        ... 省略部分代码
        return sqlSessionFactoryBean;
    }

    @Bean
    public SqlSession sqlSession() throws Exception {
        SqlSessionTemplate sqlSessionTemplate = new SqlSessionTemplate(sqlSessionFactory().getObject());
        return sqlSessionTemplate;
    }

}

此时业务代码使用 MyBatis 方式如下。

@Service
public class UserService {

    @Autowired
    private SqlSession sqlSession;

    public User getById(Integer id) {
        return sqlSession.selectOne("com.zzuhkp.blog.mybatis.mapper.UserMapper.getById", id);
    }
}

Mapper 配置及使用

通过上面的配置虽然我们已经有了线程安全的 SqlSessionTemplate,但是通常情况我们更希望使用 Mapper 接口操作数据库,Spring 环境下就需要把 Mapper 接口注入为 bean 了,通过 SqlSession#getMapper 方法获取是一个不错的选择,但是 mybatis 还提供了不通过 SqlSession 获取 Mapper 的 MapperFactoryBean 类,这个类可以创建 Mapper 接口的实现作为 bean。

@Configuration
public class MyBatisConfiguration {

    @Bean
    public DataSource dataSource() {
        DataSource dataSource = new PooledDataSource();
        return dataSource;
    }

    @Bean
    public SqlSessionFactoryBean sqlSessionFactory() throws IOException {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
		...省略部分代码
        return sqlSessionFactoryBean;
    }

    @Bean
    public MapperFactoryBean<UserMapper> userMapper() throws Exception {
        MapperFactoryBean<UserMapper> factoryBean = new MapperFactoryBean<>();
        factoryBean.setMapperInterface(UserMapper.class);
        factoryBean.setSqlSessionFactory(sqlSessionFactory().getObject());
        return factoryBean;
    }

}

通过上面的代码,我们就可以按照如下的方式使用 UserMapper 替代 SqlSession 了。

@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    public User getById(Integer id) {
        return userMapper.getById(id);
    }
}

Mapper 扫描配置

通过上面的方式我们直接将 Mapper 配置为 bean,如果项目中有几十甚至几百个 Mapper,那配置的工作量将大大增加,为了简化配置,MyBatis 提出了一个 @MpperScan 注解,这个注解会为扫描到的 Mapper 接口创建代理类,然后注入到 Spring 容器中,将这个注解添加到 Spring 配置类上即可。

@MapperScan("com.zzuhkp.blog.mybatis.mapper")
@Configuration
public class MyBatisConfiguration {

    @Bean
    public DataSource dataSource() {
        DataSource dataSource = new PooledDataSource();
        return dataSource;
    }

    @Bean
    public SqlSessionFactoryBean sqlSessionFactory() throws IOException {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        ... 省略部分代码
        return sqlSessionFactoryBean;
    }
}

使用方式和上面注入 UserMapper 的使用方式相同,从这里也可以看出,MyBatis 在不断简化配置以及降低使用的难度。

SpringBoot 整合

引入 mybatis-spring 依赖后,再加上少量的配置已经可以在 Spring 中使用 MyBatis 了,但是不满足是向上的车轮,当前我们使用更多的是 SpringBoot,它大大简化了使用 Spring 做出的大量配置,为了在 SpringBoot 环境下进一步简化 MyBatis 的使用,MyBatis 官网又提出了一个 starter,坐标如下。

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.2.0</version>
</dependency>

是的,仅仅添加上面一个依赖即可,然后就可以直接注入 Mapper 执行 SQL 了。那它有何魔法呢?默认情况这个 starter 在背后会做如下的工作。

  1. 自动向 Spring 容器注册 SqlSessionFactory 作为 bean。
  1. 所需的部分配置项从 Spring 上下文中的环境变量获取,因此可以把配置添加到 application.perperties 文件中,参见 MybatisProperties 类。
  2. 所需的数据源为 Spring 上下文中的 DataSource bean。
  3. Spring 上下文中的部分 MyBatis 组件 bean 将添加到配置中,包括 Interceptor、DatabaseIdProvider、TypeHandler、LanguageDriver。
  4. 此外实现了 MyBatis ConfigurationCustomizer 接口的 bean 将被回调用于通过代码自定义配置。
  1. 自动向 Spring 容器注册 SqlSessionTemplate 作为 bean。
  2. 自动扫描添加了 @Mapper 注解的 Mapper 并向 Spring 注册 bean。

总结

MyBatis 为了整合 Spring 提出了两个项目:mybatis-spring 用于向 springframework 整合,此时需要手动配置 SqlSessionFactory 及 Mapper,mybatis-spring-boot-starter 用于向 springboot 整合,提供了从 spring 上下文收集配置自动创建 MyBatis bean 的能力。