I’ve been migrating some raw JDBC code over to JDBI, and joyfully stripping out lines of boilerplate code for preparing statements, opening record sets, sometimes remembering to close them, handling SQL exceptions which won’t ever occur anyway, and so on. Using the SQL Object API means the only code you have to write is the SQL and a ResultSetMapper to determine how to create your domain objects from the resultset. It really promotes adherence to the single responsibility principle and discourages you from mixing logic in with your database access code.
The database in question has a number of fields containing JSON data. More specifically, they use the PostgreSQL jsonb data type. This has required a little more tinkering to get working.
Inserting jsonb data
Out of the box, JDBI provides two annotations for binding parameters. The @Bind annotation binds a single named argument, and @BindBean binds bean properties with matching names. However, to insert jsonb data, you need to first create an PGobject
instance and bind that. To do this, I created a new Binding annotation, following the guidance here: http://jdbi.org/sql_object_api_argument_binding/
The annotation code looks like this:
BindingAnnotation(BindJson.JsonBinderFactory.class) @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER}) public @interface BindJson { String value(); public static class JsonBinderFactory implements BinderFactory { @Override public Binder build(Annotation annotation) { return new Binder<BindJson, String>() { @Override public void bind(SQLStatement q, BindJson bind, String jsonString) { try { PGobject data = new PGobject(); data.setType("jsonb"); data.setValue(jsonString); q.bind(bind.value(), data); } catch (SQLException ex) { throw new IllegalStateException("Error Binding JSON",ex); } } }; } } }
To use it, annotate the json parameter with the new annotation:
@SqlUpdate("insert into my_table (id,data) VALUES (:id,:data)") void insertJson(@Bind("id") int id, @BindJson("data") String jsonString);
And that’s it; it just works.
Querying json dynamically
I had a requirement where the parameter supplied to the query was the name of the json element to return. For example, consider the json below. I wanted to be able to paramterise a query to return either one of the key values.
{ "element": { "key1": "value1", "key2": "value2", "key3": "value3" } }
Using raw JDBC it was possible (although not very pretty) to concatenate a suitable sql statement and then execute it:
String sql = "select data->'element1'->'" + subKeyName + "' as value from mytable" ...
This isn’t possible when the SQL string is specified as a JDBI annotation. I found some useful Postgres json processing functions, including jsonb_extract_path_text
which allows you to bind parameters normally:
@SqlQuery("select jsonb_extract_path_text(data,'element1',:subKeyName) as value from mytable") List<String> getSubKey(@Bind("subKeyName") String subKeyName)
So far I haven’t come across any other issues using JDBI with a PostgreSQL JSON data store. I’m looking forward to trying out the new jsonb functionality in PostgreSQL 9.5 which supports writing partial updates to json fields, yippee!