HHH-18487 align behavior of UnsavedValueFactory with semantics of persist()
Signed-off-by: Gavin King <gavin@hibernate.org>
This commit is contained in:
parent
318e4e6c5d
commit
a87e08b2e2
|
@ -21,6 +21,8 @@ import org.hibernate.type.descriptor.java.JavaType;
|
||||||
import org.hibernate.type.descriptor.java.VersionJavaType;
|
import org.hibernate.type.descriptor.java.VersionJavaType;
|
||||||
import org.hibernate.type.descriptor.java.spi.PrimitiveJavaType;
|
import org.hibernate.type.descriptor.java.spi.PrimitiveJavaType;
|
||||||
|
|
||||||
|
import static org.hibernate.engine.internal.Versioning.isNullInitialVersion;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper for dealing with unsaved value handling
|
* Helper for dealing with unsaved value handling
|
||||||
*
|
*
|
||||||
|
@ -81,25 +83,20 @@ public class UnsavedValueFactory {
|
||||||
public static <T> VersionValue getUnsavedVersionValue(
|
public static <T> VersionValue getUnsavedVersionValue(
|
||||||
KeyValue bootVersionMapping,
|
KeyValue bootVersionMapping,
|
||||||
VersionJavaType<T> jtd,
|
VersionJavaType<T> jtd,
|
||||||
Long length,
|
|
||||||
Integer precision,
|
|
||||||
Integer scale,
|
|
||||||
Getter getter,
|
Getter getter,
|
||||||
Supplier<?> templateInstanceAccess,
|
Supplier<?> templateInstanceAccess) {
|
||||||
SessionFactoryImplementor sessionFactory) {
|
|
||||||
final String unsavedValue = bootVersionMapping.getNullValue();
|
final String unsavedValue = bootVersionMapping.getNullValue();
|
||||||
if ( unsavedValue == null ) {
|
if ( unsavedValue == null ) {
|
||||||
if ( getter != null && templateInstanceAccess != null ) {
|
if ( getter != null && templateInstanceAccess != null ) {
|
||||||
Object templateInstance = templateInstanceAccess.get();
|
final Object templateInstance = templateInstanceAccess.get();
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
final T defaultValue = (T) getter.get( templateInstance );
|
final T defaultValue = (T) getter.get( templateInstance );
|
||||||
|
// if the version of a newly instantiated object is null
|
||||||
// if the version of a newly instantiated object is not the same
|
// or a negative number, use that value as the unsaved-value,
|
||||||
// as the version seed value, use that as the unsaved-value
|
// otherwise assume it's the initial version set by program
|
||||||
final T seedValue = jtd.seed( length, precision, scale, mockSession( sessionFactory ) );
|
return isNullInitialVersion( defaultValue )
|
||||||
return jtd.areEqual( seedValue, defaultValue )
|
? new VersionValue( defaultValue )
|
||||||
? VersionValue.UNDEFINED
|
: VersionValue.UNDEFINED;
|
||||||
: new VersionValue( defaultValue );
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return VersionValue.UNDEFINED;
|
return VersionValue.UNDEFINED;
|
||||||
|
|
|
@ -54,7 +54,7 @@ public class EntityVersionMappingImpl implements EntityVersionMapping, FetchOpti
|
||||||
private final Integer scale;
|
private final Integer scale;
|
||||||
private final Integer temporalPrecision;
|
private final Integer temporalPrecision;
|
||||||
|
|
||||||
private final BasicType versionBasicType;
|
private final BasicType<?> versionBasicType;
|
||||||
|
|
||||||
private final VersionValue unsavedValueStrategy;
|
private final VersionValue unsavedValueStrategy;
|
||||||
|
|
||||||
|
@ -90,15 +90,10 @@ public class EntityVersionMappingImpl implements EntityVersionMapping, FetchOpti
|
||||||
unsavedValueStrategy = UnsavedValueFactory.getUnsavedVersionValue(
|
unsavedValueStrategy = UnsavedValueFactory.getUnsavedVersionValue(
|
||||||
(KeyValue) bootEntityDescriptor.getVersion().getValue(),
|
(KeyValue) bootEntityDescriptor.getVersion().getValue(),
|
||||||
(VersionJavaType<?>) versionBasicType.getJavaTypeDescriptor(),
|
(VersionJavaType<?>) versionBasicType.getJavaTypeDescriptor(),
|
||||||
length,
|
declaringType.getRepresentationStrategy()
|
||||||
precision,
|
|
||||||
scale,
|
|
||||||
declaringType
|
|
||||||
.getRepresentationStrategy()
|
|
||||||
.resolvePropertyAccess( bootEntityDescriptor.getVersion() )
|
.resolvePropertyAccess( bootEntityDescriptor.getVersion() )
|
||||||
.getGetter(),
|
.getGetter(),
|
||||||
templateInstanceAccess,
|
templateInstanceAccess
|
||||||
creationProcess.getCreationContext().getSessionFactory()
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,10 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||||
public interface VersionJavaType<T> extends JavaType<T> {
|
public interface VersionJavaType<T> extends JavaType<T> {
|
||||||
/**
|
/**
|
||||||
* Generate an initial version.
|
* Generate an initial version.
|
||||||
|
* <p>
|
||||||
|
* Note that this operation is only used when the program sets a null or negative
|
||||||
|
* number as the value of the entity version field. It is not called when the
|
||||||
|
* program sets the version field to a sensible-looking version.
|
||||||
*
|
*
|
||||||
* @param length The length of the type
|
* @param length The length of the type
|
||||||
* @param precision The precision of the type
|
* @param precision The precision of the type
|
||||||
|
|
|
@ -0,0 +1,71 @@
|
||||||
|
package org.hibernate.orm.test.ops;
|
||||||
|
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.GeneratedValue;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.Version;
|
||||||
|
import org.hibernate.testing.orm.junit.EntityManagerFactoryScope;
|
||||||
|
import org.hibernate.testing.orm.junit.Jpa;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
@Jpa(annotatedClasses = {MergeExplicitInitialVersionTest.E.class,
|
||||||
|
MergeExplicitInitialVersionTest.F.class,
|
||||||
|
MergeExplicitInitialVersionTest.G.class})
|
||||||
|
public class MergeExplicitInitialVersionTest {
|
||||||
|
@Test public void testGeneratedId(EntityManagerFactoryScope scope) {
|
||||||
|
E e = new E();
|
||||||
|
scope.inTransaction(s->s.persist(e));
|
||||||
|
assertEquals(e.version, 1);
|
||||||
|
e.text = "hello";
|
||||||
|
E e2 = scope.fromTransaction(s->s.merge(e));
|
||||||
|
assertEquals(e2.version, 2);
|
||||||
|
}
|
||||||
|
@Test public void testAssignedId(EntityManagerFactoryScope scope) {
|
||||||
|
F f = new F();
|
||||||
|
scope.inTransaction(s->s.persist(f));
|
||||||
|
assertEquals(f.version, 1);
|
||||||
|
f.text = "hello";
|
||||||
|
F f2 = scope.fromTransaction(s->s.merge(f));
|
||||||
|
assertEquals(f2.version, 2);
|
||||||
|
}
|
||||||
|
@Test public void testNegativeVersion(EntityManagerFactoryScope scope) {
|
||||||
|
G g = new G();
|
||||||
|
scope.inTransaction(s->s.persist(g));
|
||||||
|
assertEquals(g.version, 0);
|
||||||
|
g.text = "hello";
|
||||||
|
G g2 = scope.fromTransaction(s->s.merge(g));
|
||||||
|
assertEquals(g2.version, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
static class E {
|
||||||
|
@Id
|
||||||
|
@GeneratedValue
|
||||||
|
long id;
|
||||||
|
@Version
|
||||||
|
int version = 1;
|
||||||
|
String text;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
static class F {
|
||||||
|
@Id
|
||||||
|
long id = 5;
|
||||||
|
@Version
|
||||||
|
int version = 1;
|
||||||
|
String text;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
static class G {
|
||||||
|
@Id
|
||||||
|
@GeneratedValue
|
||||||
|
long id;
|
||||||
|
@Version
|
||||||
|
int version = -1;
|
||||||
|
String text;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue