JPA many-to-one 关系 - 只需要保存 Id

JPA many-to-one relation - need to save only Id

我有 2 类: Driver 和汽车。汽车 table 在单独的过程中更新。我需要的是 Driver 中的 属性 允许我阅读完整的汽车描述并只写指向现有汽车的 Id。这是示例:

@Entity(name = "DRIVER")
public class Driver {
... ID and other properties for Driver goes here .....

    @ManyToOne(fetch=FetchType.LAZY)
    @JoinColumn(name = "CAR_ID")
    private Car car;

    @JsonView({Views.Full.class})
    public Car getCar() {
      return car;
    }
    @JsonView({Views.Short.class})
    public long getCarId() {
      return car.getId();
    }
    public void setCarId(long carId) {
      this.car = new Car (carId);
    }

}

Car object 只是典型的 JPA object,没有对 Driver.

的反向引用

所以我试图通过此实现的是:

  1. 我可以使用详细的 JSON 视图
  2. 阅读完整的汽车描述
  3. 或者我只能在 Short JsonView 中读取汽车的 Id
  4. 最重要的是,在创建新的 Driver 时,我只想传入 JSON 汽车的 ID。 这样我就不需要在持续期间对汽车进行不必要的读取,而只需更新 Id。

我收到以下错误:

object references an unsaved transient instance - save the transient instance before flushing : com.Driver.car -> com.Car

我不想在数据库中更新 Car 的实例,而只是从 Driver 中引用它。知道如何实现我想要的吗?

谢谢。

更新: 忘记提及我在创建 Driver 期间传递的汽车 ID 是数据库中现有汽车的有效 ID。

在manytoone注解中使用级联 @manytoone(级联=CascadeType.Remove)

public void setCarId(long carId) {
      this.car = new Car (carId);
    }

实际上是一个car版本没有保存。所以它是一个瞬态对象,因为它没有id。 JPA 要求您注意关系。如果实体是新的(不受上下文管理),则应先保存它,然后才能与其他 managed/detached 对象关联(实际上,MASTER 实体可以通过使用级联来维护它的子实体)。

两种方式级联从数据库中保存和检索

您还应该避免手动设置实体 ID 如果您不想 update/persist 汽车 通过它的 MASTER 实体,您应该从数据库中获取 CAR 并维护您的 driver 它是实例 。因此,如果您这样做,Car 将从持久性上下文中分离出来,但它仍然具有 ID 并且可以与任何实体关联而不会产生影响。

该错误消息意味着您的对象图中有一个未明确保留的临时实例。对象在 JPA 中可以具有的状态的简短回顾:

  • Transient: 尚未存储在数据库中的新对象(因此对于 entitymanager 而言是未知的。)没有设置 id。
  • Managed:entitymanager 跟踪的对象。托管对象是您在事务范围内使用的对象,一旦提交事务,对托管对象所做的所有更改将自动存储。
  • Detached:事务提交后仍可访问的以前管理的对象。 (事务外的托管对象。)设置了 id。

错误消息告诉您的是,您正在使用的 (managed/detached) Driver-object 持有对 Hibernate 未知的 Car-object 的引用(它是瞬态的)。为了让 Hibernate 理解任何未保存的从 Driver 引用的 Car 实例也应该被保存,你可以调用 EntityManager 的持久化方法。

或者,您可以在 persist 上添加一个级联(我认为,只是从我的头顶开始,还没有测试过),这将在持久化 Driver 之前在 Car 上执行 persist。

@ManyToOne(fetch=FetchType.LAZY, cascade=CascadeType.PERSIST)
@JoinColumn(name = "CAR_ID")
private Car car;

如果您使用实体管理器的合并方法来存储驱动程序,您应该添加 CascadeType.MERGE 代替,或两者:

@ManyToOne(fetch=FetchType.LAZY, cascade={ CascadeType.PERSIST, CascadeType.MERGE })
@JoinColumn(name = "CAR_ID")
private Car car;

您可以通过 getReference 调用 EntityManager:

EntityManager em = ...;
Car car = em.getReference(Car.class, carId);

Driver driver = ...;
driver.setCar(car);
em.persist(driver);

这不会从数据库执行 SELECT 语句。

您只能像这样使用 car ID:

@JoinColumn(name = "car")
@ManyToOne(targetEntity = Car.class, fetch = FetchType.LAZY)
@NotNull(message = "Car not set")
@JsonIgnore
private Car car;

@Column(name = "car", insertable = false, updatable = false)
private Long carId;

作为对 okutane 的回答,请参阅片段:

@JoinColumn(name = "car_id", insertable = false, updatable = false)
@ManyToOne(targetEntity = Car.class, fetch = FetchType.EAGER)
private Car car;

@Column(name = "car_id")
private Long carId;

所以这里发生的是,当您想要执行 insert/update 时,您只需填充 carId 字段并执行 insert/update。由于 car 字段是 non-insertable 和 non-updatable Hibernate 不会抱怨这个,因为在你的数据库模型中你只会填充你的 car_id 作为外键无论如何这在这一点上就足够了(并且您在数据库上的外键关系将确保您的数据完整性)。现在,当您获取您的实体时,汽车字段将由 Hibernate 填充,从而为您提供灵活性,在需要时只获取您的 parent。

添加可选字段等于 false,如下所示

@ManyToOne(optional = false) // Telling hibernate trust me (As a trusted developer in this project) when building the query that the id provided to this entity is exists in database thus build the insert/update query right away without pre-checks
private Car car; 

这样你就可以将汽车的 ID 设置为

driver.setCar(new Car(1));

然后坚持driver正常

driverRepo.save(driver);

你会看到 id 为 1 的汽车在数据库

中完美地分配给了 driver

描述:

所以这个小小的 optional=false 可能会有所帮助 https://whosebug.com/a/17987718