一、前言

Mybatis、Spring 已经成为 Java 企业级 Web 开发不可或缺的一部分。因此,Mybatis 也提供 Jar 完成两者之间的整合。

二、入门

2.1 添加依赖

  1. <dependency>
  2. <groupId>org.mybatis</groupId>
  3. <artifactId>mybatis-spring</artifactId>
  4. <version>2.0.5</version>
  5. </dependency>

2.2 Spring 配置

要和 Spring 一起使用 MyBatis,需要在 Spring 应用上下文中定义至少两样东西:一个 SqlSessionFactory 和至少一个数据映射器类。
在 MyBatis-Spring 中,可使用 SqlSessionFactoryBean来创建 SqlSessionFactory。 要配置这个工厂 bean,只需要把下面代码放在 Spring 的 XML 配置文件中:

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

@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
  SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
  factoryBean.setDataSource(dataSource());
  return factoryBean.getObject();
}

2.3 Mapper 映射类

public interface UserMapper {
  @Select("SELECT * FROM users WHERE id = #{userId}")
  User getUser(@Param("userId") String userId);
}

通过 MapperFactoryBean 将接口加入到 Spring 中:

<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
  <property name="mapperInterface" value="org.mybatis.spring.sample.mapper.UserMapper" />
  <property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>

2.4 SqlSessionFactoryBean

在基础的 MyBatis 用法中,是通过 SqlSessionFactoryBuilder 来创建 SqlSessionFactory 的。 而在 MyBatis-Spring 中,则使用 SqlSessionFactoryBean 来创建。

属性

SqlSessionFactory 有一个唯一的必要属性:用于 JDBC 的 DataSource。这可以是任意的 DataSource 对象,它的配置方法和其它 Spring 数据库连接是一样的。
一个常用的属性是 configLocation,它用来指定 MyBatis 的 XML 配置文件路径。它在需要修改 MyBatis 的基础配置非常有用。通常,基础配置指的是 元素。
需要注意的是,这个配置文件并不需要是一个完整的 MyBatis 配置。确切地说,任何环境配置(),数据源()和 MyBatis 的事务管理器()都会被忽略。SqlSessionFactoryBean 会创建它自有的 MyBatis 环境配置(Environment),并按要求设置自定义环境的值。
mapperLocations 属性接受多个资源位置。这个属性可以用来指定 MyBatis 的映射器 XML 配置文件的位置。属性的值是一个 Ant 风格的字符串,可以指定加载一个目录中的所有文件,或者从一个目录开始递归搜索所有目录。比如:

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
  <property name="dataSource" ref="dataSource" />
  <property name="mapperLocations" value="classpath*:sample/config/mappers/**/*.xml" />
</bean>

2.5 事务

一个使用 MyBatis-Spring 的其中一个主要原因是它允许 MyBatis 参与到 Spring 的事务管理中。而不是给 MyBatis 创建一个新的专用事务管理器,MyBatis-Spring 借助了 Spring 中的 DataSourceTransactionManager 来实现事务管理。
一旦配置好了 Spring 的事务管理器,你就可以在 Spring 中按你平时的方式来配置事务。并且支持 @Transactional 注解和 AOP 风格的配置。在事务处理期间,一个单独的 SqlSession 对象将会被创建和使用。当事务完成时,这个 session 会以合适的方式提交或回滚。
事务配置好了以后,MyBatis-Spring 将会透明地管理事务。这样在你的 DAO 类中就不需要额外的代码了。

2.6 标准配置

要开启 Spring 的事务处理功能,在 Spring 的配置文件中创建一个 DataSourceTransactionManager 对象:

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  <constructor-arg ref="dataSource" />
</bean>

@Bean
public DataSourceTransactionManager transactionManager() {
  return new DataSourceTransactionManager(dataSource());
}

注意:为事务管理器指定的 DataSource 必须和用来创建 SqlSessionFactoryBean 的是同一个数据源,否则事务管理器就无法工作了。

交由容器管理的事务

如果你正使用一个 JEE 容器而且想让 Spring 参与到容器管理事务(Container managed transactions,CMT)的过程中,那么 Spring 应该被设置为使用 JtaTransactionManager 或由容器指定的一个子类作为事务管理器。最简单的方式是使用 Spring 的事务命名空间或使用 JtaTransactionManagerFactoryBean:

@Bean
public JtaTransactionManager transactionManager() {
  return new JtaTransactionManagerFactoryBean().getObject();
}

