SQL CROSS JOIN со спецификацией JPA

У меня есть проект Spring Boot, в котором есть три класса сущностей, например:

@Entity
@Table("item")
public class Item extends SomeParent {

     @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true, mappedBy = "item")
     @JsonManagedReference("product-s")
     Set<Product> products;
}

@Entity
@Table(name = "product")
public class Product extends SomeParent {

     @ManyToOne
     @JoinColumn(name = "item_id", nullable = false)
     @JsonBackReference("product-s")
     private Item item;

     @ManyToOne
     @JoinColumn(name = "fruit_id", nullable = false)
     private Fruit fruit;

     private String type;
}

@Entity
@Table(name = "fruit")
public class Fruit extends SomeParent {

     private String name;

     private Integer qty;
}

Я использую спецификацию JPA для создания запроса на поиск items по fruit.name и product.type. Ниже приведены мои поисковые запросы,

1. fruit = mango, type = A and fruit = apple, type = B 
2. fruit = mango, type = A or fruit = apple, type = B 

По первому запросу нужно найти все items по fruit.name = mango and product.type = Aиfruit.name = apple and product.type = B. Товар должен содержать как манго категории А, так и яблоко категории В.

По второму запросу нужно найти все items по fruit.name = mango and product.type = Aилиfruit.name = apple and product.type = B. Если у какого-либо предмета есть манго с категорией A или яблоко с категорией B, он должен вернуть item

public Specification<Item> search() {
    return (root, query, criteriaBuilder) -> {
       SetJoin<Item, Product> productJoin = root.joinSet("products", JoinType.LEFT);

       if (isAndQuery) { 
          criteriaBuilder.and(
               criteriaBuilder.and(
                    criteriaBuilder.equal(productJoin.get("fruit").get("name"), "mango"), 
                    criteriaBuilder.equal(productJoin.get("type"), "A")));
          criteriaBuilder.and(
               criteriaBuilder.and(
                    criteriaBuilder.equal(productJoin.get("fruit").get("name"), "apple"), 
                    criteriaBuilder.equal(productJoin.get("type"), "B")));
       } else {
          criteriaBuilder.or(
               criteriaBuilder.and(
                    criteriaBuilder.equal(productJoin.get("fruit").get("name"), "mango"),
                    criteriaBuilder.equal(productJoin.get("type"), "A")));
          criteriaBuilder.or(
               criteriaBuilder.and(
                    criteriaBuilder.equal(productJoin.get("fruit").get("name"), "apple"),
                    criteriaBuilder.equal(productJoin.get("type"), "B")));
       }

       return criteriaBuilder;
    };
}

Моя проблема в том, что and query не работает, поэтому не дает мне результатов. Так может ли кто-нибудь помочь мне решить эту проблему? Что я здесь делаю неправильно?

Это сгенерированный JPA запрос AND,

select * from item item
left outer join product product on item.id=product.item_id 
cross join fruit fruit
where product.fruit_id=fruit.id
and ((fruit.name='mango')
and product.type='A'
and (fruit.name='apple')
and product.type='B')
order by item.id asc limit ?

Это сгенерированный JPA запрос OR,

select * from item item 
left outer join product product on item.id=product.item_id 
cross join fruit fruit
where product.fruit_id=fruit.id
and ((fruit.name='mango')
and product.type='A'
or (fruit.name='apple')
and product.type='B')
and 1=1
order by
item.id asc limit ?

Мое предложение состоит в том, чтобы удалить все из критерииBuilder и просто посмотреть, к чему приведет ЛЕВОЕ соединение. Просмотрите данные/столбцы, а затем медленно начните создавать свой критерийBuilder. Это может быть порядок JOIN или что-то еще, что вызывает у вас проблемы.

SMA 05.04.2022 16:29

@SMA Я разместил SQL-запросы, сгенерированные JPA. Вы можете их проверить?

Dave 05.04.2022 16:45
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
1
2
49
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

И не будет работать, потому что вы выполняете проверку фильтра для того же продукта/фрукта, что и другие проверки. Фрукт продукта не может быть одновременно яблоком и манго. Решение состоит в том, чтобы вручную создать два отдельных явных соединения.

public Specification<Item> search() {
   return (root, query, criteriaBuilder) -> {
       Predicate returnPredicate;
       SetJoin<Item, Product> productJoin = root.joinSet("products", JoinType.LEFT);

       if (isAndQuery) { 
          SetJoin<Item, Product> productJoin2 = root.joinSet("products", JoinType.LEFT);
          returnPredicate = criteriaBuilder.and(
               criteriaBuilder.and(
                    criteriaBuilder.equal(productJoin.get("fruit").get("name"), "mango"), 
                    criteriaBuilder.equal(productJoin.get("type"), "A"))),
               criteriaBuilder.and(
                    criteriaBuilder.equal(productJoin2.get("fruit").get("name"), "apple"), 
                    criteriaBuilder.equal(productJoin2.get("type"), "B")));
       } else {
          returnPredicate = criteriaBuilder.or(
               criteriaBuilder.and(
                    criteriaBuilder.equal(productJoin.get("fruit").get("name"), "mango"),
                    criteriaBuilder.equal(productJoin.get("type"), "A"))),
               criteriaBuilder.and(
                    criteriaBuilder.equal(productJoin.get("fruit").get("name"), "apple"),
                    criteriaBuilder.equal(productJoin.get("type"), "B")));
       }

       return returnPredicate;
   };
}

Это означает, что дайте мне товар, в котором есть как продукт с яблоком, так и отдельный продукт с манго.

Спасибо за ответ. Я попробую это. Кроме того, почему эти вещи and 1=1 добавлены в запрос или?

Dave 05.04.2022 23:00

Я не думаю, что этот код правильный - я просто изменил то, что у вас было. Спецификации необходимы для возврата предиката, который является результатом критерияBuilder.And(), or() и т. д. Я подозреваю, что «или» не совсем правильно, потому что вы не используете его в двух предложениях — это на результат вызова критерииBuilder.and(). Я переработаю свой ответ о том, как, по моему мнению, должен выглядеть код.

Chris 06.04.2022 16:11

Другие вопросы по теме