Details how transactions are managed in Java's Spring framework

  • 2020-04-01 04:31:34
  • OfStack

A database transaction is a sequence of operations treated as a single unit of work. These operations are either all complete or all unsuccessful. Transaction management is an important part of an RDBMS that is oriented toward enterprise applications to ensure data integrity and consistency. The concept of a transaction can be described by the following four key properties of ACID:

Atomicity: a transaction should be considered either successful or unsuccessful for any entire sequence of operations represented by a single unit of operations.

Consistency: this represents the referential integrity of the database, the consistency of the unique primary key at the table, etc

Isolation: while there may be many transactions working on the same data set, each transaction should be isolated by someone else to prevent data corruption.

Persistence: once the transaction completes, the result of this transaction must be made permanent and cannot be removed from the database due to a system failure.

A true RDBMS database system would guarantee all four attributes for each transaction. The simple idea of issuing transactions to use SQL databases is as follows:

The begin transaction command is used to start the transaction.

Perform various delete, update, or insert operations using SQL queries.

If all the operations succeed, then commit, otherwise roll back all the operations.

The Spring framework provides different layers of abstraction on top of the underlying transaction management apis. Transaction support in Spring is intended to provide EJB replacement transactions by adding transaction functionality to pojos. Spring supports two types of programmatic and declarative transaction management. An EJB application server is required, but Spring transaction management is not required for an application server implementation.

Local and global transactions
A local transaction is a distributed system that connects to a single transactional resource like a JDBC, while a global transaction can span multiple transactional resources like a transaction.

Local transaction management can be useful in a centralized computing environment where the components and resources of an application are located on a single web site, whereas transaction management involves local data management running on a single machine. Local transactions are easier to implement.

Global transaction management requires a distributed computing environment in which all resources are distributed across multiple systems. In this case, transaction management requires work at both the local and global levels. A distributed or global transaction is executed on multiple systems, and its execution requires coordination between the global transaction management system and all local data managers of all related systems.

Programming and declaration
Spring supports two types of transaction management:

Programmatic transaction management: Spring supports two types of transaction management: Declarative transaction management: this means that your business code is managed separately from transactions. You only manage transactions using annotations or based on XML configuration.

Programmatic transaction management
Programmatic transaction management approach allows you to manage transactions with the help of programmed source code. This gives great flexibility, but it is hard to maintain.

Before we start, it has at least two database tables where we can perform various CRUD operations with the help of transactions. Let's use the Student table, which can be tested in the MySQL database and created with the following DDL:


CREATE TABLE Student(
  ID  INT NOT NULL AUTO_INCREMENT,
  NAME VARCHAR(20) NOT NULL,
  AGE INT NOT NULL,
  PRIMARY KEY (ID)
);

The second table is Marks, which we will keep marked based on students over the years. Here the SID is the foreign key of the table.


CREATE TABLE Marks(
  SID INT NOT NULL,
  MARKS INT NOT NULL,
  YEAR  INT NOT NULL
);

Let's use the PlatformTransactionManager directly implement programming method to realize the transaction. To start a new transaction, you need an instance of the appropriate transaction properties for the TransactionDefinition. In this case, we will simply create DefaultTransactionDefinition instances to use the default transaction attribute.

Once the TransactionDefinition is created, you can start the transaction by calling the getTransaction() method, which returns an instance of the TransactionStatus object. TransactionStatus object is helpful to track the current state of affairs, and finally, if all goes well, you can use the submit the PlatformTransactionManager () method to commit the transaction, otherwise you can use the rollback () rolled back to complete the operation.

Now we write the Spring JDBC application to implement the simple operations of Student and Marks tables.
The following is the content of the data access object interface file studentdao.java:


package com.yiibai;

import java.util.List;
import javax.sql.DataSource;

public interface StudentDAO {
  
  public void setDataSource(DataSource ds);
  
  public void create(String name, Integer age, Integer marks, Integer year);
  
  public List<StudentMarks> listStudents();
}

Here is the StudentMarks. Java file:


package com.yiibai;

public class StudentMarks {
  private Integer age;
  private String name;
  private Integer id;
  private Integer marks;
  private Integer year;
  private Integer sid;

  public void setAge(Integer age) {
   this.age = age;
  }
  public Integer getAge() {
   return age;
  }

  public void setName(String name) {
   this.name = name;
  }
  public String getName() {
   return name;
  }

  public void setId(Integer id) {
   this.id = id;
  }
  public Integer getId() {
   return id;
  }
  public void setMarks(Integer marks) {
   this.marks = marks;
  }
  public Integer getMarks() {
   return marks;
  }

  public void setYear(Integer year) {
   this.year = year;
  }
  public Integer getYear() {
   return year;
  }

  public void setSid(Integer sid) {
   this.sid = sid;
  }
  public Integer getSid() {
   return sid;
  }
}

The following is the content of the StudentMarksMapper. Java file:


package com.yiibai;

import java.sql.ResultSet;
import java.sql.SQLException;
import org.springframework.jdbc.core.RowMapper;

public class StudentMarksMapper implements RowMapper<StudentMarks> {
  public StudentMarks mapRow(ResultSet rs, int rowNum) throws SQLException {

   StudentMarks studentMarks = new StudentMarks();

   studentMarks.setId(rs.getInt("id"));
   studentMarks.setName(rs.getString("name"));
   studentMarks.setAge(rs.getInt("age"));
   studentMarks.setSid(rs.getInt("sid"));
   studentMarks.setMarks(rs.getInt("marks"));
   studentMarks.setYear(rs.getInt("year"));

   return studentMarks;
  }
}

The following is the implementation of the class file studentjdbctemplate.java definition of the DAO interface StudentDAO:


package com.yiibai;

import java.util.List;
import javax.sql.DataSource;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;

public class StudentJDBCTemplate implements StudentDAO {
  private DataSource dataSource;
  private JdbcTemplate jdbcTemplateObject;
  private PlatformTransactionManager transactionManager;

  public void setDataSource(DataSource dataSource) {
   this.dataSource = dataSource;
   this.jdbcTemplateObject = new JdbcTemplate(dataSource);
  }

  public void setTransactionManager(
   PlatformTransactionManager transactionManager) {
   this.transactionManager = transactionManager;
  }

  public void create(String name, Integer age, Integer marks, Integer year){

   TransactionDefinition def = new DefaultTransactionDefinition();
   TransactionStatus status = transactionManager.getTransaction(def);

   try {
     String SQL1 = "insert into Student (name, age) values (?, ?)";
     jdbcTemplateObject.update( SQL1, name, age);

     // Get the latest student id to be used in Marks table
     String SQL2 = "select max(id) from Student";
     int sid = jdbcTemplateObject.queryForInt( SQL2 );

     String SQL3 = "insert into Marks(sid, marks, year) " + 
            "values (?, ?, ?)";
     jdbcTemplateObject.update( SQL3, sid, marks, year);

     System.out.println("Created Name = " + name + ", Age = " + age);
     transactionManager.commit(status);
   } catch (DataAccessException e) {
     System.out.println("Error in creating record, rolling back");
     transactionManager.rollback(status);
     throw e;
   }
   return;
  }

  public List<StudentMarks> listStudents() {
   String SQL = "select * from Student, Marks where Student.id=Marks.sid";

   List <StudentMarks> studentMarks = jdbcTemplateObject.query(SQL, 
                     new StudentMarksMapper());
   return studentMarks;
  }
}

Now let's move the main application file mainapp.java, which is as follows:


package com.yiibai;
import java.util.List;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.yiibai.StudentJDBCTemplate;

public class MainApp {
  public static void main(String[] args) {
   ApplicationContext context = 
       new ClassPathXmlApplicationContext("Beans.xml");

   StudentJDBCTemplate studentJDBCTemplate = 
   (StudentJDBCTemplate)context.getBean("studentJDBCTemplate");
   
   System.out.println("------Records creation--------" );
   studentJDBCTemplate.create("Zara", 11, 99, 2010);
   studentJDBCTemplate.create("Nuha", 20, 97, 2010);
   studentJDBCTemplate.create("Ayan", 25, 100, 2011);

   System.out.println("------Listing all the records--------" );
   List<StudentMarks> studentMarks = studentJDBCTemplate.listStudents();
   for (StudentMarks record : studentMarks) {
     System.out.print("ID : " + record.getId() );
     System.out.print(", Name : " + record.getName() );
     System.out.print(", Marks : " + record.getMarks());
     System.out.print(", Year : " + record.getYear());
     System.out.println(", Age : " + record.getAge());
   }
  }
}