在这个配置中,MyBatis 将会和其它由容器管理事务配置的 Spring 事务资源一样。Spring 会自动使用任何一个存在的容器事务管理器,并注入一个 SqlSession。如果没有正在进行的事务,而基于事务配置需要一个新的事务的时候,Spring 会开启一个新的由容器管理的事务。
注意,如果你想使用由容器管理的事务,而不想使用 Spring 的事务管理,你就不能配置任何的 Spring 事务管理器。并必须配置 SqlSessionFactoryBean 以使用基本的 MyBatis 的 ManagedTransactionFactory:

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
  <property name="dataSource" ref="dataSource" />
  <property name="transactionFactory">
    <bean class="org.apache.ibatis.transaction.managed.ManagedTransactionFactory" />
  </property>  
</bean>

或使用注解

@Bean
public SqlSessionFactory sqlSessionFactory() {
  SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
  factoryBean.setDataSource(dataSource());
  factoryBean.setTransactionFactory(new ManagedTransactionFactory());
  return factoryBean.getObject();
}

2.7、SqlSession

SqlSessionTemplate 是 MyBatis-Spring 的核心。作为 SqlSession 的一个实现,这意味着可以使用它无缝代替你代码中已经在使用的 SqlSession。SqlSessionTemplate 是线程安全的,可以被多个 DAO 或映射器所共享使用。
当调用 SQL 方法时(包括由 getMapper() 方法返回的映射器中的方法),SqlSessionTemplate 将会保证使用的 SqlSession 与当前 Spring 的事务相关。此外,它管理 session 的生命周期,包含必要的关闭、提交或回滚操作。另外,它也负责将 MyBatis 的异常翻译成 Spring 中的 DataAccessExceptions。

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

因此,你可以通过依赖注入 SqlSession进而方便使用它们:

public class UserDaoImpl implements UserDao {

  private SqlSession sqlSession;

  public void setSqlSession(SqlSession sqlSession) {
    this.sqlSession = sqlSession;
  }

  public User getUser(String userId) {
    return sqlSession.selectOne("org.mybatis.spring.sample.mapper.UserMapper.getUser", userId);
  }
}

三、mybatis-spring

mybatis-spring 项目是 Mybatis 提供的用以整合 Mybatis 框架与 Spring 框架。使用它们需要如下配置条件:

  1. 将数据源以及 Mapper 路径作为参数传入 org.mybatis.spring.SqlSessionFactoryBean ,并交给 Spring 进行管理。实现 FactoryBean 接口,getObject() 方法返回 SqlSessionFactory 对象,该对象用来创建 SqlSession 会话对象。
  2. Mapper 映射接口以及 Mapper 文件。

只需配置以上两步,我们就可以轻松在 Spring 框架中使用 Mybatis。

3.1 集成

与 Spring 框架集成,个人认为需要集成以下几个方面:

  1. 单例对象交给 Spring 容器管理。比如 SqlSessionFactory。
    1. 由于 Spring 是可以管理对象的生命周期,特别是单例,因此,SqlSessionFactory 对象自然交由 Spring 框架管理,通过实现扩展接口 FactoryBean,调用 getObject() 方法就能得到单例 SqlSessionFactory 对象。
  2. 可以通过依赖注入 Mapper 接口代理对象。
    1. 单纯使用 Mybatis 框架查询,我们需要从 SqlSession 对象中获取 Mapper 代理对象,但是有 Spring 提供的控制反转,我们完全可以将每个 Mapper 代理对象变成一个个实例注入到 Spring 容器中,通过 @Autowired 注入。
  3. 事务交由 Spring 框架管理。

    1. Mybatis 只实现了简单的 JDBC 事务,但 Spring 有七种事务传播机制,拥有更强大的事务能力。

      3.2 SqlSessionFactoryBean

      SqlSessionFactoryBean 目的是生成 SqlSessionFactory 对象(即 DefaultSqlSessionFactory )。通过实现 Spring 提供的扩展接口 FactoryBean 以自定义初始化 SqlSessionFactory。代码摘录:
      SqlSessionFactoryBean.png
      可以简单理解为它就是根据 mybatis-config.xml 配置文件实例化 DefaultSqlSessionFactory ,也可以通过依赖注入到其他对象中。

      3.3 MapperFactoryBean

      以前直接使用 Mybatis 框架时,我们是通过 SqlSession 获取 Mapper 的代理对象,但是如果还在 Spring 框架中还是使用 SqlSession 来获取 Mapper 代理对象那太不优雅了。于是在 mybatis-spring 提供 MapperFactoryBean 用以获取 Mapper 接口的代理对象。
      Mybatis 框架通过 JDK 动态代理方式让我们可以不用写 Mapper 接口的实现类就查询数据库获取数据。使用接口的目的在于:
  4. 关联 Mapper XML 映射文件,这个才是真正的 SQL 语句。

  5. 提供接口规范给开发人员调用。
  6. 与 SQL 解耦。不必将 SQL 语句嵌套在代码里。

