Spring-08-声明式事务

Spring-08-声明式事务

mark

前序

1. 回顾事务

  • 事务在项目开发过程非常重要,涉及到数据的一致性的问题,不容马虎!
  • 事务管理是企业级应用程序开发中必备技术,用来确保数据的完整性和一致性。
  • 事务就是把一系列的动作当成一个独立的工作单元,这些动作要么全部完成,要么全部不起作用。

事务的ACID原则:

  1. 原子性(Atomicity):事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成,要么完全不起作用。

  2. 一致性(Consistency):一旦事务完成(不管成功还是失败),系统必须确保它所建模的业务处于一致的状态,而不会是部分完成部分失败。在现实中的数据不应该被破坏。

  3. 隔离性(Isolation):可能有许多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。

  4. 持久性(Durability):一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响,这样就能从任何系统崩溃中恢复过来。通常情况下,事务的结果被写到持久化存储器中。

2. 普通事务测试

将上一篇的代码拷贝到一个新的项目中

  1. 在之前的案例中,我们给userDao接口新增两个方法,删除和增加用户;
1
2
3
4
5
//添加一个用户
int addUser(User user);

//根据id删除用户
int deleteUser(int id);
  1. mapper文件,我们故意把deletes 写错,测试!
1
2
3
4
5
6
7
<insert id="addUser" parameterType="com.kuang.pojo.User">
insert into user (id,name,pwd) values (#{id},#{name},#{pwd})
</insert>

<delete id="deleteUser" parameterType="int">
deletes from user where id = #{id}
</delete>
  1. 编写接口的实现类,在实现类中,我们去操作一波
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class UserDaoImpl extends SqlSessionDaoSupport implements UserMapper {

//增加一些操作
public List<User> selectUser() {
User user = new User(4,"小明","123456");
UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
mapper.addUser(user);
mapper.deleteUser(4);
return mapper.selectUser();
}

//新增
public int addUser(User user) {
UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
return mapper.addUser(user);
}
//删除
public int deleteUser(int id) {
UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
return mapper.deleteUser(id);
}
}
  1. 测试:
1
2
3
4
5
6
7
@Test
public void test2(){
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
UserMapper mapper = (UserMapper) context.getBean("userDao");
List<User> user = mapper.selectUser();
System.out.println(user);
}
  • 这时候发现报错:sql异常,delete写错了

  • 结果:插入成功,并没有回滚

    • 没有进行事务的管理;我们想让他们都成功才成功,有一个失败,就都失败,我们就应该需要事务!
    • 以前我们都需要自己手动管理事务,十分麻烦!
    • 但是Spring给我们提供了事务管理,我们只需要配置即可;

3. Spring 事务管理

  • Spring在不同的事务管理API之上定义了一个抽象层,使得开发人员不必了解底层的事务管理API就可以使用Spring的事务管理机制。
  • Spring支持编程式事务管理和声明式的事务管理。

3.1 编程式事务管理

  • 编程式事务管理

mark

  • 将事务管理代码嵌到业务方法中来控制事务的提交和回滚

  • 缺点:必须在每个事务操作业务逻辑中包含额外的事务管理代码

  • 编程式事务管理是侵入性事务管理,使用TransactionTemplate或者直接使用PlatformTransactionManager,对于编程式事务管理,Spring推荐使用TransactionTemplate

3.2 声明式事务管理

  • 声明式事务管理(只用这个,基本配置也是固定的)
    • 一般情况下比编程式事务好用。
    • 将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。
    • 声明式事务管理建立在AOP之上其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,执行完目标方法之后根据执行的情况提交或者回滚。
  1. 使用Spring管理事务,注意头文件的约束导入tx
1
2
3
4
xmlns:tx="http://www.springframework.org/schema/tx"

http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
  1. 事务管理器
  • 无论使用Spring的哪种事务管理策略(编程式或者声明式)事务管理器都是必须的。
  • 就是 Spring的核心事务管理抽象,管理封装了一组独立于技术的方法。
1
2
3
4
<!--配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
  1. 配置好事务管理器后我们需要去配置事务的通知
1
2
3
4
5
6
7
8
9
10
11
12
<!--配置事务通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
// 给哪些方法配置事务
<tx:method name="add" propagation="REQUIRED"/>
<tx:method name="delete" propagation="REQUIRED"/>
<tx:method name="update" propagation="REQUIRED"/>
<tx:method name="search*" propagation="REQUIRED"/>
<tx:method name="get" read-only="true"/>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
  • 编程式事务每次实现都要单独实现,但业务量大功能复杂时,使用编程式事务无疑是痛苦的
  • 而声明式事务不同,声明式事务属于无侵入式,不会影响业务逻辑的实现,只需要在配置文件中做相关的事务规则声明或者通过注解的方式,便可以将事务规则应用到业务逻辑中。
  • 显然声明式事务管理要优于编程式事务管理,这正是Spring倡导的非侵入式的编程方式。
  • 唯一不足的地方就是声明式事务管理的粒度是方法级别,而编程式事务管理是可以到代码块的,但是可以通过提取方法的方式完成声明式事务管理的配置。

3.3 事务的传播机制

  • 事务的传播性一般用在事务嵌套的场景,比如一个事务方法里面调用了另外一个事务方法
  • 那么两个方法是各自作为独立的方法提交还是内层的事务合并到外层的事务一起提交,这就是需要事务传播机制的配置来确定怎么样执行。

spring事务传播特性:

事务传播行为就是多个事务方法相互调用时,事务如何在这些方法间传播。

spring支持7种事务传播行为:

  • propagation_requierd:如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中,这是最常见的选择。
  • propagation_supports:支持当前事务,如果没有当前事务,就以非事务方法执行。
  • propagation_mandatory:使用当前事务,如果没有当前事务,就抛出异常。
  • propagation_required_new:新建事务,如果当前存在事务,把当前事务挂起。
  • propagation_not_supported:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
  • propagation_never:以非事务方式执行操作,如果当前事务存在则抛出异常。
  • propagation_nested:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与propagation_required类似的操作

  • Spring 默认的事务传播行为是 PROPAGATION_REQUIRED,它适合于绝大多数的情况。
1
2
3
4
假设 ServiveX#methodX() 都工作在事务环境下(即都被 Spring 事务增强了),
假设程序中存在如下的调用链:
Service1#method1()->Service2#method2()->Service3#method3(),
那么这 3 个服务类的 3 个方法通过 Spring 的事务传播机制都工作在同一个事务中。
  • 就好比,我们刚才的几个方法存在调用,所以会被放在一组事务当中!
  1. 事务的传播特性结束后,需要配置事务aop的切入
  • 导入aop的头文件

  • aop 织入

1
2
3
4
5
<!--配置aop织入事务-->
<aop:config>
<aop:pointcut id="txPointcut" expression="execution(* com.zhuuu.dao.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>
  1. 测试
  • 删掉刚才插入的数据,再次测试!
1
2
3
4
5
6
7
@Test
public void test2(){
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
UserMapper mapper = (UserMapper) context.getBean("userDao");
List<User> user = mapper.selectUser();
System.out.println(user);
}

结果发现:因为插入失败,所以事务进行了回滚!!

3.4 传播特性中的 read-only

  • 如果一个事务只对数据库执行读操作,那么该数据库就可能利用那个事务的只读特性,采取某些优化措施。
  • 通过把一个事务声明为只读,可以给后端数据库一个机会来应用那些它认为合适的优化措施。由于只读的优化措施是在一个事务启动时由后端数据库实施的,
  • 因此,只有对于那些具有可能启动一个新事务的传播行为(PROPAGATION_REQUIRES_NEW、PROPAGATION_REQUIRED、 ROPAGATION_NESTED)的方法来说,将事务声明为只读才有意义。

3.5 传播特性中的 超时

  • 为了使一个应用程序很好地执行,它的事务不能运行太长时间。因此,声明式事务的下一个特性就是它的超时。
  • 假设事务的运行时间变得格外的长,由于事务可能涉及对数据库的锁定,所以长时间运行的事务会不必要地占用数据库资源。这时就可以声明一个事务在特定秒数后自动回滚,不必等它自己结束。
  • 由于超时时钟在一个事务启动的时候开始的,因此,只有对于那些具有可能启动一个新事务的传播行为(PROPAGATION_REQUIRES_NEW、PROPAGATION_REQUIRED、ROPAGATION_NESTED)的方法来说,声明事务超时才有意义。

4. 回滚的原则

  • 在默认设置下,事务只在出现运行时异常(runtime exception)时回滚,而在出现受检查异常(checked exception)时不回滚(这一行为和EJB中的回滚行为是一致的)。
  • 不过,可以声明在出现特定受检查异常时像运行时异常一样回滚。同样,也可以声明一个事务在出现特定的异常时不回滚,即使特定的异常是运行时异常。

5. 配置参考

事物配置中有哪些属性可以配置?以下只是简单的使用参考

  1. 事务的传播性:
    @Transactional(propagation=Propagation.REQUIRED)
  2. 事务的隔离级别:
    @Transactional(isolation = Isolation.READ_UNCOMMITTED)

读取未提交数据(会出现脏读, 不可重复读) 基本不使用

  1. 只读:
    @Transactional(readOnly=true)
    该属性用于设置当前事务是否为只读事务,设置为true表示只读,false则表示可读写,默认值为false。
  2. 事务的超时性:
    @Transactional(timeout=30)
  3. 回滚:
    指定单一异常类:@Transactional(rollbackFor=RuntimeException.class)
    指定多个异常类:@Transactional(rollbackFor={RuntimeException.class, Exception.class})

该属性用于设置需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,则进行事务回滚。

参考文档http://mybatis.org/spring/zh/transactions.html

https://www.cnblogs.com/mseddl/p/11577846.html

打赏
  • 版权声明: 本博客所有文章除特别声明外,均采用 Apache License 2.0 许可协议。转载请注明出处!
  • © 2019-2022 Zhuuu
  • PV: UV:

请我喝杯咖啡吧~

支付宝
微信