Spring 事务相关小计

Spring 事务相关小计

  • Spring中使用事务方式

    • 注解方式(推荐

    在方法上加@Transactional注解并根据需求设置隔离级别传播级别以及事务执行时长最大限制

    • 编程式事务

    注入TransactionTemplate 进行事务操作,分两种执行,执行时无返回值事务TransactionCallbackWithoutResult和有返回值事务TransactionCallback

    @Autowired
    private TransactionTemplate transactionTemplate;
    
    public void run(){
        
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
    		@Override
    		protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
    			jdbcTemplate.execute("INSERT INTO FOO (ID, BAR) VALUES (1, 'aaa')");
    			log.info("COUNT IN TRANSACTION: {}", getCount());
    			// 事务执行完回滚
    			transactionStatus.setRollbackOnly();
    		}
    	});
    	log.info("COUNT AFTER TRANSACTION: {}", getCount());
    
    	// execute = "success";
    	Object execute = transactionTemplate.execute((TransactionCallback) transactionStatus -> {
    		jdbcTemplate.execute("INSERT INTO FOO (ID, BAR) VALUES (1, 'aaa')");
    		log.info("COUNT IN TRANSACTION: {}", getCount());
    		// 事务执行完回滚
    		transactionStatus.setRollbackOnly();
    		return "success";
    	});
    	log.info("COUNT AFTER TRANSACTION: {}", getCount());
    
    }
    

    TransactionCallbackWithoutResult 其实是继承TransactionCallback接口的抽象类后页调用doInTransaction,只是返回null而已

  • Spring注解事务内部类相互调用

public class FooServiceImpl implements FooService {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    @Transactional(rollbackFor = RollbackException.class)
    public void insertThenRollback() throws RollbackException {
        jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('BBB')");
        throw new RollbackException();
    }

    @Override
    public void invokeInsertThenRollback() throws RollbackException {
        // 内部调用有事务的时候,是不会触发事务,因为无法触发代理类进行代理事务操作。
        insertThenRollback();
        // 解决办法:1、可以在这里直接用@Autowired声明一下自身,再调用有事务方法
        // 2、使用AopContext.currentProxy() 获取当前类的代理对象,进行调用事务方法
        ((FooService) (AopContext.currentProxy())).insertThenRollback();
        // 3、在本方法加上事务注解,不推荐
    }
    
}
  • 事务和连接池的关系

