如何在 JPA / Hibernate 中映射只读集合而不导致数据库更新

发布于 2024-09-27 06:38:42 字数 2069 浏览 5 评论 0原文

是否可以在 hibernate / jpa 中创建在获取包含实体时获取的关系,但在保存包含实体时永远不会导致任何数据库更新?我将尝试通过一个例子来阐明要求。

我有一个简单的实体 B

@Entity
public class B {

    private int bId;

    @Id
    public int getBId() {
        return bId;
    }

    public void setBId(int aId) {
        bId = aId;
    }
}

和另一个实体 A,其中包含到此类的单向多对多映射。

@Entity
public class A {

    private int aId;
    private List<B> bs;

    @Id
    public int getAId() {
        return aId;
    }

    public void setAId(int aId) {
        this.aId = aId;
    }

    @ManyToMany
    @JoinTable(name = "A_B",
            joinColumns = {@JoinColumn(name = "AID")},
            inverseJoinColumns = {@JoinColumn(name = "BID")}
            )    
    public List<B> getBs() {
        return bs;
    }

    public void setBs(List<B> aBs) {
        bs = aBs;
    }
 }

当从数据库中获取实体 A 并按如下方式合并时

    A a = em.find(A.class, 1);
    a.getBs().size();
    em.merge(a);

,合并会产生以下 SQL 语句,

Hibernate: 
    delete 
    from
        A_B 
    where
        AID=?
Hibernate: 
    insert 
    into
        A_B
        (AID, BID) 
    values
        (?, ?)
Hibernate: 
    insert 
    into
        A_B
        (AID, BID) 
    values
        (?, ?)

我必须避免这些删除+更新。对于我的应用程序,我可以确保永远不会使用 hibernate 更新映射表。无论如何,都需要更新包含的实体。

所以我的问题是:是否可以映射此类“只读”集合并避免数据库更改?

最好的问候

托马斯

更新:

这些是我正在使用的表格和数据:

CREATE TABLE A (
        AID INTEGER NOT NULL
    )
    DATA CAPTURE NONE ;

CREATE TABLE B (
        BID INTEGER NOT NULL
    )
    DATA CAPTURE NONE ;

CREATE TABLE A_B (
        AID INTEGER NOT NULL,
        BID INTEGER NOT NULL
    )
    DATA CAPTURE NONE ;

INSERT INTO A (AID) VALUES (1);

INSERT INTO B (BID) VALUES (1);
INSERT INTO B (BID) VALUES (2);

INSERT INTO A_B (AID, BID) VALUES (1, 1);
INSERT INTO A_B (AID, BID) VALUES (1, 2);

此外,在执行合并之前还需要初始化集合:

a.getBs().size();

注意:我已将上面的行添加到原帖也是。

is it possible to create relations in hibernate / jpa that are fetched when the containing entity is fetched but will never ever result in any db updates, when the containing entity is saved? I'll try to make the requirement clear by an example.

I have a simple entity B

@Entity
public class B {

    private int bId;

    @Id
    public int getBId() {
        return bId;
    }

    public void setBId(int aId) {
        bId = aId;
    }
}

And another entity A, which contains a uni-directional many-to-many mapping to this class.

@Entity
public class A {

    private int aId;
    private List<B> bs;

    @Id
    public int getAId() {
        return aId;
    }

    public void setAId(int aId) {
        this.aId = aId;
    }

    @ManyToMany
    @JoinTable(name = "A_B",
            joinColumns = {@JoinColumn(name = "AID")},
            inverseJoinColumns = {@JoinColumn(name = "BID")}
            )    
    public List<B> getBs() {
        return bs;
    }

    public void setBs(List<B> aBs) {
        bs = aBs;
    }
 }

When entity A is fetched from db and merged afterwards as follows

    A a = em.find(A.class, 1);
    a.getBs().size();
    em.merge(a);

, the merge results in the following SQL statements

Hibernate: 
    delete 
    from
        A_B 
    where
        AID=?
Hibernate: 
    insert 
    into
        A_B
        (AID, BID) 
    values
        (?, ?)
Hibernate: 
    insert 
    into
        A_B
        (AID, BID) 
    values
        (?, ?)

I have to avoid these deletes + updates. For my application I can ensure that the mapping table will never ever be updated using hibernate. Anyway, it is required to update the containing entity.

So my question is: Is it possible to map such "read-only" collections and to avoid db changes?

Best regards

Thomas

Update:

These are the tables and the data I'm using:

CREATE TABLE A (
        AID INTEGER NOT NULL
    )
    DATA CAPTURE NONE ;

CREATE TABLE B (
        BID INTEGER NOT NULL
    )
    DATA CAPTURE NONE ;

CREATE TABLE A_B (
        AID INTEGER NOT NULL,
        BID INTEGER NOT NULL
    )
    DATA CAPTURE NONE ;

INSERT INTO A (AID) VALUES (1);

INSERT INTO B (BID) VALUES (1);
INSERT INTO B (BID) VALUES (2);

INSERT INTO A_B (AID, BID) VALUES (1, 1);
INSERT INTO A_B (AID, BID) VALUES (1, 2);

In addition the collection also needs to be initialized before the merge is performed:

a.getBs().size();

Note: I've added the line from above to the original post, too.

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

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

发布评论

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

