JDBC has the ability rollback transactions. This example shows how to rollback a transaction in the case of an exception. Exception rollbacks are a common pattern because in many cases, committing a transaction after an error can leave the database in an inconsistent state. Let’s take a look at a short example of how to rollback a transaction.
connection.autoCommit = false createOrTruncateTable(connection) //Create an updatable result set val rs = connection .createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE) .executeQuery("SELECT * FROM BURGERS.EMPLOYEES") try { /* Lines 32-46 omitted */ //Commit the previous transaction (lines 32-46) connection.commit() //Now let's do an insert but have it fail with(rs){ moveToInsertRow() updateInt("ID", 3) updateString("NAME", "Tina") insertRow() } throw Exception("Simulated") } catch (e: Exception){ println("Caught simulated exception. Rolling back...") //We can rollback the current transaction. Tina will never //get inserted into the database connection.rollback() }
The above code fragement uses an expanded try-catch block. At the start of the code fragement, turn off autoCommit on the connection object and create an updatable ResultSet that let’s us insert rows into the database table. There is an ommitted portion of code that inserts some rows into the database.
Then we commit the first transaction. So far so good. The rows are entered cleanly into the database. Then our example continues by inserting another record into the table. Rather than committing the transaction, we instead throw an Exception to act as if something went wrong with the insertion.
The catch block found at the end of the code fragement shows what to do when an exception is thrown in the middle of a transaction. In our example, we notify the user that we are rolling back the changes. Then we call rollback() on the connection object. Rollback() resets the transaction and the program can act as if the last transaction never happened.
Complete Example
Below is a complete Kotlin program that shows the demonstration code in its entirety.
pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>OCJP-DB</groupId> <artifactId>ocjpdb</artifactId> <version>1.0-SNAPSHOT</version> <properties> <kotlin.version>1.2.10</kotlin.version> <main.class>stonesoupprogramming.MainKt</main.class> </properties> <dependencies> <dependency> <groupId>org.apache.derby</groupId> <artifactId>derby</artifactId> <version>10.14.1.0</version> </dependency> <dependency> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-stdlib-jre8</artifactId> <version>${kotlin.version}</version> </dependency> <dependency> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-test</artifactId> <version>${kotlin.version}</version> <scope>test</scope> </dependency> </dependencies> <build> <sourceDirectory>src/main/kotlin</sourceDirectory> <plugins> <plugin> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-maven-plugin</artifactId> <version>${kotlin.version}</version> <executions> <execution> <id>compile</id> <phase>compile</phase> <goals> <goal>compile</goal> </goals> </execution> <execution> <id>test-compile</id> <phase>test-compile</phase> <goals> <goal>test-compile</goal> </goals> </execution> </executions> <configuration> <jvmTarget>1.8</jvmTarget> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>2.6</version> <configuration> <archive> <manifest> <addClasspath>true</addClasspath> <mainClass>${main.class}</mainClass> </manifest> </archive> </configuration> </plugin> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>exec-maven-plugin</artifactId> <version>1.2.1</version> <executions> <execution> <phase>test</phase> <goals> <goal>java</goal> </goals> </execution> </executions> <configuration> <mainClass>${main.class}</mainClass> </configuration> </plugin> </plugins> </build> </project>
Employees.kt
package stonesoupprogramming import java.sql.Connection import java.sql.DriverManager import java.sql.ResultSet import java.util.* fun main(args: Array<String>) { val properties = Properties() //Populate the properties file with user name and password with(properties) { put("user", "admin") put("password", "pw") } //Open a connection to the database DriverManager .getConnection("jdbc:derby:stonesoup;create=true", properties) .use { connection -> //Set autoCommit to false to manually manage transactions connection.autoCommit = false createOrTruncateTable(connection) //Create an updatable result set val rs = connection .createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE) .executeQuery("SELECT * FROM BURGERS.EMPLOYEES") try{ //Populate the table with data with(rs) { moveToInsertRow() updateInt("ID", 1); updateString("NAME", "Bob") insertRow() moveToInsertRow() updateInt("ID", 2) updateString("NAME", "Linda") insertRow() } //Commit the transaction connection.commit() //Now let's do an insert but have it fail with(rs){ moveToInsertRow() updateInt("ID", 3) updateString("NAME", "Tina") insertRow() } throw Exception("Simulated") } catch (e: Exception){ println("Caught simulated exception. Rolling back...") //We can rollback the current transaction. Tina will never //get inserted into the database connection.rollback() } //Read only queries are still transactions val rsq = connection .createStatement() .executeQuery("SELECT * FROM BURGERS.EMPLOYEES") with(rsq) { while (next()) { println("${getInt("ID")}\t${getString("NAME")}") } } //So we need to commit this query also even though it doesn't change anything connection.commit() } } private fun createOrTruncateTable(connection: Connection) { val metaData = connection.metaData if (!metaData.getTables(null, "BURGERS", "EMPLOYEES", null).next()) { connection .createStatement() .executeUpdate("CREATE TABLE BURGERS.EMPLOYEES (ID INT PRIMARY KEY, NAME VARCHAR(255))") } else { connection .createStatement() .executeUpdate("TRUNCATE TABLE BURGERS.EMPLOYEES") } connection.commit() }