Simplify JPA development with Spring Data JPA

Spring Data JPA Development Guide

Start with a simple JPA example

This article focuses on Spring Data JPA, but to avoid creating a big learning curve for JPA and Spring beginners, let’s start with JPA and briefly introduce a JPA example; You can then refactor the example and introduce the Spring framework. These two parts won’t cover too much space, but if you want to learn more about Spring and JPA, you can follow the resources provided at the end of this article.

Since the release of JPA with Java EE 5, it has been embraced by major vendors and the open source community. A variety of commercial and open source JPA frameworks have sprung up, providing developers with a wealth of choices. It is a change from EJB 2.x’s image of entity beans as cumbersome and difficult to use, and fully incorporates ORM ideas that have been relatively mature in the open source community. In addition, it does not rely on the EJB container and can exist as a separate persistence layer technology. At present, mature JPA frameworks mainly include Hibernate EntityManager by Jboss, EclipseLink by Oracle donated to the Eclipse community, OpenJPA by Apache, etc.

The sample code in this article is developed based on Hibernate EntityManager, but the reader can easily switch to another JPA framework with almost no code changes, as the code uses interfaces/classes provided by the JPA specification and does not use proprietary features of the framework itself. The examples mainly involve seven files, but are clear: the business layer contains an interface and an implementation; The persistence layer contains an interface, an implementation, and an entity class. Add a JPA configuration file and a test class. The related class/interface codes are as follows:

Listing 1. The entity class AccountInfo.java
@Entity @Table(name = "t_accountinfo") public class AccountInfo implements Serializable { private Long accountId; private Integer balance; // Omit the getter and setter methods here. }Copy the code
Listing 2. Business layer interface userService.java
public interface UserService { 
public AccountInfo createNewAccount(String user, String pwd, Integer init); 
}Copy the code
Listing 3. The implementation class for the business layer, UserServiceImp.java
public class UserServiceImpl implements UserService { private UserDao userDao = new UserDaoImpl(); public AccountInfo createNewAccount(String user, String pwd, Integer init){// Encapsulate the domain object AccountInfo AccountInfo = new AccountInfo(); UserInfo userInfo = new UserInfo(); userInfo.setUsername(username); userInfo.setPassword(password); accountInfo.setBalance(initBalance); accountInfo.setUserInfo(userInfo); Return userdao.save (accountInfo); // Invoke the persistence layer to save data. }}Copy the code
Listing 4. The persistence layer interface
public interface UserDao { 
public AccountInfo save(AccountInfo accountInfo); 
}Copy the code
Listing 5. Implementation class for the persistence layer
public class UserDaoImpl implements UserDao { public AccountInfo save(AccountInfo accountInfo) { EntityManagerFactory emf = Persistence.createEntityManagerFactory("SimplePU"); EntityManager em = emf.createEntityManager(); em.getTransaction().begin(); em.persist(accountInfo); em.getTransaction().commit(); emf.close(); return accountInfo; }}Copy the code
Listing 6. The JPA standard configuration file persistence.xml
<? The XML version = "1.0" encoding = "utf-8"? > < persistence XMLNS = "http://java.sun.com/xml/ns/persistence" version = "2.0" > < persistence - the unit name = "SimplePU" transaction-type="RESOURCE_LOCAL"> <provider>org.hibernate.ejb.HibernatePersistence</provider> <class>footmark.springdata.jpa.domain.UserInfo</class> <class>footmark.springdata.jpa.domain.AccountInfo</class> <properties> <property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver"/> <property Name = "hibernate. Connection. Url" value = "JDBC: mysql: / / 10.40.74.197:3306 / zhangjp" / > < property name="hibernate.connection.username" value="root"/> <property name="hibernate.connection.password" value="root"/> <property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5Dialect"/> <property name="hibernate.show_sql" value="true"/> <property name="hibernate.format_sql" value="true"/> <property name="hibernate.use_sql_comments" value="false"/> <property name="hibernate.hbm2ddl.auto" value="update"/> </properties> </persistence-unit> </persistence>Copy the code
Listing 7. This article uses the following main method for developer testing
public class SimpleSpringJpaDemo { public static void main(String[] args) { new UserServiceImpl().createNewAccount("ZhangJianPing", "123456", 1); }}Copy the code

Describe the Spring framework’s support for JPA

Next, let’s introduce Spring to demonstrate the Spring framework’s support for JPA. The business layer interface UserService remains unchanged, and three annotations have been added to UserServiceImpl to enable Spring to complete dependency injection, so the New operator is no longer required to create the UserDaoImpl object. We also use Spring’s declarative transactions:

Listing 8. Configure the business layer implementation as a Spring Bean
@Service("userService") public class UserServiceImpl implements UserService { @Autowired private UserDao userDao; @Transactional Public AccountInfo createNewAccount(String Name, String PWD, Integer init) {...... }}Copy the code

For the persistence layer, the UserDao interface also does not need to be modified, just the UserDaoImpl implementation, which looks like this:

Listing 9. Configure as a persistence layer implementation for Spring beans
@Repository("userDao") public class UserDaoImpl implements UserDao { @PersistenceContext private EntityManager em; @Transactional public Long save(AccountInfo accountInfo) { em.persist(accountInfo); return accountInfo.getAccountId(); }}Copy the code
Listing 10. Spring configuration file
<? The XML version = "1.0" encoding = "utf-8"? > <beans... > <context:component-scan base-package="footmark.springdata.jpa"/> <tx:annotation-driven transaction-manager="transactionManager"/> <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"> <property name="entityManagerFactory" ref="entityManagerFactory"/> </bean> <bean id="entityManagerFactory" class= "org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> </bean> </beans>Copy the code
Listing 11. Modified Spring-based developer test code
public class SimpleSpringJpaDemo{ public static void main(String[] args){ ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("spring-demo-cfg.xml"); UserDao userDao = ctx.getBean("userDao", UserDao.class); userDao.createNewAccount("ZhangJianPing", "123456", 1); }}Copy the code

By comparing the code before and after refactoring, we can see that Spring’s JPA simplification is excellent. We can summarize the Spring framework’s support for JPA in the following aspects:

