-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Description
Hello,
Recently found out that JPA produce a lot of redundant actions* when we persist entity with bi-directional many-to-one relation.
- I'm talking about redundant SELECT queries for each relation (even with
FetchType.LAZY
).
Related links that could help BUT:
- https://vladmihalcea.com/best-spring-data-jparepository/
- https://vladmihalcea.com/manytoone-jpa-hibernate/
- https://vladmihalcea.com/spring-data-jpa-findbyid/
So in case when you pass references to new entity it works fine, but you will not able to query before persist/insert of new record (to do some checks or send event to message queue for example).
// Just insert and no more. You will not be able to do any checks with customer or paymentMethod objects
Customer customerRef = this.customerRepository.getReferenceById(customerId);
PaymentMethod paymentMethodRef = this.paymentMethodRepository.getReferenceById(paymentMethodId);
return paymentRepository.persist(
new Payment().setAmount(amount).setPaymentMethod(paymentMethodRef).setCustomer(customerRef)
);
So when you pass references it just inserts but 99.9% of API's need to make a lot of checks before any actions with database.
Optional<PaymentMethod> paymentMethodOptional = this.paymentMethodRepository.findById(paymentMethodId);
if (paymentMethodOptional.isEmpty()){
throw new RuntimeException("PAYMENT_METHOD_NOT_FOUND");
}
PaymentMethod paymentMethod = paymentMethodOptional.get();
if (!paymentMethodHashMap.containsKey(paymentMethod.getSlug())){
throw new RuntimeException("PAYMENT_PROVIDER_NOT_FOUND");
}
AbstractPaymentMethod paymentMethodProvider = paymentMethodHashMap.get(paymentMethod.getSlug());
Optional<Customer> customerOptional = customerRepository.findById(1L);
Customer customer = customerOptional.get();
Customer customerRef = this.customersService.getRef(customerId);
PaymentMethod paymentMethodRef = this.paymentMethodRepository.getReferenceById(paymentMethodId);
paymentRepository.persist(
new Payment().setAmount(amount).setPaymentMethod(paymentMethodRef).setCustomer(customerRef)
);
it produce next logs:
Hibernate: select p1_0.id,p1_0.comission,p1_0.currency_code,p1_0.is_active,p1_0.is_public,p1_0.max_amount,p1_0.min_amount,p1_0.name,p1_0.options,p1_0.order,p1_0.slug from public.payment_methods p1_0 where p1_0.id=?
Hibernate: select c1_0.id,c1_0.balance,c1_0.created_at,c1_0.email,c1_0.last_login_at,c1_0.password,c1_0.profile_name,c1_0.updated_at from public.customers c1_0 where c1_0.id=?
2023-10-15T03:24:51.474+03:00 INFO 24544 --- [nio-8080-exec-2] c.example.demo.payments.PaymentsService : Before insert
Hibernate: select p1_0.payment_method_id,p1_0.id,p1_0.amount,p1_0.created_at,p1_0.customer_id,p1_0.updated_at from public.payments p1_0 where p1_0.payment_method_id=?
Hibernate: select p1_0.customer_id,p1_0.id,p1_0.amount,p1_0.created_at,p1_0.payment_method_id,p1_0.updated_at from public.payments p1_0 where p1_0.customer_id=?
Hibernate: insert into public.payments (amount,created_at,customer_id,payment_method_id,updated_at) values (?,localtimestamp,?,?,localtimestamp)
Hibernate: select p1_0.created_at,p1_0.updated_at from public.payments p1_0 where p1_0.id=?
2023-10-15T03:24:51.549+03:00 INFO 24544 --- [nio-8080-exec-2] c.example.demo.payments.PaymentsService : After insert
1 and 2 is fine, because we need that records for any possible checks, but then things goes too weird. JPA query and parse for all records in payments table twice.
One way to do that is to first query needed records and then detach them to do some checks but this leads to a lot of redundant weird
code and actually not a good solution that should be in code.
In case there is table with 1_000_000 records JPA will query all of them, parse, use converters that leads to a much bigger response time because of useless actions.