查询方法中的Spring Data可选参数

moh*_*1m2 39 spring hibernate jpql spring-data spring-data-jpa

我想在存储库层中编写一些查询方法.此方法必须忽略null参数.例如:

List<Foo> findByBarAndGoo(Bar barParam, @optional Goo gooParam);
Run Code Online (Sandbox Code Playgroud)

此方法必须通过以下条件返回Foo:

bar == barParam && goo == gooParam;
Run Code Online (Sandbox Code Playgroud)

如果gooParam不为null.如果gooParam为null,则条件更改为:

bar == barParam;
Run Code Online (Sandbox Code Playgroud)

有什么解决方案吗?有人能帮我吗?

cha*_*erb 26

我不相信你能用查询定义的方法名称方法做到这一点.从文档(参考):

尽管获取从方法名称派生的查询非常方便,但是可能面临这样的情况:方法名称解析器不支持要使用的关键字,或者方法名称会不必要地变得难看.因此,您可以通过命名约定使用JPA命名查询(请参阅使用JPA NamedQueries获取更多信息)或者使用@Query注释查询方法

我认为你有这种情况,所以下面的答案使用@Query注释方法,这几乎和方法名称方法(参考)一样方便.

    @Query("select foo from Foo foo where foo.bar = :bar and "
        + "(:goo is null or foo.goo = :goo)")
    public List<Foo> findByBarAndOptionalGoo(
        @Param("bar") Bar bar, 
        @Param("goo") Goo goo);
Run Code Online (Sandbox Code Playgroud)


Vit*_*eis 14

作为@chaserb的答案的补充,我个人会将参数添加为Java8可选类型,以使其在方法的签名中显式显示作为可选过滤器的语义.

@Query("select foo from Foo foo where foo.bar = :bar and "
   + "(:goo is null or foo.goo = :goo)")
public List<Foo> findByBarAndOptionalGoo(
     @Param("bar") Bar bar, 
     @Param("goo") Optional<Goo> goo);
Run Code Online (Sandbox Code Playgroud)

  • 作为参数传递的“可选”不是一个好主意:/sf/ask/2234600651/ (2认同)

Sha*_*tel 12

回答太晚了.不确定BarGoo之间的关系.检查示例是否可以帮助您.

它对我有用.我有类似的情况,实体用户有一组属性,并且有findAll方法根据属性搜索用户(这是可选的).

例,

  Class User{
    String firstName;
    String lastName;
    String id;
  }

  Class UserService{
     // All are optional
     List<User> findBy(String firstName, String lastName, String id){
        User u = new User();
        u.setFirstName(firstName);
        u.setLastName(lastName);
        u.setId(id);

        userRepository.findAll(Example.of(user));
        // userRepository is a JpaRepository class
     }
  }
Run Code Online (Sandbox Code Playgroud)


Pan*_*arg 8

您可以使用JpaSpecificationExecutor//import org.springframework.data.jpa.repository.JpaSpecificationExecutor;

第 1 步JpaSpecificationExecutor:在您的 JPA 存储库中实施

前任:

public interface TicketRepo extends JpaRepository<Ticket, Long>, JpaSpecificationExecutor<Ticket> {
Run Code Online (Sandbox Code Playgroud)

第 2 步现在,要根据可选参数获取票证,您可以使用 CriteriaBuilder 构建规范查询

前任:

public Specification<Ticket> getTicketQuery(Integer domainId, Calendar startDate, Calendar endDate, Integer gameId, Integer drawId) {
    return (root, query, criteriaBuilder) -> {
        List<Predicate> predicates = new ArrayList<>();

        predicates.add(criteriaBuilder.equal(root.get("domainId"), domainId));
        predicates.add(criteriaBuilder.greaterThanOrEqualTo(root.get("createdAt"), startDate));
        predicates.add(criteriaBuilder.lessThanOrEqualTo(root.get("createdAt"), endDate));

        if (gameId != null) {
            predicates.add(criteriaBuilder.equal(root.get("gameId"), gameId));
        }

        return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
    };
}
Run Code Online (Sandbox Code Playgroud)

步骤3:将Specification实例传递给jpaRepo.findAll(specification),它将返回实体对象的列表(运行示例中的Tickets)

ticketRepo.findAll(specification); // Pass output of function in step 2 to findAll
Run Code Online (Sandbox Code Playgroud)


jal*_*chs 7

已经有很多很棒的答案了,但我专门使用 @Pankaj Garg 的答案(使用 Spring 规范 API)实现了这一点。我在我的答案中添加了一些用例

  • 4 个参数,可以为空也可以不为空。
  • 来自存储库的分页响应。
  • 按嵌套对象中的字段进行过滤。
  • 按特定字段排序。

首先,我创建了几个实体,特别是TicketMovieCustomer。这里没什么特别的:

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.persistence.*;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
import java.util.UUID;

@Entity
@Table(name = "ticket", schema = "public")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder(toBuilder = true)
public class Ticket implements Serializable  {

