Spring 启动@Transactional 不回滚数据库插入

Spring boot @Transactional not rolling back the database inserts

这里需要一些帮助,我不明白为什么我的交易在发生异常时没有回滚。

我会尽量把我的代码放在项目上(不能在互联网上分享)

这是我的服务

@Service
@SL4j
@Transactional(propagation = propagation.SUPPORTS, readOnly=true)
public class PublicationServiceImpl implements PublicationService{
    @Autowired 
    private PartnerRepo partnerRepo;
                
    @Autowired
    private FlowRepo flowRepo;

    @Autowired
    private PubRepo pubRepo;
                
    @Override
    @Transactional(propagation = propagation.REQUIRED, rollbackFor=Exception.class)
    public int save(Request request) {
    try{
                   int  pk_id_partner = partnerRepo.save(request);
                   int  pk_id_flow = flowRepo.save(request);
        
                   String publicationCode = generatePubCode(request);
                   int publicationCode= pubRepo.save(pk_id_partner, pk_id_flow, request);
        }
        catch(Exception e){
        log.error("Exception in saving");
    }
    return 0;
    }
}

这是我的存储库(示例 1,所有 3 个存储库都遵循相同的编码标准)

@Repository
@Slf4j
public class PartnerRepo implemets PartnerRepo{
    @Autowired
    private NamedParamaterJDBCTemplate namedParamaterJDBCTemplate;
    //String Declarations .....

    private MapSqlParameterSource sqlParameterSource;

    @Override
    public int save(Request request){
        sqlParamatersSource = new MapSqlParameterSource();
        //sqlParamatersSource.addValue(.....)
        //sqlParamatersSource.addValue(.....)
        //sqlParamatersSource.addValue(.....)

        return executeQuery();
    }

    private int executeQuery(){
        try{
            keyHolder = new GenerateKeyHolder();
            namedParamaterJDBCTemplate.update(getInsertQuery(), sqlParamaterSource , kekHolder, new String[]{"pk_id"})
            return keyHolder.getKey().intValue();
        }catch(Exception e){
            log.error("Exception while saving");
            return 0;
        }
    }
}

所以问题是,考虑在方法generatePubCode(request);中存在异常,理想情况下因为我在class级别和方法级别使用了@Transactional,之前的2个回购交易() 应该回滚吧?然而它并没有发生,即使在代码执行完成后我也可以看到数据库中的记录(Postgres DB v10)。

请帮忙解决这个问题,我是不是做错了什么?

如果您需要更多可能对此处有帮助的信息,请告诉我!

P.S:我已经尝试了 @Transactional 的所有排列,没有任何效果:只有在 catch 块中有这个才有效! TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); 我想知道它是否是 springBoot 项目的正确方法

在此先感谢您的帮助!

编辑:根据建议 PublicationServiceSaverImpl.save() public

此致, 巴尔加夫

如果要使用@Transactional,方法PublicationServiceImpl.save必须是public

根据 Spring 文档:

When you use transactional proxies with Spring’s standard configuration, you should apply the @Transactional annotation only to methods with public visibility. If you do annotate protected, private, or package-visible methods with the @Transactional annotation, no error is raised, but the annotated method does not exhibit the configured transactional settings.

由于在 Spring.

中创建代理的方式,注释通常不会对从同一个 class 调用的方法起作用

它与 @Transaction 无关,特别是因为您的方法是 私有的 并且 从同一对象中调用.

请制作方法 public 并将 @Transactional 方法移动到一个单独的 class 中,用 @Service 注释并从实例外部调用它的 class

  @Service
  public class PublicationServiceSaverImpl {

     @Transactional
     **public** int save(Request request) {
      ...
     }
  }

您必须从 class PublicationServiceSaverImpl 外部调用保存方法,可能来自 PublicationServiceImpl。

代替@Transactional(propagation = propagation.REQUIRED提供@Transactional(propagation = propagation.REQUIRED_NEW

如果你使用后者,它将使用父事务边界,它在class级别。

并且您不需要显式状态 rollbackFor=Exception.class。默认情况下 spring 将回滚异常

并将私有更改为 public

试试这个

在 Spring

中有几件事会破坏正常的交易
  1. 您的服务方式是private
  2. 你正在捕捉并吞噬异常

private 方法

事实上,您的 PublicationServiceImpl save 方法是 private 基本上使得该方法上的 @Transactional 无用。由于无法代理 private 方法,因此不会应用任何事务。即使它是 public 它也不会工作,因为您从同一对象中调用该方法,因此该方法的事务性适用。

要修复,使您的方法 public 并从另一个 class 调用 save 方法(或者使调用 save 的实际方法具有正确的@Transactional.

事实上这是行不通的,因为正在使用类型操作 AOP,默认情况下 Spring 将使用代理,这是使用基于代理的 AOP 的缺点。

另一种使其与 private 方法一起工作的解决方案是切换到具有 classes 的编译时或加载时编织的成熟的 AspectJ。两者都需要额外的设置,而且可能很乏味。

捕获并吞下异常

您的存储库和服务中都有一个 try/catch 块。这些中的每一个都捕获并吞下了异常(它们被记录但没有被重新抛出)。

为了使事务正常工作,它需要查看异常。您捕获并吞下它们的事实使事务方面看不到它们,而不是进行回滚,而是进行提交。对于交易方面,一切都很好,因为没有例外。

要修复,请删除 try/catch 或重新抛出异常。

首先:制定你的方法public。

其次:你必须抛出异常。如果你捕获而不重新抛出它,你如何期望事务处理知道发生错误然后回滚?

您有两个选择:抛出 Exception 而不是捕获它,或者捕获,做一些进一步的处理然后重新抛出它。

因此在您的存储库中,只需添加一个 throws 关键字,然后在日志语句后重新抛出异常:

public int executeQuery() throws Exception {
  try {
    keyHolder = new GenerateKeyHolder();
    namedParamaterJDBCTemplate.update(getInsertQuery(), sqlParamaterSource, kekHolder, new String[] {
      "pk_id"
    })
    return keyHolder.getKey().intValue();
  } catch(Exception e) {
    log.error("Exception while saving");
    throw e;
  }
}

现在,为您服务:

示例 1 - 使用 throws 关键字传播已检查的异常:

@Override
@Transactional(propagation = propagation.REQUIRED, rollbackFor = Exception.class)
public int save(Request request) throws Exception {
  int pk_id_partner = partnerRepo.save(request);
  int pk_id_flow = flowRepo.save(request);

  String publicationCode = generatePubCode(request);
  int publicationCode = pubRepo.save(pk_id_partner, pk_id_flow, request);
  return 0;
}

示例 2 - 捕获并将其作为未检查的 RuntimeException 重新抛出。

@Override
@Transactional(propagation = propagation.REQUIRED)
public int save(Request request) {
  try {
    int pk_id_partner = partnerRepo.save(request);
    int pk_id_flow = flowRepo.save(request);

    String publicationCode = generatePubCode(request);
    int publicationCode = pubRepo.save(pk_id_partner, pk_id_flow, request);
  } catch(Exception ex) {
    throw new RuntimeException(ex);
  }
  return 0;
}

请注意,第二个示例不需要 @TransactionalrollbackFor 参数。默认情况下,如果发生未经检查的异常,事务将回滚,因此在 RuntimeException 的情况下无需显式使用 rollbackFor

如果解决方案不起作用,则必须进行另一项验证。它是为了验证数据库表是否允许回滚。为此,引擎必须在 InnoDB 中,而不是在 MyISAM 和其他中。