0

I'm using Spring Data with Hibernate as JPA-implementation.

At some point I load a car, manipulate it and save it. Using the Spring JPA-Repository the code looks like this:

@Entity @DynamicUpdate @DynamicInsert
public class Car{ 
    @PostLoad
    void postLoad(){ log.debug("post load"); }

    @PrePersist @PreUpdate
    void preSave(){ log.debug("prePersist/preUpdate"); }

    /*..*/
}

@Repository
public interface CarRepository extends JpaRepository<Car, Integer>{}

@Controller
public CarController{
    public void businessLogic(){
        Car car = carRepo.findOne(1);  // SELECT ...
        log.debug("Car loaded");
        car.setColor("red");
        // ...
        carRepo.saveAndFlush(car);     // UPDATE car SET ... <-- !!!
    }
}

This works fine in all automated tests and in 99% in production. The logs with transaction logs and SQL look like this most of the time:

SQL: select ... from Car ...
Car: post load
Car loaded
Getting transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.saveAndFlush]
Car: prePersist/preUpdate
SQL: update Car set ...
Completing transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.saveAndFlush]

There are only a few times when hibernate does a SELECT right before the UPDATE.

SQL: select ... from Car ...
Car: post load
Car loaded
Getting transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.saveAndFlush]
SQL: select ... from Car ...
Car: post load
Car: prePersist/preUpdate 
SQL: update Car set ...
Completing transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.saveAndFlush]

Can someone explain the circumstances under which a second select is done? I'm not able to see it.

Marcel
  • 4,054
  • 5
  • 36
  • 50
  • Don't know your whole setup there, but if Car has 'child objects' then Hibernate may be selecting to check if those child objects are already in a database so it doesn't have to save them too (since they are already there). – Shadov Sep 14 '16 at 17:42
  • Something else that code should be in a service which would be transactional and not in your controller. Now you have 2 (implicit) transactions while everything should be in a single transaction. Next to that you should (need to) do a `saveAndFlush` only a `save` should be enough. – M. Deinum Sep 15 '16 at 11:24

2 Answers2

1

That's Hibernate doing dirty-checking. It re-loads the entity to compare it with any changes you're saving.

There are several ways to lessen its performance impact, such as using versioning: Java - JPA - @Version annotation or bytecode modification to make the dirty checking more efficient.

Kayaman
  • 72,141
  • 5
  • 83
  • 121
  • That's what I also expected. But most of the time it doesn't reload from database. Is it somehow possible to identify the cases when Hibernate doesn't have enough information so that it does this request?? – Marcel Sep 14 '16 at 21:03
  • Do you mean programmatically? I imagine the mechanism is documented somewhere (in the source code if nowhere else), but I wouldn't start writing my own functionality around that. – Kayaman Sep 15 '16 at 05:30
  • The link is dead – sloth Sep 22 '17 at 09:31
  • @sloth Thanks, fixed. – Kayaman Sep 22 '17 at 09:43
0

Hibernate does dirty checking every time you have a SELECT statement after some save/delete operation in the same transaction. Dirty checking is done to clear 1st/2-nd level caches. Roughly, Hibernate looks up through HashMap, containing names of cached tables and compares with tables used in your request.

Ermintar
  • 1,322
  • 3
  • 22
  • 39