什么是事务,以及事务的配置和使用

一.怎么在Spring boot中配置事务

1.我们可以去写一个事务配置类:

注意:事务可以加在类上也可以加在方法上面

1.1就是利用切面编程AOP,在指定类上添加事务,

//@Configuration表明这是一个配置类
@Configuration
//@Aspect:作用是把当前类标识为一个切面供容器读取
@Aspect
public class TransactionAdviceConfig extends BaseConfig {
    //声明一个常量,需要拦截的方法-用切点语言来写
    private static final String AOP_POINTCUT_EXPRESSION = "execution(* com.gongrongfei.allDemo..service.*Service.*(..))";

    @Autowired
    private PlatformTransactionManager transactionManager;

    @Bean
    public TransactionInterceptor txAdvice() {

        RuleBasedTransactionAttribute txAttr_REQUIRED = new RuleBasedTransactionAttribute();
        //设置事务回滚规则
        txAttr_REQUIRED.setRollbackRules(Collections.singletonList(new RollbackRuleAttribute(Exception.class)));
        //设置事务的传播行为,TransactionDefinition.PROPAGATION_REQUIRED
        //当前有事务,就加入这个事务,没有事务,就新建一个事务
        txAttr_REQUIRED.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        //设置隔离级别,可重复读取
        //可重复读取是指在一个事务内,多次读同一个数据,在这个事务还没结束时,其他事务不能访问该数据(包括了读写),这样就可以在同一个事务内两次读到的数据是一样的,因此称为是可重复读隔离级别
        txAttr_REQUIRED.setIsolationLevel(TransactionDefinition.ISOLATION_REPEATABLE_READ);

        NameMatchTransactionAttributeSource source = new NameMatchTransactionAttributeSource();

        source.addTransactionalMethod("*", txAttr_REQUIRED);

        return new TransactionInterceptor(transactionManager, source);
    }

    @Bean
    public Advisor txAdviceAdvisor() {
        //声明一个aspectj切点
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        //设置需要拦截的方法
        pointcut.setExpression(AOP_POINTCUT_EXPRESSION);
        //在该切点,执行txAdvice()
        return new DefaultPointcutAdvisor(pointcut, txAdvice());
    }

}

 1,2让我们看看怎么定义切点:

 

1.3我们可以在指定的方法上面加上事务:

//@Configuration表明这是一个配置类
@Configuration
//@Aspect:作用是把当前类标识为一个切面供容器读取
@Aspect
public class TransactionAdviceConfig extends BaseConfig {
    //声明一个常量,需要拦截的方法-用切点语言来写
    private static final String AOP_POINTCUT_EXPRESSION = "execution(* com.gongrongfei.allDemo.service.*.*(..))";


    @Resource
    private DataSource dataSource;


    @Bean("txManager")
    public DataSourceTransactionManager txManager() {
        return new DataSourceTransactionManager(dataSource);
    }


    @Bean("txAdvice")
    public TransactionInterceptor txAdvice() {

        RuleBasedTransactionAttribute txAttr_REQUIRED = new RuleBasedTransactionAttribute();
        //设置进行回滚的规则
        txAttr_REQUIRED.setRollbackRules(Collections.singletonList(new RollbackRuleAttribute(Exception.class)));
        //设置事务的传播行为,TransactionDefinition.PROPAGATION_REQUIRED
        //当前有事务,就加入这个事务,没有事务,就新建一个事务
        txAttr_REQUIRED.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        //设置隔离级别,可重复读取
        //可重复读取是指在一个事务内,多次读同一个数据,在这个事务还没结束时,其他事务不能访问该数据(包括了读写),这样就可以在同一个事务内两次读到的数据是一样的,因此称为是可重复读隔离级别
        txAttr_REQUIRED.setIsolationLevel(TransactionDefinition.ISOLATION_REPEATABLE_READ);
        //新建一个map
        Map<String, TransactionAttribute> txMap = new HashMap<>();
        //在你要在指定的方法上面放上事务
        //txMap.put("add*", txAttr_REQUIRED);
        //txMap.put("save*", txAttr_REQUIRED);

        //txMap.put("insert*", txAttr_REQUIRED);

        txMap.put("update*", txAttr_REQUIRED);
        //txMap.put("delete*", txAttr_REQUIRED);
        //txMap.put("get*", txAttr_REQUIRED);
        //txMap.put("query*", txAttr_REQUIRED);

        NameMatchTransactionAttributeSource source = new NameMatchTransactionAttributeSource();
        source.setNameMap(txMap);
        return new TransactionInterceptor(txManager(), source);
    }

