У меня есть проект 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 ?
@SMA Я разместил SQL-запросы, сгенерированные JPA. Вы можете их проверить?
И не будет работать, потому что вы выполняете проверку фильтра для того же продукта/фрукта, что и другие проверки. Фрукт продукта не может быть одновременно яблоком и манго. Решение состоит в том, чтобы вручную создать два отдельных явных соединения.
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
добавлены в запрос или?
Я не думаю, что этот код правильный - я просто изменил то, что у вас было. Спецификации необходимы для возврата предиката, который является результатом критерияBuilder.And(), or() и т. д. Я подозреваю, что «или» не совсем правильно, потому что вы не используете его в двух предложениях — это на результат вызова критерииBuilder.and(). Я переработаю свой ответ о том, как, по моему мнению, должен выглядеть код.
Мое предложение состоит в том, чтобы удалить все из критерииBuilder и просто посмотреть, к чему приведет ЛЕВОЕ соединение. Просмотрите данные/столбцы, а затем медленно начните создавать свой критерийBuilder. Это может быть порядок JOIN или что-то еще, что вызывает у вас проблемы.