Texas outline

Check out Show Notes.io, the world's first dedicated podcast search and recommendation engine!

Transactional database updates with JDBI

3 March 2016

I just figured out a super-useful feature of JDBI: how to use @Transaction blocks to neatly group a set of related inserts into a single, transactional query.

As an example, suppose you had a DAO like:

public abstract class JdbiDatabaseBackend {
    @SqlUpdate("INSERT INTO users (username, email) VALUES " + 
        "(:username, :email) RETURNING id")
    public abstract int createUser(@Bind("username") String username, 
        @Bind("email") String email);

    @SqlUpdate("INSERT INTO ACCOUNTS (user_id, created) VALUES " +
        "(:user_id, current_timestamp)")
    public abstract void createUserAccount(@Bind("user_id") int userID);
}

and you wanted to chain these two queries together into one logical ‘create-user-and-account’ call:

// In e.g. a Jersey resource:
int userID = databaseBackend.createUser("ahamilton", "ahamilton@treasury.gov");
databaseBackend.createUserAccount(userID);

The above works fine, but I ran into the case where the second query (createUserAccount) could sometimes fail, leaving me with an orphaned row in my Users table. How much better would it be if I could ensure that either both queries succeeded or neither did?

Enter JDBI’s handy @Transaction annotation! Create a new method on your DAO that represents the transaction, and make your queries there:

public abstract class JdbiDatabaseBackend {
    @SqlUpdate("INSERT INTO users (username, email) VALUES " + 
        "(:username, :email) RETURNING id")
    public abstract int createUser(@Bind("username") String username, 
        @Bind("email") String email);

    @SqlUpdate("INSERT INTO ACCOUNTS (user_id, created) VALUES " +
        (:user_id, current_timestamp)")
    public abstract void createUserAccount(@Bind("user_id") int userID);

    @Transaction
    public void createUserAndAccount(String username, String email) {
        int userID = this.createUser("ahamilton", "ahamilton@treasury.gov");
        this.createUserAccount(userID);
    }
}

…replace your previous separate calls to createUser and createUserAccount with a single call to the transactional createUserAndAccount:

// In e.g. a Jersey resource:
databaseBackend.createUserAndAccount("ahamilton", "ahamilton@treasury.gov");

…and you’re good to go! No more orphaned rows, as JDBI will roll back any successful updates inside a method annotated with @Transaction if anything fails.

« Previous: Show Notes.io