]
Chris Wilson commented on HHH-979:
----------------------------------
I would also appreciate having a way to merge an object without losing its identifier.
Currently I have to check whether it's in the database first, and then either save()
or merge() depending on the result.
Please could someone explain why merge() works this way, in more than one word? Thanks.
Merge operation should retain existing identifiers
--------------------------------------------------
Key: HHH-979
URL:
http://opensource.atlassian.com/projects/hibernate/browse/HHH-979
Project: Hibernate Core
Issue Type: Improvement
Components: core
Affects Versions: 3.0.5
Reporter: Aleksei Valikov
Priority: Minor
Original Estimate: 20 minutes
Remaining Estimate: 20 minutes
Currently, merge(...) operation does not retain existing object identifiers.
Here's a demonstrating test case:
public void testIt() throws Exception {
final A a1 = new A();
final B b1 = new B();
a1.setId("A");
a1.setB(b1);
a1.setD("d1");
b1.setId("B");
b1.setC("c1");
save(a1);
final A a2 = new A();
final B b2 = new B();
a2.setId("A");
a2.setB(b2);
a2.setD("d2");
b2.setId("B");
b2.setC("c2");
save(a2);
final A a3 = load("A");
assertEquals(a3.getD(), a2.getD());
assertEquals(a3.getB().getC(), a2.getB().getC());
}
public void save(A a)
{
final Session s = openSession();
final Transaction t = s.beginTransaction();
s.merge(a);
t.commit();
s.close();
}
public A load(String id)
{
final Session s = openSession();
final A a = (A) s.get(A.class, id);
s.close();
return a;
}
load("A") returns null since merged objects have newly generated identifiers.
Moreover, we get two entities persisted in the DB, not one:
INSERT INTO B VALUES('4028e4fc067e203f01067e2042690001','c1')
INSERT INTO A
VALUES('4028e4fc067e203f01067e2042790002','4028e4fc067e203f01067e2042690001','d1')
COMMIT
INSERT INTO B VALUES('4028e4fc067e203f01067e2042e60003','c2')
INSERT INTO A
VALUES('4028e4fc067e203f01067e2042e60004','4028e4fc067e203f01067e2042e60003','d2')
COMMIT
I think a more sensible behaviour is to retain identifiers. This can be achieved with a
one-line code patch.
Before DefaultMergeEventListener.entityIsDetached(...) calls entityIsTransient(), it
should set event.requestedId:
// ....
Serializable id = event.getRequestedId();
if ( id == null ) {
id = persister.getIdentifier( entity, source.getEntityMode() );
}
else {
//TODO: check that entity id = requestedId
}
final Object result = source.get(entityName, id);
if ( result == null ) {
//TODO: we should throw an exception if we really *know* for sure
// that this is a detached instance, rather than just assuming
//throw new StaleObjectStateException(entityName, id);
event.setRequestedId(id);
// we got here because we assumed that an instance
// with an assigned id was detached, when it was
// really persistent
return entityIsTransient(event, copyCache);
}
// ...
This results in following DB operations:
INSERT INTO B VALUES('B','c1')
INSERT INTO A VALUES('A','B','d1')
COMMIT
DELETE FROM B WHERE ID='B'
INSERT INTO B VALUES('B','c2')
DELETE FROM A WHERE ID='A'
INSERT INTO A VALUES('A','B','d2')
COMMIT
At the moment, I've implemented is as my own listener, but would like in any case to
know your opinion.
Anyways, something like MergeMode (analogous to ReplicationMode) would be also nice to
have.
--
This message is automatically generated by JIRA.
-
If you think it was sent incorrectly contact one of the administrators: