views:

68

answers:

1

Hi all,

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.

+2  A: 

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?

Pascal Thivent
Hi Pascal,thank you very much for your detailed explanation. I appreciate it a lot! 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.
Thomas
Hi Pascal, thanks a lot for the support. What exactly do you mean by "not cascade changes? (if you have such a business requirement, it doesn't sound wrong to handle cascading manually)"
Thomas
@Thomas I meant nothing, this was just a dumb mistake.
Pascal Thivent