在同一个事务中,用的数据库连接池是同一个连接,而未开启事务或多个事务之间连接池用的是多个连接。

  • 事务四大特性(ACID)

    • 原子性(Atomicity)

      原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚,这和前面两篇博客介绍事务的功能是一样的概念,因此事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响。

    • 一致性(Consistency)

      一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。

      例如拿转账来说,假设用户A和用户B两者的钱加起来一共是5000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是5000,这就是事务的一致性。

    • 隔离性(Isolation)

      隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。

      即要达到这么一种效果:对于任意两个并发的事务T1和T2,在事务T1看来,T2要么在T1开始之前就已经结束,要么在T1结束之后才开始,这样每个事务都感觉不到有其他事务在并发地执行。

      关于事务的隔离性数据库提供了多种隔离级别,稍后会介绍到。

    • 持久性(Durability)

      持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。

      例如我们在使用JDBC操作数据库时,在提交事务方法后,提示用户事务操作完成,当我们程序执行完成直到看到提示后,就可以认定事务以及正确提交,即使这时候数据库出现了问题,也必须要将我们的事务完全执行完成,否则就会造成我们看到提示事务处理完毕,但是数据库因为故障而没有执行事务的重大错误。

  • 事务隔离特性

    • 隔离级别

    当多个线程都开启事务操作数据库中的数据时,数据库系统要能进行隔离操作,以保证各个线程获取数据的准确性,在介绍数据库提供的各种隔离级别之前,我们先看看如果不考虑事务的隔离性,会发生的以下几种问题:

    1、更新丢失(Lost update)

    两个事务都同时更新一行数据,一个事务对数据的更新把另一个事务对数据的更新覆盖了。比如CMS系统中,两个同时打开一篇文章进行修改,一个人先保存,另一个人后保存,后保存的就覆盖了先保存的那个人的内容,这就造成更新丢失。
    这是因为系统没有执行任何的锁操作,因此并发事务并没有被隔离开来。在并发事务处理带来的问题中,“更新丢失”通常应该是完全避免的。但防止更新丢失,并不能单靠数据库事务控制器来解决,需要应用程序对要更新的数据加必要的锁来解决,因此,防止更新丢失应该是应用的责任。

    2、脏读(Dirty reads)

    一个事务读取到了另一个事务未提交的数据操作结果。这是相当危险的,因为很可能所有的操作都被回滚。

    3、不可重复读(Non-repeatable Reads)

    一个事务对同一行数据重复读取两次,但是却得到了不同的结果。比如事务T1读取某一数据后,事务T2对其做了修改,当事务T1再次读该数据时得到与前一次不同的值。又叫虚读。

    4、幻读(Phantom Reads)

    事务在操作过程中进行两次查询,第二次查询的结果包含了第一次查询中未出现的数据或者缺少了第一次查询中出现的数据(这里并不要求两次查询的SQL语句相同)。这是因为在两次查询过程中有另外一个事务插入数据造成的。

    不可重复读的重点是修改某个记录字段,幻读的重点在于新增或者删除记录。
    对于前者, 只需要锁住满足条件的记录。对于后者, 要锁住满足条件及其相近的记录。

    “脏读”、“不可重复读”和“幻读”,其实都是数据库读一致性问题,必须由数据库提供一定的事务隔离机制来解决。
    为了避免以上情况,在标准SQL规范中,定义了4个事务隔离级别,可以逐步解决脏读、不可重复读、幻读这几类问题。

    隔离性脏读不可重复读幻读读数据一致性
    ISOLATION_READ_UNCOMMITTED (未提交读取)1最低基本,只能保证不读取物理上损坏的数据
    ISOLATION_READ_COMMITTED (已提交读取)2×语句级
    ISOLATION_REPEATABLE_READ (可重复读取)3××事务级
    ISOLATION_SERIALIZABLE (序列化)4×××最高级别,事务级

    未提交读取(Read Uncommitted)
    Spring标识:ISOLATION_READ_UNCOMMITTED。允许脏读取,但不允许更新丢失。如果一个事务已经开始写数据,则另外一个事务则不允许同时进行写操作,但允许其他事务读此行数据。该隔离级别可以通过“排他写锁”实现。

    已提交读取(Read Committed)
    Spring标识:ISOLATION_READ_COMMITTED。允许不可重复读取,但不允许脏读取。这可以通过“瞬间共享读锁”和“排他写锁”实现。读取数据的事务允许其他事务继续访问该行数据,但是未提交的写事务将会禁止其他事务访问该行。

    可重复读取(Repeatable Read)
    Spring标识:ISOLATION_REPEATABLE_READ。禁止不可重复读取和脏读取,但是有时可能出现幻读数据。这可以通过“共享读锁”和“排他写锁”实现。读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务。

    序列化(Serializable)
    Spring标识:ISOLATION_SERIALIZABLE。提供严格的事务隔离。它要求事务序列化执行,事务只能一个接着一个地执行,不能并发执行。仅仅通过“行级锁”是无法实现事务序列化的,必须通过其他机制保证新插入的数据不会被刚执行查询操作的事务访问到。

    隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大。对于多数应用程序,可以优先考虑把数据库系统的隔离级别设为Read Committed。它能够避免脏读取,而且具有较好的并发性能。尽管它会导致不可重复读、幻读和第二类丢失更新这些并发问题,在可能出现这类问题的个别场合,可以由应用程序采用悲观锁或乐观锁来控制。

    Spring默认隔离级别标识:ISOLATION_DEFAULT,表示使用后端数据库的默认隔离级别。Oracle和SQLServer使用的默认事务隔离级别是Read Committed(已提交读取),而MySQL默认事务隔离级别是Repeatable Read(可重复读取)

  • 事务传播特性

    Spring提供了7种传播特性,且默认使用PROPAGATION_REQUIRED传播级别。

    传播性描述
    PROPAGATION_REQUIRED0当前有事务就⽤当前的,没有就⽤新的
    PROPAGATION_SUPPORTS1事务可有可⽆,不是必须的
    PROPAGATION_MANDATORY2当前⼀定要有事务,不然就抛异常
    PROPAGATION_REQUIRES_NEW3⽆论是否有事务,都起个新的事务
    PROPAGATION_NOT_SUPPORTED4不⽀持事务,按⾮事务⽅式运⾏
    PROPAGATION_NEVER5不⽀持事务,如果有事务则抛异常
    PROPAGATION_NESTED6当前有事务就在当前事务⾥再起⼀个事务
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值