MyBatis-Spring:Mybatis在Spring中的使用

当我们想在 Spring 项目中使用 Mybatis 的时候就需要 MyBatis-Spring 了,它可以让 Spring 完美的整合 MyBatis 代码。使用这个类库中的类,Spring 将会加载必要的 MyBatis 工厂类和 session 类。 这个类库也提供一个简单的方式来注入 Mybatis 数据映射器和 SqlSession 到业务层的 bean 中。 而且它也会处理事务, 翻译 MyBatis 的异常到 Spring 的 DataAccessException 异常(数据访问异常)中

我们先走一个简单例子,再在基础上扩展

例子

添加依赖

使用 mybatis-spring 模块,需要添加 mybatis-spring-x.x.x.jar

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

同时加上 mybatis-x.x.x.jar 包和相关数据库连接包,spring 的依赖添加上 spring-context 和 spring-jdbc,如果是 web 项目,再加上 web 相关的 jar 包即可

创建 SqlSessionFactoryBean

我们在使用 MyBatis 的时候,最关键的是去使用 SqlSession 去执行映射的 SQL 语句。而 SqlSession 是通过 SqlSesionFactory 来获取的。而 SqlSessionFactory 的实例则是由SqlSessionFactoryBuilder 从 XML 配置文件或一个预先定制的 Configuration 的实例构建出来的

现在,我们是要在 Spring 上整合 MyBatis,需要把 SqlSessionFactory 的创建和 注入交给 Spring容器,在 Mybatis-Spring 中 SqlSessionFactory 的创建交给了 SqlSessionFactoryBean

我们在项目中创建一个 Spring 的配置文件,添加相关配置,加上 SqlSessionFactoryBean 的配置

application-mybatis.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">

<!-- 使用注解声明 -->
<context:component-scan base-package="com.brave"/>

<!-- 加载属性资源 -->
<context:property-placeholder location="classpath*:conf/*.properties"></context:property-placeholder>

<!-- 配置 SqlSessionFactoryBean -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<!-- mybatis 配置文件位置(默认配置的话,可以不用)-->
<property name="configLocation" value="classpath:conf/mybatis/mybatis-config.xml"></property>
<!-- 配置映射器文件 -->
<property name="mapperLocations" value="classpath*:com/brave/dao/mapper/*Mapper.xml" />
</bean>

<!-- 配置数据源 -->
<!-- 这里使用的是 druid 连接池,需要另外导入 druid.jar -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="driverClassName" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</bean>

<!-- 事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
</beans>

要注意 SqlSessionFactoryBean 实现了 Spring 的 FactoryBean 接口。
这就说明了由 Spring 最终创建的 bean 不是 SqlSessionFactoryBean 本身。 而是工厂类的 getObject()返回的方法的结果。这种情况下,Spring 将会在应用启动时为你 创建 SqlSessionFactory 对象,然后将它以 SqlSessionFactory 为名来存储。在 Java 中, 相同的代码是:

SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
SqlSessionFactory sessionFactory = factoryBean.getObject();

但是一般我们不直接使用 SqlSessionFactoryBean 或 SqlSessionFactory。因为 session 工厂将会被注入到映射器中 或其它扩展了 SqlSessionDaoSupport 的 DAO(Data Access Object, 数据访问对象)中

SqlSessionFactory有三个属性

  • dataSource 属性是必须的,配置数据源信息
  • configLocation 属性用来指定 MyBatis 的 XML 配置文件路径,如果基本的 MyBatis 配置需要改变(通常这会是 \<settings> 或 \<typeAliases> 的部分)
  • mapperLocations 属性用来指定映射器文件的资源位置

mapperLocations 可以配置多个位置

1
2
3
4
5
6
<property name="mapperLocations">
<list>
<value>classpath*:conf/mybatis/**/*Mapper.xml</value>
<value>classpath*:conf/com/brave/**/*Mapper.xml</value>
</list>
</property>

使用 SqlSessionTemplate

在 MyBatis 中,我们使用 SqlSessionFactory 来创建 SqlSession。获取了一个 session 之后,使用它来执行映射的 SQL 语句、提交或回滚连接,最后当不再需要它的时候关闭 session。

使用 MyBatis-Spring 之后,就不需要直接使用 SqlSessionFactory 了,因为你的 bean 可以通过一个线程安全的 SqlSession 来注入,基于 Spring 的事务配置来自动提交、回滚、关闭 session。

SqlSessionTemplate

SqlSessionTemplate 是 MyBatis-Spring 的核心。它负责管理 MyBatis 的 SqlSession, 调用 MyBatis 的 SQL 方法、翻译异常。它是线程安全的,可以被多个 DAO 所共享使用。

当调用 SQL 方法时,包含从映射器 getMapper()方法返回的方法,SqlSessionTemplate 将会保证使用的 SqlSession 是和当前 Spring 的事务相关的。它管理这 session 的生命周期,包含必要的关闭,提交或回滚操作。

SqlSessionTemplate 对象可以使用 SqlSessionFactory 作为构造方法的参数来创建

