首页 > 解决方案 > 如何使用 JPA CriteriaBuilder selectCase() 以便它可以有 Predicate 作为结果?

问题描述

我需要实现一个具有 if...then...else 场景的条件查询。我发现javax.persistence.criteria.CriteriaBuilder.selectCase()适合这个目的。它返回一个Expression结果。

要运行条件查询,我需要使用where()哪个接受谓词数组。这是因为我有多个谓词要包含在查询中。

现在,由于selectCase()正在返回Expression,我无法将它与现有的谓词列表集成。

Type mismatch: cannot convert from Expression<Object> to Predicate

如何使用 selectCase() 以便我可以将 Predicate 作为结果?或者有其他更好的方法吗?

例子:

为了说明这个问题,我有以下实现“默认情况下从印度获取所有特定年龄和(来自特定国家和城市)的用户”

如果...那么...其他情况

如果来自特定国家

   if from particular city

别的

 if from "India"

// 查询实现

@Override
    public List<User> findUsersByAgeCountryAndCity(int age, String country, String city) {
        CriteriaBuilder cb = entityManager.getCriteriaBuilder();
        CriteriaQuery<User> cq = cb.createQuery(User.class);

        Root<User> user = cq.from(User.class);
        List<Predicate> predicates = new ArrayList<>();

        predicates.add(age(user, age));

        predicates.add(country(user, country, city));

        cq.where(predicates.toArray(new Predicate[0]));

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

    private Predicate country(Root<User> user, String country, String city) {
        CriteriaBuilder cb = entityManager.getCriteriaBuilder();
        return cb.selectCase() //
                .when(cb.equal(user.get("address").get("country"), country),
                        cb.equal(user.get("address").get("city"), city))
                .otherwise(cb.equal(user.get("address").get("country"), "India")); 
    }

    private Predicate age(Root<User> entity, int age) {
        return entityManager.getCriteriaBuilder().equal(entity.get("age"), age);
    }

// 用户.java

public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;

    private String name;
    private LocalDate creationDate;
    private LocalDate lastLoginDate;
    private boolean active;
    private int age;
    @Column(unique = true, nullable = false)
    private String email;
    private Integer status;
    @Embedded
    private Address address;
    @OneToMany
    List<Possession> possessionList;
...

// 地址.java

@Embeddable
public class Address {

    private String city;
    private String country;
...

标签: javajpaspring-data-jpacriteria

解决方案


如果我正确阅读了您的问题,您需要以下逻辑:

IF is_user_from_country 
    RETURN is_user_from_city
ELSE
    RETURN is_user_from_india

将其放入查询中很棘手,因为 SQL 中的谓词没有可以返回的内在布尔值。在 SQL 术语中,它看起来像:

CASE 
    WHEN user.country = :country THEN
        CASE WHEN user.city = :city THEN 1 ELSE 0 END
    WHEN user.country = 'India' THEN 1
    ELSE 0 
END

在 Criteria API 中(注意我没有测试过,可能有语法错误):

cb.selectCase() //
    .when(cb.equal(user.get("address").get("country"), country),
            cb.selectCase()
            .when(cb.equal(user.get("address").get("city"), city), cb.literal(true))
            .otherwise(cb.literal(false))
    ))
    .when(cb.equal(user.get("address").get("country"), "India"), cb.literal(true))
    .otherwise(cb.literal(false)); 

不过,我并不完全确定 Criteria API 支持嵌套CASE语句。如果没有,您可以尝试使逻辑更简单:

SELECT CASE
    WHEN user.country = :country AND user.city = :city THEN TRUE
    WHEN user.country = :country AND user.city <> :city THEN FALSE
    WHEN user.country = 'India' THEN TRUE
    ELSE FALSE
END

推荐阅读