sql

JDBC Nested Transactions Example

A nested transaction is used to provide a transactional guarantee for a subset of operations performed within the scope of a larger transaction. Doing this allows us to commit and abort the subset of operations independently of the larger transaction.

This operation is theoretically possible, however, in JDBC 3.0 we can’t achieve this as easily as the definition looks. In JDBC, programmers can achieve this effect using savepoints.
 
 
 
 

1. Introduction

In this JDBC Nested Transactions example, we will see how to achieve this phenomenon by using the JDBC Savepoints.

1.1 Why JDBC Transactions?

In the database systems, Transaction is a set of actions to be carried out as a single or an atomic action. Either all of the actions are carried out, or none of them.

The classic example of when transactions are necessary is the example of bank accounts. Let’s say you need to transfer $100 from one account to the other. You do so by subtracting $100 from the first account, and adding $100 to the second account. If this process fails after you have subtracted the $100 from the first bank account, the $100 are never added to the second bank account and hence the money is lost in the cyber space.

To solve this problem, the subtraction and addition of the $100 are grouped into a transaction. If the subtraction succeeds, but the addition fails, you can roll-back the first subtraction. That way the database is left in the same state as before the subtraction was executed.

1.2 What are JDBC Transactions?

A transaction is a group of operation used to perform a single task; if all operations in the group are a success then the task is finished and the transaction is successfully completed. But if anyone operation in the group is failed, then the task is failed and the transaction is failed.

Fig. 1: Java Database Connectivity (JDBC) Transactions
Fig. 1: Java Database Connectivity (JDBC) Transactions

Let’s consider a movie ticket booking is a transaction. This task contains 4 operations:

  1. Choose the seats.
  2. Reserve the seats.
  3. Payment.
  4. Issue the movie tickets.

If all the above 4 operations are done successfully then a transaction is finished successfully. But if any one operation is failed in the middle then all operations are canceled and the transaction is failed.

1.3 Properties of Transaction Management

Every JDBC Transaction follows some transaction properties and these are called as ACID properties. ACID stands for Atomicity, Consistency, Isolation, and Durability.

Fig. 2: JDBC Transaction Management Properties
Fig. 2: JDBC Transaction Management Properties

  • Atomicity: Atomicity of a transaction is defined as either all the operations can be done or all the operation can be undone, but some operations are done and some operation is undone should not occur.
  • Consistency: Consistency means, after a transaction is completed successfully, the data in the datastore should be a reliable data and this reliable data is also called as consistent data.
  • Isolation: Isolation means if two transactions are going on the same data than one transaction will not disturb the another transaction.
  • Durability: Durability means after a transaction is completed the data in the datastore will be permanent until another transaction is going to be performed on that data.

1.4 Types of Transaction Management

  • Local Transaction: A local transaction means, all operation in a transaction is executed against one database. For e.g.: If transfer money from first account to second account belongs to the same bank then the transaction is a local transaction.
  • Distributed or Global Transaction: A global transaction means, all operations in a transaction are executed against multiple databases. For e.g.: If transfer money from first account to second account belongs to different banks then the transaction is a global transaction.
Tip
JDBC technology only performs the Local Transactions. For Global Transaction in Java, we need either EJB or Spring framework.

1.5 Advantage of Transaction Management

  • Fast Performance: JDBC Transactions makes the performance fast because the database is hit only at the time of commit.

1.6 Things Required for JDBC Transactions

To perform the transaction management in JDBC, we need to follow the below steps:

  1. Disable auto commit mode of JDBC.
  2. Put all operations of a transaction in try block.
  3. If all operations are done successfully then commit in the try block, otherwise rollback in a catch block.

By default, in JDBC auto-commit mode is enabled but developers need to disable it. To disable, call the setAutoCommit() method of the connection interface.

Method Syntax

1
connObj.setAutoCommit(false);

1.7 Download and Install MySQL

