一、前言
Mybatis、Spring 已经成为 Java 企业级 Web 开发不可或缺的一部分。因此,Mybatis 也提供 Jar 完成两者之间的整合。
二、入门
2.1 添加依赖
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.5</version>
</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 配置。确切地说,任何环境配置(
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 框架。使用它们需要如下配置条件:
- 将数据源以及 Mapper 路径作为参数传入
org.mybatis.spring.SqlSessionFactoryBean
,并交给 Spring 进行管理。实现FactoryBean
接口,getObject()
方法返回SqlSessionFactory
对象,该对象用来创建SqlSession
会话对象。 - Mapper 映射接口以及 Mapper 文件。
只需配置以上两步,我们就可以轻松在 Spring 框架中使用 Mybatis。
3.1 集成
与 Spring 框架集成,个人认为需要集成以下几个方面:
- 单例对象交给 Spring 容器管理。比如 SqlSessionFactory。
- 由于 Spring 是可以管理对象的生命周期,特别是单例,因此,SqlSessionFactory 对象自然交由 Spring 框架管理,通过实现扩展接口 FactoryBean,调用 getObject() 方法就能得到单例 SqlSessionFactory 对象。
- 可以通过依赖注入 Mapper 接口代理对象。
- 单纯使用 Mybatis 框架查询,我们需要从 SqlSession 对象中获取 Mapper 代理对象,但是有 Spring 提供的控制反转,我们完全可以将每个 Mapper 代理对象变成一个个实例注入到 Spring 容器中,通过
@Autowired
注入。
- 单纯使用 Mybatis 框架查询,我们需要从 SqlSession 对象中获取 Mapper 代理对象,但是有 Spring 提供的控制反转,我们完全可以将每个 Mapper 代理对象变成一个个实例注入到 Spring 容器中,通过
事务交由 Spring 框架管理。
- Mybatis 只实现了简单的 JDBC 事务,但 Spring 有七种事务传播机制,拥有更强大的事务能力。
3.2 SqlSessionFactoryBean
SqlSessionFactoryBean
目的是生成 SqlSessionFactory 对象(即DefaultSqlSessionFactory
)。通过实现 Spring 提供的扩展接口 FactoryBean 以自定义初始化 SqlSessionFactory。代码摘录:
可以简单理解为它就是根据 mybatis-config.xml 配置文件实例化DefaultSqlSessionFactory
,也可以通过依赖注入到其他对象中。3.3 MapperFactoryBean
以前直接使用 Mybatis 框架时,我们是通过 SqlSession 获取 Mapper 的代理对象,但是如果还在 Spring 框架中还是使用 SqlSession 来获取 Mapper 代理对象那太不优雅了。于是在mybatis-spring
提供MapperFactoryBean
用以获取 Mapper 接口的代理对象。
Mybatis 框架通过 JDK 动态代理方式让我们可以不用写 Mapper 接口的实现类就查询数据库获取数据。使用接口的目的在于:
- Mybatis 只实现了简单的 JDBC 事务,但 Spring 有七种事务传播机制,拥有更强大的事务能力。
关联 Mapper XML 映射文件,这个才是真正的 SQL 语句。
- 提供接口规范给开发人员调用。
- 与 SQL 解耦。不必将 SQL 语句嵌套在代码里。
当调用 getObject()
方法时,就会根据 Mapper 接口的 Class 类型从 SqlSession
里面中获取 Mapper 代理对象,这与原来的 Mybatis 编程思路是一样的。getSqlSession()
方法是通过继承 SqlSessionDaoSupport
抽象类所获取的能力,这是 mybatis-spring 提供的便于获取 SqlSesstionTemplate
对象的方式。
那当我们通过依赖注入 Mapper 对象,Spring 是如何找到合适的代理对象的呢?
3.4 注入 Mapper 代理对象发生了什么?
我们可以在 Mybatis 与 Spring 集成的环境中写出如下代码
一般在 Service 层注入 DAO 层的 Mapper,这样就可以查询数据库。那 Spring 是怎么依赖查找 AuthorMapper 代理对象的呢?
答案在于 Mybatis 初始化阶段,将一个个 Mapper 接口转换为 Spring 的 BeanDefinition 注册到 Spring BeanFactory 。核心类是 org.mybatis.spring.mapper.ClassPathMapperScanner
。
此时某个 Mapper 接口的 BeanDefinition 长这样子:
有了 BeanDefinition ,在 beanFactory.preInstantiateSingletons();
阶段就会根据 BeanDefnition 实例化非懒加载对象并放入单例池中。此时,Mapper 接口代理对象生成,并由 Spring 框架管理。
在 Spring 单例池中,每个Mapper 所对应的 MapperProxy 都是不同的对象。看到 MapperProxy 对象,如果对 Mybatis 熟悉的同学应该知道,他就是 Mapper 接口的代理对象,将我们的接口方法调用转为最终的 SQL 查询。
我们梳理一下流程:
- 首先,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 代理对象。DefaultSqlSessionFactory
单例,Spring 管理。SqlSessionTemplate
单例,Spring 管理。- 解析 Mapper 接口,变成 BeanDefinition并注册。
- Spring 实例化单例。
- 判断当前是否为 FactoryBean,因为 BeanDefinition#beanClass 为 MapperFactoryBean,肯定是 FactoryBean。先从单例池中获取 &beanName 的 FactoryBean 对象。
- 单例池不存在,创建对象、注入相关属性(比如
SqlSessionTemplate
)、放入 SpringsingletonObjects
单例池中。此时,关系映射为 mapper name -> MapperFactoryBean。 - 从 FactoryBean 对象中获取真正的对象,即 MapperProxy。
上述简单描述了 Spring 初始化 Mybatis 过程。主要核心是单例池存放 Mapper->MapperFactoryBean 映射关系,当需要注入 Mapper 对象是,则由 MapperFactory#getObject() 方法产生代理对象 MapperProxy,而 MapperProxy则是 Mybatis 提供可以查询数据库的代理对象。