HHH-17460 - Ongoing JPA 32 work

This commit is contained in:
Steve Ebersole 2024-03-18 13:34:44 -05:00
parent b5606fd279
commit 439ff8dcf7
15 changed files with 224 additions and 101 deletions

View File

@ -13,4 +13,6 @@ package org.hibernate.boot.jaxb.mapping.spi;
* @author Steve Ebersole * @author Steve Ebersole
*/ */
public interface JaxbEmbeddedMapping extends JaxbSingularAttribute { public interface JaxbEmbeddedMapping extends JaxbSingularAttribute {
String getTarget();
void setTarget(String target);
} }

View File

@ -7,7 +7,6 @@
package org.hibernate.boot.jaxb.mapping.spi; package org.hibernate.boot.jaxb.mapping.spi;
import jakarta.persistence.AccessType; import jakarta.persistence.AccessType;
import jakarta.persistence.FetchType;
/** /**
* Common interface for JAXB bindings that represent persistent attributes. * Common interface for JAXB bindings that represent persistent attributes.

View File

@ -442,7 +442,12 @@ public class MetadataBuildingProcess {
sourceModelBuildingContext sourceModelBuildingContext
); );
final XmlProcessingResult xmlProcessingResult = XmlProcessor.processXml( xmlPreProcessingResult, modelCategorizationCollector, sourceModelBuildingContext ); final XmlProcessingResult xmlProcessingResult = XmlProcessor.processXml(
xmlPreProcessingResult,
modelCategorizationCollector,
sourceModelBuildingContext,
bootstrapContext
);
final HashSet<String> categorizedClassNames = new HashSet<>(); final HashSet<String> categorizedClassNames = new HashSet<>();
allKnownClassNames.forEach( (className) -> applyKnownClass( allKnownClassNames.forEach( (className) -> applyKnownClass(

View File

@ -133,7 +133,12 @@ public class ManagedResourcesProcessor {
sourceModelBuildingContext sourceModelBuildingContext
); );
final XmlProcessingResult xmlProcessingResult = XmlProcessor.processXml( xmlPreProcessingResult, modelCategorizationCollector, sourceModelBuildingContext ); final XmlProcessingResult xmlProcessingResult = XmlProcessor.processXml(
xmlPreProcessingResult,
modelCategorizationCollector,
sourceModelBuildingContext,
bootstrapContext
);
allKnownClassNames.forEach( (className) -> { allKnownClassNames.forEach( (className) -> {
final ClassDetails classDetails = classDetailsRegistry.resolveClassDetails( className ); final ClassDetails classDetails = classDetailsRegistry.resolveClassDetails( className );

View File

@ -374,83 +374,10 @@ public class ManagedTypeProcessor {
private static TypeDetails determineDynamicAttributeJavaType( private static TypeDetails determineDynamicAttributeJavaType(
JaxbPersistentAttribute jaxbPersistentAttribute, JaxbPersistentAttribute jaxbPersistentAttribute,
XmlDocumentContext xmlDocumentContext) { XmlDocumentContext xmlDocumentContext) {
final ClassDetailsRegistry classDetailsRegistry = xmlDocumentContext.getModelBuildingContext().getClassDetailsRegistry(); final MutableClassDetails classDetails = xmlDocumentContext.resolveDynamicJavaType( jaxbPersistentAttribute );
if ( jaxbPersistentAttribute instanceof JaxbIdImpl jaxbId ) {
final MutableClassDetails classDetails = xmlDocumentContext.resolveJavaType( jaxbId.getTarget() );
return new ClassTypeDetailsImpl( classDetails, TypeDetails.Kind.CLASS ); return new ClassTypeDetailsImpl( classDetails, TypeDetails.Kind.CLASS );
} }
if ( jaxbPersistentAttribute instanceof JaxbEmbeddedIdImpl jaxbEmbeddedId ) {
final String target = jaxbEmbeddedId.getTarget();
if ( StringHelper.isEmpty( target ) ) {
return null;
}
final ClassDetails classDetails = classDetailsRegistry.resolveClassDetails(
target,
(name) -> new DynamicClassDetails( target, xmlDocumentContext.getModelBuildingContext() )
);
return new ClassTypeDetailsImpl( classDetails, TypeDetails.Kind.CLASS );
}
if ( jaxbPersistentAttribute instanceof JaxbBasicImpl jaxbBasic ) {
final MutableClassDetails classDetails = xmlDocumentContext.resolveJavaType( jaxbBasic.getTarget() );
return new ClassTypeDetailsImpl( classDetails, TypeDetails.Kind.CLASS );
}
if ( jaxbPersistentAttribute instanceof JaxbEmbeddedImpl jaxbEmbedded ) {
final String target = jaxbEmbedded.getTarget();
if ( StringHelper.isEmpty( target ) ) {
return null;
}
final ClassDetails classDetails = classDetailsRegistry.resolveClassDetails(
target,
(name) -> new DynamicClassDetails( target, xmlDocumentContext.getModelBuildingContext() )
);
return new ClassTypeDetailsImpl( classDetails, TypeDetails.Kind.CLASS );
}
if ( jaxbPersistentAttribute instanceof JaxbOneToOneImpl jaxbOneToOne ) {
final String target = jaxbOneToOne.getTargetEntity();
if ( StringHelper.isEmpty( target ) ) {
return null;
}
final ClassDetails classDetails = classDetailsRegistry.resolveClassDetails(
target,
(name) -> new DynamicClassDetails(
target,
null,
false,
null,
null,
xmlDocumentContext.getModelBuildingContext()
)
);
return new ClassTypeDetailsImpl( classDetails, TypeDetails.Kind.CLASS );
}
if ( jaxbPersistentAttribute instanceof JaxbAnyMappingImpl ) {
final ClassDetails classDetails = classDetailsRegistry.getClassDetails( Object.class.getName() );
return new ClassTypeDetailsImpl( classDetails, TypeDetails.Kind.CLASS );
}
if ( jaxbPersistentAttribute instanceof JaxbPluralAttribute jaxbPluralAttribute ) {
final LimitedCollectionClassification classification = nullif( jaxbPluralAttribute.getClassification(), LimitedCollectionClassification.BAG );
final ClassDetails collectionClassDetails;
collectionClassDetails = switch ( classification ) {
case BAG -> classDetailsRegistry.resolveClassDetails( Collection.class.getName() );
case LIST -> classDetailsRegistry.resolveClassDetails( List.class.getName() );
case SET -> classDetailsRegistry.resolveClassDetails( Set.class.getName() );
case MAP -> classDetailsRegistry.resolveClassDetails( Map.class.getName() );
};
return new ClassTypeDetailsImpl( collectionClassDetails, TypeDetails.Kind.CLASS );
}
throw new UnsupportedOperationException( "Resolution of dynamic attribute Java type not yet implemented for " + jaxbPersistentAttribute );
}
private static void adjustDynamicTypeMember( private static void adjustDynamicTypeMember(
MutableMemberDetails memberDetails, MutableMemberDetails memberDetails,
JaxbPersistentAttribute jaxbAttribute, JaxbPersistentAttribute jaxbAttribute,

View File

@ -9,6 +9,7 @@ package org.hibernate.boot.models.xml.internal;
import org.hibernate.boot.models.xml.spi.PersistenceUnitMetadata; import org.hibernate.boot.models.xml.spi.PersistenceUnitMetadata;
import org.hibernate.boot.models.xml.spi.XmlDocument; import org.hibernate.boot.models.xml.spi.XmlDocument;
import org.hibernate.boot.models.xml.spi.XmlDocumentContext; import org.hibernate.boot.models.xml.spi.XmlDocumentContext;
import org.hibernate.boot.spi.BootstrapContext;
import org.hibernate.models.spi.SourceModelBuildingContext; import org.hibernate.models.spi.SourceModelBuildingContext;
/** /**
@ -18,14 +19,17 @@ public class XmlDocumentContextImpl implements XmlDocumentContext {
private final XmlDocument xmlDocument; private final XmlDocument xmlDocument;
private final PersistenceUnitMetadata persistenceUnitMetadata; private final PersistenceUnitMetadata persistenceUnitMetadata;
private final SourceModelBuildingContext modelBuildingContext; private final SourceModelBuildingContext modelBuildingContext;
private final BootstrapContext bootstrapContext;
public XmlDocumentContextImpl( public XmlDocumentContextImpl(
XmlDocument xmlDocument, XmlDocument xmlDocument,
PersistenceUnitMetadata persistenceUnitMetadata, PersistenceUnitMetadata persistenceUnitMetadata,
SourceModelBuildingContext modelBuildingContext) { SourceModelBuildingContext modelBuildingContext,
BootstrapContext bootstrapContext) {
this.xmlDocument = xmlDocument; this.xmlDocument = xmlDocument;
this.persistenceUnitMetadata = persistenceUnitMetadata; this.persistenceUnitMetadata = persistenceUnitMetadata;
this.modelBuildingContext = modelBuildingContext; this.modelBuildingContext = modelBuildingContext;
this.bootstrapContext = bootstrapContext;
} }
@Override @Override
@ -42,4 +46,9 @@ public class XmlDocumentContextImpl implements XmlDocumentContext {
public SourceModelBuildingContext getModelBuildingContext() { public SourceModelBuildingContext getModelBuildingContext() {
return modelBuildingContext; return modelBuildingContext;
} }
@Override
public BootstrapContext getBootstrapContext() {
return bootstrapContext;
}
} }

View File

@ -6,9 +6,34 @@
*/ */
package org.hibernate.boot.models.xml.spi; package org.hibernate.boot.models.xml.spi;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.hibernate.HibernateException;
import org.hibernate.boot.internal.LimitedCollectionClassification;
import org.hibernate.boot.jaxb.mapping.spi.JaxbAnyMapping;
import org.hibernate.boot.jaxb.mapping.spi.JaxbAssociationAttribute;
import org.hibernate.boot.jaxb.mapping.spi.JaxbBasicMapping;
import org.hibernate.boot.jaxb.mapping.spi.JaxbEmbeddedMapping;
import org.hibernate.boot.jaxb.mapping.spi.JaxbPersistentAttribute;
import org.hibernate.boot.jaxb.mapping.spi.JaxbPluralAttribute;
import org.hibernate.boot.jaxb.mapping.spi.JaxbUserTypeImpl;
import org.hibernate.boot.models.xml.internal.XmlAnnotationHelper; import org.hibernate.boot.models.xml.internal.XmlAnnotationHelper;
import org.hibernate.boot.spi.BootstrapContext;
import org.hibernate.models.internal.dynamic.DynamicClassDetails;
import org.hibernate.models.spi.ClassDetails;
import org.hibernate.models.spi.MutableClassDetails; import org.hibernate.models.spi.MutableClassDetails;
import org.hibernate.models.spi.SourceModelBuildingContext; import org.hibernate.models.spi.SourceModelBuildingContext;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.usertype.UserType;
import org.checkerframework.checker.nullness.qual.NonNull;
import static org.hibernate.internal.util.NullnessHelper.nullif;
import static org.hibernate.internal.util.StringHelper.isNotEmpty;
/** /**
* Context for a specific XML mapping file * Context for a specific XML mapping file
@ -27,14 +52,148 @@ public interface XmlDocumentContext {
PersistenceUnitMetadata getPersistenceUnitMetadata(); PersistenceUnitMetadata getPersistenceUnitMetadata();
/** /**
* Access to the containing context * Access to the containing SourceModelBuildingContext
*/ */
SourceModelBuildingContext getModelBuildingContext(); SourceModelBuildingContext getModelBuildingContext();
/**
* Access to the containing BootstrapContext
*/
BootstrapContext getBootstrapContext();
/** /**
* Resolve a ClassDetails by name, accounting for XML-defined package name if one. * Resolve a ClassDetails by name, accounting for XML-defined package name if one.
*/ */
default MutableClassDetails resolveJavaType(String name) { default MutableClassDetails resolveJavaType(String name) {
try {
return (MutableClassDetails) XmlAnnotationHelper.resolveJavaType( name, this ); return (MutableClassDetails) XmlAnnotationHelper.resolveJavaType( name, this );
} }
catch (Exception e) {
final HibernateException hibernateException = new HibernateException( "Unable to resolve Java type " + name );
hibernateException.addSuppressed( e );
throw hibernateException;
}
}
/**
* Resolve a ClassDetails by name, accounting for XML-defined package name if one.
*/
default MutableClassDetails resolveDynamicJavaType(JaxbPersistentAttribute jaxbPersistentAttribute) {
if ( jaxbPersistentAttribute instanceof JaxbBasicMapping jaxbBasicMapping ) {
// <id/>, <basic/>, <tenant-id/>
// explicit <target/>
final String target = jaxbBasicMapping.getTarget();
if ( isNotEmpty( target ) ) {
return (MutableClassDetails) XmlAnnotationHelper.resolveJavaType( target, this );
}
// UserType
final JaxbUserTypeImpl userTypeNode = jaxbBasicMapping.getType();
if ( userTypeNode != null ) {
final String userTypeImplName = userTypeNode.getValue();
if ( isNotEmpty( userTypeImplName ) ) {
final ClassDetails userTypeImplDetails = XmlAnnotationHelper.resolveJavaType( userTypeImplName, this );
// safe to convert to class, though unfortunate to have to instantiate it...
final UserType<?> userType = createInstance( userTypeImplDetails );
final Class<?> modelClass = userType.returnedClass();
return (MutableClassDetails) getModelBuildingContext().getClassDetailsRegistry().getClassDetails( modelClass.getName() );
}
}
// JavaType
final String javaTypeImplName = jaxbBasicMapping.getJavaType();
if ( isNotEmpty( javaTypeImplName ) ) {
final ClassDetails javaTypeImplDetails = XmlAnnotationHelper.resolveJavaType( javaTypeImplName, this );
// safe to convert to class, though unfortunate to have to instantiate it...
final JavaType<?> javaType = createInstance( javaTypeImplDetails );
final Class<?> modelClass = javaType.getJavaTypeClass();
return (MutableClassDetails) getModelBuildingContext().getClassDetailsRegistry().getClassDetails( modelClass.getName() );
}
// JdbcType
final String jdbcTypeImplName = jaxbBasicMapping.getJdbcType();
final Integer jdbcTypeCode = jaxbBasicMapping.getJdbcTypeCode();
final JdbcType jdbcType;
if ( isNotEmpty( jdbcTypeImplName ) ) {
final ClassDetails jdbcTypeImplDetails = XmlAnnotationHelper.resolveJavaType( javaTypeImplName, this );
jdbcType = createInstance( jdbcTypeImplDetails );
}
else if ( jdbcTypeCode != null ) {
jdbcType = getBootstrapContext().getTypeConfiguration().getJdbcTypeRegistry().getDescriptor( jdbcTypeCode );
}
else {
jdbcType = null;
}
if ( jdbcType != null ) {
final JavaType<?> javaType = jdbcType.getJdbcRecommendedJavaTypeMapping( 0, 0, getBootstrapContext().getTypeConfiguration() );
final Class<?> modelClass = javaType.getJavaTypeClass();
return (MutableClassDetails) getModelBuildingContext().getClassDetailsRegistry().getClassDetails( modelClass.getName() );
}
// fall through to exception
}
if ( jaxbPersistentAttribute instanceof JaxbEmbeddedMapping jaxbEmbeddedMapping ) {
// <embedded/>, <embedded-id/>
final String target = jaxbEmbeddedMapping.getTarget();
if ( isNotEmpty( target ) ) {
return (MutableClassDetails) getModelBuildingContext().getClassDetailsRegistry().resolveClassDetails(
target,
(name) -> new DynamicClassDetails( target, getModelBuildingContext() )
);
}
// fall through to exception
}
if ( jaxbPersistentAttribute instanceof JaxbAssociationAttribute jaxbAssociationAttribute ) {
final String target = jaxbAssociationAttribute.getTargetEntity();
if ( isNotEmpty( target ) ) {
return (MutableClassDetails) getModelBuildingContext().getClassDetailsRegistry().resolveClassDetails(
target,
(name) -> new DynamicClassDetails(
target,
null,
false,
null,
null,
getModelBuildingContext()
)
);
}
// fall through to exception
}
if ( jaxbPersistentAttribute instanceof JaxbAnyMapping ) {
// todo : this is problematic because we'd really want Object, but the hibernate-models
// definition of ClassDetails(Object) is immutable. Probably the best option here
// is to create a new (unregistered) DynamicClassDetails for each
throw new UnsupportedOperationException( "Not yet implemented" );
}
if ( jaxbPersistentAttribute instanceof JaxbPluralAttribute jaxbPluralAttribute ) {
final LimitedCollectionClassification classification = nullif( jaxbPluralAttribute.getClassification(), LimitedCollectionClassification.BAG );
return switch ( classification ) {
case BAG -> resolveJavaType( Collection.class.getName() );
case LIST -> resolveJavaType( List.class.getName() );
case SET -> resolveJavaType( Set.class.getName() );
case MAP -> resolveJavaType( Map.class.getName() );
};
}
// todo : would be nice to have at least the XML origin (file name, etc) for the exception.
// the "context" (class where this happens) would be even more nicerer
throw new HibernateException( "Could not determine target type for dynamic attribute - " + jaxbPersistentAttribute.getName() );
}
@NonNull
private <T> T createInstance(ClassDetails classDetails) {
try {
//noinspection unchecked
return (T) classDetails.toJavaClass().getConstructor().newInstance();
}
catch (Exception e) {
throw new HibernateException( "Unable to create instance from incoming ClassDetails - " + classDetails );
}
}
} }

View File

@ -11,6 +11,7 @@ import org.hibernate.boot.models.xml.internal.ManagedTypeProcessor;
import org.hibernate.boot.models.xml.internal.XmlDocumentContextImpl; import org.hibernate.boot.models.xml.internal.XmlDocumentContextImpl;
import org.hibernate.boot.models.xml.internal.XmlDocumentImpl; import org.hibernate.boot.models.xml.internal.XmlDocumentImpl;
import org.hibernate.boot.models.xml.internal.XmlProcessingResultImpl; import org.hibernate.boot.models.xml.internal.XmlProcessingResultImpl;
import org.hibernate.boot.spi.BootstrapContext;
import org.hibernate.models.spi.SourceModelBuildingContext; import org.hibernate.models.spi.SourceModelBuildingContext;
/** /**
@ -23,7 +24,8 @@ public class XmlProcessor {
public static XmlProcessingResult processXml( public static XmlProcessingResult processXml(
XmlPreProcessingResult xmlPreProcessingResult, XmlPreProcessingResult xmlPreProcessingResult,
DomainModelCategorizationCollector modelCategorizationCollector, DomainModelCategorizationCollector modelCategorizationCollector,
SourceModelBuildingContext sourceModelBuildingContext) { SourceModelBuildingContext sourceModelBuildingContext,
BootstrapContext bootstrapContext) {
final boolean xmlMappingsGloballyComplete = xmlPreProcessingResult.getPersistenceUnitMetadata().areXmlMappingsComplete(); final boolean xmlMappingsGloballyComplete = xmlPreProcessingResult.getPersistenceUnitMetadata().areXmlMappingsComplete();
final XmlProcessingResultImpl xmlOverlay = new XmlProcessingResultImpl(); final XmlProcessingResultImpl xmlOverlay = new XmlProcessingResultImpl();
@ -36,7 +38,8 @@ public class XmlProcessor {
final XmlDocumentContext xmlDocumentContext = new XmlDocumentContextImpl( final XmlDocumentContext xmlDocumentContext = new XmlDocumentContextImpl(
xmlDocument, xmlDocument,
xmlPreProcessingResult.getPersistenceUnitMetadata(), xmlPreProcessingResult.getPersistenceUnitMetadata(),
sourceModelBuildingContext sourceModelBuildingContext,
bootstrapContext
); );
jaxbRoot.getEmbeddables().forEach( (jaxbEmbeddable) -> { jaxbRoot.getEmbeddables().forEach( (jaxbEmbeddable) -> {

View File

@ -77,7 +77,7 @@ public class DynamicModelTests {
assertThat( idField.getType().determineRawClass().getClassName() ).isEqualTo( Integer.class.getName() ); assertThat( idField.getType().determineRawClass().getClassName() ).isEqualTo( Integer.class.getName() );
final FieldDetails nameField = rootEntity.getClassDetails().findFieldByName( "name" ); final FieldDetails nameField = rootEntity.getClassDetails().findFieldByName( "name" );
assertThat( nameField.getType().determineRawClass().getClassName() ).isEqualTo( Object.class.getName() ); assertThat( nameField.getType().determineRawClass().getClassName() ).isEqualTo( String.class.getName() );
assertThat( nameField.getAnnotationUsage( JavaType.class ) ).isNotNull(); assertThat( nameField.getAnnotationUsage( JavaType.class ) ).isNotNull();
final FieldDetails qtyField = rootEntity.getClassDetails().findFieldByName( "quantity" ); final FieldDetails qtyField = rootEntity.getClassDetails().findFieldByName( "quantity" );

View File

@ -57,7 +57,7 @@ public class RowIdTest {
assertThat( rowIdAnnotationUsage ).isNotNull(); assertThat( rowIdAnnotationUsage ).isNotNull();
final String value = rowIdAnnotationUsage.getString( "value" ); final String value = rowIdAnnotationUsage.getString( "value" );
if ( entityName.equals( "EntityWithRowIdNoValue" ) ) { if ( entityName.equals( "EntityWithRowIdNoValue" ) ) {
assertThat( value ).isNull(); assertThat( value ).isEmpty();
} }
else { else {
assertThat( value ).isEqualTo( "ROW_ID" ); assertThat( value ).isEqualTo( "ROW_ID" );

View File

@ -20,17 +20,26 @@
</subgraph> </subgraph>
</named-entity-graph> </named-entity-graph>
<attributes> <attributes>
<id name="id"/> <id name="id">
<basic name="name"/> <target>integer</target>
<basic name="surname"/> </id>
<!-- <one-to-one name="address" fetch="LAZY"/>--> <basic name="name">
<target>string</target>
</basic>
<basic name="surname">
<target>string</target>
</basic>
<one-to-one name="address" fetch="LAZY" target-entity="Address"/>
</attributes>
</entity>
<entity name="Address" metadata-complete="true">
<attributes>
<id name="id">
<target>integer</target>
</id>
<basic name="city">
<target>string</target>
</basic>
</attributes> </attributes>
</entity> </entity>
<!-- <entity name="Address" metadata-complete="true">-->
<!-- <attributes>-->
<!-- <id name="id"/>-->
<!-- <basic name="city"/>-->
<!-- </attributes>-->
<!-- </entity>-->
</entity-mappings> </entity-mappings>

View File

@ -6,7 +6,7 @@
--> -->
<entity-mappings xmlns="http://www.hibernate.org/xsd/orm/mapping" <entity-mappings xmlns="http://www.hibernate.org/xsd/orm/mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
version="3.1"> version="3.2">
<entity name="EntityWithoutRowId" metadata-complete="true"> <entity name="EntityWithoutRowId" metadata-complete="true">
<attributes> <attributes>
<id name="id"> <id name="id">

View File

@ -6,7 +6,7 @@
--> -->
<entity-mappings xmlns="http://www.hibernate.org/xsd/orm/mapping" <entity-mappings xmlns="http://www.hibernate.org/xsd/orm/mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
version="3.1"> version="3.2">
<entity name="Contact" metadata-complete="true"> <entity name="Contact" metadata-complete="true">
<attributes> <attributes>
<id name="id"> <id name="id">

View File

@ -6,7 +6,7 @@
--> -->
<entity-mappings xmlns="http://www.hibernate.org/xsd/orm/mapping" <entity-mappings xmlns="http://www.hibernate.org/xsd/orm/mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
version="3.1"> version="3.2">
<entity name="SimpleEntity" metadata-complete="true"> <entity name="SimpleEntity" metadata-complete="true">
<attributes> <attributes>
<id name="id"> <id name="id">

View File

@ -8,7 +8,9 @@
version="3.2"> version="3.2">
<entity name="EntityWithoutTenantId" metadata-complete="true"> <entity name="EntityWithoutTenantId" metadata-complete="true">
<attributes> <attributes>
<id name="id"/> <id name="id">
<target>Long</target>
</id>
</attributes> </attributes>
</entity> </entity>
@ -17,10 +19,13 @@
<tenant-id name="tenantId" fetch="EAGER" optional="true"> <tenant-id name="tenantId" fetch="EAGER" optional="true">
<column name="TENANT_ID" insertable="false"/> <column name="TENANT_ID" insertable="false"/>
<target>String</target>
</tenant-id> </tenant-id>
<attributes> <attributes>
<id name="id"/> <id name="id">
<target>Integer</target>
</id>
</attributes> </attributes>
</entity> </entity>