Since we have just released the first milestone of the Spring Data JPA project, I would like to give you a brief introduction to its functionality. As you may know, the Spring framework provides support for building a data access layer based on JPA. So what does Spring Data JPA add to this basic support? To answer this question, I'd like to start with the data access component of the sample domain implemented with regular JPA + Spring, and point out the areas that leave room for improvement. When we're done, I'll refactor the implementation to use Spring Data JPA to solve these problem areas. Can be in Github Find a step-by-step guide to sample projects and refactoring steps.
The domain
For simplicity, we start with a small well-known domain: we Customer have Accounts.
@Entity public class Customer { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String firstname; private String lastname; // ... methods omitted }
@Entity public class Account { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; @ManyToOne private Customer customer; @Temporal(TemporalType.DATE) private Date expiryDate; // ... methods omitted }
The Account has a validity period that we will use at a later stage. Beyond that, there's nothing special about a class or mapping -- it uses plain JPA annotations. Now let's look at the Component Management Account object:
@Repository @Transactional(readOnly = true) class AccountServiceImpl implements AccountService { @PersistenceContext private EntityManager em; @Override @Transactional public Account save(Account account) { if (account.getId() == null) { em.persist(account); return account; } else { return em.merge(account); } } @Override public List<Account> findByCustomer(Customer customer) { TypedQuery query = em.createQuery("select a from Account a where a.customer = ?1", Account.class); query.setParameter(1, customer); return query.getResultList(); } }
I deliberately named this class * Service to avoid name conflicts, because we will introduce the repository layer when we start refactoring. But conceptually, the classes here are repositories, not services. So what do we actually have?
This class of annotations @Repository is used to enable exception conversion from JPA exceptions to Spring's Data Access Exception hierarchy. In addition, we use @Transactional to ensure save(... ) The operation runs in a transaction and allows readOnly-flag (at the class level) findByCustomer(... ) This results in some performance optimizations within the persistence provider and at the database level.
When we want to decide whether to call the free client merge(... ) Or persist(... ) In EntityManager, we use id-field Account to determine whether we consider an Account object as new or not. Of course, this logic can be extracted into a common superclass, because we may not want to duplicate this code for each domain object specific repository. The query method is also simple: we create a query, bind a parameter, and execute the query to get the result. Almost straight ahead, people can use code execution as a template and imagine that it comes from method signatures: we expect List Acounts, queries are very close to method names, and we just need to bind method parameters to it. As you can see, there is room for improvement.
Spring Data Repository Support
Before we begin the refactoring implementation, note that the sample project contains test cases that can be run during the refactoring process to verify that the code is still valid. Now let's see how we can improve implementation.
Spring Data JPA provides a repository programming model that begins with the interface of each managed domain object:
public interface AccountRepository extends JpaRepository<Account, Long> { ... }
Defining this interface has two purposes: First, by extension, JpaRepository adds a bunch of generic CRUD methods to our type, allowing us to save Accounts, delete them, and so on. Second, this will allow the Spring Data JPA repository infrastructure to scan the class path of this interface and create Spring bean s for it.
To enable Spring to create a bean that implements this interface, all you need to do is use the Spring JPA namespace and activate repository support with the appropriate elements:
<jpa:repositories base-package="com.acme.repositories" />
This scans all packages com.acme.repositories below to get the extended interface, JpaRepository, and create a Spring bean for it, and its implementation supports SimpleJpaRepository. Let's take the first step by refactoring our AccountService implementation, using our newly introduced repository interface:
@Repository @Transactional(readOnly = true) class AccountServiceImpl implements AccountService { @PersistenceContext private EntityManager em; @Autowired private AccountRepository repository; @Override @Transactional public Account save(Account account) { return repository.save(account); } @Override public List<Account> findByCustomer(Customer customer) { TypedQuery query = em.createQuery("select a from Account a where a.customer = ?1", Account.class); query.setParameter(1, customer); return query.getResultList(); } }
After refactoring, we simply call the delegate save(... ) To the repository. By default, if the id attribute null of the entity is exactly the same as in the previous example, the repository implementation treats the entity as a new entity (note that you can get more detailed control of the decision if you need to). In addition, we can delete the annotation of the @Transactional method because the CRUD method implemented by the Spring Data JPA repository has already annotated the @Transactional method.
Next we will refactor the query method. Let's follow the same query method delegation strategy as the save method. We introduced a Query method on the repository interface and delegated the original method to the newly introduced method:
@Transactional(readOnly = true) public interface AccountRepository extends JpaRepository<Account, Long> { List<Account> findByCustomer(Customer customer); }
@Repository @Transactional(readOnly = true) class AccountServiceImpl implements AccountService { @Autowired private AccountRepository repository; @Override @Transactional public Account save(Account account) { return repository.save(account); } @Override public List<Account> findByCustomer(Customer customer) { return repository.findByCustomer(Customer customer); } }
Let me add a quick description of transaction processing here. In this very simple case, we can completely delete the @Transactional annotation in the class, AccountServiceImpl because the CRUD method of the repository is transactional, and the query method marked @Transactional(readOnly = true) is already in the repository interface. Currently, the service level approach is marked transactional (even if it is not needed in this case), preferably because the operations that occur in the transaction are explicit when looking at the service level. In addition, if the service layer method is modified to make multiple calls to the repository method, all code will still be executed in a single transaction, because the internal transaction of the repository will simply be added to the external transaction initiated at the service layer. The transactional behavior of the repository and the possibility of adjusting it are Reference document Detailed in Record.
Try running the test case again to see if it works. Stop, we did not provide any implementation findByCustomer(... Right? How do you use it?
Query Method
When Spring Data JPA creates Spring bean instances for the AccountRepository interface, it checks all query methods defined therein and derives a query for each query method. By default, Spring Data JPA automatically parses method names and creates queries from them. The query is implemented using the JPA standard API. In this case, the findByCustomer(... ) Method is logically equivalent to JPQL query select a from Account a where A. customer = 1. This method name parser supports a large set of keywords such as And, Or, GreaterThan, LessThan, Like, IsNull, Not, etc. OrderBy can also add Clauses If you like. For a detailed overview, see Reference document . This mechanism provides us with a query method programming model, just like you used to do from Grails or Spring Roo.
Now let's assume that you want to specify the query you want to use. To do this, you can declare in the entity or your comment that the JPA named query orm.xml follows the naming convention (in this case). Alternatively, you can annotate the repository method @Query by:
@Transactional(readOnly = true) public interface AccountRepository extends JpaRepository<Account, Long> { @Query("<JPQ statement here>") List<Account> findByCustomer(Customer customer); }
Now let's compare the functionality of Customer Service Impl applications that we've seen so far:
@Repository @Transactional(readOnly = true) public class CustomerServiceImpl implements CustomerService { @PersistenceContext private EntityManager em; @Override public Customer findById(Long id) { return em.find(Customer.class, id); } @Override public List<Customer> findAll() { return em.createQuery("select c from Customer c", Customer.class).getResultList(); } @Override public List<Customer> findAll(int page, int pageSize) { TypedQuery query = em.createQuery("select c from Customer c", Customer.class); query.setFirstResult(page * pageSize); query.setMaxResults(pageSize); return query.getResultList(); } @Override @Transactional public Customer save(Customer customer) { // Is new? if (customer.getId() == null) { em.persist(customer); return customer; } else { return em.merge(customer); } } @Override public List<Customer> findByLastname(String lastname, int page, int pageSize) { TypedQuery query = em.createQuery("select c from Customer c where c.lastname = ?1", Customer.class); query.setParameter(1, lastname); query.setFirstResult(page * pageSize); query.setMaxResults(pageSize); return query.getResultList(); } }
Okay, let's first create Customer Repository and eliminate the CRUD method:
@Transactional(readOnly = true) public interface CustomerRepository extends JpaRepository<Customer, Long> { ... }
@Repository @Transactional(readOnly = true) public class CustomerServiceImpl implements CustomerService { @PersistenceContext private EntityManager em; @Autowired private CustomerRepository repository; @Override public Customer findById(Long id) { return repository.findById(id); } @Override public List<Customer> findAll() { return repository.findAll(); } @Override public List<Customer> findAll(int page, int pageSize) { TypedQuery query = em.createQuery("select c from Customer c", Customer.class); query.setFirstResult(page * pageSize); query.setMaxResults(pageSize); return query.getResultList(); } @Override @Transactional public Customer save(Customer customer) { return repository.save(customer); } @Override public List<Customer> findByLastname(String lastname, int page, int pageSize) { TypedQuery query = em.createQuery("select c from Customer c where c.lastname = ?1", Customer.class); query.setParameter(1, lastname); query.setFirstResult(page * pageSize); query.setMaxResults(pageSize); return query.getResultList(); } }
So far so good. Now there are two ways to deal with common scenarios: you don't want to access all entities of a given query, but only one page of them (for example, page 10, page 1). Now, this is done by two integers that appropriately restrict the query. There are two problems. Two integers actually represent a concept, which is not explicitly stated here. In addition, we returned a simple, List, so we lost metadata information about the actual data page: Is it the first page? Is this the last one? How many pages are there altogether? Spring Data provides an abstraction consisting of two interfaces: Pageable capturing paging request information and Page capturing results and meta-information. So let's try to add findByLastname(... ) Go to the repository interface and override findAll(... findByLastname(... ) As follows:
@Transactional(readOnly = true) public interface CustomerRepository extends JpaRepository<Customer, Long> { Page<Customer> findByLastname(String lastname, Pageable pageable); }
@Override public Page<Customer> findAll(Pageable pageable) { return repository.findAll(pageable); } @Override public Page<Customer> findByLastname(String lastname, Pageable pageable) { return repository.findByLastname(lastname, pageable); }
Make sure that test cases are adjusted according to signature changes, but they should work properly. This boils down to two things: we have a CRUD method to support paging, and the query execution mechanism knows the Pageable parameter. At this stage, our wrapper classes are actually outdated, because the client may use our repository interface directly. We get rid of the whole implementation code.
outline
In the course of this blog post, we have reduced the amount of code we have written for the repository to two interfaces in three ways and one line of XML:
@Transactional(readOnly = true) public interface CustomerRepository extends JpaRepository<Customer, Long> { Page<Customer> findByLastname(String lastname, Pageable pageable); }
@Transactional(readOnly = true) public interface AccountRepository extends JpaRepository<Account, Long> { List<Account> findByCustomer(Customer customer); }
<jpa:repositories base-package="com.acme.repositories" />
We have type-safe CRUD methods, query execution and built-in paging. Coolly, this applies not only to JPA-based repositories, but also to non-relational databases. The first non-relational database to support this approach will become part of the Spring Data Document release in a few days. You will get exactly the same functionality as Mongo DB, and we are working hard to support other databases. There are other functions that need to be explored (e.g. entity auditing, integration of custom data access codes), which we will use in our upcoming blog posts.
Original: https://spring.io/blog/2011/02/10/get-start-with-spring-data-jpa