Spring 事务管理

一、简述

事务是逻辑上的一组操作,要么都执行,要么都不执行。它具有4个属性:

  • 原子性: 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;
  • 一致性: 执行事务前后,数据保持一致;
  • 隔离性: 并发访问数据库时,一个用户的事物不被其他事务所干扰也就是说多个事务并发执行时,一个事务的执行不应影响其他事务的执行;
  • 持久性: 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。

二、事务五大属性

1. 传播行为

​ 传播行为(propagation behavior)定义了客户端与被调用方法之间的事务边界。即何时要创建一个事务,或者何时使用已有的事务:

传播行为 含义
PROPAGATION_MANDATORY 表示该方法必须在事务中进行,如果当前事务不存在,则会 抛出一个异常
PROPAGATION_NESTED 如果当前已存在一个事务,那么该方法在嵌套事务中运行。 嵌套事务可以独立于当前事务进行单独地提交或回滚。如 果当前事务不存在,那么行为和 PROPAGATION_REQUIRED一样
PROPAGATION_NEVER 表示当前方法不运行在事务上下文中。如果当前正有一个事务在运行,则会抛出异常
PROPAGATION_NOT_SUPPORTED 表示该方法不应该运行在事务上下文中,如果存在当前事务,在该方法运行期间,当前事务会被挂起
PROPAGATION_REQUIRED 表示当前方法必须运行在事务中,如果事务不存在,则启动一个新的事务
PROPAGATION_REQUIRED_NEW 表示当前方法必须运行在它自己的事务中。一个新的事务将启动。如果存在当前事务,当前事务会被挂起
PROPAGATION_SUPPORTS 表示当前方法不需要事务上下文,但是如果存在当前事务的话,那么该方法会在这个事务中运行

2. 隔离级别

隔离级别(isolation level)定义了一个事务可能受其他并发事务的影响程度。并发操作相同的数据可能产生一些问题:

  1. 脏读(Dirty reads)—— 发生在一个事务读取了另一个事务改写后但未提交的数据,如果改写被回滚了,那么第一个事务获取的数据就是“脏”的。

  2. 不可重复读(Nonrepeatable read)—— 一个事务执行两次以上相同查询得到不同的数据。这通常是另外一个事务在此期间更新了数据。

  3. 幻读(Phantom read)—— 一个事务读取了几行数据,另一个事务插入了几条数据,当第一个事务再次读取时发现多了几条原本没有的数据。

隔离级别如下表所示:

隔离级别 含义
ISOLATION_DEFAULT 使用后端数据库默认的隔离级别
ISOLATION_READ_UNCOMMITTED 允许读取尚未提交的数据表更。可能导致脏读,不可重复读,幻读
ISOLATION_READ_COMMITTED 允许读取并发事务已经提交的数据,可以阻止脏读,但不可重复读,幻读仍可能发生
ISOLATION_REPEATABLE_READ 对同一字段的多次读取结果一致,除非数据是本事务自己修改的。可以阻止脏读,不可重复读,但仍可能发生幻读
REPEATABLE_SERIALIZABLE 完全服从事务的ACID原则,避免脏读,不可重复读,幻读

可以看出,ISOLATION_READ_UNCOMMITTED隔离级别是最低的,可能导致脏读,不可重复读,幻读。REPEATABLE_SERIALIZABLE隔离级别最高,但是这会降低数据读取速率,它通常是通过完全锁定事务相关的数据库表来实现的。

3. 事务只读

只读(read-only) 如果事务只读数据库进行读操作,那么设置该属性为true可以给数据库执行优化措施。因为只读是在事务启动,由数据库实施的,所以对那些具备启动一个新的事务的传播行为(PROPAGATION_REQUIREDPROPAGATION_REQUIRED_NEWPROPAGATION_NESTED)才有意义。

设置只读还会使Hibernate的flush模式被设置为FLUSH_NEVER。

4. 事务超时

事务超时是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。假如事务运行时间过长,则会影响效率,所以可以设置超时属性,超时后执行自动回滚。因为超时时钟会在事务开启时启动,所以只有对那些具备启动一个新的事务的传播行为(PROPAGATION_REQUIREDPROPAGATION_REQUIRED_NEWPROPAGATION_NESTED)才有意义。

5. 回滚原则

默认情况遇到运行期异常就回滚。回滚原则可以定义遇到哪些异常不回滚。这些规则定义了哪些异常会导致事务回滚而哪些不会。默认情况下,事务只有遇到运行期异常(RuntimeException 的子类)时才会回滚,Error 也会导致事务回滚。

三、XML中定义事务

使用tx命名空间配置:

<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
         <!--指定哪种规则的方法上面添加事务-->
        <tx:method name="add*" propagation="REQUIRED"/>
        <tx:method name="*" propagation="SUPPORTS" read-only="true"/>
    </tx:attributes>
