Description:

I am experiencing an issue where the noRollbackFor attribute of the @Transactional annotation is not respected when calling the save method of a JpaRepository. Despite correctly configuring the noRollbackFor attribute, the transaction is rolled back when a DataIntegrityViolationException occurs within the save method.

Steps to Reproduce:

Define a service method with the @Transactional(noRollbackFor = {DataIntegrityViolationException.class}) annotation. Call a save method on a JpaRepository within this service method and throw a DataIntegrityViolationException. Observe that the transaction is rolled back despite the noRollbackFor attribute being specified. Sample Code:

Here is a simplified version of the code to demonstrate the issue: `import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional;

import javax.persistence.EntityManager; import javax.persistence.PersistenceContext;

@AllArgsConstructor @Service @Slf4j public class EventTagService {

private final EventTagRepository eventTagRepository;
private final AccountPerEventTagRepository accountPerEventTagRepository;

@PersistenceContext
private EntityManager entityManager;

@Transactional(noRollbackFor = {DataIntegrityViolationException.class})
public void createEventTagAndUpdateUser(String eventTag, Long accountId) {
    EventTag newEventTag = createEventTag(eventTag);
    updateAccountPerEventTag(newEventTag, accountId);
}

public EventTag createEventTag(String eventTag) {
    try {
        return eventTagRepository.save(EventTag.builder().name(eventTag).build());
    } catch (DataIntegrityViolationException ex) {
        log.info("error", ex);
        entityManager.clear();
        return eventTagRepository.findByName(eventTag)
                .orElseThrow(() -> new EntityNotFoundException("EventTag not found"));
    }
}

@Transactional
public void updateAccountPerEventTag(EventTag eventTag, Long accountId) {
    AccountPerEventTag accountPerEventTag = accountPerEventTagRepository.findByAccountId(accountId)
            .orElseThrow(() -> new EntityNotFoundException("AccountPerEventTag not found"));
    List<EventTag> eventTags = accountPerEventTag.getEventTags();
    eventTags.add(eventTag);
    accountPerEventTag.setEventTags(eventTags);
    accountPerEventTagRepository.save(accountPerEventTag);
}

} `

Observed Behavior:

When the DataIntegrityViolationException is thrown from the save method, the transaction is rolled back even though noRollbackFor = {DataIntegrityViolationException.class} is specified.

Expected Behavior:

The transaction should not be rolled back when a DataIntegrityViolationException is thrown, as specified by the noRollbackFor attribute in the @Transactional annotation.

Environment:

Spring Boot version: 3.2.5 JDK version: 17 Dependencies: org.springframework.boot:spring-boot-starter-data-jpa org.springframework.boot:spring-boot-starter-aop org.springframework.cloud:spring-cloud-starter-netflix-eureka-client mysql:mysql-connector-java:8.0.33 Others (full dependency list below)

Additional Information:

During debugging in IntelliJ, I observed that the TransactionAttribute object is initialized correctly with the noRollbackRule. However, when the save method is invoked and the rollbackOn method is called, the rules list is empty, leading to a rollback despite the noRollbackFor rule.

Any help or guidance to resolve this issue would be greatly appreciated.

Please feel free to adjust any details to better fit the format and information you want to present in the Github issue.

Comment From: snicoll

Steps to Reproduce:

Sorry but we can't justify spending the time to rebuild a sample piece by piece based on your instructions. If you want support, please build a small sample (ideally created from start.spring.io) that reproduces the problem you've experience. You can attach the project here as a zip or push the code to a GitHub repository.

Comment From: zgtour

@SunWooInOOP Not sure about which one is the specified save method. If the excepction occurred at eventTagRepository.save(...), you should check the inheritancerelationship between DataIntegrityViolationException and EntityNotFoundException, since the latter is eventually caught at createEventTagAndUpdateUser(....). If the exception occurred at accountPerEventTagRepository.save(...), the rollback may have been triggered by updateAccountPerEventTag(....) ....

Comment From: jhoeller

Generally, please keep in mind that every @Transactional declaration level matters in terms of rollback rules. E.g. if your createEventTagAndUpdateUser method ends up calling a repository save method which is annotated with @Transactional itself, then it might be than inner save transaction boundary deciding to flag the entire transaction for rollback. The outer createEventTagAndUpdateUser rollback rules effectively only apply to exceptions that occur within its own transaction boundary, not within nested boundaries where a local rollback decision might have been applied already. So for consistent rollback rules in that code path, you might have to redeclare @Transactional for that save method with the same rollback rules.

Comment From: sunwooo3856

Thank you for your response.

To add further context, the repository method in question extends JpaRepository: public interface EmotionTagRepository extends JpaRepository<EmotionTag, Long> {} Through several hours of debugging, I have confirmed that the rollback-only status is activated during the execution of eventTagRepository.save in the createEventTag method.

In various test cases, I have observed that unchecked exceptions can be caught via try-catch blocks to prevent rollbacks, as demonstrated by the following examples:

Case1

`@Transactional(noRollbackFor = DataIntegrityViolationException.class) public void createEventTagAndUpdateUser(String eventTag, Long accountId) { createEventTag(eventTag); }

@Transactional(noRollbackFor = DataIntegrityViolationException.class) public EventTag createEventTag(String eventTag) { throw new DataIntegrityViolationException("Ex"); } `

Case2

`@Transactional public void createEventTagAndUpdateUser(String eventTag, Long accountId) { try { createEventTag(eventTag); } catch (DataIntegrityViolationException ex) { log.info("Test"); } }

@Transactional public EventTag createEventTag(String eventTag) { throw new DataIntegrityViolationException("Ex"); } `

In these cases, no rollback occurred.

When comparing these examples with my original code:

`@Transactional(noRollbackFor = {DataIntegrityViolationException.class}) public void createEventTagAndUpdateUser(String eventTag, Long accountId) { EventTag newEventTag = createEventTag(eventTag); updateAccountPerEventTag(newEventTag, accountId); }

public EventTag createEventTag(String eventTag) { try { return eventTagRepository.save(EventTag.builder().name(eventTag).build()); } catch (DataIntegrityViolationException ex) { log.info("error", ex); entityManager.clear(); return eventTagRepository.findByName(eventTag) .orElseThrow(() -> new EntityNotFoundException("EventTag not found")); } } `

I observed that despite using try-catch and noRollbackFor, a rollback still occurs.

Additionally, even after changing the propagation level of public EventTag createEventTag(String eventTag) to REQUIRES_NEW, the issue persists.

During debugging, I noticed that the TransactionAttribute used by eventTagRepository.save and the initial TransactionAttribute (implementation: RuleBasedTransactionAttribute) with private List rollbackRules had the rollbackRules populated initially, but its size was 0 at the point of calling the save method, indicating the rules had disappeared.

I suspect the issue might be due to an unexpected behavior within the save method of the JPA repository. I will create a demo project to illustrate the issue and share it accordingly.

Thank you.

Comment From: spring-projects-issues

If you would like us to look at this issue, please provide the requested information. If the information is not provided within the next 7 days this issue will be closed.

Comment From: spring-projects-issues

Closing due to lack of requested feedback. If you would like us to look at this issue, please provide the requested information and we will re-open the issue.