TL;DR: In this blog post, we are going to learn how to use Spring Data JPA, along with Liquibase and PostgreSQL, to manage the persistence layer of a Spring Boot application. We are also going to use Project Lombok to avoid writing some tedious boilerplate code that Java and JPA require.

Throughout this post, we will create the basis of an application called QuestionMarks. The idea is that this application will enable users to practice and enhance their knowledge by answering a set of multiple choice questions. To provide a better organization, these questions will be grouped in different exams. For example, there could be an exam called JavaScript Interview that would hold a set of JavaScript related questions to help users to prepare for interviews. Of course, in this article, we won't build the whole application as it would take a lot of time and would make the article huge. However, we will be able to see the technologies aforementioned in action.

Before diving into integrating these technologies, let's first take a look at their definition.

What is Spring Data JPA?

Spring Data JPA is the Spring module that adds support and extends JPA. JPA (which stands for Java Persistence API) is a Java specification for accessing, persisting, and managing data between Java objects/classes and relational databases (e.g. PostgreSQL, MySQL, SQLServer, etc). The process of mapping object-oriented entities to entity-relationship models is also know as ORM (Object-Relation Mapping) and JPA is the contract defined by the Java community to manage such mappings.

As JPA is just an specification, we will need an implementation to do the dirty work for us (creating the SQL queries). Hibernate is the most popular implementation of the JPA specification, and actually the specification itself was created based on Hibernate. Besides that, when we import Spring Data JPA on Spring, we also get Hibernate by default. Therefore, there is no reason to search for another JPA implementation.

What is Liquibase?

Liquibase is a tool that help developers to source control the database. In other words, with Liquibase we can keep our database schema and data synced with our Java entities. This is achieved by creating, in our Java project, files that contain changesets to be run on the database. These changesets are instructions to change/refactor the database. We will see Liquibase and its changesets in action in a while.

What is PostgreSQL?

Probably PostgreSQL does not need presentations, but for those who don't know here it is: PostgreSQL is a powerful, open source object-relational database system. It has more than 15 years of active development and a proven architecture that has earned it a strong reputation for reliability, data integrity, and correctness.

As a database server, its primary functions are to store data securely and return that data in response to requests from other software applications. It can handle workloads ranging from small single-machine applications to large internet-facing applications with many concurrent users.

Launching a Dockerized PostgreSQL Instance

Since we are going to need a PostgreSQL server running locally to test the integration of the tools in question, Docker might come in handy. Instead of installing PostgreSQL directly in our machine, we will use Docker to make this database disposable. Like this, if we need a newer version of PostgreSQL, or if we need a completely different database server, we won't need to struggle to update or uninstall PostgreSQL.

Bootstrapping a Spring Boot App

Next step is to bootstrap a Spring Boot application. We have two alternatives equally easy to do that. The first one is to use the Spring Initilizr website provided by Pivotal. The second options is to clone this GitHub repository and check out the part1 branch.

Bootstrapping with Spring Initilizr

If we choose to bootstrap our application with Spring Initilizr, we will need to fill the form available with the following values:

Generate a Gradle Project with Java and Spring Boot 2.0.0.

Project Metadata Group: com.

Project Metadata Artifact: questionmarks.

Selected Dependencies: let's leave this empty.

Note that although during this blog post we will use Gradle, we could easily use Maven instead. Let's just keep in mind that if we choose Maven Project the dependency configuration will be different. Besides that, the Spring Boot version does not need to be 1.5.6. The examples here must probably work with older and newer versions.

After that we just need to import the new Spring Boot project in our preferred IDE (Integrated Development Environment).

Importing Dependencies

Now that we have our basic Spring Boot application set, we can change our dependency management tool configuration (Gradle) to import the libraries that we will use. To do that, let's open the ./build.gradle file and change it as follows:

Mapping Entities with JPA

As we already have all the dependencies properly set on our application, let's start creating the entities (classes) that we want JPA/Hibernate to manage. We will create five entities. The first one will be called Exam and we will create it in a new package called com.questionmarks.model with the following code:

