Administrator
发布于 2022-07-03 / 0 阅读
0
0

spring事务

spring事务

事务的传播类型

该属性用于设置底层数据库的事务隔离级别,事务的隔离级别介绍:

@Transactional注解可以作用于接口、接口方法、类以及类方法上,它可以通过不同的参数来选择什

么类型Exception异常下执行回滚或者不回滚操作。

//如果有事务, 那么加入事务, 没有的话新建一个(默认)

1

@Transactional(propagation=Propagation.REQUIRED)

2

//容器不为这个方法开启事务

3

@Transactional(propagation=Propagation.NOT_SUPPORTED)

4

//不管是否存在事务, 都创建一个新的事务, 原来的挂起, 新的执行完毕, 继续执行老的事务

5

@Transactional(propagation=Propagation.REQUIRES_NEW)

6

//必须在一个已有的事务中执行, 否则抛出异常

7

@Transactional(propagation=Propagation.MANDATORY)

8

//必须在一个没有的事务中执行, 否则抛出异常(与Propagation.MANDATORY相反)

9

@Transactional(propagation=Propagation.NEVER)

10

//如果其他bean调用这个方法, 在其他bean中声明事务, 那就用事务, 如果其他bean没有声明事务, 那就不用

事务

11

@Transactional(propagation=Propagation.SUPPORTS)

12

isolation

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

1

@Transactional(isolation = Isolation.READ_UNCOMMITTED)

2

// 读取已提交数据(会出现不可重复读和幻读) Oracle默认

3

@Transactional(isolation = Isolation.READ_COMMITTED)

4

// 可重复读(会出现幻读) MySQL默认

5

@Transactional(isolation = Isolation.REPEATABLE_READ)

6

// 串行化

7

@Transactional(isolation = Isolation.SERIALIZABLE)

8


9

@Transactionnal注解属性

如果事务方法所在的类没有注册到Spring IOC容器中,也就是说,事务方法所在类并没有被Spring管

理,则Spring事务会失效,举个例子 :

参数

说明

rollbackFor

用于指定必须执行事务回滚的异常类型,可以是一

个也可以是多个,当通过rollbackFor指定对应异常

之后,方法执行过程中只有抛出该类型异常,才会

触发事务的回滚,比如:

@Transactional(rollbackFor =

BusinessException.class)

rollbackForClassName

与rollbackFor功能相同,可以为完全限定类名的字

符串类型,比如:

@Transactional(rollbackForClass =

{"BusinessException.class",

"RuntimeException.class"})

noRollbackFor

用于指定不需要执行事务回滚的异常类型,可以是

一个也可以是多个,当通过noRollbackFor指定对

应异常之后,方法执行过程中抛出该类型异常,不

触发事务的回滚

noRollbackForClassName

与noRollbackFor功能相同,可以为完全限定类名

的字符串类型

propagation

用户设置事务的传播行为,例如:

@Transactional(propagation=Propagation.REQUI

RED)

Spring事务失效的场景

1. 事务方法未被Spring管理

