Spring Data JPA 的高级用法

一、自定义 SQL 查询(三种方式

  1. 通过方法名自动创建 Query,
  2. 通过 @Query 注解实现自定义 Query,
  3. 通过 @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-钓鱼-程序员

证明关联查询成功,最后的返回结果来自于两个表,按照这个思路可以进行三个或者更多表的关联查询。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值