Skip to main content

Mutable Ideas

SQL love with JDBI

Most of our interactions with SQL databases are done directly, without any ORM due performance issues. Only recently I got to know JDBI and after a while I felt in love. Follow this quick walkthrough how I’m using it.

## Get your DBI instance

// Using MySQL backend
Class.forName("com.mysql.jdbc.Driver");
DBI dbi = new DBI("jdbc:mysql://localhost/test", "testuser", "testpass");

## Get your Handler

Handle h = null;
try {
  h = dbi.open();

  ...
  ...

} finally {
  // don't forget to close it later!
  if(h!=null) h.close();
}

Mmmmm! I never liked this kind of statement because if you forget to close ResutlSet or Statement or Connection bad things happens. JDBI has an interface to solve this, similar to C# Using.

Integer employeesCount = dbi.withHandle(new HandleCallback<Integer>(final Integer id) {
  public Integer withHandle(Handle h) {

    // query goes here

}});

## Execute your Query

Integer employeesCount = dbi.withHandle(new HandleCallback<Integer>(final Integer id) {
  public Integer withHandle(Handle h) {
    return h.createQuery("select count(1) from employees WHERE contractor_id = :id")
      .map(IntegerMapper.FIRST) // [1]
      .bind("id", id) // [2]
      .first(); // [3]
}});

A lot of awesomeness happened here, let’s check it in detail:

  • [1]: It defines a Mapper, how the ResultSet will be mapped to an object, on this case a Integer (built-in class) that will contains the first field of the recordset count(1) and this field is type Integer.
  • [2]: Allows parameter name binding, which makes code more clear than using position (although they are available too)
  • [3]: Get the first (and unique) line returned from this statement

## Custom Mappers

If you have a more complex object, you can create Custom Mappers also. On this example I have a Profile and I want to return it using a similar statement:

import lombok.Data;

public @Data class Profile {
    private final long id;
    private final String firstName;
    private final String lastName;
}

Pro tip: Check this great library lombok for reducing boilerplate

public class ProfileMapper implements ResultSetMapper<Profile> {

    @Override
    public Profile map(int idx, ResultSet rs, StatementContext ctx) throws SQLException {
	      return new Profile(rs.getLong("id"), rs.getString("first_name"), rs.getString("last_name"));
    }
}

Now I can get my object the way I need!

Profile myProfile = dbi.withHandle(new HandleCallback<Integer>(final Integer profileId) {
  public Profile withHandle(Handle h) {
    return h.createQuery("select * from profile WHERE id = :id")
      .map(new ProfileMapper())
      .bind("id", profileId)
      .first();
}});

I realize above samples are very simple and probably you’re thinking why not just use a ORM and write less code, conversely this solution is easier and allow fine grained control of sql statements with JOINS and SUB-QUERIES.

## A lot more …

This is just a quick intro! There are SQL Object Batching, Transactions and more on the documentation.

## ACK

Thanks to Brian McCallister for this awesome library and to José Diaz @__joselo for telling me about it.