Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Transactional Support to the @Modifying Annotation #3733

Closed
petro-zhylenko opened this issue Jan 8, 2025 · 1 comment
Closed

Add Transactional Support to the @Modifying Annotation #3733

petro-zhylenko opened this issue Jan 8, 2025 · 1 comment
Labels
status: declined A suggestion or change that we don't feel we should currently apply

Comments

@petro-zhylenko
Copy link

petro-zhylenko commented Jan 8, 2025

Problem

The @Modifying annotation is used for INSERT, UPDATE, DELETE, and table structure modification operations. In most cases, such operations require transactional support to ensure:

  • Data consistency.
  • Protection against incorrect execution when the method is invoked within a readOnly transaction.
  • Safe execution of operations in cases of errors or exceptions.

Currently, @Modifying does not automatically enforce a transactional context, leading to several issues:

  1. When the method is invoked within a readOnly transaction, errors can occur (e.g., cannot execute statement in read-only transaction).
  2. Developers must explicitly add the @Transactional annotation to every method, resulting in repeated boilerplate code.
  3. In large projects, there is a risk of forgetting to add transactional support, which can cause unexpected behavior or be difficult to debug.

Proposal

Add transactional support to the @Modifying annotation by introducing the following changes:

  1. New transactional Parameter in @Modifying:

    • Type: boolean
    • Default value: true (i.e., transactional support enabled by default).
      This allows developers to explicitly specify when a transaction is not needed, while keeping the default behavior transactional for most cases.

    Example:

    @Modifying(transactional = true)
    public void updateEntity(Entity entity) {
        // Update data
    }
    
    @Modifying(transactional = false)
    public void createIndex() {
        jdbcTemplate.execute("CREATE INDEX idx_name ON table_name (column_name)");
    }
  2. Integration with @Transactional:
    When transactional = true, the @Modifying annotation should implicitly add behavior equivalent to @Transactional(readOnly = false).

    Implementation Example:

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @Transactional(readOnly = false)
    public @interface Modifying {
        boolean transactional() default true;
    }
  3. Atomicity Enforcement:
    Even for "atomic" operations (e.g., single UPDATE or DELETE), transactional support is beneficial as it:

    • Provides protection against potential conflicts or errors.
    • Standardizes behavior and makes the codebase more predictable.

Reasons for the Change

  1. Ensures Safe Transactional Execution:
    Avoids situations where a method with @Modifying is invoked within a readOnly transaction, leading to errors or incorrect behavior.

  2. Reduces Boilerplate Code:
    Developers will no longer need to manually add @Transactional(readOnly = false) to every method using @Modifying.

  3. Improves Standards:
    By default, transactions will be active since the cases requiring transactions significantly outnumber those where they are not needed.

  4. Flexibility:
    The additional transactional = false parameter allows for handling rare cases where transactional support is unnecessary (e.g., DDL operations or externally managed transactions).


Examples of Use

1. Default Transactional Method:

@Modifying
public void saveEntity(Entity entity) {
    repository.save(entity);
}

2. Non-Transactional Method (DDL Operation):

@Modifying(transactional = false)
public void alterTable() {
    jdbcTemplate.execute("ALTER TABLE table_name ADD COLUMN new_column INT");
}

3. Invocation Within a Read-Only Transaction:

@Transactional(readOnly = true)
public void processEntity(Entity entity) {
    updateEntity(entity); // Guaranteed to run with transactional = true
}

@Modifying
public void updateEntity(Entity entity) {
    repository.save(entity);
}

Expected Outcome

  1. Reduced risk of errors related to readOnly transactions.
  2. Simplified code with automatic transactional context handling.
  3. Flexibility for cases where transactions are not needed.

Additional Steps

Update the documentation for @Modifying to include the new transactional parameter and provide guidance on when to use it.

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Jan 8, 2025
@petro-zhylenko petro-zhylenko changed the title Add Transactional Support to the @Modify Annotation Add Transactional Support to the @Modifying Annotation Jan 8, 2025
petro-zhylenko added a commit to petro-zhylenko/spring-data-jpa that referenced this issue Jan 9, 2025
Add ModifyingTransactionAspect for conditional transactional support.

Description
Introduced an aspect to handle methods annotated with @Modifying in Spring Data JPA. The aspect checks the transactional parameter in the annotation to decide whether to open a new transaction explicitly.

Added logic to handle transaction management using PlatformTransactionManager.
Includes support for both transactional and non-transactional method executions.
Updated the class-level documentation to explain its purpose, usage, and dependencies.
This improvement allows developers to gain fine-grained control over transactional behavior for @Modifying queries.

References
Related tickets: spring-projects#3733.
@mp911de
Copy link
Member

mp911de commented Jan 9, 2025

Modifying queries do not enforce transactions because we cannot generally assume that a transaction is required. Annotation-enabled transactions are handled by Spring's Aspects and therefore these aren't integrated with meta-annotations that could enable or disable transactions. If you require transaction support for your modifying queries then we recommend creating a meta-annotation on your side that is configured with your project-specific needs.

The purpose of @Modifying is to use Query.executeUpdate() result count and optionally invoke flush and clear on EntityManager. As general rule of thumb, you should not rely on transactional annotations on the repository but rather run your unit of work in a transaction so that you control the transactional scope.

@mp911de mp911de closed this as not planned Won't fix, can't repro, duplicate, stale Jan 9, 2025
@mp911de mp911de added status: declined A suggestion or change that we don't feel we should currently apply and removed status: waiting-for-triage An issue we've not yet triaged labels Jan 9, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: declined A suggestion or change that we don't feel we should currently apply
Projects
None yet
Development

No branches or pull requests

3 participants