Skip to content

Commit

Permalink
Allow configuring the default ID optimizer and default to pooled-lo
Browse files Browse the repository at this point in the history
  • Loading branch information
yrodiere committed Mar 17, 2023
1 parent 870a71a commit 5e78877
Show file tree
Hide file tree
Showing 14 changed files with 390 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import java.util.Set;

import org.hibernate.annotations.TimeZoneStorageType;
import org.hibernate.id.enhanced.StandardOptimizerDescriptor;

import io.quarkus.runtime.annotations.ConfigDocMapKey;
import io.quarkus.runtime.annotations.ConfigDocSection;
Expand Down Expand Up @@ -381,8 +382,84 @@ public static class HibernateOrmConfigPersistenceUnitMapping {
@ConfigItem(name = "timezone.default-storage", defaultValueDocumentation = "default")
public Optional<TimeZoneStorageType> timeZoneDefaultStorage;

/**
* The optimizer to apply to identifier generators
* whose optimizer is not configured explicitly.
*
* Only relevant for table- and sequence-based identifier generators.
* Other generators, such as UUID-based generators, will ignore this setting.
*
* The optimizer is responsible for pooling new identifier values,
* in order to reduce the frequency of database calls to retrieve those values
* and thereby improve performance.
*
* @asciidoclet
*/
@ConfigItem(name = "id.optimizer.default", defaultValueDocumentation = "pooled-lo")
// Note this needs to be a build-time property due to
// org.hibernate.boot.internal.InFlightMetadataCollectorImpl.handleIdentifierValueBinding
// which may call (indirectly) org.hibernate.id.enhanced.SequenceStructure.buildSequence
// whose output depends on org.hibernate.id.enhanced.SequenceStructure.applyIncrementSizeToSourceValues
// which is determined by the optimizer.
public Optional<IdOptimizerType> idOptimizerDefault;

public boolean isAnyPropertySet() {
return timeZoneDefaultStorage.isPresent();
return timeZoneDefaultStorage.isPresent()
|| idOptimizerDefault.isPresent();
}

}

