JPA - Repository pattern
What is it?
The repository pattern is extremely popular. In its modern interpretation, it abstracts the data store and enables your business logic to define read and write operations on a logical level. It does that by providing a set of methods to read, persist, update and remove an entity from the underlying data store.
An interface defines the repository with all logical read and write operations for a specific entity. The interface gets implemented by one or more classes that provide data store specific implementations of each interface method.
The repository pattern is one of the most popular Java persistence patterns. It provides 2 main benefits:
- The pattern abstracts the data store and enables you to replace your data store without changing your business code.
- The repository improves the reusability of your persistence code, especially your queries, by encouraging you to implement all persistence operations in one place. That makes them easy to find and to reuse.
Old vs. modern interpretation
If you read Patterns of Enterprise Application Architecture by Martin Fowler et al., you will recognize the difference to the initial goal of the repository pattern. Its main goal was the abstraction of the database access code. JPA already provides this abstraction. So, there is no need for another layer that provides the same functionality.
That’s why the new interpretation of the pattern now provides a higher level of abstraction and hides all specifics of the data store. That enables you to replace a data store with a completely different one, e.g., a relational database with a NoSQL database. But what’s even more important, all database access methods for an entity are defined in the same repository and not in different parts of the business logic. That makes the implementation of your business logic and reusing queries or other database operations much easier.
Implementation
The implementation of the repository pattern is relatively simple. You need an interface that defines the persistence operations on a logical level. This interface gets implemented by one or more data store specific classes.
In most enterprise projects, you only need to define the repository interfaces. You just need to provide your own implementation, if your implementation gets especially complex.
Sample implementation:
public interface BookRepository {
Book getBookById(Long id);
Book getBookByTitle(String title);
Book saveBook(Book b);
void deleteBook(Book b);
}
Pure JPA (and Hibernate) implementation for the interface.
import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
public class BookRepositoryImpl implements BookRepository {
private EntityManager em;
public BookRepositoryImpl(EntityManager em) {
this.em = em;
}
@Override
public Book getBookById(Long id) {
return em.find(Book.class, id);
}
@Override
public Book getBookByTitle(String title) {
TypedQuery<Book> q = em.createQuery("SELECT b FROM Book b WHERE b.title = :title", Book.class);
q.setParameter("title", title);
return q.getSingleResult();
}
@Override
public Book saveBook(Book b) {
if (b.getId() == null) {
em.persist(b);
} else {
b = em.merge(b);
}
return b;
}
@Override
public void deleteBook(Book b) {
if (em.contains(b)) {
em.remove(b);
} else {
em.merge(b);
}
}
}
Reading material
https://thorben-janssen.com/implementing-the-repository-pattern-with-jpa-and-hibernate/