我们什么时候需要 .save() Spring 中的实体?

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

在一个相对较大的 Spring 引导项目中,我有一个具有以下(过度简化)事件序列的方法:

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

CarPersonEngine都是@Entity类(数据库对象)。对于此示例,它们的实现可能如下所示:

// 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;
}

上述方法只能在 REST API 方法中使用。 并且在配置属性中启用了 OSIV(在视图中打开会话)。

问题是,为什么我需要保存person.getCar()对象?它与我刚刚在同一请求处理期间创建的 Car 对象相同。在什么时候它变成了 transient 而不是 managed 由持久性上下文?我以为所有更改都会自动刷新,因为启用了 OSIV。

如何检测此类情况并准确知道何时使用.save()方法以及何时可以忽略它?

结论

这取决于该方法是否包含在事务中。

  • 在事务中: 上面的代码隐含地按预期工作。
  • 不在事务中:必须显式调用 .save() 来更新数据库。

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

  • 如果出现以下情况,则必须显式为修改后的实体调用 .save() 方法:
    1. 更改代码不属于事务
    2. 实体是新创建的:new Car()
    3. 实体对象是从外部获取的,不是在同一个事务中从数据库中获取的。 (例如,在交易开始之前已经保存在 List 中。)
  • .save() 方法 而不是 必须为同一事务中从 JPA 查询的实体调用。 (例如 carRepository.findById(id) 和其他类似方法)。

交易

如果上述事件序列是从 @Transactional 注释的方法中调用的,那么它将按预期工作。

然后,创建的 Car 对象由持久性上下文 管理,并且在同一事务中对其进行的所有更改都会在事务完成后刷新。

无交易

如果上述方法不属于任何事务,则对象的所有更改都是本地的(对象属性更改,但不会刷新到数据库)。

这就是为什么调用 persist()merge()(在上面的例子中,save() 内部使用了这两个)必须显式完成以刷新更改。

更好地使用交易

保存单个实体执行单个 SQL 查询,该查询是原子的,因此是事务本身。如果数据库事件序列不包含在事务中,则每个 save() 调用都作为一个独立的 "transaction".

通常,这是不好的做法,因为如果后面的 "transactions" 之一失败(某种 Exception 抛出),先前成功的事务已经被刷新,可能会使数据库从业务逻辑角度来看是无效状态。


在视图中打开会话

该行为与 OSIV 无关。

OSIV 在视图渲染期间保持数据库 session 打开,以便在主请求处理完成后,可以在视图层中执行进一步的查询(事务)。 Using OSIV is widely considered an anti-pattern.

  • Why is Hibernate Open Session In View considered a bad practice?
  • JPA @Entity not managed after creation?