【问题标题】:JPA many-to-one relation - need to save only IdJPA 多对一关系 - 只需要保存 Id
【发布时间】:2015-01-13 19:56:02
【问题描述】:

我有 2 个课程:司机和汽车。汽车表在单独的过程中更新。我需要的是在 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 对象只是典型的 JPA 对象,没有对 Driver 的反向引用。

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

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

我收到以下错误:

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

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

谢谢。

更新: 忘了说我在创建 Driver 时传递的 Car 的 ID 是 DB 中现有 Car 的有效 ID。

【问题讨论】:

    标签: java hibernate jpa many-to-one


    【解决方案1】:

    您可以通过getReference 拨打EntityManager 来做到这一点:

    EntityManager em = ...;
    Car car = em.getReference(Car.class, carId);
    
    Driver driver = ...;
    driver.setCar(car);
    em.persist(driver);
    

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

    【讨论】:

    • 在 Spring 中,使用 JpaRepository#getOne(),参见 Best Performance Practices for Hibernate 5 and Spring Boot 2 (Part 1),第 11 项:通过代理填充子端父关联
    • 我认为这就是 OP 正在寻找的东西,我正在寻找完全相同的东西,我想保存孩子而不必从数据库中读取父级,因为我不需要它此时,对数据库进行选择将是低效的
    【解决方案2】:

    作为对okutane的回答,请参阅sn-p:

    @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 填充,让您可以灵活地在需要时仅获取您的父级。

    【讨论】:

    • > 现在,当您获取实体时,汽车字段将由 Hibernate 填充,从而为您提供灵活性,在需要时仅获取您的父级。但是fetch = FetchType.EAGER不应该是LAZY吗?
    • @Niklas 没关系,它可以是 EAGER 或 LAZY,具体取决于您的用例。这里的重点是它是一个写优化,所以你不需要在插入你的子实体之前获取父实体,你只需填写 id 保存到数据库的往返行程。当您获取您的孩子时,您可能希望获取包括汽车字段的完整图表,并且(至少在我的情况下)我想使用 1 个查询而不是 2 个查询来获取它,因此是 EAGER。如果还不清楚,请告诉我,我将制定一个完整的工作示例来证明这一点。
    • 哦,这真的很有帮助。我以前很困惑,但现在清楚多了。
    • 我认为这是最简单的解决方案
    【解决方案3】:

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

    【讨论】:

    • 通过在实体上设置 insertable = false, updatable = false 而不是在 id 上,我设法插入了一个具有有效父 id 的子项,而无需获取父项。感谢您的提示!
    • @JonckvanderKogel 愿意分享 sn-p 吗? :)
    • @okutane 请参阅下面的 sn-p。希望对您有所帮助!
    【解决方案4】:

    该错误消息意味着您的对象图中有一个未显式持久化的瞬态实例。对象在 JPA 中可能具有的状态的简短回顾:

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

    错误消息告诉您的是,您正在使用的(托管/分离的)驱动程序对象包含对 Hibernate 未知的汽车对象的引用(它是瞬态的)。为了让 Hibernate 明白,任何从 Driver 引用的 Car 的未保存实例也应该被保存,您可以调用 EntityManager 的 persist-method。

    或者,您可以在持久化上添加一个级联(我认为,只是从我的头顶上,还没有测试过),这将在持久化驱动程序之前在汽车上执行持久化。

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

    如果你使用实体管理器的merge-method来存储Driver,你应该添加CascadeType.MERGE,或者两者都添加:

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

    【讨论】:

    • 使用 JPA 的第一件事是您必须停止考虑数据库。 JPA 处理 Java 对象,这就是您查询的内容,也就是您得到的内容。 Driver 没有属性 carId,它有一个属性 Car。所以,如果你收到这些数据(我猜名字是新司机的名字),你应该首先在 carId 上执行 EntityManager#find 来获取 Car 对象。然后创建一个新的 Driver,将获取的 Car 分配给它,并将其发送给 EntityManager#persist。由于 Car 已经被持久化和管理,我认为你不需要任何级联来工作..
    • 我明白了。我想我无法像我一样创建 Car 对象并将其标记为持久(仅提供一个 Id)。对于来自纯数据库查询世界的人来说,这感觉像是一个尴尬的限制,其中更新 ID 列是简单的任务。我想另一种解决方案是将 CAR_ID 字段从 @ManyToOne 更改为基本 Long 字段并添加 Transient 字段 Car。这将允许我在获取和设置期间仅使用 Id,当我需要获取详细的汽车信息时,我可以通过显式读取该汽车来设置来自 Resuorce 控制器的汽车字段。感谢您的帮助。
    • 那将违背 JPA 的目的,如果你想这样工作,你应该检查 Spring JDBC,在那里你可以自己编写所有查询。针对其目的使用框架很少是一个好主意..
    • 嗯,如果我需要创建/更新驱动程序,即使我不使用所有这些数据,我也必须进行不必要的读取(从 id 读取汽车),这听起来不是很笨拙。想象一下,像这样的属性很少,而不是单个持久化,您最终会在持久化之前进行 N 次读取(其中 N 是像 Car 这样的属性的数量),只是为了意识形态。我会因为它不理想而畏缩。
    • 是的,但我们必须记住,它不再是 1983 年了。我们使用的许多原则都非常古老,并且在今天并不适用,因为处理器能力、内存和磁盘空间都非常丰富。 JPA 绝不是最优的,但在 95% 的情况下它足够好。话虽如此,我目前正在加快一些 JPA 查询的速度,这可能会很痛苦。这真的取决于项目,你必须权衡速度和编程的容易性。在大多数情况下,速度不是问题,因此选择 JPA 将是正确的选择,因为它可以节省您的时间。
    【解决方案5】:
    public void setCarId(long carId) {
          this.car = new Car (carId);
        }
    

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

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

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

    【讨论】:

    • 谢谢热心人。这是否意味着它是瞬态的,因为 Id 不存在或者我没有通过阅读以某种方式初始化它?我经过的汽车的原因 ID 有效
    • 是的,因为它没有在持久化上下文中注册。 JPA 根本无法将其视为已保存的对象! :) 即使它在 DB 中不存在,那么你怎么能将它与不存在的东西联系起来呢?:)
    【解决方案6】:

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

    【讨论】:

    • 这也改变了关系的语义。当设置optional=false 时,它会更改默认值(true),从那时起,就不可能保存具有(driver.getCar() == null) == true 的驱动程序实例。如果这没问题,也许这个约束应该在提出任何问题之前就存在。
    【解决方案7】:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-12-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-25
      • 2011-07-12
      • 1970-01-01
      相关资源
      最近更新 更多