1
2
3
4
5
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSessionFactory" />
<!-- 配置了BATCH 表示所有的语句可以批量操作 -->
<constructor-arg index="1" value="BATCH" />
</bean>

SqlSessionTemplate 实现了 SqlSession 接口,不用再代码中对 MyBatis 的 SqlSession 进行替换,直接在 Dao bean中直接注入这个 SqlSession 属性就可以了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Repository
public class UpmsUserDaoImpl implements UpmsUserDao {

@Autowired
private SqlSession sqlSession;

@Override
public UpmsUser selectOne(Long userId) {
return sqlSession.selectOne("com.brave.dao.UpmsUserDao.selectOne", userId);
}

@Override
public List<UpmsUser> selectUser(UpmsUser upmsUser) {
return sqlSession.selectList("com.brave.dao.UpmsUserDao.selectUser", upmsUser);
}

@Override
public int insertUser(UpmsUser upmsUser) {
return sqlSession.insert("com.brave.dao.UpmsUserDao.insertUser", upmsUser);
}
}

这里附上我的映射文件 upmsUserMapper.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.brave.dao.UpmsUserDao">

<resultMap type="com.brave.model.UpmsUser" id="upmsUser">
<id column="user_id" property="userId"/>
<result column="loginname" property="loginname"/>
<result column="password" property="password"/>
<result column="realname" property="realname"/>
<result column="phone" property="phone"/>
<result column="email" property="email"/>
<result column="is_locked" property="locked"/>
<result column="gmt_create" property="gmtCreate"/>
<result column="gmt_modified" property="gmtModified"/>
</resultMap>

<select id="selectOne" resultMap="upmsUser">
select * from upms_user where user_id = #{userId}
</select>

<select id="selectUser" resultMap="upmsUser">
select * from upms_user
<where>
<if test="loginname != null and loginname != '' ">
loginname like #{loginname}
</if>
<if test="realname != null and realname != '' ">
and realname like #{realname}
</if>
</where>
</select>

<insert id="insertUser">
insert into upms_user (user_id,loginname,password,realname,is_locked)
value(#{userId},#{loginname},#{password},#{realname},#{locked})
</insert>

</mapper>

使用 Junit 生成测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@RunWith(SpringJUnit4ClassRunner.class)  //使用junit4进行测试  
@ContextConfiguration ("/conf/spring/applicationContext*.xml")
public class UpmsUserDaoImplTest {

@Autowired
private UpmsUserDao upmsUserDao;

@Test
public void testSelectOne() {
System.out.println(upmsUserDao.selectOne(10001L).toString());
}

@Test
public void testSelectUser() {
UpmsUser user = new UpmsUser();
user.setLoginname("zou");
user.setUserId(2L);
List<UpmsUser> upmsUsers = upmsUserDao.selectUser(user);
for (UpmsUser upmsUser : upmsUsers) {
System.out.println(upmsUser.toString());
}
}

@Test
@Transactional //声明需要事务
public void testInsertUser() {
UpmsUser upmsUser = new UpmsUser();
upmsUser.setUserId(10002L);
upmsUser.setLoginname("zou");
upmsUser.setPassword("123456");
upmsUser.setLocked(false);
int n = upmsUserDao.insertUser(upmsUser);
System.out.println("插入" + n + "行");
}
}

SqlSessionDaoSupport

SqlSessionDaoSupport 是一个抽象的支持类,用来提供 SqlSession。getSqlSession()方法可以得到一个 SqlSessionTemplate

1
2
3
4
5
6
public class UpmsUserDaoImpl2 extends SqlSessionDaoSupport implements UpmsUserDao {
@Override
public UpmsUser selectOne(Long userId) {
return getSqlSession().selectOne("com.brave.dao.UpmsUserDao.selectOne", userId);
}
}

SqlSessionDaoSupport 需要设置一个 sqlSessionFactory 或 sqlSessionTemplate 属性

假如是 UpmsUserDaoImpl2 继承了 SqlSessionDaoSupport:

1
2
3
<bean id="upmsUserDaoImpl" class="com.brave.dao.UpmsUserDaoImpl2">
<property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>

可以创建一个公用的 SuperDao 去继承 SqlSessionDaoSupport,为 SuperDao 设置 sqlSessionFactory 属性,然后让其他 dao 去继承 SuperDao,这样就不用每个 dao 都去设置这个属性了

1
2
3
4
5
6
public class SuperDao extends SqlSessionDaoSupport {
@Autowired
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
super.setSqlSessionFactory(sqlSessionFactory);
}
}

不过通常我们更常用 MapperFactoryBean(只需要接口,不用实现类),因为它不需要额外的代码。但是如果需要在 DAO 中做其它非 MyBatis 的工作或需要具体的类,那么这个类就很有用了。

事务

MyBatis-Spring 允许 MyBatis 参与到 Spring 的事务管理中。利用了存在于 Spring 中的 DataSourceTransactionManager,而不是创建一个新的特定的事务管理器