In this example, we are using the MySQL database to perform the JDBC Transaction Management. You can watch this video in order to download and install the MySQL database on your Windows operating system.

Now, open up the Eclipse IDE and let’s start building the application!

2. JDBC Nested Transactions Example

2.1 Tools Used

We are using Eclipse Kepler SR2, JDK 7, MySQL Database and Maven. Having said that, we have tested the code against JDK 1.8 and it works well.

2.2 Project Structure

Firstly, let’s review the final project structure, in case you are confused about where you should create the corresponding files or folder later!

Fig. 3: JDBC Nested Transactions Application Project Structure
Fig. 3: JDBC Nested Transactions Application Project Structure

2.3 Project Creation

This section will demonstrate on how to create a Java Maven project with Eclipse. In Eclipse IDE, go to File -> New -> Maven Project.

Fig. 4: Create Maven Project
Fig. 4: Create Maven Project

In the New Maven Project window, it will ask you to select a project location. By default, ‘Use default workspace location‘ will be selected. Select the ‘Create a simple project (skip archetype selection)‘ checkbox and just click on next button to proceed.

Fig. 5: Project Details
Fig. 5: Project Details

It will ask you to ‘Enter a group and artifact id for the project.’ We will input the details as shown in the below image. The version number will be by default 0.0.1-SNAPSHOT.

Fig. 6: Archetype Parameters
Fig. 6: Archetype Parameters

Click on Finish and the creation of a maven project will be completed. If you observe, it has downloaded the maven dependencies and a pom.xml file will be created. It will have the following code:

pom.xml

1
2
3
4
5
6
    <modelVersion>4.0.0</modelVersion>
    <groupId>JdbcTransactions</groupId>
    <artifactId>JdbcTransactions</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</project>

We can start adding the dependencies that developers want like MySQL, Log4J etc. Let’s start building the application!

3. Application Building

Below are the steps involved in developing this application.

3.1 Database & Table Creation

This tutorial uses a database called tutorialDb. The database is not included when you create the project in eclipse so you need to first create the database to follow this tutorial.

  • Create a new database tutorialDb as:
1
CREATE DATABASE tutorialDb;
  • Use the created database tutorialDb to create table as:
1
USE tutorialDb;
  • Create the table user_table as shown below:
1
CREATE TABLE user_table (user_id int(11), user_name varchar(15), created_by varchar(100), created_date DATE, PRIMARY KEY (user_id));

If everything goes well, the table will be shown as below in the MySQL workbench:

Fig. 7: Database & Table Creation
Fig. 7: Database & Table Creation

3.2 Maven Dependencies

In this example, we are using the latest MySQL version (i.e. mysql-connector-java-5.1.21) and Log4J dependencies. The updated file will have the following code:

pom.xml

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
    <modelVersion>4.0.0</modelVersion>
    <groupId>JdbcTransactions</groupId>
    <artifactId>JdbcTransactions</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>
    <dependencies>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.21</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.16</version>
        </dependency>
    </dependencies>
    <build>
        <finalName>${project.artifactId}</finalName>
    </build>
</project>

3.3 Java Class Creation

Let’s create the required Java files. Right click on src/main/java folder, New -> Package.

Fig. 8: Java Package Creation
Fig. 8: Java Package Creation

A new pop window will open where we will enter the package name as: com.jcg.jdbc.transactions.example.

Fig. 9: Java Package Name (com.jcg.jdbc.transactions.example)
Fig. 9: Java Package Name (com.jcg.jdbc.transactions.example)

Once the package is created, we will need to create the implementation classes. Right click on the newly created package, New -> Class.

Fig. 10: Java Class Creation
Fig. 10: Java Class Creation

A new pop window will open and enter the file name as: JDBCTransactionsDemo. The implementation class will be created inside the package: com.jcg.jdbc.transactions.example.

Fig. 11: Java Class (JDBCTransactionsDemo.java)
Fig. 11: Java Class (JDBCTransactionsDemo.java)

