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

151
05 июля 2022, 21:10

У меня есть две сущности: 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.

Answer 1

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

Для начала надо разобраться с требованиями. Проведем эксперимент: будем последовательно увеличивать колличество 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 тоже будет являться решением задачи.

READ ALSO
Как получить размер экрана в пикселях в android?

Как получить размер экрана в пикселях в android?

Мне нужно получить ширину и высоту экрана на устройстве, на котором сейчас выполняется приложениеКак мне это сделать?

242
Как дождаться выполнение асинхронного метода?

Как дождаться выполнение асинхронного метода?

Как мне дождаться выполнение асинхронного метода? Есть такой метод ServergetData(DataListener listener); Данные загружаются в отдельном потоке, и по завершению...

239
передача данных из одного окна в другое JavaFX

передача данных из одного окна в другое JavaFX

Здесь на странице регистрации я хочу передать допустим имя в следующее окно вот так:

166
redirect и вызов в чем отличие

redirect и вызов в чем отличие

В контроллере есть метод:

236