HHH-17934 sort out version handling in upsert()

Signed-off-by: Gavin King <gavin@hibernate.org>
This commit is contained in:
Gavin King 2024-04-11 13:55:52 +02:00 committed by Christian Beikov
parent ef0cc752c6
commit 909a8b0b4a
3 changed files with 45 additions and 25 deletions

View File

@ -50,11 +50,11 @@ public abstract class SqlAstTranslatorWithMerge<T extends JdbcOperation> extends
); );
} }
@Override // @Override
public void visitOptionalTableUpdate(OptionalTableUpdate tableUpdate) { // public void visitOptionalTableUpdate(OptionalTableUpdate tableUpdate) {
renderMergeStatement(tableUpdate); // renderMergeStatement(tableUpdate);
} // }
//
/** /**
* Renders the OptionalTableUpdate as a MERGE query. * Renders the OptionalTableUpdate as a MERGE query.
* *

View File

@ -31,14 +31,14 @@ import org.hibernate.engine.spi.PersistentAttributeInterceptable;
import org.hibernate.engine.spi.PersistentAttributeInterceptor; import org.hibernate.engine.spi.PersistentAttributeInterceptor;
import org.hibernate.engine.transaction.internal.jta.JtaStatusHelper; import org.hibernate.engine.transaction.internal.jta.JtaStatusHelper;
import org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform; import org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform;
import org.hibernate.generator.BeforeExecutionGenerator;
import org.hibernate.generator.Generator;
import org.hibernate.graph.GraphSemantic; import org.hibernate.graph.GraphSemantic;
import org.hibernate.graph.spi.RootGraphImplementor; import org.hibernate.graph.spi.RootGraphImplementor;
import org.hibernate.loader.ast.spi.CascadingFetchProfile; import org.hibernate.loader.ast.spi.CascadingFetchProfile;
import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.proxy.LazyInitializer; import org.hibernate.proxy.LazyInitializer;
import org.hibernate.generator.Generator;
import org.hibernate.generator.BeforeExecutionGenerator;
import org.hibernate.tuple.entity.EntityMetamodel; import org.hibernate.tuple.entity.EntityMetamodel;
import jakarta.transaction.SystemException; import jakarta.transaction.SystemException;
@ -173,37 +173,50 @@ public class StatelessSessionImpl extends AbstractSharedSessionContract implemen
public void upsert(String entityName, Object entity) { public void upsert(String entityName, Object entity) {
checkOpen(); checkOpen();
final EntityPersister persister = getEntityPersister( entityName, entity ); final EntityPersister persister = getEntityPersister( entityName, entity );
Object id = persister.getIdentifier( entity, this ); final Object id = idToUpsert( entity, persister );
Boolean knownTransient = persister.isTransient( entity, this );
if ( knownTransient!=null && knownTransient ) {
throw new TransientObjectException(
"Object passed to upsert() has a null identifier: "
+ persister.getEntityName() );
// final Generator generator = persister.getGenerator();
// if ( !generator.generatedOnExecution() ) {
// id = ( (BeforeExecutionGenerator) generator).generate( this, entity, null, INSERT );
// }
}
final Object[] state = persister.getValues( entity ); final Object[] state = persister.getValues( entity );
final Object oldVersion; final Object oldVersion = versionToUpsert( entity, persister, state );
persister.merge( id, state, null, false, null, oldVersion, entity, null, this );
}
private Object versionToUpsert(Object entity, EntityPersister persister, Object[] state) {
if ( persister.isVersioned() ) { if ( persister.isVersioned() ) {
oldVersion = persister.getVersion( entity ); final Object oldVersion = persister.getVersion( entity );
if ( oldVersion == null ) { final Boolean knownTransient =
persister.getVersionMapping()
.getUnsavedStrategy()
.isUnsaved( oldVersion );
if ( knownTransient != null && knownTransient ) {
if ( seedVersion( entity, state, persister, this ) ) { if ( seedVersion( entity, state, persister, this ) ) {
persister.setValues( entity, state ); persister.setValues( entity, state );
} }
// this is a nonsense but avoids setting version restriction
// parameter to null later on deep in the guts
return state[persister.getVersionProperty()];
} }
else { else {
final Object newVersion = incrementVersion( entity, oldVersion, persister, this ); final Object newVersion = incrementVersion( entity, oldVersion, persister, this );
setVersion( state, newVersion, persister ); setVersion( state, newVersion, persister );
persister.setValues( entity, state ); persister.setValues( entity, state );
return oldVersion;
} }
} }
else { else {
oldVersion = null; return null;
} }
persister.merge( id, state, null, false, null, oldVersion, entity, null, this ); }
// persister.setIdentifier( entity, id, this );
private Object idToUpsert(Object entity, EntityPersister persister) {
final Object id = persister.getIdentifier( entity, this );
final Boolean unsaved =
persister.getIdentifierMapping()
.getUnsavedStrategy()
.isUnsaved( id );
if ( unsaved != null && unsaved ) {
throw new TransientObjectException( "Object passed to upsert() has an unsaved identifier value: "
+ persister.getEntityName() );
}
return id;
} }

View File

@ -15,7 +15,7 @@ import static org.junit.Assert.assertEquals;
public class UpsertVersionedTest { public class UpsertVersionedTest {
@Test void test(SessionFactoryScope scope) { @Test void test(SessionFactoryScope scope) {
scope.inStatelessTransaction(s-> { scope.inStatelessTransaction(s-> {
s.upsert(new Record(123L,0L,"hello earth")); s.upsert(new Record(123L,null,"hello earth"));
s.upsert(new Record(456L,2L,"hello mars")); s.upsert(new Record(456L,2L,"hello mars"));
}); });
scope.inStatelessTransaction(s-> { scope.inStatelessTransaction(s-> {
@ -29,6 +29,13 @@ public class UpsertVersionedTest {
assertEquals("goodbye earth",s.get(Record.class,123L).message); assertEquals("goodbye earth",s.get(Record.class,123L).message);
assertEquals("hello mars",s.get(Record.class,456L).message); assertEquals("hello mars",s.get(Record.class,456L).message);
}); });
scope.inStatelessTransaction(s-> {
s.upsert(new Record(456L,4L,"goodbye mars"));
});
scope.inStatelessTransaction(s-> {
assertEquals("goodbye earth",s.get(Record.class,123L).message);
assertEquals("goodbye mars",s.get(Record.class,456L).message);
});
} }
@Entity @Entity
static class Record { static class Record {