当集合未更改时,阻止 Hibernate 更新集合

发布于 2024-10-12 22:44:43 字数 2773 浏览 7 评论 0原文

我有两个实体 bean 定义如下(删除了不相关的内容):

@Entity
@Table(...)
public class MasterItem implements java.io.Serializable {

  private Set<CriticalItems> criticalItemses = new HashSet<CriticalItems>(0);

  @OneToMany(fetch = FetchType.EAGER, mappedBy = "masterItem", orphanRemoval = true,
            cascade = {javax.persistence.CascadeType.DETACH})
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.DELETE})
    public Set<CriticalItems> getCriticalItemses() {
        return this.criticalItemses;
    }
}

CriticalItems 定义如下:

@Entity
@Table(...)
public class CriticalItems implements java.io.Serializable {

    private MasterItem masterItem;

    @ManyToOne(fetch = FetchType.LAZY, optional = false,
            cascade = {javax.persistence.CascadeType.DETACH})
    @Cascade({CascadeType.SAVE_UPDATE})
    @JoinColumn(name = "mi_item_id", nullable = false)
    public MasterItem getMasterItem() {
        return this.masterItem;
    }
}

在我的 DAO 代码中 - 我有这些方法:

public MasterItem load(int id) {
    MasterItem results = (MasterItem) getSessionFactory().getCurrentSession()
        .get("com.xxx.MasterItem", id);

}

public void save(MasterItem master) {
    // master has been changed by the UI since it
    getSessionFactory().getCurrentSession().saveOrUpdate(master);
}

当我加载 MasterItem 时,它会正确加载,并且还会加载带有数据的 CriticalItems Set,按照指示。然后,我将此数据发送到我的 UI,并获取更新后的副本,然后尝试保留该副本。用户更新 MasterItem 对象中的字段,但不会触及 CriticalItems Set 或其中的任何内容 - 它保持不变。

当调用我的 save() 方法时,Hibernate 坚持为 CriticalItems 集中的每个项目发送 SQL 更新,即使它们都没有发生任何更改。

经过一番挖掘后,我认为正在发生的事情如下。当我执行 saveOrUpdate() 时,Hibernate 发现我的 MasterItem 对象处于分离状态,因此它尝试从磁盘重新加载它。然而,这样做时,它似乎使用了准备好的语句(该语句是由 Hibernate 在启动时自动创建的),并且该准备好的语句不会尝试连接到 CriticalItems 数据。

因此,Hibernate 更新了 MasterItem 对象,其中包含一整套 CriticalItems,但使用不带集合的 MasterItem 作为其“previousState”对象。因此,所有 CriticalItems 都通过 SQL 进行更新(不是插入,这本身很有趣)。

我在注释中做了什么导致了这种行为吗?我知道我可以使用拦截器来找出项目是否确实发生了变化,或者更改了脏标志以覆盖 Hibernate 的默认算法 - 但这似乎是 Hibernate 应该自行处理而无需我干预的事情。

任何见解将不胜感激。

更新: 根据评论,我想我理解了 saveOrUpdate() 和 merge() 之间的区别。我意识到 saveOrUpdate() 在所有情况下都会导致 SQL INSERT 或 SQL UPDATE,并且理论上,合并只会在对象已从其持久状态更改时发出更新,但为了确定这一点,Hibernate必须首先通过 SQL SELECT 重新加载对象。

所以,我想我可以回到我的代码并将 saveOrUpdate() 更改为 merge() 并且它会起作用,但事实并非如此。

当我使用 merge() 时,我得到了

org.springframework.orm.hibernate3.HibernateSystemException: could not initialize proxy - no Session; nested exception is org.hibernate.LazyInitializationException: could not initialize proxy - no Session

,但如果我改回 saveOrUpdate() ,它工作得很好。

我终于找到了原因 - 我没有在我的 @Cascade 注释中包含 CascadeType.MERGE (呃)。一旦我解决了这个问题,异常就消失了。

I have two entity beans defined as follows (unrelated stuff removed):

@Entity
@Table(...)
public class MasterItem implements java.io.Serializable {

  private Set<CriticalItems> criticalItemses = new HashSet<CriticalItems>(0);

  @OneToMany(fetch = FetchType.EAGER, mappedBy = "masterItem", orphanRemoval = true,
            cascade = {javax.persistence.CascadeType.DETACH})
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.DELETE})
    public Set<CriticalItems> getCriticalItemses() {
        return this.criticalItemses;
    }
}

CriticalItems is defined as follows:

@Entity
@Table(...)
public class CriticalItems implements java.io.Serializable {

    private MasterItem masterItem;

    @ManyToOne(fetch = FetchType.LAZY, optional = false,
            cascade = {javax.persistence.CascadeType.DETACH})
    @Cascade({CascadeType.SAVE_UPDATE})
    @JoinColumn(name = "mi_item_id", nullable = false)
    public MasterItem getMasterItem() {
        return this.masterItem;
    }
}

And in my DAO code - I have these methods:

public MasterItem load(int id) {
    MasterItem results = (MasterItem) getSessionFactory().getCurrentSession()
        .get("com.xxx.MasterItem", id);

}

public void save(MasterItem master) {
    // master has been changed by the UI since it
    getSessionFactory().getCurrentSession().saveOrUpdate(master);
}

When I load a MasterItem, it loads correctly, and also loads the CriticalItems Set with data, as directed. Then, I send this data to my UI, and get an updated copy back, which I then try to persist. The user updates fields in the MasterItem object, but does not touch the CriticalItems Set or anything in it - it remains unmodified.

When my save() method is invoked, Hibernate is insisting on sending SQL updates for each item in the Set of CriticalItems, even though none of them have changed in any way.

After some digging, here's what I think is happening. When I do a saveOrUpdate(), Hibernate sees my MasterItem object is in a detached state, so it tries to reload it from disk. However, when doing so, it appears to be using a prepared statement (which was auto-created by Hibernate at start-up) and this prepared statement does not attempt to join to the CriticalItems data.

So Hibernate has my updated MasterItem object with a full Set of CriticalItems, but uses a MasterItem without collections as its "previousState" object. Thus, all CriticalItems get updated via SQL (not inserted, which is interesting in itself).

Did I do something in my annotations that caused this behavior? I know I can use an Interceptor to find out of the item has really changed, or changed the dirty flag to override Hibernate's default algorithm - but this seems to be something that Hibernate should just handle on its own without my intervention.

Any insight would be appreciated.

UPDATE:
Based on comments, I think I understand the difference between saveOrUpdate() and merge(). I realize that saveOrUpdate() will result in either an SQL INSERT or an SQL UPDATE in all cases, and that merge, in theory, will only issue updates if the object has changed from it's persistent state, but in order to determine that, Hibernate has to reload the object first via SQL SELECT.

So, I thought I could just go back into my code and change saveOrUpdate() to merge() and it would work, but that wasn't quite the case.

When I used merge(), I was getting

org.springframework.orm.hibernate3.HibernateSystemException: could not initialize proxy - no Session; nested exception is org.hibernate.LazyInitializationException: could not initialize proxy - no Session

but it worked fine if I changed back to saveOrUpdate().

I finally found out why - I didn't include CascadeType.MERGE in my @Cascade annotation (ugh). Once I fixed that, the exception went away.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

趴在窗边数星星i 2024-10-19 22:44:43

这就是 update()merge() 之间的语义差异。

来自 Christian Bauer 和 Gavin King 的 Java Persistence with Hibernate (我在Hibernate 文档):

update()方法强制更新对象的持久状态
数据库,始终安排 SQL 更新。
...
item 对象在传递给之前或之后修改并不重要
更新()。
...
休眠
始终将对象视为脏对象并计划执行 SQL UPDATE。
冲洗期间。

另一方面,merge() 首先查询数据库,如果状态没有改变则不执行更新。

因此,如果您希望 Hibernate 首先查询数据库,则需要使用 merge() (尽管 update() 的默认行为可以通过指定 @ 来覆盖org.hibernate.annotations.Entity(selectBeforeUpdate = true) 在您的实体上)。

That's the semantical difference between update() and merge().

From Christian Bauer and Gavin King's Java Persistence with Hibernate (I can't find clear explanation of this behaviour in the Hibernate docs):

The update() method forces an update to the persistent state of the object in
the database, always scheduling an SQL UPDATE.
...
It doesn’t matter if the item object is modified before or after it’s passed to
update().
...
Hibernate
always treats the object as dirty and schedules an SQL UPDATE., which will be executed
during flush.

On the other hand, merge() queries the database first, and doesn't perform update if state haven't changed.

So, if you want Hibernate to query the database first, you need to use merge() (though default behaviour of update() can be overriden by specifing @org.hibernate.annotations.Entity(selectBeforeUpdate = true) on your entities).

寄人书 2024-10-19 22:44:43

尝试向您的实体添加修订列(乐观锁定)

@Version
Date lastModified;

try to add a revision column (optimistic locking) to your entities

@Version
Date lastModified;
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文