  • First, it makes JPA configuration more flexible. The JPA specification requires that the configuration file be named persistence.xml and reside in the meta-INF directory under the classpath. This file usually contains all the information needed to initialize the JPA engine. Spring provides LocalContainerEntityManagerFactoryBean provides a very flexible configuration, persistence. The information in the XML can be provided in the form of attributes into here.
  • Second, Spring implements some of the features that are only available in the EJB container environment, such as container injection support for @Persistencecontext and @PersistenceUnit.
  • Third, and most significant, Spring abstracts and manages EntityManager creation and destruction, transaction management, and so on. Developers don’t need to care about these. As shown in the previous code, the business methods only have code that operates on domain objects. Transaction management and EntityManager creation and destruction of code are no longer a developer’s concern.

One step further: Spring Data JPA makes everything nearly perfect

As we can see from the previous analysis, Spring’s support for JPA is already very strong, and developers only need to worry about the implementation code of the core business logic, and do not need to worry too much about the creation of EntityManager, transaction processing and other JPA-related processing, which is basically the limit of what can be done as a development framework. However, the Spring development team has not stopped there. They have recently released the Spring Data JPA framework, which addresses the only business logic code that Spring has not simplified. So far, developers have been left to implement the persistence layer business logic. Just declare the interface to the persistence layer and let Spring Data JPA do the rest for you!

At this point, the reader might wonder, how can a framework implement business logic on the developer’s behalf? After all, the persistence layer business and even domain objects of every application are different. How does the framework do this? The idea behind this isn’t that complicated. For example, when you see a method declaration such as userdao.findUserById (), you should probably be able to tell that this is a query for a User object that satisfies a given condition based on its ID. What Spring Data JPA does is specify the name of the method and determine what logic the method needs to implement based on the name that conforms to the specification.

Let’s adapt the previous example and let Spring Data JPA help us with the business logic. Before starting to write code, developers need to download the Spring Data JPA distribution package (Spring Data Commons and Spring Data JPA are both required, Commons is the common base package of Spring Data). Add the associated dependent JAR files to your CLASSPATH.

First, let the persistence layer interface UserDao inherit from the Repository interface. This interface uses generics and needs to be provided with two types: the first is the type of the domain object handled by the interface, and the second is the primary key type of the domain object. The modified UserDao is as follows:

Listing 12. Spring Data JPA-style persistence layer interface
public interface UserDao extends Repository<AccountInfo, Long> { 
   public AccountInfo save(AccountInfo accountInfo); 
}Copy the code

Then remove the UserDaoImpl class because, as we said earlier, the framework will do the business logic for us. Finally, we need to add the following configuration to the Spring configuration file to make Spring recognize the persistence layer interface that needs to be implemented for it:

Listing 13. Enable scanning and automatically creating proxies in the Spring configuration file
In < < - beans > tag to add jpa namespace reference -- > < jpa: repositories base - package = "footmark. Springdata. Jpa. Dao" entity-manager-factory-ref="entityManagerFactory" transaction-manager-ref="transactionManager"/>Copy the code

And that’s it! Run the test code and look at the database. The new data has been added to the table as expected. What if you want to add a new persistence layer business, such as an AccountInfo object that you want to query for ID? Simply add a line of code to the UserDao interface:

Listing 14. Modified persistence layer interface, adding a method declaration
public interface UserDao extends Repository<AccountInfo, Long> { public AccountInfo save(AccountInfo accountInfo); // All you need to do is add the following method declaration: public AccountInfo findByAccountId(Long accountId); }Copy the code

Here’s a summary of the three steps required to develop a persistence layer using Spring Data JPA:

