Skip to content

Prevent select before insert #3199

@frct1

Description

@frct1

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:

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.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions