- 8 minutes read

Over the ages, Spring's approach to database programming has changed a lot. If you're still using the merry old template approach, this article is for you. Templates are old technology, revolutionary when I had my first love affair with Spring in 2003. Java 5 made them superfluous by introducing annotations. Later, Java 8 made using templates fun by adding Lambdas. Too bad everybody and their grandma already had abandoned templates in favor of annotations. Sometimes technology evolves like a river, meandering oxbows included.

Hint: if you're an experienced Spring user, just ignore this article. It's an entry-level article, written by a Spring noob for other Spring newbies. It's not even a tutorial. It's just a collection of ideas that work so you can start with the useful tutorials instead of learning outdated technology. That's the downside of the famous backward-compatibility of Spring. Everything that's ever been added to Spring is still there. When new ideas are added to Spring, that doesn't mean old ideas are pruned. After almost 16 years, it looks more like a jungle than like a garden.

New vocabulary

So let's explore what I consider the 2019 approach.[1]

Like so often, the first thing to do when exploring a new framework is learning new vocab. One of the first things I stumbled upon is the word "repository." Repositories are one of those patterns that were tremendously popular a decade ago before functional programming taught us how to solve the same problems easier. So I was a bit hesitant at first. However, the magic of Spring makes repositories a useful concept.

What's a Spring repository?

In a nutshell, repositories are persistence layers. As a user, you can consider the repository a data store. Saving an expense in the ExpenseRepository saves the expense in the database. That's all you need to know to be a successful Spring programmer. The repository is just an interface, plus a lot of magic. At first, it feels a bit odd to use an interface instead of a class. However, using the CrudRepository gives you the full range of operations you need: save, load, find, and delete. As a programmer, you're happy to know that these few lines are all it takes to implement a great database API:

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

This interface provides you with methods like save() and findAll(), and it even has a custom method to find the expense with a WHERE hotel=? clause. I'll come back to that in a minute.

(Just in case you didn't read me previous article: my demo project manages my travel expenses. A large part of my traveling budget goes to hotels, so calling findByHotel() gives me an overview over how much money I've wasted by not living in a tent).

Benefits of using Spring repositories

Spring repositories offer many advantages over using plain JDBC.Side remark
Thorben Jannsen has a great article explaining the concept of an repository. In his article, you can also see how much code frameworks like Spring Data and - if you prefer JavaEE over Spring - Apache Deltaspike Data generate for you.
First of all, Spring wraps the persistence exceptions by Spring exceptions. That simplifies a developer's life a lot. Well, most of the time. Hibernate throws checked exceptions. Spring converts them to unchecked RuntimeExceptions. A co-worker of mine told me that every once in a while, your application runs into an exception your development team didn't consider when writing the application. Normally, that doesn't happen, but you'd rather add a global error handler for runtime exceptions, just to be sure.

Excursion: where does the name "repository" come from?

At its heart, the repository pattern is what I said two paragraphs above: it collects all the code dealing with a particular database table (aka entity) in a single class.

Let me quote the words of Maurizio Pozzobon, who's written an excellent article about the repository pattern. (Following the link, you'll notice his article isn't about Java, and that his database is Sharepoint. That's the essence of a design pattern: it works with any language, in a large variety of environments). Maurizio says that

a repository is an object that sits between your application data and your business logic

and

Repositories are used to isolate from data sources like databases, file system or a Sharepoint list, and from other difficult to integrate objects like web services. When all your business logic deals with data retrieval or persistence through a repository, you can test your business logic in isolation replacing the real repository with a test double.

In other words, repositories make mocking a walk in the park. Just inject a different set of test repositories in your unit tests.

CRUD operations

Every Spring CrudRepository offers the basic operations out of the box:

save(entity); findById(id); findAll(); delete(entity); // plus several similar operations

There's a second interface PagingAndSortingRepository adding support for loading tremendous search results in chunks (pagination) and sorting the search result.

JpaRepository adds another set of interesting methods providing "query by example" searches. This is an almost forgotten approach to implementing search dialogs. The idea is that all the fields you need in the WHERE clause of your search are part of the entity itself. So you can use an instance of the entity as an example of what you're looking for. Spring adds all non-empty fields to the WHERE clause your the SELECT statement. The Spring documentation covers that nicely.

Advanced find and delete operations

It's time to talk about another killer feature of Spring Data. Did you think using an interface to deal with a database is pretty cool? You ain't seen nothing yet! Add these two methods to our example interface:

public List findByHotelName(String name); public void deleteByHotelNameAndCity(String name, String city); public Page findByHotelName(String name, Pageable page); public List findByHotelName(String name, Sort sort);

When Spring encounters a findBy or deleteBy method, it analyzes the rest of the method name and takes it as a hint how to assemble the WHERE clause.

My first example is simple. The string after findBy is used as search criterion.

The second example demonstrates you can use more than one search criterion.

You can also unlock pagination and sorting by merely adding a Pageable or Sort parameter.

Mind you, we've implemented an impressive range of database operations almost without writing code. We've just declared what we want to do. Spring implements the database operations for us. Of course, you can use a custom implementation if you must. The nice thing is you rarely have to do that.

Transactions

As a side effect, repositories usually also care about transaction management. We've exposed our repository methods to REST, so Spring automatically adds transaction management. If you're using the same repository in your backend code, you can define the transaction boundaries by adding the @Transactional annotation.

More precisely, standard CRUD operations are transactional by default. However, findByX and deleteByY are not. You have to add an @Transactional annotation to the method stub in the interface to make these methods transactional.

Let's explore how that works. We need to add an option to make the database operation fail. The easiest way to achieve this is to add a validation constraint to our Entity:

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

Next, we write a REST service saving a valid expense first, and trying to save an invalid expense after that:

@RestController public class ExpenseController { @Autowired private ExpenseRepository repository; @GetMapping("/addExpense") public String addExpense() { Expense first = new Expense(); first.setHotel("Grand Hotel"); first.setAmount(450); repository.save(first); Expense second = new Expense(); second.setHotel("Petit Hotel"); second.setAmount(1777); repository.save(second); return "Success"; } }

We are rewarded with an impressive JPA exception. Looking at the details, we see that it's a RollbackException. However, looking at the database, we see that the Grand Hotel has successfully been added. It's only the expensive hotel that has been rejected.

Adding a Transactional annotation changes that. Now neither hotel is saved:

@GetMapping("/addExpenseTransactional") @Transactional public String addExpenseTransactional() { Expense first = new Expense(); first.setHotel("Grand Hotel"); first.setAmount(450); repository.save(first); Expense second = new Expense(); second.setHotel("Petit Hotel"); second.setAmount(1777); repository.save(second); return "Success"; }

What about the Entitymanager?

If you're coming from JavaEE, or if you've read older Spring or JPA tutorials, you're probabably familiar with the @PersistenceContext and the Entitymanager?. You don't need them if you're using Spring repositories.

Wrapping it up

Spring Data allows for a remarkable simple approach to database programming. If you're working on a green-field project, you can get away with knowing little to none about stuff like SQL, Hibernate, and JDBC. Legacy databases probably require more work, but even then, I assume Spring Data makes working with these databases simple.

Dig deeper

Spring Data manual

Query by example with Spring

Thorben Jannsen has a great article explaining the concept of an repository.

Apache Deltaspike Data


  1. The technology is a lot older. "2019" approach" is just a shorthand for "If you were to learn Spring in 2019, you'd use this approach".↩

Comments