Repeat the step (i.e. Fig. 10) and enter the filename as JDBCTransactionSavePointDemo. The Savepoint implementation class will be created inside the package: com.jcg.jdbc.transactions.example.

Fig. 12: Java Class (JDBCTransactionSavePointDemo.java)
Fig. 12: Java Class (JDBCTransactionSavePointDemo.java)

3.3.1 Implementation of Main Class

In JDBC, Connection interface provides different methods to carefully manage the JDBC Transactions,

MethodDescription
void setAutoCommit(boolean status)It is set by default to true i.e. each transaction is auto committed to the database. Developers need to set it to false so that they themselves can commit or rollback the data based on the conditions.
void commit()Commits the transaction (i.e. data) to the database.
void rollback()Cancels the transaction from the database. Usually, developers add this statement in catch block.
setSavepoint()Allows developers to assign or create a logical group and by using rollback(String) method we can roll-back all the statements after the given savepoint has been set.

 

Let’s see the simple example of transaction management using PreparedStatement. Add the following code to it:

JDBCTransactionsDemo.java

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
package com.jcg.jdbc.transactions.example;
 
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
 
import org.apache.log4j.Logger;
 
public class JDBCTransactionsDemo {
 
    // JDBC Driver Name & Database URL
    private final static String JDBC_DRIVER = "com.mysql.jdbc.Driver"
    private final static String JDBC_DB_URL = "jdbc:mysql://localhost:3306/tutorialDb";
 
    // JDBC Database Credentials
    private final static String JDBC_USER = "root";
    private final static String JDBC_PASS = "";
 
    private static Connection connObj;
    public final static Logger logger = Logger.getLogger(JDBCTransactionsDemo.class);
 
    public static void connectDb() {
        try {
            Class.forName(JDBC_DRIVER);
            connObj = DriverManager.getConnection(JDBC_DB_URL, JDBC_USER, JDBC_PASS);
            logger.info("\n=======DATABASE CONNECTION OPEN=======\n");
        } catch(Exception sqlException) {
            sqlException.printStackTrace();
        }
    }
 
    public static void disconnectDb() {
        try {
            connObj.close();
            logger.info("\n=======DATABASE CONNECTION CLOSED=======\n");
        } catch (Exception sqlException) {
            sqlException.printStackTrace();
        }
    }
 
    public static void showTableRecords(String table_name) throws SQLException {
        ResultSet rsObj = null;
        Statement stmtObj = connObj.createStatement(); 
        rsObj = stmtObj.executeQuery("select user_id, user_name, created_date from " + table_name + ";");
        if(!rsObj.next()) {
            logger.info("No Records In The Table\n");
        } else {
            logger.info("Id: "+ rsObj.getInt("user_id") + ", Name: " + rsObj.getString("user_name") + ", Joining Date: " + rsObj.getInt("created_date") + "\n");
        }
    }
 
