Spring Data - 如果参数为空,则忽略该参数值

78

我希望创建一个Spring Data存储库接口,它需要两个参数。有没有办法使它具有以下行为?

MyObject findByParameterOneAndParameterTwo( String parameterOne, String parameterTwo);
如果两个参数都有值,我希望它能够正常工作并对这两个值执行“AND”操作。
如果例如第二个参数为null,则仅通过ParameterOne进行搜索。
有什么建议吗?

3
Spring Data提供的从存储库方法名称中获取查询的机制是为了在查询事先已知的情况下使用。但是,希望这种机制用于只在运行时准确知道查询的情况是不现实的。对于动态情况,有几个其他选项,例如@Query和QueryDSL。SQL和JPA支持COALESCE函数,该函数可用于解决某些参数可能具有NULL值的情况。应该可以使用@Query("SELECT e FROM MyObject e WHERE COALESCE(e.parameterOne, ?1) = ?1 AND COALESCE(e.parameterOne, ?2) = ?2")来解决问题。 - manish
2
@manish 我认为 COALESCE(?1, e.parameterOne) = e.parameterOne 是你的意图。 - Blank
@Forward,我只是给发帖人指明了方向,因为我不确定发帖人想要匹配的具体方式。例如,尚未指定数据库是否可以包含这些列的“null”值,如果可以,匹配应该如何进行等等。但是,是的,仅基于已发布的内容,您的评论是正确的。 - manish
@manish COALESCE函数将返回第一个非空值,那么如果我们有不同的情况呢? - Rishabh Agarwal
1
可以使用JpaRepository和Specification,如此处所述https://dev59.com/410a5IYBdhLWcg3wipFA#61948111,它允许您使用构建器处理此问题。 - ganaraj
显示剩余3条评论
14个回答

60

我不确定是否可以通过仓库方法的命名实现,但是你可以像这样使用@Query


(:parameterOne is null or parameter1 = :parameterOne) and (:parameterTwo is null or parameter2 = :parameterTwo)

如果param1和param2之间是或关系,那该怎么办?null is null将返回整个表。 - user666
2
它只适用于字符串值。它不适用于任何其他类型。此外,在IN子句的情况下,它也不起作用,因为空或空检查无法与列表一起使用。 - abhishek ringsia

36

这里缺少的一个解决方案是 Spring Data JPA 的 Query By Example 特性,使用 ExampleMatcher#ignoreNullValues 即可解决此问题。自定义查询和查询构建器不是必要的。

这是Spring Data查询:

ExampleMatcher matcher = ExampleMatcher.matching().withIgnoreNullValues();
Example<MyObject> exampleQuery = Example.of(new MyObject("foo", null), matcher);
List<MyObject> results = repository.findAll(exampleQuery);

生成一个类似以下查询的查询:

select * 
from myObject 
where parameter1 = "foo"

下面是:

ExampleMatcher matcher = ExampleMatcher.matching().withIgnoreNullValues();
Example<MyObject> exampleQuery = Example.of(new MyObject("foo", "bar"), matcher);
List<MyObject> results = repository.findAll(exampleQuery);

产量:

select * 
from myObject 
where parameter1 = "foo"
and parameter2 = "bar"

非常酷!

注意: 你需要对你的Repository接口进行一项操作,即添加QueryByExample接口。你可以通过直接扩展QueryByExample接口或通过JpaRepository隐式实现来完成此操作:

public interface MyObjectRepository extends JpaRepository<MyObject, Long> {}

2
太棒了:正是我正在寻找的。如果有人正在扩展JPARepository,则无需扩展QueryByExample接口,因为JPA存储库已经扩展了它。 - Neeraj Jain
如果第二个参数为空,查询是否可以是“select * from myObject where parameter1 = 'foo' and parameter2 is null”? - Muhammad Waqas Dilawar
2
@Wiqi 上面的建议没有查询由 #withIgnoreNullValues 指定的 null 值。可以使用 withIncludeNullValues 和/或 NullHandler 来修改并对该行为进行更精细的控制。 - Dovmo
如果我想传递 MyObject 的列表怎么办? - karthick S
当您在示例中调用findAll时,您是否调用了来自QueryByParameters的函数。如果我们从另一个具有相同方法但不同参数类型的存储库进行扩展会发生什么?我可能是错的,但在这种情况下,我们被迫继续使用原始存储库中的方法,它可能无法正常工作。 - Columb1a

23

目前在 Spring-data-jpa 中无法实现这一点。

关于此事有一个 JIRA 工单,仍在由 Spring 团队进行调查

enter image description here

但如果您想要一个解决方案,您可以查看一个简单的条件查询示例。


更新:

提到的工单已关闭,Spring团队不再有兴趣实现此功能,因为涉及到的复杂性以及查询示例是可行的选项。请参见此评论

enter image description here


15

以下是实现该目标的方法:

@Query("SELECT c FROM Customer c WHERE (:name is null or c.name = :name) and (:email is null"
      + " or c.email = :email)")
    List<Customer> findCustomerByNameAndEmail(@Param("name") String name, @Param("email") String email);