    @Bean
    public Advisor txAdviceAdvisor() {
        //声明一个aspectj切点
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        //设置需要拦截的方法
        pointcut.setExpression(AOP_POINTCUT_EXPRESSION);
        //在该切点,执行通知
        return new DefaultPointcutAdvisor(pointcut, txAdvice());
    }

}

根据上面的配置,你们可以试一下:

@Service
//@Transactional(propagation = Propagation.REQUIRED)
public class UserService {
    //将UserDao注入进来
    @Resource
    private UserDao userDao;
    @Resource
    private DeptService deptService;


    public int insertUser(User user) {
        int n = 0;
        n = userDao.insertUser(user);
      if(true){
            throw new RuntimeException();
        }
        return n;
    }
    public void updateUser(String userName){
        userDao.updateUser(userName);
        if(true){
            throw new RuntimeException();
        }
    }


}

根据上面的事务配置,两个方法都在同一个UserService层,但inserUser不支持事务,updateUser支持事务。

2,使用@Transaction注解,在Spring的@Transaction中,有两个重要的属性:Propagation,指的是事务方法之间发生嵌套调用时,事务的传播行为(当前调用的这个方法的事务,和当前的其他事务之间的关系),Isolation:指的是若干个并发的事务之间的隔离程度,如图。

3.我们也可以用配置xml的方式去配置注解,但是,spring boot 就是为了减少xml的配置,我建议利用,配置事务类来添加事务,利用注解给每个类或方法上面加上注解,太累了

二,我用实列看看事务到底有什么作用,这里我用注解的方法进行演示:

第一个测试:

这是user表:

这是dept_table表:

我们在UserService加上事务注解,在UserService层里面调用DpetService:

@Service
@Transactional(propagation = Propagation.REQUIRED)
public class UserService {
    //将UserDao注入进来
    @Resource
    private UserDao userDao;
    @Resource
    private DeptService deptService;


    public int insertUser(User user) {
        int n = 0;
        n = userDao.insertUser(user);
        if (n > 0) {
            System.out.println("进来了");
            deptService.inserDept(user.getId());
        }
        return n;
    }


}

在DeptService上不加事务注解,但是到DeptService层里面会报错:

@Service
//@Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.REPEATABLE_READ)
public class DeptService {
    @Resource
    private DeptDao deptDao;
    public void inserDept(String userId){
        Dept dept=new Dept();
        dept.setId("11111");
        dept.setUser_id(userId);
        dept.setDept_manager("魔笑");
        dept.setDept_name("开发部门");
        dept.setDept_description("格子衫");
        deptDao.insertDept(dept);
        if(true){
            throw new RuntimeException();
        }
    }
}

用postman测试:

显然user表已经执行了插入,到deptService层报错,结果是都进行了回滚,两个表都没有数据插入:

第二个测试:

如果我们把UserService层事务注解去掉:

@Service
//@Transactional(propagation = Propagation.REQUIRED)
public class UserService {
    //将UserDao注入进来
    @Resource
    private UserDao userDao;
    @Resource
    private DeptService deptService;


    public int insertUser(User user) {
        int n = 0;
        n = userDao.insertUser(user);
        if (n > 0) {
            System.out.println("进来了");
            deptService.inserDept(user.getId());
        }
        return n;
    }


}

我们把deptService加上注解,还是在DeptService报错:

@Service
@Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.REPEATABLE_READ)
public class DeptService {
    @Resource
    private DeptDao deptDao;
    public void inserDept(String userId){
        Dept dept=new Dept();
        dept.setId("11111");
        dept.setUser_id(userId);
        dept.setDept_manager("魔笑");
        dept.setDept_name("开发部门");
        dept.setDept_description("格子衫");
        deptDao.insertDept(dept);
        if(true){
            throw new RuntimeException();
        }
    }
}

我们继续用postman测试:

同样的错误:

结果user表有数据:

dept_table表里面没有数据:

第三个测试:

我们把错误放在userService层:

@Service
//@Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.READ_UNCOMMITTED)
public class UserService {
    //将UserDao注入进来
    @Resource
    private UserDao userDao;
    @Resource
    private DeptService deptService;

    public List<User> selectUser(){

        return userDao.selectUser();
    }

    public int insertUser(User user) {
        int n = 0;

        n = userDao.insertUser(user);
        if(n>0){
            deptService.inserDept(user.getId());
        }

        if(true){
            throw new RuntimeException();
        }
        return n;
    }

}

我们用postman测试后,两个表里面都有数据。

接着我又增加了一个方法,继续测试了一番,这里我就不列举出来了

