Using JDBI With Spring Boot - Baeldung

Download as pdf or txt
Download as pdf or txt
You are on page 1of 11

3/9/2021 Using JDBI with Spring Boot | Baeldung

(/)

Using JDBI with Spring Boot


Last modi ed: January 27, 2020

by Philippe Sevestre (https://www.baeldung.com/author/philippe-


sevestre/)

Persistence (https://www.baeldung.com/category/persistence/)
Spring Boot (https://www.baeldung.com/category/spring/spring-
boot/)

Get started with Spring Data JPA through the reference


Learn Spring Data JPA course:
>> CHECK OUT THE COURSE (/learn-spring-data-jpa-course)

1. Introduction
In a previous tutorial (/jdbi), we covered the basics of JDBI (http://jdbi.org/), an
open-source library for relational database access that removes much of
the boilerplate code related to direct JDBC usage.
This time, we'll see how we can use JDBI  in a Spring Boot application. We'll
also cover some aspects of this library that make it a good alternative to
Spring Data JPA in some scenarios.
https://www.baeldung.com/spring-boot-jdbi 1/11
3/9/2021 Using JDBI with Spring Boot | Baeldung

2. Project Setup
First of all, let's add the appropriate JDBI dependencies to our project. This
time, we'll use JDBI's Spring integration plugin, which brings all required
core dependencies. We'll also bring in the SqlObject plugin, which adds some
extra features to base JDBI that we'll use in our examples:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<version>2.1.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.jdbi</groupId>
<artifactId>jdbi3-spring4</artifactId>
<version>3.9.1</version>
</dependency>
<dependency>
<groupId>org.jdbi</groupId>
<artifactId>jdbi3-sqlobject</artifactId>
<version>3.9.1</version>
</dependency>

The latest version of those artifacts can be found in Maven Central:


Spring Boot Start JDBC
(https://search.maven.org/classic/#search%7Cga%7C1%7Cg%3A%22
org.springframework.boot%22%20AND%20a%3A%22spring-boot-
starter-jdbc%22)
JDBI Spring Integration
(https://search.maven.org/classic/#search%7Cga%7C1%7Cg%3A%22
org.jdbi%22%20AND%20a%3A%22jdbi3-spring4%22)
JDBI SqlObject Plugin
(https://search.maven.org/classic/#search%7Cga%7C1%7Cg%3A%22
org.jdbi%22%20AND%20a%3A%22jdbi3-sqlobject%22)
We also need a suitable JDBC driver to access our database. In this article
we'll use H2
(https://search.maven.org/classic/#search%7Cga%7C1%7Cg%3A%22com.h2da
tabase%22%20AND%20a%3A%22h2%22), so we must add its driver to our
dependencies list as well:

https://www.baeldung.com/spring-boot-jdbi 2/11
3/9/2021 Using JDBI with Spring Boot | Baeldung

<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.199</version>
<scope>runtime</scope>
</dependency>

3. JDBI Instantiation and Con guration


We've already seen in our previous article that we need a Jdbi instance as our
entry point to access JDBI's API. As we're in the Spring world, it makes sense
to make an instance of this class available as a bean.
We'll leverage Spring Boot's auto-con guration capabilities to initialize a
DataSource and pass it to a @Bean-annotated method which will create our
global Jdbi instance.
We'll also pass any discovered plugins and RowMapper instances to this
method so that they're registered upfront:

@Configuration
public class JdbiConfiguration {
@Bean
public Jdbi jdbi(DataSource ds, List<JdbiPlugin> jdbiPlugins,
List<RowMapper<?>> rowMappers) {
TransactionAwareDataSourceProxy proxy = new
TransactionAwareDataSourceProxy(ds);
Jdbi jdbi = Jdbi.create(proxy);
jdbiPlugins.forEach(plugin -> jdbi.installPlugin(plugin));
rowMappers.forEach(mapper -> jdbi.registerRowMapper(mapper));
return jdbi;
}
}

Here, we're using an available DataSource and wrapping it in a


TransactionAwareDataSourceProxy. We need this wrapper in order to
integrate Spring-managed transactions with JDBI, as we'll see later.
Registering plugins and RowMapper instances is straightforward. All we have
to do is call installPlugin and installRowMapper for every available JdbiPlugin
and RowMapper, respectively. After that, we have a fully con gured Jdbi
https://www.baeldung.com/spring-boot-jdbi 3/11
3/9/2021 Using JDBI with Spring Boot | Baeldung

instance that we can use in our application.

4. Sample Domain
Our example uses a very simple domain model consisting of just two classes:
CarMaker and CarModel. Since JDBI does not require any annotations on our
domain classes, we can use simple POJOs:

public class CarMaker {


private Long id;
private String name;
private List<CarModel> models;
// getters and setters ...
}

public class CarModel {


private Long id;
private String name;
private Integer year;
private String sku;
private Long makerId;
// getters and setters ...
}

5. Creating DAOs
Now, let's create Data Access Objects (DAOs) for our domain classes. JDBI
SqlObject plugin o ers an easy way to implement those classes, which
resembles Spring Data's way of dealing with this subject.
We just have to de ne an interface with a few annotations and, automagically,
JDBI will handle all low-level stu such as handling JDBC connections and
creating/disposing of statements and ResultSets:

https://www.baeldung.com/spring-boot-jdbi 4/11
3/9/2021 Using JDBI with Spring Boot | Baeldung

@UseClasspathSqlLocator
public interface CarMakerDao {
@SqlUpdate
@GetGeneratedKeys
Long insert(@BindBean CarMaker carMaker);

@SqlBatch("insert")
@GetGeneratedKeys
List<Long> bulkInsert(@BindBean List<CarMaker> carMakers);

@SqlQuery
CarMaker findById(Long id);
}

@UseClasspathSqlLocator
public interface CarModelDao {
@SqlUpdate
@GetGeneratedKeys
Long insert(@BindBean CarModel carModel);

@SqlBatch("insert")
@GetGeneratedKeys
List<Long> bulkInsert(@BindBean List<CarModel> models);

@SqlQuery
CarModel findByMakerIdAndSku(@Bind("makerId") Long makerId,
@Bind("sku") String sku );
}

Those interfaces are heavily annotated, so let's take a quick look at each of
them.

5.1. @UseClasspathSqlLocator
The @UseClasspathSqlLocator annotation tells JDBI that actual SQL
statements associated with each method are located at external resource
les. By default, JDBI will lookup a resource using the interface's fully quali ed
name and method. For instance, given an interface's FQN of a.b.c.Foo with a
ndById() method, JDBI will look for a resource named
a/b/c/Foo/ ndById.sql.
This default behavior can be overridden for any given method by passing the
resource name as the value for the @SqlXXX annotation.

https://www.baeldung.com/spring-boot-jdbi 5/11
3/9/2021 Using JDBI with Spring Boot | Baeldung

5.2. @SqlUpdate/@SqlBatch/@SqlQuery
We use the @SqlUpdate, @SqlBatch, and @SqlQuery annotations to mark
data-access methods, which will be executed using the given parameters.
Those annotations can take an optional string value, which will be the literal
SQL statement to execute – including any named parameters – or when used
with @UseClasspathSqlLocator, the resource name containing it.
@SqlBatch-annotated methods can have collection-like arguments and
execute the same SQL statement for every available item in a single batch
statement. In each of the above DAO classes, we have a bulkInsert method
that illustrates its use. The main advantage of using batch statements is the
added performance we can achieve when dealing with large data sets.

5.3. @GetGeneratedKeys
As the name implies, the @GetGeneratedKeys annotation allows us to
recover any generated keys as a result of successful execution. It's mostly
used in insert statements where our database will auto-generate new
identi ers and we need to recover them in our code.

5.4. @BindBean/@Bind
We use @BindBean and @Bind annotations to bind the named parameters
in the SQL statement with method parameters. @BindBean uses standard
bean conventions to extract properties from a POJO – including nested ones.
@Bind uses the parameter name or the supplied value to map its value to a
named parameter.

6. Using DAOs
To use those DAOs in our application, we have to instantiate them using one of
the factory methods available in JDBI.
In a Spring context, the simplest way is to create a bean for every DAO using
the onDemand method:
https://www.baeldung.com/spring-boot-jdbi 6/11
3/9/2021 Using JDBI with Spring Boot | Baeldung

@Bean
public CarMakerDao carMakerDao(Jdbi jdbi) {
return jdbi.onDemand(CarMakerDao.class);
}

@Bean
public CarModelDao carModelDao(Jdbi jdbi) {
return jdbi.onDemand(CarModelDao.class);
}

The onDemand-created instance is thread-safe and uses a database


connection only during a method call. Since JDBI we'll use the supplied
TransactionAwareDataSourceProxy, this means we can use it seamlessly with
Spring-managed transactions.
While simple, the approach we've used here is far from ideal when we have to
deal with more than a few tables. One way to avoid writing this kind of
boilerplate code is to create a custom BeanFactory. Describing how to
implement such a component is beyond the scope of this tutorial, though.

7. Transactional Services
Let's use our DAO classes in a simple service class that creates a
few CarModel instances given a CarMaker populated with models. First, we'll
check if the given CarMaker was previously saved, saving it to the database if
needed. Then, we'll insert every CarModel one by one.
If there's a unique key violation (or some other error) at any point, the whole
operation must fail and a full rollback should be performed.
JDBI provides a @Transaction annotation, but we can't use it here as it is
unaware of other resources that might be participating in the same business
transaction. Instead, we'll use Spring's @Transactional annotation in our service
method:

https://www.baeldung.com/spring-boot-jdbi 7/11
3/9/2021 Using JDBI with Spring Boot | Baeldung

@Service
public class CarMakerService {

private CarMakerDao carMakerDao;


private CarModelDao carModelDao;

public CarMakerService(CarMakerDao carMakerDao,CarModelDao


carModelDao) {
this.carMakerDao = carMakerDao;
this.carModelDao = carModelDao;
}

@Transactional
public int bulkInsert(CarMaker carMaker) {
Long carMakerId;
if (carMaker.getId() == null ) {
carMakerId = carMakerDao.insert(carMaker);
carMaker.setId(carMakerId);
}
carMaker.getModels().forEach(m -> {
m.setMakerId(carMaker.getId());
carModelDao.insert(m);
});
return carMaker.getModels().size();
}
}

The operation's implementation itself is quite simple: we're using the standard
convention that a null value in the id eld implies this entity has not yet been
persisted to the database. If this is the case, we use the CarMakerDao instance
injected in the constructor to insert a new record in the database and get the
generated id.
Once we have the CarMaker‘s id, we iterate over the models, setting
the makerId  eld for each one before saving it to the database.
All those database operations will happen using the same underlying
connection and will be part of the same transaction. The trick here lies in the
way we've tied JDBI to Spring using TransactionAwareDataSourceProxy and
creating onDemand DAOs. When JDBI requests a new Connection, it will get
an existing one associated with the current transaction, thus integrating its
lifecycle to other resources that might be enrolled.

8. Conclusion
https://www.baeldung.com/spring-boot-jdbi 8/11
3/9/2021 Using JDBI with Spring Boot | Baeldung

In this article, we've shown how to quickly integrate JDBI into a Spring Boot
application. This is a powerful combination in scenarios where we can't use
Spring Data JPA for some reason but still want to use all other features such as
transaction management, integration and so on.
As usual, all code is available over at GitHub
(https://github.com/eugenp/tutorials/tree/master/persistence-
modules/spring-boot-persistence-2).

Get started with Spring Data JPA through the reference Learn
Spring Data JPA course: >> CHECK OUT THE COURSE (/learn-
spring-data-jpa-course#table)

An intro SPRING data, JPA


and Transaction Semantics Details with JPA
Get Persistence right with Spring
Download the E-book (/persistence-with-spring)

Comments are closed on this article!

https://www.baeldung.com/spring-boot-jdbi 9/11
3/9/2021 Using JDBI with Spring Boot | Baeldung

COURSES
ALL COURSES (/ALL-COURSES)
ALL BULK COURSES (/ALL-BULK-COURSES)
THE COURSES PLATFORM (HTTPS://COURSES.BAELDUNG.COM)

SERIES
JAVA “BACK TO BASICS” TUTORIAL (/JAVA-TUTORIAL)
JACKSON JSON TUTORIAL (/JACKSON)
HTTPCLIENT 4 TUTORIAL (/HTTPCLIENT-GUIDE)
REST WITH SPRING TUTORIAL (/REST-WITH-SPRING-SERIES)
SPRING PERSISTENCE TUTORIAL (/PERSISTENCE-WITH-SPRING-SERIES)
SECURITY WITH SPRING (/SECURITY-SPRING)
SPRING REACTIVE TUTORIALS (/SPRING-REACTIVE-GUIDE)

ABOUT
ABOUT BAELDUNG (/ABOUT)
THE FULL ARCHIVE (/FULL_ARCHIVE)
WRITE FOR BAELDUNG (/CONTRIBUTION-GUIDELINES)
EDITORS (/EDITORS)
JOBS (/TAG/ACTIVE-JOB/)
OUR PARTNERS (/PARTNERS)
ADVERTISE ON BAELDUNG (/ADVERTISE)

TERMS OF SERVICE (/TERMS-OF-SERVICE)


PRIVACY POLICY (/PRIVACY-POLICY)
COMPANY INFO (/BAELDUNG-COMPANY-INFO)
CONTACT (/CONTACT)

https://www.baeldung.com/spring-boot-jdbi 10/11
3/9/2021 Using JDBI with Spring Boot | Baeldung

https://www.baeldung.com/spring-boot-jdbi 11/11

You might also like