Spring Data JPA simple learning

Start with a simple JPA example

This article mainly describes Spring Data JPA, but in order not to cause a large learning curve for beginners of JPA and Spring, we first start with JPA and briefly introduce a JPA example; then refactor the example and introduce the Spring framework, these two The part will not involve too much space. If you want to learn Spring and JPA in depth, you can further study according to the references provided at the end of this article.

Since JPA was released with Java EE 5, it has been sought after by major manufacturers and open source communities. Various commercial and open source JPA frameworks have sprung up, providing developers with a wealth of choices. It has changed the image of entity bean in EJB 2.x that was cumbersome and difficult to use, and fully absorbed the ORM idea that has been relatively mature in the open source community. In addition, it does not depend on the EJB container and can exist as an independent persistence layer technology. The more mature JPA frameworks include Jboss's Hibernate EntityManager, Oracle's EclipseLink donated to the Eclipse community, and Apache's OpenJPA.

The sample code in this article is developed based on Hibernate EntityManager, but readers can easily switch to other JPA frameworks without modifying any code, because all the interfaces/classes provided by the JPA specification are used in the code, and the framework itself is not used. private features. The example mainly involves seven files, but it's clear: the business layer contains an interface and an implementation; the persistence layer contains an interface, an implementation, and an entity class; plus a JPA configuration file and a test class. The relevant class/interface code is as follows:

Listing 1. Entity class AccountInfo.java
@Entity
 @Table(name = "t_accountinfo")
 public class AccountInfo implements Serializable {
 private Long accountId;
 private Integer balance;

 // The getter and setter methods are omitted here.
 }
Listing 2. Business layer interface UserService.java
public interface UserService {
 public AccountInfo createNewAccount(String user, String pwd, Integer init);
 }
Listing 3. The implementation class UserServiceImpl.java of the business layer
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);
 // Call the persistence layer to save the data
 return userDao.save(accountInfo);
    }
 }
Listing 4. Persistence layer interface
public interface UserDao {
 public AccountInfo save(AccountInfo accountInfo);
 }
Listing 5. The implementation class of 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;
    }
 }
Listing 6. JPA standard configuration file persistence.xml
<?xml version="1.0" encoding="UTF-8"?>
 <persistence xmlns="http://java.sun.com/xml/ns/persistence" version="2.0">
 <persistence-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>
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);
    }
 }

Briefly describe the Spring framework's support for JPA

Next we introduce Spring to demonstrate the Spring framework's support for JPA. The business layer interface UserService remains unchanged, and three annotations are added to UserServiceImpl to allow Spring to complete dependency injection, so it is no longer necessary to use the new operator to create the UserDaoImpl object. We also use Spring's declarative transactions:

Listing 8. Business layer implementation configured 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) { …… }
 }

For the persistence layer, the UserDao interface does not need to be modified, only the UserDaoImpl implementation needs to be modified. The modified code is as follows:

Listing 9. Persistence layer implementation configured as a Spring Bean
@Repository("userDao")
 public class UserDaoImpl implements UserDao {

 @PersistenceContext
 private EntityManager em;

 @Transactional
   public Long save(AccountInfo accountInfo) {
 em.persist(accountInfo);
 return accountInfo.getAccountId();
 }
 }
Listing 10. Spring configuration file
<?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>
Listing 11. Revamped 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);
 }
 }

By comparing the code before and after the refactoring, we can find that Spring's simplification of JPA has been very good. We can roughly summarize the support provided by the Spring framework for JPA mainly in the following aspects:

  • First, it makes JPA configuration more flexible. The JPA specification requires that the configuration file must be named persistence.xml and exist in the META-INF directory on the classpath. This file usually contains all the information needed to initialize the JPA engine. The LocalContainerEntityManagerFactoryBean provided by Spring provides very flexible configuration, and the information in persistence.xml can be provided here by way of property injection.
  • Secondly, Spring implements some functions that are only available in the EJB container environment, such as container injection support for @PersistenceContext and @PersistenceUnit.
  • Third, and most meaningful, Spring extracts the creation and destruction, transaction management and other codes of EntityManager, and manages them in a unified manner. Developers do not need to care about these. As shown in the previous code, only the business methods are left. The code for manipulating domain objects, transaction management, and the code for creating and destroying EntityManager no longer need developers to care.

Going a step further: Spring Data JPA makes everything nearly perfect

