- 8 minutes read

Let's write a REST service providing all the CRUD functionalities of an entity! Yes, I know, that's a lot of work. This blog post is gonna be soporific, second only to your TV. But fear not: We're using Spring Boot, 2019 edition. Spring is doing all the hard work for us.

Here we go. As you may or may not remember, I've started to write an application for managing my travel expenses. So here's our entity:

@Entity public class Expense { @Id @GeneratedValue private Long id; private String hotel; private double amount; // plus getters and setters }

And this is our REST service:

@RepositoryRestResource(collectionResourceRel = "expenses", path = "expenses") public interface ExpenseRepository extends PagingAndSortingRepository { Expense save(Expense expense); List findAll(); }

Believe it or not, that's all it takes to write a simple REST service with Spring Boot. Now we've got the full range of standard REST CRUD operators: GET, POST, PUT, PATCH, and DELETE.

Convention over configuration

The first surprise is that we didn't implement anything. All we did is to write an interface. But that's enough. Spring detects the interfaces and fills in the missing bits automagically. Convention over configuration means that declaring the method save() is a hint to Spring: "please implement a method to INSERT or UPDATE rows in a database". It also tells Spring to implement the corresponding REST services: POST, PUT, and PATCH.

Where does the DELETE operator come from?

Remains the question where the DELETE operator comes from. Well, it's hidden in the PagingAndSortingRepository. Our repository inherits from it, so we've got the full range of standard operators. Truth to tell, it goes beyond simple CRUD operators: Spring also implements pagination and sorting. Plus, we can simplify our interface even further. If we don't need it in our Spring backend, this version does the trick just as well:

@RepositoryRestResource(collectionResourceRel = "expenses", path = "expenses") public interface ExpenseRepository extends PagingAndSortingRepository {}

Truth to tell, we've still written more code than necessary. By default, Spring Boot exposes every repository to REST. In other words, we can drop the annotation. I recommend doing so because this way you automatically use the naming conventions of Spring. So here's the final code:

public interface ExpenseRepository extends PagingAndSortingRepository {} }

Now Spring derives the resource name from the entity class name (after putting it to lower case and adding a plural "s"). By the way, this tutorial from 2017 explains all that in much detail. Among other things, it tells you how to make the REST annotation mandatory. Sounds odd? Yes, but it gives you fine-grained control which repositories are available as REST service and which repositories are not.

Discovering the REST URLs

It's high time to see your REST services in action! At this point, I could tell you the naming conventions of Spring, but it's more fun to use Spring Boot itself to explore the paths. A good starting point is surfing to http://localhost:8080. Now you see a HATEOAS representation of all the available REST URLs:

{ "_embedded": { "expenses": [ ] }, "_links": { "self": { "href": "http://127.0.0.1:8080/expenses{?page,size,sort}", "templated": true }, "profile": { "href": "http://127.0.0.1:8080/profile/expenses" }, "search": { "href": "http://127.0.0.1:8080/expenses/search" } }, "page": { "size": 20, "totalElements": 2, "totalPages": 1, "number": 0 } }

Now for the fun part. Let's add some interactivity. These four lines in your pom.xml activate the HAL browser:

org.springframework.data spring-data-rest-hal-browser

Now navigating to http://localhost:8080 shows you a tool to interactively discover all the URL.

REST URLs in a nutshell

There are just a few conventions you should know. I assume you already know the general ideas of REST. Here's the Spring interpretation of these principles:

  • GET http://127.0.0.1:8080/expenses lists all entities in the database table.
  • POST http://127.0.0.1:8080/expenses inserts a new entity into the database table. The data is passed as a Json object. If you're using Postman (or something like this), don't forget to set the media type to application/json. If you're using the HAL browser, you're lucky: it generates a simple form to input each field.
  • Each entity has an individual address. You access it by adding the primary key (aka ID) to the URL, like so: GET http://127.0.0.1:8080/expenses/17.
  • To update an entity, use PUT or PACTH. To not add the ID to the Json object. That's not the Spring way. We've already learned that the ID is part of the URL, so the correct way is to send an PUT http://127.0.0.1:8080/expenses/17 or PATCH http://127.0.0.1:8080/expenses/17
  • Delete follows the same pattern: DELETE http://127.0.0.1:8080/expenses/17

PATCH vs. PUT

There are many discussions about the difference between PUT and PATCH. In theory, PUT replaces an object, while PATCH is a partial update. If you're designing your REST API yourself, both operations update the database entity. In general, I recommend ignoring the flame wars. Just choose either PUT or PATCH and stick with it. However, we're using all the magic of Spring Boot, and it does distinguish these two operations. If you're sending an incomplete Json object, PUT deletes all the other fields of the entity. PATCH behaves more kindly: it just updates the fields defined in the Json-object without touching the other values.

I'd expect PATCH to take more time because it requires to read the data it wants to keep. However, when I tried this, both PUT and PATCH did a SELECT before the UPDATE. Weird.

By the way, to log the SQL statements generated automagically, add these two lines to the application.properties file:

logging.level.org.hibernate.SQL=DEBUG logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE

Can I use the repository within my backend?

Back to the Java source code. So far, we've defined a repository and used it to expose REST calls. Can you use it without REST?

Sure you can. Despite all the REST magic, the repository is still a traditional service. So you can have it injected via @Autowired.

Adding queries (almost) without writing code

I'll cover Repositories and Spring Data in another article. Even so, I'll give you a short glimpse into what you can do. Let's add a method to our interface:

public interface ExpenseRepository extends PagingAndSortingRepository { List findByHotel(String hotel); }

Behind the scenes, Spring Data generates a SELECT * FROM EXPENSE WHERE HOTEL=? for you. Spring Rest also exposes a REST API. The HAL explorer allows us to find the corresponding URL quickly:

http://localhost:8080/expenses/search/findByHotel?hotel=Grand%20Hotel

We can even build complex queries like so:

public interface ExpenseRepository extends PagingAndSortingRepository { List findByHotel(String hotel); List findByHotelAndAmount(String hotel, double amount); }

The second query is called by

GET http://localhost:8080/expenses/search /findByHotel?hotel=Grand%20Hotel&amount=33

Sorting and pagination

Just one more thing: the GET operator also allows for sorting and pagination.

GET http://localhost:8080/expenses?sort=hotel GET http://localhost:8080/expenses?page=3&size=10

You can even combine them:

GET http://localhost:8080/expenses?page=3&size=10&sort=hotel

The result returns a page object telling you wether you can paginate upwards or downwards. Alternatively, you can inspect the list of links in the result object. If it's possible to scroll through the data, there's a next and / or prev link. For the sake of convenience, there are also the first and last links:

{ "_embedded": { "expenses": [] }, "_links": { "first": { "href": "http://localhost:8080/expenses?page=0&size=10" }, "prev": { "href": "http://localhost:8080/expenses?page=1&size=10" }, "self": { "href": "http://localhost:8080/expenses{&sort}", "templated": true }, "last": { "href": "http://localhost:8080/expenses?page=0&size=10" }, "profile": { "href": "http://localhost:8080/profile/expenses" }, "search": { "href": "http://localhost:8080/expenses/search" } }, "page": { "size": 10, "totalElements": 0, "totalPages": 0, "number": 2 } }

Wrapping it up

When I showed this to a co-worker, he answered: "that doesn't work with my database." True. I've deliberately shown you the greenfield approach, assuming the database structure is identical to what the client needs.

In the age of microservices, that's a reasonable approach. Microservices come with their own database so that you can optimize it for your use case. More often than not, the microservice database is synchronized with the legacy battered but battle-proven databases your customer already has. That's a job of synchronization batch jobs (or something like that). The idea is to remove this particular part of complexity from the microservice.

And there's always the option to do it with less magic. Spring REST can expose arbitrary methods. So you can write your REST service containing business logic, or you can write a persistence layer mapping historical data structures to the current one, and so on. You've got all the flexibility you need.

That's just not what I wanted to show you. This article is about how easy it is to write a REST service accessing a database in 2019.[1] Awesome!

Dig deeper

Thorben Jannsen on Spring repositories

Spring Data + Rest tutorial from 2017

Official Spring documentation about Repository URLs


  1. To be honest, the technologies are several years old. But that doesn't change anything. I'm describing how it feels to use Spring in 2019.↩

Comments