The main purpose of this note is to show how to use Jdbi with Java records, given a Java record is not a JavaBean - and therefore you cannot rely on some of the more traditional approaches to using Jdbi.

Assume the following table (in this case, for MySQL 8):

1
2
3
4
5
6
CREATE TABLE `datatables`.`country` (
  `country_code` VARCHAR(2) NOT NULL,
  `country_name` VARCHAR(100) NOT NULL,
  PRIMARY KEY (`country_code`),
  UNIQUE INDEX `country_code_UNIQUE` (`country_code` ASC) VISIBLE,
  UNIQUE INDEX `country_name_UNIQUE` (`country_name` ASC) VISIBLE);

The Java code uses Maven with the following Jdbi dependencies:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<dependency>
    <groupId>org.jdbi</groupId>
    <artifactId>jdbi3-core</artifactId>
    <version>3.42.0</version>
</dependency>

<dependency>
    <groupId>org.jdbi</groupId>
    <artifactId>jdbi3-sqlobject</artifactId>
    <version>3.42.0</version>
</dependency>

Note the dependency for jdbi3-sqlobject. This is a plug-in, which will need to be installed in your jdbi object, later on - see below.

(Read more about the SQL Objects extension.)

For our simple demo, the Java record is defined as follows:

1
2
3
4
5
public record Country(
        String countryCode,
        String countryName) {

}

For simplicity, this relies on Jdbi’s ability to automatically translate snake-case column names such as country_code to camel-case field names such as countryCode. (We need to do this for Java records, since we cannot annotate the record’s getters/setters with @ColumnName. A Java record is not a JavaBean.)

I will assume you have already created your org.jdbi.v3.core.Jdbi instance in the usual way - typically using a connection pool - so, something like:

1
Jdbi jdbi = Jdbi.create(createConnectionPool()); // implementation not shown

The Jdbi object needs to have the jdbi3-sqlobject plugin installed:

1
jdbi.installPlugin(new SqlObjectPlugin());

This uses the org.jdbi.v3.sqlobject.SqlObjectPlugin class.

Side note: I typically also need this config option (but this is not directly relevant to this discussion):

1
jdbi.getConfig(SqlStatements.class).setUnusedBindingAllowed(true);

There are various ways you can use the plugin. Here is a minimal example showing one way:

First create a new interface:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import com.northcoder.mydemo.dto.Country;
//import org.jdbi.v3.sqlobject.config.RegisterConstructorMapper; // used later on...
import org.jdbi.v3.sqlobject.customizer.BindMethods;
import org.jdbi.v3.sqlobject.statement.SqlUpdate;

public interface CountryDao {

    @SqlUpdate("""
        insert into country
        (country_code, country_name)
        values (:countryCode, :countryName)
        """)
    int addCountry(@BindMethods Country country);
}

Note the @BindMethods annotation which allows Jdbi to use the fields in our country instance and map them to the table’s columns for us.

To use this interface, do the following:

1
final CountryDao countryDao = jdbi.onDemand(CountryDao.class);

This allows Jdbi to generate implementations of the methods provided in that interface. Jdbi does all this for you - you do not need to provide your own implementations of the method stubs in the interface (in this specific example). This is not the only way to do this, but I think it’s the simplest - using the least amount of code.

Our generated method can then be invoked as follows:

1
int count = countryDao.addCountry(new Country("FR", "France"));

To select all records from the table, you can add the following method stub to the interface:

1
2
3
@SqlQuery("select country_code, country_name from country")
@RegisterConstructorMapper(Country.class)
List<Country> getCountries();

And then use it as follows:

1
List<Country> countries = countryDao.getCountries();

In this case we needed to use the @RegisterConstructorMapper annotation to allow Jdbi to invoke our Java record’s constructor, by automatically mapping the table’s column names to the record’s field names.

(You can, of course, use Jdbi bind parameters to create SQL statements using where clauses, together with your Java records.)

For alternative approaches see the Jdbi documentation. Specifically, note the caveats mentioned here regarding the use of onDemand():

The performance of on-demand objects depends on the database and database connection pooling. For performance critical operations, it may be better to manage the handle manually and use attached objects.

Overall, this is a remarkably small amount of code.

Background reading: Make sure record types work.