1
这是答案 - Adam Mudianto
很遗憾,这不适用于nativeQuery - Serga
它只能使用字符串参数工作。 - Tooraj Jam

9

试试这个Kolobok

@FindWithOptionalParams
Iterable<MyObject> findByParameterOneAndParameterTwo( String parameterOne, String parameterTwo);

3
  1. JPA 查询
@Query("SELECT c FROM Customer c WHERE (:name is null or c.name = :name)")

JPA查询(nativeQuery = true)
@Query(nativeQuery = true, "SELECT id, name FROM Customer WHERE (false = :nameOn OR name = :name)")
List<Entity> findAll(@Param(value = "nameOn") Boolean nameOn, @Param(value = "name ") String name);
  • 如果名称为空,nativeQuery需要更改名称为“EMPTY String”。

1
我使用了三个类来使用 Criteria Builder。
使用 JPA 的 Repository 接口。
 @Repository
 public interface NotificationRepository extends JpaRepository<Notification, 
 Long>, NotificationRepositoryCustom {
}

自定义界面
public interface NotificationRepositoryCustom {

    List<Notification> findNotificationByCustomerIdAndRecipientAndNotificationAckStatusAndNotificationRequestChannel
            (Long customerId, String userId, List<String> status, List<String> channels);
}

NotificationRepositoryCustom的实现

public class NotificationRepositoryCustomImpl implements NotificationRepositoryCustom {

    @PersistenceContext
    private EntityManager entityManager;

    @Override
    public List<Notification> findNotificationByCustomerIdAndRecipientAndNotificationAckStatusAndNotificationRequestChannel(Long customerId, String userId, List<String> status, List<String> channels) {

        CriteriaBuilder cb = entityManager.getCriteriaBuilder();
        CriteriaQuery<Notification> query = cb.createQuery(Notification.class);
        Root<Notification> notification = query.from(Notification.class);


        List<Predicate> predicates = new ArrayList<Predicate>();
        if (userId != null)
            predicates.add(cb.equal(notification.get("recipient"), userId));
        if (customerId != null)
            predicates.add(cb.equal(notification.get("customerId"), customerId));
        if (CollectionUtils.isNotEmpty(status))
            predicates.add(notification.get("notificationAckStatus").get("ackStatusCode").in(status));
        if (CollectionUtils.isNotEmpty(channels))
            predicates.add(notification.get("notificationRequestChannel").get("notificationChannel").get("channelCode").in(channels));


        if (!predicates.isEmpty()) {
            query
                    .select(notification)
                    .where(cb.and(
                            predicates.toArray(new Predicate[predicates.size()])));

        }
        return entityManager.createQuery(query).getResultList();
    }
}

0
如果你想检查参数是否为空或者为null值,你应该像这样实现:
@Query("SELECT t FROM Test t WHERE (:parameterOne IS NULL) OR (:parameterOne = '')");

0

使用Spring JPA Data 规范 是解决这个问题的一个非常好的方法。

这个解决方案适用于所有数据类型,并允许所有DB函数(大于/小于/类似/等于/或/不等等)。

我个人认为,将业务逻辑/条件构建器封装到这些方法中可以使服务代码更加可读,特别是如果您给您的规范方法命名良好。您甚至可以拥有私有方法,供公共方法使用,以使您的规范代码更易读!

对于OP的示例,请创建一个包含这些方法的类。

public static Specification<Entity> equalsParameterOne(String parameterOne){
    //If the specification is null it will be excluded from the SQL and ignored
    if(parameterOne == null || parameterOne.length = 0) return null;
         
    return (root, query, cb) -> cb.equal(root.get("fieldOne"), parameterOne);
}

public static Specification<Entity> equalsParameterTwo(String parameterTwo){
    //If the specification is null it will be excluded from the SQL and ignored
    if(parameterTwo== null || parameterTwo.length = 0) return null;
         
    return (root, query, cb) -> cb.equal(root.get("fieldTwo"), parameterTwo);
}

然后在您使用jpaRepo的服务代码中,您可以像这样使用findAll()。

//Be careful with your parenthesis
Specification<Entity> customDynamicSpecs = 
Specification
.where(equalsParameterOne(criteria.getParamOne()))  
.and(equalsParameterTwo(criteria.getParamTwo()));
//.or().not()
//... as many as you want.

//findAll() can take Pageable or Sort as 2nd argument for extra functionality.
repo.findAll(customDynamicSpecs);

为了使这个工作正常,你必须声明你的 repo 扩展 JpaSpecificationExecutor,并且进行比任何人都应该感到舒适更多的静态导入。但对我来说,可读性的代码胜出。

0

试试这个,

      @Query(value = "SELECT pr FROM ABCTable pr " +
        "WHERE((pr.parameterOne = :parameterOne) or (pr.parameterOne = null and :parameterOne = null)) and 
        ((pr.parameterTwo = :parameterTwo) or (pr.parameterTwo = null and :parameterTwo = null)) ")
      List<PaymentRequest> getSomething (@Param("parameterOne") String parameterOne,
                                             @Param("parameterTwo") String parameterTwo);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接