From the previous analysis, we can see that Spring's support for JPA is already very strong. Developers only need to care about the implementation code of the core business logic, and do not need to pay too much attention to JPA-related processing such as the creation of EntityManager and transaction processing. The limit of what a development framework can do. However, the Spring development team did not stop. They continued their efforts and recently launched the Spring Data JPA framework, which mainly aimed at the only business logic code that Spring has not simplified. So far, developers have only the remaining work to implement the business logic of the persistence layer. All are saved, the only thing to do is to declare the interface of the persistence layer, and leave the rest to Spring Data JPA to help you!

At this point, readers may have a question, how can the framework implement business logic instead of developers? After all, the persistence layer business and even the domain objects of each application are different. How does the framework do it? In fact, the idea behind this is not complicated. For example, when you see a method declaration such as UserDao.findUserById(), you should roughly be able to judge that this is to query the User object that meets the condition based on the ID of the given condition. What Spring Data JPA does is to standardize the name of the method, and determine what logic the method needs to implement according to the name that conforms to the specification.

Next, we will transform the previous example and let Spring Data JPA help us complete the business logic. Before starting to write the code, developers need to download the Spring Data JPA release package (you need to download both Spring Data Commons and Spring Data JPA release packages at the same time, Commons is the common base package of Spring Data), and put the related dependency JAR files Add to CLASSPATH.

First, let the persistence layer interface UserDao inherit the Repository interface. The interface uses generics and needs to be provided with two types: the first is the domain object type 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 interface
public interface UserDao extends Repository<AccountInfo, Long> {
    public AccountInfo save(AccountInfo accountInfo);
 }

Then delete 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. Enabling scanning and auto-creation of proxies in the Spring configuration file
<-- A reference to the jpa namespace needs to be added to the <beans> tag -->
 <jpa:repositories base-package="footmark.springdata.jpa.dao"
 entity-manager-factory-ref="entityManagerFactory"
 transaction-manager-ref="transactionManager"/>

So far you're done! Execute the test code, and then look at the database, the new data has been added to the table as we expected. If you want to add a new persistence layer business, for example, you want to query the AccountInfo object given the ID, what should you do? It's very simple, just 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 line of method declaration
 public AccountInfo findByAccountId(Long accountId);
 }

The following summarizes the three steps required for persistence layer development using Spring Data JPA:

  1. Declare the interface of the persistence layer, which inherits Repository. Repository is a marker interface, which does not contain any methods. Of course, if necessary, Spring Data also provides several Repository sub-interfaces, which define some commonly used additions, deletions, changes, and checks, and Paging related methods.
  2. Declare the required business methods in the interface. Spring Data will generate the implementation code for it according to the given strategy (the specific strategy will be explained later).
  3. Add a line of declaration to the Spring configuration file to let Spring create a proxy object for the declared interface. After configuring <jpa:repositories>, when Spring initializes the container, it will scan the package directory and its subdirectories specified by base-package, create proxy objects for interfaces that inherit Repository or its subinterfaces, and register the proxy objects as Spring Beans. The business layer can use the object directly through the features that Spring automatically encapsulates.

In addition, <jpa:repository> provides some attributes and subtags for more fine-grained control. You can use <context:include-filter>, <context:exclude-filter> inside <jpa:repository> to filter out some undesired interfaces. For specific usage, see the Spring reference documentation .

Which interface should be inherited?

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

Listing 15. Examples of two equivalent ways of inheriting interfaces
public interface UserDao extends Repository<AccountInfo, Long> { …… }

 @RepositoryDefinition(domainClass = AccountInfo.class, idClass = Long.class)
 public interface UserDao { …… }

If there are many interfaces in the persistence layer, and each interface needs to declare similar methods of adding, deleting, modifying, and checking, it is a bit verbose to directly inherit Repository. In this case, CrudRepository can be inherited, and it will automatically create adding, deleting, modifying and checking methods for domain objects for the business layer. Use directly. The developer just added four more letters of "Crud", and immediately provided ten CRUD methods out of the box for domain objects.

However, using CrudRepository also has side effects, it may expose methods you don't want to expose to the business layer. For example, for some interfaces, you only want to provide adding operations and do not want to provide delete methods. In response to this situation, developers can only go back to the Repository interface, and then go to CrudRepository and copy the method declarations that they want to keep to the custom interface.

Paging query and sorting are commonly used functions of the persistence layer. Spring Data provides the PagingAndSortingRepository interface for this purpose, which inherits from the CrudRepository interface and adds two paging-related methods based on CrudRepository. However, we rarely directly inherit the custom persistence layer interface from PagingAndSortingRepository, but on the basis of inheriting Repository or CrudRepository, add a Pageable or Sort type parameter at the end of the method parameter list declared by ourselves to specify paging Or sort the information, which provides more flexibility than using the PagingAndSortingRepository directly.

JpaRepository is an interface for JPA technology inherited from PagingAndSortingRepository. It provides other methods, such as flush(), saveAndFlush(), deleteInBatch(), etc. on the basis of the parent interface. If there is such a need, you can inherit this interface.

The above four interfaces, how should developers choose? In fact, the basis is very simple, according to the specific business needs, choose one of them. The author recommends that the Repository interface be preferred under normal circumstances. Because the Repository interface can already meet the daily needs, what other interfaces can do can also be done in the Repository, and there is no problem of functional strength between them. It's just that the Repository needs to display the methods required for the declaration, while others may already provide related methods and do not need to be explicitly declared. However, if you are not familiar with Spring Data JPA, others will have doubts when viewing the code or taking over the relevant code. I don't understand why three methods are declared in the persistence layer interface, but when the business layer uses this interface, it is found that there are seven or eight methods available. From this point of view, the Repository interface should be given priority.

As mentioned earlier, when Spring Data JPA creates a proxy object for the persistence layer interface in the background, it parses the method name and implements the corresponding function. In addition to the method name, it can also specify the query statement in the following two ways:

  1. Spring Data JPA can access JPA named query statements. Developers only need to specify a name that conforms to a given format when defining a named query, and Spring Data JPA will use the named query to implement its functions when creating a proxy object.
  2. Developers can also use the @Query annotation directly on the declared method and provide a query statement as a parameter. When Spring Data JPA creates a proxy object, it will use the provided query statement to realize its function.

Below we describe three ways to create a query.

Create a query by parsing the method name

Through the previous examples, the reader basically has a general understanding of how to parse method names to create queries, which is also a very important factor in attracting developers to Spring Data JPA. This function is not actually initiated by Spring Data JPA, but originated from Hades, an open source JPA framework. The author of the framework, Oliver Gierke, is also the leader of the Spring Data JPA project, so it is logical to introduce the advantages of Hades into Spring Data JPA. .

When the framework parses the method name, it first intercepts the redundant prefix of the method name, such as find, findBy, read, readBy, get, getBy, and then parses the rest. And if the last parameter of the method is of Sort or Pageable type, it will also extract relevant information for sorting by rules or paging query.

When creating a query, we express it by using the property name in the method name, such as findByUserAddressZip(). When the framework parses this method, it first removes findBy, and then parses the remaining attributes. The detailed rules are as follows (here, it is assumed that the domain object targeted by this method is of type AccountInfo):

  • First determine whether userAddressZip (according to the POJO specification, the first letter becomes lowercase, the same below) is an attribute of AccountInfo, if so, it means to query according to this attribute; if there is no such attribute, continue to the second step;
  • Intercept the string starting with the first uppercase letter (Zip here) from right to left, and then check whether the remaining string is an attribute of AccountInfo, if so, it means query according to this attribute; if there is no such attribute , then repeat the second step, continue to intercept from right to left; finally assume that user is an attribute of AccountInfo;
  • Then process the remaining part (AddressZip), first determine whether the type corresponding to user has the addressZip attribute, if so, it means that the method is finally queried according to the value of "AccountInfo.user.addressZip"; otherwise, continue to follow step 2. The rule is intercepted from right to left, and finally means that the query is performed according to the value of "AccountInfo.user.address.zip".

There may be a special case, such as AccountInfo contains a user attribute, also has a userAddress attribute, there will be confusion. Readers can explicitly add "_" between attributes to express intent, such as "findByUser_AddressZip()" or "findByUserAddress_Zip()".

When querying, it is usually necessary to query based on multiple attributes at the same time, and the query conditions are also in various formats (greater than a certain value, within a certain range, etc.), Spring Data JPA provides some keywords for expressing conditional queries for this purpose. , roughly as follows:

  • And --- Equivalent to the and keyword in SQL, 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, such as findByUsernameLike(String user);
  • NotLike --- Equivalent to "not like" in SQL, such as findByUsernameNotLike(String user);
  • OrderBy --- Equivalent to "order by" in SQL, such as findByUsernameOrderBySalaryAsc(String user);
  • Not --- Equivalent to "!=" in SQL, such as findByUsernameNot(String user);
  • In --- Equivalent to "in" in SQL, such as findByUsernameIn(Collection<String> userList), the parameters of the method can be of Collection type, arrays or indefinite-length parameters;
  • NotIn --- Equivalent to "not in" in SQL, such as findByUsernameNotIn(Collection<String> userList), the parameter of the method can be a Collection type, an array or a variable length parameter;

Create a query with @Query

The use of the @Query annotation is very simple, just mark the annotation on the declared method and provide a JP QL query statement, as shown below:

Listing 16. Example of providing 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);
 }

Many developers prefer to use named parameters instead of position numbers when creating JP QL, and @Query supports this as well. In the JP QL statement, the parameters are specified in the format of ": variable", and @Param is used in front of the method parameters to correspond the method parameters to the named parameters in JP QL. The example is as follows:

Listing 17. @Query supports named parameters example
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);
 }

In addition, developers can also perform an update operation by using @Query. To this end, we need to use @Query and use @Modifying to mark the operation as a modifying query, so that the framework will eventually generate an update operation, rather than query. As follows:

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

Create a query by calling a JPA named query statement

Named query is a function provided by JPA to separate the query statement from the method body so that it can be shared by multiple methods. Spring Data JPA also has good support for named queries. The user only needs to define the query statement in the orm.xml file according to the JPA specification or use @NamedQuery (or @NamedNativeQuery) in the code. rule. Suppose the following interface is defined:

Listing 19. When using JPA named queries, declaring interfaces and methods requires no special handling
public interface UserDao extends Repository<AccountInfo, Long> {

 ......
   
 public List<AccountInfo> findTop5();
 }

If we want to create a named query for findTop5() and associate it with it, we only need to define the named query statement in the appropriate place, and name it "AccountInfo.findTop5", the framework resolves to this during the process of creating the proxy class method, first look for a named query definition named "AccountInfo.findTop5", if not found, try to parse the method name and create a query based on the method name.

The order in which the query was created

When Spring Data JPA creates a proxy object for an interface, if it finds that multiple of the above situations are available at the same time, which strategy should it prefer? To this end, <jpa:repositories> provides the query-lookup-strategy attribute to specify the order of lookups. It has the following three values:

  • create --- create a query by parsing the method name. Even if there is a matching named query, or the method specified by the @Query statement, it will be ignored.
  • create-if-not-found --- If the method specifies a query statement through @Query, use this statement to implement the query; if not, find out whether a qualified named query is defined, and if found, use the named query; If neither is found, a query is created by parsing the method name. This is the default value of the query-lookup-strategy property.
  • use-declared-query --- If the method specifies a query statement through @Query, use this statement to implement the query; if not, find out whether a qualified named query is defined, if found, use the named query; if two If none is found, an exception is thrown.

Spring Data JPA support for transactions

By default, Spring Data JPA implements methods that use transactions. Methods for query types are equivalent to @Transactional(readOnly=true); methods for adding, deleting, and modifying types are equivalent to @Transactional. It can be seen that, except for setting the query method as a read-only transaction, all other transaction attributes use default values.

If the user deems necessary, the transaction attribute can be explicitly specified using @Transactional on the interface method, which overrides the default value provided by Spring Data JPA. At the same time, developers can also use @Transactional to specify transaction attributes on business layer methods. This is mainly for the case that a business layer method calls the persistence layer method multiple times. The transaction of the persistence layer will decide whether to suspend the transaction of the business layer or join the transaction of the business layer according to the set transaction propagation behavior. For specific usage of @Transactional, please refer to Spring 's reference documentation .

Provide custom implementations for some methods in the interface

Sometimes, developers may need to do some special processing in some methods, and the automatically generated proxy objects cannot fully meet the requirements. In order to enjoy the convenience brought by Spring Data JPA and provide custom implementations for some methods, we can use the following methods:

  • Extract the methods that need to be implemented manually by developers from the persistence layer interface (assuming AccountDao), and form a new interface (assuming AccountDaoPlus), and let AccountDao inherit AccountDaoPlus;
  • Provide a custom implementation for AccountDaoPlus (let's say AccountDaoPlusImpl);
  • Configure AccountDaoPlusImpl as a Spring Bean;
  • Configure as shown in Listing 19 in <jpa:repositories>.
Listing 20. Specifying a 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="......."/>

In addition, <jpa:repositories> provides a repository-impl-postfix attribute to specify the suffix of the implementing class. Suppose the following configuration is made:

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

When the framework scans the AccountDao interface, it will try to find AccountDaoImpl.java in the same package directory, and if found, the implementation method in it will be used as the implementation of the corresponding method in the final generated proxy class.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325999082&siteId=291194637