HHH-16218 Natural id cache is extremely slow for entities with compound natural id
This commit is contained in:
parent
c5897db954
commit
40f22e482f
|
@ -69,8 +69,16 @@ public class DefaultCacheKeysFactory implements CacheKeysFactory {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Object staticCreateNaturalIdKey(Object naturalIdValues, EntityPersister persister, SharedSessionContractImplementor session) {
|
public static Object staticCreateNaturalIdKey(
|
||||||
return new NaturalIdCacheKey( naturalIdValues, persister, session );
|
Object naturalIdValues,
|
||||||
|
EntityPersister persister,
|
||||||
|
SharedSessionContractImplementor session) {
|
||||||
|
NaturalIdCacheKey.NaturalIdCacheKeyBuilder builder = new NaturalIdCacheKey.NaturalIdCacheKeyBuilder(
|
||||||
|
naturalIdValues,
|
||||||
|
persister,
|
||||||
|
session
|
||||||
|
);
|
||||||
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Object staticGetEntityId(Object cacheKey) {
|
public static Object staticGetEntityId(Object cacheKey) {
|
||||||
|
|
|
@ -9,9 +9,12 @@ package org.hibernate.cache.internal;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.ObjectInputStream;
|
import java.io.ObjectInputStream;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import org.hibernate.Internal;
|
import org.hibernate.Internal;
|
||||||
|
import org.hibernate.cache.MutableCacheKeyBuilder;
|
||||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||||
import org.hibernate.internal.util.ValueHolder;
|
import org.hibernate.internal.util.ValueHolder;
|
||||||
import org.hibernate.persister.entity.EntityPersister;
|
import org.hibernate.persister.entity.EntityPersister;
|
||||||
|
@ -33,12 +36,51 @@ public class NaturalIdCacheKey implements Serializable {
|
||||||
// "transient" is important here -- NaturalIdCacheKey needs to be Serializable
|
// "transient" is important here -- NaturalIdCacheKey needs to be Serializable
|
||||||
private transient ValueHolder<String> toString;
|
private transient ValueHolder<String> toString;
|
||||||
|
|
||||||
public NaturalIdCacheKey(Object naturalIdValues, EntityPersister persister, SharedSessionContractImplementor session) {
|
public static class NaturalIdCacheKeyBuilder implements MutableCacheKeyBuilder {
|
||||||
this( naturalIdValues, persister, persister.getRootEntityName(), session );
|
|
||||||
}
|
|
||||||
|
|
||||||
public NaturalIdCacheKey(Object naturalIdValues, EntityPersister persister, String entityName, SharedSessionContractImplementor session) {
|
private final String entityName;
|
||||||
this( persister.getNaturalIdMapping().disassemble( naturalIdValues, session ), entityName, session.getTenantIdentifier(), persister.getNaturalIdMapping().calculateHashCode( naturalIdValues, session ) );
|
private final String tenantIdentifier;
|
||||||
|
|
||||||
|
private final List<Object> values;
|
||||||
|
private int hashCode;
|
||||||
|
|
||||||
|
public NaturalIdCacheKeyBuilder(
|
||||||
|
Object naturalIdValues,
|
||||||
|
EntityPersister persister,
|
||||||
|
String entityName,
|
||||||
|
SharedSessionContractImplementor session) {
|
||||||
|
this.entityName = entityName;
|
||||||
|
this.tenantIdentifier = session.getTenantIdentifier();
|
||||||
|
values = new ArrayList<>();
|
||||||
|
persister.getNaturalIdMapping().addToCacheKey( this, naturalIdValues, session );
|
||||||
|
}
|
||||||
|
|
||||||
|
public NaturalIdCacheKeyBuilder(
|
||||||
|
Object naturalIdValues,
|
||||||
|
EntityPersister persister,
|
||||||
|
SharedSessionContractImplementor session) {
|
||||||
|
this( naturalIdValues, persister, persister.getRootEntityName(), session );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addValue(Object value) {
|
||||||
|
values.add( value );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addHashCode(int hashCode) {
|
||||||
|
this.hashCode = 37 * this.hashCode + hashCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NaturalIdCacheKey build() {
|
||||||
|
return new NaturalIdCacheKey(
|
||||||
|
values.toArray( new Object[0] ),
|
||||||
|
entityName,
|
||||||
|
tenantIdentifier,
|
||||||
|
hashCode
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Internal
|
@Internal
|
||||||
|
|
|
@ -32,9 +32,18 @@ public class SimpleCacheKeysFactory implements CacheKeysFactory {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object createNaturalIdKey(Object naturalIdValues, EntityPersister persister, SharedSessionContractImplementor session) {
|
public Object createNaturalIdKey(
|
||||||
|
Object naturalIdValues,
|
||||||
|
EntityPersister persister,
|
||||||
|
SharedSessionContractImplementor session) {
|
||||||
// natural ids always need to be wrapped
|
// natural ids always need to be wrapped
|
||||||
return new NaturalIdCacheKey(naturalIdValues, persister, null, session);
|
NaturalIdCacheKey.NaturalIdCacheKeyBuilder builder = new NaturalIdCacheKey.NaturalIdCacheKeyBuilder(
|
||||||
|
naturalIdValues,
|
||||||
|
persister,
|
||||||
|
null,
|
||||||
|
session
|
||||||
|
);
|
||||||
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -770,7 +770,7 @@ public class NaturalIdResolutionsImpl implements NaturalIdResolutions, Serializa
|
||||||
final int prime = 31;
|
final int prime = 31;
|
||||||
int hashCodeCalculation = 1;
|
int hashCodeCalculation = 1;
|
||||||
hashCodeCalculation = prime * hashCodeCalculation + entityDescriptor.hashCode();
|
hashCodeCalculation = prime * hashCodeCalculation + entityDescriptor.hashCode();
|
||||||
hashCodeCalculation = prime * hashCodeCalculation + entityDescriptor.getNaturalIdMapping().calculateHashCode( naturalIdValue, persistenceContext.getSession() );
|
hashCodeCalculation = prime * hashCodeCalculation + entityDescriptor.getNaturalIdMapping().calculateHashCode( naturalIdValue );
|
||||||
|
|
||||||
this.hashCode = hashCodeCalculation;
|
this.hashCode = hashCodeCalculation;
|
||||||
}
|
}
|
||||||
|
|
|
@ -112,7 +112,7 @@ public interface NaturalIdMapping extends VirtualModelPart {
|
||||||
* @param value The natural-id value
|
* @param value The natural-id value
|
||||||
* @return The hash-code
|
* @return The hash-code
|
||||||
*/
|
*/
|
||||||
int calculateHashCode(Object value, SharedSessionContractImplementor session);
|
int calculateHashCode(Object value);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Make a loader capable of loading a single entity by natural-id
|
* Make a loader capable of loading a single entity by natural-id
|
||||||
|
|
|
@ -172,9 +172,19 @@ public class CompoundNaturalIdMapping extends AbstractNaturalIdMapping implement
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int calculateHashCode(Object value, SharedSessionContractImplementor session) {
|
public int calculateHashCode(Object value) {
|
||||||
Object o = disassemble( value, session ) ;
|
if ( value == null ) {
|
||||||
return Arrays.hashCode((Object[]) o);
|
return 0;
|
||||||
|
}
|
||||||
|
Object[] values = (Object[]) value;
|
||||||
|
int hashcode = 0;
|
||||||
|
for ( int i = 0; i < attributes.size(); i++ ) {
|
||||||
|
final Object o = values[i];
|
||||||
|
if ( o != null ) {
|
||||||
|
hashcode = 27 * hashcode + ( (JavaType) attributes.get( i ).getExpressibleJavaType() ).extractHashCode( o );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return hashcode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -136,7 +136,7 @@ public class SimpleNaturalIdMapping extends AbstractNaturalIdMapping implements
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int calculateHashCode(Object value, SharedSessionContractImplementor session) {
|
public int calculateHashCode(Object value) {
|
||||||
//noinspection unchecked
|
//noinspection unchecked
|
||||||
return value == null ? 0 : ( (JavaType<Object>) getJavaType() ).extractHashCode( value );
|
return value == null ? 0 : ( (JavaType<Object>) getJavaType() ).extractHashCode( value );
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,7 +46,7 @@ public class NaturalIdCacheKeyTest {
|
||||||
when( entityPersister.getRootEntityName() ).thenReturn( "EntityName" );
|
when( entityPersister.getRootEntityName() ).thenReturn( "EntityName" );
|
||||||
when( entityPersister.getNaturalIdMapping() ).thenReturn( naturalIdMapping );
|
when( entityPersister.getNaturalIdMapping() ).thenReturn( naturalIdMapping );
|
||||||
when( naturalIdMapping.disassemble( any(), eq( sessionImplementor ) ) ).thenAnswer( invocation -> invocation.getArguments()[0] );
|
when( naturalIdMapping.disassemble( any(), eq( sessionImplementor ) ) ).thenAnswer( invocation -> invocation.getArguments()[0] );
|
||||||
when( naturalIdMapping.calculateHashCode( any(), eq( sessionImplementor ) ) ).thenAnswer( invocation -> invocation.getArguments()[0].hashCode() );
|
when( naturalIdMapping.calculateHashCode( any() ) ).thenAnswer( invocation -> invocation.getArguments()[0].hashCode() );
|
||||||
|
|
||||||
|
|
||||||
final NaturalIdCacheKey key = (NaturalIdCacheKey) DefaultCacheKeysFactory.staticCreateNaturalIdKey( new Object[] {"a", "b", "c"}, entityPersister, sessionImplementor );
|
final NaturalIdCacheKey key = (NaturalIdCacheKey) DefaultCacheKeysFactory.staticCreateNaturalIdKey( new Object[] {"a", "b", "c"}, entityPersister, sessionImplementor );
|
||||||
|
|
|
@ -6,181 +6,164 @@
|
||||||
*/
|
*/
|
||||||
package org.hibernate.orm.test.mapping.naturalid.compound;
|
package org.hibernate.orm.test.mapping.naturalid.compound;
|
||||||
|
|
||||||
|
import org.hibernate.annotations.NaturalId;
|
||||||
|
import org.hibernate.annotations.NaturalIdCache;
|
||||||
|
import org.hibernate.cfg.AvailableSettings;
|
||||||
|
import org.hibernate.engine.internal.NaturalIdResolutionsImpl;
|
||||||
|
import org.hibernate.stat.NaturalIdStatistics;
|
||||||
|
import org.hibernate.stat.spi.StatisticsImplementor;
|
||||||
|
|
||||||
|
import org.hibernate.testing.TestForIssue;
|
||||||
|
import org.hibernate.testing.orm.junit.DomainModel;
|
||||||
|
import org.hibernate.testing.orm.junit.ServiceRegistry;
|
||||||
|
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||||
|
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||||
|
import org.hibernate.testing.orm.junit.Setting;
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import jakarta.persistence.Entity;
|
import jakarta.persistence.Entity;
|
||||||
import jakarta.persistence.GeneratedValue;
|
import jakarta.persistence.GeneratedValue;
|
||||||
import jakarta.persistence.Id;
|
import jakarta.persistence.Id;
|
||||||
import org.hibernate.Session;
|
|
||||||
import org.hibernate.Transaction;
|
|
||||||
import org.hibernate.annotations.NaturalId;
|
|
||||||
import org.hibernate.cfg.AvailableSettings;
|
|
||||||
import org.hibernate.cfg.Configuration;
|
|
||||||
import org.hibernate.query.criteria.HibernateCriteriaBuilder;
|
|
||||||
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
|
|
||||||
import org.hibernate.testing.TestForIssue;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.jupiter.api.Timeout;
|
|
||||||
|
|
||||||
import java.util.function.Function;
|
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
|
||||||
|
import static org.hibernate.testing.cache.CachingRegionFactory.DEFAULT_ACCESSTYPE;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Sylvain Dusart
|
* @author Sylvain Dusart
|
||||||
*/
|
*/
|
||||||
@TestForIssue(jiraKey = "HHH-16218")
|
@TestForIssue(jiraKey = "HHH-16218")
|
||||||
public class CompoundNaturalIdCacheTest extends BaseCoreFunctionalTestCase {
|
@DomainModel(
|
||||||
|
annotatedClasses = {
|
||||||
|
CompoundNaturalIdCacheTest.EntityWithSimpleNaturalId.class,
|
||||||
|
CompoundNaturalIdCacheTest.EntityWithCompoundNaturalId.class
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@ServiceRegistry(
|
||||||
|
settings = {
|
||||||
|
@Setting(name = DEFAULT_ACCESSTYPE, value = "nonstrict-read-write"),
|
||||||
|
@Setting(name = AvailableSettings.USE_QUERY_CACHE, value = "true"),
|
||||||
|
@Setting(name = AvailableSettings.SHOW_SQL, value = "false"),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@SessionFactory(generateStatistics = true)
|
||||||
|
public class CompoundNaturalIdCacheTest {
|
||||||
|
|
||||||
@Override
|
private static final int OBJECT_NUMBER = 3;
|
||||||
protected Class[] getAnnotatedClasses() {
|
|
||||||
return new Class[]{
|
|
||||||
EntityWithSimpleNaturalId.class,
|
|
||||||
EntityWithCompoundNaturalId.class
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@BeforeAll
|
||||||
protected void configure(Configuration configuration) {
|
public void setUp(SessionFactoryScope scope) {
|
||||||
super.configure(configuration);
|
scope.inTransaction(
|
||||||
|
session -> {
|
||||||
|
for ( int i = 0; i < OBJECT_NUMBER; i++ ) {
|
||||||
|
EntityWithCompoundNaturalId compoundNaturalIdEntity = new EntityWithCompoundNaturalId();
|
||||||
|
final String str = String.valueOf( i );
|
||||||
|
compoundNaturalIdEntity.setFirstname( str );
|
||||||
|
compoundNaturalIdEntity.setLastname( str );
|
||||||
|
session.persist( compoundNaturalIdEntity );
|
||||||
|
|
||||||
configuration.setProperty(AvailableSettings.STATEMENT_BATCH_SIZE, "5000");
|
EntityWithSimpleNaturalId withSimpleNaturalIdEntity = new EntityWithSimpleNaturalId();
|
||||||
configuration.setProperty( AvailableSettings.SHOW_SQL, Boolean.FALSE.toString() );
|
withSimpleNaturalIdEntity.setName( str );
|
||||||
configuration.setProperty(AvailableSettings.GENERATE_STATISTICS, "false");
|
session.persist( withSimpleNaturalIdEntity );
|
||||||
}
|
NaturalIdResolutionsImpl naturalIdResolutions = (NaturalIdResolutionsImpl) session.getPersistenceContext()
|
||||||
|
.getNaturalIdResolutions();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Timeout(30)
|
public void createThenLoadTest(SessionFactoryScope scope) {
|
||||||
public void createThenLoadTest() {
|
StatisticsImplementor statistics = scope.getSessionFactory().getStatistics();
|
||||||
Session s = openSession();
|
statistics.clear();
|
||||||
Transaction tx = s.beginTransaction();
|
NaturalIdStatistics naturalIdStatistics = statistics.getNaturalIdStatistics( EntityWithCompoundNaturalId.class.getName() );
|
||||||
|
|
||||||
int objectsNb = 20000;
|
loadEntityWithCompoundNaturalId( "0", "0", scope );
|
||||||
|
assertThat( naturalIdStatistics.getCacheHitCount() ).isEqualTo( 1 );
|
||||||
|
|
||||||
log.info("Starting creations");
|
loadEntityWithCompoundNaturalId( "1", "1", scope );
|
||||||
|
assertThat( naturalIdStatistics.getCacheHitCount() ).isEqualTo( 2 );
|
||||||
|
|
||||||
var creationDurationForCompoundNaturalId = createEntities(i -> {
|
loadEntityWithCompoundNaturalId( "2", "2", scope );
|
||||||
var entity = new EntityWithCompoundNaturalId();
|
assertThat( naturalIdStatistics.getCacheHitCount() ).isEqualTo( 3 );
|
||||||
final var str = String.valueOf(i);
|
|
||||||
entity.setFirstname(str);
|
|
||||||
entity.setLastname(str);
|
|
||||||
return entity;
|
|
||||||
}, objectsNb);
|
|
||||||
|
|
||||||
log.info("Persisted " + objectsNb + ' ' + EntityWithCompoundNaturalId.class.getSimpleName() +
|
}
|
||||||
" objects, duration=" + creationDurationForCompoundNaturalId + "ms");
|
|
||||||
|
|
||||||
var creationDurationForSimpleNaturalId = createEntities(i -> {
|
private void loadEntityWithCompoundNaturalId(String firstname, String lastname, SessionFactoryScope scope) {
|
||||||
var entity = new EntityWithSimpleNaturalId();
|
scope.inSession(
|
||||||
entity.setName(String.valueOf(i));
|
session -> {
|
||||||
return entity;
|
session.byNaturalId( EntityWithCompoundNaturalId.class )
|
||||||
}, objectsNb);
|
.using( "firstname", firstname )
|
||||||
|
.using( "lastname", lastname )
|
||||||
|
.load();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
log.info("Persisted " + objectsNb + ' ' + EntityWithSimpleNaturalId.class.getSimpleName() +
|
@Entity(name = "EntityWithSimpleNaturalId")
|
||||||
" objects, duration=" + creationDurationForSimpleNaturalId + "ms");
|
@NaturalIdCache
|
||||||
|
public static class EntityWithSimpleNaturalId {
|
||||||
|
|
||||||
tx.commit();
|
@Id
|
||||||
s.close();
|
@GeneratedValue
|
||||||
|
private Long id;
|
||||||
|
|
||||||
int maxResults = 20000;
|
@NaturalId
|
||||||
var loadDurationForCompoundNaturalId = loadEntities(EntityWithCompoundNaturalId.class, maxResults);
|
private String name;
|
||||||
var loadDurationForSimpleNaturalId = loadEntities(EntityWithSimpleNaturalId.class, maxResults);
|
|
||||||
|
|
||||||
s.close();
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
assertTrue(loadDurationForCompoundNaturalId <= 5 * loadDurationForSimpleNaturalId,
|
public void setId(final Long id) {
|
||||||
"it should not be soo long to load entities with compound naturalId");
|
this.id = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
private long createEntities(final Function<Integer, Object> creator, final int objectsNb) {
|
public String getName() {
|
||||||
var start = System.currentTimeMillis();
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
for (int i = 0; i < objectsNb; i++) {
|
public void setName(final String name) {
|
||||||
session.persist(creator.apply(i));
|
this.name = name;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return System.currentTimeMillis() - start;
|
@Entity(name = "EntityWithCompoundNaturalId")
|
||||||
}
|
@NaturalIdCache
|
||||||
|
public static class EntityWithCompoundNaturalId {
|
||||||
|
|
||||||
private long loadEntities(final Class<?> clazz, final int maxResults) {
|
@Id
|
||||||
var s = openSession();
|
@GeneratedValue
|
||||||
|
private Long id;
|
||||||
|
|
||||||
var start = System.currentTimeMillis();
|
@NaturalId
|
||||||
log.info("Loading at most " + maxResults + " instances of " + clazz);
|
private String firstname;
|
||||||
|
|
||||||
final HibernateCriteriaBuilder cb = s.getCriteriaBuilder();
|
@NaturalId
|
||||||
final var query = cb.createQuery(clazz);
|
private String lastname;
|
||||||
query.from(clazz);
|
|
||||||
var objects = s.createQuery(query).setMaxResults(maxResults).list();
|
|
||||||
|
|
||||||
var duration = System.currentTimeMillis() - start;
|
public Long getId() {
|
||||||
log.info("Loaded " + objects.size() + " instances of " + clazz + ", duration=" + duration + "ms");
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
s.close();
|
public void setId(final Long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
return duration;
|
public String getFirstname() {
|
||||||
}
|
return firstname;
|
||||||
|
}
|
||||||
|
|
||||||
@Entity
|
public void setFirstname(final String firstname) {
|
||||||
public static class EntityWithSimpleNaturalId {
|
this.firstname = firstname;
|
||||||
|
}
|
||||||
|
|
||||||
@Id
|
public String getLastname() {
|
||||||
@GeneratedValue
|
return lastname;
|
||||||
private Long id;
|
}
|
||||||
|
|
||||||
@NaturalId
|
public void setLastname(final String lastname) {
|
||||||
private String name;
|
this.lastname = lastname;
|
||||||
|
}
|
||||||
public Long getId() {
|
}
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setId(final Long id) {
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getName() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setName(final String name) {
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
public static class EntityWithCompoundNaturalId {
|
|
||||||
|
|
||||||
@Id
|
|
||||||
@GeneratedValue
|
|
||||||
private Long id;
|
|
||||||
|
|
||||||
@NaturalId
|
|
||||||
private String firstname;
|
|
||||||
|
|
||||||
@NaturalId
|
|
||||||
private String lastname;
|
|
||||||
|
|
||||||
public Long getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setId(final Long id) {
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getFirstname() {
|
|
||||||
return firstname;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFirstname(final String firstname) {
|
|
||||||
this.firstname = firstname;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getLastname() {
|
|
||||||
return lastname;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLastname(final String lastname) {
|
|
||||||
this.lastname = lastname;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,161 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
|
||||||
|
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
|
||||||
|
*/
|
||||||
|
package org.hibernate.orm.test.mapping.naturalid.compound;
|
||||||
|
|
||||||
|
import org.hibernate.annotations.NaturalId;
|
||||||
|
import org.hibernate.cfg.AvailableSettings;
|
||||||
|
import org.hibernate.engine.spi.NaturalIdResolutions;
|
||||||
|
import org.hibernate.query.criteria.HibernateCriteriaBuilder;
|
||||||
|
import org.hibernate.query.criteria.JpaCriteriaQuery;
|
||||||
|
import org.hibernate.query.criteria.JpaRoot;
|
||||||
|
|
||||||
|
import org.hibernate.testing.TestForIssue;
|
||||||
|
import org.hibernate.testing.orm.junit.DomainModel;
|
||||||
|
import org.hibernate.testing.orm.junit.ServiceRegistry;
|
||||||
|
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||||
|
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||||
|
import org.hibernate.testing.orm.junit.Setting;
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.GeneratedValue;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Sylvain Dusart
|
||||||
|
*/
|
||||||
|
@TestForIssue(jiraKey = "HHH-16218")
|
||||||
|
@DomainModel(
|
||||||
|
annotatedClasses = {
|
||||||
|
CompoundNaturalIdTest.EntityWithSimpleNaturalId.class,
|
||||||
|
CompoundNaturalIdTest.EntityWithCompoundNaturalId.class
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@ServiceRegistry(
|
||||||
|
settings = {
|
||||||
|
@Setting(name = AvailableSettings.STATEMENT_BATCH_SIZE, value = "500"),
|
||||||
|
@Setting(name = AvailableSettings.SHOW_SQL, value = "false"),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@SessionFactory
|
||||||
|
public class CompoundNaturalIdTest {
|
||||||
|
|
||||||
|
private static final int OBJECT_NUMBER = 2000;
|
||||||
|
private static final int MAX_RESULTS = 2000;
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
public void setUp(SessionFactoryScope scope) {
|
||||||
|
scope.inTransaction(
|
||||||
|
session -> {
|
||||||
|
for ( int i = 0; i < OBJECT_NUMBER; i++ ) {
|
||||||
|
EntityWithCompoundNaturalId compoundNaturalIdEntity = new EntityWithCompoundNaturalId();
|
||||||
|
final String str = String.valueOf( i );
|
||||||
|
compoundNaturalIdEntity.setFirstname( str );
|
||||||
|
compoundNaturalIdEntity.setLastname( str );
|
||||||
|
session.persist( compoundNaturalIdEntity );
|
||||||
|
|
||||||
|
EntityWithSimpleNaturalId withSimpleNaturalIdEntity = new EntityWithSimpleNaturalId();
|
||||||
|
withSimpleNaturalIdEntity.setName( str );
|
||||||
|
session.persist( withSimpleNaturalIdEntity );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void createThenLoadTest(SessionFactoryScope scope) {
|
||||||
|
long loadDurationForCompoundNaturalId = loadEntities( EntityWithCompoundNaturalId.class, MAX_RESULTS, scope );
|
||||||
|
long loadDurationForSimpleNaturalId = loadEntities( EntityWithSimpleNaturalId.class, MAX_RESULTS, scope );
|
||||||
|
|
||||||
|
assertTrue(
|
||||||
|
loadDurationForCompoundNaturalId <= 5 * loadDurationForSimpleNaturalId,
|
||||||
|
"it should not be soo long to load entities with compound naturalId"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private long loadEntities(final Class<?> clazz, final int maxResults, SessionFactoryScope scope) {
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
|
scope.inSession(
|
||||||
|
session -> {
|
||||||
|
final HibernateCriteriaBuilder cb = session.getCriteriaBuilder();
|
||||||
|
JpaCriteriaQuery<?> query = cb.createQuery( clazz );
|
||||||
|
query.from( clazz );
|
||||||
|
session.createQuery( query ).setMaxResults( maxResults ).list();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
long duration = System.currentTimeMillis() - start;
|
||||||
|
return duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity(name = "EntityWithSimpleNaturalId")
|
||||||
|
public static class EntityWithSimpleNaturalId {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@NaturalId
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(final Long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(final String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity(name = "EntityWithCompoundNaturalId")
|
||||||
|
public static class EntityWithCompoundNaturalId {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@NaturalId
|
||||||
|
private String firstname;
|
||||||
|
|
||||||
|
@NaturalId
|
||||||
|
private String lastname;
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(final Long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFirstname() {
|
||||||
|
return firstname;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFirstname(final String firstname) {
|
||||||
|
this.firstname = firstname;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLastname() {
|
||||||
|
return lastname;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLastname(final String lastname) {
|
||||||
|
this.lastname = lastname;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue