【问题标题】:Specification and predicates collection using AND operator CriteriaBuilder使用 AND 运算符 CriteriaBuilder 的规范和谓词集合
【发布时间】:2014-07-13 23:46:36
【问题描述】:

为了从我的数据库中获取申请人的元素列表,我在尝试检索它们时遇到了问题。

我知道在这个例子中我正在通过一个错误的方法获取:获取实体的字符串 id,但这需要重构;)

这是我的规范和带有连接的谓词循环:

public static Specification<Applicant> applicantsMatchMobility(final String... mobilities) {
    return new Specification<Applicant>() {

        @Override
        public Predicate toPredicate(Root<Applicant> root, CriteriaQuery<?> query, CriteriaBuilder builder) {

            Collection<Predicate> predicates = new ArrayList<>();

            Join<Applicant, Mobility> applicantMobilityJoin = root.join("mobility");

            for(String mobility : mobilities) {
                predicates.add(builder.equal(applicantMobilityJoin.<Mobility>get("id"), Integer.parseInt(mobility)));
            }

            return builder.and(predicates.toArray(new Predicate[predicates.size()]));

        }

    };
}

当我只传递 Mobility 实体的一个 id 时它工作正常,但当它是一个 String ids 数组 > 1 时,它总是返回 0..

这是我的junit测试:

 @Test
public void ShouldHaveOneApplicantWhenSearchMobilityMultipleTotallyEquals(){

    String mobilitiesId[] = {"0", "1"};
    List<Applicant> applicantList = applicantRepository.findAll(applicantsMatchMobility(mobilitiesId));
    Assert.assertNotNull(applicantList);
    Assert.assertEquals("Result", 1, applicantList.size());

    Applicant applicant = applicantList.get(0);
    Assert.assertNotNull(applicant);
    Assert.assertEquals("Result", "XBNC", applicant.getFirstName());

}

这是由此生成的请求:

select
    * 
from
    applicant applicant0_ 
inner join
    applicant_mobility mobility1_ 
        on applicant0_.id=mobility1_.id_applicant 
inner join
    mobility mobility2_ 
        on mobility1_.id_mobility=mobility2_.id 
where
    mobility2_.id=0 
    and mobility2_.id=1

这是我的测试数据集:

    <?xml version='1.0' encoding='UTF-8'?>
<dataset>
    <MOBILITY ID="0" NAME="sud"/>
    <MOBILITY ID="1" NAME="nice"/>
    <MOBILITY ID="2" NAME="cannes"/>
    <MOBILITY ID="3" NAME="nowhere"/>

    <TAGS ID="0" NAME="tags1"/>
    <TAGS ID="1" NAME="tags2"/>
    <TAGS ID="2" NAME="tags3"/>

    <STATUS ID="0" NAME="status1"/>
    <STATUS ID="1" NAME="status2"/>

    <AVAILABILITY ID="0" NAME="availability1"/>

    <APPLICANT ID="0" LAST_NAME="XBNC" FIRST_NAME="XBNC" YEAR="2001" WAGE_CLAIM="50" ID_STATUS="0" ID_AVAILABILITY="0" />
    <APPLICANT ID="1" LAST_NAME="XBN" FIRST_NAME="XBN" YEAR="0" WAGE_CLAIM="70" ID_STATUS="1" ID_AVAILABILITY="0"/>
    <APPLICANT ID="2" LAST_NAME="MI" FIRST_NAME="MI" YEAR="1995" WAGE_CLAIM="0" ID_STATUS="1" ID_AVAILABILITY="0"/>
    <APPLICANT ID="3" LAST_NAME="bisTronomique" FIRST_NAME="bisTronomique" YEAR="-400" WAGE_CLAIM="0" ID_STATUS="0" ID_AVAILABILITY="0"/>

    <APPLICANT_MOBILITY ID_APPLICANT="0" ID_MOBILITY="0"/>
    <APPLICANT_MOBILITY ID_APPLICANT="0" ID_MOBILITY="1"/>

    <APPLICANT_MOBILITY ID_APPLICANT="1" ID_MOBILITY="0"/>
    <APPLICANT_MOBILITY ID_APPLICANT="1" ID_MOBILITY="2"/>

    <APPLICANT_MOBILITY ID_APPLICANT="2" ID_MOBILITY="1"/>

    <APPLICANT_TAGS ID_APPLICANT="0" ID_TAGS="0"/>
    <APPLICANT_TAGS ID_APPLICANT="0" ID_TAGS="1"/>
    <APPLICANT_TAGS ID_APPLICANT="0" ID_TAGS="2"/>

</dataset>

你能在这个测试中看到我只需要取回“XBNC”申请人,因为他包含 2 个对应的 MobilityeID ..

【问题讨论】:

    标签: java spring junit jpa-2.0 spring-data


    【解决方案1】:

    这是一个基本的 SQL 逻辑问题,我猜您正在尝试检索与移动 ID 0 和 1 对应的 2 行:当前代码中的 where 子句 (critria) 构建 [where id = '0' and id = '1'],这将始终不返回任何行,因为特定表字段不能同时等于两个值。
    您需要使用or() 方法代替and(),该方法将构建一个where 子句,如[where id = '0' or id = '1']。如果恰好一行一行仅将mobilityID 设置为1,而一行一行仅将mobilityID 设置为0,则您应该从运行查询中返回两行。

    更新:
    在您的问题编辑后,我想我可以看到您的问题所在。这仍然是一个 SQL 问题,但这意味着您的查询需要稍微复杂一些。您需要获取如下查询:

    select * from applicant applicant0_ inner join applicant_mobility mobility1_ on applicant0_.id=mobility1_.id_applicant inner join mobility mobility2_ on mobility1_.id_mobility=mobility2_.id where exists ( select * from applicant_mobility where id_applicant = applicant0_.id and id_mobility = 1; ) and exists ( select * from applicant_mobility where id_applicant = applicant0_.id and id_mobility = 0; )
    注意子查询中applicantapplicant_mobility 之间的连接。这实际上是在说:对于主查询找到的每个申请者实例,验证它是否在一个集合中,其中至少有一个对应的申请者移动性实例的移动性设置为 0,另一个移动性设置为 1。
    因此,您的函数中的循环需要根据提供的移动 ID 创建子查询,然后使用构建器 and()exists() 将它们放在一起。

    【讨论】:

    • or 方法不符合我的预期。它现在返回数据库中的所有申请人(以及没有映射任何移动对象的申请人......)
    • 是的,谢谢,但我的循环正在添加谓词,这应该为第一个谓词添加 WHERE 并为另一个谓词添加 AND,我错了吗?从您的角度来看,我必须在循环中执行什么样的子查询?
    • 您提交的代码的问题是我在第一个答案中概述的。 and() 只是创建一个布尔谓词: [this AND that] 在您的情况下永远不会为真(mobilityID 在任何特定行中不能同时等于 0 和 1。)。我不太了解 JPA,无法准确告诉您要使用哪些类,但快速查看 API 表明您需要创建一个 SubQuery 并使用 correlate() 方法将子查询连接到封闭查询。
    【解决方案2】:

    添加到@NotSoOldNick 指定的内容。您似乎需要一个 IN 子句来仅检索具有匹配移动性的匹配申请人。您可以将其更改为以下或类似的。

    public static Specification<Applicant> applicantsMatchMobility(final String... mobilities) {
        return new Specification<Applicant>() {
    
            @Override
            public Predicate toPredicate(Root<Applicant> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
    
                Collection<Predicate> predicates = new ArrayList<>();
    
                Join<Applicant, Mobility> applicantMobilityJoin = root.join("mobility");
    
                Expression<String> idExp = applicantMobilityJoin.<Mobility>get("id");
    
                return builder.and(idExp.in(mobilities));
    
            }
    
        };
    }
    

    【讨论】:

    • 我只是用这个替换你的答案:Expression idExp = applicationMobilityJoin.get("id");但这确实也返回了 4 个元素,而预期只有 1 个......当我从数据集中删除申请人时,行为相同!奇怪!
    • 你有多少数据?所有这些数据的 mobitity.id 都为 0 或 1?
    • 我的数据只是在我的帖子中,我只有一个具有 mibility.id 0 和 1 的申请人
    • 我认为问题在于它只获取第二个内部连接元素:mobility2_ 而不是mobility1_
    猜你喜欢
    • 2022-11-30
    • 2021-01-19
    • 1970-01-01
    • 1970-01-01
    • 2018-05-11
    • 2019-08-27
    • 1970-01-01
    • 2019-03-17
    • 1970-01-01
    相关资源
    最近更新 更多