  1. Repository is a marker interface that does not contain any methods. Spring Data also provides several Repository subinterfaces that define common additions, deletions, changes, and searches. And paging related methods.
  2. Declare the required business methods in the interface. Spring Data will generate implementation code for this based on the given policy, which will be explained later.
  3. Add a one-line declaration to the Spring configuration file that lets Spring create a proxy object for the declared interface. With < JPA: Repositories > configured, Spring initializes the container by scanning the package directory specified by base-Package and its subdirectories, creating proxy objects for interfaces that inherit Repository or their subinterfaces, and registering proxy objects as Spring beans. The business layer can then use this object directly through Spring’s auto-wrapped features.

In addition, < JPA: Repository > provides attributes and subtags for finer grained control. You can use

and

inside < jPA :repository to filter out interfaces that you do not want to be scanned. See the Spring Reference documentation for details on how to use it.

Which interface should be inherited?

As mentioned earlier, inheriting Repository from the persistence layer interface is not the only option. The Repository interface is a core interface of Spring Data. It does not provide any methods, and developers need to declare the required methods in their own interfaces. An equivalent of inheriting Repository is to use the @RepositoryDefinition annotation on the persistence layer interface and specify the domainClass and idClass properties for it. The following two approaches are completely equivalent:

Listing 15. Examples of two equivalent ways of inheriting an interface
Public Interface UserDao extends Repository<AccountInfo, Long> {...... } @repositoryDefinition (domainClass = AccountInfo.class, idClass = Long.class) public Interface UserDao {...... }Copy the code

If the persistence layer has a large number of interfaces, and each interface needs to declare similar add, delete, change, and query methods, it may be a bit cumbersome to inherit Repository directly. Instead, inherit CrudRepository, which automatically creates add, delete, change and query methods for domain objects for use directly by the business layer. The developers simply added “Crud” and immediately provided ten methods for adding, deleting, modifying, and viewing domain objects out of the box.

However, using CrudRepository has the side effect of exposing methods that you do not want to expose to the business layer. For example, in some interfaces you only want to provide operations to add but not methods to delete. In this case, the developer can only fall back to the Repository interface and then go to CrudRepository and copy the method declarations they want to keep into the custom interface.

Paging query and sort is persistence layer commonly used functions, Spring Data provides PagingAndSortingRepository interface, it inherits from CrudRepository interface, Two new methods related to paging have been added to CrudRepository. However, we rarely will custom persistence layer interface directly inherited from PagingAndSortingRepository, but, on the basis of inheriting the Repository or CrudRepository In their statement method parameter list finally add a Pageable or Sort type parameters, is used to specify the paging or sorting information, than the direct use of PagingAndSortingRepository provides more flexibility.

JpaRepository is inherited from PagingAndSortingRepository of JPA technology provided interfaces, it, on the basis of the parent interface provides some other methods, such as flush (), saveAndFlush (), DeleteInBatch (), etc. If such a requirement exists, the interface can be inherited.

How should developers choose from these four interfaces? In fact, the basis is very simple, according to the specific business needs, choose one of them. The authors recommend that the Repository interface be preferred in general. Because the Repository interface already meets everyday needs, other interfaces can do what they do in Repository as well, and there is no issue of strength or weakness between them. However, if you are not familiar with the Spring Data JPA, it may be confusing for others to view or handle the relevant code. They do not understand why there are seven or eight methods available at the business layer when three methods are declared in the persistence layer interface. From this point of view, the Repository interface should be preferred.

As mentioned earlier, the Spring Data JPA parses method names and implements functionality when creating proxy objects for the persistence layer interface behind the scenes. In addition to the method name, it can specify a query statement in two ways:

  1. Spring Data JPA has access to JPA named queries. All you need to do is define a named query with a name that conforms to a given format, and Spring Data JPA uses the named query to implement its functionality when creating the proxy object.
  2. Developers can also use the @Query annotation directly on declared methods and provide a Query statement as a parameter, which Spring Data JPA uses to implement its functionality when creating proxy objects.

Here are three ways to create a query.

Create queries by resolving method names

From the previous examples, the reader basically gets a sense of how parsing method names creates queries, which is a big part of the appeal of Spring Data JPA to developers. This feature is not actually a Spring Data JPA initiative, but is derived from an open source JPA framework, Hades, written by Oliver Gierke, who is himself the Leader of the Spring Data JPA project. So it makes sense to bring the advantages of Hades to Spring Data JPA.

When parsing a method name, the framework intercepts the prefixes of the method name, such as find, findBy, read, readBy, get, and getBy, and then parses the rest. And if the last argument to the method is of type Sort or Pageable, the relevant information is also extracted for regular sorting or paging queries.

When creating a query, we express this by using the attribute name in the method name, such as findByUserAddressZip (). When parsing this method, the framework first removes findBy and then parses the remaining attributes as follows (assuming the domain object is of type AccountInfo) :

  • Check whether userAddressZip is an attribute of AccountInfo. If so, query is performed based on this attribute. If not, proceed to step 2;
  • Truncate the string starting with the first uppercase letter (Zip in this case) from right to left, and then check whether the remaining string is an attribute of AccountInfo, and if so, query against that attribute; If not, repeat the second step and continue to intercept from right to left. Finally, assume that user is an attribute of AccountInfo;
  • Then deal with the remaining part (AddressZip), first determine the user corresponds to the type of AddressZip attributes, if any, indicates the method is ultimately based on “AccountInfo. User. AddressZip” query values; Otherwise follow the rules of the step 2 of from right to left, finally said, according to “AccountInfo. User. Address. Zip” the value of the query.

There may be a special case where there is confusion, such as when AccountInfo contains a user attribute and also a userAddress attribute. Readers can explicitly add “_” between attributes to express intent, such as “findByUser_AddressZip()” or “findByUserAddress_Zip()”.

Spring Data JPA provides some keywords to express conditional queries, which are typically queried against multiple attributes at the same time and in a variety of formats (greater than a certain value, in a certain range, and so on).

  • In the And – equivalent to the SQL And keywords, such as findByUsernameAndPassword (String user, Striang PWD).
  • Or — equivalent to the Or keyword in SQL, such as findByUsernameOrAddress(String user, String addr);
  • Between – Equivalent to the Between keyword in SQL, such as findBySalaryBetween(int Max, int min);
  • LessThan – Equivalent to “<” in SQL, such as findBySalaryLessThan(int Max);
  • GreaterThan — equivalent to “>” in SQL, such as findBySalaryGreaterThan(int min);
  • IsNull — equivalent to “is null” in SQL, such as findByUsernameIsNull();
  • IsNotNull — equivalent to “is not null” in SQL, such as findByUsernameIsNotNull();
  • NotNull – equivalent to IsNotNull;
  • Like – equivalent to “Like” in SQL, as in findByUsernameLike(String user);
  • NotLike – equivalent to “not like” in SQL, as in findByUsernameNotLike(String user);
  • OrderBy – equivalent to the SQL “order by”, such as findByUsernameOrderBySalaryAsc (String user);
  • Not — equivalent to “! FindByUsernameNot (String user);
  • In – equivalent to “In” In SQL, such as findByUsernameIn(Collection

    userList). The arguments to the method can be Collection, array, or variable.
  • NotIn – equivalent to “not in” in SQL, such as findByUsernameNotIn(Collection

    userList). Methods can take arguments of Collection type, array, or variable length.

Create a Query using @query

Using the @query annotation is as simple as annotating the annotation above the declared method and providing a JP QL Query statement, as follows:

Listing 16. Provides an example of a custom Query statement using @Query
public interface UserDao extends Repository<AccountInfo, Long> { @Query("select a from AccountInfo a where a.accountId = ? 1") public AccountInfo findByAccountId(Long accountId); @Query("select a from AccountInfo a where a.balance > ? 1") public Page<AccountInfo> findByBalanceGreaterThan( Integer balance,Pageable pageable); }Copy the code

Many developers like to use named arguments instead of positional numbers when creating JP QL, and @Query also supports this. The JP QL statement uses the format “: variable “to specify the parameter, and uses @param in front of the method parameter to correspond to the named parameter in JP QL, as shown in the following example:

Listing 17.@query supports an example of named parameters
public interface UserDao extends Repository<AccountInfo, Long> { 
 
public AccountInfo save(AccountInfo accountInfo); 
 
@Query("from AccountInfo a where a.accountId = :id") 
public AccountInfo findByAccountId(@Param("id")Long accountId); 
 
  @Query("from AccountInfo a where a.balance > :balance") 
  public Page<AccountInfo> findByBalanceGreaterThan( 
@Param("balance")Integer balance,Pageable pageable); 
}Copy the code

In addition, developers can perform an update operation by using @Query. To do so, we need to use @Query while marking the operation as a Modifying Query with @modifying, so that the framework ends up producing an update operation instead of a Query. As follows:

Listing 18. Using @modifying to identify a query as a modified query
@Modifying @Query("update AccountInfo a set a.salary = ? 1 where a.salary < ? 2") public int increaseSalary(int after, int before);Copy the code

The query is created by calling the JPA named query statement

Named queries are a feature provided by JPA to separate query statements from the method body for use by multiple methods. Spring Data JPA also provides good support for named queries. The user only needs to define the query using @NamedQuery (or @NamedNativeQuery) in the orm. XML file or in the code according to the JPA specification. The only thing to do is to name the query. The domainclass.methodName () naming rule must be met. Suppose the following interface is defined:

Listing 19. When using JPA named queries, no special handling is required to declare interfaces and methods
public interface UserDao extends Repository<AccountInfo, Long> { 
 
...... 
   
public List<AccountInfo> findTop5(); 
}Copy the code

If we want to create and associate a named query with findTop5(), we simply define a named query statement in place and name it “accountInfo.findTop5”. When the framework resolves to this method during the creation of the proxy class, The named query definition named “accountInfo.findtop5” is searched first. If it is not found, the method name is resolved and the query is created based on the method name.

The order in which queries are created

When Spring Data JPA creates proxy objects for interfaces, which strategy should it use in preference if it finds that more than one of these conditions are available at the same time? For this purpose, < JPA: Repositories > provides the query-lookup-strategy attribute, which specifies the order of lookups. It has the following three values:

  • Create – Creates a query by parsing the method name. Even if there is a matching named Query, or if the method is specified by @query, it will be ignored.
  • Create-if-not-found — If the method specified a Query statement with @query, the Query will be implemented with that statement; If not, it looks up whether a qualified named query is defined. If so, the named query is used. If neither is found, the query is created by parsing the method name. This is the default value for the query-lookup-strategy attribute.
  • Use-declared -query — if the method specified a query statement via @query, the query will be used. If not, it looks up whether a qualified named query is defined. If so, the named query is used. If neither is found, an exception is thrown.

Spring Data JPA support for transactions

By default, the methods implemented by Spring Data JPA use transactions. Methods for query types are equivalent to @transactional (readOnly=true); Methods for adding, subtracting, and changing types are equivalent to @transactional. As you can see, the default transaction attributes are used except for setting the method of the query to a read-only transaction.

Users can explicitly specify transaction attributes using @Transactional on interface methods if they feel it necessary, which overrides the default values provided by Spring Data JPA. Developers can also use @Transactional on business layer methods to specify transaction attributes, especially when a business layer method calls persistence layer methods multiple times. Transactions at the persistence layer decide whether to suspend or join a business layer transaction based on the transaction propagation behavior set. Refer to Spring’s reference documentation for the use of @Transactional.

Provides custom implementations for some of the methods in the interface

In some cases, the developer may need to do some special processing in some methods, and the automatically generated proxy object may not be sufficient. To enjoy the benefits of Spring Data JPA while providing custom implementations for some methods, we can use the following methods:

  • The methods that need to be manually implemented by developers are extracted from the persistence layer interface (let’s say AccountDao) into a new interface (let’s say AccountDaoPlus), and the AccountDao inherits AccountDaoPlus.
  • Provide a custom implementation for AccountDaoPlus (assuming AccountDaoPlusImpl);
  • Configure AccountDaoPlusImpl as a Spring Bean;
  • Configure it in < JPA: Repositories > as shown in Listing 19.
Listing 20. Specifying the custom implementation class
<jpa:repositories base-package="footmark.springdata.jpa.dao"> <jpa:repository id="accountDao" repository-impl-ref=" accountDaoPlus " /> </jpa:repositories> <bean id="accountDaoPlus" class="......." />Copy the code

In addition, < JPA: Repositories > provides a repository-Imp-Postfix property that specifies the suffix for the implementation class. Assume that the following configuration is performed:

Listing 21. Setting the default custom implementation class naming rules for automatic lookups
<jpa:repositories base-package="footmark.springdata.jpa.dao"
repository-impl-postfix="Impl"/>Copy the code

When the framework scans the AccountDao interface, it tries to find AccountDaoImp.java in the same package directory and, if found, uses the implementation method as an implementation of the corresponding method in the resulting proxy class.

conclusion

This article focuses on the use of Spring Data JPA and its seamless integration with the Spring framework. The Spring Data JPA does not actually rely on the Spring framework, but interested readers should refer to the “Resources” section at the end of this article for further information.

Download resources

  • The sample code (sample – code. Rar | 5 KB)

On the topic

  • Visit the Spring Data home page to learn about the Spring Data framework and its support for different persistence technologies, as well as extensive learning documentation, and download the latest Spring Data JPA release.
  • Join the Spring Data forum, where you can talk to developers around the world and get your questions answered right away.
  • A guide to getting started by the Spring Data JPA project lead. You can use this as a starting point for learning about the Spring Data JPA framework.
  • Download the SpringSource Tool Suite, a free Eclipse-based IDE from SpringSource that provides powerful support for developing Spring applications.
  • Stay tuned for developerWorks technical events and Webcasts.
  • Visit the developerWorks Open Source zone for a wealth of how-to information, tool and project updates, and the most popular articles and tutorials to help you develop with Open source technologies and use them with IBM products.