Skip to content

Instantly share code, notes, and snippets.

@projected1
Created March 2, 2021 23:33
Show Gist options
  • Save projected1/feef60b500df03d8b493e08f355ebbbd to your computer and use it in GitHub Desktop.
Save projected1/feef60b500df03d8b493e08f355ebbbd to your computer and use it in GitHub Desktop.
Transactions management and configuration in the Spring framework.

Spring Transactions

JTA (Java Transaction API) Transaction Management

Transaction synchronization is the process by which a persistence context is registered with a transaction so that the persistence context can be notified when a transaction commits. The provider uses this notification to ensure that a given persistence context is correctly flushed to the database.

Transaction association is the act of binding a persistence context to a transaction. You can also think of this as the active persistence context within the scope of that transaction.

Transaction propagation is the process of sharing a persistence context between multiple container-managed entity managers in a single transaction. There can be only one persistence context associated with and propagated across a JTA transaction. All container-managed entity managers in the same transaction must share the same propagated persistence context.

Basic Concepts

Isolation: The degree to which this transaction is isolated from the work of other transactions. e.g. Can this transaction see uncommitted writes from other transactions?

Propagation: Typically, all code executed within a transaction scope will run in that transaction. However, you have the option of specifying the behaviour in the event that a transactional method is executed when a transaction context already exists. For example, code can continue running in the existing transaction (the common case); or the existing transaction can be suspended and a new transaction created. Spring offers all of the transaction propagation options familiar from EJB CMT. To read about the semantics of transaction propagation in Spring, see Section 16.5.7, "Transaction propagation".

Timeout: How long this transaction runs before timing out and being rolled back automatically by the underlying transaction infrastructure.

Read-only status: A read-only transaction can be used when your code reads but does not modify data. Read-only transactions can be a useful optimization in some cases, such as when you are using Hibernate.

Flushing Hibernate Session

Hibernate Entity Manager

3.10. Flush the persistence context From time to time the entity manager will execute the SQL DML statements needed to synchronize the data store with the > state of objects held in memory. This process is called flushing.

Hibernate will keep the managed objects in session and will flush and clear them regularly. Poorly designed data access logic may cause Hibernate to flush the session too frequently and greatly impact the performance. If you want absolute control over the query, you can use a native query.

  1. Start a transaction.
  2. Create a Hibernate session, and bind it to the transaction.
  3. Execute the service method, including any DAO calls.
  4. Flush the Hibernate session to the database.
  5. Commit (or roll back) the transaction.
  6. Clean up (unbind resources, close the Hibernate session).

Normally you wouldn't write the first database check (the check to verify that the change didn't make it), although it doesn't hurt anything. You'd flush the session and then check the database.

