02-事务的属性之传播行为,隔离级别,事务的回滚

2023-12-16 16:05:55

事务的属性

Transactional注解的源码

public interface Transactional{
    @AliasFor("transactionManager")
    String value() default "";
    @ATiasFor("value")
    String transactionManager() default"";
    String[] label() default {};
    // 事务传播行为
    Propagation propagation() default Propagation.REOUIRED;
    // 事务隔离级别
    Isolation isolation() default Isolation.DEFAULT;
    // 事务超时时间 , 默认-1 , 即没有时间限制
    int timeout() default -1;
    String timeoutString() default "";
    // 只读事务(不能出现增删改的DML语句)
    boolean readOnly() default false;
    // 设置出现哪些异常回滚事务
    class<? extends Throwable>[] rollbackFor() default {};
    String[] rollbackForclassName() default {};
    // 设置出现哪些异常不回滚事务
    class<? extends Throwable>[] noRollbackFor() default {};
    String[] noRolbackForclassName() default {};
}
属性名功能
Propagation(枚举类型)设置事务传播行为
Isolation(枚举类型)设置事务隔离级别
timeout(默认值-1)设置事务超时时间(以最后一条DML语句执行所耗的时间为准) , 默认值是-1表示没有时间限制
readOnly(默认fassle)设置事务是否是只读事务,只读事务内只能出现查询语句,执行DML(增删改)语句时会报错,该特性可以提供查询的效率
rollbackFor设置出现哪些异常后回滚事务
noRollbackFor设置出现哪些异常后不回滚事务

事务传播行为

在XxxService类中的a()方法和b()方法都有事务,若a()方法在执行过程中调用了b()方法,此时就会出现事务传递的行为,所以我们要对这个行为设定一个处理方式

事务传播行为属性Propagation在Spring框架中被定义为枚举类型,一共有七种传播行为

属性值功能
REQUIRED(默认)支持当前所在的事务,如果当前没有开启事务就新建一个
SUPPORTS支持当前所在的事务,如果当前没有开启事务,就以非事务方式执行DML操作
MANDATORY支持当前所在的事务,如果当前没有开启事务将抛出一个异常
REQUIRES_NEW直接开启一个新的事务,如果当前也开启了事务则将当前事务挂起(暂停)
NOT_SUPPORTED支持以非事务方式执行DML操作,如果当前开启了事务则将当前事务挂起(暂停)
NEVER支持以非事务方式执行DML操作,如果当前开启了事务则抛出异常
NESTED如果当前开启了事务,就在当前事务里再嵌套一个完全独立的事务,嵌套的事务可以独立于外层事务进行提交或回滚
如果当前事务不存在,就像REQUIRED一样新建了一个事务,因为嵌套事务的外层事务不存在

实现: 在AccountServiceImpl1中的声明了事务的方法调用AccountServiceImpl2中声明了事务传播行为的方法

第一步: 编写Dao接口及其实现类

public interface AccountDao {
    // 保存账户信息
    int insert(Account act);
}
@Repository("accountDao")
public class AccountDaoImpl implements AccountDao {
    @Resource(name = "jdbcTemplate")
    private JdbcTemplate jdbcTemplate;

    @Override
    public int insert(Account act) {
        String sql = "insert into t_act values(?,?)";
        return jdbcTemplate.update(sql, act.getActno(), act.getBalance());
    }

}

第二步: 编写Service接口及其实现类AccountServiceImpl1AccountServiceImpl2,在业务类1中的业务方法调用业务类2中的业务方法

  • REQUIRES: 业务2处于业务1的事务中,即业务1和2在同一个事务中,若2出现了异常需要事务回滚那么1肯定也要回滚(1和2是同一个事务,1捕捉异常也没用)
  • REQUIRES_NEW: 业务类2处于自己的事务中,业务类1的事务会被挂起,若2发生了异常事务肯定回滚,但若1捕捉了2的异常那么1的事务就不会回滚(两个事务)