    @Id
    @Basic(optional = false)
    @NotNull
    @Column(name = "id", nullable = false)
    private UUID id;

    @JoinColumn(name = "movie_id", referencedColumnName = "id", nullable = false)
    @ManyToOne(fetch = FetchType.EAGER)
    private Movie movie;

    @JoinColumn(name = "customer_id", referencedColumnName = "id", nullable = false)
    @ManyToOne(fetch = FetchType.EAGER)
    private Customer customer;

    @Column(name = "booking_date")
    @Temporal(TemporalType.TIMESTAMP)
    private Date bookingDate;
}
Run Code Online (Sandbox Code Playgroud)

电影:

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.persistence.*;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.io.Serializable;

@Entity
@Table(name = "movie", schema = "public")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder(toBuilder = true)
public class Movie implements Serializable {

    @Id
    @Basic(optional = false)
    @NotNull
    @Column(name = "id", nullable = false)
    private UUID id;

    @Basic(optional = false)
    @NotNull
    @Size(max = 100)
    @Column(name = "movie_name", nullable = false, length = 100)
    private String movieName;
}
Run Code Online (Sandbox Code Playgroud)

顾客:

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.persistence.*;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.io.Serializable;

@Entity
@Table(name = "customer", schema = "public")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder(toBuilder = true)
public class Customer implements Serializable {

    @Id
    @Basic(optional = false)
    @NotNull
    @Column(name = "id", nullable = false)
    private UUID id;

    @Basic(optional = false)
    @NotNull
    @Size(max = 100)
    @Column(name = "full_name", nullable = false, length = 100)
    private String fullName;
}
Run Code Online (Sandbox Code Playgroud)

然后我创建一个类,其中包含我希望过滤的参数的字段:

import lombok.AllArgsConstructor;
import lombok.Data;

import java.util.Date;
import java.util.UUID;

@Data
@AllArgsConstructor
public class TicketFilterParam {
    private UUID movieId;
    private UUID customerId;
    private Date start;
    private Date end;
}
Run Code Online (Sandbox Code Playgroud)

接下来我创建一个类来Specification根据过滤器参数生成一个。请注意访问嵌套对象的方式以及将排序添加到查询的方式。

import org.springframework.data.jpa.domain.Specification;

import javax.persistence.criteria.Predicate;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

public class TicketSpecifications {
    public static Specification<Ticket> getFilteredTickets(TicketFilterParam params) {
        return (root, criteriaQuery, criteriaBuilder) -> {
            List<Predicate> predicates = new ArrayList<>();

            if (params.getMovieId() != null) {
                predicates.add(criteriaBuilder.equal(root.get("movie").<UUID> get("id"), params.getMarketerId()));
            }

            if (params.getCustomerId() != null) {
                predicates.add(criteriaBuilder.equal(root.get("customer").<UUID> get("id"), params.getDepotId()));
            }

            if (params.getStart() != null && params.getEnd() != null) {
                predicates.add(criteriaBuilder.between(root.get("bookingDate"), params.getStart(), params.getEnd()));
            }

            criteriaQuery.orderBy(criteriaBuilder.desc(root.get("bookingDate")));

            return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
        };
    }
}
Run Code Online (Sandbox Code Playgroud)

接下来我定义存储库接口。JpaRepository这不仅会JpaSpecificationExecutor

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.stereotype.Repository;

@Repository
public interface TicketRepository extends JpaRepository<Ticket, UUID>, JpaSpecificationExecutor<Ticket> {
}
Run Code Online (Sandbox Code Playgroud)

最后,在某些服务类中,我得到如下结果:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;

@Service
public class TicketService {
    @Autowired
    private TicketRepository ticketRepository;

    public Page<Ticket> getTickets(TicketFilterParam params, PageRequest pageRequest) {
        Specification<Ticket> specification = TicketSpecifications.getFilteredTickets(params);
        return ticketRepository.findAll(specification, pageRequest);
    }
}
Run Code Online (Sandbox Code Playgroud)

PageRequest并且TicketFilterParam可能从休息端点上的一些参数和值获得。


use*_*468 5

您可以自己编写代码,只需几行:

List<Foo> findByBarAndOptionalGoo(Bar bar, Goo goo) {
   return (goo == null) ? this.findByBar(bar) : this.findByBarAndGoo(bar, goo);
}
Run Code Online (Sandbox Code Playgroud)

否则,我不知道 Spring-Data 是否支持开箱即用。

  • 对于 2 个过滤器效果很好,但如果您有 4 个可选过滤器则不可能。 (7认同)