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

时间:2015-01-13 19:56:02

标签: 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);
    }

}

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

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

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

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

谢谢。

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

7 个答案:

答案 0 :(得分:13)

您可以通过getReference中的EntityManager来电:

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

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

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

答案 1 :(得分:7)

作为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;

所以这里发生的是当你想进行插入/更新时,你只填充carId字段并执行插入/更新。由于car字段是不可插入的且不可更新的,Hibernate不会抱怨这一点,因为在你的数据库模型中你只能将你的car_id填充为外键,无论如何这已经足够了(你的数据库上的外键关系将会确保您的数据完整性)。现在,当您获取实体时,Hibernate将填充car字段,为您提供灵活性,只需在需要时获取父项。

答案 2 :(得分:5)

您只能使用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;

答案 3 :(得分:4)

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

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

错误消息告诉您的是,您正在使用的(托管/已分离)Driver对象持有对Hibernate未知的Car对象的引用(它是瞬态的)。为了使Hibernate能够理解保存的任何未保存的Car实例也应该保存,你可以调用EntityManager的persist-method。

或者,你可以在persist上添加一个级联(我想,只是从头顶开始,我们还没有对它进行过测试),这会在持久化Driver之前在Car上执行一个持久化。

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

如果您使用entitymanager的merge-method存储驱动程序,则应添加CascadeType.MERGE,或两者都添加:

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

答案 4 :(得分:3)

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

它实际上不是car的保存版本。所以它是瞬态对象,因为它没有id。 JPA要求你应该关心关系。如果实体是新的(不由上下文管理),则应该在它与其他托管/分离对象相关之前保存它(实际上MASTER实体可以通过使用级联来维护它的子代)。

两种方式级联从db中保存和检索

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

答案 5 :(得分:0)

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

@ManyToOne(optional = false) // Telling hibernate trust me (As the creator of 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

答案 6 :(得分:-2)

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