简体   繁体   English

事务性不会回滚 Spring 中的已检查异常 Boot with Data JPA

[英]Transactional doesn't roll back on checked exception in Spring Boot with Data JPA

I have a ProcessRecon usecase class with a single method named execute .我有一个ProcessRecon用例 class,其中包含一个名为execute的方法。 It saves an entity Reconciliation using paymentRepository.saveRecon and calls a web service as part of acknowledgement using paymentRepository.sendReconAck .它使用 paymentRepository.saveRecon 保存实体Reconciliation ,并使用paymentRepository.saveRecon调用 web 服务作为确认的paymentRepository.sendReconAck

Now there's a chance that this external web service might fail in which case I want to rollback the changes ie the saved entity.现在这个外部 web 服务有可能失败,在这种情况下我想回滚更改,即保存的实体。 Since I am using Unirest, it throws UnirestException which is a checked exception.由于我使用的是 Unirest,它会抛出 UnirestException,这是一个已检查的异常。

There are no errors on the console but this will probably be helpful [UPDATED] .控制台上没有错误,但这可能会有所帮助[更新]

2020-08-20 17:21:42,035 DEBUG [http-nio-7012-exec-6] org.springframework.transaction.support.AbstractPlatformTransactionManager: Creating new transaction with name [com.eyantra.payment.features.payment.domain.usecases.ProcessRecon.execute]:PROPAGATION_REQUIRED,ISOLATION_DEFAULT,-com.mashape.unirest.http.exceptions.UnirestException
...
2020-08-20 17:21:44,041 DEBUG [http-nio-7012-exec-2] org.springframework.transaction.support.AbstractPlatformTransactionManager: Initiating transaction rollback
2020-08-20 17:21:44,044 DEBUG [http-nio-7012-exec-2] org.springframework.orm.jpa.JpaTransactionManager: Rolling back JPA transaction on EntityManager [SessionImpl(621663440<open>)]
2020-08-20 17:21:44,059 DEBUG [http-nio-7012-exec-2] org.springframework.orm.jpa.JpaTransactionManager: Not closing pre-bound JPA EntityManager after transaction
2020-08-20 17:22:40,020 DEBUG [http-nio-7012-exec-2] org.springframework.orm.jpa.support.OpenEntityManagerInViewInterceptor: Closing JPA EntityManager in OpenEntityManagerInViewInterceptor

What I see at the moment is that entity gets pushed to database even if there's a UnirestException .我目前看到的是,即使存在UnirestException ,实体也会被推送到数据库。 But I expect no data be saved to database.但我希望没有数据保存到数据库中。

I am using Spring Boot 2.3.3 with MySQL 5.7.我正在使用 Spring Boot 2.3.3 和 MySQL 5.7。 This is the code I have for it.这是我的代码。

ProcessRecon.java ProcessRecon.java

@Usecase // this custom annotation is derived from @service
public class ProcessRecon {

    private final PaymentRepository paymentRepository;

    @Autowired
    public ProcessRecon(PaymentRepository paymentRepository) {
        this.paymentRepository = paymentRepository;
    }

    @Transactional(rollbackFor = UnirestException.class)
    public Reconciliation execute(final Reconciliation reconciliation) throws UnirestException {
        
        PaymentDetails paymentDetails = paymentRepository.getByReqId(reconciliation.getReqId());

        if (paymentDetails == null)
            throw new EntityNotFoundException(ExceptionMessages.PAYMENT_DETAILS_NOT_FOUND);
        reconciliation.setPaymentDetails(paymentDetails);

        Long transId = null;
        if (paymentDetails.getImmediateResponse() != null)
            transId = paymentDetails.getImmediateResponse().getTransId();

        if (transId != null)
            reconciliation.setTransId(transId);

        if (reconciliation.getTransId() == null)
            throw new ValidationException("transId should be provided in Reconciliation if there is no immediate" +
                    " response for a particular reqId!");

        // THIS GETS SAVED
        Reconciliation savedRecon = paymentRepository.saveRecon(reconciliation);
        paymentDetails.setReconciliation(savedRecon);
        
        // IF THROWS SOME ERROR, ROLLBACK
        paymentRepository.sendReconAck(reconciliation);
        return savedRecon;
    }
}

PaymentRepositoryImpl.java PaymentRepositoryImpl.java

@CleanRepository
public class PaymentRepositoryImpl implements PaymentRepository {

    @Override
    public String sendReconAck(final Reconciliation recon) throws UnirestException {
        // Acknowledge OP
        return sendAck(recon.getRequestType(), recon.getTransId());
    }

    String sendAck(final String requestType, final Long transId) throws UnirestException {
        // TODO: Check if restTemplate can work with characters (requestType)
        final Map<String, Object> queryParams = new HashMap<String, Object>();
        queryParams.put("transId", transId);
        queryParams.put("requestType", requestType);

        logger.debug("{}", queryParams);

        final HttpResponse<String> result = Unirest.get(makeAckUrl()).queryString(queryParams).asString();

        logger.debug("Output of ack with queryParams {} is {}", queryParams, result.getBody());
        return result.getBody();
    }

    @Override
    public Reconciliation saveRecon(final Reconciliation recon) {
        try {
            return reconDS.save(recon);
        }
        catch (DataIntegrityViolationException ex) {
            throw new EntityExistsException(ExceptionMessages.CONSTRAINT_VIOLATION);
        }
    }
}

ReconciliationDatasource.java ReconciliationDatasource.java

@Datasource // extends from @Repository
public interface ReconciliationDatasource extends JpaRepository<Reconciliation, Long> {
    List<Reconciliation> findByPaymentDetails_User_Id(Long userId);
}

To make annotations work you have to use interfaces instead of classes for dependency injection.要使注释起作用,您必须使用接口而不是类来进行依赖注入。

interface ProcessRecon {
      Reconciliation execute(final Reconciliation reconciliation) 
          throws UnirestException;
}

Then然后

@Usecase
public class ProcessReconImpl implements ProcessRecon {

    private final PaymentRepository paymentRepository;

    @Autowired
    public ProcessReconImpl(PaymentRepository paymentRepository) {
        this.paymentRepository = paymentRepository;
    }

    @Transactional(rollbackFor = UnirestException.class)
    public Reconciliation execute(final Reconciliation reconciliation) throws UnirestException {
        //method implementation...
    }
}

Usage用法

@Autowired
ProcessRecon processRecon;

public void executeServiceMethod(Reconciliation reconciliation) {
    processRecon.execute(reconciliation)
}

This way you have got proxy of ProcessReconImpl with provided by annotations additional functionality.通过这种方式,您ProcessReconImpl的代理,并具有注释提供的附加功能。

I assumed the default engine for the tables would be InnoDB but to my surpise, the tables were created using MyISAM engine which doesn't support transactions.我假设表的默认引擎是InnoDB ,但令我惊讶的是,这些表是使用不支持事务的MyISAM引擎创建的。

I resolved the problem by using the below property as suggested here我按照此处的建议使用以下属性解决了问题

spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect

instead of代替

spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect

That was the only change required.这是唯一需要的改变。 Thanks!谢谢!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM