一、自定义 SQL 查询(三种方式)
- 通过方法名自动创建 Query,
- 通过 @Query 注解实现自定义 Query,
- 通过 @NamedQuery 注解来定义 Query。
1、 @Query 注解+Hql查询语句
@Query("select u from User u")
Page<User> findALL(Pageable pageable);
2、 @Query 注解+原生SQL查询语句,@Query 注解需要再添加一个参数 nativeQuery = true
@Query("select * from user u where u.nick_name = ?1", nativeQuery = true)
Page<User> findByNickName(String nickName, Pageable pageable);
3、@Query 上面的 1 代表的是方法参数里面的顺序,如果有多个参数也可以按照这个方式添加 1、2、3…
4、除了按照这种方式传参外,还可以使用 @Param 来支持:
@Query("select u from User u where u.nickName = :nickName")
Page<User> findByNickName(@Param("nickName") String nickName, Pageable pageable);
5、如涉及到删除和修改需要加上 @Modifying,也可以根据需要添加 @Transactional 对事务的支持、操作超时设置等。
@Transactional(timeout = 10)
@Modifying
@Query("update User set userName = ?1 where id = ?2")
int modifyById(String userName, Long id);
@Transactional
@Modifying
@Query("delete from User where id = ?1")
void deleteById(Long id);
6、使用已命名的查询, @NamedQueries 注解
//其中,User为Entity
@Entity
@NamedQueries({
@NamedQuery(name = "User.findByPassWord", query = "select u from User u where u.passWord = ?1"),
@NamedQuery(name = "User.findByNickName", query = "select u from User u where u.nickName = ?1"),
})
通过 @NamedQueries 注解可以定义多个命名 Query
name 属性定义了 Query 的名称,注意加上 Entity 名称 . 作为前缀
query 属性定义查询语句。
定义相对应的方法就简单了
List<User> findByPassWord(String passWord);
List<User> findByNickName(String nickName);
二、分页查询(传入参数 Pageable,返回Page对象或者Slice对象)
Spring Data JPA 已经帮我们内置了分页功能,在查询的方法中,需要传入参数 Pageable,当查询中有多个参数的时候 Pageable 建议作为最后一个参数传入。
- Pageable 是 Spring 封装的分页实现类,使用的时候需要传入页数、每页条数和排序规则
- Page 是 Spring 封装的分页对象,封装了总页数、分页数据等。
- 返回对象除使用 Page 外,还可以使用 Slice 作为返回值。
Slice<User> findByNickNameAndEmail(String nickName, String email,Pageable pageable);
Page 和 Slice 的区别如下:
- Page 接口继承自 Slice 接口,而 Slice 继承自 Iterable 接口。
- Page 接口扩展了 Slice 接口,添加了获取总页数和元素总数量的方法,因此,返回 Page 接口时,必须执行两条 SQL,一条复杂查询分页数据,另一条负责统计数据数量。
- 返回 Slice 结果时,查询的 SQL 只会有查询分页数据这一条,不统计数据数量。
- 用途不一样:Slice 不需要知道总页数、总数据量,只需要知道是否有下一页、上一页,是否是首页、尾页等,比如前端滑动加载一页可用;而 Page 知道总页数、总数据量,可以用于展示具体的页数信息,比如后台分页查询。
@Test
public void testPageQuery() {
int page=1,size=2;
Sort sort = new Sort(Sort.Direction.DESC, “id”);
Pageable pageable = PageRequest.of(page, size, sort);
userRepository.findALL(pageable);
userRepository.findByNickName(“aa”, pageable);
}
//Sort,控制分页数据的排序,可以选择升序和降序。
//PageRequest,控制分页的辅助类,可以设置页码、每页的数据条数、排序等。
还有一些更简洁的方式来排序和分页查询,如下。
三、限制查询(方法名很长,还不能解决动态多条件查询的场景)
有时候我们只需要查询前 N 个元素,或者只取前一个实体。
User findFirstByOrderByLastnameAsc();
User findTopByOrderByAgeDesc();
Page<User> queryFirst10ByLastname(String lastname, Pageable pageable);
List<User> findFirst10ByLastname(String lastname, Sort sort);
List<User> findTop10ByLastname(String lastname, Pageable pageable);
四、复杂查询(Repository实现JpaSpecificationExecutor接口,实现 toPredicate 方法)
JpaSpecificationExecutor可动态多条件查询
JpaSpecificationExecutor 的源码解释:
public interface JpaSpecificationExecutor<T> {
//根据 Specification 条件查询单个对象,注意的是,如果条件能查出来多个会报错
T findOne(@Nullable Specification<T> spec);
//根据 Specification 条件查询 List 结果
List<T> findAll(@Nullable Specification<T> spec);
//根据 Specification 条件,分页查询
Page<T> findAll(@Nullable Specification<T> spec, Pageable pageable);
//根据 Specification 条件,带排序的查询结果
List<T> findAll(@Nullable Specification<T> spec, Sort sort);
//根据 Specification 条件,查询数量
long count(@Nullable Specification<T> spec);
}
JpaSpecificationExecutor 的源码很简单,根据 Specification 的查询条件返回 List、Page 或者 count 数据。在使用 JpaSpecificationExecutor 构建复杂查询场景之前,我们需要了解几个概念:
Root root:代表了可以查询和操作的实体对象的根,开一个通过 get(“属性名”) 来获取对应的值。
CriteriaQuery query:代表一个 specific 的顶层查询对象,它包含着查询的各个部分,比如 select 、from、where、group by、order by 等。
CriteriaBuilder cb:来构建 CritiaQuery 的构建器对象,其实就相当于条件或者是条件组合,并以 Predicate 的形式返回。
使用案例:
1、定义一个 UserDetail 对象,作为演示的数据模型
@Entity
public class UserDetail {
@Id
@GeneratedValue
private Long id;
@Column(nullable = false, unique = true)
private Long userId;
private Integer age;
private String realName;
private String status;
private String hobby;
private String introduction;
private String lastLoginIp;
}
2、创建 UserDetail 对应的 Repository
public interface UserDetailRepository extends JpaSpecificationExecutor<UserDetail>,JpaRepository<UserDetail, Long> {
}
3、定义一个查询 Page 的接口
public interface UserDetailService {
public Page<UserDetail> findByCondition(UserDetailParam detailParam, Pageable pageable);
}
4、在 UserDetailServiceImpl 中,我们来演示 JpaSpecificationExecutor 的具体使用
@Service
public class UserDetailServiceImpl implements UserDetailService{
@Resource
private UserDetailRepository userDetailRepository;
@Override
public Page<UserDetail> findByCondition(UserDetailParam detailParam, Pageable pageable){
return userDetailRepository.findAll((root, query, cb) -> {
List<Predicate> predicates = new ArrayList<Predicate>();
//equal 示例
if (!StringUtils.isNullOrEmpty(detailParam.getIntroduction())){
predicates.add(cb.equal(root.get("introduction"),detailParam.getIntroduction()));
}
//like 示例
if (!StringUtils.isNullOrEmpty(detailParam.getRealName())){
predicates.add(cb.like(root.get("realName"),"%"+detailParam.getRealName()+"%"));
}
//between 示例
if (detailParam.getMinAge()!=null && detailParam.getMaxAge()!=null) {
Predicate agePredicate = cb.between(root.get("age"), detailParam.getMinAge(), detailParam.getMaxAge());
predicates.add(agePredicate);
}
//greaterThan 大于等于示例
if (detailParam.getMinAge()!=null){
predicates.add(cb.greaterThan(root.get("age"),detailParam.getMinAge()));
}
return query.where(predicates.toArray(new Predicate[predicates.size()])).getRestriction();
}, pageable);
}
}
上面的示例是根据不同条件来动态查询 UserDetail 分页数据,UserDetailParam 是参数的封装,示例中使用了常用的大于、like、等于等示例,根据这个思路我们可以不断扩展完成更复杂的动态 SQL 查询。
使用时只需要将 UserDetailService 注入调用相关方法即可:
@RunWith(SpringRunner.class)
@SpringBootTest
public class JpaSpecificationTests {
@Resource
private UserDetailService userDetailService;
@Test
public void testFindByCondition() {
int page=0,size=10;
Sort sort = new Sort(Sort.Direction.DESC, "id");
Pageable pageable = PageRequest.of(page, size, sort);
UserDetailParam param=new UserDetailParam();
param.setIntroduction("程序员");
param.setMinAge(10);
param.setMaxAge(30);
Page<UserDetail> page1=userDetailService.findByCondition(param,pageable);
for (UserDetail userDetail:page1){
System.out.println("userDetail: "+userDetail.toString());
}
}
}
五、多表查询(定义一个结果集的接口类+Hql查询语句)
多表查询在 Spring Data JPA 中有两种实现方式,第一种是利用 Hibernate 的级联查询来实现,第二种是创建一个结果集的接口来接收连表查询后的结果,这里主要介绍第二种方式。
我们还是使用上面的 UserDetail 作为数据模型来使用,定义一个结果集的接口类,接口类的内容来自于用户表和用户详情表。
public interface UserInfo {
String getUserName();
String getEmail();
String getAddress();
String getHobby();
}
在运行中 Spring 会给接口(UserInfo)自动生产一个代理类来接收返回的结果,代码中使用 getXX 的形式来获取。
在 UserDetailRepository 中添加查询的方法,返回类型设置为 UserInfo:
@Query("select u.userName as userName, u.email as email, d.introduction as introduction , d.hobby as hobby from User u , UserDetail d " +
"where u.id=d.userId and d.hobby = ?1 ")
List<UserInfo> findUserInfo(String hobby);
特别注意这里的 SQL 是 HQL,需要写类的名和属性,这块很容易出错。
测试验证:
@Test
public void testUserInfo() {
List<UserInfo> userInfos=userDetailRepository.findUserInfo("钓鱼");
for (UserInfo userInfo:userInfos){
System.out.println("userInfo: "+userInfo.getUserName()+"-"+userInfo.getEmail()+"-"+userInfo.getHobby()+"-"+userInfo.getIntroduction());
}
}
运行测试方法后返回:
userInfo: aa-aa@126.com-钓鱼-程序员
证明关联查询成功,最后的返回结果来自于两个表,按照这个思路可以进行三个或者更多表的关联查询。