MapperFactoryBean.png
当调用 getObject() 方法时,就会根据 Mapper 接口的 Class 类型从 SqlSession 里面中获取 Mapper 代理对象,这与原来的 Mybatis 编程思路是一样的。getSqlSession() 方法是通过继承 SqlSessionDaoSupport 抽象类所获取的能力,这是 mybatis-spring 提供的便于获取 SqlSesstionTemplate 对象的方式。

那当我们通过依赖注入 Mapper 对象,Spring 是如何找到合适的代理对象的呢?

3.4 注入 Mapper 代理对象发生了什么?

我们可以在 Mybatis 与 Spring 集成的环境中写出如下代码
AuthorMapper_Code.png
一般在 Service 层注入 DAO 层的 Mapper,这样就可以查询数据库。那 Spring 是怎么依赖查找 AuthorMapper 代理对象的呢?
答案在于 Mybatis 初始化阶段,将一个个 Mapper 接口转换为 Spring 的 BeanDefinition 注册到 Spring BeanFactory 。核心类是 org.mybatis.spring.mapper.ClassPathMapperScanner
ClassPathMapperScanner.png
此时某个 Mapper 接口的 BeanDefinition 长这样子:
MapperFactoryBean_Detail.png
有了 BeanDefinition ,在 beanFactory.preInstantiateSingletons(); 阶段就会根据 BeanDefnition 实例化非懒加载对象并放入单例池中。此时,Mapper 接口代理对象生成,并由 Spring 框架管理。
MapperProxy.png
在 Spring 单例池中,每个Mapper 所对应的 MapperProxy 都是不同的对象。看到 MapperProxy 对象,如果对 Mybatis 熟悉的同学应该知道,他就是 Mapper 接口的代理对象,将我们的接口方法调用转为最终的 SQL 查询。
我们梳理一下流程:

  1. 首先,Mybatis 根据配置文件、注解等扫描接口定义、XML 配置文件、Mapper-XML 文件等,按正常流程解析并将配置信息封装为 org.apache.ibatis.session.Configuration 对象。同时需要实例化 org.apache.ibatis.session.defaults.DefaultSqlSessionFactory 对象并交给 Spring 管理。此外,mybatis-spring 提供了线程安全的 SqlSessionTemplate 用以管理 SqlSession ,当然,他只是管理 SqlSession,是 ‘大自然的搬运工’。因此,他需要 SqlSessionFactory 对象以创建 SqlSession。解析 Mapper 接口这一步既关键也巧妙,Mybatis 将类信息变成 BeanDefinition 注册到 Spring BeanFactory 中,其中 BeanDefinition#beanClass 这一属性变为 MapperFactoryBean ,它实现 Spring FactoryBean 接口,而 Mybatis 则是让他在 Spring实例化单例阶段过程中生成 MapperProxy 代理对象。
    1. DefaultSqlSessionFactory 单例,Spring 管理。
    2. SqlSessionTemplate 单例,Spring 管理。
    3. 解析 Mapper 接口,变成 BeanDefinition并注册。
  2. Spring 实例化单例。
    1. 判断当前是否为 FactoryBean,因为 BeanDefinition#beanClass 为 MapperFactoryBean,肯定是 FactoryBean。先从单例池中获取 &beanName 的 FactoryBean 对象。
    2. 单例池不存在,创建对象、注入相关属性(比如 SqlSessionTemplate )、放入 Spring singletonObjects 单例池中。此时,关系映射为 mapper name -> MapperFactoryBean。
    3. 从 FactoryBean 对象中获取真正的对象,即 MapperProxy。

上述简单描述了 Spring 初始化 Mybatis 过程。主要核心是单例池存放 Mapper->MapperFactoryBean 映射关系,当需要注入 Mapper 对象是,则由 MapperFactory#getObject() 方法产生代理对象 MapperProxy,而 MapperProxy则是 Mybatis 提供可以查询数据库的代理对象。