第六章 事务管理
在软件开发领域,全有全无的操作称为事务。事务允许你将几个操作组合成一个要么全部发生,要么全部不发生的工作单元。
6.1 理解事务
6.1.1 事务的四个特性
- 原子性(Atomic):事务是有一个或多个活动所组成的一个工作单元。原子性确保事务中的所有操作全部发生或全部不发生。若果所有的活动都成功了,事务也就成功了,否则,失败!
- 一致性(Consistent):一旦事务完成(无论成功失败),系统必须确保他所建模的业务处于一致的状态。现实的数据不应该被损坏。
- 隔离性(Isolated):事务允许多个用户对相同的数据进行操作,每个操作不会与其他用户纠缠在一起。因此,事务应该被彼此隔离,避免发生同步读写相同数据的事情(隔离性往往涉及到锁定数据库中的行或表)。
- 持久性(Durable):一旦事务完成,事务的结果因该持久化,这样就能从任何的系统崩溃中回复过来。
6.1.2 Spring对事务管理的支持
Spring提供了对编码式和声明式事务管理的支持。
EJB的容器管理事务(container-managed transation)CMT。允许声明式地定义事务边界。
6.2 选择事务管理器
spring并不直接管理事务,提供了多种事务管理器,他们将事务管理的职责委托给JTA(java事务API:Java Transaction API)或其他持久化机制所提供的平台相关的事物实现(应该达到了解耦的目标),如下图。
6.2.1 JDBC事物
jdbc的事务配置如下的bean,jdbc的事务底层还是java.sql.Connection原生的java API中的commit()和rollbcak()方法:
1 <!-- jdbc的事务bean --> 2 <bean id="transationManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> 3 <property name="dataSource" ref="dataSource"></property> 4 </bean>
6.2.3 Hibernate事物
需要pom.xml依赖Spring-orm的jar包下下来:
1 <dependency> 2 <groupId>org.springframework</groupId> 3 <artifactId>spring-orm</artifactId> 4 <version>4.2.9.RELEASE</version> 5 </dependency>
1 <!-- hibernate 的事务,需要一个session工厂 --> 2 <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager"> 3 <property name="sessionFactory" ref="sessionFactory"></property> 4 </bean>
6.2.3 java持久化API事务(JPA事务)
和上面hibernate配置差不多,都是依赖Spring-orm的jar包(jpa了解的不是很多,改日再详谈,但是看的不是很懂)。
6.4 声明式事务
spring对声明式的事物支持是通过使用Spring AOP框架实现的。
Spring提供了3种方式来声明事物边界。Spring AOP和TransactionPrxoyFactoryBean,Spring的tx命名空间和@Transactional注解。后面一种方式更好。
6.4.1 定义事务属性
声明事务是通过事务属性来定义的。事务属性包含5个方面。传播行为、隔离级别、回滚规则、事务超时、是否只读。
传播行为定义了客户端与被调用方法之间的事务边界。传播规则:新的事务应该被启动还是被挂起,或者方法是否在事务环境中运行。
七种传播行为:
PROPAGATION_MANDATORY(propagation_mandatory):表示该方法必须在事务中运行,如果当前事务不存在,则会抛出一个异常;
PROPAGATION_NESTED(propagation_nested):表示如果当前已经存在一个事务,那么该方法将会在嵌套事务中运行。嵌套的事务可以独立于当前事务进行单独地提交或回滚。如果当前事务不存在,那么其行为与PROPAGATION_REQUIRED一样,
PROPAGATION_NEVER(propagation_never):表示当前方法不应该运行在事务上下文。如果正有一个事务在运行,则会抛出异常。
PROPAGATION_SUPPORTED(propagation_supported):表示该方法不应该运行在事务中。如果存在当前事务,在该方法运行期间,当前事务将被挂起。
PROPAGATION_REQUIRED(propagation_required):表示当前方法必须运行事务中。如果当前事务存在,方法将会在该事务中运行。否则,会启动一个新的事务。
PROPAGATION_REQUIRES_NEW(propagation_required_new ):表示当前方法必须运行在它自己的事务中。一个新的事务将被启动。如果存在当前事务,在该方法执行期间,当前事务会被挂起。
PROPAGATION_SUPPORTS(propagation_supports):表示当前方法不需要事务上下问,但是如果存在当期那事务的话,那么该方法会在这个事务中运行。
隔离级别:
隔离级别定义了一个事务可能受其他并发事务影响程度。另一种考虑隔离级别的方式是将其想象成事务对于数据的自私程度。
多个事务并发运行,会出现以下问题:
- 脏读(Dirty reads)——脏读发生在一个事务读取了另一个事务改写但尚未提交的数据时。如果改写在稍后被回滚了,那么第一个事务获取的数据就是无效的。
- 不可重复读(Nonrepeatable read)——不可重复读发生在一个事务执行相同的查询两次货两次以上,但是每次都得到不同的数据时。这通常是因为另一个并发事务在两次查询期间更新了数据。
- 幻读(Phantom read)——欢度与不可重复读类似。发生在一个事务(T1)读取几行数据,接着另一个并发事务(T2)插入了一些数据时,在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录。
ISOLATION_DEFAULT:使用后端数据库默认的隔离级别。
ISOLATION_READ_UNCOMMITED:允许读取未提交的数据变更。可能会导致脏读、幻读、或不可重复读,效率最高效,隔离程度最低。
ISOLATION_READ_COMMITED:允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。
ISOLATION_REPEATABLE_READ:对同一个字段的多次读取结果是一致的,除非数据是被事务自己所修改,可以阻止脏读和不可重复读,幻读仍有可能发生。
ISOLATION_SERIALIZABLE:完全服从ACID的隔离级别,确保阻止脏读,不可重复读以及幻读。这是最慢的事务隔离级别,因为它通常是通过完全锁定事务相关的数据库表来实现的。
以上的隔离级别,并不是所有的数据库都支持。
只读:
如果事务只对后端的数据库进行读操作,数据库可以利用事务的只读特性来进行一些特定的优化(没用过,不怎么理解)。
事务超时:
假设事务的运行时间变得特别长。因为事务可能涉及对后端数据库的锁定,所以长时间的事务会不必要的占用数据库的资源。你可以声明一个事务,在特定的秒数后自动回滚,而不是等待其结束。
因为超时时钟会在事务开始时启动,所以,只有对那些具备可能启动一个新事务的传播行为(PROPAGATION_REQUIRED,PROPAGATION_REQUIRED_NEW以及PROPAGATION_NESTED)的方法来书,声明事务超时才有意义。
回滚规则:
默认情况下,事务只有在遇到运行期异常时才会回滚,而在遇到检查型异常时不会回滚。
但是你可以声明事务在遇到特定的检查型异常时像遇到运行期异常那样回滚。同样,你还可以声明事务遇到特定的异常不回滚,即使这些异常时运行期异常。
6.4.2 在XML中定义事务
借助spring提供的tx命名空间声明事务。
tx的命名空间:xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.0.xsd"
我用mybatis测试数据:所以涉及到简单的mybatis知识,不详细展开,整个工程的主要用到的包如下。
pom.xml中增加依赖jar包:
1 <!-- mybatis jar --> 2 <dependency> 3 <groupId>org.mybatis</groupId> 4 <artifactId>mybatis</artifactId> 5 <version>3.2.2</version> 6 </dependency> 7 <!-- mybatis spring jar --> 8 <dependency> 9 <groupId>org.mybatis</groupId> 10 <artifactId>mybatis-spring</artifactId> 11 <version>1.3.0</version> 12 </dependency>
dao接口:
1 package com.springinaction.dao; 2 3 import java.util.List; 4 5 import org.springframework.stereotype.Component; 6 import org.springframework.stereotype.Repository; 7 8 import com.springinaction.model.User; 9 10 /** 11 * 12 * @ClassName: UserDao 13 * @Description: 用户dao的接口 14 * @author mao 15 * @date 2017年3月29日 下午11:58:50 16 * 17 */ 18 19 public interface IUserDao { 20 21 /** 22 * 23 * @Title: addUser 24 * @Description: 增加一个用户 25 * @param @param user 26 * @return int 返回影响的行数 27 * @throws 28 */ 29 public int addUser(User user) throws Exception; 30 31 /** 32 * 33 * @Title: deleteOneUser 34 * @Description: 根据id删除一个用户 35 * @param @param id 36 * @param @return 37 * @param @throws Exception 38 * @return int 返回影响的行数 39 * @throws 40 */ 41 public int deleteOneUser(int id) throws Exception; 42 43 /** 44 * 45 * @Title: selectAllUsers 46 * @Description: 查询全部的用户信息 47 * @param @return 48 * @param @throws Exception 49 * @return List<User> 返回一个用户集合 50 * @throws 51 */ 52 public List<User> selectAllUsers() throws Exception; 53 54 }
同时,和dao接口在同一个包中有一个mapper文件
1 <?xml version="1.0" encoding="UTF-8" ?> 2 <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 3 "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> 4 <mapper namespace="com.springinaction.dao.IUserDao"> 5 6 <!-- 增加一个用户 --> 7 <insert id="addUser" parameterType="com.springinaction.model.User"> 8 insert into user1(id,userName,birthday,sex,address) 9 values(#{id},#{userName},#{birthday},#{sex},#{address}) 10 </insert> 11 12 <!-- 删除一个用户 --> 13 <delete id="deleteOneUser" parameterType="int"> 14 delete from user1 where id=#{id} 15 </delete> 16 <!-- 查询全部的用户 --> 17 <select id="selectAllUsers" resultType="com.springinaction.model.User"> 18 select * from user1 19 </select> 20 21 </mapper>
Service接口(和dao接口一模一样):
1 package com.springinaction.service; 2 3 import java.util.List; 4 5 import com.springinaction.model.User; 6 7 public interface IUserService { 8 9 /** 10 * 11 * @Title: addUser 12 * @Description: 先珊瑚一个用户,再增加一个用户 13 * @param @param user 14 * @param @param id 15 * @param @return 16 * @param @throws Exception 17 * @return int 18 * @throws 19 */ 20 public int addUser(User user,int id) throws Exception; 21 22 /** 23 * 24 * @Title: deleteOneUser 25 * @Description: 根据id删除一个用户 26 * @param @param id 27 * @param @return 28 * @param @throws Exception 29 * @return int 返回影响的行数 30 * @throws 31 */ 32 public int deleteOneUser(int id) throws Exception; 33 34 /** 35 * 36 * @Title: selectAllUsers 37 * @Description: 查询全部的用户信息 38 * @param @return 39 * @param @throws Exception 40 * @return List<User> 返回一个用户集合 41 * @throws 42 */ 43 public List<User> selectAllUsers() throws Exception; 44 }
service实现类:
1 package com.springinaction.service.impl; 2 3 import java.util.List; 4 5 6 import org.springframework.beans.factory.annotation.Autowired; 7 import org.springframework.beans.factory.annotation.Qualifier; 8 import org.springframework.stereotype.Component; 9 import org.springframework.stereotype.Service; 10 import org.springframework.transaction.annotation.Transactional; 11 12 import com.springinaction.dao.IUserDao; 13 import com.springinaction.model.User; 14 import com.springinaction.service.IUserService; 15 16 @Component("userService") 17 public class UserServiceImpl implements IUserService { 18 19 @Autowired 20 @Qualifier("userDao") 21 private IUserDao userDao; 22 23 24 25 26 27 public int addUser(User user,int id) throws Exception { 28 29 userDao.deleteOneUser(id); 30 31 //这里是测试会不会回滚的 32 // int i = 1/0; 33 34 userDao.addUser(user); 35 36 37 return 0; 38 } 39 40 public int deleteOneUser(int id) throws Exception { 41 42 return userDao.deleteOneUser(id); 43 } 44 45 public List<User> selectAllUsers() throws Exception { 46 47 return userDao.selectAllUsers(); 48 } 49 50 }
springbean.xml文件:
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xmlns:context="http://www.springframework.org/schema/context" 5 xmlns:aop="http://www.springframework.org/schema/aop" 6 xmlns:tx="http://www.springframework.org/schema/tx" 7 xmlns:p="http://www.springframework.org/schema/p" 8 xsi:schemaLocation="http://www.springframework.org/schema/beans 9 http://www.springframework.org/schema/beans/spring-beans-4.0.xsd 10 http://www.springframework.org/schema/context 11 http://www.springframework.org/schema/context/spring-context-4.0.xsd 12 http://www.springframework.org/schema/aop 13 http://www.springframework.org/schema/aop/spring-aop-4.0.xsd 14 http://www.springframework.org/schema/tx 15 http://www.springframework.org/schema/tx/spring-tx-4.0.xsd"> 16 17 18 <!--注解形式的 组件扫描 --> 19 <context:component-scan 20 base-package="com.springinaction.*"></context:component-scan> 21 22 23 <!--基于dbcp 配置数据源 --> 24 <!-- <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"> 25 <property name="driverClassName" value="com.mysql.jdbc.Driver"></property> 26 <property name="url" value="jdbc:mysql://localhost:3306/test"></property> 27 <property name="username" value="root"></property> 28 <property name="password" value="mysql"></property> 29 <property name="initialSize" value="5"></property> 30 <property name="maxActive" value="10"></property> 31 </bean> --> 32 33 <!--基于jdbc 配置数据源 --> 34 <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> 35 <property name="driverClassName" value="com.mysql.jdbc.Driver"></property> 36 <property name="url" value="jdbc:mysql://localhost:3306/test"></property> 37 <property name="username" value="root"></property> 38 <property name="password" value="mysql"></property> 39 <!-- <property name="initialSize" value="5"></property> 40 <property name="maxActive" value="10"></property> --> 41 </bean> 42 43 <!-- 使用SimpleJdbcTemplate --> 44 <!-- <bean id="jdbcTemplate" class="org.springframework.jdbc.core.simple.SimpleJdbcTemplate"> 45 <constructor-arg ref="dataSource"/> 46 </bean> --> 47 48 <!-- dao层 --> 49 <!-- <bean id="jdbcUserDao" class="com.springinaction.dao.impl.JdbcUserDao"> 50 <property name="jdbcTemplate" ref="jdbcTemplate"></property> 51 </bean> --> 52 53 <!-- jdbc的事务bean 事务管理器 --> 54 <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> 55 <property name="dataSource" ref="dataSource"></property> 56 </bean> 57 58 <!-- hibernate 的事务,需要一个session工厂 --> 59 <!-- <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager"> 60 <property name="sessionFactory" ref="sessionFactory"></property> 61 </bean> --> 62 63 64 65 66 <!-- 配置mybatis的sqlSessionFactory,他有三个属性,需要配置 --> 67 <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> 68 <!-- 对model包线下面的类起别名,在mapper文件中可以直接使用类名对应一个类型 --> 69 <property name="typeAliasesPackage" value="com.springinaction.model"></property> 70 <property name="dataSource" ref="dataSource"></property> 71 <!-- 配置mapper文件的位置 --> 72 <property name="mapperLocations" value="classpath:com/springinaction/dao/*Mapper.xml"></property> 73 </bean> 74 <!-- 根据sqlSessionFactory和mapper生成dao的实现:这个配置和上面一个配置,是mybatis的基础的配置, --> 75 <bean id="userDao" class="org.mybatis.spring.mapper.MapperFactoryBean"> 76 <property name="sqlSessionFactory" ref="sqlSessionFactory"></property> 77 <property name="mapperInterface" value="com.springinaction.dao.IUserDao"></property> 78 </bean> 79 80 <!-- 这里只是配置了一个事务的通知,应用在哪些方法上,以及传播行为 --> 81 <tx:advice id="txAdvice" transaction-manager="transactionManager"> 82 <tx:attributes> 83 <!-- 这个add的注释掉后,就不会回滚了 --> 84 <tx:method name="add*" propagation="REQUIRED"/> 85 <tx:method name="select*" propagation="SUPPORTS" read-only="true"/> 86 </tx:attributes> 87 </tx:advice> 88 89 <!-- 下面是切面的表达式,告诉在哪个类应用上面的通知 --> 90 <aop:config> 91 <aop:advisor advice-ref="txAdvice" pointcut="execution(* com.springinaction.service.IUserService.* (..))"/> 92 </aop:config> 93 94 95 96 97 </beans>
数据库的数据如下:
现在正常测试:
1 @Test 2 public void testTx() throws Exception { 3 4 IUserService userService = (IUserService) ac.getBean("userService"); 5 6 //增加一个id为3的用户,删除id24的用户 7 userService.addUser(new User(3, "李四3", "2001-01-01", "1","北京"), 24); 8 9 10 }
测试结果:
前后对比,是预期效果。然后,把service实现类的 int i = 1/0的注释打开,
1 public int addUser(User user,int id) throws Exception { 2 3 userDao.deleteOneUser(id); 4 5 //这里是测试会不会回滚的 6 int i = 1/0; 7 8 userDao.addUser(user); 9 10 11 return 0; 12 }
测试代码:
1 @Test 2 public void testTx() throws Exception { 3 4 IUserService userService = (IUserService) ac.getBean("userService"); 5 6 //增加一个id为4的用户,删除id22的用户,中途抛出异常,会出现回滚,即不会删除id22的用户 7 userService.addUser(new User(4, "李四3", "2001-01-01", "1","北京"), 22); 8 9 10 }
抛出异常:
数据库数据没变化:
这时候,把springbean.xml文件中<tx:advice>中的那个add的注释掉:会出现id22被删除,新插入id4的不成功。即没有为add方法添加事务
1 <!-- 这里只是配置了一个事务的通知,应用在哪些方法上,以及传播行为 --> 2 <tx:advice id="txAdvice" transaction-manager="transactionManager"> 3 <tx:attributes> 4 <!-- 这个add的注释掉后,就不会回滚了 --> 5 <!-- <tx:method name="add*" propagation="REQUIRED"/> --> 6 <tx:method name="select*" propagation="SUPPORTS" read-only="true"/> 7 </tx:attributes> 8 </tx:advice>
service实现类的和测试代码如上不变,直接测试:
结果如下图:
6.4.3 定义注解驱动的事务
spring的tx命名空间提供了注解驱动<tx:annotation-driven transaction-manager="transactionManager"/>,可以代替上面的事务通知,切点表达式的复杂配置。主要是通过注解@Transactional来实现的,注解驱动可以扫描这个注解(注解里面可以传不同的参数)。
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xmlns:context="http://www.springframework.org/schema/context" 5 xmlns:aop="http://www.springframework.org/schema/aop" 6 xmlns:tx="http://www.springframework.org/schema/tx" 7 xmlns:p="http://www.springframework.org/schema/p" 8 xsi:schemaLocation="http://www.springframework.org/schema/beans 9 http://www.springframework.org/schema/beans/spring-beans-4.0.xsd 10 http://www.springframework.org/schema/context 11 http://www.springframework.org/schema/context/spring-context-4.0.xsd 12 http://www.springframework.org/schema/aop 13 http://www.springframework.org/schema/aop/spring-aop-4.0.xsd 14 http://www.springframework.org/schema/tx 15 http://www.springframework.org/schema/tx/spring-tx-4.0.xsd"> 16 17 18 <!--注解形式的 组件扫描 --> 19 <context:component-scan 20 base-package="com.springinaction.*"></context:component-scan> 21 22 23 <!--基于dbcp 配置数据源 --> 24 <!-- <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"> 25 <property name="driverClassName" value="com.mysql.jdbc.Driver"></property> 26 <property name="url" value="jdbc:mysql://localhost:3306/test"></property> 27 <property name="username" value="root"></property> 28 <property name="password" value="mysql"></property> 29 <property name="initialSize" value="5"></property> 30 <property name="maxActive" value="10"></property> 31 </bean> --> 32 33 <!--基于jdbc 配置数据源 --> 34 <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> 35 <property name="driverClassName" value="com.mysql.jdbc.Driver"></property> 36 <property name="url" value="jdbc:mysql://localhost:3306/test"></property> 37 <property name="username" value="root"></property> 38 <property name="password" value="mysql"></property> 39 <!-- <property name="initialSize" value="5"></property> 40 <property name="maxActive" value="10"></property> --> 41 </bean> 42 43 <!-- 使用SimpleJdbcTemplate --> 44 <!-- <bean id="jdbcTemplate" class="org.springframework.jdbc.core.simple.SimpleJdbcTemplate"> 45 <constructor-arg ref="dataSource"/> 46 </bean> --> 47 48 <!-- dao层 --> 49 <!-- <bean id="jdbcUserDao" class="com.springinaction.dao.impl.JdbcUserDao"> 50 <property name="jdbcTemplate" ref="jdbcTemplate"></property> 51 </bean> --> 52 53 <!-- jdbc的事务bean 事务管理器 --> 54 <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> 55 <property name="dataSource" ref="dataSource"></property> 56 </bean> 57 58 <!-- hibernate 的事务,需要一个session工厂 --> 59 <!-- <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager"> 60 <property name="sessionFactory" ref="sessionFactory"></property> 61 </bean> --> 62 63 64 <!-- 配置mybatis的sqlSessionFactory,他有三个属性,需要配置 --> 65 <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> 66 <!-- 对model包线下面的类起别名,在mapper文件中可以直接使用类名对应一个类型 --> 67 <property name="typeAliasesPackage" value="com.springinaction.model"></property> 68 <property name="dataSource" ref="dataSource"></property> 69 <!-- 配置mapper文件的位置 --> 70 <property name="mapperLocations" value="classpath:com/springinaction/dao/*Mapper.xml"></property> 71 </bean> 72 <!-- 根据sqlSessionFactory和mapper生成dao的实现:这个配置和上面一个配置,是mybatis的基础的配置, --> 73 <bean id="userDao" class="org.mybatis.spring.mapper.MapperFactoryBean"> 74 <property name="sqlSessionFactory" ref="sqlSessionFactory"></property> 75 <property name="mapperInterface" value="com.springinaction.dao.IUserDao"></property> 76 </bean> 77 78 <!-- 这里只是配置了一个事务的通知,应用在哪些方法上,以及传播行为 --> 79 <!-- <tx:advice id="txAdvice" transaction-manager="transactionManager"> 80 <tx:attributes> 81 这个add的注释掉后,就不会回滚了 82 <tx:method name="add*" propagation="REQUIRED"/> 83 <tx:method name="select*" propagation="SUPPORTS" read-only="true"/> 84 </tx:attributes> 85 </tx:advice> --> 86 87 <!-- 下面是切面的表达式,告诉在哪个类应用上面的通知 --> 88 <!-- <aop:config> 89 <aop:advisor advice-ref="txAdvice" pointcut="execution(* com.springinaction.service.IUserService.* (..))"/> 90 </aop:config> --> 91 92 <!-- 使用tx命名空间提供的tx注解驱动 --> 93 <tx:annotation-driven transaction-manager="transactionManager"/> 94 95 96 97 98 </beans>
1 package com.springinaction.service.impl; 2 3 import java.util.List; 4 5 6 7 import org.springframework.beans.factory.annotation.Autowired; 8 import org.springframework.beans.factory.annotation.Qualifier; 9 import org.springframework.stereotype.Component; 10 import org.springframework.stereotype.Service; 11 import org.springframework.transaction.annotation.Propagation; 12 import org.springframework.transaction.annotation.Transactional; 13 14 import com.springinaction.dao.IUserDao; 15 import com.springinaction.model.User; 16 import com.springinaction.service.IUserService; 17 18 @Component("userService") 19 public class UserServiceImpl implements IUserService { 20 21 @Autowired 22 @Qualifier("userDao") 23 private IUserDao userDao; 24 25 26 27 28 @Transactional(propagation=Propagation.REQUIRED)//这里是注解 29 public int addUser(User user,int id) throws Exception { 30 31 userDao.deleteOneUser(id); 32 33 //这里是测试会不会回滚的 34 // int i = 1/0; 35 36 userDao.addUser(user); 37 38 39 return 0; 40 } 41 42 public int deleteOneUser(int id) throws Exception { 43 44 return userDao.deleteOneUser(id); 45 } 46 47 public List<User> selectAllUsers() throws Exception { 48 49 return userDao.selectAllUsers(); 50 } 51 52 }
加上注解后,测试和6.4.2节里面一样的测试,可以得到同样的效果。