Java 8 Friday Goodies: Local Transaction Scope


At Data Geekery, we love Java. And as we’re really into jOOQ’s fluent API and query DSL, we’re absolutely thrilled about what Java 8 will bring to our ecosystem. We have blogged a couple of times about some nice Java 8 goodies, and now we feel it’s time to start a new blog series, the…

Java 8 Friday

Every Friday, we’re showing you a couple of nice new tutorial-style Java 8 features, which take advantage of lambda expressions, extension methods, and other great stuff. You’ll find the source code on GitHub. tweet this

Java 8 Goodie: Local Transaction Scope

The JavaScript folks often abuse anonymous functions to create local scope. Like any other language feature, this can be abused, but in some contexts, local scoping is really awesome. Java also allows for local scoping, although until Java 8, this has been equally cumbersome:

JavaScript

(function() {
    var local = function() { 
            scoping(); 
        },
        scoping = function() { 
            alert('If you really must');
        };

    local();
})();

Java

new Object() {
    void local() {
        scoping();
    }
    void scoping() {
        System.out.println(
            "Ouch, my fingers. Too much typing");
    }
}.local();

Both examples look really awkward, although the JavaScript folks call this a design pattern. No one would create such local scope in Java, even if the two pieces of code are roughly equivalent.

tweet thisAwkwardness can be a design pattern in JavaScript.

Local scoping in Java 8

But with Java 8, everything changes, and so does local scoping. Let’s have a look at how we can creat a local semantic scope for transactions. For this, we’ll create two types. The Transactional interface:

@FunctionalInterface
interface Transactional {
    void run(DSLContext ctx);
}

For the example, we’re going to be using jOOQ to avoid checked exceptions and verbose statement creation. You can replace it by your SQL API of choice. So, jOOQ provides us with a locally scoped ctx object, which implicitly contains the transaction state. This transaction state is generated using a TransactionRunner:

class TransactionRunner {
    private final boolean silent;
    private final Connection connection;

    TransactionRunner(Connection connection) {
        this(connection, true);
    }

    TransactionRunner(Connection connection,
                      boolean silent) {
        this.connection = connection;
        this.silent = silent;
    }

    void run(Transactional tx) {
        // Initialise some jOOQ objects
        final DefaultConnectionProvider c =
            new DefaultConnectionProvider(connection);
        final Configuration configuration =
            new DefaultConfiguration()
                .set(c).set(SQLDialect.H2);

        try {
            // Run the transaction and pass a jOOQ
            // DSLContext object to it
            tx.run(DSL.using(configuration));

            // If we get here, then commit the
            // transaction
            c.commit();
        }
        catch (RuntimeException e) {

            // Any exception will cause a rollback
            c.rollback();
            System.err.println(e.getMessage());

            // Eat exceptions in silent mode.
            if (!silent)
                throw e;
        }
    }
}

The above is framework code, which we’ll write only once. From now on, we can use the above API trivially in our Java programs. For this, we’ll set up a TransactionRunner like such:

public static void main(String[] args) 
throws Exception {
    Class.forName("org.h2.Driver");
    try (Connection c = DriverManager.getConnection(
            "jdbc:h2:~/test-scope-goodies", 
            "sa", "")) {
        c.setAutoCommit(false);
        TransactionRunner silent = 
            new TransactionRunner(c);

        // Transactional code here ...
    }
}

And now, behold the wonders of Java 8!

// This is a transaction
silent.run(ctx -> {
    ctx.execute("drop table if exists person");
    ctx.execute("create table person(" + 
                "  id integer," +
                "  first_name varchar(50)," +
                "  last_name varchar(50)," +
                "  primary key(id)"+
                ")");
});

// And this is also one transaction
silent.run(ctx -> {
    ctx.execute("insert into person" +
                "  values(1, 'John', 'Smith');");
    ctx.execute("insert into person" +
                "  values(1, 'Steve', 'Adams');");
    // Ouch, fails -------^
    // Transaction rolls back
});

// And this is also one transaction
silent.run(ctx -> {
    ctx.execute("insert into person" + 
                "  values(2, 'Jane', 'Miller');");
    // Works, yay!
});

// And this is also one transaction
silent.run(ctx -> {
    ctx.execute("insert into person" +
                "  values(2, 'Anne', 'Roberts');");
    // Ouch, fails -------^
    // Transaction rolls back
});

What do we get from the above? Let’s check:

silent.run(ctx -> {
    System.out.println(
        ctx.fetch("select * from person"));
});

The above program will yield this output:

SQL [insert into person values(1, 'Steve', 'Adams');];
Unique index or primary key violation: "PRIMARY KEY ON PUBLIC.PERSON(ID)"; SQL statement:
insert into person values(1, 'Steve', 'Adams'); [23505-174]
SQL [insert into person values(2, 'Anne', 'Roberts');];
Unique index or primary key violation: "PRIMARY KEY ON PUBLIC.PERSON(ID)"; SQL statement:
insert into person values(2, 'Anne', 'Roberts'); [23505-174]
+----+----------+---------+
|  ID|FIRST_NAME|LAST_NAME|
+----+----------+---------+
|   2|Jane      |Miller   |
+----+----------+---------+

So, our commits and rollbacks worked as expected!

Nested transactions

We can also create nested calls to our TransactionRunner, e.g. when we’re inside methods calling other methods. For this, would have to adapt our TransactionRunner to count the nesting level, and remove the “silent” functionality. On the other hand, it would be very easy to implement savepoint functionality this way. Each time we nest another transaction, we’ll create a new savepoint.

Conclusion

As always in this series, we didn’t invent anything new. All of these things could be done with vanilla Java 7. But the client code of this TransactionRunner certainly wouldn’t look as lean as our lambdas.

Next week in this blog series, we’re going to look at how Java 8 will allow you to define local caching scope very easily, so stay tuned!

More on Java 8

In the mean time, have a look at Eugen Paraschiv’s awesome Java 8 resources page

Tags: , , , ,

2 responses to “Java 8 Friday Goodies: Local Transaction Scope”

  1. Ryan How says :

    That’s great. I’ve been doing this with JOOQ with Java 7 from the start. Anything to get away from those try finally blocks for every transaction!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 1,955 other followers

%d bloggers like this: