HQL-запрос к Базе Данных работает не корректно с OneToMany

У меня есть две сущности: Customer и CustomerOrder. Зависимость между ними One-to-Many. В файле с репозиторием я создаю запрос, который должен:

  • Возвращает Customer с CustomerOrders, которые в поле active имеют значение true и пользователь существует
  • Возвращает Customer БЕЗ CustomerOrders, если пользователь существует, но у него нет прикрепленных customerOrders
  • Возвращает Customer БЕЗ CustomerOrders, если пользователь существует, но у него все значения active равны false.

Запрос, который представлен ниже, он работает с первым пунктом из списка выше, но не с двумя оставшимися. Как правильно должен писаться запрос к БД, чтобы все пункты могли выполняться?

Запрос:

@Query("from Customer cs left join fetch cs.customerOrder custOrd " +
       "where custOrd.active = true")
Customer findByCustomerId(Long id);

Ниже будет приведен пример таблиц и ответы в зависимости от запроса.


Сущности:

Customer.java:

@Entity
@Getter
@Setter
@Table
@ToString
public class Customer {

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

    @Column
    private String name;

    @Column
    private String surname;

    @OneToMany(
            mappedBy = "customer",
            cascade = CascadeType.ALL,
            orphanRemoval = true
    )
    private List<CustomerOrder> customerOrders = new ArrayList<>();

    public void addCustomerOrder(CustomerOrder customerOrder) {
        customerOrders.add(customerOrder);
        customerOrder.setCustomer(this);
    }

    public void removeCustomerOrder(CustomerOrder customerOrder) {
        customerOrders.remove(customerOrder);
        customerOrder.setCustomer(null);
    }

    // equals() и hashCode()
}

CustomerOrder.java:

@Entity
@Getter
@Setter
@Table
@ToString
@NoArgsConstructor
public class CustomerOrder {

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

    private boolean active;

    private String orderNumber;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name="customer_id")
    private Customer customer;

    // equals() и hashCode()

}

Пример таблиц:

Customer:

table: customer
+----+------+---------+
| id | name | surname |
+----+------+---------+
|  1 | John | Smith   |
|  2 | Mary | Johnson |
|  3 | Sam  | Brown   |
+----+------+---------+

CustomerOrder:

table: customer_order
+----+--------+--------------+-------------+
| id | active | order_number | customer_id |
+----+--------+--------------+-------------+
|  1 | true   | AE123        |           1 |
|  2 | false  | AE456        |           1 |
|  3 | false  | AE567        |           2 |
|  4 | false  | AE890        |           2 |
+----+--------+--------------+-------------+

Результаты:

findByCustomerId(1) [John]:

возвращается Customer John и прикрепленных к нему CustomerOrder с id: 2

findByCustomerId(2) [Mary]:

возвращается Customer Mary. customer.getCustomerOrder().size() должен быть равен 0.

findByCustomerId(3) [Sam]:

возвращается Customer Sam. customer.getCustomerOrder().size() должен быть равен 0.


Ответы (1 шт):

Автор решения: tequillaz

Вы выбрали не тот инструмент, решение задачи лежит в другой плоскости.

Для начала надо разобраться с требованиями. Проведем эксперимент: будем последовательно увеличивать колличество CustomerOrder и смотреть как это влияет на конечный результат.

  1. Предположим сначала, что у Customer нет ни одного CustomerOrder. В таком случае данный Customer должен быть выбран по п.2 требований.

  2. Добавим к данному Customer один CustomerOrder: Если у этого CustomerOrder active == false, то данный Customer должен быть выбран по п.3 требований. Если у этого CustomerOrder active == true, то данный Customer должен быть выбран по п.1 требований.

  3. Продолжим и дальше добавлять новые CustomerOrder. Предположим, что у Customer уже есть N CustomerOrder и для всех из них верно, что active == false. Данный Customer выбирается по п.3 требований. Теперь добавим новый CustomerOrder. Если у нового CustomerOrder active == false, то данный Customer должен быть выбран по п.3 тербований. Если у нового CustomerOrder active == true, то данный Customer должен быть выбран по п.1 требований.

Становится понятно, что не имеет значения какое колличество CustomerOrder (и с какими атрибутами) есть у Customer, любой Customer подойдет под требования запроса. Поэтому задача сводится не к поиску Customer, а к фильтрации CustomerOrder. Сама фильтрация может быть решена множеством путей:

  1. с использованием @Where из Hibernate:
public class Customer {

    // ...

    @OneToMany(
            mappedBy = "customer",
            cascade = CascadeType.ALL,
            orphanRemoval = true
    )
    private List<CustomerOrder> customerOrders = new ArrayList<>();

    public void addCustomerOrder(CustomerOrder customerOrder) {
        customerOrders.add(customerOrder);
        customerOrder.setCustomer(this);
    }

    public void removeCustomerOrder(CustomerOrder customerOrder) {
        customerOrders.remove(customerOrder);
        customerOrder.setCustomer(null);
    }

    // Дополнительная readonly-коллекция заказов, удовлетворяющих дополнительным условиям
    @OneToMany
    @JoinColumn(name = "customer_id", insertable = false, updatable = false, nullable = false)
    @Where(clause = "active = true")
    private List<CustomerOrder> customerOrdersSample;

    // ...

}
  1. запрос с фильтрацией на уровне бизнес-логики:

public class CustomerService {

    // ...

    private final CustomerRepository customerRepository;

    // ...

    public List<CustomerOrder> getCustomerOrdersByCustomerId(Long customerId) {
        return customerRepository
                .findById(customerId)
                .map(Customer::getCustomerOrders)
                .orElse(Collections.emptyList())
                .stream()
                .filter(CustomerOrder::isActive)
                .collect(Collectors.toList());
    }

}

Запрос в репозиторий с CustomerOrder на выбор всех сущностей с active == true и принадлежащих определенному Customer тоже будет являться решением задачи.

→ Ссылка