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?
It depends on whether the method is contained in a transaction or not.
.save()
must be called explicitly to update the database.How can I detect cases like these and know exactly when to use the
.save()
method and when can I ignore it?
.save()
method must be called for a modified entity explicitly if:
new Car()
.List
before the transaction begins.).save()
method does not have to be called for entities which are queried from JPA within the same transaction. (E.g. carRepository.findById(id)
and other similar methods).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.
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.
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.
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.