</tx:advice>

transaction-manager为上述的事务管理器。

<tx:method>元素为某个(某些)name属性指定的方法定义事务参数。

<tx:method>有多个属性来帮助定义方法的事务策略,这些属性对于上述的事务五大属性:

属性 含义
isolation 指定隔离级别
propagation 指定传播原则
read-only 指定事务为只读
回滚原则: rollback-for no-rollback-for rollback-for指定事务为哪些检查型异常回滚 no-rollback-for指定事务对哪些异常继续运行而不回滚
timeout 设定事务超时时间

我们还需设定哪些Bean应该被通知,使用aop定义一个通知器(advisor):

<aop:config>
    <aop:advisor
        pointcut="execution(* *..UserDaoImpl.*(..))"	
        advice-ref="txAdvice"/>
</aop:config>

advice-ref属性引用了名为txAdvice的通知。

现在测试添加事务后的addUser()方法:

public class TestTransaction {
   
    public static void main(String[] args) {
   
        ApplicationContext ac
            = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserDao dao=ac.getBean("userDao",UserDao.class);
        User u=new User();
        u.setName("testTx");
        u.setAge("30");
        u.setPassword("123456");
        dao.addUser(u);
    }
}

控制台抛出异常:

Exception in thread "main" java.lang.NullPointerException
	at com.spring.dao.UserDaoImpl.addUser(UserDaoImpl.java:51)

查询数据:

SQL> select * from lzp.userinfo;
 
USERI NAME       AGE PWD
----- ---------- --- ----------
8     testUser   24  123456
4     SCOTT      25  123456

可以发现testTx并没有被插入。

四、注解定义事务

除了使用XML定义事务,我们还可以注解事务,通过声明:

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

<tx:annotation-driven>告诉Spring检查上下文中所有使用@Transaction注解的Bean,不管这个注解是在类级别上还是方法级别上。

使用注解修改UserDaoImpl:

@Repository("userDao")
@Transactional(propagation=Propagation.SUPPORTS,readOnly=true)
public class UserDaoImpl implements UserDao {
   
    //...
    @Transactional(propagation=Propagation.REQUIRED,readOnly=false)
    public void addUser(User user) {
   
        Map<String, Object> params=new HashMap<String, Object>();
        params.put("name", user.getName());
        params.put("age", user.getAge());
        params.put("pwd", user.getPassword());
        jdbcTemplate.update(SQL_INSERT_USER, params);
        String a=null;
        a.toString();
    }
    //...
}

再次运行testTransaction()方法,抛出异常后查询数据库:

SQL> select * from lzp.userinfo;
 
USERI NAME       AGE PWD
----- ---------- --- ----------
8     testUser   24  123456
4     SCOTT      25  123456

可发现和XML配置事务效果是一样的。

相关推荐

  1. Spring 事务管理

    2023-12-07 09:44:05       44 阅读
  2. Spring事务管理

    2023-12-07 09:44:05       22 阅读
  3. spring 笔记十 Spring事务管理

    2023-12-07 09:44:05       42 阅读
  4. Spring事务管理Spring AOP详解

    2023-12-07 09:44:05       6 阅读

最近更新

  1. TCP协议是安全的吗?

    2023-12-07 09:44:05       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2023-12-07 09:44:05       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2023-12-07 09:44:05       18 阅读
  4. 通过文章id递归查询所有评论(xml)

    2023-12-07 09:44:05       20 阅读

热门阅读

  1. 如何写好PPT报告

    2023-12-07 09:44:05       53 阅读
  2. Qt5.15.2的镜像网址

    2023-12-07 09:44:05       44 阅读
  3. flink获取kafka的key value timestamp header

    2023-12-07 09:44:05       41 阅读
  4. 图论|684.冗余连接 685. 冗余连接 II

    2023-12-07 09:44:05       43 阅读
  5. React useCallback 详解

    2023-12-07 09:44:05       34 阅读
  6. Redis Reactor事件驱动模型源码

    2023-12-07 09:44:05       46 阅读
  7. Nginx之Openresty缓存解读

    2023-12-07 09:44:05       39 阅读
  8. Redis中持久化策略RDB与AOF优缺点对比

    2023-12-07 09:44:05       40 阅读
  9. 华纳云:有效解决服务器宕机的办法

    2023-12-07 09:44:05       42 阅读
  10. OpenGL中使用链表实现透明度排序

    2023-12-07 09:44:05       36 阅读
  11. MySql数据库优化的八种方式

    2023-12-07 09:44:05       35 阅读
  12. 数据结构——堆(存储完全二叉树)

    2023-12-07 09:44:05       41 阅读