总结出:

1.当A方法调用BC方法时,A方法没有事务,调用的BC方法有事务,并且报错是在BC方法里面时,对应的方法才会回滚,如果报错是在A方法,那么ABC都不会回滚。

2.当A方法调用BC方法时,A方法有事务,不论BC有没有事务,出现错误,都会进行回滚

下面网上应该都有,为了方便我以后回顾,和想知道的小伙伴,我把它列举了出来

三.让我们看看什么是事务

事务:是数据库操作的最小工作单元,是作为单个逻辑工作单元执行的一系列操作;这些操作作为一个整体一起向系统提交,要么都执行、要么都不执行;事务是一组不可再分割的操作集合(工作逻辑单元)

四.事务的四大特性:

1 、原子性 
事务是数据库的逻辑工作单位,事务中包含的各操作要么都做,要么都不做 
2 、一致性 
事 务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态。因此当数据库只包含成功事务提交的结果时,就说数据库处于一致性状态。如果数据库系统 运行中发生故障,有些事务尚未完成就被迫中断,这些未完成事务对数据库所做的修改有一部分已写入物理数据库,这时数据库就处于一种不正确的状态,或者说是 不一致的状态。 
3 、隔离性 
一个事务的执行不能其它事务干扰。即一个事务内部的操作及使用的数据对其它并发事务是隔离的,并发执行的各个事务之间不能互相干扰。 
4 、持续性 
也称永久性,指一个事务一旦提交,它对数据库中的数据的改变就应该是永久性的。接下来的其它操作或故障不应该对其执行结果有任何影响。

五,事务的传播行为

在TransactionDefinition中定义了7种事务的传播行为

事务的默认值:REQUIRED

1.当前有事务,就加入这个事务,没有事务,就新建一个事务

REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),

2.当前有事务,就加入这个事务,没有事务,就以非事务的方式执行

SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),

3.当前有事务,就加入这个事务,没有事务,就抛出异常

MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),

4.新建一个事务执行,如果当前有事务,就把当前的事务挂起

REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),

5.在无事务状态下执行,如果当前有事务,就把当前的事务挂起

NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED),

6.在无事务状态下执行,如果当前有事务,会抛出异常

NEVER(TransactionDefinition.PROPAGATION_NEVER),

7.当前有事务,就新建一个事务,嵌套执行,当前无事务,就新建一个事务执行

NESTED(TransactionDefinition.PROPAGATION_NESTED);

六.事务的隔离级别

隔离级别是指若干个并发的事务之间的隔离程度,与我们开发时候主要相关的场景包括:脏读取、重复读、幻读。

我们一般选择:Repeatable read(可重复读取)

第一种隔离级别:Read uncommitted(读未提交:解决了更新丢失,但还是可能会出现脏读)

如果一个事务已经开始写数据,则另外一个事务不允许同时进行写操作,但允许其他事务读此行数据,该隔离级别可以通过“排他写锁”,但是不排斥读线程实现。这样就避免了更新丢失,却可能出现脏读,也就是说事务B读取到了事务A未提交的数据

 

第二种隔离级别:Read committed(读提交:解决了更新丢失和脏读问题)

如果是一个读事务(线程),则允许其他事务读写,如果是写事务将会禁止其他事务访问该行数据,该隔离级别避免了脏读,但是可能出现不可重复读。事务A事先读取了数据,事务B紧接着更新了数据,并提交了事务,而事务A再次读取该数据时,数据已经发生了改变。

第三种隔离级别:Repeatable read(可重复读取:解决了更新丢失、脏读、不可重复读、但是还会出现幻读)

可重复读取是指在一个事务内,多次读同一个数据,在这个事务还没结束时,其他事务不能访问该数据(包括了读写),这样就可以在同一个事务内两次读到的数据是一样的,因此称为是可重复读隔离级别,读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务(包括了读写),这样避免了不可重复读和脏读,但是有时可能会出现幻读。(读取数据的事务)可以通过“共享读镜”和“排他写锁”实现。

第四种隔离级别:Serializable(可序化)

提供严格的事务隔离,它要求事务序列化执行,事务只能一个接着一个地执行,但不能并发执行,如果仅仅通过“行级锁”是无法实现序列化的,必须通过其他机制保证新插入的数据不会被执行查询操作的事务访问到。序列化是最高的事务隔离级别,同时代价也是最高的,性能很低,一般很少使用,在该级别下,事务顺序执行,不仅可以避免脏读、不可重复读,还避免了幻读

以上就是我对事务的理解,希望对你有帮助

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值