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

And*_*i V 16 java hibernate jpa many-to-one

我有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);
    }

}
Run Code Online (Sandbox Code Playgroud)

Car对象只是典型的JPA对象,没有对Driver的反向引用.

所以我试图通过这个实现:1)我可以使用详细的JSON View 2)阅读完整的汽车描述或者我只能阅读简短的JsonView 3中的汽车ID而且最重要的是,当创建新的驱动程序我只是想要传递汽车的JSON ID.这样我就不需要在持久化期间为汽车执行不必要的读取操作,而只是更新Id.

我得到以下错误:"对象引用未保存的瞬态实例 - 在刷新之前保存瞬态实例:com.Driver.car - > com.Car"

我不想在DB中更新Car的实例,而只是从Driver中引用它.知道怎么做到我想要的吗?

谢谢.

更新:忘记提及我在创建驱动程序期间传递的汽车ID是DB中现有汽车的有效ID.

sce*_*tix 19

您可以通过以下方式getReference拨打电话EntityManager:

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

Driver driver = ...;
driver.setCar(car);
em.persist(driver);
Run Code Online (Sandbox Code Playgroud)

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

  • 在 Spring 中,使用 JpaRepository#getOne(),参见 [Best Performance Practices for Hibernate 5 and Spring Boot 2 (Part 1)](https://dzone.com/articles/50-best-performance-practices-for-hibernate- 5-amp),第 11 项:通过代理填充儿童方家长协会 (8认同)

Jon*_*gel 9

作为对okutane的回答,请参见代码段:

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

@Column(name = "car_id")
private Long carId;
Run Code Online (Sandbox Code Playgroud)

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

  • @Niklas,这并不重要,它可以是急切的或懒惰的,具体取决于您的用例。这里的要点是,这是一个写入优化,因此您不需要在插入子实体之前获取父实体,您只需填写 id 即可保存到数据库的往返行程。当您获取您的孩子时,您可能希望获取完整的图表,包括汽车字段,并且(至少在我的情况下)我想使用 1 个查询而不是 2 个查询来获取它,因此是 EAGER。如果还不清楚,请告诉我,我将制定一个完整的工作示例来证明这一点。 (2认同)

Tob*_*obb 6

该错误消息表示您的对象图中有一个瞬态实例,没有明确保留。简短回顾一下对象在JPA中可以具有的状态:

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

错误消息告诉您,正在使用的(托管/分离)驱动程序对象持有对Hibernate未知(瞬态)的Car对象的引用。为了使Hibernate了解从驱动程序引用的任何未保存的Car实例也应保存,您可以调用EntityManager的persist-method。

另外,您可以在持久性上添加一个层叠(我想,只是从我的头顶上还没有测试过),它将在持久化驱动程序之前在Car上执行持久化。

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

如果您使用entitymanager的合并方法来存储驱动程序,则应该添加CascadeType.MERGE,或两者都添加:

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

  • JPA的第一件事是您必须停止思考数据库。JPA处理Java对象,即您所查询的内容,即您所获取的内容。驱动程序没有属性carId,它具有属性Car。因此,如果您接收到这些数据(我想名称是新Driver的名称),则应首先在carId上执行EntityManager#find以获得Car对象。然后创建一个新的Driver,为其分配获取的Car,然后将其发送给EntityManager#persist。由于该汽车已经存在并可以管理,因此我认为您不需要任何级联即可工作。 (4认同)
  • 我知道了。我想我无法像我一样创建 Car 对象并将其标记为持久化(仅提供一个 Id)。对于来自纯 DB 查询世界的人来说,更新 ID 列是一项简单的任务,这感觉就像是尴尬的限制。我想另一个解决方案是将 CAR_ID 字段从 @ManyToOne 更改为基本 Long 字段并添加 Transient 字段 Car。这将允许我在获取和设置期间仅使用 Id,当我需要获取详细的 Car 信息时,我可以通过显式读取该 Car 来从 Resuorce Controller 设置 Car 字段。感谢你的帮助。 (2认同)
  • 这将违背 JPA 的目的,如果你想像那样工作,你应该查看 Spring JDBC,在那里你可以自己编写所有查询。违背其目的使用框架很少是一个好主意.. (2认同)
  • 嗯,这听起来很笨拙,如果我需要创建/更新驱动程序,即使我不使用所有这些数据,我也必须做非必要的读取(从id读取Car)。想象一下,几乎没有这样的属性,而不是单一的持久性,而是为了意识形态,最终在持久性之前先读取N次(其中N是像Car这样的属性的数量)。我不愿意这样做。 (2认同)
  • 是的,但我们必须记住,现在已经不是 1983 年了。我们使用的许多原则都非常陈旧,如今由于处理器能力、内存和磁盘空间充裕而无法很好地应用。JPA 绝不是最优的,但在 95% 的情况下它*足够好*。话虽如此,我目前正在努力加快一些 JPA 查询的速度,这可能很痛苦。这确实取决于项目,您必须权衡速度和编程的难易程度。在大多数情况下,速度不会成为问题,因此选择 JPA 将是正确的选择,因为它可以节省您的时间。 (2认同)
  • 在少数情况下,速度是第一位的(权重高于开发成本),应该避免使用 JPA,而应该使用 JDBC(Spring JDBC-template 在这些情况下会有很大帮助,因为它消除了很多样板代码并产生更漂亮的代码。) (2认同)

Rad*_*cea 5

您只能使用以下carID:

@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;
Run Code Online (Sandbox Code Playgroud)

  • 通过在实体上而不是在id上设置insertable = false,updatable = false,我设法插入了具有有效父ID的子级,而不必获取父级。谢谢你的提示! (2认同)