The following is the configuration file beans.xml:


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xsi:schemaLocation="http://www.springframework.org/schema/beans
  http://www.springframework.org/schema/beans/spring-beans-3.0.xsd ">

  <!-- 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 studentJDBCTemplate bean -->
  <bean id="studentJDBCTemplate"
   class="com.yiibai.StudentJDBCTemplate">
   <property name="dataSource" ref="dataSource" />
   <property name="transactionManager" ref="transactionManager" />  
  </bean>
   
</beans>

Once the source code and bean configuration files are created, let's run the application. If all goes well, this will print the following information:


------Records creation--------
Created Name = Zara, Age = 11
Created Name = Nuha, Age = 20
Created Name = Ayan, Age = 25
------Listing all the records--------
ID : 1, Name : Zara, Marks : 99, Year : 2010, Age : 11
ID : 2, Name : Nuha, Marks : 97, Year : 2010, Age : 20
ID : 3, Name : Ayan, Marks : 100, Year : 2011, Age : 25

Declarative transaction management
The declarative transaction management approach helps you manage configuration rather than hard-coded transactions in source code. This means that transactions can be managed separately from business code. Only annotations or xml-based configurations are used to manage transactions. The method that the configuration of the bean will specify is transactional. The following steps are declarative and transaction-related:

We use < Tx: advice / > Tag, which will create a pointcut that we've defined that matches all the transactions we want to make, and references the transaction advice methods in it while processing the recommendations.

If a method's name has been included in the transaction configuration, then create an opinion that will invoke the method before starting the transaction.

The target method will be executed in a try/ catch block.

If the method completes normally and the AOP recommendation commits the transaction successfully, otherwise the rollback is performed.

Let's see why the above steps work, but before we get started, it's important that it has at least two database tables where we can perform various CRUD operations with the help of transactions. Let's use the Student table, which can be tested in the MySQL database and created with the following DDL:


CREATE TABLE Student(
  ID  INT NOT NULL AUTO_INCREMENT,
  NAME VARCHAR(20) NOT NULL,
  AGE INT NOT NULL,
  PRIMARY KEY (ID)
);

The second table is Marks, which we will keep marked based on students over the years. Here the SID is the foreign key for the table Student.


CREATE TABLE Marks(
  SID INT NOT NULL,
  MARKS INT NOT NULL,
  YEAR  INT NOT NULL
);

Again, let's look at the case of contrast.
The following is the content of the data access object interface file studentdao.java:


package com.yiibai;

import java.util.List;
import javax.sql.DataSource;

public interface StudentDAO {
  
  public void setDataSource(DataSource ds);
  
  public void create(String name, Integer age, Integer marks, Integer year);
  
  public List<StudentMarks> listStudents();
}

Here is the StudentMarks. Java file:


package com.yiibai;

public class StudentMarks {
  private Integer age;
  private String name;
  private Integer id;
  private Integer marks;
  private Integer year;
  private Integer sid;

  public void setAge(Integer age) {
   this.age = age;
  }
  public Integer getAge() {
   return age;
  }

  public void setName(String name) {
   this.name = name;
  }
  public String getName() {
   return name;
  }

  public void setId(Integer id) {
   this.id = id;
  }
  public Integer getId() {
   return id;
  }
  public void setMarks(Integer marks) {
   this.marks = marks;
  }
  public Integer getMarks() {
   return marks;
  }

  public void setYear(Integer year) {
   this.year = year;
  }
  public Integer getYear() {
   return year;
  }

  public void setSid(Integer sid) {
   this.sid = sid;
  }
  public Integer getSid() {
   return sid;
  }
}

The following is the content of the StudentMarksMapper. Java file:


package com.yiibai;

import java.sql.ResultSet;
import java.sql.SQLException;
import org.springframework.jdbc.core.RowMapper;

public class StudentMarksMapper implements RowMapper<StudentMarks> {
  public StudentMarks mapRow(ResultSet rs, int rowNum) throws SQLException {

   StudentMarks studentMarks = new StudentMarks();

   studentMarks.setId(rs.getInt("id"));
   studentMarks.setName(rs.getString("name"));
   studentMarks.setAge(rs.getInt("age"));
   studentMarks.setSid(rs.getInt("sid"));
   studentMarks.setMarks(rs.getInt("marks"));
   studentMarks.setYear(rs.getInt("year"));

   return studentMarks;
  }
}

