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

Tho*_*mas 5 java mapping hibernate jpa readonly

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

我有一个简单的实体 B

@Entity
public class B {

    private int bId;

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

    public void setBId(int aId) {
        bId = aId;
    }
}
Run Code Online (Sandbox Code Playgroud)

另一个实体 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;
    }
 }
Run Code Online (Sandbox Code Playgroud)

当实体 A 从数据库中获取并随后合并时,如下所示

    A a = em.find(A.class, 1);
    a.getBs().size();
    em.merge(a);
Run Code Online (Sandbox Code Playgroud)

,合并结果为以下 SQL 语句

Hibernate: 
    delete 
    from
        A_B 
    where
        AID=?
Hibernate: 
    insert 
    into
        A_B
        (AID, BID) 
    values
        (?, ?)
Hibernate: 
    insert 
    into
        A_B
        (AID, BID) 
    values
        (?, ?)
Run Code Online (Sandbox Code Playgroud)

我必须避免这些删除+更新。对于我的应用程序,我可以确保永远不会使用 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);
Run Code Online (Sandbox Code Playgroud)

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

a.getBs().size();
Run Code Online (Sandbox Code Playgroud)

注意:我也将上面的行添加到原始帖子中。

Pas*_*ent 2

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

当使用 aCollectionList 使用a @IndexColumn(或 a @CollectionId)时,您会得到Bag 语义及其所有缺点:当您删除元素或更改集合时,Hibernate 首先删除所有元素,然后插入(它无法维护顺序)。

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

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

  2. 带主键的包语义(即使用 aList和 a@CollectionId) -我没有测试这个

  3. true列表语义(即,如果您使用的是 JPA 2.0,则使用 aList与 a@org.hibernate.annotations.IndexColumn或 JPA 2.0 等效项)@OrderColumn

如果您不需要List. 如果您这样做,我只测试了选项 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; }
}
Run Code Online (Sandbox Code Playgroud)

Hibernate 将BS_ORDER根据需要在更新/删除Bs.

参考


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

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

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

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

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