public interface AccountService {
    // 保存账户信息方法
    void save(Account act);
}
@Service("accountService")
public class AccountServiceImpl1 implements AccountService {
    @Resource(name = "accountDao")
    private AccountDao accountDao;
    @Resource(name = "accountService2")
    private AccountService accountService;

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void save(Account act) {
        // 调用accountDao的insert方法保存act-003账户信息
        accountDao.insert(act); 

        // 创建账户对象
        Account act2 = new Account("act-004", 1000.0);
        // 如果业务类1捕捉到了业务类2的save方法出现的异常,当前事务就不会回滚,因为业务1和业务2就不是同一个事务
        try {
            // 调用AccountServiceImpl2的save方法保存act-004账户信息
            accountService.save(act2); 
        } catch (Exception e) {

        }
        // 业务类1如果捕捉到了业务类2的save方法出现的异常,所以后续的DML操作可以继续执行...........
    }
}
@Service("accountService2")
public class AccountServiceImpl2 implements AccountService {
    @Resource(name = "accountDao")
    private AccountDao accountDao;

    @Override
    // 直接开启一个新的事务,如果当前也开启了事务则将当前事务挂起(暂停)
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void save(Account act) {
        // 调用accountDao的insert方法保存act-004账户信息
        accountDao.insert(act);
        // 模拟出现异常,虽然事务还没有结束,但由于出现了异常,当前事务就会回滚那么后续的其他DML语句就无法执行到
        String s = null;
        s.toString();
        // ...............
    }
}

第三步: 集成Log4j2日志框架(引入依赖和配置文件),把警告级别改成DEBUG级别,在日志信息中查看事务对象的创建情况

@Test
public void testPropagation() {
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
    // 获取1号service对象
    AccountService accountService = applicationContext.getBean("accountService", AccountService.class);
    Account act = new Account("act-003", 1000.0);
    accountService.save(act);
}

事务隔离级别

事务隔离级别属性Isolation在Spring框架中被定义为枚举类型,一共四种隔离级别

隔离级别描述脏读不可重复读幻读加锁读
读未提交(理论级别)(READ_UNCOMMITTED)在一个事务中可以看到其他事务未提交的修改数据,大多数的数据库隔离级别都是二档起步不加锁
读已提交 (Oracle默认级别)
(READ_COMMITTED)
在一个事务中只能看到其他事务已经提交的修改数据,这种隔离级别每次读到的都是真实数据不加锁
可重复读(MySQL默认级别)
(REPEATABLE_READ)
一个事务中多次执行相同的SELECT语句得到的是相同的结果,永远读取的都是自己刚开启事务时的数据,不管其他事务是否提交了修改数据不加锁
序列化(最高隔离级别)
SERIALIZABLE
一个事务与其他事务完全地隔离,每一次读取到的数据都是最真实的,但是所有事务只能排队执行,不支持并发所以效率最低加锁

第一步: 编写dao层接口及其实现类

public interface AccountDao {
    // 根据账号查询账户信息
    Account selectByActno(String actno);
    // 保存账户信息
    int insert(Account act);
}
@Repository("accountDao")
public class AccountDaoImpl implements AccountDao {
    @Resource(name = "jdbcTemplate")
    private JdbcTemplate jdbcTemplate;

    @Override
    public Account selectByActno(String actno) {
        String sql = "select actno, balance from t_act where actno = ?";
        Account account = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(Account.class), actno);
        return account;
    }

    @Override
    public int insert(Account act) {
        String sql = "insert into t_act values(?,?)";
        return jdbcTemplate.update(sql, act.getActno(), act.getBalance());
    }
}

第二步: 编写Service层接口及其实现类IsolationService1IsolationService2,业务类1负责查询账户,业务类2负责插入账户同时模拟延迟

  • READ_UNCOMMITTED: 表示当前事务可以读取到其他的事务没有提交的数据,没有提交就代表事务可能回滚将来数据可能不存在
  • READ_COMMITTED: 在一个事务中只能看到其他事务已经提交的修改数据读不到则报异常,这种隔离级别每次读到的都是真实数据
