当前位置: 首页 > 知识库问答 >
问题:

如何在JPA规范中将复杂的SQL转换为CriteriaBuilder

叶文博
2023-03-14

表sql:在此处输入图像描述

链接创建和测试SQL:https://www.db-fiddle.com/f/9ihygssmwErvhb4ekcUnG5/5

我没有办法将这个sql转换为Spring(方法:toPredicate(Root root, CriteriaQuery

在java中有实体:产品,属性

class Product{
  private Long id;
  private String name;

@JsonIgnore
@ManyToMany(cascade = { CascadeType.PERSIST, CascadeType.MERGE })
@JoinTable(name = "product_attribute", joinColumns = @JoinColumn(name = "product_id"), 
inverseJoinColumns = @JoinColumn(name = "attribute_id"))
List<Attribute> attributes = new ArrayList<>();
}

class Attribute{
    private Long id;
    private String name;
    private String value;

    @JsonIgnore
@ManyToMany(mappedBy = "attributes")
List<Product> products = new ArrayList<>();
}

SQL:

 SELECT pa1.product_id 
        FROM  product_attribute pa1,  
        product_attribute pa2, 
        product_attribute pa3,
        product_attribute pa4
        where pa1.attribute_id IN ('A','B')
        and pa2.attribute_id IN ('C','D')
        and pa3.attribute_id IN ('E','F')
        and pa4.attribute_id IN ('G','H')
                AND pa2.product_id = pa3.product_id
                AND pa1.product_id = pa3.product_id
                AND pa1.product_id = pa4.product_id
                AND pa2.product_id = pa4.product_id
                AND pa3.product_id = pa4.product_id

我尝试过预测方法,但失败了

public class ProductSpecification implements Specification<Product>  {
   @Override
    public Predicate toPredicate(Root<Product> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
        Join<Object, Object> a1Join = root.join( "attributes" );
        a1Join.on( criteriaBuilder.or(
                criteriaBuilder.and(
                        criteriaBuilder.equal( a1Join.get( "name" ), "branch" ),
                        criteriaBuilder.equal( a1Join.get( "value" ), "Acer" )
                ),
                criteriaBuilder.and(
                        criteriaBuilder.equal( a1Join.get( "name" ), "branch" ),
                        criteriaBuilder.equal( a1Join.get( "value" ), "Dell" )
                )
        ) );

        Join<Object, Object> a2Join = root.join( "attributes" );
        a2Join.on( criteriaBuilder.or(
                criteriaBuilder.and(
                        criteriaBuilder.equal( a2Join.get( "name" ), "screen" ),
                        criteriaBuilder.equal( a2Join.get( "value" ), "13 Inch" )
                ),
                criteriaBuilder.and(
                        criteriaBuilder.equal( a2Join.get( "name" ), "screen" ),
                        criteriaBuilder.equal( a2Join.get( "value" ), "15.6 Inch" )
                )
       ) );

        Join<Object, Object> a3Join = root.join( "attributes" );
        a3Join.on( criteriaBuilder.and(
                criteriaBuilder.equal( a3Join.get( "name" ), "need" ),
                criteriaBuilder.equal( a3Join.get( "value" ), "gaming" )
        ) );

        Join<Object, Object> a4Join = root.join( "attributes" );
        a4Join.on( criteriaBuilder.and(
                criteriaBuilder.equal( a4Join.get( "name" ), "memory" ),
                criteriaBuilder.equal( a4Join.get( "value" ), "32 G" )
        ) );

         query.distinct(true);

        List<Predicate> predicates = new ArrayList<>();
        predicates.add(root.in(a1Join,a2Join,a3Join,a4Join));
        return builder.and(predicates.toArray(new Predicate[0]));
    }

}

结果日志:

Hibernate: select distinct product0_.id as id1_7_, product0_.discount as discount2_7_, product0_.long_description as long_des3_7_, product0_.name as name4_7_, product0_.price as price5_7_, product0_.promotion as promotio6_7_, product0_.quantity as quantity7_7_, product0_.short_description as short_de8_7_, product0_.status as status9_7_, product0_.summary as summary10_7_, product0_.view as view11_7_ from product product0_ inner 
join  
(product_attribute attributes1_ inner join attribute attribute2_ on attributes1_.attribute_id=attribute2_.id) on product0_.id=attributes1_.product_id and (attribute2_.name=? and attribute2_.value=?) inner join  (product_attribute attributes3_ inner join attribute attribute4_ on attributes3_.attribute_id=attribute4_.id) on product0_.id=attributes3_.product_id and (attribute4_.name=? and attribute4_.value=?) inner join  (product_attribute attributes5_ inner join attribute attribute6_ on attributes5_.attribute_id=attribute6_.id) on product0_.id=attributes5_.product_id and (attribute6_.name=? and attribute6_.value=?) inner join  (product_attribute attributes7_ inner join attribute attribute8_ on attributes7_.attribute_id=attribute8_.id) on product0_.id=attributes7_.product_id and (attribute8_.name=? and attribute8_.value=?) where product0_.id in (attribute2_.id , attribute4_.id , attribute6_.id , attribute8_.id) limit ?

共有1个答案

谭志用
2023-03-14

问题在于,您使用的是@manytomy,这意味着您实际上无法作为一个实体访问桥接表。要编写准确的查询,需要使用桥实体更改映射。参见Hibernate ORM文档中的示例172.双向多对多链接实体。

映射将更改为:

class Product{
  private Long id;
  private String name;

    ...
    @OneToMany(
        mappedBy = "product",
        cascade = CascadeType.ALL,
        orphanRemoval = true
    )
    List<ProductAttribute> attributes = new ArrayList<>();
}

@Entity(name = "ProductAttribute")
@Table(name = "product_attribute")
public static class ProductAttribute implements Serializable {

    @Id
    @ManyToOne
    private Product product;

    @Id
    @ManyToOne
    private Attribute attribute;

}

class Attribute{

    ...

    @OneToMany(
        mappedBy = "attribute",
        cascade = CascadeType.ALL,
        orphanRemoval = true
    )
    List<ProductAttribute> products = new ArrayList<>();
}

现在,您可以编写条件:

CriteriaBuilder criteriaBuilder = ormFactory.getCriteriaBuilder();
CriteriaQuery<Product> criteria = criteriaBuilder.createQuery( Product.class );
Root<Product> root = criteria.from( Product.class );
Join<Object, Object> a1Join = root.join( "attributes" );
Join<Object, Object> a2Join = root.join( "attributes" );
Join<Object, Object> a3Join = root.join( "attributes" );
Join<Object, Object> a4Join = root.join( "attributes" );

Predicate predicate = builder.and(
    a1Join.get( "attribute" ).in('A', 'B'),
    a2Join.get( "attribute" ).in('C', 'D'),
    a3Join.get( "attribute" ).in('E', 'F'),
    a4Join.get( "attribute" ).in('G', 'H')
);

criteria.where(predicate);

也就是说,可以创建一个使用连接返回相同结果的条件。我已将您提供的测试sql查询转换为标准。这不是相同的查询,但应该会给你相同的结果(我已经使用Hibernate ORM测试过了):

        CriteriaBuilder criteriaBuilder = ormFactory.getCriteriaBuilder();
        CriteriaQuery<Product> criteria = criteriaBuilder.createQuery( Product.class );
        Root<Product> root = criteria.from( Product.class );

        criteria.select( root );
        criteria.distinct( true );

        Join<Object, Object> a1Join = root.join( "attributes" );
        a1Join.on( criteriaBuilder.or(
                criteriaBuilder.and(
                        criteriaBuilder.equal( a1Join.get( "name" ), "branch" ),
                        criteriaBuilder.equal( a1Join.get( "value" ), "Acer" )
                ),
                criteriaBuilder.and(
                        criteriaBuilder.equal( a1Join.get( "name" ), "branch" ),
                        criteriaBuilder.equal( a1Join.get( "value" ), "Dell" )
                )
        ) );

        Join<Object, Object> a2Join = root.join( "attributes" );
        a2Join.on( criteriaBuilder.or(
                criteriaBuilder.and(
                        criteriaBuilder.equal( a2Join.get( "name" ), "screen" ),
                        criteriaBuilder.equal( a2Join.get( "value" ), "13 Inch" )
                ),
                criteriaBuilder.and(
                        criteriaBuilder.equal( a2Join.get( "name" ), "screen" ),
                        criteriaBuilder.equal( a2Join.get( "value" ), "15.6 Inch" )
                )
       ) );

        Join<Object, Object> a3Join = root.join( "attributes" );
        a3Join.on( criteriaBuilder.and(
                criteriaBuilder.equal( a3Join.get( "name" ), "need" ),
                criteriaBuilder.equal( a3Join.get( "value" ), "gaming" )
        ) );

        Join<Object, Object> a4Join = root.join( "attributes" );
        a4Join.on( criteriaBuilder.and(
                criteriaBuilder.equal( a4Join.get( "name" ), "memory" ),
                criteriaBuilder.equal( a4Join.get( "value" ), "32 G" )
        ) );

        try (Session session = ormFactory.openSession()) {
            List<Product> resultList = session.createQuery( criteria ).getResultList();
            System.out.println( resultList );
        }

但是您需要更改方法toPredicate的签名:

public void toPredicate(Root<Product> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
    Join<Object, Object> a1Join = root.join( "attributes" );
    ... // The same as in the example above


    Join<Object, Object> a4Join = root.join( "attributes" );
    a4Join.on( ... );
}

你可以像这样使用这个方法:

CriteriaBuilder criteriaBuilder = ...
CriteriaQuery<Product> query = ...
Root<Product> root = criteria.from( Product.class );
specification.toPredicate(root, query, criteriaBuilder);

// Now you can run the query and it will work
 类似资料:
  • 问题内容: 我没主意了。现在,我有一天在Google上进行了谷歌搜索,但仍然找不到我的问题的任何有用答案。 到目前为止,我一直尝试使用原始SQL,但是没有运气。 使用此原始SQL查询时,返回的结果为零,但是在其中运行相同的查询时,返回正确的结果。 我进一步发现,将查询打印到终端时,它不能正确处理该子句。 打印时我的查询如下所示: 如何将此SQL查询转换为SQLAlchemy 我的桌子看起来像这样:

  • 我已将此JSON转换为Hashmap http://www.mocky.io/v2/5d0dc72d3400008c00ca4a62 我有嵌套的Hashmap,我想把它转换成ArrayList 我想从获取所有数据并添加到列表中。我还希望hashmap的密钥也将作为指南导入列表中。 这是银行舱 这是银行名单课程 我试过的 但我不明白为什么会有例外 如果可以的话,请给我推荐其他算法

  • 我有一个相当大的系统,它的规范是由子实体上的几个方法构建的。所以我有一个用户,他有一个宠物木偶,就像这个问题一样。我的关系是双向的,所以Pet在用户上也有多对一的关系,我正在努力将子实体上的规范转换为应用于父实体。 我查找的大多数问题都显示了如何在不同的实体上应用规范,或者在规范执行后获得不同的实体。这不是我想要的。 我试着写一个这样的方法: 但我不知道如何编写它(我尝试过使用Join,但没能“告

  • 我需要使用jolt spec在JSON中将(多个)键转换为值,将值转换为键。做这件事的正确规范应该是什么? 输入: 所需输出:

  • 问题内容: 我正在做这个 它给了我这个 但是我想要这个 请帮忙。 问题答案: 首先将其转换为十进制, SQLFiddle演示

  • 问题内容: 我有一个应用程序,该应用程序使用Jackson将我的复杂对象编组为JSON,从而在DynamoDB中存储一些数据。 例如,我要编组的对象可能如下所示: SomeObject可能看起来像这样: 和SomeOtherObject可能看起来像这样: 可以很好地将对象整理成问题并将其作为JSON字符串存储在数据库中。 当需要从DynamoDB检索数据时,Jackson会自动检索JSON并将其转