    public static void saveUserDetails(int userId, String userName, String sysName) {
 
        PreparedStatement insertStatement = null,
                updateStatement = null;
 
        try {
            connObj.setAutoCommit(false);
 
            logger.info("\n=======Inserting Data In The Table=======\n");
            String insertTableSQL = "insert into user_table (user_id, user_name, created_by, created_date) VALUES (?, ?, ?, ?);";
 
            insertStatement = connObj.prepareStatement(insertTableSQL);
            insertStatement.setInt(1, userId);
            insertStatement.setString(2, userName);
            insertStatement.setString(3, sysName);
            insertStatement.setTimestamp(4, new java.sql.Timestamp(new java.util.Date().getTime()));
            insertStatement.executeUpdate();        // Record Is Not Committed In Database At This Moment
 
            logger.info("\n=======Updating Value In The Table=======\n");
            String updateTableSQL = "update user_table set user_name =? where user_id = ?";
 
            updateStatement = connObj.prepareStatement(updateTableSQL);
 
            // Line No. 79 - This line Will Result In An Exception & The Data Will Rollback Including The 'Insert' Statement.
            updateStatement.setString(1, "A Very Very Long String Resulting In A Database Error");
 
            // updateStatement.setString(1, "Lucifer Star");
            updateStatement.setInt(2, userId);
            updateStatement.executeUpdate();
 
            connObj.commit();
            showTableRecords("user_table");
        } catch (Exception sqlException) {
            try {
                connObj.rollback();
                logger.info("\n=======!Db Exception! Rolling Back Data=======\n");
                showTableRecords("user_table");
            } catch (SQLException sqlEx) {
                sqlEx.printStackTrace();
            }          
        } finally {
            try {
                if (insertStatement != null ) {
                    insertStatement.close();
                }
                if (updateStatement != null ) {
                    updateStatement.close();
                }
                connObj.setAutoCommit(true);
            } catch (Exception sqlException) {
                sqlException.printStackTrace();
            }
        }
    }
 
    public static void main(String[] args) {
        connectDb();
        saveUserDetails(101, "Harry Potter", "sys_admin");
        disconnectDb();
    }
}

We will try to execute the transaction in the above example and the below result will be displayed.

Fig. 13: Output for JDBCTransactionsDemo.java
Fig. 13: Output for JDBCTransactionsDemo.java

Here we should note that, UPDATE operation does not run correctly, hence the INSERT entry isn’t made and the database remains unchanged.

Fig. 14: Output from MySQL Workbench
Fig. 14: Output from MySQL Workbench

3.3.2 Implementation of Savepoints Main Class

The JDBC API provides the connObj.setSavepoint() method that marks a point to which the transaction can be rolled back. The rollback() method is an overloaded method to take a savepoint as its argument: connObj.rollback(savepointObj).

The following code will help you understand how the savepoints are used in a JDBC Transaction:

JDBCTransactionSavePointDemo.java

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
package com.jcg.jdbc.transactions.example;
 
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Savepoint;
import java.sql.Statement;
 
import org.apache.log4j.Logger;
 
public class JDBCTransactionSavePointDemo {
 
    // JDBC Driver Name & Database URL
    private final static String JDBC_DRIVER = "com.mysql.jdbc.Driver"
    private final static String JDBC_DB_URL = "jdbc:mysql://localhost:3306/tutorialDb";
 
    // JDBC Database Credentials
    private final static String JDBC_USER = "root";
    private final static String JDBC_PASS = "";
 
    private static Connection connObj;
    public final static Logger logger = Logger.getLogger(JDBCTransactionsDemo.class);
 
    public static void connectDb() {
        try {
            Class.forName(JDBC_DRIVER);
            connObj = DriverManager.getConnection(JDBC_DB_URL, JDBC_USER, JDBC_PASS);
            logger.info("\n=======DATABASE CONNECTION OPEN=======\n");
        } catch(Exception sqlException) {
            sqlException.printStackTrace();
        }
    }
 
    public static void disconnectDb() {
        try {
            connObj.close();
            logger.info("\n=======DATABASE CONNECTION CLOSED=======\n");
        } catch (Exception sqlException) {
            sqlException.printStackTrace();
        }
    }
 
    public static void showTableRecords(String table_name) throws SQLException {
        ResultSet rsObj = null;
        Statement stmtObj = connObj.createStatement(); 
        rsObj = stmtObj.executeQuery("select user_id, user_name, created_date from " + table_name + ";");
        if(!rsObj.next()) {
            logger.info("No Records In The Table\n");
        } else {
            logger.info("Id: "+ rsObj.getInt("user_id") + ", Name: " + rsObj.getString("user_name") + ", Joining Date: " + rsObj.getInt("created_date") + "\n");
        }
    }
 
