Spring Data JPA @Query
1.概述
Spring Data提供了许多方法来定义我们可以执行的查询。其中之一是@Query注解。
我们将演示如何在Spring Data JPA中使用@Query注解执行JPQL和Native SQL查询。
我们还将展示在@Query注解不够时如何构建动态查询。
2.Select Query
为了定义要为Spring Data repository method 执行的SQL,我们可以使用@Query注解对方法进行注解-其value属性包含要执行的JPQL或SQL。
@Query注解优先于使用@NamedQuery注解或在orm.xml文件中定义的命名查询。
2.1 JPQL
默认情况下,查询定义使用JPQL。
让我们看一个简单的repository method ,该方法从数据库返回活动的User实体:
@Query("SELECT u FROM User u WHERE u.status = 1")
Collection<User> findAllActiveUsers();
2.2 Native
我们还可以使用native SQL定义查询。我们要做的就是将nativeQuery属性的值设置为true,并在注解的value属性中定义Native SQL查询:
@Query( value = "SELECT * FROM USERS u WHERE u.status = 1", nativeQuery = true) Collection<User> findAllActiveUsersNative();
3.在查询中定义顺序
我们可以将Sort类型的附加参数传递给具有@Query注解的Spring Data方法声明。它将转换为传递给数据库的ORDER BY子句。
3.1 JPA提供和派生方法的排序
对于我们开箱即用的方法,例如findAll(Sort)或通过解析方法签名生成的方法,我们只能使用对象属性来定义我们的排序:
userRepository.findAll(Sort.by(Sort.Direction.ASC, "name"));
现在假设我们要按名称属性的长度进行排序:
userRepository.findAll(Sort.by("LENGTH(name)"));
当我们执行上面的代码时,我们将收到一个异常:
org.springframework.data.mapping.PropertyReferenceException: No property LENGTH(name) found for type User!
3.2 JPQL
当我们使用JPQL作为查询定义时,Spring Data可以毫无问题地处理排序-我们要做的就是添加类型为Sort的方法参数:
@Query(value = "SELECT u FROM User u")
List<User> findAllUsers(Sort sort);
我们可以调用此方法并传递一个Sort参数,该参数将按User对象的name属性对结果进行排序:
userRepository.findAllUsers(Sort.by("name"));
并且由于我们使用了@Query注解,因此我们可以使用相同的方法按用户名称的长度来获取用户的排序列表:
userRepository.findAllUsers(JpaSort.unsafe("LENGTH(name)"));
至关重要的是,我们使用JpaSort.unsafe()创建Sort对象实例。
当我们使用时:
Sort.by("LENGTH(name)");
那么我们将收到与上面在findAll()方法中看到的完全相同的异常。
当Spring Data发现使用@Query注解的方法的不安全排序顺序时,它将仅将sort子句附加到查询中-跳过检查要排序的属性是否属于域模型。
3.3 Native
当@Query注解使用native SQL时,则无法定义排序。
如果这样做,我们将收到一个异常:
org.springframework.data.jpa.repository.query.InvalidJpaQueryMethodException: Cannot use native queries with dynamic sorting and/or pagination
如异常所示,native查询不支持该排序。该错误消息向我们暗示了分页也会导致异常。
但是,有一种变通方法可以启用分页,我们将在下一节中介绍它。
4.Pagination
分页使我们可以在Page中仅返回整个结果的一个子集。例如,在浏览网页上的几页数据时,这很有用。
分页的另一个优点是可以最大程度地减少从服务器发送到客户端的数据量。通过发送较小的数据,我们通常可以看到性能的提高。
4.1 JPQL
在JPQL查询定义中使用分页很简单:
@Query(value = "SELECT u FROM User u ORDER BY id")
Page<User> findAllUsersWithPagination(Pageable pageable);
我们可以传递一个PageRequest参数来获取数据页面。
本机查询也支持分页,但是需要一些额外的工作。
4.2 Native
我们可以通过声明其他属性countQuery为native query启用分页。
这定义了要执行以计算数量的SQL
整个结果中的行:
@Query( value = "SELECT * FROM Users ORDER BY id", countQuery = "SELECT count(*) FROM Users", nativeQuery = true) Page<User> findAllUsersWithPagination(Pageable pageable);
4.3 2.0.4之前的Spring Data JPA版本
以上针对本机查询的解决方案在Spring Data JPA 2.0.4及更高版本中均能正常工作。
在该版本之前,当我们尝试执行此类查询时,我们将收到与上一部分有关排序的异常相同的异常。
我们可以通过在查询中添加一个用于分页的附加参数来克服此问题:
@Query( value = "SELECT * FROM Users ORDER BY id \n-- #pageable\n", countQuery = "SELECT count(*) FROM Users", nativeQuery = true) Page<User> findAllUsersWithPagination(Pageable pageable);
在上面的示例中,我们添加“ \ n– #pageable \ n”作为分页参数的占位符。这告诉Spring Data JPA如何解析查询并注入pageable参数。该解决方案适用于H2数据库。
我们已经介绍了如何通过JPQL和本机SQL创建简单的选择查询。接下来,我们将展示如何定义其他参数。
5.Indexed Query Parameters
我们可以通过两种方法将方法参数传递给查询:索引参数和命名参数。
在本节中,我们将介绍索引参数。
5.1 JPQL
对于JPQL中的索引参数,Spring Data会将方法参数按照在方法声明中出现的顺序传递给查询:
@Query("SELECT u FROM User u WHERE u.status = ?1") User findUserByStatus(Integer status); @Query("SELECT u FROM User u WHERE u.status = ?1 and u.name = ?2") User findUserByStatusAndName(Integer status, String name);
5.2 Native
native query的索引参数与JPQL的工作方式完全相同:
@Query( value = "SELECT * FROM Users u WHERE u.status = ?1", nativeQuery = true) User findUserByStatusNative(Integer status);
在下一节中,我们将展示一种不同的方法:通过名称传递参数。
6.Named Parameters
我们还可以使用命名参数将方法参数传递给查询。我们使用存储库方法声明中的@Param批注定义它们。
用@Param注解的每个参数必须具有与相应的JPQL或SQL查询参数名称匹配的值字符串。具有命名参数的查询更易于阅读,并且在需要重构查询的情况下更不易出错。
6.1 JPQL
如上所述,我们在方法声明中使用@Param批注,以将JPQL中由名称定义的参数与方法声明中的参数进行匹配:
@Query("SELECT u FROM User u WHERE u.status = :status and u.name = :name") User findUserByStatusAndNameNamedParams( @Param("status") Integer status, @Param("name") String name);
请注意,在上面的示例中,我们将SQL查询和方法参数定义为具有相同的名称,但是只要值字符串相同,就不需要此参数:
@Query("SELECT u FROM User u WHERE u.status = :status and u.name = :name") User findUserByUserStatusAndUserName(@Param("status") Integer userStatus, @Param("name") String userName);
6.2 Native
对于native query定义,与JPQL相比,我们如何通过名称将参数传递给查询没有什么区别-我们使用@Param批注:
@Query(value = "SELECT * FROM Users u WHERE u.status = :status and u.name = :name", nativeQuery = true) User findUserByStatusAndNameNamedParamsNative( @Param("status") Integer status, @Param("name") String name);
7.Collection Parameter
让我们考虑一下JPQL或SQL查询的where子句包含IN(or NOT IN)关键字的情况:
SELECT u FROM User u WHERE u.name IN :names
在这种情况下,我们可以定义一个以Collection为参数的查询方法:
@Query(value = "SELECT u FROM User u WHERE u.name IN :names")
List<User> findUserByNameList(@Param("names") Collection<String> names);
由于该参数是一个Collection,因此可以与List,HashSet等一起使用。
接下来,我们将展示如何使用@Modifying注解修改数据。
8.使用@Modifying Update Queries
我们还可以通过将@Modifying注解添加到repository method中,使用@Query注解来修改数据库的状态。
8.1 JPQL
与select查询相比,修改数据的repository method有两个区别-它具有@Modifying注解,当然,JPQL查询使用update而不是select:
@Modifying @Query("update User u set u.status = :status where u.name = :name") int updateUserSetStatusForName(@Param("status") Integer status, @Param("name") String name);
返回值定义查询的执行更新了多少行。索引参数和命名参数都可以在更新查询中使用。
8.2 Native
我们可以修改数据库的状态使用native query。我们只需要添加@Modifying批注:
@Modifying @Query(value = "update Users u set u.status = ? where u.name = ?", nativeQuery = true) int updateUserSetStatusForNameNative(Integer status, String name);
8.3 Inserts
要执行插入操作,我们必须同时应用@Modifying并使用native query,因为INSERT不是JPA接口的一部分:
@Modifying @Query( value = "insert into Users (name, age, email, status) values (:name, :age, :email, :status)", nativeQuery = true) void insertUser(@Param("name") String name, @Param("age") Integer age, @Param("status") Integer status, @Param("email") String email);
9.Dynamic Query
通常,我们会遇到需要根据条件或数据集(其值仅在运行时才知道)构建SQL语句的需求。在这种情况下,我们不能只使用静态查询。
9.1 动态查询的例子
例如,让我们设想一下一种情况,我们需要从运行时定义的一组中选择所有电子邮件为“像一个”的用户-email1,email2,...,emailn:
SELECT u FROM User u WHERE u.email LIKE '%email1%' or u.email LIKE '%email2%' ... or u.email LIKE '%emailn%'
由于集合是动态构造的,因此在编译时我们无法知道要添加多少个LIKE子句。
在这种情况下,我们不能仅使用@Query注解,因为我们不能提供静态SQL语句。
相反,通过实现自定义的复合存储库,我们可以扩展基本的JpaRepository功能,并提供我们自己的逻辑来构建动态查询。让我们来看看如何做到这一点。
9.2 定制存储库和JPA Criteria API
对我们来说幸运的是,Spring提供了一种通过使用自定义片段接口来扩展基础存储库的方法。然后,我们可以将它们链接在一起以创建一个复合存储库。
我们将从创建一个自定义片段接口开始:
public interface UserRepositoryCustom { List<User> findUserByEmails(Set<String> emails); }
然后我们将实现它:
public class UserRepositoryCustomImpl implements UserRepositoryCustom { @PersistenceContext private EntityManager entityManager; @Override public List<User> findUserByEmails(Set<String> emails) { CriteriaBuilder cb = entityManager.getCriteriaBuilder(); CriteriaQuery<User> query = cb.createQuery(User.class); Root<User> user = query.from(User.class); Path<String> emailPath = user.get("email"); List<Predicate> predicates = new ArrayList<>(); for (String email : emails) { predicates.add(cb.like(emailPath, email)); } query.select(user) .where(cb.or(predicates.toArray(new Predicate[predicates.size()]))); return entityManager.createQuery(query) .getResultList(); } }
如上所示,我们利用了JPA Criteria API来构建动态查询。
另外,我们需要确保在类名中包含Impl后缀。 Spring将以UserRepositoryCustomImpl搜索UserRepositoryCustom实现。由于片段本身不是存储库,因此Spring依靠此机制来查找片段实现。
9.3 扩展现有 repository
请注意,第2节到第7节中的所有查询方法都在UserRepository中。
因此,现在我们将通过扩展UserRepository中的新接口来集成我们的片段:
public interface UserRepository extends JpaRepository<User, Integer>, UserRepositoryCustom { // query methods from section 2 - section 7 }
9.4 使用repository
最后,我们可以调用动态查询方法:
Set<String> emails = new HashSet<>(); // filling the set with any number of items userRepository.findUserByEmails(emails);
我们已经成功创建了一个复合repository ,并调用了我们的自定义方法。
10.结论
在本文中,我们介绍了使用@Query批注在Spring Data JPA存储库方法中定义查询的几种方法。
我们还学习了如何实现自定义存储库和创建动态查询。
repository method