JPA, the Java Persistence API, is the standard for relational database access using Object-Relational mapping techniques in Java. Hibernate is a popular implementation of the standard, and is the default JPA library in Spring Boot.

1. Dependencies

➜ Add spring-boot-starter-data-jpa to the dependencies list

build.gradle
@@ -15,6 +15,7 @@ repositories {
 dependencies {
   compile 'org.springframework.boot:spring-boot-starter-web'
   compile 'org.springframework.boot:spring-boot-starter-thymeleaf'
+  compile 'org.springframework.boot:spring-boot-starter-data-jpa'

   compile 'org.springframework.boot:spring-boot-devtools'

➜ Check what libraries get added to the classpath as a consequence

➜  gradle dependencies --configuration compile

...
\--- org.springframework.boot:spring-boot-starter-data-jpa -> 2.0.3.RELEASE
     +--- org.springframework.boot:spring-boot-starter:2.0.3.RELEASE (*)
     +--- org.springframework.boot:spring-boot-starter-aop:2.0.3.RELEASE
     |    +--- org.springframework.boot:spring-boot-starter:2.0.3.RELEASE (*)
     |    +--- org.springframework:spring-aop:5.0.7.RELEASE (*)
     |    \--- org.aspectj:aspectjweaver:1.8.13
     +--- org.springframework.boot:spring-boot-starter-jdbc:2.0.3.RELEASE
     |    +--- org.springframework.boot:spring-boot-starter:2.0.3.RELEASE (*)
     |    +--- com.zaxxer:HikariCP:2.7.9
     |    |    \--- org.slf4j:slf4j-api:1.7.25
     |    \--- org.springframework:spring-jdbc:5.0.7.RELEASE
     |         +--- org.springframework:spring-beans:5.0.7.RELEASE (*)
     |         +--- org.springframework:spring-core:5.0.7.RELEASE (*)
     |         \--- org.springframework:spring-tx:5.0.7.RELEASE
     |              +--- org.springframework:spring-beans:5.0.7.RELEASE (*)
     |              \--- org.springframework:spring-core:5.0.7.RELEASE (*)
     +--- org.hibernate:hibernate-core:5.2.17.Final
     |    +--- org.jboss.logging:jboss-logging:3.3.1.Final -> 3.3.2.Final
     |    +--- org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.0.Final -> 1.0.2.Final
     |    +--- org.javassist:javassist:3.22.0-GA
     |    +--- antlr:antlr:2.7.7
     |    +--- org.jboss:jandex:2.0.3.Final
     |    +--- com.fasterxml:classmate:1.3.0 -> 1.3.4
     |    +--- dom4j:dom4j:1.6.1
     |    \--- org.hibernate.common:hibernate-commons-annotations:5.0.1.Final
     |         \--- org.jboss.logging:jboss-logging:3.3.0.Final -> 3.3.2.Final
     +--- javax.transaction:javax.transaction-api:1.2
     +--- org.springframework.data:spring-data-jpa:2.0.8.RELEASE
     |    +--- org.springframework.data:spring-data-commons:2.0.8.RELEASE
     |    |    +--- org.springframework:spring-core:5.0.7.RELEASE (*)
     |    |    +--- org.springframework:spring-beans:5.0.7.RELEASE (*)
     |    |    \--- org.slf4j:slf4j-api:1.7.25
     |    +--- org.springframework:spring-orm:5.0.7.RELEASE
     |    |    +--- org.springframework:spring-beans:5.0.7.RELEASE (*)
     |    |    +--- org.springframework:spring-core:5.0.7.RELEASE (*)
     |    |    +--- org.springframework:spring-jdbc:5.0.7.RELEASE (*)
     |    |    \--- org.springframework:spring-tx:5.0.7.RELEASE (*)
     |    +--- org.springframework:spring-context:5.0.7.RELEASE (*)
     |    +--- org.springframework:spring-aop:5.0.7.RELEASE (*)
     |    +--- org.springframework:spring-tx:5.0.7.RELEASE (*)
     |    +--- org.springframework:spring-beans:5.0.7.RELEASE (*)
     |    +--- org.springframework:spring-core:5.0.7.RELEASE (*)
     |    \--- org.slf4j:slf4j-api:1.7.25
     \--- org.springframework:spring-aspects:5.0.7.RELEASE
          \--- org.aspectj:aspectjweaver:1.8.13

hibernate, spring-data-jpa, and spring-jdbc are notable items on the list. As with other types of Spring Boot starters, it’s easy to override defaults, and to select a different implementation of the JPA standard.

Spring Data JPA is one of a number of Spring projects that address application integration with database back-ends. Each project focuses on a specific data technology such as MongDB, GemFire, Redis, and Cassandra. Together, these projects are a part of the Spring Data umbrella project. All Spring Data projects have a common foundation and consistent design, facilitating the task of moving from one persistence back-end to another.

2. A database

Along with a database framework, we need a database. For this lab, we’ll pick something lightweight, and that requires no additional installation: H2.

The H2 database has many merits. Not only is it lightweight, it’s implemented in Java, and supports in-memory mode, which makes is very useful for testing scenarios. Spring Boot makes it easy to swap the database implementation with a different product, such as mysql, or postgres.

➜ Add h2 as a runtime dependency:

build.gradle
@@ -21,5 +21,7 @@ dependencies {

   compileOnly 'org.projectlombok:lombok'

+  runtime 'com.h2database:h2'
+
   testCompile 'org.springframework.boot:spring-boot-starter-test'
 }

➜ Start the application:

$ gradle bootRun

Spring Boot automatically detects the new dependency and automatically makes the H2 database web console accessible as an application endpoint.

  • Visit the H2 console at http://localhost:8080/h2-console

  • Set the JDBC URL to jdbc:h2:mem:testdb

    h2 login
  • Click Test Connection to verify our connection to the in-memory database

  • Click Connect

h2 console

From this console, we can easily view and navigate the list of tables, issue queries, and more.

Now that we have a database, let’s move Todo’s off of our home-grown in-memory repository from the previous lab and to an actual database.

3. Make Todo.java a JPA Entity

The first step is to mark our Todo domain object as a persisted entity. We use JPA annotations for that:

  1. In your IDE, open Todo.java

  2. Annotate the class with @Entity (from javax.persistence)

  3. Annotate the id field with the @Id annotation

  4. Also add the @GeneratedValue annotation to the field, so that the database can be in charge of generating a unique id for each new instance created

Todo.java
@@ -3,11 +3,16 @@ package com.example.demo;
 import lombok.Builder;
 import lombok.Data;

+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
 import java.time.LocalDate;

+@Entity
 @Builder(toBuilder = true)
 @Data
 public class Todo {
+  @Id @GeneratedValue
   private final Long id;
   private final String title, description;
   private final LocalDate dueDate;

➜ Run the application once more and re-visit the H2 console:

$ gradle bootRun
h2 console todo

Notice that we now automatically get a database table named TODO!

3.1. Schema Creation

➜ Review the application’s startup logs

hibernate log

Observe how Hibernate initializes, detects the H2 database, and exports the schema after scanning the Todo domain object as an entity. Hibernate also warns us that Todo.java is lacking a default (no-argument) constructor, which is a requirement for reconstituting Todo objects from a database result set.

How did we obtain a connection to the database without configuring a DataSource?

Spring Boot’s autoconfiguration detects spring-boot-data-jpa and h2 in the classpath, notices that we haven’t configured a datasource ourselves, and so automatically volunteers one our behalf. Since it finds no database configuration in the application, it falls back to the only thing it can do: configure an in-memory database.

3.2. Review Auto-Configured Spring Beans

Spring Boot provides a number of ways for us to discover what Spring Beans it auto-configures. One is to simply run our application in debug mode, like this:

$ gradle build
$ java -jar build/libs/demo-0.0.1-SNAPSHOT.jar --debug

Spring Boot will output to the console a long "auto-configuration report" (also known as the "conditions evaluation report") of all beans it added to the application context, and why.

autoconfiguration report

The list of "Positive Matches" clearly shows that DataSource AutoConfiguration took place.

If you’re running the STS or IntelliJ IDE, they both sport Spring Boot integration, and there’s a good chance that you can run the application in debug mode from the comfort of your IDE. In IntelliJ, it’s configured with a checkbox from the launch configuration dialog:

ide boot debug mode

3.3. JPA Repositories

Spring Data has the concept of a repository. It supports CRUD Operations (Create, Read, Update, Delete), via the CrudRepository interface. It extends this base interface with paging and sorting capabilities via the PagingAndSortingRepository interface.

Both are defined in a library common to all Spring Data projects: spring-data-commons. Spring Data JPA adds its own specialized interface, the JPARepository.

The interesting aspect of this design is that Spring Data can automatically synthesize an implementation of the repository from an interface definition, for any domain type, by simply defining a sub-interface of JpaRepository.

➜ Create the interface:

TodoJpaRepository.java
package com.example.demo;

import org.springframework.data.jpa.repository.JpaRepository;

public interface TodoJpaRepository extends JpaRepository<Todo, Long> {
}

The generic types Todo and Long indicate that this interface represents a repository of Todos whose primary key is of type Long.

Armed with our new TodoJpaRepository, it’s time to exercise it.

➜ Inject it into Runner.java and TodoController.java (not the Rest Controller, the plain html one), like this:

Runner.java
@@ -14,9 +14,9 @@ public class Runner implements CommandLineRunner {
   private final Logger logger = LoggerFactory.getLogger(Runner.class);

   private final HelloService helloService;
-  private final TodoRepository todoRepository;
+  private final TodoJpaRepository todoRepository;

-  public Runner(HelloService helloService, TodoRepository todoRepository) {
+  public Runner(HelloService helloService, TodoJpaRepository todoRepository) {
     this.helloService = helloService;
     this.todoRepository = todoRepository;
   }
diff --git a/src/main/java/com/example/demo/TodoController.java b/src/main/java/com/example/demo/TodoController.java
index d88615b..4cfacc0 100644
TodoController.java
@@ -13,9 +13,9 @@ import java.util.stream.Collectors;
 @Controller
 public class TodoController {

-  private final TodoRepository todoRepository;
+  private final TodoJpaRepository todoRepository;

-  public TodoController(TodoRepository todoRepository) {
+  public TodoController(TodoJpaRepository todoRepository) {
     this.todoRepository = todoRepository;
   }

We wish to find out whether we can actually store Todo entries in the database (instead of in memory) and fetch them back.

  • Re-run the application

  • Check the h2 console and run a query against the todo table

Were the entries saved by the runner?

h2 populated

Did the todos appear on the page?

no default constructor

Now we understand the original warning: JPA expects entities to have a no-argument constructor. This presents a problem for us, given that Todo defines all its fields as final. To solve this problem, let’s relax this requirement and modify Todo.java accordingly:

  • Remove the final keyword from all the fields

  • Add both a no-argument constructor, and an "all-arguments" constructor (the lombok builder needs it to build the Todos)

Todo.java
@@ -1,7 +1,9 @@
 package com.example.demo;

+import lombok.AllArgsConstructor;
 import lombok.Builder;
 import lombok.Data;
+import lombok.NoArgsConstructor;

 import javax.persistence.Entity;
 import javax.persistence.GeneratedValue;
@@ -11,9 +13,11 @@ import java.time.LocalDate;
 @Entity
 @Builder(toBuilder = true)
 @Data
+@NoArgsConstructor
+@AllArgsConstructor
 public class Todo {
   @Id @GeneratedValue
-  private final Long id;
-  private final String title, description;
-  private final LocalDate dueDate;
+  private Long id;
+  private String title, description;
+  private LocalDate dueDate;
 }

Repeat the test. This time, the TodoController should work properly.

3.4. Retrofit the TodoRestController

Let’s continue:

➜ Retrofit TodoRestController as follows:

TodoRestController.java
@@ -11,9 +11,9 @@ import java.util.Optional;
 @RequestMapping("/api/todos")
 public class TodoRestController {

-  private final TodoRepository repository;
+  private final TodoJpaRepository repository;

-  public TodoRestController(TodoRepository repository) {
+  public TodoRestController(TodoJpaRepository repository) {
     this.repository = repository;
   }

Because we modeled the original TodoRepository as a subset of the full Spring Data CrudRepository interface, the above swap requires no code changes: it remains compatible with the interface we’d originally coded against.

Re-run the application, test some of the REST API endpoints, such as http://localhost:8080/api/todos, and verify that the REST Controller functions as expected.

3.5. Make the tests pass again

Re-run the tests. There should be some failures.

  • Have a closer look at TodoRestControllerTest.java

Notice that @MockBean is now mocking the wrong bean. We need to mock TodoJpaRepistory.

  • Revise the test:

TodoRestControllerTests.java
@@ -31,7 +31,7 @@ public class TodoRestControllerTests {
   private TestRestTemplate template;

   @MockBean
-  private TodoRepository repository;
+  private TodoJpaRepository repository; (1)
   private Todo todo1;

   @Before
1 Replace TodoRepository with TodoJpaRepository

The tests should now pass!

3.6. Cleanup

  • We could, though we don’t have to, delete the original TodoRepository interface, and the InMemoryTodoRepository (along with its test).

  • We can definitely remove the now unused TodoRepository bean from the DemoApplication class:

DemoApplication.java
@@ -13,8 +13,4 @@ public class DemoApplication {
     SpringApplication.run(DemoApplication.class, args);
   }

-  @Bean
-  public TodoRepository todoRepository() {
-    return new InMemoryTodoRepository();
-  }
 }

For good measure, re-build and re-run the tests for your project to be sure that your code is in a working state.

4. Switch to Mysql (Optional)

Before proceeding with this portion of the lab, you’ll need to install mysql on your platform, and know your mysql root username and password.

Assuming you have mysql installed on your local machine, with an admin user named root with no password, log in to mysql:

$ mysql -u root

Here are the commands to create a database, a user, and to give that user privileges over the database just created:

mysql> SHOW DATABASES;
mysql> CREATE DATABASE todos;
mysql> SHOW DATABASES;
mysql> CREATE USER 'todo_user'@'localhost' IDENTIFIED BY 'secret';
mysql> GRANT ALL PRIVILEGES ON todos.* TO 'todo_user'@'localhost';
mysql> FLUSH PRIVILEGES;

In build.gradle add the mysql jdbc driver:

build.gradle
@@ -22,6 +22,8 @@ dependencies {
   compileOnly 'org.projectlombok:lombok'

   runtime 'com.h2database:h2'
+  runtime 'mysql:mysql-connector-java'
+

   testCompile 'org.springframework.boot:spring-boot-starter-test'
 }

Configure the datasource:

application.yml
@@ -4,4 +4,10 @@ logging:
     com.example: DEBUG

 hello:
   greeting: Bonjour
+
+spring:
+  datasource:
+    url: jdbc:mysql://localhost:3306/todos?autoReconnect=true&useSSL=false
+    username: todo_user
+    password: secret
+  jpa:
+    generate-ddl: true (1)
1 With H2, we didn’t have to explicitly ask for Hibernate to generate the DDL for us. With mysql, we have to be explicit.

For version 8.0 of MySQL server, add the option allowPublicKeyRetrieval=true to the datasource url.

➜ Start the application once more.

Note how the console output shows Hibernate detecting the MySQL dialect:

log output mysql

Log in once more to the mysql console, and issue a query to list the contents of the newly-created todo table:

mysql query

Notice how seamless it was to move from our original H2 database to using a different RDBMS.

4.1. Data Initialization on Startup

Now that we’re no longer using an in-memory database, it is no longer appropriate to assume the TODO table is empty on startup, and to unconditionally initialize the table with todos in Runner.java.

➜ Modify Runner.java: add a condition around the todoRepository.save() statements. The condition should ensure that the initialization takes place only if the table is empty (repository.count() == 0):

src/main/java/com/example/demo/Runner.java
@@ -25,6 +25,14 @@ public class Runner implements CommandLineRunner {
   public void run(String... args) {
     helloService.greet();

+    if (todoRepository.count() == 0) {
+      initializeTodos();
+    }
+
+    logger.debug("exiting run method..");
+  }
+
+  private void initializeTodos() {
     LocalDate nextWeek = LocalDate.now().plus(7, ChronoUnit.DAYS);

     todoRepository.save(Todo.builder()
@@ -43,8 +51,5 @@ public class Runner implements CommandLineRunner {
         .title("Fly")
         .description("Fly to some mysterious destination")
         .dueDate(nextWeek).build());
-
-
-    logger.debug("exiting run method..");
   }
 }

4.2. Profiles

It would be convenient to have the option to choose H2 in some cases, and mysql in others. For example, colleagues might want to play with the application without having to install mysql.

Spring Profiles is perfect for this scenario. We can define the configuration of the mysql datasource only when the profile named mysql is active.

Spring Boot provides conventions for configuration for specific profiles:

  • For .properties files: the naming convention is application-<profilename>.properties; for example place mysql-specific configuration in the file named application-mysql.properties

  • For .yml files, we can do even better: YAML supports the notion of multiple documents defined within the same file, with the --- document separator

Here’s an example:

src/main/resources/application.yml
---
logging:
  level:
    com.example: DEBUG

hello:
  greeting: Bonjour

---
spring:
  profiles: mysql
  datasource:
    url: jdbc:mysql://localhost:3306/todos?autoReconnect=true&useSSL=false
    username: todo_user
    password: secret
  jpa:
    generate-ddl: true

Above, we’re moving the mysql datasource [and other pertinent] configuration to a section labeled with the Spring profile named mysql.

This basically means that by default, when no profile is set, we’ll use H2. And when we specify the mysql profile, we’ll get the mysql configuration.

➜ Start the application normally, and verify that data is now saved to H2 and not mysql

Inspect the console output on application startup: do you see what dialect Hibernate selected?

.. org.hibernate.dialect.Dialect : HHH000400: Using dialect: org.hibernate.dialect.H2Dialect

To specify the list of active profiles when starting an application, use the spring.profiles.active configuration property.

It’s often simpler to use an environment variable before starting the application, like so:

$ SPRING_PROFILES_ACTIVE=mysql gradle bootRun

Verify that this time, Hibernate selects the mysql dialect.

This can be baked into the build process by adding a task named, say, bootRunMysql, as follows (credit):

build.gradle
task bootRunMysql(type: org.springframework.boot.gradle.tasks.run.BootRun, dependsOn: 'build') {
  group = 'Application'
  description = 'Run the boot application with the mysql profile'
  doFirst() {
    main = 'com.example.demo.DemoApplication'
    classpath = sourceSets.main.runtimeClasspath
    systemProperty 'spring.profiles.active', 'mysql'
  }
}

IDEs make this process even simpler. They have built-in support for multiple run configurations, as shown in the following screenshot:

run config mysql

Above, I’ve duplicated my existing run configuration, and explicitly set the active profile to mysql.

5. Summary

The goal of this lab was not to show how to build database applications, but rather to demonstrate how, here too (in the data domain), Spring Boot simplifies development:

  • Autoconfiguration goes a long way to configuring the Spring application context according to the dependencies we have declared.

  • By using conventions, configuration is simplified: just edit application.yml and set spring.datasource.url.

  • Retrospect on how easy (or difficult) it was to switch from our own InMemoryTodoRepository to one using JPA.

  • [Opinion:] Spring Boot makes development a more pleasurable activity.

5.1. Explore Further

Here are some resources to explore, as suggestions for going further:

  • Some self-paced guides on JPA and MySql.

  • Database migrations with flyway.

  • Database integration with Spring’s jdbcTemplate and spring-boot-starter-jdbc instead of JPA. Here’s a short self-paced guide.

  • Spring Data with MongoDB: Here’s a short self-paced guide.

  • Spring Data REST is a unique project that automatically generates REST API controllers for CrudRepositories. Here’s a short self-paced guide on this topic.