/**

1

  • 商品业务实现层
  • 2

    *

    3

  • @author: austin
  • 4

  • @since: 2023/2/10 14:19
  • 5

    */

    6

    public class ProductServiceImpl extends ServiceImpl implements

    IProductService {

    7


    8

    @Autowired

    9

    private ProductMapper productMapper;

    10


    11

    @Override

    12

    ProductServiceImpl实现类上没有添加@Service注解,Product的实例也就没有被加载到Spring

    IOC容器,此时updateProductStockById()方法的事务就会在Spring中失效。

    有时候,某个方法不想被子类重新,这时可以将该方法定义成final的。普通方法这样定义是没问题

    的,但如果将事务方法定义成final,例如:

    OrderServiceImpl的cancel取消订单方法被final修饰符修饰,Spring事务底层使用了AOP,也就

    是通过JDK动态代理或者cglib,帮我们生成了代理类,在代理类中实现的事务功能。但如果某个方法用

    final修饰了,那么在它的代理类中,就无法重写该方法,从而无法添加事务功能。这种情况事务就会

    在Spring中失效。

    Tips: 如果某个方法是static的,同样无法通过动态代理将方法声明为事务方法。

    如果事务方式不是public修饰,此时Spring事务会失效,举个例子 :

    @Transactional(propagation = Propagation.REQUIRES_NEW)

    13

    public void updateProductStockById(Integer stockCount, Long productId) {

    14

    productMapper.updateProductStockById(stockCount, productId);

    15

    }

    16

    }

    17

    2. 方法使用final类型修饰

    @Service

    1

    public class OrderServiceImpl {

    2


    3

    @Transactional

    4

    public final void cancel(OrderDTO orderDTO) {

    5

    // 取消订单

    6

    cancelOrder(orderDTO);

    7

    }

    8

    }

    9

    3. 非public修饰的方法

    /**

    1

  • 商品业务实现层
  • 2

    *

    3

  • @author: austin
  • 4

  • @since: 2023/2/10 14:19
  • 5

    虽然ProductServiceImpl添加了@Service注解,同时updateProductStockById()方法上添加

    了@Transactional(propagation = Propagation.REQUIRES_NEW)注解,但是由于事务方法

    updateProductStockById()被 private 定义为方法内私有,同样Spring事务会失效。

    */

    6

    @Service

    7

    public class ProductServiceImpl extends ServiceImpl implements

    IProductService {

    8


    9

    @Autowired

    10

    private ProductMapper productMapper;

    11


    12

    @Override

    13

    @Transactional(propagation = Propagation.REQUIRES_NEW)

    14

    private void updateProductStockById(Integer stockCount, String productId) {

    15

    productMapper.updateProductStockById(stockCount, productId);

    16

    }

    17

    }

    18

    4. 同一个类中的方法相互调用

    @Service

    1

    public class OrderServiceImpl extends ServiceImpl implements

    IOrderService {

    2

    @Autowired

    3

    private OrderMapper orderMapper;

    4

    @Autowired

    5

    private ProductMapper productMapper;

    6


    7

    @Override

    8

    public ResponseEntity submitOrder(Order order) {

    9

    // 保存生成订单信息

    10

    long orderNo = Math.abs(ThreadLocalRandom.current().nextLong(1000));

    11

    order.setOrderNo("ORDER_" + orderNo);

    12

    orderMapper.insert(order);

    13


    14

    // 扣减库存

    15

    this.updateProductStockById(order.getProductId(), 1L);

    16

    return new ResponseEntity(HttpStatus.OK);

    17

    }

    18

    submitOrder()方法和updateProductStockById()方法都在OrderService类中,然而

    submitOrder()方法没有添加事务注解,updateProductStockById()方法虽然添加了事务注解,

    这种情况updateProductStockById()会在Spring事务中失效。

    如果内部方法的事务传播类型为不支持事务的传播类型,则内部方法的事务同样会在Spring中失效,举

    个例子:


    19

    @Transactional(propagation = Propagation.REQUIRES_NEW)

    20

    public void updateProductStockById(Integer num, Long productId) {

    21

    productMapper.updateProductStockById(num, productId);

    22

    }

    23

    }

    24


    25

    5. 方法的事务传播类型不支持事务

    @Service

    1

    public class OrderServiceImpl extends ServiceImpl implements

    IOrderService {

    2

    @Autowired

    3

    private OrderMapper orderMapper;

    4

    @Autowired

    5

    private ProductMapper productMapper;

    6


    7

    @Override

    8

    @Transactional(propagation = Propagation.REQUIRES_NEW)

    9

    public ResponseEntity submitOrder(Order order) {

    10

    long orderNo = Math.abs(ThreadLocalRandom.current().nextLong(1000));

    11

    order.setOrderNo("ORDER_" + orderNo);

    12

    orderMapper.insert(order);

    13


    14

    // 扣减库存

    15

    this.updateProductStockById(order.getProductId(), 1L);

    16

    return new ResponseEntity(HttpStatus.OK);

    17

    }

    18


    19


    20

    /**

    21

  • 扣减库存方法事务类型声明为NOT_SUPPORTED不支持事务的传播
  • 22

    */

    23

    @Transactional(propagation = Propagation.NOT_SUPPORTED)

    24

    public void updateProductStockById(Integer num, Long productId) {

    25

    productMapper.updateProductStockById(num, productId);

    26

    }

    27

    }

    28

    6. 异常被内部catch,程序生吞异常

    @Service

    1

    public class OrderServiceImpl extends ServiceImpl implements

    IOrderService {

    2

    @Autowired

    3

    private OrderMapper orderMapper;

    4

    @Autowired

    5

    private ProductMapper productMapper;

    6


    7

    @Override

    8

    @Transactional(propagation = Propagation.REQUIRES_NEW)

    9

    public ResponseEntity submitOrder(Order order) {

    10

    long orderNo = Math.abs(ThreadLocalRandom.current().nextLong(1000));

    11

    order.setOrderNo("ORDER_" + orderNo);

    12

    orderMapper.insert(order);

    13


    14

    // 扣减库存

    15

    this.updateProductStockById(order.getProductId(), 1L);

    16

    return new ResponseEntity(HttpStatus.OK);

    17

    }

    18


    19

    /**

    20

  • 扣减库存方法事务类型声明为NOT_SUPPORTED不支持事务的传播
  • 21

    */

    22

    @Transactional(propagation = Propagation.NOT_SUPPORTED)

    23

    public void updateProductStockById(Integer num, Long productId) {

    24

    try {

    25

    productMapper.updateProductStockById(num, productId);

    26

    } catch (Exception e) {

    27

    // 这里仅仅是捕获异常之后的打印(相当于程序吞掉了异常)

    28

    log.error("Error updating product Stock: {}", e);

    29

    Spring事务生效的前提是连接的数据库支持事务,如果底层的数据库都不支持事务,则Spring事务肯

    定会失效的,例如 :使用MySQL数据库,选用MyISAM存储引擎,因为MyISAM存储引擎本身不支持事

    务,因此事务毫无疑问会失效。

    如果项目中没有配置Spring的事务管理器,即使使用了Spring的事务管理功能,Spring的事务也不会

    生效,例如,如果你是Spring Boot项目,没有在SpringBoot项目中配置如下代码:

    如果是以往的Spring MVC项目,如果没有配置下面的代码,Spring事务也不会生效,正常需要在

    applicationContext.xml文件中,手动配置事务相关参数,比如:

    }

    30

    }

    31

    }

    32

    7. 数据库不支持事务

    8. 未配置开启事务

    @Bean

    1

    public PlatformTransactionManager transactionManager(DataSource dataSource) {

    2

    return new DataSourceTransactionManager(dataSource);

    3

    }

    4

    1

    id="transactionManager">

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    其实,我们在使用@Transactional注解时,是可以指定propagation参数的。

    该参数的作用是指定事务的传播特性,目前Spring支持7种传播特性:

    如果我们在手动设置propagation参数的时候,把传播特性设置错了,比如:

    我们可以看到cancelOrder()方法的事务传播特性定义成了Propagation.NEVER,这种类型的传播

    特性不支持事务,如果有事务则会抛异常。

    在实际项目开发中,多线程的使用场景还是挺多的。如果Spring事务用在多线程场景中使用不当,也会

    导致事务无法生效。

    9. 错误的传播特性

    REQUIRED

    SUPPORTS

    MANDATORY

    REQUIRES_NEW

    NOT_SUPPORTED

    NEVER

    NESTED

    @Service

    1

    public class OrderServiceImpl {

    2


    3

    @Transactional(propagation = Propagation.NEVER)

    4

    public void cancelOrder(UserModel userModel) {

    5

    // 取消订单

    6

    cancelOrder(orderDTO);

    7

    // 还原库存

    8

    restoreProductStock(orderDTO.getProductId(), orderDTO.getProductCount());

    9

    }

    10

    }

    11

    10. 多线程调用

    @Slf4j

    1

    @Service

    2

    public class OrderServiceImpl {

    3


    4

    @Autowired

    5

    private OrderMapper orderMapper;

    6

    通过示例,我们可以看到订单提交的事务方法orderCommit()中,调用了发送短信的事务方法

    sendSms(),但是发送短信的事务方法sendSms()是另起了一个线程调用的。

    这样会导致两个方法不在同一个线程中,从而是两个不同的事务。如果是sendSms()方法中抛了异常,

    orderCommit()方法也回滚是不可能的。

    实际上,Spring的事务是通过ThreadLocal来保证线程安全的,事务和当前线程绑定,多个线程自然

    会让事务失效。

    @Autowired

    7

    private MessageService messageService;

    8


    9

    @Transactional

    10

    public void orderCommit(orderModel orderModel) throws Exception {

    11

    orderMapper.insertOrder(orderModel);

    12

    new Thread(() -> {

    13

    messageService.sendSms();

    14

    }).start();

    15

    }

    16

    }

    17


    18

    @Service

    19

    public class MessageService {

    20


    21

    @Transactional

    22

    public void sendSms() {

    23

    // 发送短信

    24

    }

    25

    }

    26


    本文整理自实践经验,如有问题欢迎交流讨论。


    评论