How can read-only collections be mapped in JPA / Hibernate that don't cause DB updates
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.开发者_Python百科
@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.
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:
Set semantic (i.e. use a
Set
if you don't need aList
, which is true for 95% of the cases).Bag semantic with primary key (i.e. use a
List
with a@CollectionId
) - I didn't test this.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
- Hibernate Annotations 3.4 Reference Guide
- 2.2.5.3. Collections
- 2.4.6.2. Extra collection types
- Hibernate Annotations 3.5 Reference Guide
- 2.4.6. Collection related annotations
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:
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.
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?
精彩评论