【问题标题】:Why does Hibernate join unrelated entities on save()为什么 Hibernate 在 save() 上加入不相关的实体
【发布时间】:2020-03-02 09:35:53
【问题描述】:

在我的 Spring Boot 2.1.8 项目中,我有以下实体:

@Entity
@Table(name = "teacher")
@Where(clause = "active = true")
class TeacherEntity {
  @Id
  @Column(name = "id")
  public String id;

  @Basic
  @Column(name = "active")
  public boolean active;

  @Basic
  @Column(name = "name")
  public String name;
}

@Entity
@Table(name = "course")
class CourseEntity {
  @Id
  @Column(name = "id")
  public int id;

  @ManyToOne
  @JoinColumn(name = "course_id")
  public TeacherEntity teacher;
}

@Entity
@Table(name = "seat")
class SeatEntity {
  @Id
  @Column(name = "id")
  public int id;

  @ManyToOne
  @JoinColumn(name = "parent_id")
  public CourseEntity course;

  @Basic
  @Column(name = "blabla")
  public String blabla;
}

情况:为具有有效教师的有效课程创建并保留了 SeatEntity。后来,我们从数据库中加载了 SeatEntity,修改了它的blabla 字段,并尝试保存。

public void updateSeats() {
  List<SeatEntity> seats = seatRepository.findAllBy____();
  seats.forEach(seat -> {
    seat.blabla = "new blabla!";
    seat = seatRepository.save(seat); // Exception if Teacher.active = false!
  });
}

我得到的例外是:

原因:javax.persistence.EntityNotFoundException:无法找到 com.company.TeacherEntity,ID 为 5770d5e3-d7e8-401e-84c5-bb3d83e2e484 在 org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl$JpaEntityNotFoundDelegate.handleEntityNotFound(EntityManagerFactoryBuilderImpl.java:162) 在 org.hibernate.event.internal.DefaultLoadEventListener.load(DefaultLoadEventListener.java:216) 在 org.hibernate.event.internal.DefaultLoadEventListener.proxyOrLoad(DefaultLoadEventListener.java:333) 在 org.hibernate.event.internal.DefaultLoadEventListener.doOnLoad(DefaultLoadEventListener.java:108) 在 org.hibernate.event.internal.DefaultLoadEventListener.onLoad(DefaultLoadEventListener.java:74) 在 org.hibernate.internal.SessionImpl.fireLoadNoChecks(SessionImpl.java:1298) 在 org.hibernate.internal.SessionImpl.internalLoad(SessionImpl.java:1161) 在 org.hibernate.type.EntityType.resolveIdentifier(EntityType.java:687) 在 org.hibernate.type.EntityType.resolve(EntityType.java:464) 在 org.hibernate.type.ManyToOneType.resolve(ManyToOneType.java:240) 在 org.hibernate.engine.internal.TwoPhaseLoad.doInitializeEntity(TwoPhaseLoad.java:226) 在 org.hibernate.engine.internal.TwoPhaseLoad.initializeEntity(TwoPhaseLoad.java:160) 在 org.hibernate.loader.Loader.initializeEntitiesAndCollections(Loader.java:1179) 在 org.hibernate.loader.Loader.processResultSet(Loader.java:1028) 在 org.hibernate.loader.Loader.doQuery(Loader.java:964) 在 org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:354) 在 org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:324) 在 org.hibernate.loader.Loader.loadEntity(Loader.java:2401) 在 org.hibernate.loader.entity.AbstractEntityLoader.load(AbstractEntityLoader.java:64) 在 org.hibernate.loader.entity.AbstractEntityLoader.load(AbstractEntityLoader.java:54) 在 org.hibernate.persister.entity.AbstractEntityPersister.load(AbstractEntityPersister.java:4312) 在 org.hibernate.event.internal.DefaultLoadEventListener.loadFromDatasource(DefaultLoadEventListener.java:570) 在 org.hibernate.event.internal.DefaultLoadEventListener.doLoad(DefaultLoadEventListener.java:538) 在 org.hibernate.event.internal.DefaultLoadEventListener.load(DefaultLoadEventListener.java:208) 在 org.hibernate.event.internal.DefaultLoadEventListener.proxyOrLoad(DefaultLoadEventListener.java:333) 在 org.hibernate.event.internal.DefaultLoadEventListener.doOnLoad(DefaultLoadEventListener.java:108) 在 org.hibernate.event.internal.DefaultLoadEventListener.onLoad(DefaultLoadEventListener.java:74) 在 org.hibernate.internal.SessionImpl.fireLoadNoChecks(SessionImpl.java:1298) 在 org.hibernate.internal.SessionImpl.internalLoad(SessionImpl.java:1161) 在 org.hibernate.type.EntityType.resolveIdentifier(EntityType.java:687) 在 org.hibernate.type.EntityType.resolve(EntityType.java:464) 在 org.hibernate.type.ManyToOneType.resolve(ManyToOneType.java:240) 在 org.hibernate.engine.internal.TwoPhaseLoad.doInitializeEntity(TwoPhaseLoad.java:226) 在 org.hibernate.engine.internal.TwoPhaseLoad.initializeEntity(TwoPhaseLoad.java:160) 在 org.hibernate.loader.Loader.initializeEntitiesAndCollections(Loader.java:1179) 在 org.hibernate.loader.Loader.processResultSet(Loader.java:1028) 在 org.hibernate.loader.Loader.doQuery(Loader.java:964) 在 org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:354) 在 org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:324)

为什么 Hibernate 会抱怨加载与我要保存的数据无关的双联表?这个问题的解决方案是什么?我是否需要编写本机更新查询来回避无意义的连接并保存数据?

【问题讨论】:

  • 与您要保存的数据无关?你的SeatEntity 依赖于CourseEntity,而TeacherEntity 又依赖于TeacherEntity。它与它有关,但您的 @Where 子句会阻止加载非活跃教师。您正在尝试保存不完整的数据,这是因为是这样设计的。也许你不应该有 @Where 注释,因为它会立即咬你的后背。
  • 我正在尝试更新seat 表上的列。我已经有了实体。当我只需要一个简单的UPDATE seat...WHERE id=xxx 时,我为什么还要浪费时间加入其他桌子?
  • 更不用说:1.我没有亲自这样设计它(你从来没有继承过别人的代码?) 2.@Where注解已经投入生产很长一段时间了,现在没有其他不良影响,所以它没有“立即咬我” 3. 这个问题专门询问为什么 Hibernate 在只需要更新单个 @Basic 列时进行连接。如果有的话,我希望这会在findAllBy____ 调用上失败,而不是save() - save() 在做什么(关于Teacher)它不应该已经做了?
  • 我从来没有继承过家庭作业,因为这就是老师、课程和座位听起来的样子。该映射定义了一个依赖关系,Hibernate 正在检查依赖关系是否得到满足(即座位链接到链接到老师的课程)。如果它不检查该依赖关系,它将允许保存损坏的数据。您可以通过拿起一本书并阅读它来了解更多关于对象关系映射的美妙和不那么美妙的世界。您当然可以使用本机查询对其进行更新,但 ORM 正在完成其工作(请注意,这也是正确的)。
  • 不要以为您无需了解 ORM 的工作原理就可以享受 ORM 的好处。

标签: java hibernate spring-boot jpa


【解决方案1】:

原来问题是通过将方法包装在事务中来解决的:

@Transactional
public void updateSeats() {
  // same code as above
}

通过在同一个事务中进行加载和保存,Hibernate 不再尝试重新加载整个实体图。

【讨论】:

    猜你喜欢
    • 2016-02-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-07-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多