In Hibernate, objects have one of four states:

  • Transient (Hibernate isn't managing it)
  • Persistent (Hibernate is managing it)
  • Removed
  • Detached

Session.save() moves an object from the transient state to the persistent state.

Spring Transaction Infrastructure

Spring transaction infrastructure integrates with the life cycle of the EntityManager and can set the FlushMode for it to MANUAL, preventing it from checking each entity in the persistence context for changes (so-called dirty checking). Especially with a large set of objects loaded into the persistence context, this can lead to a significant improvement in performance.

JPA providers like Hibernate can cache the SQL instructions they are supposed to send to the database, often until you actually commit the transaction. For example, you call em.persist(), Hibernate remembers it has to make a database INSERT, but does not actually execute the instruction until you commit the transaction. Afaik, this is mainly done for performance reasons. In some cases you want the SQL instructions to be executed immediately; generally when you need the result of some side effects, like an autogenerated key, or a database trigger. What em.flush() does is to empty the internal SQL instructions cache, and execute it immediately to the database.

OptimisticLockException Provider implementations may defer writing to the database until the end of the transaction, when consistent with the lock mode and flush mode settings in effect. In this case, an optimistic lock check may not occur until commit time, and the OptimisticLockException may be thrown in the "before completion" phase of the commit. If the OptimisticLockException must be caught or handled by the application, the flush method should be used by the application to force the database writes to occur. This will allow the application to catch and handle optimistic lock exceptions.

When used with Hibernate the flush mode is set to NEVER when you configure a transaction as readOnly which causes Hibernate to skip dirty checks (a noticeable improvement on large object trees).

Adding @Transactional to the service methods makes the transaction end after the call to the service method the transaction is committed. Removing it makes the transaction commit after the call to the repository method. Extend JpaRepository and call the saveAndFlush method instead of save. This will execute the SQL (not committing the transaction) and trigger a constraint violation exception.

PersistenceContext

A PersistenceContext is a set of entity instances. It acts as a kind of cache of the database. EntityManager is associated with PersistenceContext and is used to create, remove and query for entities.

  1. Merge Is used to merge the state of an existing entity into the PersistenceContext associated with the EntityManager. Your entity's status change from detached to managed. If your entity is new, it's the same as a persist(). But if your entity already exists, it will update it.

  2. Flush Synchronize the persistence context to the underlying database. All your entity will be insert/update/remove in the database. No commit is done in the database. It's useful when you need to do queries JPQL in the database and you want to see the changes you did in the PersistenceContext in the result of the query.

  3. Refresh Refresh the state of an entity instance from the database, overwriting changes made to the entity, if any.

Spring Transaction Configuration

Enables Spring's annotation-driven transaction management capability.

@Configuration
@EnableTransactionManagement
public class DatabaseConfiguration {...}

Transaction Rollback

Transactions are marked for rollback in the case of runtime, unchecked exceptions: RuntimeException/Error. Checked exceptions that are thrown from a transactional method do not result in rollback in the default configuration.

@Transactional Annotation Settings

Attribute Default Description
propagation REQUIRED Transaction propagation behavior
isolation DEFAULT Transaction isolation level
timeout -1 Transaction timeout value (in seconds)
readOnly FALSE Is this transaction read-only?
rollbackFor {} Array of exception classes that must cause rollback
rollbackForClassName {} Array of exception names that must cause rollback
noRollbackFor {} Array of exception classes that must not cause rollback
noRollbackForClassName {} Array of exception names that must not cause rollback

Transaction Propagation (org.springframework.transaction.annotation.Propagation)

Typically, all code executed within a transaction scope will run in that transaction. However, you have the option of specifying the behavior in the event that a transactional method is executed when a transaction context already exists. For example, code can continue running in the existing transaction (the common case); or the existing transaction can be suspended and a new transaction created.

REQUIRED

Support a current transaction, create a new one if none exists. Analogous to EJB transaction attribute of the same name. This is the default setting of a transaction annotation.

When the propagation setting is PROPAGATION_REQUIRED, a logical transaction scope is created for each method upon which the setting is applied. Each such logical transaction scope can determine rollback-only status individually, with an outer transaction scope being logically independent from the inner transaction scope. Of course, in case of standard PROPAGATION_REQUIRED behavior, all these scopes will be mapped to the same physical transaction. So a rollback-only marker set in the inner transaction scope does affect the outer transaction's chance to actually commit (as you would expect it to).

However, in the case where an inner transaction scope sets the rollback-only marker, the outer transaction has not decided on the rollback itself, and so the rollback (silently triggered by the inner transaction scope) is unexpected. A corresponding UnexpectedRollbackException is thrown at that point. This is expected behavior so that the caller of a transaction can never be misled to assume that a commit was performed when it really was not. So if an inner transaction (of which the outer caller is not aware) silently marks a transaction as rollback-only, the outer caller still calls commit. The outer caller needs to receive an UnexpectedRollbackException to indicate clearly that a rollback was performed instead.

SUPPORTS

Support a current transaction, execute non-transactionally if none exists. Analogous to EJB transaction attribute of the same name. Note: For transaction managers with transaction synchronization, PROPAGATION_SUPPORTS is slightly different from no transaction at all, as it defines a transaction scope that synchronization will apply for. As a consequence, the same resources (JDBC Connection, Hibernate Session, etc) will be shared for the entire specified scope. Note that this depends on the actual synchronization configuration of the transaction manager. @see org.springframework.transaction.support.AbstractPlatformTransactionManager#setTransactionSynchronization

MANDATORY

Support a current transaction, throw an exception if none exists. Analogous to EJB transaction attribute of the same name.

REQUIRES_NEW

Create a new transaction, and suspend the current transaction if one exists. Analogous to the EJB transaction attribute of the same name. NOTE: Actual transaction suspension will not work out-of-the-box on all transaction managers. This in particular applies to {@link org.springframework.transaction.jta.JtaTransactionManager}, which requires the {@code javax.transaction.TransactionManager} to be made available it to it (which is server-specific in standard Java EE). @see org.springframework.transaction.jta.JtaTransactionManager#setTransactionManager

PROPAGATION_REQUIRES_NEW, in contrast to PROPAGATION_REQUIRED, uses a completely independent transaction for each affected transaction scope. In that case, the underlying physical transactions are different and hence can commit or roll back independently, with an outer transaction not affected by an inner transaction's rollback status.

NOT_SUPPORTED

Execute non-transactionally, suspend the current transaction if one exists. Analogous to EJB transaction attribute of the same name. NOTE: Actual transaction suspension will not work out-of-the-box on all transaction managers. This in particular applies to {@link org.springframework.transaction.jta.JtaTransactionManager}, which requires the {@code javax.transaction.TransactionManager} to be made available it to it (which is server-specific in standard Java EE). @see org.springframework.transaction.jta.JtaTransactionManager#setTransactionManager

NEVER

Execute non-transactionally, throw an exception if a transaction exists. Analogous to EJB transaction attribute of the same name.

NESTED

Execute within a nested transaction if a current transaction exists, behave like PROPAGATION_REQUIRED else. There is no analogous feature in EJB. Note: Actual creation of a nested transaction will only work on specific transaction managers. Out of the box, this only applies to the JDBC DataSourceTransactionManager when working on a JDBC 3.0 driver. Some JTA providers might support nested transactions as well. @see org.springframework.jdbc.datasource.DataSourceTransactionManager

PROPAGATION_NESTED uses a single physical transaction with multiple savepoints that it can roll back to. Such partial rollbacks allow an inner transaction scope to trigger a rollback for its scope, with the outer transaction being able to continue the physical transaction despite some operations having been rolled back. This setting is typically mapped onto JDBC savepoints, so will only work with JDBC resource transactions. See Spring's DataSourceTransactionManager.

Transaction Isolation (org.springframework.transaction.annotation.Isolation)

The degree to which this transaction is isolated from the work of other transactions. For example, can this transaction see uncommitted writes from other transactions?

Hibernate default: TRANSACTION_READ_COMMITTED

Dirty Reads Non-Repeatable Reads Phantom Reads
READ_UNCOMMITTED yes yes yes
READ_COMMITTED no yes yes
REPEATABLE_READ no no yes
SERIALIZABLE no no no

DEFAULT

Use the default isolation level of the underlying datastore. All other levels correspond to the JDBC isolation levels. @see java.sql.Connection

READ_UNCOMMITTED

A constant indicating that dirty reads, non-repeatable reads and phantom reads can occur. This level allows a row changed by one transaction to be read by another transaction before any changes in that row have been committed (a "dirty read"). If any of the changes are rolled back, the second transaction will have retrieved an invalid row. @see java.sql.Connection#TRANSACTION_READ_UNCOMMITTED

READ_COMMITTED

A constant indicating that dirty reads are prevented; non-repeatable reads and phantom reads can occur. This level only prohibits a transaction from reading a row with uncommitted changes in it. @see java.sql.Connection#TRANSACTION_READ_COMMITTED

REPEATABLE_READ

A constant indicating that dirty reads and non-repeatable reads are prevented; phantom reads can occur. This level prohibits a transaction from reading a row with uncommitted changes in it, and it also prohibits the situation where one transaction reads a row, a second transaction alters the row, and the first transaction rereads the row, getting different values the second time (a "non-repeatable read"). @see java.sql.Connection#TRANSACTION_REPEATABLE_READ

SERIALIZABLE

A constant indicating that dirty reads, non-repeatable reads and phantom reads are prevented. This level includes the prohibitions in {@code ISOLATION_REPEATABLE_READ} and further prohibits the situation where one transaction reads all rows that satisfy a {@code WHERE} condition, a second transaction inserts a row that satisfies that {@code WHERE} condition, and the first transaction rereads for the same condition, retrieving the additional "phantom" row in the second read. @see java.sql.Connection#TRANSACTION_SERIALIZABLE

Class Methods Visibility

When using proxies, 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. Consider the use of AspectJ (see below) if you need to annotate non-public methods.

Final Notes

Spring recommends that you only annotate concrete classes (and methods of concrete classes) with the @Transactional annotation, as opposed to annotating interfaces. You certainly can place the @Transactional annotation on an interface (or an interface method), but this works only as you would expect it to if you are using interface-based proxies. The fact that Java annotations are not inherited from interfaces means that if you are using class-based proxies (proxy-target-class="true") or the weaving-based aspect (mode="aspectj"), then the transaction settings are not recognized by the proxying and weaving infrastructure, and the object will not be wrapped in a transactional proxy, which would be decidedly bad.

In proxy mode (which is the default), only external method calls coming in through the proxy are intercepted. This means that self-invocation, in effect, a method within the target object calling another method of the target object, will not lead to an actual transaction at runtime even if the invoked method is marked with @Transactional.

Use Cases & Examples

  1. Commits the transaction
@Transactional
public void foo() throws Exception {
    throw new Exception();
}
  1. Rolls-back the transaction
@Transactional
public void foo() {
    throw new RuntimeException();
}
  1. Commits the transaction
@Transactional
public void foo() {
    try {
        throw new RuntimeException();
    } catch (RuntimeException e) {
    }
}
  1. Commits the transaction
@Service
@Transactional
public class FooService {
    public void foo() {
        try {
            bar();
        } catch (RuntimeException e) {
        }
    }

    public void bar() {
        throw new RuntimeException();
    }
}
  1. Rolls-back the transaction
@Service
@Transactional
public class FooService {
    @Inject
    private BarService barService;

    public void foo() {
        try {
            barService.bar();
        } catch (RuntimeException e) {
        }
    }
}

@Service
@Transactional
public class BarService {
    public void bar() {
        throw new RuntimeException();
    }
}
  1. Commits the 1st transaction, rolls-back the 2nd transaction
@Service
@Transactional
public class FooService {
    @Inject
    private BarService barService;

    public void foo() {
        try {
            barService.bar();
        } catch (RuntimeException e) {
        }
    }
}

@Service
@Transactional
public class BarService {
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void bar() {
        throw new RuntimeException();
    }
}

References

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment