A Spring Boot Application using Spring Data JPA

a developer journal to building a (somewhat) realistic application using Spring Boot and Spring Data JPA.

View on GitHub

Bootstrap your Application

Start with the Spring Initializr web site at http://start.spring.io.

The Initializr will help you set up your Spring Boot project. By filling out just a few fields, you can generate and download a zip file that contains (almost) everything you need to get started. Don’t worry, the Initializr is not a code generator in the traditional sense, but more of a generator for initial project scaffolding.

In the Dependencies field, type in: web, actuator, jpa, h2 and devtools. If you hit Enter after each tag is matched, it will be permanently selected. Feel free to customize the Group and Artifact coordinates as you see fit but keep the default Spring Boot Version.

Don’t be afraid to click “Switch to the full version” and explore the other Spring Boot options. You may even choose to start your project with a Gradle build configuration instead (however, I will assume Maven for the remainder of this journal).

Once the dependencies selected, click the the Generate Project button. When the download finishes, unzip the file and import the project into your development environment. I primarily use the IntelliJ IDE from JetBrains, so some of the steps that I describe may not match exactly, but they should be close.

Run the application.

Depending on your environment, the Run profile might be automatically configured for you. If not, just navigate the project source folders until you find the default Application class and run it manually. If everything goes well, you should see several colorful INFO messages logged out to the console and finally a Tomcat instance listening on the default port of 8080.

Try it out at: localhost:8080

You should see the default “White label” error page in your browser. Don’t worry, you haven’t done anything wrong, this is the default Spring Boot error page. The Initializr only generates just enough code to get you started and this is the expected result.

If you use the unix curl command instead, you will see a JSON error response. This is Spring Boot being smart about the requested Content Type. By default, Boot will return the most appropriate type in the response.

Ultimately, we may customize this behavior but for now, lets write some code.

Start with the Domain

Lets start by creating a domain object. By a domain object, I mean an object that we will use to represent the business entity that we want to model. To do this, we will use a design technique called Domain-Driven Design. The Wikipedia entry for Domain-Driven Design states:

Domain-driven design (DDD) is an approach to software development for complex needs by connecting the implementation to an evolving model.

That sounds quite abstract, but in practice, it will become apparent that DDD is just a set of concepts that are technology agnostic and help to provide a ubiquitous language that can be used solve many types of complex problems.

You are not required to be an expert in DDD, but it does help to understand the basics. The Wikipedia article linked above is a great start, but for more information, please refer to Eric Evans’ excellent book: Domain-Driven Design: Tackling Complexity in the Heart of Software. Or, for the more impatient reader Domain-Driven Design Distilled by Vaughn Vernon.

The Patient Entity

In DDD, an Entity is a special type of domain object that has a unique, unchangeable identity that when combined with mutable attributes, can be used to express a model.

In the the domain of the Pain Management application, we need to represent a Patient. This would appear to map well to the @Entity annotation defined by the standard Java Persistence API (JPA). However, it is important to note that, from a design perspective, we should resist coupling an Entity with the (sometimes) cumbersome details of the underlying storage technology.

Initially, don’t worry about schemas, columns or indexes. We will deal with those concerns when it becomes appropriate. For now, just worry about the details of the identity and attributes that we need to define a Patient.

To create the Patient Entity, first create a domain package under the default package name that you used for the project. Then create a new Java Class called Patient and add the following code:

@Entity
public class Patient {
    @Id
    @GeneratedValue
    private Long id;
}

This is really the simplest Entity that we could define using JPA.

The Patient’s identity (id) is a synthetic value that is arguably a poor choice for representing the concept long term. However, the choice is intentional as it is an immediate convenience but we will be able to refactor it later.

DDD “purists” may dislike this approach, arguing that the JPA annotations immediately couple the technology to the domain. In general, I tend to be more pragmatic on this topic, particularly when using Spring Data as it helps balance design and development with the right level of abstraction and still get things done.

Save and restart the application.

This time, if you watch the logs closely, you should see additional messages from Hibernate. Hibernate is Spring Boot’s default JPA implementation and, because we have an @Entity defined, it is able to act on our behalf. In fact, Hibernate automatically defined an initial schema and created a database table for us, but how?

When we originally created the project, we chose the H2 database and, by including it’s dependency, Spring Boot will automatically activated it in-memory during local development. Hibernate uses H2 and will execute the commands to create a table per the @Entity definition. This is a very fast and convenient way to get your design ideas realized without having to immediately fuss with storage details.

H2 also comes with a very handy web console that Spring Boot can launch. If your application does not have the console, you may need to add a property to enable it in the default application.properties file:

spring.h2.console.enabled=true

I have noticed that the H2 console is automatically enabled if you also have the devtools dependency in your project (this may be unintended so be aware of the configuration option above). Also, make sure you set the JDBC URL to jdbc:h2:mem:testdb instead of the console default or you won’t see the PATIENT table.

As mentioned earlier, do not get too concerned with the underlying database technology. For the time being, we will let Hibernate decide how best to define and manage the database schema. In this way we initially get the flexibility and agility that can come with using so called “schemaless” systems.

For now lets focus on the basics of interacting with the Patient entity.

Create a Repository

A Repository is another DDD concept that is used to describe an abstraction that mediates the domain model from data mapping layers.

With Spring Data, we use an @Repository annotation to wrap common persistence operations for a specific Entity type and, hopefully, keep the underlying storage details out of the domain.

To create a Patient Repository, first create a package called repositories in the base package and then create a PatientRepository Java interface. Finally, add the following code to the interface:

@Repository
public interface PatientRepository extends JpaRepository<Patient, Long> {
}

Surprisingly, that’s not much code. But what does it actually do?

By extending the JpaRepository class we are creating a custom Spring Data Repository for the Patient Entity that will be initialized with a JPA entity manager. It is a powerful abstraction in Spring Data that also provides several default CRUD operations and a custom query system that we will discuss later in more detail.

When Spring manages the Repository, it can generate JPA queries based on the Entity definition that it is typed to and we will rarely have to deal with JPA directly.

// TODO: talk about why I’m not using @RestRepositories. In short, I find Spring Data REST to be a little too opinionated and harder to customize.

Before we get too far ahead of ourselves, lets enable some basic configuration options that will help with future debugging:

Edit the default application.properties file in the src/main/resources folder and add:

spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true

I do STRONGLY recommend adding these properties and keeping them set for the duration of your local development. You will frequently need to review the SQL that is being created and executed by Hibernate.

Create a REST Controller

Create a package called controllers in the application package and then create a PatientController class within it. Add the following code to the class:

@RestController
public class PatientController {

    private final PatientRepository patientRepository;

    @Autowired
    public PatientController(PatientRepository patientRepository) {
        this.patientRepository = patientRepository;
    }
    
    @GetMapping("/patients/{id}")
    public Patient getPatient(@PathVariable("id") Long id) {
        return patientRepository.findOne(id);
    }
}

Restart the application.

When the application starts, you should be able to make “RESTful” GET calls on the /patients resource. Let’s try one out at: http://localhost:8080/patients/1.

However, nothing is displayed in the browser, the HTTP response is just an empty page (and that result seems a bit odd). Intuitively you should already be thinking that there can’t be a resource at “patients/1” because we haven’t created it yet (and you would be correct). However, our expectation should also be that when a resource is not found, we should receive a 404 NOT FOUND error as a response.

The answer to this little puzzle lays in the nuanced default behavior of the built-in findOne implementation. From the Spring documentation, we see that the Returns block for this method says:

the entity with the given id or null if none found

Since the null result is happily passed back up the call stack, the response is processes as a success with an empty payload.

Create a JPA ‘findBy’ Implementation

Lets crete a more appropriate method to the Repository interface, one that we can exert more control over to get the desired behavior. In the PatientRepository, add a findById interface method:

public interface PatientRepository extends JpaRepository<Patient, Long> {
    Optional<Patient> findById(Long id);
}

Here we are leveraging the Spring Data findBy query naming convention to produce a JPA query that will fetch a Patient using the id field. We’re also taking advantage of a Java 8 feature by wrapping the Patient response with an Optional<>. This means that even null results returned by the repository will be wrapped in a usable Object.

Finally, change the @GetMapping implementation in the Patient Controller to use the new query:

@GetMapping("/patients/{id}")
public Patient getPatient(@PathVariable("id") Long id) {
    return patientRepository.findById(id).orElseThrow(() -> new IllegalArgumentException("Not found"));
}

The orElseThrows() is a method call chained onto the Optional return type. This construct is null safe and frees us from having to write (ugly?) null checking code. If there is a null response, this method will throw a built-in Java exception with a message indicating that the requested resource was not found.

Restart the application.

Use curl this time on the /patients/1 resource:

curl -sS localhost:8080/patients/1 | jq

The result is a 500 status error code. [Sad Trombone].

Wait, I thought this approach was supposed to help? A 500 error code is even worse than the empty 200 because it implies that there is something wrong with the server. This could have real-world implications if the application were being hosted behind a reverse proxy configured to remove instances returning “unhealthy” response codes.

To be fair, this is partially my own fault.

For expediency, I chose to use an existing Java runtime exception and the Spring MVC response handler treats it as an unhandled exception and returns a 500 response. There are multiple ways to address this behavior but for now, we will create a new type of exception that Spring will handle correctly.

// TODO Discuss Spring’s lack of standard HTTP error exceptions and why 500 exceptions are a poor default behavior.

Create a Custom Exception

To fix this, create an exceptions package in the application root and then add a Java class called NotFoundException with the following code:

@ResponseStatus(HttpStatus.NOT_FOUND)
public class NotFoundException extends RuntimeException {
    public NotFoundException() {
        super();
    }
    
    public NotFoundException(String message) {
        super(message);
    }
}

Modify the Patient GET method to throw this new exception instead of the original IllegalArgumentException:

        return patientRepository.findById(id)
                .orElseThrow(() -> new NotFoundException(String.format("Patient %d not found", id)));

Restart the application and attempt the curl command again.

{
  "timestamp": 1481488691203,
  "status": 404,
  "error": "Not Found",
  "exception": "io.undertree.symptom.exceptions.NotFoundException",
  "message": "Patient 1 not found",
  "path": "/patients/1"
}

That looks much better! By marking the Exception with a @ResponseStatus, the default exception handler interprets the exception as a 404 and returns the appropriate error block. This is exactly what we and our eventual clients should expect.

Add a Patient via POST Method

We have a functional GET but now we need a way to add a Patient. Using REST semantics this should be expressed with a POST. To support this, add the following code to the PatientController:

    @PostMapping("/patients")
    public Patient addPatient(@RequestBody Patient patient) {
        return patientRepository.save(patient);
    }

Restart and issue a curl -H "Content-Type: application/json" -X POST localhost:8080/patient -d '{}'

Interestingly we get a “{}” back. If we look at the logs we should be able to see that an insert did take place but where is the id of the entity we just created?

If we open up the H2 console again, we should see that a row was created with the ID of 1. If we use our GET operation, we should get the entity back right?

curl -sS localhost:8080/patients/1 | jq

Still the same empty empty JSON object?

The reason we don’t see the id field is that we failed to provide a getter method on the entity class. The default Jackson library that Spring uses to convert the object to JSON needs getters and setters to be able to access object’s internal values.

Modify the Patient class to add a getter for the id field:

    public Long getId() {
        return id;
    }

Now when you execute the POST we can see an id field on the JSON object

curl -sS -H "Content-Type: application/json" -X POST localhost:8080/patients -d '{}' | jq

// TODO discuss Lombok

Extending the Patient Model

Our basic Patient domain object is not very exciting and lacks the typical data elements that one might expect. If we do a little research on sites like schema.org, we can see that there are definitions already out there for a Patient.

Let’s use some of that information to start filling out the domain.

Make the Patient model a bit more… interesting

Add the following attributes to the Patient entity (and Add the associated getter/setters using your IDEs code generator if possible):

    private String givenName;
    private String familyName;
    private LocalDate birthDate;

Using curl again submit a Patient add request:

curl -sS -H "Content-Type: application/json" -X POST localhost:8080/patients -d '{"givenName":"Max","familyName":"Colorado","birthDate":"1942-12-11"}' | jq

400 Error?

So the LocalDate type is causes a JsonMappingException. We need to add a Jackson module dependency to the project so it knows how to handle JSR 310 Dates (introduced in Java 8).

Add this dependency to the project pom.xml:

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
</dependency>

Refresh the dependencies and restart the application (Spring Boot will automatically register this module with the default Jackson ObjectMapper).

Now if we execute the curl again, we see an odd looking date structure:

"birthDate": [
    1942,
    12,
    11
  ]

While an accurate date, that isn’t exactly the JSON structure what we want. There is yet another configuration change we need here to tell Jackson to format the date correctly.

Update the application.properties in resources and include the property:

spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS=false

This somewhat confusingly named configuration option tells Jackson to output Dates in ISO-8601 textual value. This is probably the configuration that you’ll want to use in most applications.

But wait.

If we look at how the date was actually stored in the database through the H2 console, it looks like a BLOB. That isn’t a good choice since we might want to be able to query against it later.

// TODO: Discuss why JPA doesn’t support LocalDate?

Fixing the LocalDate Time Database Mapping

Okay, I lied. I said not to worry about the actual database implementation at this point, but we are going to take this one detour.

In general, you shouldn’t normally need worry too much about the type mappings until much later in the design process but this specific issue can be resolved fairly easy during the initial project set up.

As of the time of writing, JPA does not natively support the JSR 310 dates. Fortunately, Hibernate stepped in and provides an additional library to help with managing the type conversions. You can read more about it here.

Thankfully, it is just as simple as adding the following dependency to your pom.xml:

    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-java8</artifactId>
      <version>${hibernate.version}</version>
    </dependency>

This library will introduce the appropriate type converters that will deal with the new date types in a more elegant fashion. When you refresh the dependencies and restart the application, you will see in the logs that the table is now being create with a proper DATE type:

Hibernate: 
    create table patient (
        id bigint generated by default as identity,
        birth_date date,
        family_name varchar(255),
        given_name varchar(255),
        primary key (id)
    )

The need for this additional library will eventually go away when Spring Boot upgrades to Hibernate 5.2 (see the Hibernate Migration Guide 5.2).