I am the founder and CEO of Data Geekery GmbH, located in Zurich, Switzerland. With our company, we have been selling database products and services around Java and SQL since 2013. Ever since my Master's studies at EPFL in 2006, I have been fascinated by the interaction of Java and SQL. Most of this experience I have obtained in the Swiss E-Banking field through various variants (JDBC, Hibernate, mostly with Oracle). I am happy to share this knowledge at various conferences, JUGs, in-house presentations and on our blog. Lukas is a DZone MVB and is not an employee of DZone and has posted 248 posts at DZone. You can read more from them at their website. View Full User Profile

Java 8 Friday Goodies: Local Transaction Scope

02.23.2014
| 4614 views |
  • submit to reddit

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.

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!

Published at DZone with permission of Lukas Eder, author and DZone MVB. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Tags: