HHH-18095 - transform column read/write fragments

This commit is contained in:
Steve Ebersole 2024-05-29 19:52:23 -05:00
parent 61a00b1e6c
commit 086c7208cb
7 changed files with 239 additions and 5 deletions

View File

@ -91,6 +91,20 @@ public class AdditionalManagedResourcesImpl implements ManagedResources {
private List<String> packageNames;
private Collection<Binding<JaxbBindableMappingDescriptor>> xmlMappings;
public Builder(boolean validateMappings, boolean transformHbmMappings) {
this( new MappingBinder.Options() {
@Override
public boolean validateMappings() {
return validateMappings;
}
@Override
public boolean transformHbmMappings() {
return transformHbmMappings;
}
} );
}
public Builder() {
this( new MappingBinder.Options() {
@Override

View File

@ -17,6 +17,7 @@ import java.util.function.Consumer;
import org.hibernate.AnnotationException;
import org.hibernate.annotations.CascadeType;
import org.hibernate.annotations.ColumnTransformer;
import org.hibernate.annotations.NotFoundAction;
import org.hibernate.annotations.Parameter;
import org.hibernate.annotations.ResultCheckStyle;
@ -73,6 +74,7 @@ import org.hibernate.boot.models.annotations.internal.CheckConstraintJpaAnnotati
import org.hibernate.boot.models.annotations.internal.CollectionIdAnnotation;
import org.hibernate.boot.models.annotations.internal.CollectionTypeAnnotation;
import org.hibernate.boot.models.annotations.internal.ColumnJpaAnnotation;
import org.hibernate.boot.models.annotations.internal.ColumnTransformerAnnotation;
import org.hibernate.boot.models.annotations.internal.ConvertJpaAnnotation;
import org.hibernate.boot.models.annotations.internal.ConvertsJpaAnnotation;
import org.hibernate.boot.models.annotations.internal.DiscriminatorColumnJpaAnnotation;
@ -197,19 +199,44 @@ public class XmlAnnotationHelper {
return;
}
createColumnAnnotation( jaxbColumn, memberDetails, xmlDocumentContext );
final ColumnJpaAnnotation columnAnnotationUsage = (ColumnJpaAnnotation) memberDetails.applyAnnotationUsage(
COLUMN,
xmlDocumentContext.getModelBuildingContext()
);
columnAnnotationUsage.apply( jaxbColumn, xmlDocumentContext );
}
private static ColumnJpaAnnotation createColumnAnnotation(
JaxbColumnImpl jaxbColumn,
MutableAnnotationTarget target,
XmlDocumentContext xmlDocumentContext) {
final ColumnJpaAnnotation columnAnnotationUsage = (ColumnJpaAnnotation) target.applyAnnotationUsage(
COLUMN,
final ColumnJpaAnnotation usage = COLUMN.createUsage( xmlDocumentContext.getModelBuildingContext() );
usage.apply( jaxbColumn, xmlDocumentContext );
return usage;
}
public static void applyColumnTransformation(
JaxbColumnImpl jaxbColumn,
MutableMemberDetails memberDetails,
XmlDocumentContext xmlDocumentContext) {
if ( StringHelper.isEmpty( jaxbColumn.getRead() )
&& StringHelper.isEmpty( jaxbColumn.getWrite() ) ) {
return;
}
final ColumnTransformerAnnotation annotationUsage = (ColumnTransformerAnnotation) memberDetails.applyAnnotationUsage(
HibernateAnnotations.COLUMN_TRANSFORMER,
xmlDocumentContext.getModelBuildingContext()
);
columnAnnotationUsage.apply( jaxbColumn, xmlDocumentContext );
return columnAnnotationUsage;
annotationUsage.forColumn( jaxbColumn.getName() );
if ( StringHelper.isNotEmpty( jaxbColumn.getRead() ) ) {
annotationUsage.read( jaxbColumn.getRead() );
}
if ( StringHelper.isNotEmpty( jaxbColumn.getWrite() ) ) {
annotationUsage.write( jaxbColumn.getWrite() );
}
}
public static void applyUserType(

View File

@ -70,6 +70,7 @@ public class BasicAttributeProcessing {
xmlDocumentContext.getModelBuildingContext()
);
columnAnn.apply( jaxbBasic.getColumn(), xmlDocumentContext );
XmlAnnotationHelper.applyColumnTransformation( jaxbBasic.getColumn(), memberDetails, xmlDocumentContext );
}
XmlAnnotationHelper.applyConvert( jaxbBasic.getConvert(), memberDetails, xmlDocumentContext );

View File

@ -0,0 +1,46 @@
/*
* 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.models.xml.column.transform;
/**
* @author Steve Ebersole
*/
public class Item {
private Integer id;
private String name;
private double cost;
protected Item() {
// for Hibernate use
}
public Item(Integer id, String name, double cost) {
this.id = id;
this.name = name;
this.cost = cost;
}
public Integer getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getCost() {
return cost;
}
public void setCost(double cost) {
this.cost = cost;
}
}

View File

@ -0,0 +1,101 @@
/*
* 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.models.xml.column.transform;
import org.hibernate.annotations.ColumnTransformer;
import org.hibernate.boot.internal.BootstrapContextImpl;
import org.hibernate.boot.internal.MetadataBuilderImpl;
import org.hibernate.boot.model.process.spi.ManagedResources;
import org.hibernate.boot.model.source.internal.annotations.AdditionalManagedResourcesImpl;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.mapping.Column;
import org.hibernate.mapping.Property;
import org.hibernate.models.spi.ClassDetails;
import org.hibernate.models.spi.FieldDetails;
import org.hibernate.models.spi.SourceModelBuildingContext;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.DomainModelScope;
import org.hibernate.testing.orm.junit.ServiceRegistry;
import org.hibernate.testing.orm.junit.ServiceRegistryScope;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hibernate.orm.test.boot.models.SourceModelTestHelper.createBuildingContext;
/**
* @author Steve Ebersole
*/
@SuppressWarnings("JUnitMalformedDeclaration")
public class ModelTests {
@ServiceRegistry
@Test
void testMappingXml(ServiceRegistryScope scope) {
final ManagedResources managedResources = new AdditionalManagedResourcesImpl.Builder()
.addXmlMappings( "mappings/models/column/transform/mapping.xml" )
.build();
final StandardServiceRegistry serviceRegistry = scope.getRegistry();
final BootstrapContextImpl bootstrapContext = new BootstrapContextImpl(
serviceRegistry,
new MetadataBuilderImpl.MetadataBuildingOptionsImpl( serviceRegistry )
);
final SourceModelBuildingContext sourceModelBuildingContext = createBuildingContext(
managedResources,
false,
new MetadataBuilderImpl.MetadataBuildingOptionsImpl( bootstrapContext.getServiceRegistry() ),
bootstrapContext
);
final ClassDetails classDetails = sourceModelBuildingContext.getClassDetailsRegistry().getClassDetails( Item.class.getName() );
final FieldDetails costField = classDetails.findFieldByName( "cost" );
final ColumnTransformer transformerAnn = costField.getAnnotationUsage( ColumnTransformer.class, sourceModelBuildingContext );
assertThat( transformerAnn ).isNotNull();
assertThat( transformerAnn.read() ).isEqualTo( "cost / 100.00" );
assertThat( transformerAnn.write() ).isEqualTo( "? * 100.00" );
}
@ServiceRegistry
@Test
void testHbmXml(ServiceRegistryScope scope) {
final ManagedResources managedResources = new AdditionalManagedResourcesImpl.Builder( true, true )
.addXmlMappings( "mappings/models/column/transform/hbm.xml" )
.build();
final StandardServiceRegistry serviceRegistry = scope.getRegistry();
final BootstrapContextImpl bootstrapContext = new BootstrapContextImpl(
serviceRegistry,
new MetadataBuilderImpl.MetadataBuildingOptionsImpl( serviceRegistry )
);
final SourceModelBuildingContext sourceModelBuildingContext = createBuildingContext(
managedResources,
false,
new MetadataBuilderImpl.MetadataBuildingOptionsImpl( bootstrapContext.getServiceRegistry() ),
bootstrapContext
);
final ClassDetails classDetails = sourceModelBuildingContext.getClassDetailsRegistry().getClassDetails( Item.class.getName() );
final FieldDetails costField = classDetails.findFieldByName( "cost" );
final ColumnTransformer transformerAnn = costField.getAnnotationUsage( ColumnTransformer.class, sourceModelBuildingContext );
assertThat( transformerAnn ).isNotNull();
assertThat( transformerAnn.read() ).isEqualTo( "cost / 100.00" );
assertThat( transformerAnn.write() ).isEqualTo( "? * 100.00" );
}
@ServiceRegistry
@DomainModel(xmlMappings = "mappings/models/column/transform/mapping.xml")
@Test
void testMappingModel(DomainModelScope domainModelScope) {
domainModelScope.withHierarchy( Item.class, (rootClass) -> {
final Property costProperty = rootClass.getProperty( "cost" );
assertThat( costProperty.getColumns() ).hasSize( 1 );
final Column column = costProperty.getColumns().get( 0 );
assertThat( column.getCustomRead() ).isEqualTo( "cost / 100.00" );
assertThat( column.getCustomWrite() ).isEqualTo( "? * 100.00" );
} );
}
}

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.
-->
<hibernate-mapping
xmlns="http://www.hibernate.org/xsd/orm/hbm"
default-access="field"
package="org.hibernate.orm.test.boot.models.xml.column.transform">
<class name="Item" >
<id name="id"/>
<property name="name"/>
<property name="cost">
<column name="cost" read="cost / 100.00" write="? * 100.00"/>
</property>
</class>
</hibernate-mapping>

View File

@ -0,0 +1,25 @@
<!--
~ Hibernate, Relational Persistence for Idiomatic Java
~
~ SPDX-License-Identifier: Apache-2.0
~ Copyright: Red Hat Inc. and Hibernate Authors
-->
<entity-mappings xmlns="http://www.hibernate.org/xsd/orm/mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
version="7.0">
<package>org.hibernate.orm.test.boot.models.xml.column.transform</package>
<entity class="Item" metadata-complete="true" access="FIELD">
<table name="items"/>
<attributes>
<id name="id"/>
<basic name="name"/>
<basic name="cost">
<column>
<read>cost / 100.00</read>
<write>? * 100.00</write>
</column>
<jdbc-type-code>2</jdbc-type-code>
</basic>
</attributes>
</entity>
</entity-mappings>