51工具盒子

依楼听风雨
笑看云卷云舒,淡观潮起潮落

Spring 事务传播行为

Preface {#preface}

前两天在运行代码插入数据的时候总是报索引重复,数据死活插入不了。

情景再现 {#情景再现}

环境:Springboot,JPA。

数据库中有一张表,姑且称为table_a,结构如下:

@Getter
@Setter
@ToString
@RequiredArgsConstructor
@Entity
@Table(name = TableA.TABLE_NAME, indexes = {
        @Index(name = "idx_spuId", columnList = "spuId", unique = true)
})
public class TableA {

    public static final String TABLE_NAME = "table_a";

    /**
     * 主键id
     */
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY, generator = TABLE_NAME + "_generator")
    private Integer id;

    /**
     * 商品spu_id
     */
    @NotNull
    @ColumnDefault("0")
    private Long spuId = 0L;

    /**
     * 商品名称
     */
    private String goodsName;

    /**
     * 商品信息更新次数
     */
    @NotNull
    @ColumnDefault("0")
    private Integer updateNum = 0;

}

对应dao内容如下:

public interface ITableADao extends JpaRepository<TableA, Integer> {

    /**
     * 根据 spuId 查询
     * @param spuId spuId
     * @return -
     */
    TableA findFirstBySpuId(@NotNull Long spuId(@NotNull Long spuId);

}

有两个 Service 实现,分别称为ServiceAImplServiceBImpl吧。

ServiceAImpl中的add方法首先判断要插入的商品是否已在数据库中了,存在则更新数据,不存在则插入数据。

@Service
@RequiredArgsConstructor
public class ServiceAImpl implements ServiceA {

    private final ITableADao tableADao;

    /**
     * 插入商品信息
     *
     * @param form form
     * @return -
     */
    @Override
    @Transactional(propagation= Propagation.REQUIRES_NEW)
    public TableA add(TableA form) {

        TableA tableA = tableADao.findFirstBySpuId(form.getSpuId());

        if (tableA == null) {
            // 插入数据
            tableA = new TableA();
        }

        // 更新数据
        // 模拟调用第三方接口查询数据
        TableA result = Api.searchBySpuId(spuId);
        BeanUtils.copyProperties(result, tableA);
        tableADao.save(tableA);

        return tableA;
    }
}

ServiceBImpl中调用ServiceAImpladd方法。

@Service
@RequiredArgsConstructor
public class ServiceBImpl implements IServiceB{

    private final IServiceA serviceA;
    private final ITableADao tableADao;

    /**
     * 处理商品信息
     *
     * @param spuId spuId
     * @return -
     */
    @Override
    @Transactional
    public boolean handleGoods(Long spuId) {

        // 前面的一些代码逻辑,省略...

        deal(spuId);

        // 后面的一些代码逻辑,省略...

        return true;
    }

    /**
     * 处理商品信息
     *
     * @param spuId spuId
     */
    private void deal(Long spuId) {

        TableA tableA = serviceA.add(spuId);

        // 设置商品的更新次数
        tableA.setUpdateNum(tableA.getUpdateNum());
        tableADao.save(tableA);
    }

}

发生的异常情况是在调用handleGoods方法传入数据库中不存在的spuId时,在save时会报错,提示重复的spuId,无法插入数据。

// 设置商品的更新次数
tableA.setUpdateNum(tableA.getUpdateNum());
tableADao.save(tableA);

这就很奇怪了,明明插入的spuId是数据库中不存在的,为什么会报spuId重复呢?

经过一番简单的思考,发现是add方法上的

@Transactional(propagation= Propagation.REQUIRES_NEW)

在作祟。add方法中开启了一个新的事务,在插入新的数据时,就会导致在别的事务中查不到新插入的数据,因为此时数据还没有真正插入到数据库中。所以

// 设置商品的更新次数
tableA.setUpdateNum(tableA.getUpdateNum());
tableADao.save(tableA);

save方法由于事务不同,查不到刚刚在ServiceAImpladd方法添加的商品信息,也会执行insert语句。最终在提交事务时add方法中的savedeal方法中的save都会执行insert语句,就导致报spuId重复的错误。

解决方法就是将add方法上的

@Transactional(propagation= Propagation.REQUIRES_NEW)

改为

@Transactional(propagation= Propagation.REQUIRED)

这也是 Spring 默认的事务传播行为,表示没有就创建事务,有就加入事务

至于之前为什么这么写,那是之前设计的问题了😂。

Spring 事务传播行为 {#spring-事务传播行为}

事务传播行为(propagation behavior)指的就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行。 例如:methodA 事务方法调用 methodB 事务方法时,methodB 是继续在调用者 methodA 的事务中运行呢,还是为自己开启一个新事务运行,这就是由 methodB 的事务传播行为决定的。

Spring 目前共有 7 种事务传播行为。

| 事务传播行为类型 | 说明 | |---------------------------|------------------------------------------------------------| | PROPAGATION_REQUIRED | 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。 | | PROPAGATION_SUPPORTS | 支持当前事务,如果当前没有事务,就以非事务方式执行。 | | PROPAGATION_MANDATORY | 使用当前的事务,如果当前没有事务,就抛出异常。 | | PROPAGATION_REQUIRES_NEW | 新建事务,如果当前存在事务,把当前事务挂起。 | | PROPAGATION_NOT_SUPPORTED | 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 | | PROPAGATION_NEVER | 以非事务方式执行,如果当前存在事务,则抛出异常。 | | PROPAGATION_NESTED | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。 |

参考资料 {#参考资料}

赞(3)
未经允许不得转载:工具盒子 » Spring 事务传播行为