public enum IdOptimizerType {
/**
* Assumes the value retrieved from the table/sequence is the lower end of the pool.
*
* Upon retrieving value `N`, the new pool of identifiers will go from `N` to `N + <allocation size> - 1`, inclusive.
* `pooled`::
* Assumes the value retrieved from the table/sequence is the higher end of the pool.
* +
* Upon retrieving value `N`, the new pool of identifiers will go from `N - <allocation size>` to `N + <allocation size>
* - 1`, inclusive.
* +
* The first value, `1`, is handled differently to avoid negative identifiers.
* +
* Use this to get the legacy behavior of Quarkus 2 / Hibernate ORM 5 or older.
* `none`::
* No optimizer, resulting in a database call each and every time an identifier value is needed from the generator.
* +
* Not recommended in production environments:
* may result in degraded performance and/or frequent gaps in identifier values.
*
* @asciidoclet
*/
POOLED_LO(StandardOptimizerDescriptor.POOLED_LO),
/**
* Assumes the value retrieved from the table/sequence is the higher end of the pool.
*
* Upon retrieving value `N`, the new pool of identifiers will go from `N - <allocation size>` to `N + <allocation size>
* - 1`, inclusive.
*
* The first value, `1`, is handled differently to avoid negative identifiers.
*
* Use this to get the legacy behavior of Quarkus 2 / Hibernate ORM 5 or older.
*
* @asciidoclet
*/
POOLED(StandardOptimizerDescriptor.POOLED),
/**
* No optimizer, resulting in a database call each and every time an identifier value is needed from the generator.
*
* Not recommended in production environments:
* may result in degraded performance and/or frequent gaps in identifier values.
*
* @asciidoclet
*/
NONE(StandardOptimizerDescriptor.NONE);

public final String configName;

IdOptimizerType(StandardOptimizerDescriptor delegate) {
configName = delegate.getExternalName();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1022,6 +1022,9 @@ private static void producePersistenceUnitDescriptorFromConfig(
descriptor.getProperties().setProperty(AvailableSettings.TIMEZONE_DEFAULT_STORAGE,
persistenceUnitConfig.mapping.timeZoneDefaultStorage.get().name());
}
descriptor.getProperties().setProperty(AvailableSettings.PREFERRED_POOLED_OPTIMIZER,
persistenceUnitConfig.mapping.idOptimizerDefault
.orElse(HibernateOrmConfigPersistenceUnit.IdOptimizerType.POOLED_LO).configName);

//charset
descriptor.getProperties().setProperty(AvailableSettings.HBM2DDL_CHARSET_NAME,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import jakarta.persistence.EntityManagerFactory;

import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.generator.Generator;
import org.hibernate.metamodel.MappingMetamodel;
import org.hibernate.metamodel.mapping.SelectableConsumer;
import org.hibernate.metamodel.mapping.SelectableMapping;
Expand Down Expand Up @@ -50,4 +51,11 @@ public void accept(int selectionIndex, SelectableMapping selectableMapping) {
entityDescriptor.forEachSelectable(columnFinder);
return columnFinder.found.getJdbcMapping().getJdbcType().getFriendlyName();
}

public static Generator getGenerator(EntityManagerFactory entityManagerFactory, Class<?> entityType) {
MappingMetamodel domainModel = entityManagerFactory
.unwrap(SessionFactoryImplementor.class).getRuntimeMetamodels().getMappingMetamodel();
EntityPersister entityDescriptor = domainModel.findEntityDescriptor(entityType);
return entityDescriptor.getGenerator();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package io.quarkus.hibernate.orm.mapping.id.optimizer;

import static org.assertj.core.api.Assertions.assertThat;

import java.util.List;

import jakarta.inject.Inject;

import org.assertj.core.api.AbstractObjectAssert;
import org.assertj.core.api.InstanceOfAssertFactories;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.id.OptimizableGenerator;
import org.hibernate.id.enhanced.Optimizer;
import org.hibernate.id.enhanced.PooledLoOptimizer;
import org.hibernate.id.enhanced.PooledOptimizer;
import org.junit.jupiter.api.Test;

import io.quarkus.hibernate.orm.SchemaUtil;
import io.quarkus.narayana.jta.QuarkusTransaction;

public abstract class AbstractIdOptimizerDefaultTest {

@Inject
SessionFactory sessionFactory;

@Inject
Session session;

abstract Class<?> defaultOptimizerType();

@Test
public void defaults() {
assertThat(List.of(
EntityWithDefaultGenerator.class,
EntityWithGenericGenerator.class,
EntityWithSequenceGenerator.class,
EntityWithTableGenerator.class))
.allSatisfy(c -> assertOptimizer(c).isInstanceOf(defaultOptimizerType()));
}

@Test
public void explicitOverrides() {
assertOptimizer(EntityWithGenericGeneratorAndPooledOptimizer.class)
.isInstanceOf(PooledOptimizer.class);
assertOptimizer(EntityWithGenericGeneratorAndPooledLoOptimizer.class)
.isInstanceOf(PooledLoOptimizer.class);
}

@Test
public void ids() {
for (long i = 1; i <= 51; i++) {
assertThat(QuarkusTransaction.requiringNew().call(() -> {
var entity = new EntityWithSequenceGenerator();
session.persist(entity);
return entity.id;
}))
.isEqualTo(i);
}
}

AbstractObjectAssert<?, Optimizer> assertOptimizer(Class<?> entityType) {
return assertThat(SchemaUtil.getGenerator(sessionFactory, entityType))
.as("ID generator for entity type " + entityType.getSimpleName())
.asInstanceOf(InstanceOfAssertFactories.type(OptimizableGenerator.class))
.extracting(OptimizableGenerator::getOptimizer)
.as("ID optimizer for entity type " + entityType.getSimpleName());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package io.quarkus.hibernate.orm.mapping.id.optimizer;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;

@Entity
public class EntityWithDefaultGenerator {

@Id
@GeneratedValue
Long id;

public EntityWithDefaultGenerator() {
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package io.quarkus.hibernate.orm.mapping.id.optimizer;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;

import org.hibernate.annotations.GenericGenerator;
import org.hibernate.id.enhanced.SequenceStyleGenerator;

@Entity
public class EntityWithGenericGenerator {

@Id
@GeneratedValue(generator = "gen_gen")
@GenericGenerator(name = "gen_gen", type = SequenceStyleGenerator.class)
Long id;

public EntityWithGenericGenerator() {
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package io.quarkus.hibernate.orm.mapping.id.optimizer;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;

import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.Parameter;
import org.hibernate.id.OptimizableGenerator;
import org.hibernate.id.enhanced.SequenceStyleGenerator;

@Entity
public class EntityWithGenericGeneratorAndPooledLoOptimizer {

@Id
@GeneratedValue(generator = "gen_gen_pooled_lo")
@GenericGenerator(name = "gen_gen_pooled_lo", type = SequenceStyleGenerator.class, parameters = @Parameter(name = OptimizableGenerator.OPT_PARAM, value = "pooled-lo"))
Long id;

public EntityWithGenericGeneratorAndPooledLoOptimizer() {
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package io.quarkus.hibernate.orm.mapping.id.optimizer;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;

import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.Parameter;
import org.hibernate.id.OptimizableGenerator;
import org.hibernate.id.enhanced.SequenceStyleGenerator;

@Entity
public class EntityWithGenericGeneratorAndPooledOptimizer {

@Id
@GeneratedValue(generator = "gen_gen_pooled_lo")
@GenericGenerator(name = "gen_gen_pooled_lo", type = SequenceStyleGenerator.class, parameters = @Parameter(name = OptimizableGenerator.OPT_PARAM, value = "pooled"))
Long id;

public EntityWithGenericGeneratorAndPooledOptimizer() {
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package io.quarkus.hibernate.orm.mapping.id.optimizer;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.SequenceGenerator;

@Entity
public class EntityWithSequenceGenerator {

@Id
@GeneratedValue(generator = "seq_gen")
@SequenceGenerator(name = "seq_gen")
Long id;

public EntityWithSequenceGenerator() {
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package io.quarkus.hibernate.orm.mapping.id.optimizer;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.TableGenerator;

@Entity
public class EntityWithTableGenerator {

@Id
@GeneratedValue(generator = "tab_gen")
@TableGenerator(name = "tab_gen")
Long id;

public EntityWithTableGenerator() {
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package io.quarkus.hibernate.orm.mapping.id.optimizer;

import org.hibernate.id.enhanced.PooledLoOptimizer;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.hibernate.orm.SchemaUtil;
import io.quarkus.test.QuarkusUnitTest;

public class IdOptimizerDefaultDefaultTest extends AbstractIdOptimizerDefaultTest {

@RegisterExtension
static QuarkusUnitTest TEST = new QuarkusUnitTest()
.withApplicationRoot((jar) -> jar
.addClasses(EntityWithDefaultGenerator.class, EntityWithGenericGenerator.class,
EntityWithSequenceGenerator.class, EntityWithTableGenerator.class,
EntityWithGenericGeneratorAndPooledOptimizer.class,
EntityWithGenericGeneratorAndPooledLoOptimizer.class)
.addClasses(SchemaUtil.class))
.withConfigurationResource("application.properties");

@Override
Class<?> defaultOptimizerType() {
return PooledLoOptimizer.class;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package io.quarkus.hibernate.orm.mapping.id.optimizer;

import org.hibernate.id.enhanced.NoopOptimizer;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.hibernate.orm.SchemaUtil;
import io.quarkus.test.QuarkusUnitTest;

public class IdOptimizerDefaultNoneTest extends AbstractIdOptimizerDefaultTest {

@RegisterExtension
static QuarkusUnitTest TEST = new QuarkusUnitTest()
.withApplicationRoot((jar) -> jar
.addClasses(EntityWithDefaultGenerator.class, EntityWithGenericGenerator.class,
EntityWithSequenceGenerator.class, EntityWithTableGenerator.class,
EntityWithGenericGeneratorAndPooledOptimizer.class,
EntityWithGenericGeneratorAndPooledLoOptimizer.class)
.addClasses(SchemaUtil.class))
.withConfigurationResource("application.properties")
.overrideConfigKey("quarkus.hibernate-orm.mapping.id.optimizer.default", "none");

@Override
@Disabled("The 'none' optimizer will produce a different stream of IDs (1 then 51 then 101 then ...)")
public void ids() {
super.ids();
}

@Override
Class<?> defaultOptimizerType() {
return NoopOptimizer.class;
}
}
Loading

0 comments on commit 5e78877

Please sign in to comment.