On my entity (myGroup : Group
) I have a collection property (members : HashMap<Long, GroupMember>
) and I want to allow the user to add multiple new (unpersisted) GroupMember
s to the myGroup.members
map.
The reason why I am using a hashmap for the collection property is to ensure the use of the collection is performant (i.e. must be able to get a GroupMember
by its ID quickly and multiple times during execution of the business logic).
The reason why I don't want to persist them straight away is to support reversion of the additions. The user must be able to manipulate the myGroup.members
map, adding and removing members until satisfied, before persisting (saving the entities in the Hibernate session and flushing).
The problem is that new GroupMember
s must be assigned an ID of zero for Hibernate to detect that they're unsaved.(*) Because the members are in a map, keyed by ID, that means only one new member can be added prior to either a save or revert. The addition of a second new member with ID zero simply overrides (the reference to) the first.
My question is how can I best adapt my code solution to support the addition of multiple unsaved entities to the collection (myGroup.members
) while keeping the performance advantage of using a hashmap or other map/dictionary?
Note: I have already tried a hack whereby a ID is derived from unique combination of data in a GroupMember
instance and then, just before a save, the ID is set to zero in order for Hibernate to detect it needs to be persisted, but it is inelegant and I haven't found a way for it to work reliably.
* I don't fully understand Hibernate's behaviour in this regard.
Abbreviated code for the Group
entity below:
@Entity
@Table (name="GROUP")
public class Group
{
private Map<Long,GroupMember> members = new HashMap<Long,GroupMember>();
@OneToMany(fetch = FetchType.LAZY, mappedBy = "ownerGroup", cascade = { CascadeType.ALL })
@MapKey(name="id")
public Map<Long,GroupMember> getMembers() { return this.members; }
public void setMembers(Map<Long,GroupMember> members) { this.members = members; }
// ...other properties and methods...
}
I am a bit confused on what you are going to achieve. Members that are not persisted do not have any ID. What do you expect the "lookup" behavior for these members as you are using ID as the key? It don't seems reasonable for me that you can lookup new members by ID, as they do not have any identity yet.
If lookup new members is not required, I will suggest you:
If lookup of new members is REQUIRED, and from your passage, it seems that you are capable to generate a proven unique key yourself, will you consider NOT having hibernate to generate the ID for you? You have the option in Hibernate to decide whether the ID is generated by Hibernate (as a surrogate key) and provided by you (kind of a natural key). I believe a self-provided ID is more suitable in your case, as you seems not treating the ID as a surrogate key.
Edited: with respect to the comment from the OP about the reason for unable to store it separately, it can be easily solved in this approach (and I think it makes more sense too):
(pusedo code)
class Group {
private List<Member> members; // mapped in hibernate
private List<Member> unsavedMembers; // unmapped
public Map<Long, Member> getProposedMemberMap() {
// returned the combined "view" members and unsavedMembers
}
@PreUpdate // you may consider running it it pre-update too
public void commit() {
members.addAll(unsavedMembers);
unsavedMembers.clear();
}
public void revert() {
unsavedMembers.clear();
}
}
By doing so, you have a view of the "proposed members" for you to iterate, while storing the actual and unsaved members separately.