hibernatespring-bootspring-data-jpaopen-session-in-view

When do we need to .save() an entity in Spring?


In a relatively large Spring Boot project, I have a method with the following (overly simplified) sequence of events:

Car car = carRepository.save(new Car());
Person person = personRepository.save(new Person(car)); // Car is a field of Person

Engine engine = engineRepository.save(new Engine());
person.getCar().setEngine(engine);

carRepository.save(person.getCar()); // without this the engine and car relation is not registered

Car, Person and Engine are all @Entity classes (database objects). For this example, their implementation could be the following:

// Car.java
@Entity
@Table(name = "car_tbl")
public class Car {
    @Id
    @GeneratedValue
    @Column(name = "car_id")
    private Long id;

    @OneToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "engine_id", unique = true)
    private Engine engine;
}

// Person.java
@Entity
@Table(name = "person_tbl")
public class Person {
    @Id
    @GeneratedValue
    @Column(name = "person_id")
    private Long id;

    @OneToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "car_id", nullable = false, unique = true)
    private Car car;
}

// Engine.java
@Entity
@Table(name = "engine_tbl")
public class Engine {
    @Id
    @GeneratedValue
    @Column(name = "engine_id")
    private Long id;
}

The above method is only used from inside a REST API method. And OSIV (Open Session In View) is enabled in the configuration properties.

The question is, why do I need to save the person.getCar() object? It is the same Car object which I just created during the same request processing. At which point does it become transient and not managed by the persistence context? I thought all changes would be flushed automatically, because OSIV is enabled.

How can I detect cases like these and know exactly when to use the .save() method and when can I ignore it?


Solution

  • Conclusion

    It depends on whether the method is contained in a transaction or not.


    How can I detect cases like these and know exactly when to use the .save() method and when can I ignore it?


    Transaction

    If the above sequence of events is called from a method annotated by @Transactional, then it would work as expected.

    Then the created Car object becomes managed by the persistence context and all changes to it, within the same transaction, are flushed after the transaction is completed.

    No Transaction

    If the above method does not belong to any transaction, all changes to the objects are local (object properties change, but are not flushed to database).

    That is why calling persist() or merge() (in the above case, save() which internally uses those two) must be done explicitly to flush the changes.

    Better to Use Transactions

    Saving a single entity executes a single SQL query, which is atomic, thus a transaction itself. If the sequence of database events is not contained in a transaction, then each save() call acts as a standalone "transaction".

    Usually, this is bad practice, because if one of the later "transactions" fail (some kind of Exception thrown), the previous succeeded transactions have already been flushed, possibly bringing the database to an invalid state from the business logic perspective.


    Open Session In View

    The behavior has nothing to do with OSIV.

    OSIV keeps the database session open during view rendering, so that further queries (transactions) could be performed after the main request processing has finished, in the view layer. Using OSIV is widely considered an anti-pattern.