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

我想写一些查询方法在存储库层。这个方法必须忽略空参数。例如:

List<Foo> findByBarAndGoo(Bar barParam, @optional Goo gooParam);

这个方法必须按照下列条件返回 Foo:

bar == barParam && goo == gooParam;

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

bar == barParam;

有办法吗? 有人能帮我吗?

81401 次浏览

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

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

否则,我不知道 Spring-Data 是否会支持这种立场。

我不相信使用查询定义的方法名方法可以做到这一点。根据文件(< em > 参考 ) :

尽管从方法名派生查询非常困难 方便的情况下,人们可能面临的情况下,无论是该方法 Name 解析器不支持要使用的关键字或方法 名称会变得不必要的难看 通过变数命名原则查询(请参阅使用 JPA NamedQuery 更多信息)或者更确切地说,用@Query 注释查询方法

我认为您在这里遇到了这种情况,因此下面的答案使用了@Query 注释方法,它几乎与方法名方法(< em > 参考 )一样方便。

    @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);

补充@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);

回答太迟了。不确定 酒吧黏糊糊的之间的关系。检查 例子是否能帮助你。

这招对我很管用。我有一个类似的情况,实体 用户有一组属性,还有一个 寻找一切方法,根据属性搜索用户(这是可选的)。

例如,

  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
}
}

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

步骤1 : 在 JPA 存储库中实现 JpaSpecificationExecutor

例如:

public interface TicketRepo extends JpaRepository<Ticket, Long>, JpaSpecificationExecutor<Ticket> {

步骤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]));
};
}

步骤3: 将规范实例传递给 jpaRepo.findAll (规范) ,它将返回实体对象的列表(在运行的示例中,在这里检查票务)

ticketRepo.findAll(specification); // Pass output of function in step 2 to findAll

现在回答这个问题已经太迟了,但是对于那些寻找解决方案的人来说,下面有一个更简单的方法,我遇到过同样的问题,最终我可以找到一个看起来比其他方法更简单、更有效的解决方案:

我的控制类别:

@RestController
@RequestMapping("/order")
public class OrderController {


private final IOrderService service;


public OrderController(IOrderService service) {
this.service = service;
}


@RequestMapping(value = "/{username}/", method = RequestMethod.GET)
public ResponseEntity<ListResponse<UserOrdersResponse>> getUserOrders(
@RequestHeader Map<String, String> requestHeaders,
@RequestParam(required=false) Long id,
@RequestParam(required=false) Long flags,
@RequestParam(required=true) Long offset,
@RequestParam(required=true) Long length) {
// Return successful response
return new ResponseEntity<>(service.getUserOrders(requestDTO), HttpStatus.OK);
}
}

正如你所看到的,我有 Username作为 @PathVariablelengthoffset,这是我所需要的参数,但我接受 idflags过滤搜索结果,所以他们是我的可选参数,不是必要的调用 REST 服务。

我的仓库接口:

@Query("select new com.ada.bourse.wealth.services.models.response.UserOrdersResponse(FIELDS ARE DELETED TO BECOME MORE READABLE)" +
" from User u join Orders o on u.id = o.user.id where u.userName = :username" +
" and (:orderId is null or o.id = :orderId) and (:flag is null or o.flags = :flag)")
Page<UserOrdersResponse> findUsersOrders(String username, Long orderId, Long flag, Pageable page);

就是这样,你们可以看到我用 (:orderId is null or o.id = :orderId)(:flag is null or o.flags = :flag)检查了我的可选参数我认为需要强调的是 我检查了我的论点is null条件 不是我的专栏数据所以如果客户端为我发送 Idflags参数我会用它们过滤结果否则我只用 username查询也就是我的 @PathVariable

已经有很多很棒的答案了,但是我特别用@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;
}

电影:

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;
}

顾客:

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;
}

然后,我创建一个类,其中包含我希望通过以下方式筛选的参数的字段:

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;
}

接下来,我创建一个类,以根据筛选器参数生成 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]));
};
}
}

接下来我定义 Repository 接口,它不仅有 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> {
}

最后,在一些服务类中,我得到了这样的结果:

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);
}
}

PageRequestTicketFilterParam可能是从静止端点上的某些参数和值获得的。