What Is This Spring.Jpa.Open-In-View=True Property in Spring Boot

What is this spring.jpa.open-in-view=true property in Spring Boot?

This property will register an OpenEntityManagerInViewInterceptor, which registers an EntityManager to the current thread, so you will have the same EntityManager until the web request is finished. It has nothing to do with a Hibernate SessionFactory etc.

Why spring jpa repository always load eagerly in @GetMapping method?

I can see 2 problems:

  1. Lazy loading:

Check if spring.jpa.open-in-view is explicitely configured.
Unfortunately, the default is true

You can get more info about this setting at: What is this spring.jpa.open-in-view=true property in Spring Boot?

If you dont have it configured, you may receive a warning on startup:

WebConfiguration$JpaWebMvcConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning

  1. JSON serialisation

You have 2 entities that cross reference each other, when you serialise a publisher you also serialiseits books, but the book has publisher you already visited. You enter an endless loop in the serialisation phase.

Either use JSON annotations to exclude one side of the relation from serialisation, or use custom DTOs to transfer data.

Update

I expect when you set spring.jpa.open-in-view = false and you don't specify what to fetch, you will start to have LazyInitializationException. This is because you still try to access the fields while serialising them to JSON (but now the objects are not attached to a session). Contrary to your comment, this is a proof that collection is loaded lazily (that means, you have a proxy instead of a collection. You can access this proxy, which forces loading, but only if the session is still open - same transaction or open-in-view setting).

My suggestion: attack JSON serialisation first, this is the real bug. Worry about fetching strategy after that is fixed.

My repository is not called in my service - Spring Data JPA

The reason

The reason of the issue is this part of the code

personConnected.getWeightsList().contains(weightRecordToDelete)

Why it happens?

There is a spring data property spring.jpa.open-in-view and it is true by default. It means that JPA Persistent Context (Hibernate Session) is opened during entire HTTP request. What is this spring.jpa.open-in-view=true property in Spring Boot?

If Persistent Context is opened, fetch = FetchType.LAZY property in the code below doesn't work at all.

@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "person")
private List<WeightRecord> weightsList = new ArrayList<WeightRecord>();

When you do personConnected.getWeightsList().conatins(), Hibernate loads weightsList in the background. That's why the last SQL log entry is logged.

    select
weightslis0_.person_id_person as person_i8_3_0_
...
from
weight_record weightslis0_
where
weightslis0_.person_id_person=?

So when you delete a WeightRecord, it remains in the Person.weightsList, because of it was loaded by personConnected.getWeightsList().conatins().

When HTTP request is completed, Persistent Context becomes closed, and Hibernate flushes all changes to the database. There is cascade = CascadeType.ALL on the Person side, so Hibernate should keep in mind a deleted WeightRecord in the weightsList. So it does nothing, because you could delete WeightRecord and insert it by some reasons again.

You can verify statements above just by removing personConnected.getWeightsList().conatins() part of the code, delete will start work.

How to solve

  1. Set spring.jpa.open-in-view=false in the application.property. So you will have LazyInitializationException with personConnected.getWeightsList().conatins()

  2. Remove personConnected.getWeightsList().conatins() code. You can do the same just comparing WeightRecord.Person.id and a current Person.id.

        Optional<Person> personConnected = personRepository.findById(appUserConnectedId);
if (personConnected.isPresent() ) {
WeightRecord weightRecordToDelete = weightRecordRepository.findById(weightId).orElseThrow();
Long userPersonId = personConnected.get().getIdPerson();
Long recordPersonId = weightRecordToDelete.getPerson().getIdPerson();
if (Objects.equals(userPersonId, recordPersonId)) {
logger.info("SERVICE FOR DELETING");
return ResponseEntity.ok(weightRecordServiceImpl.deleteWeightById(weightRecordToDelete));
}
}

logger.info("BAD USER FOR BAD WEIGHT");
return ResponseEntity.notFound().build();

Now you can keep cascade = CascadeType.ALL on the weightsList on Person side.

Notes

Always use spring.jpa.open-in-view=false. LazyInitializationException is your friend.

Always use fetch = FetchType.LAZY on the @ManyToOne part of the association. It is FetchType.EAGER by default.

    @ManyToOne(fetch = FetchType.LAZY)
private Person person;

Never use Jackson annotations like @JsonIgnore and Hibernate Validator annotations like @Min(1) with entities.
Use PersonEntity class for an entity and Person for JSON. Do mapping on the service level.



Related Topics



Leave a reply



Submit