一旦 Spring 的 PlatformTransactionManager 配置好了,可以在 Spring 中以通常的做法来配置事务。@Transactional 注解和 AOP 样式的配置都是支持的。在事务处理期间,一个单独的 SqlSession 对象将会被创建和使用。当事务完成时,这个 session 会以合适的方式提交或回滚。

一旦事务创建之后,MyBatis-Spring 将会透明的管理事务。在你的 DAO 类中就不需要额外的代码了

标准配置

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

1
2
3
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>

注入映射器

在 MyBatis 中,我们可以创建一个来绑定映射的语句的接口映射器(实际上也就是我们的 dao 接口),在映射文件的命名空间与我们的映射器完全限定名一致,映射语句 id 名就是映射器的方法名,这样我们可以不用实现这个映射器,通过 sqlSession 获取映射器,直接使用 sql 方法

1
2
UpmsUserDao upmsUserDao = sqlSession.getMapper(UpmsUserDao.class);
UpmsUse upmsUse = upmsUserDao.selectOne(10002L);

为了代替手工使用 SqlSessionDaoSupport 或 SqlSessionTemplate 编写数据访问对象 (DAO)的代码,MyBatis-Spring 也提供了一个动态代理的实现:MapperFactoryBean。这个类可以让你直接注入 数据映射器接口 到你的 service 层 bean 中。当使用映射器时,直接调用就可以了,不需要实现 DAO接口,因为 MyBatis-Spring 将会为你创建代理。

使用注入的映射器代码,在 MyBatis、Spring 或 MyBatis-Spring 上面不会有直接的依赖。 MapperFactoryBean 创建的代理控制开放和关闭 session,会翻译任意的异常到 Spring 的 DataAccessException 异常中。此外,如果需要或参与到一个已经存在活动事务中,代理将会开启一个新的 Spring 事务。

MapperFactoryBean

想要直接使用 数据映射器接口,先添加 MapperFactoryBean 到 Spring 中:

1
2
3
4
<bean id="upmsUserMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface" value="com.brave.mapper.UpmsUserMapper" />
<property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>

MapperFactoryBean 创建的代理类实现了 UpmsUserMapper 接口,并且注入到应用程序中。 因为代理创建在运行时环境中,那么指定的映射器必须是一个接口,而不是一个具体的实现类。

如果 XML 映射器文件在类路径的位置和映射器类相同时,它会被 MapperFactoryBean 自动解析,否则你需要在 SqlSessionFactoryBean 的 mapperLocations 或是在 MyBatis 的配置文件中指明映射文件的位置

这里附上映射文件,这里的命名空间一定要是映射器的完全限定名

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 这里的命名空间一定要是映射器的完全限定名 -->
<mapper namespace="com.brave.mapper.UpmsUserMapper">
<!-- 这里的id与映射器的方法一致 -->
<select id="selectUser" resultType="com.brave.model.UpmsUser">
select * from upms_user
</select>
</mapper>

接口映射器 UpmsUserMapper.java

1
2
3
public interface UpmsUserMapper {
List<UpmsUser> selectUser();
}

然后我们在应用程序逻辑中可以直接使用

1
2
3
4
5
6
7
8
9
10
@Service
public class UpmsUserServiceImpl implements UpmsUserService {
@Autowired
private UpmsUserMapper upmsUserMapper;

@Override
public List<UpmsUser> listUser() {
return upmsUserMapper.selectUser();
}
}

MapperScannerConfigurer

我们没有必要在 Spring 的 XML 配置文件中注册所有的映射器。只用使用一个 MapperScannerConfigurer 即可,它会查找类路径下的映射器并自动将它们创建成 MapperFactoryBean

1
2
3
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.brave.mapper" />
</bean>

这样就不用每个 mapper 都配置一个 MapperFactoryBean 了

basePackage 属性是让你为映射器接口文件设置基本的包路径。 可以使用 分号 或 逗号 作为分隔符设置多于一个的包路径。每个映射器将会在指定的包路径中递归地被搜索到

@MapperScan 和 mybatis:scan

另外还有两种扫描映射器的方法

  • <mybatis:scan> 元素
  • @MapperScan 注解

\mybatis:scan\ 元素

<mybatis:scan> 元素将在特定的以逗号分隔的包名列表中搜索映射器 mapper 接口。 要使用这个元素需要添加以下的 schema 声明

1
2
3
4
5
6
7
8
9
10
11
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mybatis="http://mybatis.org/schema/mybatis-spring"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://mybatis.org/schema/mybatis-spring
http://mybatis.org/schema/mybatis-spring.xsd">

<mybatis:scan base-package="com.brave.mapper" />

</beans>

@MapperScan 注解

如果使用基于java的配置,可以使用@MapperScan 注解来扫描映射器 Mapper 接口。 @MapperScan 和 <mybatis:scan/> 工作方式

相同,并且也提供了对应的自定义选项

在 Spring-Boot 项目中可能会使用到

1
2
3
4
5
6
7
@SpringBootApplication  
@MapperScan("com.brave.mapper")
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}