评论(1

━╋う一瞬間旳綻放 2024-10-04 06:38:42

正如评论中所写,我最初无法重现该行为。在不改变 B 集合的情况下,Hibernate 只是更新 A,而保持连接表不变。但是,通过更改 B 的集合(例如添加 B),我可以获得先删除后插入的行为。我不知道这是否说明了您的情况,但这是我的解释...

当使用CollectionList没有时@IndexColumn (或 @CollectionId),您将得到 Bag 语义 及其所有缺点:当您删除元素或更改集合时,Hibernate 首先删除所有元素,然后插入(它没有办法维持秩序)。

因此,为了避免这种行为,请使用:

  1. Set语义(即,如果不需要List,则使用Set, 95% 的情况都是如此)。

  2. 带主键的包语义(即使用 List@CollectionId) - 我没有对此进行测试

  3. true 列表语义< /a> (即使用带有 @org.hibernate.annotations.IndexColumnList 或 JPA 2.0 等效的 @OrderColumn(如果您使用的是JPA 2.0)

如果您不需要 List,那么选项 1 是显而易见的选择。如果你这样做,我只测试了选项3(感觉更自然),你可以像这样实现(需要一个额外的列和连接表中(B_ID,BS_ORDER)的唯一约束):

@Entity
public class A {

    private int aId;
    private List<B> bs;

    @Id
    public int getAId() { return aId; }

    public void setAId(int aId) { this.aId = aId; }

    @ManyToMany
    @JoinTable(name = "A_B",
            joinColumns = {@JoinColumn(name = "AID")},
            inverseJoinColumns = {@JoinColumn(name = "BID")}
            )
    @org.hibernate.annotations.IndexColumn(name = "BS_ORDER")
    public List<B> getBs() { return bs; }

    public void setBs(List<B> aBs) { bs = aBs; }
}

并且Hibernate将更新BS_ORDER更新/删除 Bs 时所需的 列。

参考资料


我修改了我的测试代码并切换到 Set 作为 List 的替代品。由于此更改,只要集合保持不变,就不再生成删除和更新语句。不幸的是,我的项目有条件,集合被修改。因此,我要求一个只读语义,其中休眠从数据库加载映射数据但不保存任何更改,这可能已对集合进行。

是的,我知道这就是您所要求的,但是:

  1. 我认为您关心的是删除然后插入,并且您问题的“只读部分”看起来像是真正问题的丑陋解决方法。

  2. “额外”的要求,即不保存已修改的持久集合的状态(这是不寻常的,当你有一个持久集合时,如果你修改它的状态,你通常希望保存它)并不清楚,至少对我来说不是。

无论如何......关于第二点,Hibernate 的 @Immutable 注释不适合这里(它不允许所有修改,如果有的话抛出异常)。但也许您可以处理集合的临时副本,而不是修改持久副本?

As written in a comment, I couldn't initially reproduce the behavior. Without altering the collection of B, Hibernate was just updating A, leaving the join table untouched. However, by altering the collection of Bs (e.g. adding a B), I could get the DELETE then INSERT behavior. I don't know if this illustrates your scenario but here is my explanation...

When using a Collection or List without an @IndexColumn (or a @CollectionId), you get Bag semantic with all their drawbacks: when you remove an element or alter the collection, Hibernate first delete all elements and then insert (it has no way to maintain the order).

So, to avoid this behavior, either use:

  1. Set semantic (i.e. use a Set if you don't need a List, which is true for 95% of the cases).

  2. Bag semantic with primary key (i.e. use a List with a @CollectionId) - I didn't test this.

  3. true List semantic (i.e. use a List with a @org.hibernate.annotations.IndexColumn or the JPA 2.0 equivalent @OrderColumn if you are using JPA 2.0)

The Option 1 is the obvious choice if you don't need a List. If you do, I only tested Option 3 (feels more natural) that you would implement like this (requires an extra column and a unique constraint on (B_ID, BS_ORDER) in the join table):

@Entity
public class A {

    private int aId;
    private List<B> bs;

    @Id
    public int getAId() { return aId; }

    public void setAId(int aId) { this.aId = aId; }

    @ManyToMany
    @JoinTable(name = "A_B",
            joinColumns = {@JoinColumn(name = "AID")},
            inverseJoinColumns = {@JoinColumn(name = "BID")}
            )
    @org.hibernate.annotations.IndexColumn(name = "BS_ORDER")
    public List<B> getBs() { return bs; }

    public void setBs(List<B> aBs) { bs = aBs; }
}

And Hibernate will update the BS_ORDER column as required upon update/removal of Bs.

References


I've modified my test code and switched to Set as a replacement for the List. Due to this change the delete and update statements get no longer generated, as long as the collection remains unmodified. Unfortunately I have conditions in my project, where the collection is modified. Therefore I was asking for a read-only semantic where hibernate loads the mapped data from db but does not save any changes, which might have been done to the collection.

Yes, I know that this is what you were asking for but:

  1. I thought that your concern was the DELETE then INSERT and the "read only part" of your question was looking like an ugly workaround of the real problem.

  2. The "extra" requirement i.e. not saving the state of a persistent collection that has been modified (which is unusual, when you have a persistent collection, you usually want to save it if you modify its state) wasn't clear, at least not for me.

Anyway... Regarding the second point, Hibernate's @Immutable annotation won't fit here (it disallows all modifications, throwing an exception if any). But maybe you could work on a transient copy of the collection instead of modifying the persistent one?

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