HHH-18096 Support for JPA 3.2 database generator options

This commit is contained in:
Andrea Boriero 2024-05-14 12:17:53 +02:00 committed by Steve Ebersole
parent 49964af5a9
commit a76a4a585b
15 changed files with 328 additions and 25 deletions

View File

@ -134,7 +134,7 @@ public class GenerationStrategyInterpreter {
final String options = tableGeneratorAnnotation.getString( "options" );
if ( StringHelper.isNotEmpty( options ) ) {
definitionBuilder.addParam(
org.hibernate.id.enhanced.TableGenerator.TABLE_OPTIONS,
PersistentIdentifierGenerator.OPTIONS,
options
);
}
@ -186,5 +186,10 @@ public class GenerationStrategyInterpreter {
SequenceStyleGenerator.INITIAL_PARAM,
String.valueOf( sequenceGeneratorAnnotation.getInteger( "initialValue" ) )
);
final String options = sequenceGeneratorAnnotation.getString( "options" );
if ( StringHelper.isNotEmpty( options ) ) {
definitionBuilder.addParam( PersistentIdentifierGenerator.OPTIONS, options );
}
}
}

View File

@ -27,22 +27,16 @@ public class Sequence implements ContributableDatabaseObject {
private final QualifiedSequenceName name;
private final String exportIdentifier;
private final String contributor;
private int initialValue = 1;
private int incrementSize = 1;
private final int initialValue;
private final int incrementSize;
private final String options;
public Sequence(
String contributor,
Identifier catalogName,
Identifier schemaName,
Identifier sequenceName) {
this.contributor = contributor;
this.name = new QualifiedSequenceName(
catalogName,
schemaName,
sequenceName
);
this.exportIdentifier = name.render();
this( contributor, catalogName, schemaName, sequenceName, 1, 1, null );
}
public Sequence(
@ -52,9 +46,27 @@ public class Sequence implements ContributableDatabaseObject {
Identifier sequenceName,
int initialValue,
int incrementSize) {
this( contributor, catalogName, schemaName, sequenceName );
this( contributor, catalogName, schemaName, sequenceName, initialValue, incrementSize, null );
}
public Sequence(
String contributor,
Identifier catalogName,
Identifier schemaName,
Identifier sequenceName,
int initialValue,
int incrementSize,
String options) {
this.contributor = contributor;
this.name = new QualifiedSequenceName(
catalogName,
schemaName,
sequenceName
);
this.exportIdentifier = name.render();
this.initialValue = initialValue;
this.incrementSize = incrementSize;
this.options = options;
}
public QualifiedSequenceName getName() {
@ -79,6 +91,10 @@ public class Sequence implements ContributableDatabaseObject {
return incrementSize;
}
public String getOptions() {
return options;
}
public void validate(int initialValue, int incrementSize) {
if ( this.initialValue != initialValue ) {
throw new HibernateException(

View File

@ -29,7 +29,6 @@ import org.hibernate.boot.jaxb.mapping.spi.JaxbPersistentAttribute;
import org.hibernate.boot.jaxb.mapping.spi.JaxbTenantIdImpl;
import org.hibernate.boot.models.HibernateAnnotations;
import org.hibernate.boot.models.JpaAnnotations;
import org.hibernate.boot.models.categorize.spi.JpaEventListenerStyle;
import org.hibernate.boot.models.xml.internal.attr.BasicAttributeProcessing;
import org.hibernate.boot.models.xml.internal.attr.BasicIdAttributeProcessing;
import org.hibernate.boot.models.xml.internal.attr.CommonAttributeProcessing;
@ -55,7 +54,6 @@ import org.hibernate.models.spi.MutableMemberDetails;
import org.hibernate.models.spi.SourceModelBuildingContext;
import org.hibernate.models.spi.TypeDetails;
import org.hibernate.property.access.spi.BuiltInPropertyAccessStrategies;
import org.hibernate.property.access.spi.PropertyAccessStrategy;
import jakarta.persistence.Access;
import jakarta.persistence.AccessType;
@ -596,7 +594,13 @@ public class ManagedTypeProcessor {
jaxbEntity, classDetails, xmlDocumentContext
);
XmlAnnotationHelper.applyTableGenerators( jaxbEntity.getTableGenerators(), classDetails, xmlDocumentContext );
XmlAnnotationHelper.applyTableGenerator( jaxbEntity.getTableGenerators(), classDetails, xmlDocumentContext );
XmlAnnotationHelper.applySequenceGenerator(
jaxbEntity.getSequenceGenerators(),
classDetails,
xmlDocumentContext
);
renderClass( classDetails, xmlDocumentContext );
}

View File

@ -131,7 +131,6 @@ import jakarta.persistence.SecondaryTables;
import jakarta.persistence.SequenceGenerator;
import jakarta.persistence.Table;
import jakarta.persistence.TableGenerator;
import jakarta.persistence.TableGenerators;
import jakarta.persistence.Temporal;
import jakarta.persistence.TemporalType;
import jakarta.persistence.UniqueConstraint;
@ -564,6 +563,29 @@ public class XmlAnnotationHelper {
xmlDocumentContext.getModelBuildingContext()
);
applySequenceGeneratorAttributes( jaxbGenerator, sequenceAnn );
}
public static void applySequenceGenerator(
JaxbSequenceGeneratorImpl jaxbGenerator,
MutableClassDetails classDetails,
XmlDocumentContext xmlDocumentContext) {
if ( jaxbGenerator == null ) {
return;
}
final MutableAnnotationUsage<SequenceGenerator> sequenceAnn = classDetails.replaceAnnotationUsage(
JpaAnnotations.SEQUENCE_GENERATOR,
xmlDocumentContext.getModelBuildingContext()
);
applySequenceGeneratorAttributes( jaxbGenerator, sequenceAnn );
}
private static void applySequenceGeneratorAttributes(
JaxbSequenceGeneratorImpl jaxbGenerator,
MutableAnnotationUsage<SequenceGenerator> sequenceAnn) {
XmlProcessingHelper.applyAttributeIfSpecified( "name", jaxbGenerator.getName(), sequenceAnn );
XmlProcessingHelper.applyAttributeIfSpecified( "sequenceName", jaxbGenerator.getSequenceName(), sequenceAnn );
XmlProcessingHelper.applyAttributeIfSpecified( "catalog", jaxbGenerator.getCatalog(), sequenceAnn );
@ -573,7 +595,7 @@ public class XmlAnnotationHelper {
XmlProcessingHelper.applyAttributeIfSpecified( "options", jaxbGenerator.getOptions(), sequenceAnn );
}
public static void applyTableGenerators(
public static void applyTableGenerator(
JaxbTableGeneratorImpl jaxbGenerator,
MutableClassDetails classDetails,
XmlDocumentContext xmlDocumentContext) {

View File

@ -43,6 +43,12 @@ public class NoSequenceSupport implements SequenceSupport {
throw new MappingException("dialect does not support sequences");
}
@Override
public String[] getCreateSequenceStrings(String sequenceName, int initialValue, int incrementSize, String options)
throws MappingException {
throw new MappingException( "dialect does not support sequences" );
}
@Override
public String[] getCreateSequenceStrings(String sequenceName, int initialValue, int incrementSize) throws MappingException {
throw new MappingException("dialect does not support sequences");

View File

@ -7,6 +7,7 @@
package org.hibernate.dialect.sequence;
import org.hibernate.MappingException;
import org.hibernate.internal.util.StringHelper;
/**
* A set of operations providing support for sequences in a
@ -112,6 +113,26 @@ public interface SequenceSupport {
return getSequenceNextValString( sequenceName );
}
/**
* An optional multi-line form for databases which {@link #supportsPooledSequences()}.
*
* @param sequenceName The name of the sequence
* @param initialValue The initial value to apply to 'create sequence' statement
* @param incrementSize The increment value to apply to 'create sequence' statement
* @param options A SQL fragment appended to the generated DDL.
* @return The sequence creation commands
* @throws MappingException If sequences are not supported.
*/
default String[] getCreateSequenceStrings(String sequenceName, int initialValue, int incrementSize, String options)
throws MappingException {
return new String[] {
StringHelper.isNotEmpty( options ) ?
getCreateSequenceString( sequenceName, initialValue, incrementSize ) + " " + options :
getCreateSequenceString( sequenceName, initialValue, incrementSize ),
};
}
/**
* An optional multi-line form for databases which {@link #supportsPooledSequences()}.
*

View File

@ -55,4 +55,9 @@ public interface PersistentIdentifierGenerator extends OptimizableGenerator {
* The key under which to find the {@link org.hibernate.boot.model.naming.ObjectNameNormalizer} in the config param map.
*/
String IDENTIFIER_NORMALIZER = "identifier_normalizer";
/**
* The configuration parameter holding the generator options.
*/
String OPTIONS = "options";
}

View File

@ -40,6 +40,7 @@ public class SequenceStructure implements DatabaseStructure {
private final int initialValue;
private final int incrementSize;
private final Class numberType;
private final String options;
private String sql;
private boolean applyIncrementSizeToSourceValues;
@ -59,6 +60,24 @@ public class SequenceStructure implements DatabaseStructure {
this.initialValue = initialValue;
this.incrementSize = incrementSize;
this.numberType = numberType;
this.options = null;
}
public SequenceStructure(
JdbcEnvironment jdbcEnvironment,
String contributor,
QualifiedName qualifiedSequenceName,
int initialValue,
int incrementSize,
String options,
Class numberType) {
this.contributor = contributor;
this.logicalQualifiedSequenceName = qualifiedSequenceName;
this.initialValue = initialValue;
this.incrementSize = incrementSize;
this.options = options;
this.numberType = numberType;
}
@Override
@ -188,7 +207,8 @@ public class SequenceStructure implements DatabaseStructure {
namespace.getPhysicalName().getSchema(),
physicalName,
initialValue,
sourceIncrementSize
sourceIncrementSize,
options
)
);
}

View File

@ -162,6 +162,7 @@ public class SequenceStyleGenerator
private DatabaseStructure databaseStructure;
private Optimizer optimizer;
private Type identifierType;
private String options;
/**
* Getter for property 'databaseStructure'.
@ -242,6 +243,8 @@ public class SequenceStyleGenerator
getInt( INITIAL_PARAM, parameters, -1 )
);
this.databaseStructure.configure( optimizer );
this.options = parameters.getProperty( OPTIONS );
}
private int adjustIncrementSize(
@ -531,6 +534,7 @@ public class SequenceStyleGenerator
sequenceName,
initialValue,
incrementSize,
params.getProperty( OPTIONS ),
type.getReturnedClass()
);
}
@ -552,6 +556,7 @@ public class SequenceStyleGenerator
valueColumnName,
initialValue,
incrementSize,
params.getProperty( OPTIONS ),
type.getReturnedClass()
);
}

View File

@ -196,11 +196,6 @@ public class TableGenerator implements PersistentIdentifierGenerator {
*/
public static final int DEF_SEGMENT_LENGTH = 255;
/**
* Configures the options of the table to use.
*/
public static final String TABLE_OPTIONS = "options";
private boolean storeLastUsedValue;
@ -365,7 +360,7 @@ public class TableGenerator implements PersistentIdentifierGenerator {
if ( contributor == null ) {
contributor = "orm";
}
options = parameters.getProperty( TABLE_OPTIONS );
options = parameters.getProperty( OPTIONS );
}
private static OptimizerDescriptor determineOptimizationStrategy(Properties parameters, int incrementSize) {

View File

@ -55,6 +55,7 @@ public class TableStructure implements DatabaseStructure {
private final int initialValue;
private final int incrementSize;
private final Class numberType;
private final String options;
private String contributor;
@ -76,12 +77,34 @@ public class TableStructure implements DatabaseStructure {
int initialValue,
int incrementSize,
Class numberType) {
this(
jdbcEnvironment,
contributor,
qualifiedTableName,
valueColumnNameIdentifier,
initialValue,
incrementSize,
null,
numberType
);
}
public TableStructure(
JdbcEnvironment jdbcEnvironment,
String contributor,
QualifiedName qualifiedTableName,
Identifier valueColumnNameIdentifier,
int initialValue,
int incrementSize,
String options,
Class numberType) {
this.contributor = contributor;
this.logicalQualifiedTableName = qualifiedTableName;
this.logicalValueColumnNameIdentifier = valueColumnNameIdentifier;
this.initialValue = initialValue;
this.incrementSize = incrementSize;
this.options = options;
this.numberType = numberType;
}
@ -292,6 +315,8 @@ public class TableStructure implements DatabaseStructure {
table.addColumn( valueColumn );
table.setOptions( options );
table.addInitCommand( context -> new InitCommand( "insert into "
+ context.format( physicalTableName ) + " values ( " + initialValue + " )" ) );
}

View File

@ -30,7 +30,8 @@ public class StandardSequenceExporter implements Exporter<Sequence> {
return dialect.getSequenceSupport().getCreateSequenceStrings(
getFormattedSequenceName( sequence.getName(), metadata, context ),
sequence.getInitialValue(),
sequence.getIncrementSize()
sequence.getIncrementSize(),
sequence.getOptions()
);
}

View File

@ -0,0 +1,123 @@
package org.hibernate.orm.test.schemaupdate.sequencegenerator;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.EnumSet;
import java.util.Locale;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.boot.spi.MetadataImplementor;
import org.hibernate.dialect.Dialect;
import org.hibernate.tool.hbm2ddl.SchemaExport;
import org.hibernate.tool.schema.TargetType;
import org.hibernate.testing.orm.junit.BaseUnitTest;
import org.hibernate.testing.util.ServiceRegistryUtil;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertTrue;
@BaseUnitTest
public class SequenceGeneratorOptionsTest {
static final String TABLE_NAME = "TEST_ENTITY_TABLE";
static final String SEQUENCE_GENERATOR_NAME = "TEST_SEQUENCE_GENERATOR";
static final String SEQUENCE_GENERATOR_OPTIONS = "option_0";
private File output;
private StandardServiceRegistry ssr;
private MetadataImplementor metadata;
@BeforeEach
public void setUp() throws IOException {
output = File.createTempFile( "update_script", ".sql" );
output.deleteOnExit();
ssr = ServiceRegistryUtil.serviceRegistry();
}
@AfterEach
public void tearsDown() {
output.delete();
StandardServiceRegistryBuilder.destroy( ssr );
}
@Test
public void testSequenceOptionsAreCreated() throws Exception {
createSchema( TestEntity.class );
assertTrue(
tableSequenceStatementContainsOptions( output, SEQUENCE_GENERATOR_NAME, SEQUENCE_GENERATOR_OPTIONS, metadata.getDatabase().getDialect() ),
"Sequence " + SEQUENCE_GENERATOR_NAME + " options has not been created "
);
}
@Test
public void testXmlMappingSequenceOptionsAreCreated() throws Exception {
createSchema( "org/hibernate/orm/test/schemaupdate/sequencegenerator/TestEntity.xml" );
assertTrue(
tableSequenceStatementContainsOptions( output, SEQUENCE_GENERATOR_NAME, SEQUENCE_GENERATOR_OPTIONS, metadata.getDatabase().getDialect() ),
"Sequence " + SEQUENCE_GENERATOR_NAME + " options has not been created "
);
}
private static boolean tableSequenceStatementContainsOptions(
File output,
String sequenceName,
String options,
Dialect dialect) throws Exception {
String[] fileContent = new String( Files.readAllBytes( output.toPath() ) ).toLowerCase()
.split( System.lineSeparator() );
for ( int i = 0; i < fileContent.length; i++ ) {
String statement = fileContent[i].toUpperCase( Locale.ROOT );
if ( dialect.getSequenceSupport().supportsSequences() ) {
if ( statement.contains( "CREATE SEQUENCE " + sequenceName.toUpperCase( Locale.ROOT ) ) ) {
if ( statement.contains( options.toUpperCase( Locale.ROOT ) ) ) {
return true;
}
}
}
else if ( statement.contains( "CREATE TABLE " + sequenceName.toUpperCase( Locale.ROOT ) ) ) {
if ( statement.contains( options.toUpperCase( Locale.ROOT ) ) ) {
return true;
}
}
}
return false;
}
private void createSchema(String... xmlMapping) {
final MetadataSources metadataSources = new MetadataSources( ssr );
for ( String xml : xmlMapping ) {
metadataSources.addResource( xml );
}
metadata = (MetadataImplementor) metadataSources.buildMetadata();
metadata.orderColumns( false );
metadata.validate();
new SchemaExport()
.setHaltOnError( true )
.setOutputFile( output.getAbsolutePath() )
.setFormat( false )
.createOnly( EnumSet.of( TargetType.SCRIPT ), metadata );
}
private void createSchema(Class... annotatedClasses) {
final MetadataSources metadataSources = new MetadataSources( ssr );
for ( Class c : annotatedClasses ) {
metadataSources.addAnnotatedClass( c );
}
metadata = (MetadataImplementor) metadataSources.buildMetadata();
metadata.orderColumns( false );
metadata.validate();
new SchemaExport()
.setHaltOnError( true )
.setOutputFile( output.getAbsolutePath() )
.setFormat( false )
.createOnly( EnumSet.of( TargetType.SCRIPT ), metadata );
}
}

View File

@ -0,0 +1,35 @@
package org.hibernate.orm.test.schemaupdate.sequencegenerator;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.SequenceGenerator;
import jakarta.persistence.Table;
@Entity
@Table(name = SequenceGeneratorOptionsTest.TABLE_NAME)
public class TestEntity {
@Id
@SequenceGenerator(name = "seq_gen", sequenceName = SequenceGeneratorOptionsTest.SEQUENCE_GENERATOR_NAME, options = SequenceGeneratorOptionsTest.SEQUENCE_GENERATOR_OPTIONS)
@GeneratedValue(generator = "seq_gen", strategy = GenerationType.SEQUENCE)
private Long id;
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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>.
-->
<entity-mappings xmlns="http://www.hibernate.org/xsd/orm/mapping" version="3.2">
<package>org.hibernate.orm.test.schemaupdate.sequencegenerator</package>
<entity class="TestEntity" metadata-complete="true">
<table name="TEST_ENTITY_TABLE" />
<sequence-generator name="seq_gen" sequence-name="TEST_SEQUENCE_GENERATOR" options="option_0"/>
<attributes>
<id name="id">
<generated-value strategy="SEQUENCE" generator="seq_gen"/>
</id>
<basic name="name"/>
</attributes>
</entity>
</entity-mappings>