Kotlin JDBC – Savepoints

The JDBC connection object has the ability to create SavePoint objects that are used to rollback a transaction to a specific point in time. One possible use case is providing users the ability to have “Undo” options while working in a database client program. Of course, we can also use SavePoints in Exception handlers or other areas of the program as needed.

SavePoints are used when the connection’s autoCommit property is set to false. We create a SavePoint like so

val bob = connection.setSavePoint("Bob") //Name is optional

Later on, we can pass the SavePoint to the rollback() method on the connection object.

connection.rollback(bob)

Once the connection is rollback to a SavePoint, any work performed on the conncetion after the SavePoint is lost.

Below is an example program that demonstrates using SavePoints.

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")

                //Populate the table with data
                with(rs) {
                    moveToInsertRow()
                    updateInt("ID", 1);
                    updateString("NAME", "Bob")
                    insertRow()

                    val bob = connection.setSavepoint("Bob")

                    moveToInsertRow()
                    updateInt("ID", 2)
                    updateString("NAME", "Linda")
                    insertRow()

                    val linda = connection.setSavepoint("Linda")

                    moveToInsertRow()
                    updateInt("ID", 3)
                    updateString("NAME", "Tina")
                    insertRow()

                    val tina = connection.setSavepoint("Tina")

                    print("Enter Bob, Linda, or Tina => ")
                    val choice = readLine()

                    when (choice) {
                        "Bob" -> connection.rollback(bob)
                        "Linda" -> connection.rollback(linda)
                        "Tina" -> connection.rollback(tina)
                    }
                }
                //Commit the transaction
                connection.commit()


                //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()
}

Explanation

Our program begins by establishing a connection, preparing a table, and creating an updatable ResultSet. We start inserting into the table beginning on line 32. Line 38 is where we create our first SavePoint, after inserting Bob into the table. Once we have the bob SavePoint established, we move on and insert Linda. Linda also gets a SavePoint (line 45), followed by Tina (line 52).

The user is present with a choice on line 55. When they enter Bob, the connection is rolled back to Bob, meaning that neither Linda or Tina are inserted into the database. When the user picks Linda, the connection is rolled back to the linda SavePoint, meaning that Bob and Linda are inserted into the database, but not Tina. If Tina is picked, then all three employees are inserted into the database. Line 64 commits the transaction and the inserts are performed into the database.

Advertisement
%d bloggers like this: