Skip to content

Commit

Permalink
Merge pull request #31947 from yrodiere/optimizers
Browse files Browse the repository at this point in the history
Allow configuring the default ID optimizer and default to pooled-lo
  • Loading branch information
gsmet authored Mar 20, 2023
2 parents 820ee91 + ee1583d commit 03ccd2e
Show file tree
Hide file tree
Showing 24 changed files with 406 additions and 11 deletions.
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 03ccd2e

Please sign in to comment.