使用IntelliJ IDEA开发SpringMVC网站(五)集成事务管理

在上一篇《使用IntelliJ IDEA开发SpringMVC网站(四)集成MyBatis》文章中我们集成了MyBatis,本文在原来的基础上加入事务管理。

加入事务管理之前,我们先完善一下原来的代码增加更多的数据表的操作,并用Junit进行测试。

修改UserMapper.xml文件,增加对数据insert,update,delete的SQL,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<delete id="deleteById" parameterType="java.lang.Integer">
DELETE FROM t_user WHERE user_id = #{userId}
</delete>

<insert id="insert" parameterType="User">
INSERT INTO t_user(user_id,user_name,user_age) VALUES(#{id},#{userName},#{age});
</insert>

<update id="update" parameterType="User">
UPDATE t_user
<set>
<if test="userName != null">
user_name = #{userName},
</if>
<if test="age != null">
user_age = #{age}
</if>
</set>
WHERE user_id = #{id}
</update>

<select id="getAll" resultMap="userMap">
SELECT user_id,user_name,user_age FROM t_user
</select>

parameterType=”User” 这里的User使用了简写,实际上是 parameterType=”com.springmvcdemo.model.User” 为了支持这种写法我们需要在sqlSessionFactory上配置typeAliasesPackage属性,如下所示:

1
2
3
4
5
6
<!-- SqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="mapperLocations" value="classpath:mapper/*.xml" />
<property name="typeAliasesPackage" value="com.springmvcdemo.model" />
</bean>

修改UserMapper接口:

1
2
3
4
5
6
7
8
9
10
11
12
@Repository
public interface UserMapper {
User findById(@Param("userId") int userId);

int deleteById(int userId);

int insert(User user);

int update(User user);

List<User> getAll();
}

修改UserService:

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
@Service
public class UserService {
@Autowired
private UserMapper userMapper;

public User findById(int userId) {
return userMapper.findById(userId);
}

public int deleteById(int userId) {
return userMapper.deleteById(userId);
}

public int insert(User user) {
return userMapper.insert(user);
}

public int update(User user) {
return userMapper.update(user);
}

public List<User> getAll() {
return userMapper.getAll();
}
}

使用Junit进行测试,首先引入依赖的spring-test jar包:

1
2
3
4
5
6
<!-- spring test -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
</dependency>

因为我们使用了Maven,maven目录结构中要测试相关代码存放在test目录下,所以我们在现有的src目录下手动创建 test/java 目录,并设置为 Tests 如下所示:

image

在src/test/java目录下面新建一个名为TestMyBatis的测试类:

书写测试代码的过程中,报了一个错误,如下所示:

image

“Could not autowire. No beans of ‘UserService’ type found”,不能自动装配UserService,原来是我们开启Spring自动检测Bean的配置是在spring-mvc.xml文件里面的,如下所示:

1
2
<!-- Spring Auto scanning components -->
<context:component-scan base-package="com.springmvcdemo" />

而我们这里通过Junit测试,不需要依赖spring mvc 环境,我们只引入了spring.xml和spring-mybatis.xml两个文件

1
@ContextConfiguration(locations = {"file:src/main/webapp/WEB-INF/spring.xml","file:src/main/webapp/WEB-INF/spring-mybatis.xml"})

所以对spring-mvc.xml里面的配置稍作修改:

1
<context:component-scan base-package="com.springmvcdemo.controller" />

base-package 指定为com.springmvcdemo.controller表明只对controller包下面的类进行扫描。

在spring.xml文件里面增加配置:

1
<context:component-scan base-package="com.springmvcdemo.mapper, com.springmvcdemo.service" />

表明对mapper和service包及其子包进行扫描,找到能够自动注册为Spring Bean的类。

修改完成后,我们来编写测试类TestMyBatis:

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
41
42
43
@RunWith(SpringJUnit4ClassRunner.class) // = extends SpringJUnit4ClassRunner
@ContextConfiguration(locations = {"file:src/main/webapp/WEB-INF/spring.xml","file:src/main/webapp/WEB-INF/spring-mybatis.xml"})
public class TestMyBatis {
private static final Logger logger = Logger.getLogger(TestMyBatis.class);

@Autowired
private UserService userService;

@Test
public void test1() {
User user = userService.findById(2);
logger.info("username:" + user.getUserName() + " , userAge=" + user.getAge());
}

@Test
public void test2() {
User user = new User();
user.setId(3);
user.setUserName("Toby");
user.setAge("28");
userService.insert(user);
}

@Test
public void test3() {
User user = userService.findById(3);
user.setAge("27");
userService.update(user);
}

@Test
public void test4() {
List<User> userList = userService.getAll();
for (User user : userList) {
logger.info("username:" + user.getUserName() + " , userAge=" + user.getAge());
}
}

@Test
public void test5() {
userService.deleteById(3);
}
}

依次执行 test1()到test5()方法,都可正常执行。

下面我们来对MyBatis加入事务管理。

参考MyBatis-Spring官方文档:http://www.mybatis.org/spring/zh/transactions.html

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

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

  • 注:Spring并不直接管理事务,而是提供了多种事务管理器,它们将事务管理的职责委托给JTA或其他持久化机制所提供的平台相关的事务实现:

image

(图片摘自Spring实战 第3版 第6章)

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

要 开 启 Spring 的 事 务 处 理 , 在 Spring 的 XML 配 置 文 件 中 简 单 创 建 一 个 DataSourceTransactionManager 对象,在spring-mybatis.xml文件中增加如下配置:

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

我们使用声明式事务的方式,只需要在spring上下文中添加一行XML,即可声明事务,我们在spring-mybatis.xml文件中增加如下配置:

1
<tx:annotation-driven />

就是这样!如果你期望更多的代码,很抱歉,就这么多了!为了让它更有趣一些,我们可以通过transaction-manager属性(默认值是transactionManager)来指定特定的事务管理器:

1
<tx:annotation-driven transaction-manager="txManager" />

否则,它就那么多而已。这一行XML包含了强大的功能,它允许在最有意义的位置声明事务规则:在事务的方法上。

tx:annotation-driven 元素告诉Spring检查上下文中所有Bean并查找使用@Transactional注解的Bean,而不管这个注解时用在类级别上还是方法级别上。

我们修改UserService,增加事务:

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
@Service
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
public class UserService {
@Autowired
private UserMapper userMapper;

public User findById(int userId) {
return userMapper.findById(userId);
}

@Transactional(propagation = Propagation.REQUIRED, readOnly = false)
public int deleteById(int userId) {
return userMapper.deleteById(userId);
}

@Transactional(propagation = Propagation.REQUIRED, readOnly = false)
public int insert(User user) {
return userMapper.insert(user);
}

@Transactional(propagation = Propagation.REQUIRED, readOnly = false)
public int update(User user) {
return userMapper.update(user);
}

public List<User> getAll() {
return userMapper.getAll();
}
}

在类级别上,UserService使用了@Transactional注解,表示所有的方法都支持事务并且是只读的。在方法级别上,insert、delete、update 方法通过注解来表示这个方法所需要的事务上下文。

至此,增加事务管理功能完成。接下来会继续完善框架。
(代码已提交到GitHub:https://github.com/jasonli822/MySpringMvcFrame)