📜 ⬆️ ⬇️

Efficient Spring Transaction Management

Good day everyone!

Well, the end of the month is always intensive, and here we have only one day left before the start of the second stream of the course “Developer on the Spring Framework” - a wonderful and interesting course that leads a no less beautiful and evil Yuri (as some students call him for the level of requirements in DZ), so let's consider another material that we have prepared for you.

Go.
')
Introduction

Most of the time, developers do not attach importance to transaction management. As a result, either most of the code is rewritten later, or the developer implements transaction management without knowing how it should actually work or what aspects should be used specifically in their case.

An important aspect of transaction management is determining the correct transaction boundaries, when the transaction should begin and when to end, when data should be added to the database and when it should be rolled back (in case of an exception).



The most important aspect for developers is to understand how to implement transaction management in the application in the best possible way. Therefore, let's consider various options.

Transaction Management Methods

Transactions can be managed in the following ways:

1. Software control by writing custom code

This is the old way to manage transactions.

EntityManagerFactory factory = Persistence.createEntityManagerFactory("PERSISTENCE_UNIT_NAME"); EntityManager entityManager = entityManagerFactory.createEntityManager(); Transaction transaction = entityManager.getTransaction() try { transaction.begin(); someBusinessCode(); transaction.commit(); } catch(Exception ex) { transaction.rollback(); throw ex; } 

Pros :


Cons :


2. Using Spring to manage transactions

Spring supports two types of transaction management

1. Software Transaction Management : You must manage transactions through programming. This method is quite flexible, but it is difficult to maintain.

2. Declarative transaction management : You separate transaction management from business logic. You use only annotations in an XML-based configuration for transaction management.

We strongly recommend using declarative transactions. If you want to know the reasons, then read on, otherwise go directly to the section Declarative Transaction Management, if you want to implement this option.

Now let's look at each approach in detail.

2.1. Software transaction management:

The Spring framework provides two tools for programmatic transaction management.

a. Using TransactionTemplate (recommended by the Spring team):

Let's look at how you can implement this type using the example code below (taken from the Spring documentation with some modifications)

Note that the code snippets are from Spring Docs.

Context Xml file:

 <!-- Initialization for data source --> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/TEST"/> <property name="username" value="root"/> <property name="password" value="password"/> </bean> <!-- Initialization for TransactionManager --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <!-- Definition for ServiceImpl bean --> <bean id="serviceImpl" class="com.service.ServiceImpl"> <constructor-arg ref="transactionManager"/> </bean> 