    public static void saveUserDetails(int userId, String userName, String sysName) {
 
        PreparedStatement insertStatement = null,
                updateStatement = null;
 
        Savepoint saveObj =null;
 
        try {
            connObj.setAutoCommit(false);
 
            logger.info("\n=======Inserting Data In The Table=======\n");
            String insertTableSQL = "insert into user_table (user_id, user_name, created_by, created_date) VALUES (?, ?, ?, ?);";
 
            insertStatement = connObj.prepareStatement(insertTableSQL);
            insertStatement.setInt(1, userId);
            insertStatement.setString(2, userName);
            insertStatement.setString(3, sysName);
            insertStatement.setTimestamp(4, new java.sql.Timestamp(new java.util.Date().getTime()));
            insertStatement.executeUpdate();        // Record Is Not Committed In Database At This Moment
 
            saveObj = connObj.setSavepoint();   // Savepoint Will Allow To RollBack Only Till This Checkpoint Incase An Exception Occurs.
 
            logger.info("\n=======Updating Value In The Table=======\n");
            String updateTableSQL = "update user_table set user_name =? where user_id = ?";
 
            updateStatement = connObj.prepareStatement(updateTableSQL);
 
            // Line No. 84 - This line Will Result In An Exception & The Data Will Rolled-Back
            updateStatement.setString(1, "A Very Very Long String Resulting In A Database Error");
 
            // updateStatement.setString(1, "Lucifer Star");
            updateStatement.setInt(2, userId);
            updateStatement.executeUpdate();
 
            connObj.commit();
            showTableRecords("user_table");
        } catch (Exception sqlException) {
            try {
                connObj.rollback(saveObj);                  // Here, The Rollback Command Will Execute But The 'Insert' Will Still Be Committed To The Database As We Have Introduced A Savepoint at Line No. 76
                logger.info("\n=======!Db Exception! Rolling Back The Update Data But Not Insert=======\n");
                showTableRecords("user_table");
            } catch (SQLException sqlEx) {
                sqlEx.printStackTrace();
            }          
        } finally {
            try {
                if (insertStatement != null ) {
                    insertStatement.close();
                }
                if (updateStatement != null ) {
                    updateStatement.close();
                }
                connObj.setAutoCommit(true);
            } catch (Exception sqlException) {
                sqlException.printStackTrace();
            }
        }
    }
 
    public static void main(String[] args) {
        connectDb();
        saveUserDetails(101, "Harry Potter", "sys_admin");
        disconnectDb();
    }
}

Do Note:

  • The JDBC API provides the connObj.releaseSavepoint(savepointObj) method that removes the specified savepoint from the current transaction. A savepoint that has been released become invalid and cannot be rolled back to. Any attempt to roll back the transaction to a released savepoint causes a SQLException.
  • A savepoint is automatically released and becomes invalid when the transaction is committed or when the entire transaction is rolled back.

4. Run the Application

To run the application, Right click on the JDBCTransactionSavePointDemo class, Run As -> Java Application.

Fig. 15: Run Application
Fig. 15: Run Application

5. Project Demo

The code shows the following status as output:

Fig. 16: Application Output
Fig. 16: Application Output

That’s all for this post. Happy Learning!!

6. Conclusion

Here, in this example, we tried to understand how to manage the JDBC Operations through transactions and how to make check points by means of Savepoint class.

7. Download the Eclipse Project

This was an example of JBDC Transactions Using Savepoints.

Download
You can download the full source code of this example here: Jdbc Transactions

Yatin

An experience full-stack engineer well versed with Core Java, Spring/Springboot, MVC, Security, AOP, Frontend (Angular & React), and cloud technologies (such as AWS, GCP, Jenkins, Docker, K8).
Subscribe
Notify of
guest


This site uses Akismet to reduce spam. Learn how your comment data is processed.

1 Comment
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Pooja
Pooja
6 years ago

I want to implement connection pool along with jdbc transaction management into my code which is having a number of sql updates queries.Need reference how to implement.

Back to top button