HHH-16990 Add support for more hibernate-specific features to XML mappings

- org.hibernate.annotations.Type
- org.hibernate.annotations.JdbcTypeCode
- org.hibernate.annotations.UuidGenerator
This commit is contained in:
marko-bekhta 2023-07-25 11:08:32 +02:00 committed by Christian Beikov
parent 7edb7984a8
commit ae8b3f9a33
7 changed files with 254 additions and 3 deletions

View File

@ -0,0 +1,24 @@
/*
* 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.boot.jaxb.mapping.marshall;
import java.util.Locale;
import org.hibernate.annotations.UuidGenerator;
/**
* JAXB marshalling for {@link UuidGenerator.Style}
*/
public class UuidGeneratorStyleMarshalling {
public static UuidGenerator.Style fromXml(String name) {
return name == null ? null : UuidGenerator.Style.valueOf( name.toUpperCase( Locale.ROOT ) );
}
public static String toXml(UuidGenerator.Style style) {
return style == null ? null : style.name().toLowerCase( Locale.ROOT );
}
}

View File

@ -28,8 +28,12 @@ import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy; import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.Cascade; import org.hibernate.annotations.Cascade;
import org.hibernate.annotations.Columns; import org.hibernate.annotations.Columns;
import org.hibernate.annotations.JdbcTypeCode;
import org.hibernate.annotations.ManyToAny; import org.hibernate.annotations.ManyToAny;
import org.hibernate.annotations.Parameter;
import org.hibernate.annotations.Subselect; import org.hibernate.annotations.Subselect;
import org.hibernate.annotations.Type;
import org.hibernate.annotations.UuidGenerator;
import org.hibernate.annotations.common.annotationfactory.AnnotationDescriptor; import org.hibernate.annotations.common.annotationfactory.AnnotationDescriptor;
import org.hibernate.annotations.common.annotationfactory.AnnotationFactory; import org.hibernate.annotations.common.annotationfactory.AnnotationFactory;
import org.hibernate.annotations.common.reflection.AnnotationReader; import org.hibernate.annotations.common.reflection.AnnotationReader;
@ -46,6 +50,8 @@ import org.hibernate.boot.jaxb.mapping.JaxbCascadeType;
import org.hibernate.boot.jaxb.mapping.JaxbCollectionTable; import org.hibernate.boot.jaxb.mapping.JaxbCollectionTable;
import org.hibernate.boot.jaxb.mapping.JaxbColumn; import org.hibernate.boot.jaxb.mapping.JaxbColumn;
import org.hibernate.boot.jaxb.mapping.JaxbColumnResult; import org.hibernate.boot.jaxb.mapping.JaxbColumnResult;
import org.hibernate.boot.jaxb.mapping.JaxbColumnType;
import org.hibernate.boot.jaxb.mapping.JaxbConfigurationParameter;
import org.hibernate.boot.jaxb.mapping.JaxbConstructorResult; import org.hibernate.boot.jaxb.mapping.JaxbConstructorResult;
import org.hibernate.boot.jaxb.mapping.JaxbConvert; import org.hibernate.boot.jaxb.mapping.JaxbConvert;
import org.hibernate.boot.jaxb.mapping.JaxbDiscriminatorColumn; import org.hibernate.boot.jaxb.mapping.JaxbDiscriminatorColumn;
@ -93,6 +99,7 @@ import org.hibernate.boot.jaxb.mapping.JaxbSynchronizedTable;
import org.hibernate.boot.jaxb.mapping.JaxbTable; import org.hibernate.boot.jaxb.mapping.JaxbTable;
import org.hibernate.boot.jaxb.mapping.JaxbTableGenerator; import org.hibernate.boot.jaxb.mapping.JaxbTableGenerator;
import org.hibernate.boot.jaxb.mapping.JaxbUniqueConstraint; import org.hibernate.boot.jaxb.mapping.JaxbUniqueConstraint;
import org.hibernate.boot.jaxb.mapping.JaxbUuidGenerator;
import org.hibernate.boot.jaxb.mapping.JaxbVersion; import org.hibernate.boot.jaxb.mapping.JaxbVersion;
import org.hibernate.boot.jaxb.mapping.LifecycleCallbackContainer; import org.hibernate.boot.jaxb.mapping.LifecycleCallbackContainer;
import org.hibernate.boot.jaxb.mapping.ManagedType; import org.hibernate.boot.jaxb.mapping.ManagedType;
@ -255,6 +262,7 @@ public class JPAXMLOverriddenAnnotationReader implements AnnotationReader {
annotationToXml.put( ExcludeDefaultListeners.class, "exclude-default-listeners" ); annotationToXml.put( ExcludeDefaultListeners.class, "exclude-default-listeners" );
annotationToXml.put( ExcludeSuperclassListeners.class, "exclude-superclass-listeners" ); annotationToXml.put( ExcludeSuperclassListeners.class, "exclude-superclass-listeners" );
// annotationToXml.put( AccessType.class, "access" ); // annotationToXml.put( AccessType.class, "access" );
// FIXME: adding same annotation as a key multiple times:
annotationToXml.put( AttributeOverride.class, "attribute-override" ); annotationToXml.put( AttributeOverride.class, "attribute-override" );
annotationToXml.put( AttributeOverrides.class, "attribute-override" ); annotationToXml.put( AttributeOverrides.class, "attribute-override" );
annotationToXml.put( AttributeOverride.class, "association-override" ); annotationToXml.put( AttributeOverride.class, "association-override" );
@ -306,7 +314,9 @@ public class JPAXMLOverriddenAnnotationReader implements AnnotationReader {
annotationToXml.put( Convert.class, "convert" ); annotationToXml.put( Convert.class, "convert" );
annotationToXml.put( Converts.class, "convert" ); annotationToXml.put( Converts.class, "convert" );
annotationToXml.put( ConstructorResult.class, "constructor-result" ); annotationToXml.put( ConstructorResult.class, "constructor-result" );
annotationToXml.put( Type.class, "type" );
annotationToXml.put( JdbcTypeCode.class, "jdbc-type-code" );
annotationToXml.put( UuidGenerator.class, "uuid-generator" );
} }
private final XMLContext xmlContext; private final XMLContext xmlContext;
@ -1592,6 +1602,8 @@ public class JPAXMLOverriddenAnnotationReader implements AnnotationReader {
getFetchType( basic, element.getFetch() ); getFetchType( basic, element.getFetch() );
copyAttribute( basic, "optional", element.isOptional(), false ); copyAttribute( basic, "optional", element.isOptional(), false );
annotationList.add( AnnotationFactory.create( basic ) ); annotationList.add( AnnotationFactory.create( basic ) );
getType( annotationList, element.getType() );
getJdbcTypeCode( annotationList, element.getJdbcTypeCode() );
} }
if ( elementsForProperty.isEmpty() && defaults.canUseJavaAnnotations() ) { if ( elementsForProperty.isEmpty() && defaults.canUseJavaAnnotations() ) {
//no annotation presence constraint, basic is the default //no annotation presence constraint, basic is the default
@ -1618,6 +1630,40 @@ public class JPAXMLOverriddenAnnotationReader implements AnnotationReader {
} }
} }
private void getType(List<Annotation> annotationList, JaxbColumnType type) {
if ( type != null ) {
AnnotationDescriptor ad = new AnnotationDescriptor( Type.class );
ad.setValue( "value", classLoaderAccess.classForName( type.getValue() ) );
Parameter[] parameters = new Parameter[type.getParameters().size()];
for ( int i = 0; i < parameters.length; i++ ) {
JaxbConfigurationParameter parameter = type.getParameters().get( i );
AnnotationDescriptor param = new AnnotationDescriptor( Parameter.class );
param.setValue( "name", parameter.getName() );
param.setValue( "value", parameter.getValue() );
parameters[i] = AnnotationFactory.create( param );
}
ad.setValue( "parameters", parameters );
annotationList.add( AnnotationFactory.create( ad ) );
}
}
private void getUuidGenerator(List<Annotation> annotationList, JaxbUuidGenerator uuidGenerator) {
if ( uuidGenerator != null ) {
AnnotationDescriptor ad = new AnnotationDescriptor( UuidGenerator.class );
ad.setValue( "style", uuidGenerator.getStyle() );
annotationList.add( AnnotationFactory.create( ad ) );
}
}
private void getJdbcTypeCode(List<Annotation> annotationList, Integer jdbcTypeCode) {
if ( jdbcTypeCode != null ) {
AnnotationDescriptor ad = new AnnotationDescriptor( JdbcTypeCode.class );
ad.setValue( "value", jdbcTypeCode );
annotationList.add( AnnotationFactory.create( ad ) );
}
}
private void getEnumerated(List<Annotation> annotationList, EnumType enumType) { private void getEnumerated(List<Annotation> annotationList, EnumType enumType) {
if ( enumType != null ) { if ( enumType != null ) {
AnnotationDescriptor ad = new AnnotationDescriptor( Enumerated.class ); AnnotationDescriptor ad = new AnnotationDescriptor( Enumerated.class );
@ -1710,6 +1756,8 @@ public class JPAXMLOverriddenAnnotationReader implements AnnotationReader {
AnnotationDescriptor id = new AnnotationDescriptor( Id.class ); AnnotationDescriptor id = new AnnotationDescriptor( Id.class );
annotationList.add( AnnotationFactory.create( id ) ); annotationList.add( AnnotationFactory.create( id ) );
getAccessType( annotationList, element.getAccess() ); getAccessType( annotationList, element.getAccess() );
getType( annotationList, element.getType() );
getUuidGenerator( annotationList, element.getUuidGenerator() );
} }
} }
if ( elementsForProperty.isEmpty() && defaults.canUseJavaAnnotations() ) { if ( elementsForProperty.isEmpty() && defaults.canUseJavaAnnotations() ) {

View File

@ -8,6 +8,7 @@ package org.hibernate.boot.model.internal;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -1304,7 +1305,7 @@ public class PropertyBinder {
final XProperty idProperty = inferredData.getProperty(); final XProperty idProperty = inferredData.getProperty();
final List<Annotation> idGeneratorAnnotations = findContainingAnnotations( idProperty, IdGeneratorType.class ); final List<Annotation> idGeneratorAnnotations = findContainingAnnotations( idProperty, IdGeneratorType.class );
final List<Annotation> generatorAnnotations = findContainingAnnotations( idProperty, ValueGenerationType.class ); final List<Annotation> generatorAnnotations = findContainingAnnotations( idProperty, ValueGenerationType.class );
generatorAnnotations.removeAll( idGeneratorAnnotations ); removeIdGenerators( generatorAnnotations, idGeneratorAnnotations );
if ( idGeneratorAnnotations.size() + generatorAnnotations.size() > 1 ) { if ( idGeneratorAnnotations.size() + generatorAnnotations.size() > 1 ) {
throw new AnnotationException( "Property '"+ getPath( propertyHolder, inferredData ) throw new AnnotationException( "Property '"+ getPath( propertyHolder, inferredData )
+ "' has too many generator annotations " + combine( idGeneratorAnnotations, generatorAnnotations ) ); + "' has too many generator annotations " + combine( idGeneratorAnnotations, generatorAnnotations ) );
@ -1330,4 +1331,20 @@ public class PropertyBinder {
} }
} }
} }
// Since these collections may contain Proxies created by common-annotations module we cannot reliably use simple remove/removeAll
// collection methods as those proxies do not implement hashcode/equals and even a simple `a.equals(a)` will return `false`.
// Instead, we will check the annotation types, since generator annotations should not be "repeatable" we should have only
// at most one annotation for a generator:
private static void removeIdGenerators(List<Annotation> generatorAnnotations, List<Annotation> idGeneratorAnnotations) {
for ( Annotation id : idGeneratorAnnotations ) {
Iterator<Annotation> iterator = generatorAnnotations.iterator();
while ( iterator.hasNext() ) {
Annotation gen = iterator.next();
if ( gen.annotationType().equals( id.annotationType() ) ) {
iterator.remove();
}
}
}
}
} }

