The Jdbi @ColumnName Annotation

03 Apr 2021

Jdbi has a @ColumnName annotation which can be used to map a table’s column name to the corresponding field of a JavaBean. See http://jdbi.org/#_beanmapper.

The examples given in the documentation show how to use the annotation with a field name:

1
2
3
4
5
6
7
public class User {

  @ColumnName("user_id")
  public int id;

  public String name;
}

And also with a constructor:

1
2
3
4
public User(@ColumnName("user_id") int id, String name) {
  this.id = id;
  this.name = name;
}

In my case, I tried using the annotation on a boolean field:

Java
1
2
@ColumnName("is_admin")
private boolean admin;

The related column was in a MySQL 8 table, and was defined as follows:

SQL
1
is_admin  tinyint(1)

When using plain JDBC, the tinyint(1) data was correctly converted from 0 and 1 to false and true.

However, when using Jdbi, this did not work and the annotation had no effect.

Specifically, when reading data from the table, the related getter was never called - and therefore all values were set to false (the default value for a Java boolean):

Java
1
2
3
4
5
6
7
8
Jdbi jdbi = Jdbi.create(url, user, pass);
List<User> users = jdbi.withHandle(handle -> handle
            .select("select user_id, is_admin from my_db.user")
            .mapToBean(User.class)
            .list());
for (User user : users) {
    System.out.println(user.getUserID() + " " + user.isAdmin());
}

The other table column user_id (a string) was handled correctly.

There is one note in the Jdbi documentation which states:

The @ColumnName annotation can be placed on either the getter (or setter) method, instead of on the field declaration.

When I moved the annotation to the getter, the problem was resolved.

Here is the full User bean, for reference:

Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import org.jdbi.v3.core.mapper.reflect.ColumnName;

public class User {

    //@ColumnName("is_admin") // annotation has no effect here
    private boolean admin;

    @ColumnName("user_id")
    private String userID;

    public String getUserID() {
        return userID;
    }

    public void setUserID(String userID) {
        this.userID = userID;
    }

    @ColumnName("is_admin") // annotation works when placed here
    // same behavior is also seen when using getAdmin():
    public boolean isAdmin() {
        return admin;
    }

    public void setAdmin(boolean admin) {
        this.admin = admin;
    }

}

Conclusion: From now onwards, I will always place this annotation on a field’s getter or setter.

Postscript:

The reason my code worked for column name user_id mapping to field name userIDis that I am using one of Jdbi’s “reflective mappers” - the bean mapper - and these mappers automatically map snake_case column names to camelCase field names.

From the Jdbi documentation:

Reflective mappers treat column names as bean property names (BeanMapper), constructor parameter names (ConstructorMapper), or field names (FieldMapper).

Reflective mappers are snake_case aware and will automatically match up these columns to camelCase field/argument/property names.

However, the nature of the snake-to-camel conversion does seem to have one subtlety. In my case, my database column is:

user_id

and my field name is:

userID

Strict conversion from snakecase to camelcase should produce this:

userId

But userID works correctly.

This is because the converter is case insensitive.