public interface AccountService {
    // 根据账号查询账户信息
    void getByActno(String actno);
    // 保存账户信息方法
    void save(Account act);
}         
@Service("i1")
public class IsolationService1 {
    @Resource(name = "accountDao")
    private AccountDao accountDao;

    // 当前事务可以读取到其他事务没有提交的数据
    //@Transactional(isolation = Isolation.READ_UNCOMMITTED)
    // 当前事务只能看到其他事务已经提交的修改数据读不到则报异常
    @Transactional(isolation = Isolation.READ_COMMITTED)
    public void getByActno(String actno) {
        Account account = accountDao.selectByActno(actno);
        System.out.println("查询到的账户信息:" + account);
    }
}
@Service("i2")
public class IsolationService2 {
    @Resource(name = "accountDao")
    private AccountDao accountDao;

    @Transactional
    public void save(Account act) {
        accountDao.insert(act);
        // 睡眠一会让当前事务晚点提交,在睡眠期间看其他事务能否查到当前事务未提交的数据
        try {
            Thread.sleep(1000 * 20);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

测试先执行业务类2的save方法保存一条账户数据,然后看业务类1中是否能查询到业务类2保存的账户信息

// 先调用业务类2的save方法先保存一条账户数据
@Test
public void testIsolation2(){
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
    IsolationService2 i2 = applicationContext.getBean("i2", IsolationService2.class);
    Account act = new Account("act-004", 1000.0);
    i2.save(act);
}
// 在业务类2的save方法事务还没有提交的期间内,查看业务类2保存的账户信息
@Test
public void testIsolation1(){
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
    IsolationService1 i1 = applicationContext.getBean("i1", IsolationService1.class);
    i1.getByActno("act-004");
}

事务超时

设置事务的超时时间后(默认没有时间限制),当如果该事务中执行最后一条DML语句时一共所耗的时间已经超过了规定的时间事务会选择回滚

//  -1是默认值,表示执行事务没有时间限制
@Transactional(timeout = -1)
// 表示超过10秒如果该事务中所有的DML语句还没有执行完毕的话事务会选择回滚
@Transactional(timeout = 10)

事务的超时时间是指执行最后一条DML语句时一共所耗的时间,最后一条DML语句后面的业务代码执行所耗的时间不会被计入超时时间内

@Transactional(timeout = 10) // 设置事务超时时间为10秒。
public void save(Account act) {
    accountDao.insert(act);
    // 这段睡眠时间是不会被计入事务的超时时间内的
    try {
        Thread.sleep(1000 * 15);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

如果想让整个方法的所有代码都计入超时时间的话,可以在方法最后一行添加一行无关紧要的DML语句

@Transactional(timeout = 10) // 设置事务超时时间为10秒。
public void save(Account act) {
    // 这段睡眠时间是会被计入事务的超时时间内的
    try {
        Thread.sleep(1000 * 15);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    
    // 该DML语句之前执行所耗的时间也会被计入事务的超时时间内
    accountDao.insert(act);
}

只读事务

只读事务表示在当前事务执行过程中只允许select语句执行,执行DML(增删改)语句时会报错

  • 虽然执行select语句可以不用加事务控制,但若该事务中确实没有增删改操作设置为只读事务后可以启动Spring的优化策略从而提高select语句的执行效率
// 将当前事务设置为只读事务,默认值为false
@Transactional(readOnly = true)

异常回滚事务

设置只有发生Xxx异常该异常的子类异常时事务才会回滚,发生其他异常时事务都不会回滚

// 只有发生RuntimeException异常或该异常的子类异常时事务才会回滚
@Transactional(rollbackFor = RuntimeException.class)

异常不回滚事务

设置只有发生Xxx异常该异常的子类异常时事务才不回滚,发生其他异常时事务都会回滚

// 只有发生NullPointerException异常或该异常的子类异常时事务才不回滚
@Transactional(noRollbackFor = NullPointerException.class)

文章来源:https://blog.csdn.net/qq_57005976/article/details/135032419
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。