JPA 多对一关系 - 只需要保存 Id



我有2个类:司机和汽车。汽车表在单独的进程中更新。我需要的是驱动程序中的属性,该属性允许我阅读完整的汽车描述并仅写入指向现有汽车的 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);
    }
}

汽车对象只是典型的 JPA 对象,没有对驱动程序的反向引用。

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

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

我收到以下错误:

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

我不想更新数据库中的汽车实例,而只是从驱动程序引用它。知道如何实现我想要的吗?

谢谢。

更新:忘了提到我在创建驱动程序期间传递的汽车 ID 是数据库中现有汽车的有效 ID。

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

@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;

因此,这里发生的事情是,当您要进行插入/更新时,只需填充carId字段并执行插入/更新。由于 car 字段是不可插入和不可更新的,Hibernate 不会抱怨这一点,并且由于在您的数据库模型中您只会将car_id填充为外键,因此此时这就足够了(您在数据库上的外键关系将确保您的数据完整性)。现在,当您获取实体时,汽车字段将由Hibernate填充,为您提供灵活性,在需要时仅获取您的父级。

您可以通过

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;

该错误消息表示对象图中有一个未显式保留的瞬态实例。简要回顾对象在 JPA 中可以具有的状态:

  • 瞬态:尚未存储在数据库中的新对象(因此实体管理器不知道)。没有设置 id。
  • 托管:实体管理器跟踪的对象。托管对象是您在事务范围内使用的对象,对托管对象所做的所有更改将在事务提交后自动存储。
  • 已分离:以前托管的对象,在转换提交后仍可访问。(事务外部的托管对象。设置了 ID。

错误消息告诉您的是,您正在使用的(托管/分离)驱动程序对象包含对休眠未知的 Car-对象的引用(它是暂时性的)。为了使Hibernate理解从驱动程序引用的任何未保存的Car实例也应该保存,您可以调用EntityManager的persist-方法。

或者,您可以在持久化

上添加一个级联(我认为,只是从我的头顶来看,还没有测试过它),这将在持久化驱动程序之前在 Car 上执行持久化。

@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;
public void setCarId(long carId) {
    this.car = new Car (carId);
}

它实际上不是car的保存版本。所以它是一个瞬态对象,因为它没有id。JPA要求你应该关心关系。如果实体是新的(不按上下文管理),则应先保存它,然后才能与其他托管/分离的对象关联(实际上 MASTER 实体可以使用级联来维护其子项)。

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

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

添加可选字段等于 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));

然后保持驱动程序正常

driverRepo.save(driver);

您将看到ID 为 1 的汽车在数据库中完美地分配给驾驶员

描述:

因此,这个小optional=false的制作可能是这将有助于更多的 https://stackoverflow.com/a/17987718

这是Adi Sutanto链接的缺失文章。

第 11 项:通过代理填充子端父关联执行比所需更多的 SQL 语句始终会降低性能。重要的是要努力尽可能减少它们的数量,依赖引用是易于使用的优化之一。

描述:当子实体可以通过对其父实体的引用(@ManyToOne@OneToOne惰性关联)持久化时,Hibernate 代理非常有用。在这种情况下,从数据库中获取父实体(执行 SELECT 语句)是一种性能损失和毫无意义的操作。休眠可以为未初始化的代理设置基础外键值。

要点:

  • 依靠春天的EntityManager#getReference()

  • use JpaRepository#getOne() 在本例中使用,

  • 在休眠中,使用 load()

  • 假设两个实体,AuthorBook,涉及单向@ManyToOne关联(作者是父端)我们通过代理获取作者(这不会触发SELECT),我们创建一个新书

  • 我们将代理设置为本书的作者并保存该书(这将触发图书表中的INSERT

输出样本:

  • 控制台输出将显示仅触发INSERT,没有SELECT

源代码可以在这里找到。

如果你想看到整篇文章,https://dzone.com/articles/50-best-performance-practices-for-hibernate-5-amp 放进回溯机器。我没有找到这篇文章的实时版本。

附言。我目前正在使用杰克逊对象映射器从前端反序列化实体时很好地处理这个问题。如果您对这一切如何发挥作用感兴趣,请发表评论。

在多通一注释中使用级联 @manytoone(cascade=CascadeType.Remove)

最新更新