View File

@ -519,7 +519,6 @@
<xsd:annotation> <xsd:annotation>
<xsd:documentation> <xsd:documentation>
See `@org.hibernate.annotations.TenantId` See `@org.hibernate.annotations.TenantId`
See `@org.hibernate.annotations.TenantId`
</xsd:documentation> </xsd:documentation>
</xsd:annotation> </xsd:annotation>
<xsd:complexContent> <xsd:complexContent>
@ -598,6 +597,8 @@
See `@org.hibernate.annotations.Nationalized` See `@org.hibernate.annotations.Nationalized`
See `@org.hibernate.annotations.OptimisticLock` See `@org.hibernate.annotations.OptimisticLock`
See `@org.hibernate.annotations.AttributeAccessor` See `@org.hibernate.annotations.AttributeAccessor`
See `@org.hibernate.annotations.Type`
See `@org.hibernate.annotations.JdbcTypeCode`
</xsd:documentation> </xsd:documentation>
</xsd:annotation> </xsd:annotation>
<xsd:sequence> <xsd:sequence>
@ -614,6 +615,7 @@
</xsd:choice> </xsd:choice>
<xsd:element name="generated" minOccurs="0" type="orm:basic-generation-timing-type"/> <xsd:element name="generated" minOccurs="0" type="orm:basic-generation-timing-type"/>
<xsd:element name="type" type="orm:column-type" minOccurs="0"/>
</xsd:sequence> </xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required"/> <xsd:attribute name="name" type="xsd:string" use="required"/>
@ -623,6 +625,7 @@
<!-- hbm: Hibernate's pluggable accessor spi --> <!-- hbm: Hibernate's pluggable accessor spi -->
<xsd:attribute name="attribute-accessor" type="xsd:string" /> <xsd:attribute name="attribute-accessor" type="xsd:string" />
<xsd:attribute name="optimistic-lock" type="xsd:boolean" default="true" /> <xsd:attribute name="optimistic-lock" type="xsd:boolean" default="true" />
<xsd:attribute name="jdbc-type-code" type="xsd:int" />
</xsd:complexType> </xsd:complexType>
@ -1058,6 +1061,9 @@
<xsd:documentation> <xsd:documentation>
See `@jakarta.persistence.Id` See `@jakarta.persistence.Id`
See `@org.hibernate.annotations.AttributeAccessor` See `@org.hibernate.annotations.AttributeAccessor`
See `@org.hibernate.annotations.Type`
See `@org.hibernate.annotations.JdbcTypeCode`
See `@org.hibernate.annotations.UuidGenerator`
</xsd:documentation> </xsd:documentation>
</xsd:annotation> </xsd:annotation>
<xsd:sequence> <xsd:sequence>
@ -1068,12 +1074,15 @@
<xsd:element name="table-generator" type="orm:table-generator" minOccurs="0"/> <xsd:element name="table-generator" type="orm:table-generator" minOccurs="0"/>
<xsd:element name="sequence-generator" type="orm:sequence-generator" minOccurs="0"/> <xsd:element name="sequence-generator" type="orm:sequence-generator" minOccurs="0"/>
<xsd:element name="uuid-generator" type="orm:uuid-generator" minOccurs="0"/>
<xsd:element name="unsaved-value" type="xsd:string" minOccurs="0"/> <xsd:element name="unsaved-value" type="xsd:string" minOccurs="0"/>
<xsd:element name="type" type="orm:column-type" minOccurs="0"/>
</xsd:sequence> </xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required"/> <xsd:attribute name="name" type="xsd:string" use="required"/>
<xsd:attribute name="access" type="orm:access-type"/> <xsd:attribute name="access" type="orm:access-type"/>
<xsd:attribute name="attribute-accessor" type="xsd:string" /> <xsd:attribute name="attribute-accessor" type="xsd:string" />
<xsd:attribute name="jdbc-type-code" type="xsd:int" />
</xsd:complexType> </xsd:complexType>
<!-- **************************************************** --> <!-- **************************************************** -->
@ -2815,4 +2824,42 @@
</xsd:restriction> </xsd:restriction>
</xsd:simpleType> </xsd:simpleType>
<!-- **************************************************** -->
<xsd:complexType name="column-type">
<xsd:annotation>
<xsd:documentation>
See `@org.hibernate.annotations.Type`
</xsd:documentation>
</xsd:annotation>
<xsd:sequence>
<xsd:element name="parameters" minOccurs="0" maxOccurs="unbounded" type="configuration-parameter"/>
</xsd:sequence>
<xsd:attribute name="value" type="xsd:string" use="required"/>
</xsd:complexType>
<!-- **************************************************** -->
<xsd:simpleType name="uuid-generator-style">
<xsd:annotation>
<xsd:documentation>
org.hibernate.annotations.UuidGenerator.Style enum values
</xsd:documentation>
</xsd:annotation>
<xsd:restriction base="xsd:token">
<xsd:enumeration value="auto"/>
<xsd:enumeration value="random"/>
<xsd:enumeration value="time"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="uuid-generator">
<xsd:annotation>
<xsd:documentation>
See `@org.hibernate.annotations.UuidGenerator`
</xsd:documentation>
</xsd:annotation>
<xsd:attribute name="style" type="orm:uuid-generator-style" use="required"/>
</xsd:complexType>
</xsd:schema> </xsd:schema>

View File

@ -423,6 +423,12 @@
printMethod="org.hibernate.boot.jaxb.mapping.marshall.CollectionClassificationMarshalling.toXml" /> printMethod="org.hibernate.boot.jaxb.mapping.marshall.CollectionClassificationMarshalling.toXml" />
</bindings> </bindings>
<bindings node="//xsd:simpleType[@name='uuid-generator-style']">
<javaType name="org.hibernate.annotations.UuidGenerator.Style"
parseMethod="org.hibernate.boot.jaxb.mapping.marshall.UuidGeneratorStyleMarshalling.fromXml"
printMethod="org.hibernate.boot.jaxb.mapping.marshall.UuidGeneratorStyleMarshalling.toXml" />
</bindings>
</bindings> </bindings>

View File

@ -0,0 +1,87 @@
/*
* 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.boot.jaxb.mapping;
import static org.assertj.core.api.Assertions.assertThat;
import java.sql.Types;
import java.util.Set;
import java.util.UUID;
import org.hibernate.boot.model.process.internal.UserTypeResolution;
import org.hibernate.generator.Generator;
import org.hibernate.id.uuid.UuidGenerator;
import org.hibernate.mapping.BasicValue;
import org.hibernate.mapping.Property;
import org.hibernate.type.SqlTypes;
import org.hibernate.usertype.UserTypeSupport;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.DomainModelScope;
import org.junit.jupiter.api.Test;
@DomainModel(annotatedClasses = HibernateOrmSpecificAttributesMappingTest.MyEntity.class, xmlMappings = "xml/jaxb/mapping/partial/hibernate-orm-specific-attributes.xml")
public class HibernateOrmSpecificAttributesMappingTest {
@Test
public void verifyMapping(DomainModelScope scope) {
scope.withHierarchy( HibernateOrmSpecificAttributesMappingTest.MyEntity.class, (entityDescriptor) -> {
Generator generator = entityDescriptor.getIdentifierProperty().createGenerator( null );
assertThat( generator )
.isInstanceOf( UuidGenerator.class );
Property name = entityDescriptor.getProperty( "name" );
assertThat( name.getValue() )
.isInstanceOf( BasicValue.class );
assertThat( ( (BasicValue) name.getValue() ).getExplicitJdbcTypeCode() )
.isEqualTo( SqlTypes.CLOB );
Property tags = entityDescriptor.getProperty( "tags" );
assertThat( tags.getValue() )
.isInstanceOf( BasicValue.class );
assertThat( ( (BasicValue) tags.getValue() ).getResolution() )
.isInstanceOf( UserTypeResolution.class );
} );
}
public static class MyEntity {
private UUID id;
private String name;
private Set<String> tags;
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set<String> getTags() {
return tags;
}
public void setTags(Set<String> tags) {
this.tags = tags;
}
}
public static class DelimitedStringsJavaType extends UserTypeSupport<Set> {
public DelimitedStringsJavaType() {
super( Set.class, Types.VARCHAR );
}
}
}

View File

@ -0,0 +1,22 @@
<?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.1">
<package>org.hibernate.orm.test.boot.jaxb.mapping</package>
<entity class="HibernateOrmSpecificAttributesMappingTest$MyEntity">
<attributes>
<id name="id">
<uuid-generator style="time"/>
</id>
<basic name="name" jdbc-type-code="2005"/>
<basic name="tags">
<type value="org.hibernate.orm.test.boot.jaxb.mapping.HibernateOrmSpecificAttributesMappingTest$DelimitedStringsJavaType"/>
</basic>
</attributes>
</entity>
</entity-mappings>