Service class:

 public class ServiceImpl implements Service { private final TransactionTemplate transactionTemplate; //       PlatformTransactionManager public ServiceImpl(PlatformTransactionManager transactionManager) { this.transactionTemplate = new TransactionTemplate(transactionManager); } //       ,   ,    //       xml  this.transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_UNCOMMITTED); this.transactionTemplate.setTimeout(30); //30  ///    public Object someServiceMethod() { return transactionTemplate.execute(new TransactionCallback() { //         public Object doInTransaction(TransactionStatus status) { updateOperation1(); return resultOfUpdateOperation2(); } }); }} 

If there is no return value, use the convenience class TransactionCallbackWithoutResult with an anonymous class, as shown below:

 transactionTemplate.execute(new TransactionCallbackWithoutResult() { protected void doInTransactionWithoutResult(TransactionStatus status) { updateOperation1(); updateOperation2(); } }); 


b. Using the PlatformTransactionManager implementation directly:

Let's look at this option again in the code.

 <!-- Initialization for data source --> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/TEST"/> <property name="username" value="root"/> <property name="password" value="password"/> </bean> <!-- Initialization for TransactionManager --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> public class ServiceImpl implements Service { private PlatformTransactionManager transactionManager; public void setTransactionManager( PlatformTransactionManager transactionManager) { this.transactionManager = transactionManager; } DefaultTransactionDefinition def = new DefaultTransactionDefinition(); //     -  ,       def.setName("SomeTxName"); def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); TransactionStatus status = txManager.getTransaction(def); try { //   -  } catch (Exception ex) { txManager.rollback(status); throw ex; } txManager.commit(status); } 

Now, before moving on to the next method of managing transactions, let's see how to decide which type of transaction management to choose.

Choosing between Software and Declarative Transaction Management :


2.2. Declarative transactions (Usually used in almost all scenarios of any web application)

Step 1 : Define the transaction manager in the context xml file of your spring application.

 <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"/> <tx:annotation-driven transaction-manager="txManager"/> 

Step 2 : Enable annotation support by adding an entry in the context xml file of your spring application.

OR add @EnableTransactionManagement to your configuration file, as shown below:

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

Spring recommends annotating only specific classes (and methods of specific classes) with the @Transactional annotation versus annotating interfaces.

The reason for this is that you place the annotation at the interface level, and if you use proxy classes ( proxy-target-class = «true» ) or intertwining aspect ( mode = «aspectj» ), then the transaction parameters are not recognized by the proxy infrastructure and plexuses, for example Transactional behavior will not apply.

Step 3 : Add the @Transactional annotation to a class (class method) or interface (interface method).

 <tx:annotation-driven proxy-target-class="true"> 

The default configuration is: proxy-target-class="false"


Now let's @Transactional difference between the attributes of the @Transactional annotation

@Transactional (isolation=Isolation.READ_COMMITTED)


DEFAULT : Use the default isolation level in the underlying database.

READ_COMMITTED (fixed data read): Constant, indicating that dirty read is prevented; non-repetitive reading and phantom reading may occur.

READ_UNCOMMITTED (read uncommitted data): This isolation level indicates that a transaction can read data that has not yet been deleted by other transactions.

REPEATABLE_READ (repeatability of reading): Constant, indicating that “dirty” reading and non-repeatable reading are prevented; phantom reading may appear.

SERIALIZABLE (orderability): Permanent, indicating that “dirty” reading, non-repeatable reading and phantom reading are prevented.

What do these slang mean: “dirty” reading, phantom reading or repeated reading?


@Transactional(timeout=60)

The default is the default timeout for the underlying transaction system.

Tells the tx manager about the length of time to wait for the tx downtime before deciding whether to roll back non-responding transactions.

@Transactional(propagation=Propagation.REQUIRED)

If not specified, the propagating default behavior is REQUIRED .

Other options: REQUIRES_NEW, MANDATORY, SUPPORTS, NOT_SUPPORTED, NEVER and NESTED .

REQUIRED

Indicates that the target method cannot work without active tx. If tx is already running before calling this method, then it will continue in the same tx, or the new tx will start shortly after calling this method.

REQUIRES_NEW


MANDATORY


SUPPORTS



NOT_SUPPORTED


NEVER


@Transactional (rollbackFor=Exception.class)

Default value: rollbackFor=RunTimeException.class

In Spring, all API classes throw a RuntimeException, which means that if a method fails, the container always rolls back the current transaction.

The problem is only proven exceptions. Thus, this parameter can be used for declarative rollback of a transaction if a Checked Exception occurs.

@Transactional (noRollbackFor=IllegalStateException.class)

Indicates that a rollback should not occur if the target method raises this exception.

Now the final, but most important step in transaction management is the placement of the @Transactiona l annotation. In most cases, confusion arises where the abstract should be placed: at the service level or at the DAO level?

@Transactional : Service or DAO level?

Service is the best place to place @Transactional , the service level should contain the behavior of the use case at the level of detail for user interaction, which logically goes into the transaction.

There are many CRUD applications that do not have significant business logic, have a service level, which simply transfers data between controllers and data access objects, which is not useful. In these cases, we can place the transaction annotation at the DAO level.

Therefore, in practice, you can put them anywhere, it is up to you.

In addition, if you place @Transactional at the DAO level and if your DAO level is reused by different services, then it will be difficult to place it at the DAO level, since different services may have different requirements.

If your service level retrieves objects using Hibernate, and let's say you have lazy initializations in the definition of a domain object, then you need to open a transaction at the service level, otherwise you will have to face LazyInitializationException thrown by the ORM.

Consider another example where your service level can call two different DAO methods to perform database operations. If your first DAO operation fails, the other two can be transferred, and you end the inconsistent database state. Annotation at the service level can save you from such situations.

Hope this article has helped you.

THE END

It is always interesting to see your comments or questions.

Source: https://habr.com/ru/post/431508/


All Articles