This class, although quite small and simple, has some interesting characteristics. The main one, for those who are not familiar with Lombok, is the @Data annotation. As explained by the documentation, @Data is a convenient shortcut annotation that bundles the features of @ToString, @EqualsAndHashCode, @Getter, @Setter and @RequiredArgsConstructor together. In other words, @Data generates all the boilerplate that is normally associated with simple POJOs (Plain Old Java Objects) and beans: getters for all fields, setters for all non-final fields, and appropriate toString, equals and hashCode implementations that involve the fields of the class, and a constructor that initializes all final fields, as well as all non-final fields with no initializer that have been marked with @NonNull, in order to ensure the field is never null.

Added @Entity to mark this class as an entity that will be managed by JPA/Hibernate.

Added @Id to indicate that the id property is the primary key of this entity.

Added @GeneratedValue with the GenerationType.IDENTITY strategy to indicate that the primary key value will be assigned by the persistence provider (i.e. PostgreSQL).

Added @NotNull to both title and description properties to avoid persisting empty data for these fields.

Most of the annotations used in the Exam entity are also going to be used in the other entities, as they provide the basis for JPA/Hibernate to function. The next entity that we are going to create will be called Question. Let's create this class inside the com.questionmarks.model package with the following code:

Besides the annotations that we already covered while creating the Exam entity, Question makes use of two new annotations:

@ManyToOne indicates to JPA/Hibernate that Many questions can exist ToOne exam.

@JoinColumn indicates that there will be a column called exam_id, in the table that supports Question, to reference the exam that owns this question.

This basically means that an exam will have many questions and that there will be a foreign key in the question table that points to the exam. Soon we will create these tables and these relationships in our database with the help of Liquibase.

The third entity that we will map is Alternative. As we are developing an application that provides multiple choice questions, we need to map these choices (alternatives) to keep track of which is the right one and also which alternative the user chooses while answering a question. Let's create the Alternative class in the com.questionmarks.model package with the following code:

This class doesn't use any new annotation, or any new feature. It simply uses the @Data annotation to avoid the boilerplate code, the JPA annotations to mark it as a managed entity with a primary key, and the @ManyToOne along with @JoinColumn to indicate that many alternatives may exist to a single question.

Both the Alternative and Question entities have two properties in common. A text (String) property to hold the description of the alternative/question, and a numerical (long) order that defines on what order the alternative will be shown in the question, or the question will be shown in the exam. Besides that, Alternative has a boolean property called correct to indicate if it is the correct answer or not.

The fourth entity that we will create is going to be User. We will create this class in the com.questionmarks.model package with the following code:

Contrastingly to the other entities that we have created, User does not have a numerical (long) id, but a textual (String) primary key. Besides that it doesn't have usual properties like email, name, and password. These unusual characteristics have an explanation. As we don't want to handle sensitive data and we don't want to waste valuable time trying to write secure authentication mechanisms, we are going to use Auth0 to manage user authentication for us. Therefore, all we will have to do is to persist a unique id that will be sent for us through JWTs (JSON Web Tokens).

This approach will free us to focus on the core functionality while we rest assured that one of the most critical parts of our application (security and authentication) will be supported by a great team of experts: Auth0!

Finally, the last entity that we will create will be called Attempt. This class will represent the attempts that users make to answer a question.

Again, nothing particularly new about the implementation of this class. The only perceptible difference for the other entities is that this one has two properties marked as @ManyToOne. Since many attempts will be made by oneuser, and that these attempts will refer to different alternatives, we annotated both properties with @ManyToOne. Besides that we also created a property to hold when the attempt was made (date) and created a boolean property called correct to indicate if the user answered the question correctly or not. With these properties we will be able to, in the future, provide some nice charts and some intelligence to our users.

As this was the last entity that we needed to create, we can now focus on creating the database schema that will support our application. We will solve this question by using Liquibase.

Managing the Database Schema with Liquibase

To manage the database structure of our application and to keep it synced with the entities that compose our system, we will use Liquibase. What is great about this tool is that it supports a wide variety of languages to manage the schema. For example, we can define and refactor the database by using XML, YAML, JSON, and SQL formats. Furthermore, Spring Boot provides a great support for Liquibase as we will see in this section.

Enough said, let's focus on solving the problem. First of all, we need to configure the database connection on our Spring Boot application. Spring Boot will provide this configuration both for JPA/Hibernate and for Liquibase. The properties to communicate with the database will be set in the ./src/main/resources/application.properties file:

The first property, spring.datasource.url, defines the address of our database. As we are running a dockerized PostgreSQL container and are bridging the default PostgreSQL port between our machine and the Docker container, we can reach the database by passing jdbc:postgresql://localhost/questionmarks. The second property defines the user that will communicate with the database, postgres in this case. The third property defines mysecretpassword as the password for postgres (the same that we passed when creating our dockerized PostgreSQL container). The last property defines the org.postgresql.Driver class as the driver responsible for handling the communication.

With these properties set, we can work on the Liquibase configuration. This will be an easy task, we are simply going to tell Liquibase to apply all the changesets available in a specific folder. To do that let's create a master Liquibase file called db.changelog-master.yaml in the src/main/resources/db/changelog/ folder. We will probably need to create the db folder and its child changelog as they are not provided by Spring Boot. The master file will have the following content:

databaseChangeLog:
- includeAll:
path: db/changelog/changes/

Note that the path value provided is relative to src/main/resources, and therefore we will need to create a folder called changes inside src/main/resources/db/changelog/. In this new folder we are going to create a new file called v0001.sql. This SQL file will contain the commands to create the tables that will support the entities of our application:

That was the last change that we needed to make in our application to make Liquibase responsible for running refactorings in our database. Running our application now, through the IDE or through the ./gradlew bootRun, will result in the following output:

Therefore, as we can see, everything worked as expected. Liquibase managed to apply the schema defined in the v0001.sql, and the application started successfully. This means that Spring Boot was able to run the application and that JPA/Hibernate found the tables needed to support our entities.

In the following sections, we are going to learn how to use Auth0 to secure Spring APIs. As we will see, the process is simple and fast.

Creating the API

First, we need to create an API on our free Auth0 account. To do that, we have to go to the APIs section of the management dashboard and click on "Create API". On the dialog that appears, we can name our API as "Contacts API" (the name isn't really important) and identify it as https://contacts.mycompany.com (we will use this value later).

After creating it, we have to go to the "Scopes" tab of the API and define the desired scopes. For this sample, we will define two scopes: read:contacts and add:contacts. They will represent two different operations (read and add) over the same entity (contacts).

On the popup shown, let's set the name of this new application as "Contacts Application" and choose "Single Page Web App" as the application type. After hitting the "Create" button, we have to go to the "Settings" tab of this application and change two properties. First, we have to set http://auth0.digituz.com.br/ in the "Allowed Web Origins" property. Second, we have to set http://auth0.digituz.com.br/callback in the "Allowed Callback URLs" property.

clientID: We have to copy this value from the "Client ID" field of the "Settings" tab of "Contacts Application".

domain: We can also copy this value from the "Settings" tab of "Contacts Application".

audience: We have to set this property to meet the identifier of the "Contacts API" that we created earlier.

scope: This property will define the authority that the access_token will get access to in the backend API. For example: read:contacts or both read:contacts add:contacts.

Then we can hit the "Sign In with Auth0" button.

After signing in, we can use the application to submit requests to our secured Spring API. For example, if we issue a GET request to http://localhost:8080/contacts/, the Angular app will include the access_token in the Authorization header and our API will respond with a list of contacts.

Next Steps: Defining a RESTful API and Querying the Database

So far, we have defined five entities that will hold the data that flows on our application. We also integrated PostgreSQL, with the help of Spring Data JPA, to persist this data and configured Liquibase to automatically run scripts that keep our database synced up with the entities structure. What we need now is to start defining the RESTful endpoints of our API that will support external clients (e.g. web application and iOS/Android mobile apps). This feature will be addressed in another article that will be released soon. Stay tuned!