The following is the implementation of the class file studentjdbctemplate.java definition of the DAO interface StudentDAO:


package com.yiibai;

import java.util.List;
import javax.sql.DataSource;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;

public class StudentJDBCTemplate implements StudentDAO{
  private JdbcTemplate jdbcTemplateObject;

  public void setDataSource(DataSource dataSource) {
   this.jdbcTemplateObject = new JdbcTemplate(dataSource);
  }

  public void create(String name, Integer age, Integer marks, Integer year){

   try {
     String SQL1 = "insert into Student (name, age) values (?, ?)";
     jdbcTemplateObject.update( SQL1, name, age);

     // Get the latest student id to be used in Marks table
     String SQL2 = "select max(id) from Student";
     int sid = jdbcTemplateObject.queryForInt( SQL2 );

     String SQL3 = "insert into Marks(sid, marks, year) " + 
            "values (?, ?, ?)";
     jdbcTemplateObject.update( SQL3, sid, marks, year);

     System.out.println("Created Name = " + name + ", Age = " + age);
     // to simulate the exception.
     throw new RuntimeException("simulate Error condition") ;
   } catch (DataAccessException e) {
     System.out.println("Error in creating record, rolling back");
     throw e;
   }
  }

  public List<StudentMarks> listStudents() {
   String SQL = "select * from Student, Marks where Student.id=Marks.sid";

   List <StudentMarks> studentMarks=jdbcTemplateObject.query(SQL, 
   new StudentMarksMapper());
   return studentMarks;
  }
}

Now we move the main application file, mainapp.java, as follows:


package com.yiibai;
import java.util.List;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MainApp {
  public static void main(String[] args) {
   ApplicationContext context = 
       new ClassPathXmlApplicationContext("Beans.xml");

   StudentDAO studentJDBCTemplate = 
   (StudentDAO)context.getBean("studentJDBCTemplate");
   
   System.out.println("------Records creation--------" );
   studentJDBCTemplate.create("Zara", 11, 99, 2010);
   studentJDBCTemplate.create("Nuha", 20, 97, 2010);
   studentJDBCTemplate.create("Ayan", 25, 100, 2011);

   System.out.println("------Listing all the records--------" );
   List<StudentMarks> studentMarks = studentJDBCTemplate.listStudents();
   for (StudentMarks record : studentMarks) {
     System.out.print("ID : " + record.getId() );
     System.out.print(", Name : " + record.getName() );
     System.out.print(", Marks : " + record.getMarks());
     System.out.print(", Year : " + record.getYear());
     System.out.println(", Age : " + record.getAge());
   }
  }
}

The following is the configuration file beans.xml:


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:tx="http://www.springframework.org/schema/tx"
  xmlns:aop="http://www.springframework.org/schema/aop"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
  http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 
  http://www.springframework.org/schema/tx
  http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
  http://www.springframework.org/schema/aop
  http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

  <!-- 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="cohondob"/>
  </bean>
 
  <tx:advice id="txAdvice" transaction-manager="transactionManager">
   <tx:attributes>
   <tx:method name="create"/>
   </tx:attributes>
  </tx:advice>
 
  <aop:config>
   <aop:pointcut id="createOperation" 
   expression="execution(* com.yiibai.StudentJDBCTemplate.create(..))"/>
   <aop:advisor advice-ref="txAdvice" pointcut-ref="createOperation"/>
  </aop:config>
 
  <!-- Initialization for TransactionManager -->
  <bean id="transactionManager"
  class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
   <property name="dataSource" ref="dataSource" />  
  </bean>

  <!-- Definition for studentJDBCTemplate bean -->
  <bean id="studentJDBCTemplate" 
  class="com.yiibai.StudentJDBCTemplate">
   <property name="dataSource" ref="dataSource" /> 
  </bean>

</beans>

Create the source code and the bean configuration file to complete, and let's run the application. If all goes well, this will print the following, which will throw an exception. In this case, the transaction will be rolled back and no records will be created in the database table.


------Records creation--------
Created Name = Zara, Age = 11
Exception in thread "main" java.lang.RuntimeException: simulate Error condition

You can try removing the exception from the above example, in which case the transaction should be committed and you should see the record in the database.


Related articles: