JPA 的查询方式
约定方法名一定要根据命名规范来书写,Spring Data 会根据前缀、中间连接词(Or、And、Like、NotNull 等类似 SQL 中的关键字)、内部拼接 SQL 代理生成方法的实现。约定方法名的方法见表:
关键词 | SQL符号 | 样例 | 对应JPQL 语句片段 |
And |
| findByLastnameAndFirstname | … where x.lastname = ?1 and x.firstname = ?2 |
Or |
| findByLastnameOrFirstname | … where x.lastname = ?1 or x.firstname = ?2 |
Is,Equals |
| findByFirstname,findByFirstnameIs,findByFirstnameEquals | … where x.firstname = ?1 |
Between |
| findByStartDateBetween | … where x.startDate between ?1 and ?2 |
LessThan |
| findByAgeLessThan | … where x.age < ?1 |
LessThanEqual |
| findByAgeLessThanEqual | … where x.age <= ?1 |
GreaterThan |
| findByAgeGreaterThan | … where x.age > ?1 |
GreaterThanEqual |
| findByAgeGreaterThanEqual | … where x.age >= ?1 |
After |
| findByStartDateAfter | … where x.startDate > ?1 |
Before |
| findByStartDateBefore | … where x.startDate < ?1 |
IsNull |
| findByAgeIsNull | … where x.age is null |
IsNotNull,NotNull |
| findByAge(Is)NotNull | … where x.age not null |
Like |
| findByFirstnameLike | … where x.firstname like ?1 |
NotLike |
| findByFirstnameNotLike | … where x.firstname not like ?1 |
StartingWith |
| findByFirstnameStartingWith | … where x.firstname like ?1(parameter bound with appended %) |
EndingWith |
| findByFirstnameEndingWith | … where x.firstname like ?1(parameter bound with prepended %) |
Containing |
| findByFirstnameContaining | … where x.firstname like ?1(parameter bound wrapped in %) |
OrderBy |
| findByAgeOrderByLastnameDesc | … where x.age = ?1 order by x.lastname desc |
Not |
| findByLastnameNot | … where x.lastname <> ?1 |
In |
| findByAgeIn(Collection | … where x.age in ?1 |
NotIn |
| findByAgeNotIn(Collection | … where x.age not in ?1 |
TRUE |
| findByActiveTrue() | … where x.active = true |
FALSE |
| findByActiveFalse() | … where x.active = false |
IgnoreCase |
| findByFirstnameIgnoreCase | … where UPPER(x.firstame) = UPPER(?1) |
接口方法的命名规则也很简单,明白And、Or、Is、Equal、Greater、StartingWith等英文单词的含义,就可以写接口方法了。
具体用法如下:
package com.example.repository;
import com.example.jpa.User;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.Sort;
import org.springframework.data.repository.Repository;
import java.util.List;
public interface UserRepository extends Repository<User, Long> {
User findFirstByOrderByNameAsc();
List<User> findByEmailOrName(String email, String name);
Page<User> queryFirst100ByName(String name, Pageable pageable);
Slice<User> findTop100ByName(String name, Pageable pageable);
List<User> findFirst100ByName(String name, Sort sort);
}
用 JPQL 进行查询
JPQL 语言(Java Persistence Query Language)是一种和 SQL 非常类似的中间性和对象化的查询语言,它最终会被编译成针对不同底层数据库的 SQL 语言,从而屏蔽不同数据库的差异。
JPQL 语言通过 Query 接口封装执行,Query 接口封装了执行数据库查询的相关方法。调用 EntityManager 的 Query、NamedQuery 及 NativeQuery 方法可以获得查询对象,进而可调用 Query 接口的相关方法来执行查询操作。
JPQL 是面向对象进行查询的语言,可以通过自定义的 JPQL 完成 UPDATE 和 DELETE 操作。JPQL 不支持使用 INSERT。对于 UPDATE 或 DELETE 操作,必须使用注解 @Modifying
进行修饰。
JPQL 的用法:
package com.example.repository;
import com.example.jpa.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import java.util.List;
public interface UserRepository2 extends JpaRepository<User, Long> {
// 根据名称查询
@Query("select u from User u where u.name = ?1")
User fingByName(String name);
// 根据名称模糊查询
@Query("select u from User u where u.name like %?1")
List<User> findByName(String name);
}
用原生 SQL 进行查询
package com.example.repository;
import com.example.jpa.User;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;
import java.util.Optional;
public interface UserRepository2 extends JpaRepository<User, Long> {
// 根据 ID 查询
@Query(value = "select * from User u where u.id = :id", nativeQuery = true)
Optional<User> findById(@Param("id") Long id);
// 查询所有用户
@Query(value = "select * from User", nativeQuery = true)
List<User> findAllNative();
// 根据 Email 查询
@Query(value = "select * from User where email = ?1", nativeQuery = true)
User findByEmail(String email);
// 根据 Name 查询,并返回分页对象 Page
@Query(value = "select * from User where name = ?1",
countQuery = "select count(*) from User where name = ?1",
nativeQuery = true)
Page<User> findByName(String name, Pageable pageable);
// 根据 Name 来修改 Email 的值
@Modifying
@Transactional
@Query("update User set email = :email where name = :name")
Integer updateUserEmailByName(@Param("name") String name, @Param("email") String email);
}
使用事务
UPDATE 或 DELETE 操作需要使用事务。此时需要先定义 Service 层,然后在 Service 层的方法上添加事务操作。对于自定义的方法,如果需要改变 Spring Data 提供的事务默认方法,则可以在方法上使用注解 @Transactional
:
// 根据 Name 来修改 Email 的值
@Modifying
@Transactional
@Query("update User set email = :email where name = :name")
Integer updateUserEmailByName(@Param("name") String name, @Param("email") String email);
测试
package com.example.bean;
import com.example.jpa.User;
import com.example.repository.UserRepository2;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@SpringBootTest
class UserServiceTest {
@Autowired
UserRepository2 userRepository2;
@Test
void TestUpdateEmailByName() {
Integer i = userRepository2.updateUserEmailByName("张三", "sdfgsgsdfg@163.com");
System.out.println(i);
}
@Test
void TestFindById() {
List<User> list = userRepository2.findAllNative();
System.out.println(list);
}
}
用 Specifications 查询
如果想使 Repository 支持 Specification 查询,则需要在 Repository 中继承 JpaSpecificationExecutor 接口
package com.example.repository;
import com.example.jpa.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.jpa.domain.Specification;
import javax.persistence.criteria.*;
@SpringBootTest
class UserRepository3Test {
@Autowired
private UserRepository3 userRepository3;
@Test
public void testJpa() {
PageRequest pageable = PageRequest.of(0, 10);
// 通常使用 Specification 的匿名内部类
Specification<User> specification = new Specification<User>() {
@Override
public Predicate toPredicate(Root<User> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
Path path = root.get("id");
// gt 是大于的意思,这里表示 ID 大于 2
Predicate predicate1 = criteriaBuilder.gt(path, 2);
// equal 是等于的意思,代表查询 name 值为 赵四 的数据记录
Predicate predicate2 = criteriaBuilder.equal(root.get("name"), "赵四");
// 构建组合的 Predicate
Predicate predicate = criteriaBuilder.and(predicate1, predicate2);
return predicate;
}
};
Page<User> page = userRepository3.findAll(specification, pageable);
System.out.println("总记录数:" + page.getTotalElements());
System.out.println("当前第:" + (page.getNumber() + 1) + " 页");
System.out.println("总页数:" + page.getTotalPages());
System.out.println("当前页面的 List:" + page.getContent());
System.out.println("当前页面的记录数:" + page.getNumberOfElements());
}
}
Hibernate: select user0_.id as id1_0_, user0_.email as email2_0_, user0_.name as name3_0_, user0_.pswd as pswd4_0_ from user user0_ where user0_.id>2 and user0_.name=? limit ?
Hibernate: select count(user0_.id) as col_0_0_ from user user0_ where user0_.id>2 and user0_.name=?
总记录数:100
当前第:1 页
总页数:10
当前页面的 List:[User(id=4, name=赵四, pswd=123456, email=345634@qq.com), User(id=5, name=赵四, pswd=123456, email=345634@qq.com), User(id=6, name=赵四, pswd=123456, email=345634@qq.com), User(id=7, name=赵四, pswd=123456, email=345634@qq.com), User(id=8, name=赵四, pswd=123456, email=345634@qq.com), User(id=9, name=赵四, pswd=123456, email=345634@qq.com), User(id=10, name=赵四, pswd=123456, email=345634@qq.com), User(id=11, name=赵四, pswd=123456, email=345634@qq.com), User(id=12, name=赵四, pswd=123456, email=345634@qq.com), User(id=13, name=赵四, pswd=123456, email=345634@qq.com)]
当前页面的记录数:10
用 ExampleMatcher 进行查询
Spring Data 可以通过 Example 对象来构造 JPQL 查询
package com.example.repository;
import com.example.jpa.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.ExampleMatcher;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.jpa.domain.Specification;
import javax.persistence.criteria.*;
import java.util.List;
@SpringBootTest
class UserRepository3Test {
@Autowired
private UserRepository3 userRepository3;
@Test
public void testExample() {
User user = new User();
// 构建查询条件
user.setName("张三");
// 创建一个 ExampleMatcher
ExampleMatcher matcher = ExampleMatcher.matching()
// 不区分大小写匹配 Name
.withIgnorePaths("name")
// 包含 null 值
.withIncludeNullValues();
// 通过 Example 构建查询
Example<User> example = Example.of(user, matcher);
List<User> list = userRepository3.findAll(example);
System.out.println(list);
}
}