Implement support for CompositeUserType and re-enable tests that make use of it

This commit is contained in:
Christian Beikov 2022-02-17 16:29:23 +01:00
parent 277f10d987
commit c520b48487
83 changed files with 2129 additions and 486 deletions

View File

@ -11,12 +11,16 @@
import java.util.Locale;
import org.hibernate.annotations.ColumnTransformer;
import org.hibernate.annotations.CompositeType;
import org.hibernate.dialect.H2Dialect;
import org.hibernate.orm.test.jpa.BaseEntityManagerFunctionalTestCase;
import org.hibernate.testing.RequiresDialect;
import org.junit.Test;
import jakarta.persistence.AttributeOverride;
import jakarta.persistence.AttributeOverrides;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
@ -43,15 +47,14 @@ public void testLifecycle() {
//tag::basic-datetime-temporal-date-persist-example[]
Savings savings = new Savings();
savings.setId(1L);
savings.setCurrency(Currency.getInstance(Locale.US));
savings.setAmount(BigDecimal.TEN);
savings.setWallet(new MonetaryAmount(BigDecimal.TEN, Currency.getInstance(Locale.US)));
entityManager.persist(savings);
});
doInJPA(this::entityManagerFactory, entityManager -> {
Savings savings = entityManager.find(Savings.class, 1L);
assertEquals(10, savings.getAmount().intValue());
assertEquals(Currency.getInstance(Locale.US), savings.getCurrency());
assertEquals(10, savings.getWallet().getAmount().intValue());
assertEquals(Currency.getInstance(Locale.US), savings.getWallet().getCurrency());
});
//end::mapping-column-read-and-write-composite-type-persistence-example[]
}
@ -63,12 +66,17 @@ public static class Savings {
@Id
private Long id;
@CompositeType(MonetaryAmountUserType.class)
@AttributeOverrides({
@AttributeOverride(name = "amount", column = @Column(name = "money")),
@AttributeOverride(name = "currency", column = @Column(name = "currency"))
})
@ColumnTransformer(
read = "amount / 100",
forColumn = "money",
read = "money / 100",
write = "? * 100"
)
private BigDecimal amount;
private Currency currency;
private MonetaryAmount wallet;
//Getters and setters omitted for brevity
@ -81,20 +89,12 @@ public void setId(Long id) {
this.id = id;
}
public BigDecimal getAmount() {
return amount;
public MonetaryAmount getWallet() {
return wallet;
}
public void setAmount(BigDecimal amount) {
this.amount = amount;
}
public Currency getCurrency() {
return currency;
}
public void setCurrency(Currency currency) {
this.currency = currency;
public void setWallet(MonetaryAmount wallet) {
this.wallet = wallet;
}

View File

@ -0,0 +1,96 @@
/*
* 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.userguide.mapping.basic;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Currency;
import java.util.function.Supplier;
import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.usertype.CompositeUserType;
/**
* @author Emmanuel Bernard
*/
public class MonetaryAmountUserType implements CompositeUserType<MonetaryAmount> {
@Override
public Object getPropertyValue(MonetaryAmount component, int property) throws HibernateException {
switch ( property ) {
case 0:
return component.getAmount();
case 1:
return component.getCurrency();
}
throw new HibernateException( "Illegal property index: " + property );
}
@Override
public MonetaryAmount instantiate(Supplier<Object[]> valueSupplier, SessionFactoryImplementor sessionFactory) {
final Object[] values = valueSupplier.get();
return new MonetaryAmount( (BigDecimal) values[0], (Currency) values[1] );
}
@Override
public Class<?> embeddable() {
return MonetaryAmountEmbeddable.class;
}
@Override
public Class<MonetaryAmount> returnedClass() {
return MonetaryAmount.class;
}
@Override
public boolean isMutable() {
return true;
}
@Override
public Object deepCopy(Object value) {
MonetaryAmount ma = (MonetaryAmount) value;
return new MonetaryAmount( ma.getAmount(), ma.getCurrency() );
}
@Override
public boolean equals(Object x, Object y) {
if ( x == y ) {
return true;
}
if ( x == null || y == null ) {
return false;
}
return x.equals( y );
}
@Override
public Serializable disassemble(Object value) throws HibernateException {
return (Serializable) deepCopy( value );
}
@Override
public Object assemble(Serializable cached, Object owner) throws HibernateException {
return deepCopy( cached );
}
@Override
public Object replace(Object original, Object target, Object owner) throws HibernateException {
return deepCopy( original ); //TODO: improve
}
@Override
public int hashCode(Object x) throws HibernateException {
return x.hashCode();
}
public static class MonetaryAmountEmbeddable {
private BigDecimal amount;
private Currency currency;
}
}

View File

@ -0,0 +1,28 @@
/*
* 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.annotations;
import java.lang.annotation.Retention;
import org.hibernate.usertype.CompositeUserType;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* Applies a custom {@link CompositeUserType} for the attribute mapping.
*/
@java.lang.annotation.Target({METHOD, FIELD})
@Retention(RUNTIME)
public @interface CompositeType {
/**
* The custom type implementor class
*/
Class<? extends CompositeUserType<?>> value();
}

View File

@ -0,0 +1,32 @@
/*
* 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.annotations;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import org.hibernate.usertype.CompositeUserType;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.PACKAGE;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* Registers a custom composite user type implementation to be used
* for all references to a particular {@link jakarta.persistence.Embeddable}.
* <p/>
* May be overridden for a specific embedded using {@link org.hibernate.annotations.CompositeType}
*/
@Target( {TYPE, ANNOTATION_TYPE, PACKAGE} )
@Retention( RUNTIME )
@Repeatable( CompositeTypeRegistrations.class )
public @interface CompositeTypeRegistration {
Class<?> embeddableClass();
Class<? extends CompositeUserType<?>> userType();
}

View File

@ -0,0 +1,26 @@
/*
* 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.annotations;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.PACKAGE;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* Grouping of {@link CompositeTypeRegistration}
*
* @author Steve Ebersole
*/
@Target( {TYPE, ANNOTATION_TYPE, PACKAGE} )
@Retention( RUNTIME )
public @interface CompositeTypeRegistrations {
CompositeTypeRegistration[] value();
}

View File

@ -0,0 +1,23 @@
/*
* 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.annotations;
import org.hibernate.usertype.CompositeUserType;
/**
* Form of {@link CompositeType} for use with map-keys
*
* @since 6.0
*/
public @interface MapKeyCustomCompositeType {
/**
* The custom type implementor class
*
* @see CompositeType#value
*/
Class<? extends CompositeUserType<?>> value();
}

View File

@ -104,6 +104,7 @@
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.spi.TypeConfiguration;
import org.hibernate.usertype.CompositeUserType;
import jakarta.persistence.AttributeConverter;
import jakarta.persistence.Embeddable;
@ -410,6 +411,25 @@ public Class<? extends EmbeddableInstantiator> findRegisteredEmbeddableInstantia
return registeredInstantiators.get( embeddableType );
}
private Map<Class<?>, Class<? extends CompositeUserType<?>>> registeredCompositeUserTypes;
@Override
public void registerCompositeUserType(Class<?> embeddableType, Class<? extends CompositeUserType<?>> userType) {
if ( registeredCompositeUserTypes == null ) {
registeredCompositeUserTypes = new HashMap<>();
}
registeredCompositeUserTypes.put( embeddableType, userType );
}
@Override
public Class<? extends CompositeUserType<?>> findRegisteredCompositeUserType(Class<?> embeddableType) {
if ( registeredCompositeUserTypes == null ) {
return null;
}
return registeredCompositeUserTypes.get( embeddableType );
}
private Map<CollectionClassification, CollectionTypeRegistrationDescriptor> collectionTypeRegistrations;

View File

@ -153,6 +153,7 @@
import org.hibernate.type.CustomType;
import org.hibernate.type.ForeignKeyDirection;
import org.hibernate.type.StandardBasicTypes;
import org.hibernate.usertype.CompositeUserType;
import org.hibernate.usertype.ParameterizedType;
import org.hibernate.usertype.UserType;
@ -2636,6 +2637,22 @@ else if ( isVirtual ) {
else {
log.debugf( "Binding component [%s]", role );
if ( StringHelper.isNotEmpty( explicitComponentClassName ) ) {
try {
final Class<Object> componentClass = sourceDocument.getBootstrapContext().getClassLoaderAccess()
.classForName( explicitComponentClassName );
if ( CompositeUserType.class.isAssignableFrom( componentClass ) ) {
componentBinding.setTypeName( explicitComponentClassName );
CompositeUserType<?> compositeUserType = (CompositeUserType<?>) sourceDocument.getBootstrapContext()
.getServiceRegistry()
.getService( ManagedBeanRegistry.class )
.getBean( componentClass )
.getBeanInstance();
explicitComponentClassName = compositeUserType.embeddable().getName();
}
}
catch (ClassLoadingException ex) {
log.debugf( ex, "Could load component class [%s]", explicitComponentClassName );
}
log.debugf( "Binding component [%s] to explicitly specified class", role, explicitComponentClassName );
componentBinding.setComponentClassName( explicitComponentClassName );
}

View File

@ -653,11 +653,11 @@ public FetchMemento resolve(ResultSetMappingResolutionContext resolutionContext)
final FetchParentMemento fetchParentMemento = parent.resolveParentMemento( resolutionContext );
NavigablePath navigablePath = fetchParentMemento.getNavigablePath().append( propertyPathParts[ 0 ] );
Fetchable fetchable = (Fetchable) fetchParentMemento.getFetchableContainer().findSubPart(
propertyPathParts[ 0 ],
null
);
NavigablePath navigablePath = fetchParentMemento.getNavigablePath().append( fetchable.getFetchableName() );
for ( int i = 1; i < propertyPathParts.length; i++ ) {
if ( ! ( fetchable instanceof FetchableContainer ) ) {
@ -666,8 +666,8 @@ public FetchMemento resolve(ResultSetMappingResolutionContext resolutionContext)
+ " did not reference FetchableContainer"
);
}
navigablePath = navigablePath.append( propertyPathParts[ i ] );
fetchable = (Fetchable) ( (FetchableContainer) fetchable ).findSubPart( propertyPathParts[i], null );
navigablePath = navigablePath.append( fetchable.getFetchableName() );
}
if ( fetchable instanceof BasicValuedModelPart ) {

View File

@ -7,6 +7,7 @@
package org.hibernate.boot.spi;
import java.io.Serializable;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@ -53,6 +54,7 @@
import org.hibernate.metamodel.spi.EmbeddableInstantiator;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.usertype.CompositeUserType;
import org.hibernate.usertype.UserCollectionType;
import jakarta.persistence.AttributeConverter;
@ -312,6 +314,9 @@ void addTableNameBinding(
void registerEmbeddableInstantiator(Class<?> embeddableType, Class<? extends EmbeddableInstantiator> instantiator);
Class<? extends EmbeddableInstantiator> findRegisteredEmbeddableInstantiator(Class<?> embeddableType);
void registerCompositeUserType(Class<?> embeddableType, Class<? extends CompositeUserType<?>> userType);
Class<? extends CompositeUserType<?>> findRegisteredCompositeUserType(Class<?> embeddableType);
void addCollectionTypeRegistration(CollectionTypeRegistration registrationAnnotation);
void addCollectionTypeRegistration(CollectionClassification classification, CollectionTypeRegistrationDescriptor descriptor);
CollectionTypeRegistrationDescriptor findCollectionTypeRegistration(CollectionClassification classification);

View File

@ -25,6 +25,8 @@
import org.hibernate.AnnotationException;
import org.hibernate.AssertionFailure;
import org.hibernate.annotations.ColumnTransformer;
import org.hibernate.annotations.ColumnTransformers;
import org.hibernate.annotations.common.reflection.XAnnotatedElement;
import org.hibernate.annotations.common.reflection.XClass;
import org.hibernate.annotations.common.reflection.XProperty;
@ -47,6 +49,8 @@ public abstract class AbstractPropertyHolder implements PropertyHolder {
protected AbstractPropertyHolder parent;
private Map<String, Column[]> holderColumnOverride;
private Map<String, Column[]> currentPropertyColumnOverride;
private Map<String, ColumnTransformer> holderColumnTransformerOverride;
private Map<String, ColumnTransformer> currentPropertyColumnTransformerOverride;
private Map<String, JoinColumn[]> holderJoinColumnOverride;
private Map<String, JoinColumn[]> currentPropertyJoinColumnOverride;
private Map<String, JoinTable> holderJoinTableOverride;
@ -170,6 +174,7 @@ protected MetadataBuildingContext getContext() {
protected void setCurrentProperty(XProperty property) {
if ( property == null ) {
this.currentPropertyColumnOverride = null;
this.currentPropertyColumnTransformerOverride = null;
this.currentPropertyJoinColumnOverride = null;
this.currentPropertyJoinTableOverride = null;
this.currentPropertyForeignKeyOverride = null;
@ -180,6 +185,11 @@ protected void setCurrentProperty(XProperty property) {
this.currentPropertyColumnOverride = null;
}
this.currentPropertyColumnTransformerOverride = buildColumnTransformerOverride( property, getPath() );
if ( this.currentPropertyColumnTransformerOverride.size() == 0 ) {
this.currentPropertyColumnTransformerOverride = null;
}
this.currentPropertyJoinColumnOverride = buildJoinColumnOverride( property, getPath() );
if ( this.currentPropertyJoinColumnOverride.size() == 0 ) {
this.currentPropertyJoinColumnOverride = null;
@ -247,6 +257,21 @@ public Column[] getOverriddenColumn(String propertyName) {
return result;
}
@Override
public ColumnTransformer getOverriddenColumnTransformer(String logicalColumnName) {
ColumnTransformer override = null;
if ( parent != null ) {
override = parent.getOverriddenColumnTransformer( logicalColumnName );
}
if ( override == null && currentPropertyColumnTransformerOverride != null ) {
override = currentPropertyColumnTransformerOverride.get( logicalColumnName );
}
if ( override == null && holderColumnTransformerOverride != null ) {
override = holderColumnTransformerOverride.get( logicalColumnName );
}
return override;
}
/**
* Get column overriding, property first, then parent, then holder
* find the overridden rules from the exact property name.
@ -375,6 +400,7 @@ private JoinTable getExactOverriddenJoinTable(String propertyName) {
private void buildHierarchyColumnOverride(XClass element) {
XClass current = element;
Map<String, Column[]> columnOverride = new HashMap<>();
Map<String, ColumnTransformer> columnTransformerOverride = new HashMap<>();
Map<String, JoinColumn[]> joinColumnOverride = new HashMap<>();
Map<String, JoinTable> joinTableOverride = new HashMap<>();
Map<String, ForeignKey> foreignKeyOverride = new HashMap<>();
@ -383,14 +409,17 @@ private void buildHierarchyColumnOverride(XClass element) {
|| current.isAnnotationPresent( Embeddable.class ) ) {
//FIXME is embeddable override?
Map<String, Column[]> currentOverride = buildColumnOverride( current, getPath() );
Map<String, ColumnTransformer> currentTransformerOverride = buildColumnTransformerOverride( current, getPath() );
Map<String, JoinColumn[]> currentJoinOverride = buildJoinColumnOverride( current, getPath() );
Map<String, JoinTable> currentJoinTableOverride = buildJoinTableOverride( current, getPath() );
Map<String, ForeignKey> currentForeignKeyOverride = buildForeignKeyOverride( current, getPath() );
currentOverride.putAll( columnOverride ); //subclasses have precedence over superclasses
currentTransformerOverride.putAll( columnTransformerOverride ); //subclasses have precedence over superclasses
currentJoinOverride.putAll( joinColumnOverride ); //subclasses have precedence over superclasses
currentJoinTableOverride.putAll( joinTableOverride ); //subclasses have precedence over superclasses
currentForeignKeyOverride.putAll( foreignKeyOverride ); //subclasses have precedence over superclasses
columnOverride = currentOverride;
columnTransformerOverride = currentTransformerOverride;
joinColumnOverride = currentJoinOverride;
joinTableOverride = currentJoinTableOverride;
foreignKeyOverride = currentForeignKeyOverride;
@ -399,6 +428,7 @@ private void buildHierarchyColumnOverride(XClass element) {
}
holderColumnOverride = columnOverride.size() > 0 ? columnOverride : null;
holderColumnTransformerOverride = columnTransformerOverride.size() > 0 ? columnTransformerOverride : null;
holderJoinColumnOverride = joinColumnOverride.size() > 0 ? joinColumnOverride : null;
holderJoinTableOverride = joinTableOverride.size() > 0 ? joinTableOverride : null;
holderForeignKeyOverride = foreignKeyOverride.size() > 0 ? foreignKeyOverride : null;
@ -448,6 +478,34 @@ else if ( multipleOverrides != null ) {
return columnOverride;
}
private static Map<String, ColumnTransformer> buildColumnTransformerOverride(XAnnotatedElement element, String path) {
Map<String, ColumnTransformer> columnOverride = new HashMap<>();
if ( element != null ) {
ColumnTransformer singleOverride = element.getAnnotation( ColumnTransformer.class );
ColumnTransformers multipleOverrides = element.getAnnotation( ColumnTransformers.class );
ColumnTransformer[] overrides;
if ( singleOverride != null ) {
overrides = new ColumnTransformer[]{ singleOverride };
}
else if ( multipleOverrides != null ) {
overrides = multipleOverrides.value();
}
else {
overrides = null;
}
if ( overrides != null ) {
for ( ColumnTransformer depAttr : overrides ) {
columnOverride.put(
depAttr.forColumn(),
depAttr
);
}
}
}
return columnOverride;
}
private static Map<String, JoinColumn[]> buildJoinColumnOverride(XAnnotatedElement element, String path) {
Map<String, JoinColumn[]> columnOverride = new HashMap<>();
if ( element != null ) {

View File

@ -843,6 +843,9 @@ private void extractDataFromPropertyData(PropertyData inferredData) {
if ( inferredData != null ) {
XProperty property = inferredData.getProperty();
if ( property != null ) {
if ( propertyHolder.isComponent() ) {
processExpression( propertyHolder.getOverriddenColumnTransformer( logicalColumnName ) );
}
processExpression( property.getAnnotation( ColumnTransformer.class ) );
ColumnTransformers annotations = property.getAnnotation( ColumnTransformers.class );
if (annotations != null) {

View File

@ -11,6 +11,7 @@
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@ -39,6 +40,9 @@
import org.hibernate.annotations.CollectionTypeRegistrations;
import org.hibernate.annotations.Columns;
import org.hibernate.annotations.Comment;
import org.hibernate.annotations.CompositeType;
import org.hibernate.annotations.CompositeTypeRegistration;
import org.hibernate.annotations.CompositeTypeRegistrations;
import org.hibernate.annotations.DialectOverride;
import org.hibernate.annotations.DialectOverride.OverridesAnnotation;
import org.hibernate.annotations.DiscriminatorFormula;
@ -139,6 +143,9 @@
import org.hibernate.mapping.UnionSubclass;
import org.hibernate.metamodel.spi.EmbeddableInstantiator;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.property.access.internal.PropertyAccessStrategyCompositeUserTypeImpl;
import org.hibernate.property.access.internal.PropertyAccessStrategyMixedImpl;
import org.hibernate.property.access.spi.PropertyAccessStrategy;
import org.hibernate.resource.beans.spi.ManagedBean;
import org.hibernate.resource.beans.spi.ManagedBeanRegistry;
import org.hibernate.type.CustomType;
@ -147,6 +154,7 @@
import org.hibernate.type.descriptor.java.spi.JavaTypeRegistry;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.spi.TypeConfiguration;
import org.hibernate.usertype.CompositeUserType;
import org.hibernate.usertype.UserType;
import jakarta.persistence.AttributeConverter;
@ -392,6 +400,7 @@ public static void bindPackage(ClassLoaderService cls, String packageName, Metad
handleTypeDescriptorRegistrations( pckg, context );
bindEmbeddableInstantiatorRegistrations( pckg, context );
bindCompositeUserTypeRegistrations( pckg, context );
bindGenericGenerators( pckg, context );
bindQueries( pckg, context );
@ -651,6 +660,7 @@ public static void bindClass(
HashMap<String, IdentifierGeneratorDefinition> classGenerators = buildGenerators( clazzToProcess, context );
handleTypeDescriptorRegistrations( clazzToProcess, context );
bindEmbeddableInstantiatorRegistrations( clazzToProcess, context );
bindCompositeUserTypeRegistrations( clazzToProcess, context );
// check properties
final InheritanceState.ElementsToProcess elementsToProcess = inheritanceState.getElementsToProcess();
@ -1111,6 +1121,32 @@ private static void handleEmbeddableInstantiatorRegistration(
);
}
private static void bindCompositeUserTypeRegistrations(XAnnotatedElement annotatedElement, MetadataBuildingContext context) {
final CompositeTypeRegistration singleRegistration =
annotatedElement.getAnnotation( CompositeTypeRegistration.class );
if ( singleRegistration != null ) {
handleCompositeUserTypeRegistration( context, singleRegistration );
}
else {
final CompositeTypeRegistrations annotation = annotatedElement.getAnnotation( CompositeTypeRegistrations.class );
if ( annotation != null ) {
final CompositeTypeRegistration[] registrations = annotation.value();
for ( CompositeTypeRegistration registration : registrations ) {
handleCompositeUserTypeRegistration(context, registration);
}
}
}
}
private static void handleCompositeUserTypeRegistration(
MetadataBuildingContext context,
CompositeTypeRegistration annotation) {
context.getMetadataCollector().registerCompositeUserType(
annotation.embeddableClass(),
annotation.userType()
);
}
/**
* Process all discriminator-related metadata per rules for "single table" inheritance
*/
@ -1350,6 +1386,7 @@ private static boolean mapAsIdClass(
true,
false,
null,
null,
context,
inheritanceStatePerClass
);
@ -2191,9 +2228,13 @@ private static AnnotatedColumn[] bindBasic(
|| property.isAnnotationPresent( Embedded.class )
|| property.isAnnotationPresent( EmbeddedId.class )
|| returnedClass.isAnnotationPresent( Embeddable.class );
final Class<? extends CompositeUserType<?>> compositeUserType = resolveCompositeUserType(
inferredData.getProperty(),
inferredData.getClassOrElement(),
context
);
if ( isComponent ) {
if ( isComponent || compositeUserType != null ) {
String referencedEntityName = null;
if ( isOverridden ) {
PropertyData mapsIdProperty = getPropertyOverriddenByMapperOrMapsId(
@ -2214,6 +2255,7 @@ private static AnnotatedColumn[] bindBasic(
inheritanceStatePerClass,
referencedEntityName,
determineCustomInstantiator(property, returnedClass, context),
compositeUserType,
isOverridden ? ( AnnotatedJoinColumn[] ) columns : null
);
}
@ -2829,6 +2871,29 @@ private static Class<? extends EmbeddableInstantiator> determineCustomInstantiat
return null;
}
private static Class<? extends CompositeUserType<?>> resolveCompositeUserType(
XProperty property,
XClass returnedClass,
MetadataBuildingContext context) {
if ( property != null ) {
final CompositeType compositeType = property.getAnnotation( CompositeType.class );
if ( compositeType != null ) {
return compositeType.value();
}
}
if ( returnedClass != null ) {
final Class<?> embeddableClass = context.getBootstrapContext()
.getReflectionManager()
.toClass( returnedClass );
if ( embeddableClass != null ) {
return context.getMetadataCollector().findRegisteredCompositeUserType( embeddableClass );
}
}
return null;
}
private static boolean isGlobalGeneratorNameGlobal(MetadataBuildingContext context) {
return context.getBootstrapContext().getJpaCompliance().isGlobalGeneratorScopeEnabled();
}
@ -3051,6 +3116,7 @@ private static PropertyBinder bindComponent(
Map<XClass, InheritanceState> inheritanceStatePerClass,
String referencedEntityName, //is a component who is overridden by a @MapsId
Class<? extends EmbeddableInstantiator> customInstantiatorImpl,
Class<? extends CompositeUserType<?>> compositeUserTypeClass,
AnnotatedJoinColumn[] columns) {
Component comp;
if ( referencedEntityName != null ) {
@ -3081,6 +3147,7 @@ private static PropertyBinder bindComponent(
isIdentifierMapper,
false,
customInstantiatorImpl,
compositeUserTypeClass,
buildingContext,
inheritanceStatePerClass
);
@ -3128,6 +3195,7 @@ public static Component fillComponent(
boolean isIdentifierMapper,
boolean inSecondPass,
Class<? extends EmbeddableInstantiator> customInstantiatorImpl,
Class<? extends CompositeUserType<?>> compositeUserTypeClass,
MetadataBuildingContext buildingContext,
Map<XClass, InheritanceState> inheritanceStatePerClass) {
return fillComponent(
@ -3141,6 +3209,7 @@ public static Component fillComponent(
isIdentifierMapper,
inSecondPass,
customInstantiatorImpl,
compositeUserTypeClass,
buildingContext,
inheritanceStatePerClass
);
@ -3157,6 +3226,7 @@ public static Component fillComponent(
boolean isIdentifierMapper,
boolean inSecondPass,
Class<? extends EmbeddableInstantiator> customInstantiatorImpl,
Class<? extends CompositeUserType<?>> compositeUserTypeClass,
MetadataBuildingContext buildingContext,
Map<XClass, InheritanceState> inheritanceStatePerClass) {
/*
@ -3190,7 +3260,22 @@ public static Component fillComponent(
final XClass xClassProcessed = inferredData.getPropertyClass();
List<PropertyData> classElements = new ArrayList<>();
XClass returnedClassOrElement = inferredData.getClassOrElement();
final CompositeUserType<?> compositeUserType;
XClass returnedClassOrElement;
if ( compositeUserTypeClass == null ) {
compositeUserType = null;
returnedClassOrElement = inferredData.getClassOrElement();
}
else {
compositeUserType = buildingContext.getBootstrapContext()
.getServiceRegistry()
.getService( ManagedBeanRegistry.class )
.getBean( compositeUserTypeClass )
.getBeanInstance();
comp.setTypeName( compositeUserTypeClass.getName() );
returnedClassOrElement = buildingContext.getBootstrapContext().getReflectionManager().toXClass( compositeUserType.embeddable() );
}
List<PropertyData> baseClassElements = null;
Map<String, PropertyData> orderedBaseClassElements = new HashMap<>();
@ -3295,6 +3380,7 @@ public static Component fillComponent(
handleTypeDescriptorRegistrations( property, buildingContext );
bindEmbeddableInstantiatorRegistrations( property, buildingContext );
bindCompositeUserTypeRegistrations( property, buildingContext );
}
else {
Map<String, IdentifierGeneratorDefinition> localGenerators =
@ -3310,6 +3396,24 @@ public static Component fillComponent(
}
}
}
if ( compositeUserType != null ) {
comp.sortProperties();
final List<String> sortedPropertyNames = new ArrayList<>( comp.getPropertySpan() );
final List<Type> sortedPropertyTypes = new ArrayList<>( comp.getPropertySpan() );
final PropertyAccessStrategy strategy = new PropertyAccessStrategyCompositeUserTypeImpl( compositeUserType, sortedPropertyNames, sortedPropertyTypes );
for ( Property property : comp.getProperties() ) {
sortedPropertyNames.add( property.getName() );
sortedPropertyTypes.add(
PropertyAccessStrategyMixedImpl.INSTANCE.buildPropertyAccess(
compositeUserType.embeddable(),
property.getName(),
false
).getGetter().getReturnType()
);
property.setPropertyAccessStrategy( strategy );
}
}
return comp;
}
@ -3371,6 +3475,7 @@ private static void bindIdClass(
false,
false,
null,
null,
buildingContext,
inheritanceStatePerClass
);

View File

@ -11,6 +11,7 @@
import jakarta.persistence.JoinColumn;
import jakarta.persistence.JoinTable;
import org.hibernate.annotations.ColumnTransformer;
import org.hibernate.annotations.common.reflection.XClass;
import org.hibernate.annotations.common.reflection.XProperty;
import org.hibernate.boot.model.convert.spi.ConverterDescriptor;
@ -76,6 +77,8 @@ default ForeignKey getOverriddenForeignKey(String propertyName) {
return null;
}
ColumnTransformer getOverriddenColumnTransformer(String logicalColumnName);
/**
* return
* - null if no join table is present,

View File

@ -26,6 +26,7 @@
import org.hibernate.annotations.CollectionIdJdbcType;
import org.hibernate.annotations.CollectionIdJdbcTypeCode;
import org.hibernate.annotations.CollectionType;
import org.hibernate.annotations.CompositeType;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.Filter;
import org.hibernate.annotations.FilterJoinTable;
@ -43,6 +44,7 @@
import org.hibernate.annotations.ListIndexJdbcTypeCode;
import org.hibernate.annotations.Loader;
import org.hibernate.annotations.ManyToAny;
import org.hibernate.annotations.MapKeyCustomCompositeType;
import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;
import org.hibernate.annotations.OptimisticLock;
@ -107,6 +109,7 @@
import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.resource.beans.spi.ManagedBean;
import org.hibernate.resource.beans.spi.ManagedBeanRegistry;
import org.hibernate.usertype.CompositeUserType;
import org.hibernate.usertype.ParameterizedType;
import org.hibernate.usertype.UserCollectionType;
@ -1731,7 +1734,12 @@ private void handleElementCollection(Collection collValue, AnnotatedColumn[] ele
}
}
if ( AnnotatedClassType.EMBEDDABLE == classType ) {
final Class<? extends CompositeUserType<?>> compositeUserType = resolveCompositeUserType(
property,
elementClass,
buildingContext
);
if ( AnnotatedClassType.EMBEDDABLE == classType || compositeUserType != null ) {
holder.prepare(property);
EntityBinder entityBinder = new EntityBinder();
@ -1774,6 +1782,7 @@ else if ( owner.getIdentifierMapper() != null && owner.getIdentifierMapper().get
false,
true,
resolveCustomInstantiator(property, elementClass, buildingContext),
compositeUserType,
buildingContext,
inheritanceStatePerClass
);
@ -2104,6 +2113,27 @@ private Class<? extends EmbeddableInstantiator> resolveCustomInstantiator(
return null;
}
private static Class<? extends CompositeUserType<?>> resolveCompositeUserType(
XProperty property,
XClass returnedClass,
MetadataBuildingContext context) {
final CompositeType compositeType = property.getAnnotation( CompositeType.class );
if ( compositeType != null ) {
return compositeType.value();
}
if ( returnedClass != null ) {
final Class<?> embeddableClass = context.getBootstrapContext()
.getReflectionManager()
.toClass( returnedClass );
if ( embeddableClass != null ) {
return context.getMetadataCollector().findRegisteredCompositeUserType( embeddableClass );
}
}
return null;
}
private String extractHqlOrderBy(jakarta.persistence.OrderBy jpaOrderBy) {
if ( jpaOrderBy != null ) {
return jpaOrderBy.value(); // Null not possible. In case of empty expression, apply default ordering.

View File

@ -15,6 +15,7 @@
import org.hibernate.AssertionFailure;
import org.hibernate.FetchMode;
import org.hibernate.MappingException;
import org.hibernate.annotations.MapKeyCustomCompositeType;
import org.hibernate.annotations.common.reflection.XClass;
import org.hibernate.annotations.common.reflection.XProperty;
import org.hibernate.boot.spi.BootstrapContext;
@ -48,6 +49,7 @@
import org.hibernate.mapping.Table;
import org.hibernate.mapping.Value;
import org.hibernate.resource.beans.spi.ManagedBean;
import org.hibernate.usertype.CompositeUserType;
import org.hibernate.usertype.UserCollectionType;
import jakarta.persistence.AttributeOverride;
@ -275,8 +277,12 @@ else if ( owner.getIdentifierMapper() != null && owner.getIdentifierMapper().get
else {
throw new AssertionFailure( "Unable to guess collection property accessor name" );
}
if ( AnnotatedClassType.EMBEDDABLE.equals( classType ) ) {
final Class<? extends CompositeUserType<?>> compositeUserType = resolveCompositeUserType(
property,
keyXClass,
buildingContext
);
if ( AnnotatedClassType.EMBEDDABLE.equals( classType ) || compositeUserType != null ) {
EntityBinder entityBinder = new EntityBinder();
PropertyData inferredData = isHibernateExtensionMapping()
@ -295,6 +301,7 @@ else if ( owner.getIdentifierMapper() != null && owner.getIdentifierMapper().get
false,
true,
null,
compositeUserType,
buildingContext,
inheritanceStatePerClass
);
@ -371,6 +378,27 @@ && getBuildingContext().getBuildingOptions().isNoConstraintByDefault() ) {
}
}
private static Class<? extends CompositeUserType<?>> resolveCompositeUserType(
XProperty property,
XClass returnedClass,
MetadataBuildingContext context) {
final MapKeyCustomCompositeType compositeType = property.getAnnotation( MapKeyCustomCompositeType.class );
if ( compositeType != null ) {
return compositeType.value();
}
if ( returnedClass != null ) {
final Class<?> embeddableClass = context.getBootstrapContext()
.getReflectionManager()
.toClass( returnedClass );
if ( embeddableClass != null ) {
return context.getMetadataCollector().findRegisteredCompositeUserType( embeddableClass );
}
}
return null;
}
private jakarta.persistence.ForeignKey getMapKeyForeignKey(XProperty property) {
final MapKeyJoinColumns mapKeyJoinColumns = property.getAnnotation( MapKeyJoinColumns.class );
if ( mapKeyJoinColumns != null ) {

View File

@ -40,6 +40,7 @@
import org.hibernate.mapping.ToOne;
import org.hibernate.mapping.Value;
import org.hibernate.metamodel.spi.EmbeddableInstantiator;
import org.hibernate.property.access.spi.PropertyAccessStrategy;
import org.hibernate.tuple.AnnotationValueGeneration;
import org.hibernate.tuple.AttributeBinder;
import org.hibernate.tuple.GenerationTiming;
@ -78,6 +79,7 @@ public class PropertyBinder {
private EntityBinder entityBinder;
private boolean isXToMany;
private String referencedEntityName;
private PropertyAccessStrategy propertyAccessStrategy;
public void setReferencedEntityName(String referencedEntityName) {
this.referencedEntityName = referencedEntityName;
@ -151,6 +153,10 @@ public void setBuildingContext(MetadataBuildingContext buildingContext) {
this.buildingContext = buildingContext;
}
public void setPropertyAccessStrategy(PropertyAccessStrategy propertyAccessStrategy) {
this.propertyAccessStrategy = propertyAccessStrategy;
}
public void setDeclaringClass(XClass declaringClass) {
this.declaringClass = declaringClass;
this.declaringClassSet = true;
@ -319,6 +325,7 @@ public Property makeProperty() {
property.setInsertable( insertable );
property.setUpdateable( updatable );
property.setPropertyAccessStrategy( propertyAccessStrategy );
inferOptimisticLocking(property);

View File

@ -3951,33 +3951,21 @@ public Size resolveSize(
Long length) {
final Size size = new Size();
int jdbcTypeCode = jdbcType.getDefaultSqlTypeCode();
switch (jdbcTypeCode) {
case Types.BIT:
// Use the default length for Boolean if we encounter the JPA default 255 instead
if ( javaType.getJavaTypeClass() == Boolean.class && length != null && length == Size.DEFAULT_LENGTH ) {
// Set the explicit length to null if we encounter the JPA default 255
if ( length != null && length == Size.DEFAULT_LENGTH ) {
length = null;
}
size.setLength( javaType.getDefaultSqlLength( Dialect.this, jdbcType ) );
break;
switch ( jdbcTypeCode ) {
case Types.BIT:
case Types.CHAR:
case Types.NCHAR:
// Use the default length for char and UUID if we encounter the JPA default 255 instead
if ( length != null && length == Size.DEFAULT_LENGTH ) {
if ( javaType.getJavaTypeClass() == Character.class || javaType.getJavaTypeClass() == UUID.class ) {
length = null;
}
}
size.setLength( javaType.getDefaultSqlLength( Dialect.this, jdbcType ) );
break;
case Types.VARCHAR:
case Types.NVARCHAR:
case Types.BINARY:
case Types.VARBINARY:
// Use the default length for UUID if we encounter the JPA default 255 instead
if ( javaType.getJavaTypeClass() == UUID.class && length != null && length == Size.DEFAULT_LENGTH ) {
length = null;
}
case Types.CLOB:
case Types.BLOB:
size.setLength( javaType.getDefaultSqlLength( Dialect.this, jdbcType ) );
break;
case Types.LONGVARCHAR:
@ -4009,13 +3997,6 @@ public Size resolveSize(
break;
case Types.NUMERIC:
case Types.DECIMAL:
size.setPrecision( javaType.getDefaultSqlPrecision( Dialect.this, jdbcType ) );
size.setScale( javaType.getDefaultSqlScale( Dialect.this, jdbcType ) );
break;
case Types.CLOB:
case Types.BLOB:
size.setLength( javaType.getDefaultSqlLength( Dialect.this, jdbcType ) );
break;
case SqlTypes.INTERVAL_SECOND:
size.setPrecision( javaType.getDefaultSqlPrecision( Dialect.this, jdbcType ) );
size.setScale( javaType.getDefaultSqlScale( Dialect.this, jdbcType ) );

View File

@ -45,6 +45,7 @@ public class Property implements Serializable, MetaAttributable {
private boolean optimisticLocked = true;
private ValueGeneration valueGenerationStrategy;
private String propertyAccessorName;
private PropertyAccessStrategy propertyAccessStrategy;
private boolean lazy;
private String lazyGroup;
private boolean optional;
@ -237,6 +238,14 @@ public void setPropertyAccessorName(String string) {
propertyAccessorName = string;
}
public PropertyAccessStrategy getPropertyAccessStrategy() {
return propertyAccessStrategy;
}
public void setPropertyAccessStrategy(PropertyAccessStrategy propertyAccessStrategy) {
this.propertyAccessStrategy = propertyAccessStrategy;
}
/**
* Approximate!
*/
@ -355,6 +364,10 @@ public Setter getSetter(Class clazz) throws PropertyNotFoundException, MappingEx
// todo : remove
public PropertyAccessStrategy getPropertyAccessStrategy(Class clazz) throws MappingException {
final PropertyAccessStrategy propertyAccessStrategy = getPropertyAccessStrategy();
if ( propertyAccessStrategy != null ) {
return propertyAccessStrategy;
}
String accessName = getPropertyAccessorName();
if ( accessName == null ) {
if ( clazz == null || java.util.Map.class.equals( clazz ) ) {
@ -438,6 +451,7 @@ public Property copy() {
prop.setOptimisticLocked( isOptimisticLocked() );
prop.setValueGenerationStrategy( getValueGenerationStrategy() );
prop.setPropertyAccessorName( getPropertyAccessorName() );
prop.setPropertyAccessStrategy( getPropertyAccessStrategy() );
prop.setLazy( isLazy() );
prop.setLazyGroup( getLazyGroup() );
prop.setOptional( isOptional() );

View File

@ -0,0 +1,40 @@
/*
* 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.metamodel.internal;
import java.util.function.Supplier;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.spi.EmbeddableInstantiator;
import org.hibernate.usertype.CompositeUserType;
/**
* @author Christian Beikov
*/
public class EmbeddableCompositeUserTypeInstantiator implements EmbeddableInstantiator {
private final CompositeUserType<Object> userType;
public EmbeddableCompositeUserTypeInstantiator(CompositeUserType<Object> userType) {
this.userType = userType;
}
@Override
public Object instantiate(Supplier<Object[]> valuesAccess, SessionFactoryImplementor sessionFactory) {
return userType.instantiate( valuesAccess, sessionFactory );
}
@Override
public boolean isInstance(Object object, SessionFactoryImplementor sessionFactory) {
return userType.returnedClass().isInstance( object );
}
@Override
public boolean isSameClass(Object object, SessionFactoryImplementor sessionFactory) {
return object.getClass().equals( userType.returnedClass() );
}
}

View File

@ -31,6 +31,10 @@
import org.hibernate.property.access.spi.BuiltInPropertyAccessStrategies;
import org.hibernate.property.access.spi.PropertyAccess;
import org.hibernate.property.access.spi.PropertyAccessStrategy;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.java.spi.JavaTypeRegistry;
import org.hibernate.type.internal.CompositeUserTypeJavaTypeWrapper;
import org.hibernate.usertype.CompositeUserType;
/**
* @author Steve Ebersole
@ -45,12 +49,11 @@ public EmbeddableRepresentationStrategyPojo(
Component bootDescriptor,
Supplier<EmbeddableMappingType> runtimeDescriptorAccess,
EmbeddableInstantiator customInstantiator,
CompositeUserType<Object> compositeUserType,
RuntimeModelCreationContext creationContext) {
super(
bootDescriptor,
creationContext.getTypeConfiguration()
.getJavaTypeRegistry()
.resolveDescriptor( bootDescriptor.getComponentClass() ),
resolveEmbeddableJavaType( bootDescriptor, compositeUserType, creationContext ),
creationContext
);
@ -75,6 +78,20 @@ public EmbeddableRepresentationStrategyPojo(
: determineInstantiator( bootDescriptor, runtimeDescriptorAccess, creationContext );
}
private static <T> JavaType<T> resolveEmbeddableJavaType(
Component bootDescriptor,
CompositeUserType<T> compositeUserType,
RuntimeModelCreationContext creationContext) {
final JavaTypeRegistry javaTypeRegistry = creationContext.getTypeConfiguration().getJavaTypeRegistry();
if ( compositeUserType == null ) {
return javaTypeRegistry.resolveDescriptor( bootDescriptor.getComponentClass() );
}
return javaTypeRegistry.resolveDescriptor(
compositeUserType.returnedClass(),
() -> new CompositeUserTypeJavaTypeWrapper<>( compositeUserType )
);
}
private EmbeddableInstantiator determineInstantiator(
Component bootDescriptor,
Supplier<EmbeddableMappingType> runtimeDescriptorAccess,

View File

@ -117,6 +117,7 @@ public EntityRepresentationStrategyPojoStandard(
.getMappingModelPart().getEmbeddableTypeDescriptor(),
// we currently do not support custom instantiators for identifiers
null,
null,
creationContext
);
}
@ -127,6 +128,7 @@ else if ( bootDescriptorIdentifier != null ) {
.getMappingModelPart().getEmbeddableTypeDescriptor(),
// we currently do not support custom instantiators for identifiers
null,
null,
creationContext
);
}

View File

@ -20,6 +20,7 @@
import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.resource.beans.spi.ManagedBeanRegistry;
import org.hibernate.usertype.CompositeUserType;
/**
* @author Steve Ebersole
@ -76,6 +77,21 @@ public EmbeddableRepresentationStrategy resolveStrategy(
}
}
final CompositeUserType<Object> compositeUserType;
if ( bootDescriptor.getTypeName() != null ) {
compositeUserType = (CompositeUserType<Object>) creationContext.getBootstrapContext()
.getServiceRegistry()
.getService( ManagedBeanRegistry.class )
.getBean(
creationContext.getBootstrapContext()
.getClassLoaderAccess()
.classForName( bootDescriptor.getTypeName() )
)
.getBeanInstance();
}
else {
compositeUserType = null;
}
final EmbeddableInstantiator customInstantiator;
if ( bootDescriptor.getCustomInstantiator() != null ) {
final Class<? extends EmbeddableInstantiator> customInstantiatorImpl = bootDescriptor.getCustomInstantiator();
@ -85,6 +101,9 @@ public EmbeddableRepresentationStrategy resolveStrategy(
.getBean( customInstantiatorImpl )
.getBeanInstance();
}
else if ( compositeUserType != null ) {
customInstantiator = new EmbeddableCompositeUserTypeInstantiator( compositeUserType );
}
else {
customInstantiator = null;
}
@ -108,6 +127,7 @@ public EmbeddableRepresentationStrategy resolveStrategy(
bootDescriptor,
runtimeDescriptorAccess,
customInstantiator,
compositeUserType,
creationContext
);
}

View File

@ -213,7 +213,7 @@ public MappingType getMappedType() {
@Override
public String getFetchableName() {
return nature == Nature.ELEMENT ? "{value}" : "{key}";
return nature.getName();
}
@Override

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.property.access.internal;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.Map;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.internal.util.ReflectHelper;
import org.hibernate.property.access.spi.Getter;
import org.hibernate.property.access.spi.PropertyAccess;
import org.hibernate.property.access.spi.PropertyAccessStrategy;
import org.hibernate.property.access.spi.Setter;
/**
* PropertyAccessor for accessing the wrapped property via get/set pair, which may be nonpublic.
*
* @author Steve Ebersole
*
* @see PropertyAccessStrategyBasicImpl
*/
public class PropertyAccessCompositeUserTypeImpl implements PropertyAccess, Getter {
private final PropertyAccessStrategyCompositeUserTypeImpl strategy;
private final int propertyIndex;
public PropertyAccessCompositeUserTypeImpl(PropertyAccessStrategyCompositeUserTypeImpl strategy, String property) {
this.strategy = strategy;
this.propertyIndex = strategy.sortedPropertyNames.indexOf( property );
}
@Override
public PropertyAccessStrategy getPropertyAccessStrategy() {
return strategy;
}
@Override
public Getter getGetter() {
return this;
}
@Override
public Setter getSetter() {
return null;
}
@Override
public Object get(Object owner) {
return strategy.compositeUserType.getPropertyValue( owner, propertyIndex );
}
@Override
public Object getForInsert(Object owner, Map mergeMap, SharedSessionContractImplementor session) {
return get( owner );
}
@Override
public Class<?> getReturnTypeClass() {
return ReflectHelper.getClass( strategy.sortedPropertyTypes.get(propertyIndex) );
}
@Override
public Type getReturnType() {
return strategy.sortedPropertyTypes.get(propertyIndex);
}
@Override
public Member getMember() {
return null;
}
@Override
public String getMethodName() {
return null;
}
@Override
public Method getMethod() {
return null;
}
}

View File

@ -0,0 +1,41 @@
/*
* 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.property.access.internal;
import java.lang.reflect.Type;
import java.util.List;
import org.hibernate.property.access.spi.PropertyAccess;
import org.hibernate.property.access.spi.PropertyAccessStrategy;
import org.hibernate.usertype.CompositeUserType;
/**
* Defines a strategy for accessing property values via a CompositeUserType.
*
* @author Christian Beikov
*/
public class PropertyAccessStrategyCompositeUserTypeImpl implements PropertyAccessStrategy {
final CompositeUserType<Object> compositeUserType;
final List<String> sortedPropertyNames;
final List<Type> sortedPropertyTypes;
public PropertyAccessStrategyCompositeUserTypeImpl(
CompositeUserType<?> compositeUserType,
List<String> sortedPropertyNames,
List<Type> sortedPropertyTypes) {
//noinspection unchecked
this.compositeUserType = (CompositeUserType<Object>) compositeUserType;
this.sortedPropertyNames = sortedPropertyNames;
this.sortedPropertyTypes = sortedPropertyTypes;
}
@Override
public PropertyAccess buildPropertyAccess(Class<?> containerJavaType, final String propertyName, boolean setterRequired) {
return new PropertyAccessCompositeUserTypeImpl( this, propertyName );
}
}

View File

@ -12,9 +12,12 @@
import java.util.function.Consumer;
import org.hibernate.LockMode;
import org.hibernate.metamodel.mapping.CollectionPart;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping;
import org.hibernate.query.results.complete.CompleteFetchBuilderEntityValuedModelPart;
import org.hibernate.query.results.dynamic.DynamicFetchBuilder;
import org.hibernate.query.results.dynamic.DynamicResultBuilderEntityStandard;
import org.hibernate.query.spi.NavigablePath;
import org.hibernate.query.named.FetchMemento;
@ -27,6 +30,9 @@
* @author Steve Ebersole
*/
public class FetchMementoHbmStandard implements FetchMemento, FetchMemento.Parent {
private static final String ELEMENT_PREFIX = "element.";
public interface FetchParentMemento {
NavigablePath getNavigablePath();
FetchableContainer getFetchableContainer();
@ -84,6 +90,28 @@ public FetchBuilder resolve(
tableAlias,
navigablePath
);
FetchBuilder element = fetchBuilderMap.get( "element" );
if ( element != null ) {
if ( element instanceof DynamicFetchBuilder ) {
resultBuilder.addIdColumnAliases(
( (DynamicFetchBuilder) element ).getColumnAliases().toArray( new String[0] )
);
}
else {
resultBuilder.addIdColumnAliases(
( (CompleteFetchBuilderEntityValuedModelPart) element ).getColumnAliases().toArray( new String[0] )
);
}
}
FetchBuilder index = fetchBuilderMap.get( "index" );
if ( index != null ) {
resultBuilder.addFetchBuilder( CollectionPart.Nature.INDEX.getName(), index );
}
for ( Map.Entry<String, FetchBuilder> entry : fetchBuilderMap.entrySet() ) {
if ( entry.getKey().startsWith( ELEMENT_PREFIX ) ) {
resultBuilder.addFetchBuilder( entry.getKey().substring( ELEMENT_PREFIX.length() ), entry.getValue() );
}
}
}
else {
resultBuilder = new DynamicResultBuilderEntityStandard(
@ -91,6 +119,7 @@ public FetchBuilder resolve(
tableAlias,
navigablePath
);
fetchBuilderMap.forEach( resultBuilder::addFetchBuilder );
}
return new DynamicFetchBuilderLegacy(
tableAlias,

View File

@ -66,6 +66,10 @@ public EntityValuedFetchable getReferencedPart() {
return modelPart;
}
public List<String> getColumnAliases() {
return columnAliases;
}
@Override
public Fetch buildFetch(
FetchParent parent,

View File

@ -19,16 +19,16 @@
*/
public abstract class AbstractFetchBuilderContainer<T extends AbstractFetchBuilderContainer<T>>
implements DynamicFetchBuilderContainer {
private Map<String, DynamicFetchBuilder> fetchBuilderMap;
private Map<String, FetchBuilder> fetchBuilderMap;
protected AbstractFetchBuilderContainer() {
}
protected AbstractFetchBuilderContainer(AbstractFetchBuilderContainer<T> original) {
if ( original.fetchBuilderMap != null ) {
final Map<String, DynamicFetchBuilder> fetchBuilderMap = new HashMap<>( original.fetchBuilderMap.size() );
for ( Map.Entry<String, DynamicFetchBuilder> entry : original.fetchBuilderMap.entrySet() ) {
final DynamicFetchBuilder fetchBuilder;
final Map<String, FetchBuilder> fetchBuilderMap = new HashMap<>( original.fetchBuilderMap.size() );
for ( Map.Entry<String, FetchBuilder> entry : original.fetchBuilderMap.entrySet() ) {
final FetchBuilder fetchBuilder;
if ( entry.getValue() instanceof DynamicFetchBuilderStandard ) {
fetchBuilder = ( (DynamicFetchBuilderStandard) entry.getValue() ).cacheKeyInstance( this );
}
@ -44,7 +44,7 @@ protected AbstractFetchBuilderContainer(AbstractFetchBuilderContainer<T> origina
protected abstract String getPropertyBase();
@Override
public DynamicFetchBuilder findFetchBuilder(String fetchableName) {
public FetchBuilder findFetchBuilder(String fetchableName) {
return fetchBuilderMap == null ? null : fetchBuilderMap.get( fetchableName );
}
@ -93,7 +93,7 @@ public DynamicFetchBuilder addProperty(String propertyName) {
return fetchBuilder;
}
public void addFetchBuilder(String propertyName, DynamicFetchBuilder fetchBuilder) {
public void addFetchBuilder(String propertyName, FetchBuilder fetchBuilder) {
if ( fetchBuilderMap == null ) {
fetchBuilderMap = new HashMap<>();
}

View File

@ -6,6 +6,8 @@
*/
package org.hibernate.query.results.dynamic;
import org.hibernate.query.results.FetchBuilder;
/**
* @author Steve Ebersole
*/
@ -13,7 +15,7 @@ public interface DynamicFetchBuilderContainer {
/**
* Locate an explicit fetch definition for the named fetchable
*/
DynamicFetchBuilder findFetchBuilder(String fetchableName);
FetchBuilder findFetchBuilder(String fetchableName);
/**
* Add a property mapped to a single column.
@ -29,4 +31,6 @@ public interface DynamicFetchBuilderContainer {
* Add a property whose columns can later be defined using {@link DynamicFetchBuilder#addColumnAlias}
*/
DynamicFetchBuilder addProperty(String propertyName);
void addFetchBuilder(String propertyName, FetchBuilder fetchBuilder);
}

View File

@ -16,6 +16,7 @@
import org.hibernate.LockMode;
import org.hibernate.engine.FetchTiming;
import org.hibernate.metamodel.mapping.AttributeMapping;
import org.hibernate.metamodel.mapping.CollectionPart;
import org.hibernate.metamodel.mapping.ForeignKeyDescriptor;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
@ -44,7 +45,10 @@
* @author Steve Ebersole
* @author Christian Beikov
*/
public class DynamicFetchBuilderLegacy implements DynamicFetchBuilder, NativeQuery.FetchReturn {
public class DynamicFetchBuilderLegacy implements DynamicFetchBuilder, NativeQuery.FetchReturn, DynamicFetchBuilderContainer {
private static final String ELEMENT_PREFIX = CollectionPart.Nature.ELEMENT.getName() + ".";
private static final String INDEX_PREFIX = CollectionPart.Nature.INDEX.getName() + ".";
private final String tableAlias;
@ -112,7 +116,7 @@ public DynamicFetchBuilderLegacy cacheKeyInstance() {
tableAlias,
ownerTableAlias,
fetchableName,
List.copyOf( columnNames ),
columnNames == null ? null : List.copyOf( columnNames ),
fetchBuilderMap,
resultBuilderEntity == null ? null : resultBuilderEntity.cacheKeyInstance()
);
@ -193,6 +197,25 @@ public Fetch buildFetch(
);
}
}
try {
final NavigablePath currentRelativePath = creationState.getCurrentRelativePath();
final String prefix;
if ( currentRelativePath == null ) {
prefix = "";
}
else {
prefix = currentRelativePath.getFullPath()
.replace( ELEMENT_PREFIX, "" )
.replace( INDEX_PREFIX, "" ) + ".";
}
creationState.pushExplicitFetchMementoResolver(
relativePath -> {
if ( relativePath.startsWith( prefix ) ) {
return findFetchBuilder( relativePath.substring( prefix.length() ) );
}
return null;
}
);
return parent.generateFetchableFetch(
attributeMapping,
parent.resolveNavigablePath( attributeMapping ),
@ -202,6 +225,10 @@ public Fetch buildFetch(
domainResultCreationState
);
}
finally {
creationState.popExplicitFetchMementoResolver();
}
}
private void resolveSqlSelection(
String columnAlias,
@ -242,18 +269,37 @@ public NativeQuery.FetchReturn setLockMode(LockMode lockMode) {
}
@Override
public NativeQuery.FetchReturn addProperty(String propertyName, String columnAlias) {
public DynamicFetchBuilderLegacy addProperty(String propertyName, String columnAlias) {
addProperty( propertyName ).addColumnAlias( columnAlias );
return this;
}
@Override
public NativeQuery.ReturnProperty addProperty(String propertyName) {
public DynamicFetchBuilder addProperty(String propertyName) {
DynamicFetchBuilderStandard fetchBuilder = new DynamicFetchBuilderStandard( propertyName );
fetchBuilderMap.put( propertyName, fetchBuilder );
return fetchBuilder;
}
@Override
public FetchBuilder findFetchBuilder(String fetchableName) {
return fetchBuilderMap.get( fetchableName );
}
@Override
public DynamicFetchBuilderContainer addProperty(String propertyName, String... columnAliases) {
final DynamicFetchBuilder fetchBuilder = addProperty( propertyName );
for ( String columnAlias : columnAliases ) {
fetchBuilder.addColumnAlias( columnAlias );
}
return this;
}
@Override
public void addFetchBuilder(String propertyName, FetchBuilder fetchBuilder) {
fetchBuilderMap.put( propertyName, fetchBuilder );
}
@Override
public void visitFetchBuilders(BiConsumer<String, FetchBuilder> consumer) {
fetchBuilderMap.forEach( consumer );

View File

@ -127,7 +127,8 @@ else if ( attributeMapping instanceof EmbeddedAttributeMapping ) {
}
else if ( attributeMapping instanceof ToOneAttributeMapping ) {
final ToOneAttributeMapping toOneAttributeMapping = (ToOneAttributeMapping) attributeMapping;
toOneAttributeMapping.getForeignKeyDescriptor().visitKeySelectables( selectableConsumer );
toOneAttributeMapping.getForeignKeyDescriptor().getPart( toOneAttributeMapping.getSideNature() )
.forEachSelectable( selectableConsumer );
return parent.generateFetchableFetch(
attributeMapping,
fetchPath,

View File

@ -21,6 +21,7 @@
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.internal.SingleAttributeIdentifierMapping;
import org.hibernate.query.NativeQuery;
import org.hibernate.query.results.FetchBuilder;
import org.hibernate.query.spi.NavigablePath;
import org.hibernate.query.results.DomainResultCreationStateImpl;
import org.hibernate.query.results.ResultSetMappingSqlSelection;
@ -49,6 +50,7 @@ public class DynamicResultBuilderEntityStandard
implements DynamicResultBuilderEntity, NativeQuery.RootReturn {
private static final String ELEMENT_PREFIX = CollectionPart.Nature.ELEMENT.getName() + ".";
private static final String INDEX_PREFIX = CollectionPart.Nature.INDEX.getName() + ".";
private final NavigablePath navigablePath;
@ -192,12 +194,12 @@ private <T> T buildResultOrFetch(
);
final TableReference tableReference = tableGroup.getPrimaryTableReference();
final List<String> idColumnAliases;
final DynamicFetchBuilder idFetchBuilder;
final FetchBuilder idFetchBuilder;
if ( this.idColumnNames != null ) {
idColumnAliases = this.idColumnNames;
}
else if ( ( idFetchBuilder = findIdFetchBuilder() ) != null ) {
idColumnAliases = idFetchBuilder.getColumnAliases();
idColumnAliases = ( (DynamicFetchBuilder) idFetchBuilder ).getColumnAliases();
}
else {
idColumnAliases = null;
@ -237,7 +239,9 @@ else if ( ( idFetchBuilder = findIdFetchBuilder() ) != null ) {
prefix = "";
}
else {
prefix = currentRelativePath.getFullPath() + ".";
prefix = currentRelativePath.getFullPath()
.replace( ELEMENT_PREFIX, "" )
.replace( INDEX_PREFIX, "" ) + ".";
}
creationState.pushExplicitFetchMementoResolver(
relativePath -> {
@ -261,7 +265,7 @@ else if ( ( idFetchBuilder = findIdFetchBuilder() ) != null ) {
}
}
private DynamicFetchBuilder findIdFetchBuilder() {
private FetchBuilder findIdFetchBuilder() {
final EntityIdentifierMapping identifierMapping = entityMapping.getIdentifierMapping();
if ( identifierMapping instanceof SingleAttributeIdentifierMapping ) {
return findFetchBuilder( ( (SingleAttributeIdentifierMapping) identifierMapping ).getAttributeName() );

View File

@ -24,6 +24,7 @@
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.collections.ArrayHelper;
import org.hibernate.loader.internal.AliasConstantsHelper;
import org.hibernate.metamodel.mapping.CollectionPart;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.persister.collection.QueryableCollection;
@ -33,6 +34,7 @@
import org.hibernate.persister.entity.Loadable;
import org.hibernate.persister.entity.SQLLoadable;
import org.hibernate.query.NativeQuery;
import org.hibernate.query.results.dynamic.DynamicFetchBuilderContainer;
import org.hibernate.query.spi.NavigablePath;
import org.hibernate.query.results.FetchBuilder;
import org.hibernate.query.results.ResultSetMapping;
@ -41,6 +43,7 @@
import org.hibernate.query.results.dynamic.DynamicFetchBuilderLegacy;
import org.hibernate.query.results.dynamic.DynamicResultBuilderEntityStandard;
import org.hibernate.type.CollectionType;
import org.hibernate.type.ComponentType;
import org.hibernate.type.EntityType;
import org.hibernate.type.Type;
@ -254,7 +257,7 @@ private void applyFetchBuilder(
columnNames = Arrays.asList( keyColumnAliases );
if ( collectionPersister.hasIndex() ) {
resultBuilderEntity.addProperty(
"{key}", // That's what BasicValuedCollectionPart returns..
CollectionPart.Nature.INDEX.getName(),
collectionPersister.getIndexColumnAliases( collectionSuffix )
);
}
@ -326,8 +329,30 @@ private DynamicResultBuilderEntityStandard createSuffixedResultBuilder(
for ( String propertyName : loadable.getPropertyNames() ) {
final String[] columnAliases = loadable.getSubclassPropertyColumnAliases( propertyName, suffix );
if ( columnAliases.length == 0 ) {
final Type propertyType = loadable.getPropertyType( propertyName );
addFetchBuilder(
suffix,
loadable,
resultBuilderEntity,
tableAlias,
identifierAliases,
propertyName,
columnAliases,
propertyType
);
}
return resultBuilderEntity;
}
private void addFetchBuilder(
String suffix,
SQLLoadable loadable,
DynamicFetchBuilderContainer resultBuilderEntity,
String tableAlias,
String[] identifierAliases,
String propertyName,
String[] columnAliases,
Type propertyType) {
if ( propertyType instanceof CollectionType ) {
final CollectionType collectionType = (CollectionType) propertyType;
final String[] keyColumnAliases;
@ -342,13 +367,40 @@ private DynamicResultBuilderEntityStandard createSuffixedResultBuilder(
}
resultBuilderEntity.addProperty( propertyName, keyColumnAliases );
}
else if ( propertyType instanceof ComponentType ) {
final Map<String, FetchBuilder> fetchBuilderMap = new HashMap<>();
final DynamicFetchBuilderLegacy fetchBuilder = new DynamicFetchBuilderLegacy(
"",
tableAlias,
propertyName,
null,
fetchBuilderMap
);
final ComponentType componentType = (ComponentType) propertyType;
final String[] propertyNames = componentType.getPropertyNames();
final Type[] propertyTypes = componentType.getSubtypes();
int aliasIndex = 0;
for ( int i = 0; i < propertyNames.length; i++ ) {
final int columnSpan = propertyTypes[i].getColumnSpan( loadable.getFactory() );
addFetchBuilder(
suffix,
loadable,
fetchBuilder,
tableAlias,
identifierAliases,
propertyNames[i],
ArrayHelper.slice( columnAliases, aliasIndex, columnSpan ),
propertyTypes[i]
);
aliasIndex += columnSpan;
}
else {
resultBuilderEntity.addFetchBuilder( propertyName, fetchBuilder );
}
else if ( columnAliases.length != 0 ) {
resultBuilderEntity.addProperty( propertyName, columnAliases );
}
}
return resultBuilderEntity;
}
private CompleteResultBuilderCollectionStandard createSuffixedResultBuilder(
NativeQuery.CollectionReturn collectionReturn,

View File

@ -37,11 +37,13 @@
import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess;
import org.hibernate.property.access.spi.PropertyAccess;
import org.hibernate.query.sqm.SqmExpressible;
import org.hibernate.resource.beans.spi.ManagedBeanRegistry;
import org.hibernate.tuple.PropertyFactory;
import org.hibernate.tuple.StandardProperty;
import org.hibernate.tuple.ValueGeneration;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.spi.CompositeTypeImplementor;
import org.hibernate.usertype.CompositeUserType;
/**
* Handles "component" mappings
@ -63,6 +65,7 @@ public class ComponentType extends AbstractType implements CompositeTypeImplemen
private final boolean isKey;
private boolean hasNotNullProperty;
private final boolean createEmptyCompositesEnabled;
private final CompositeUserType<Object> compositeUserType;
private EmbeddableValuedModelPart mappingModelPart;
@ -102,6 +105,21 @@ public ComponentType(Component component, int[] originalPropertyOrder, MetadataB
buildingContext.getBootstrapContext().getServiceRegistry().getService( ConfigurationService.class ).getSettings(),
false
);
if ( component.getTypeName() != null ) {
//noinspection unchecked
this.compositeUserType = (CompositeUserType<Object>) buildingContext.getBootstrapContext()
.getServiceRegistry()
.getService( ManagedBeanRegistry.class )
.getBean(
buildingContext.getBootstrapContext()
.getClassLoaderAccess()
.classForName( component.getTypeName() )
)
.getBeanInstance();
}
else {
this.compositeUserType = null;
}
}
public boolean isKey() {
@ -162,6 +180,9 @@ public boolean isEqual(final Object x, final Object y) throws HibernateException
if ( x == y ) {
return true;
}
if ( compositeUserType != null ) {
return compositeUserType.equals( x, y );
}
// null value and empty component are considered equivalent
for ( int i = 0; i < propertySpan; i++ ) {
if ( !propertyTypes[i].isEqual( getPropertyValue( x, i ), getPropertyValue( y, i ) ) ) {
@ -177,6 +198,9 @@ public boolean isEqual(final Object x, final Object y, final SessionFactoryImple
if ( x == y ) {
return true;
}
if ( compositeUserType != null ) {
return compositeUserType.equals( x, y );
}
// null value and empty component are considered equivalent
for ( int i = 0; i < propertySpan; i++ ) {
if ( !propertyTypes[i].isEqual( getPropertyValue( x, i ), getPropertyValue( y, i ), factory ) ) {
@ -206,6 +230,9 @@ public boolean isMethodOf(Method method) {
@Override
public int getHashCode(final Object x) {
if ( compositeUserType != null ) {
return compositeUserType.hashCode( x );
}
int result = 17;
for ( int i = 0; i < propertySpan; i++ ) {
Object y = getPropertyValue( x, i );
@ -219,6 +246,9 @@ public int getHashCode(final Object x) {
@Override
public int getHashCode(final Object x, final SessionFactoryImplementor factory) {
if ( compositeUserType != null ) {
return compositeUserType.hashCode( x );
}
int result = 17;
for ( int i = 0; i < propertySpan; i++ ) {
Object y = getPropertyValue( x, i );
@ -456,6 +486,9 @@ public Object deepCopy(Object component, SessionFactoryImplementor factory) {
return null;
}
if ( compositeUserType != null ) {
return compositeUserType.deepCopy( component );
}
final Object[] values = getPropertyValues( component );
for ( int i = 0; i < propertySpan; i++ ) {
values[i] = propertyTypes[i].deepCopy( values[i], factory );
@ -487,6 +520,9 @@ public Object replace(
if ( original == null ) {
return null;
}
if ( compositeUserType != null ) {
return compositeUserType.replace( original, target, owner );
}
//if ( original == target ) return target;
final Object[] originalValues = getPropertyValues( original );
@ -532,6 +568,9 @@ public Object replace(
if ( original == null ) {
return null;
}
if ( compositeUserType != null ) {
return compositeUserType.replace( original, target, owner );
}
//if ( original == target ) return target;
@ -574,7 +613,7 @@ public CascadeStyle getCascadeStyle(int i) {
@Override
public boolean isMutable() {
return true;
return compositeUserType == null || compositeUserType.isMutable();
}
@Override
@ -584,6 +623,9 @@ public Serializable disassemble(Object value, SharedSessionContractImplementor s
if ( value == null ) {
return null;
}
else if ( compositeUserType != null ) {
return compositeUserType.disassemble( value );
}
else {
Object[] values = getPropertyValues( value );
for ( int i = 0; i < propertyTypes.length; i++ ) {
@ -600,6 +642,9 @@ public Object assemble(Serializable object, SharedSessionContractImplementor ses
if ( object == null ) {
return null;
}
else if ( compositeUserType != null ) {
return compositeUserType.assemble( object, owner );
}
else {
Object[] values = (Object[]) object;
Object[] assembled = new Object[values.length];

View File

@ -8,7 +8,9 @@
import java.util.Currency;
import org.hibernate.dialect.Dialect;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.jdbc.JdbcType;
/**
* Descriptor for {@link Currency} handling.
@ -53,4 +55,9 @@ public <X> Currency wrap(X value, WrapperOptions options) {
}
throw unknownWrap( value.getClass() );
}
@Override
public long getDefaultSqlLength(Dialect dialect, JdbcType jdbcType) {
return 3;
}
}

View File

@ -0,0 +1,154 @@
/*
* 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.type.internal;
import java.io.Serializable;
import java.util.Comparator;
import org.hibernate.SharedSessionContract;
import org.hibernate.annotations.Immutable;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.ImmutableMutabilityPlan;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.java.MutabilityPlan;
import org.hibernate.type.descriptor.java.MutabilityPlanExposer;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;
import org.hibernate.usertype.CompositeUserType;
/**
*
* @author Christian Beikov
*/
public class CompositeUserTypeJavaTypeWrapper<J> implements JavaType<J> {
protected final CompositeUserType<J> userType;
private final MutabilityPlan<J> mutabilityPlan;
private final Comparator<J> comparator;
public CompositeUserTypeJavaTypeWrapper(CompositeUserType<J> userType) {
this.userType = userType;
MutabilityPlan<J> resolvedMutabilityPlan = null;
if ( userType instanceof MutabilityPlanExposer ) {
//noinspection unchecked
resolvedMutabilityPlan = ( (MutabilityPlanExposer<J>) userType ).getExposedMutabilityPlan();
}
if ( resolvedMutabilityPlan == null ) {
final Class<J> jClass = userType.returnedClass();
if ( jClass != null ) {
if ( jClass.getAnnotation( Immutable.class ) != null ) {
resolvedMutabilityPlan = ImmutableMutabilityPlan.instance();
}
}
}
if ( resolvedMutabilityPlan == null ) {
resolvedMutabilityPlan = new MutabilityPlanWrapper<>( userType );
}
this.mutabilityPlan = resolvedMutabilityPlan;
if ( userType instanceof Comparator ) {
//noinspection unchecked
this.comparator = ( (Comparator<J>) userType );
}
else {
this.comparator = this::compare;
}
}
private int compare(J first, J second) {
if ( userType.equals( first, second ) ) {
return 0;
}
return Comparator.comparing( userType::hashCode ).compare( first, second );
}
@Override
public MutabilityPlan<J> getMutabilityPlan() {
return mutabilityPlan;
}
@Override
public JdbcType getRecommendedJdbcType(JdbcTypeIndicators context) {
return null;
}
@Override
public Comparator<J> getComparator() {
return comparator;
}
@Override
public int extractHashCode(J value) {
return userType.hashCode(value );
}
@Override
public boolean areEqual(J one, J another) {
return userType.equals( one, another );
}
@Override
public J fromString(CharSequence string) {
throw new UnsupportedOperationException( "No support for parsing UserType values from String: " + userType );
}
@Override
public <X> X unwrap(J value, Class<X> type, WrapperOptions options) {
assert value == null || userType.returnedClass().isInstance( value );
//noinspection unchecked
return (X) value;
}
@Override
public <X> J wrap(X value, WrapperOptions options) {
// assert value == null || userType.returnedClass().isInstance( value );
//noinspection unchecked
return (J) value;
}
@Override
public Class<J> getJavaTypeClass() {
return userType.returnedClass();
}
public static class MutabilityPlanWrapper<J> implements MutabilityPlan<J> {
private final CompositeUserType<J> userType;
public MutabilityPlanWrapper(CompositeUserType<J> userType) {
this.userType = userType;
}
@Override
public boolean isMutable() {
return userType.isMutable();
}
@Override
public J deepCopy(J value) {
//noinspection unchecked
return (J) userType.deepCopy( value );
}
@Override
public Serializable disassemble(J value, SharedSessionContract session) {
return userType.disassemble( value );
}
@Override
public J assemble(Serializable cached, SharedSessionContract session) {
//noinspection unchecked
return (J) userType.disassemble( cached );
}
}
}

View File

@ -0,0 +1,142 @@
/*
* 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.usertype;
import java.io.Serializable;
import java.util.function.Supplier;
import org.hibernate.HibernateException;
import org.hibernate.Incubating;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.spi.EmbeddableInstantiator;
/**
* A <tt>UserType</tt> that may be dereferenced in a query.
* This interface allows a custom type to define "properties".
* These need not necessarily correspond to physical JavaBeans
* style properties.<br>
* <br>
* A <tt>CompositeUserType</tt> may be used in almost every way
* that a component may be used. It may even contain many-to-one
* associations.<br>
* <br>
* Implementors must be immutable and must declare a public
* default constructor.<br>
* <br>
* Unlike <tt>UserType</tt>, cacheability does not depend upon
* serializability. Instead, <tt>assemble()</tt> and
* <tt>disassemble</tt> provide conversion to/from a cacheable
* representation.
* <br>
* Properties are ordered by the order of their names
* i.e. they are alphabetically ordered, such that
* <code>properties[i].name < properties[i + 1].name</code>
* for all <code>i >= 0</code>.
*/
@Incubating
public interface CompositeUserType<J> extends EmbeddableInstantiator {
/**
* Get the value of a property.
*
* @param component an instance of class mapped by this "type"
* @param property the property index
* @return the property value
* @throws HibernateException
*/
Object getPropertyValue(J component, int property) throws HibernateException;
@Override
J instantiate(Supplier<Object[]> values, SessionFactoryImplementor sessionFactory);
/**
* The class that represents the embeddable mapping of the type.
*
* @return Class
*/
Class<?> embeddable();
/**
* The class returned by {@code instantiate()}.
*
* @return Class
*/
Class<J> returnedClass();
/**
* Compare two instances of the class mapped by this type for persistence "equality".
* Equality of the persistent state.
*/
boolean equals(Object x, Object y);
/**
* Get a hashcode for the instance, consistent with persistence "equality"
*/
int hashCode(Object x);
/**
* Return a deep copy of the persistent state, stopping at entities and at
* collections. It is not necessary to copy immutable objects, or null
* values, in which case it is safe to simply return the argument.
*
* @param value the object to be cloned, which may be null
* @return Object a copy
*/
Object deepCopy(Object value);
/**
* Are objects of this type mutable?
*
* @return boolean
*/
boolean isMutable();
/**
* Transform the object into its cacheable representation. At the very least this
* method should perform a deep copy if the type is mutable. That may not be enough
* for some implementations, however; for example, associations must be cached as
* identifier values. (optional operation)
*
* @param value the object to be cached
* @return a cacheable representation of the object
*/
Serializable disassemble(Object value);
/**
* Reconstruct an object from the cacheable representation. At the very least this
* method should perform a deep copy if the type is mutable. (optional operation)
*
* @param cached the object to be cached
* @param owner the owner of the cached object
* @return a reconstructed object from the cacheable representation
*/
Object assemble(Serializable cached, Object owner);
/**
* During merge, replace the existing (target) value in the entity we are merging to
* with a new (original) value from the detached entity we are merging. For immutable
* objects, or null values, it is safe to simply return the first parameter. For
* mutable objects, it is safe to return a copy of the first parameter. For objects
* with component values, it might make sense to recursively replace component values.
*
* @param detached the value from the detached entity being merged
* @param managed the value in the managed entity
*
* @return the value to be merged
*/
Object replace(Object detached, Object managed, Object owner);
@Override
default boolean isInstance(Object object, SessionFactoryImplementor sessionFactory) {
return returnedClass().isInstance( object );
}
@Override
default boolean isSameClass(Object object, SessionFactoryImplementor sessionFactory) {
return object.getClass().equals( returnedClass() );
}
}

View File

@ -7,15 +7,13 @@
package org.hibernate.orm.test.sql.hand;
import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.math.BigDecimal;
import java.util.Currency;
import java.util.function.Supplier;
import org.hibernate.HibernateException;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.usertype.UserType;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.usertype.CompositeUserType;
/**
* This is a simple Hibernate custom mapping type for MonetaryAmount value types.
@ -23,17 +21,32 @@
*
* @author Max & Christian
*/
public class MonetaryAmountUserType implements UserType {
private static final int[] SQL_TYPES = {Types.NUMERIC, Types.VARCHAR};
public class MonetaryAmountUserType implements CompositeUserType<MonetaryAmount> {
@Override
public int[] sqlTypes() {
return SQL_TYPES;
public Object getPropertyValue(MonetaryAmount component, int property) throws HibernateException {
switch ( property ) {
case 0:
return component.getCurrency();
case 1:
return component.getValue();
}
throw new HibernateException( "Illegal property index: " + property );
}
@Override
public Class returnedClass() {
public MonetaryAmount instantiate(Supplier<Object[]> valueSupplier, SessionFactoryImplementor sessionFactory) {
final Object[] values = valueSupplier.get();
return new MonetaryAmount( (BigDecimal) values[0], (Currency) values[1] );
}
@Override
public Class<?> embeddable() {
return MonetaryAmountEmbeddable.class;
}
@Override
public Class<MonetaryAmount> returnedClass() {
return MonetaryAmount.class;
}
@ -58,29 +71,6 @@ public boolean equals(Object x, Object y) {
return x.equals( y );
}
@Override
public Object nullSafeGet(ResultSet rs, int position, SharedSessionContractImplementor session, Object owner) throws SQLException {
// needs CompositeUserType
throw new NotYetImplementedFor6Exception( getClass() );
}
@Override
public void nullSafeSet(
PreparedStatement statement,
Object value,
int index,
SharedSessionContractImplementor session) throws HibernateException, SQLException {
if ( value == null ) {
statement.setNull( index, Types.NUMERIC );
statement.setNull( index + 1, Types.VARCHAR );
}
else {
MonetaryAmount currency = (MonetaryAmount) value;
statement.setBigDecimal( index, currency.getValue() );
statement.setString( index + 1, currency.getCurrency().getCurrencyCode() );
}
}
@Override
public Serializable disassemble(Object value) throws HibernateException {
return (Serializable) value;
@ -100,4 +90,9 @@ public Object replace(Object original, Object target, Object owner) throws Hiber
public int hashCode(Object x) throws HibernateException {
return x.hashCode();
}
public static class MonetaryAmountEmbeddable {
private BigDecimal value;
private Currency currency;
}
}

View File

@ -135,7 +135,8 @@ public void testTextProperty() {
public void testImageProperty() {
Session s = openSession();
Transaction t = s.beginTransaction();
byte[] photo = buildLongByteArray( 15000, true );
// Make sure the last byte is non-zero as Sybase cuts that off
byte[] photo = buildLongByteArray( 14999, true );
ImageHolder holder = new ImageHolder( photo );
s.save( holder );
t.commit();

View File

@ -12,10 +12,9 @@
import org.junit.Test;
import org.hibernate.HibernateException;
import org.hibernate.query.Query;
import org.hibernate.procedure.ProcedureCall;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.engine.query.ParameterRecognitionException;
import org.hibernate.orm.test.sql.hand.Employment;
import org.hibernate.orm.test.sql.hand.Organization;
import org.hibernate.orm.test.sql.hand.Person;
@ -41,9 +40,10 @@ protected String getBaseForMappings() {
@Test
public void testScalarStoredProcedure() throws HibernateException, SQLException {
Session s = openSession();
Query namedQuery = s.getNamedQuery( "simpleScalar" );
// Query namedQuery = s.getNamedQuery( "simpleScalar" );
ProcedureCall namedQuery = s.createNamedStoredProcedureQuery( "simpleScalar" );
namedQuery.setParameter( "number", 43 );
List list = namedQuery.list();
List list = namedQuery.getResultList();
Object o[] = ( Object[] ) list.get( 0 );
assertEquals( o[0], "getAll" );
assertEquals( o[1], Long.valueOf( 43 ) );
@ -54,10 +54,11 @@ public void testScalarStoredProcedure() throws HibernateException, SQLException
public void testParameterHandling() throws HibernateException, SQLException {
Session s = openSession();
Query namedQuery = s.getNamedQuery( "paramhandling" );
// Query namedQuery = s.getNamedQuery( "paramhandling" );
ProcedureCall namedQuery = s.createNamedStoredProcedureQuery( "paramhandling" );
namedQuery.setParameter( 1, 10 );
namedQuery.setParameter( 2, 20 );
List list = namedQuery.list();
List list = namedQuery.getResultList();
Object[] o = ( Object[] ) list.get( 0 );
assertEquals( o[0], Long.valueOf( 10 ) );
assertEquals( o[1], Long.valueOf( 20 ) );
@ -69,11 +70,14 @@ public void testMixedParameterHandling() throws HibernateException, SQLException
inTransaction(
session -> {
try {
session.getNamedQuery( "paramhandling_mixed" );
session.createNamedStoredProcedureQuery( "paramhandling_mixed" );
fail( "Expecting an exception" );
}
catch (ParameterRecognitionException expected) {
// expected outcome
catch (IllegalArgumentException expected) {
assertEquals(
"Cannot mix named parameter with positional parameter registrations",
expected.getMessage()
);
}
catch (Exception other) {
throw new AssertionError( "Expecting a ParameterRecognitionException, but encountered " + other.getClass().getSimpleName(), other );
@ -95,9 +99,11 @@ public void testEntityStoredProcedure() throws HibernateException, SQLException
s.persist( jboss );
s.persist( gavin );
s.persist( emp );
s.flush();
Query namedQuery = s.getNamedQuery( "selectAllEmployments" );
List list = namedQuery.list();
// Query namedQuery = s.getNamedQuery( "selectAllEmployments" );
ProcedureCall namedQuery = s.createNamedStoredProcedureQuery( "selectAllEmployments" );
List list = namedQuery.getResultList();
assertTrue( list.get( 0 ) instanceof Employment );
s.delete( emp );
s.delete( ifa );

View File

@ -39,10 +39,10 @@
<return-property name="endDate" column="ENDDATE"/>
<return-property name="regionCode" column="REGIONCODE"/>
<return-property name="employmentId" column="EMPID"/>
<return-property name="salary">
<return-column name="`VALUE`"/>
<return-column name="CURRENCY"/>
</return-property>
<!-- as multi column properties are not supported via the
{}-syntax, we need to provide an explicit column list for salary via <return-property> -->
<return-property name="salary.value" column="`VALUE`"/>
<return-property name="salary.currency" column="CURRENCY"/>
</return>
{ call allEmployments() }
</sql-query>

View File

@ -10,8 +10,6 @@
import org.hibernate.orm.test.sql.hand.custom.CustomStoredProcTestSupport;
import org.hibernate.testing.RequiresDialect;
import org.hibernate.testing.orm.junit.NotImplementedYet;
import org.junit.Ignore;
/**
* Custom SQL tests for DB2
@ -19,9 +17,6 @@
* @author Max Rydahl Andersen
*/
@RequiresDialect( DB2Dialect.class )
// todo (6.0): needs a composite user type mechanism e.g. by providing a custom embeddable strategy or istantiator
@Ignore( "Missing support for composite user types" )
@NotImplementedYet
public class DB2CustomSQLTest extends CustomStoredProcTestSupport {
public String[] getMappings() {
return new String[] { "sql/hand/custom/db2/Mappings.hbm.xml" };

View File

@ -56,10 +56,12 @@
<property name="startDate" column="STARTDATE" not-null="true" update="false" insert="false"/>
<property name="endDate" column="ENDDATE" insert="false"/>
<property name="regionCode" column="REGIONCODE" update="false"/>
<property name="salary" type="org.hibernate.orm.test.sql.hand.MonetaryAmountUserType">
<column name="`VALUE`" sql-type="float"/>
<column name="CURRENCY"/>
<component name="salary" class="org.hibernate.orm.test.sql.hand.MonetaryAmountUserType">
<property name="value" column="`VALUE`">
<type name="float"/>
</property>
<property name="currency" column="CURRENCY"/>
</component>
<loader query-ref="employment"/>
<sql-insert>
INSERT INTO EMPLOYMENT
@ -165,12 +167,10 @@
<sql-query name="organizationCurrentEmployments">
<return alias="emp" class="Employment">
<return-property name="salary">
<!-- as multi column properties are not supported via the
{}-syntax, we need to provide an explicit column list for salary via <return-property> -->
<return-column name="`VALUE`"/>
<return-column name="CURRENCY"/>
</return-property>
<return-property name="salary.value" column="`VALUE`"/>
<return-property name="salary.currency" column="CURRENCY"/>
<!-- Here we are remapping endDate. Notice that we can still use {emp.endDate} in the SQL. -->
<return-property name="endDate" column="myEndDate"/>
</return>
@ -209,12 +209,10 @@
<return-property name="endDate" column="ENDDATE"/>
<return-property name="regionCode" column="REGIONCODE"/>
<return-property name="id" column="EMPID"/>
<return-property name="salary">
<!-- as multi column properties are not supported via the
{}-syntax, we need to provide an explicit column list for salary via <return-property> -->
<return-column name="`VALUE`"/>
<return-column name="CURRENCY"/>
</return-property>
<return-property name="salary.value" column="`VALUE`"/>
<return-property name="salary.currency" column="CURRENCY"/>
</return>
{ call selectAllEmployments() }
</sql-query>

View File

@ -10,16 +10,11 @@
import org.hibernate.orm.test.sql.hand.custom.CustomStoredProcTestSupport;
import org.hibernate.testing.RequiresDialect;
import org.hibernate.testing.orm.junit.NotImplementedYet;
import org.junit.Ignore;
/**
* @author Andrea Boriero
*/
@RequiresDialect(DerbyDialect.class)
// todo (6.0): needs a composite user type mechanism e.g. by providing a custom embeddable strategy or istantiator
@Ignore( "Missing support for composite user types" )
@NotImplementedYet
public class DerbyCustomSQLTest extends CustomStoredProcTestSupport {
public String[] getMappings() {
return new String[] {"sql/hand/custom/derby/Mappings.hbm.xml"};

View File

@ -22,7 +22,7 @@ public static void selectAllEmployments(ResultSet[] resultSets) throws SQLExcept
PreparedStatement statement = conn.prepareStatement(
"select EMPLOYEE, EMPLOYER, STARTDATE, ENDDATE," +
" REGIONCODE, EMPID, 'VALUE', CURRENCY" +
" REGIONCODE, EMPID, \"VALUE\", CURRENCY" +
" FROM EMPLOYMENT"
);
resultSets[0] = statement.executeQuery();

View File

@ -56,10 +56,12 @@
<property name="startDate" column="STARTDATE" not-null="true" update="false" insert="false"/>
<property name="endDate" column="ENDDATE" insert="false"/>
<property name="regionCode" column="REGIONCODE" update="false"/>
<property name="salary" type="org.hibernate.orm.test.sql.hand.MonetaryAmountUserType">
<column name="`VALUE`" sql-type="float"/>
<column name="CURRENCY"/>
<component name="salary" class="org.hibernate.orm.test.sql.hand.MonetaryAmountUserType">
<property name="value" column="`VALUE`">
<type name="float"/>
</property>
<property name="currency" column="CURRENCY"/>
</component>
<loader query-ref="employment"/>
<sql-insert>
INSERT INTO EMPLOYMENT
@ -165,12 +167,10 @@
<sql-query name="organizationCurrentEmployments">
<return alias="emp" class="Employment">
<return-property name="salary">
<!-- as multi column properties are not supported via the
{}-syntax, we need to provide an explicit column list for salary via <return-property> -->
<return-column name="`VALUE`"/>
<return-column name="CURRENCY"/>
</return-property>
<return-property name="salary.value" column="`VALUE`"/>
<return-property name="salary.currency" column="CURRENCY"/>
<!-- Here we are remapping endDate. Notice that we can still use {emp.endDate} in the SQL. -->
<return-property name="endDate" column="myEndDate"/>
</return>
@ -209,12 +209,10 @@
<return-property name="endDate" column="ENDDATE"/>
<return-property name="regionCode" column="REGIONCODE"/>
<return-property name="id" column="EMPID"/>
<return-property name="salary">
<!-- as multi column properties are not supported via the
{}-syntax, we need to provide an explicit column list for salary via <return-property> -->
<return-column name="`VALUE`"/>
<return-column name="CURRENCY"/>
</return-property>
<return-property name="salary.value" column="`VALUE`"/>
<return-property name="salary.currency" column="CURRENCY"/>
</return>
{ call selectAllEmployments() }
</sql-query>

View File

@ -57,10 +57,12 @@
<property name="startDate" not-null="true" update="false" insert="false"/>
<property name="endDate" insert="false"/>
<property name="regionCode" update="false"/>
<property name="salary" type="org.hibernate.orm.test.sql.hand.MonetaryAmountUserType">
<column name="`VALUE`" sql-type="float"/>
<column name="CURRENCY"/>
<component name="salary" class="org.hibernate.orm.test.sql.hand.MonetaryAmountUserType">
<property name="value" column="`VALUE`">
<type name="float"/>
</property>
<property name="currency" column="CURRENCY"/>
</component>
<loader query-ref="employment"/>
<sql-insert>
INSERT INTO Employment
@ -163,12 +165,10 @@
<sql-query name="organizationCurrentEmployments">
<return alias="emp" class="Employment">
<return-property name="salary">
<!-- as multi column properties are not supported via the
{}-syntax, we need to provide an explicit column list for salary via <return-property> -->
<return-column name="`VALUE`"/>
<return-column name="CURRENCY"/>
</return-property>
<return-property name="salary.value" column="`VALUE`"/>
<return-property name="salary.currency" column="CURRENCY"/>
<!-- Here we are remapping endDate. Notice that we can still use {emp.endDate} in the SQL. -->
<return-property name="endDate" column="myEndDate"/>
</return>
@ -206,13 +206,10 @@
<return-property name="startDate" column="STARTDATE"/>
<return-property name="endDate" column="ENDDATE"/>
<return-property name="regionCode" column="REGIONCODE"/>
<return-property name="id" column="EMPID"/>
<return-property name="salary">
<!-- as multi column properties are not supported via the
<return-property name="id" column="EMPID"/> <!-- as multi column properties are not supported via the
{}-syntax, we need to provide an explicit column list for salary via <return-property> -->
<return-column name="`VALUE`"/>
<return-column name="CURRENCY"/>
</return-property>
<return-property name="salary.value" column="`VALUE`"/>
<return-property name="salary.currency" column="CURRENCY"/>
</return>
{ call selectAllEmployments() }
</sql-query>

View File

@ -7,11 +7,11 @@
package org.hibernate.orm.test.sql.hand.custom.mysql;
import org.hibernate.dialect.MySQLDialect;
import org.hibernate.dialect.TiDBDialect;
import org.hibernate.orm.test.sql.hand.custom.CustomStoredProcTestSupport;
import org.hibernate.testing.RequiresDialect;
import org.hibernate.testing.orm.junit.NotImplementedYet;
import org.junit.Ignore;
import org.hibernate.testing.SkipForDialect;
/**
* Custom SQL tests for MySQL
@ -19,9 +19,7 @@
* @author Gavin King
*/
@RequiresDialect( MySQLDialect.class )
// todo (6.0): needs a composite user type mechanism e.g. by providing a custom embeddable strategy or istantiator
@Ignore( "Missing support for composite user types" )
@NotImplementedYet
@SkipForDialect(value = TiDBDialect.class, comment = "TiDB doesn't support stored procedures")
public class MySQLCustomSQLTest extends CustomStoredProcTestSupport {
public String[] getMappings() {
return new String[] { "sql/hand/custom/mysql/Mappings.hbm.xml" };

View File

@ -57,10 +57,12 @@
<property name="startDate" not-null="true" update="false" insert="false"/>
<property name="endDate" insert="false"/>
<property name="regionCode" update="false"/>
<property name="salary" type="org.hibernate.orm.test.sql.hand.MonetaryAmountUserType">
<column name="`VALUE`" sql-type="float"/>
<column name="CURRENCY"/>
<component name="salary" class="org.hibernate.orm.test.sql.hand.MonetaryAmountUserType">
<property name="value" column="`VALUE`">
<type name="float"/>
</property>
<property name="currency" column="CURRENCY"/>
</component>
<loader query-ref="employment"/>
<sql-insert>
INSERT INTO EMPLOYMENT
@ -157,13 +159,10 @@
<sql-query name="organizationCurrentEmployments">
<return alias="emp" class="Employment">
<return-property name="salary">
<!-- as multi column properties are not supported via the
<return alias="emp" class="Employment"><!-- as multi column properties are not supported via the
{}-syntax, we need to provide an explicit column list for salary via <return-property> -->
<return-column name="`VALUE`"/>
<return-column name="CURRENCY"/>
</return-property>
<return-property name="salary.value" column="`VALUE`"/>
<return-property name="salary.currency" column="CURRENCY"/>
<!-- Here we are remapping endDate. Notice that we can still use {emp.endDate} in the SQL. -->
<return-property name="endDate" column="myEndDate"/>
</return>

View File

@ -10,8 +10,6 @@
import org.hibernate.orm.test.sql.hand.custom.CustomStoredProcTestSupport;
import org.hibernate.testing.RequiresDialect;
import org.hibernate.testing.orm.junit.NotImplementedYet;
import org.junit.Ignore;
/**
* Custom SQL tests for Oracle
@ -19,9 +17,6 @@
* @author Gavin King
*/
@RequiresDialect( OracleDialect.class )
// todo (6.0): needs a composite user type mechanism e.g. by providing a custom embeddable strategy or istantiator
@Ignore( "Missing support for composite user types" )
@NotImplementedYet
public class OracleCustomSQLTest extends CustomStoredProcTestSupport {
public String[] getMappings() {
return new String[] { "sql/hand/custom/oracle/Mappings.hbm.xml", "sql/hand/custom/oracle/StoredProcedures.hbm.xml" };

View File

@ -40,10 +40,8 @@
<return-property name="endDate" column="ENDDATE"/>
<return-property name="regionCode" column="REGIONCODE"/>
<return-property name="employmentId" column="EMPID"/>
<return-property name="salary">
<return-column name="`VALUE`"/>
<return-column name="CURRENCY"/>
</return-property>
<return-property name="salary.value" column="`VALUE`"/>
<return-property name="salary.currency" column="CURRENCY"/>
</return>
{ ? = call allEmployments() }
</sql-query>

View File

@ -56,10 +56,12 @@
<property name="startDate" column="STARTDATE" not-null="true" update="false" insert="false"/>
<property name="endDate" column="ENDDATE" insert="false"/>
<property name="regionCode" column="REGIONCODE" update="false"/>
<property name="salary" type="org.hibernate.orm.test.sql.hand.MonetaryAmountUserType">
<column name="VALUE" sql-type="float"/>
<column name="CURRENCY"/>
<component name="salary" class="org.hibernate.orm.test.sql.hand.MonetaryAmountUserType">
<property name="value" column="`VALUE`">
<type name="float"/>
</property>
<property name="currency" column="CURRENCY"/>
</component>
<loader query-ref="employment"/>
<sql-insert>
INSERT INTO EMPLOYMENT
@ -70,11 +72,11 @@
<sql-delete>DELETE FROM EMPLOYMENT WHERE EMPID=?</sql-delete>
</class>
<class name="TextHolder">
<id name="id" column="id">
<class name="TextHolder" table="TEXTHOLDER">
<id name="id" column="ID">
<generator class="increment"/>
</id>
<property name="description" type="text" length="15000"/>
<property name="description" column="DESCRIPTION" type="text" length="15000"/>
<loader query-ref="textholder"/>
<sql-insert>
INSERT INTO TEXTHOLDER
@ -85,11 +87,11 @@
<sql-delete>DELETE FROM TEXTHOLDER WHERE ID=?</sql-delete>
</class>
<class name="ImageHolder">
<id name="id" column="id">
<class name="ImageHolder" table="IMAGEHOLDER">
<id name="id" column="ID">
<generator class="increment"/>
</id>
<property name="photo" type="image" length="15000"/>
<property name="photo" column="PHOTO" type="image" length="15000"/>
<loader query-ref="imageholder"/>
<sql-insert>
INSERT INTO IMAGEHOLDER
@ -164,12 +166,10 @@
<sql-query name="organizationCurrentEmployments">
<return alias="emp" class="Employment">
<return-property name="salary">
<!-- as multi column properties are not supported via the
{}-syntax, we need to provide an explicit column list for salary via <return-property> -->
<return-column name="`VALUE`"/>
<return-column name="CURRENCY"/>
</return-property>
<return-property name="salary.value" column="`VALUE`"/>
<return-property name="salary.currency" column="CURRENCY"/>
<!-- Here we are remapping endDate. Notice that we can still use {emp.endDate} in the SQL. -->
<return-property name="endDate" column="myEndDate"/>
</return>
@ -208,12 +208,10 @@
<return-property name="endDate" column="ENDDATE"/>
<return-property name="regionCode" column="REGIONCODE"/>
<return-property name="id" column="EMPID"/>
<return-property name="salary">
<!-- as multi column properties are not supported via the
{}-syntax, we need to provide an explicit column list for salary via <return-property> -->
<return-column name="`VALUE`"/>
<return-column name="CURRENCY"/>
</return-property>
<return-property name="salary.value" column="`VALUE`"/>
<return-property name="salary.currency" column="CURRENCY"/>
</return>
{ call selectAllEmployments() }
</sql-query>

View File

@ -10,8 +10,6 @@
import org.hibernate.orm.test.sql.hand.custom.CustomStoredProcTestSupport;
import org.hibernate.testing.RequiresDialect;
import org.hibernate.testing.orm.junit.NotImplementedYet;
import org.junit.Ignore;
/**
* Custom SQL tests for SQLServer
@ -19,9 +17,6 @@
* @author Gail Badner
*/
@RequiresDialect( SQLServerDialect.class )
// todo (6.0): needs a composite user type mechanism e.g. by providing a custom embeddable strategy or istantiator
@Ignore( "Missing support for composite user types" )
@NotImplementedYet
public class SQLServerCustomSQLTest extends CustomStoredProcTestSupport {
public String[] getMappings() {
return new String[] { "sql/hand/custom/sqlserver/Mappings.hbm.xml" };

View File

@ -56,10 +56,12 @@
<property name="startDate" column="STARTDATE" not-null="true" update="false" insert="false"/>
<property name="endDate" column="ENDDATE" insert="false"/>
<property name="regionCode" column="REGIONCODE" update="false"/>
<property name="salary" type="org.hibernate.orm.test.sql.hand.MonetaryAmountUserType">
<column name="`VALUE`" sql-type="float"/>
<column name="CURRENCY"/>
<component name="salary" class="org.hibernate.orm.test.sql.hand.MonetaryAmountUserType">
<property name="value" column="`VALUE`">
<type name="float"/>
</property>
<property name="currency" column="CURRENCY"/>
</component>
<loader query-ref="employment"/>
<sql-insert>
INSERT INTO EMPLOYMENT
@ -70,11 +72,11 @@
<sql-delete>DELETE FROM EMPLOYMENT WHERE EMPID=?</sql-delete>
</class>
<class name="TextHolder">
<id name="id" column="id">
<class name="TextHolder" table="TEXTHOLDER">
<id name="id" column="ID">
<generator class="increment"/>
</id>
<property name="description" type="text" length="15000"/>
<property name="description" column="DESCRIPTION" type="text" length="15000"/>
<loader query-ref="textholder"/>
<sql-insert>
INSERT INTO TEXTHOLDER
@ -85,11 +87,11 @@
<sql-delete>DELETE FROM TEXTHOLDER WHERE ID=?</sql-delete>
</class>
<class name="ImageHolder">
<id name="id" column="id">
<class name="ImageHolder" table="IMAGEHOLDER">
<id name="id" column="ID">
<generator class="increment"/>
</id>
<property name="photo" type="image" length="15000"/>
<property name="photo" column="PHOTO" type="image" length="15000"/>
<loader query-ref="imageholder"/>
<sql-insert>
INSERT INTO IMAGEHOLDER
@ -164,12 +166,10 @@
<sql-query name="organizationCurrentEmployments">
<return alias="emp" class="Employment">
<return-property name="salary">
<!-- as multi column properties are not supported via the
{}-syntax, we need to provide an explicit column list for salary via <return-property> -->
<return-column name="`VALUE`"/>
<return-column name="CURRENCY"/>
</return-property>
<return-property name="salary.value" column="`VALUE`"/>
<return-property name="salary.currency" column="CURRENCY"/>
<!-- Here we are remapping endDate. Notice that we can still use {emp.endDate} in the SQL. -->
<return-property name="endDate" column="myEndDate"/>
</return>
@ -208,12 +208,10 @@
<return-property name="endDate" column="ENDDATE"/>
<return-property name="regionCode" column="REGIONCODE"/>
<return-property name="id" column="EMPID"/>
<return-property name="salary">
<!-- as multi column properties are not supported via the
{}-syntax, we need to provide an explicit column list for salary via <return-property> -->
<return-column name="`VALUE`"/>
<return-column name="CURRENCY"/>
</return-property>
<return-property name="salary.value" column="`VALUE`"/>
<return-property name="salary.currency" column="CURRENCY"/>
</return>
{ call selectAllEmployments() }
</sql-query>

View File

@ -10,8 +10,6 @@
import org.hibernate.orm.test.sql.hand.custom.CustomStoredProcTestSupport;
import org.hibernate.testing.RequiresDialect;
import org.hibernate.testing.orm.junit.NotImplementedYet;
import org.junit.Ignore;
/**
* Custom SQL tests for Sybase dialects
@ -19,9 +17,6 @@
* @author Gavin King
*/
@RequiresDialect( { SybaseDialect.class })
// todo (6.0): needs a composite user type mechanism e.g. by providing a custom embeddable strategy or istantiator
@Ignore( "Missing support for composite user types" )
@NotImplementedYet
public class SybaseCustomSQLTest extends CustomStoredProcTestSupport {
public String[] getMappings() {
return new String[] { "sql/hand/custom/sybase/Mappings.hbm.xml" };

View File

@ -58,12 +58,12 @@
<property name="startDate" column="STARTDATE" not-null="false"/>
<property name="endDate" column="ENDDATE" insert="false"/>
<property name="regionCode" column="REGIONCODE" update="false"/>
<!--
<property name="salary" type="org.hibernate.orm.test.sql.hand.MonetaryAmountUserType">
<column name="AMOUNT" sql-type="float"/>
<column name="CURRENCY"/>
<component name="salary" class="org.hibernate.orm.test.sql.hand.MonetaryAmountUserType">
<property name="value" column="AMOUNT">
<type name="float"/>
</property>
-->
<property name="currency" column="CURRENCY"/>
</component>
</class>
<class name="Order" table="TBL_ORDER">
@ -129,14 +129,14 @@
<id name="id" column="id">
<generator class="increment"/>
</id>
<property name="description" type="text" length="15000"/>
<property name="description" column="DESCRIPTION" type="text" length="15000"/>
</class>
<class name="ImageHolder" table="IMAGE_HOLDER">
<id name="id" column="id">
<generator class="increment"/>
</id>
<property name="photo" type="image" length="15000"/>
<property name="photo" column="PHOTO" type="image" length="15000"/>
</class>
<resultset name="org-emp-regionCode">
@ -221,13 +221,13 @@
</sql-query>
<sql-query name="AllEmploymentAsMapped">
<return class="Employment"/>
<return alias="emp" class="Employment"/>
SELECT * FROM EMPLOYMENT
</sql-query>
<sql-query name="EmploymentAndPerson">
<return class="Employment"/>
<return class="Person"/>
<return alias="emp" class="Employment"/>
<return alias="pers" class="Person"/>
SELECT * FROM EMPLOYMENT, PERSON
</sql-query>
@ -253,24 +253,22 @@
<return-property name="element.endDate" column="ENDDATE"/>
<return-property name="element.regionCode" column="REGIONCODE"/>
<return-property name="element.employmentId" column="EMPID"/>
<return-property name="element.salary">
<return-column name="AMOUNT"/>
<return-column name="CURRENCY"/>
</return-property>
<return-property name="element.salary.value" column="AMOUNT1"/>
<return-property name="element.salary.currency" column="CURRENCY"/>
</return-join>
SELECT org.ORGID as orgid,
org.NAME as name,
emp.EMPLOYER as employer,
emp.EMPID as empid,
emp.EMPLOYEE as employee,
emp.EMPLOYER as employer,
emp.STARTDATE as xstartDate,
emp.ENDDATE as endDate,
emp.REGIONCODE as regionCode,
emp.AMOUNT as AMOUNT,
emp.AMOUNT as AMOUNT1,
emp.CURRENCY as CURRENCY
FROM ORGANIZATION org
LEFT OUTER JOIN EMPLOYMENT emp ON org.ORGID = emp.EMPLOYER
ORDER BY org.NAME DESC
</sql-query>
@ -278,7 +276,6 @@
<!-- equal to "organizationpropertyreturn" but since no {} nor return-property are used hibernate will fallback to use the columns directly from the mapping -->
SELECT org.ORGID as orgid,
org.NAME as name,
emp.EMPLOYER as employer,
emp.EMPID as empid,
emp.EMPLOYEE as employee,
emp.EMPLOYER as employer,

View File

@ -4,12 +4,17 @@
* 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.test.sql.hand.query;
package org.hibernate.orm.test.sql.hand.query;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
@ -35,13 +40,13 @@
import org.hibernate.orm.test.sql.hand.TextHolder;
import org.hibernate.query.NativeQuery;
import org.hibernate.query.Query;
import org.hibernate.query.ResultListTransformer;
import org.hibernate.transform.ResultTransformer;
import org.hibernate.transform.Transformers;
import org.hibernate.type.StandardBasicTypes;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.FailureExpected;
import org.hibernate.testing.orm.junit.NotImplementedYet;
import org.hibernate.testing.orm.junit.RequiresDialect;
import org.hibernate.testing.orm.junit.ServiceRegistry;
import org.hibernate.testing.orm.junit.SessionFactory;
@ -73,7 +78,6 @@
xmlMappings = { "org/hibernate/orm/test/sql/hand/query/NativeSQLQueries.hbm.xml" }
)
@SessionFactory
@NotImplementedYet( strict = false, reason = "needs a composite user type mechanism e.g. by providing a custom embeddable strategy or instantiator" )
public class NativeSQLQueriesTest {
protected String getOrganizationFetchJoinEmploymentSQL() {
@ -99,7 +103,7 @@ protected String getEmploymentSQL() {
}
protected String getEmploymentSQLMixedScalarEntity() {
return "SELECT e.*, e.employer as employerid FROM EMPLOYMENT e" ;
return "SELECT e.*, e.EMPLOYER as employerid FROM EMPLOYMENT e" ;
}
protected String getOrgEmpRegionSQL() {
@ -190,6 +194,7 @@ public void testSQLQueryInterface(SessionFactoryScope scope) {
session.persist(jboss);
session.persist(gavin);
session.persist(emp);
session.flush();
List l = session.createNativeQuery( getOrgEmpRegionSQL() )
.addEntity("org", Organization.class)
@ -203,7 +208,7 @@ public void testSQLQueryInterface(SessionFactoryScope scope) {
.addJoin("emp", "org.employments")
.addJoin("pers", "emp.employee")
.list();
assertEquals( l.size(), 1 );
assertEquals( 1, l.size() );
}
);
@ -214,14 +219,21 @@ public void testSQLQueryInterface(SessionFactoryScope scope) {
" left outer join EMPLOYMENT emp on org.ORGID = emp.EMPLOYER, ORGANIZATION org2" )
.addEntity("org", Organization.class)
.addJoin("emp", "org.employments")
.setResultTransformer(new ResultTransformer() {
.setResultListTransformer( new ResultListTransformer() {
@Override
public Object transformTuple(Object[] tuple, String[] aliases) {
return tuple[0];
public List transformList(List list) {
List<Object> result = new ArrayList<>( list.size() );
Map<Object, Object> distinct = new IdentityHashMap<>();
for ( Object entity : list ) {
if ( distinct.put( entity, entity ) == null ) {
result.add( entity );
}
})
}
return result;
}
} )
.list();
assertEquals( l.size(), 2 );
assertEquals( 2, l.size() );
}
);
@ -248,12 +260,13 @@ public void testResultSetMappingDefinition(SessionFactoryScope scope) {
session.persist(jboss);
session.persist(gavin);
session.persist(emp);
session.flush();
List l = session.createNativeQuery( getOrgEmpRegionSQL(), "org-emp-regionCode" ).list();
assertEquals( l.size(), 2 );
assertEquals( 2, l.size() );
l = session.createNativeQuery( getOrgEmpPersonSQL(), "org-emp-person" ).list();
assertEquals( l.size(), 1 );
assertEquals( 1, l.size() );
session.delete(emp);
session.delete(gavin);
@ -276,12 +289,13 @@ public void testResultSetMappingDefinitionWithResultClass(SessionFactoryScope sc
session.persist(jboss);
session.persist(gavin);
session.persist(emp);
session.flush();
List<Object[]> l = session.createNativeQuery( getOrgEmpRegionSQL(), "org-emp-regionCode", Object[].class ).list();
assertEquals( l.size(), 2 );
assertEquals( 2, l.size() );
l = session.createNativeQuery( getOrgEmpPersonSQL(), "org-emp-person", Object[].class ).list();
assertEquals( l.size(), 1 );
assertEquals( 1, l.size() );
session.delete(emp);
session.delete(gavin);
@ -410,20 +424,18 @@ public void testMappedAliasStrategy(SessionFactoryScope scope) {
List list = sqlQuery.list();
assertEquals( 2,list.size() );
Map m = (Map) list.get(0);
assertEquals( 2, m.size() );
assertEquals( 1, m.size() );
assertTrue( m.containsKey("org") );
assertTrue( m.containsKey("emp") );
assertClassAssignability( m.get("org").getClass(), Organization.class );
if ( jboss.getId() == ( (Organization) m.get("org") ).getId() ) {
assertClassAssignability( m.get("emp").getClass(), Employment.class );
assertTrue( Hibernate.isInitialized( ( (Organization) m.get("org") ).getEmployments() ) );
}
Map m2 = (Map) list.get(1);
assertEquals( 2, m.size() );
assertEquals( 1, m.size() );
assertTrue( m2.containsKey("org") );
assertTrue( m2.containsKey("emp") );
assertClassAssignability( m2.get("org").getClass(), Organization.class );
if ( jboss.getId() == ( (Organization) m2.get("org") ).getId() ) {
assertClassAssignability( m2.get("emp").getClass(), Employment.class );
if ( ifa.getId() == ( (Organization) m2.get("org") ).getId() ) {
assertTrue( Hibernate.isInitialized( ( (Organization) m2.get("org") ).getEmployments() ) );
}
}
);
@ -643,6 +655,7 @@ public void testAutoDetectAliasing(SessionFactoryScope scope) {
Dimension d = new Dimension(45, 10);
enterprise.setDimensions( d );
session.save( enterprise );
session.flush();
Object[] result = (Object[]) session.getNamedQuery( "spaceship" ).uniqueResult();
assertEquals( 3, result.length, "expecting 3 result values" );
enterprise = ( SpaceShip ) result[0];
@ -674,7 +687,6 @@ public void testExplicitReturnAPI(SessionFactoryScope scope) {
String sql =
"SELECT org.ORGID as orgid," +
" org.NAME as name," +
" emp.EMPLOYER as employer," +
" emp.EMPID as empid," +
" emp.EMPLOYEE as employee," +
" emp.EMPLOYER as employer," +
@ -823,7 +835,6 @@ public void testAddJoinForManyToMany(SessionFactoryScope scope) {
);
}
@SkipForDialect(dialectClass = AbstractHANADialect.class, reason = "On HANA, this returns an clob for the text column which doesn't get mapped to a String")
@Test
public void testTextTypeInSQLQuery(SessionFactoryScope scope) {
String description = buildLongString( 15000, 'a' );
@ -835,18 +846,31 @@ public void testTextTypeInSQLQuery(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
String descriptionRead = ( String ) session.createNativeQuery( getDescriptionsSQL() )
.uniqueResult();
Object result = session.createNativeQuery( getDescriptionsSQL() ).uniqueResult();
String descriptionRead;
if ( result instanceof String ) {
descriptionRead = (String) result;
}
else {
Clob clob = (Clob) result;
try {
descriptionRead = clob.getSubString( 1L, (int) clob.length() );
}
catch (SQLException e) {
throw new RuntimeException( e );
}
}
assertEquals( description, descriptionRead );
session.delete( holder );
}
);
}
@SkipForDialect(dialectClass = AbstractHANADialect.class, reason = "On HANA, this returns a blob for the image column which doesn't get mapped to a byte[]")
@Test
public void testImageTypeInSQLQuery(SessionFactoryScope scope) {
byte[] photo = buildLongByteArray( 15000, true );
// Make sure the last byte is non-zero as Sybase cuts that off
byte[] photo = buildLongByteArray( 14999, true );
ImageHolder holder = new ImageHolder( photo );
scope.inTransaction(
@ -855,8 +879,20 @@ public void testImageTypeInSQLQuery(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
byte[] photoRead = ( byte[] ) session.createNativeQuery( getPhotosSQL() )
.uniqueResult();
Object result = session.createNativeQuery( getPhotosSQL() ).uniqueResult();
byte[] photoRead;
if ( result instanceof byte[] ) {
photoRead = (byte[]) result;
}
else {
Blob blob = (Blob) result;
try {
photoRead = blob.getBytes( 1L, (int) blob.length() );
}
catch (SQLException e) {
throw new RuntimeException( e );
}
}
assertTrue( Arrays.equals( photo, photoRead ) );
session.delete( holder );
}

View File

@ -335,6 +335,7 @@ private MiddleComponentData addComponentValueToMiddleTable(
valueMetadataGenerator.addValue(
entity,
component.getProperty( auditedPropertyName ).getValue(),
component.getProperty( auditedPropertyName ).getPropertyAccessStrategy(),
componentMapper,
prefix,
context.getEntityMappingData(),
@ -351,6 +352,7 @@ private MiddleComponentData addComponentValueToMiddleTable(
valueMetadataGenerator.addValue(
entity,
component.getProperty( auditedPropertyName ).getValue(),
component.getProperty( auditedPropertyName ).getPropertyAccessStrategy(),
componentMapper,
context.getReferencingEntityName(),
context.getEntityMappingData(),

View File

@ -105,6 +105,7 @@ private void addProperties(
valueMetadataGenerator.addValue(
attributeContainer,
property.getValue(),
property.getPropertyAccessStrategy(),
currentMapper,
entityName,
mappingData,
@ -370,6 +371,7 @@ private void addSynthetics(
valueMetadataGenerator.addValue(
entity,
propertyAuditingData.getValue(),
null,
currentMapper,
entityName,
mappingData,

View File

@ -19,6 +19,10 @@
import org.hibernate.mapping.Component;
import org.hibernate.mapping.Property;
import org.hibernate.mapping.Value;
import org.hibernate.metamodel.internal.EmbeddableCompositeUserTypeInstantiator;
import org.hibernate.metamodel.spi.EmbeddableInstantiator;
import org.hibernate.resource.beans.spi.ManagedBeanRegistry;
import org.hibernate.usertype.CompositeUserType;
/**
* Generates metadata for components.
@ -46,19 +50,50 @@ public void addComponent(
EntityMappingData mappingData,
boolean firstPass) {
final Component propComponent = (Component) value;
final Class<? extends EmbeddableInstantiator> instantiatorClass;
if ( propComponent.getCustomInstantiator() != null ) {
instantiatorClass = propComponent.getCustomInstantiator();
}
else {
instantiatorClass = null;
}
final EmbeddableInstantiator instantiator;
if ( propComponent.getCustomInstantiator() != null ) {
instantiator = getMetadataBuildingContext().getBootstrapContext()
.getServiceRegistry()
.getService( ManagedBeanRegistry.class )
.getBean( propComponent.getCustomInstantiator() )
.getBeanInstance();
}
else if ( propComponent.getTypeName() != null ) {
final CompositeUserType<Object> compositeUserType = (CompositeUserType<Object>) getMetadataBuildingContext().getBootstrapContext()
.getServiceRegistry()
.getService( ManagedBeanRegistry.class )
.getBean(
getMetadataBuildingContext().getBootstrapContext()
.getClassLoaderAccess()
.classForName( propComponent.getTypeName() )
)
.getBeanInstance();
instantiator = new EmbeddableCompositeUserTypeInstantiator( compositeUserType );
}
else {
instantiator = null;
}
final CompositeMapperBuilder componentMapper = mapper.addComponent(
propertyAuditingData.resolvePropertyData(),
ClassLoaderAccessHelper.loadClass(
getMetadataBuildingContext(),
getClassNameForComponent( propComponent )
)
),
instantiator
);
// The property auditing data must be for a component.
final ComponentAuditingData componentAuditingData = (ComponentAuditingData) propertyAuditingData;
// Adding all properties of the component
propComponent.sortProperties();
final Iterator<Property> properties = propComponent.getPropertyIterator();
while ( properties.hasNext() ) {
final Property property = properties.next();
@ -71,6 +106,7 @@ public void addComponent(
valueGenerator.addValue(
attributeContainer,
property.getValue(),
property.getPropertyAccessStrategy(),
componentMapper,
entityName,
mappingData,

View File

@ -18,6 +18,7 @@
import org.hibernate.mapping.Collection;
import org.hibernate.mapping.OneToOne;
import org.hibernate.mapping.Value;
import org.hibernate.property.access.spi.PropertyAccessStrategy;
import org.hibernate.type.BasicType;
import org.hibernate.type.CollectionType;
import org.hibernate.type.ComponentType;
@ -52,6 +53,7 @@ public ValueMetadataGenerator(
public void addValue(
AttributeContainer attributeContainer,
Value value,
PropertyAccessStrategy propertyAccessStrategy,
CompositeMapperBuilder currentMapper,
String entityName,
EntityMappingData mappingData,
@ -63,6 +65,7 @@ public void addValue(
addValueInFirstPass(
attributeContainer,
value,
propertyAccessStrategy,
currentMapper,
entityName,
mappingData,
@ -75,6 +78,7 @@ public void addValue(
addValueInSecondPass(
attributeContainer,
value,
propertyAccessStrategy,
currentMapper,
entityName,
mappingData,
@ -88,6 +92,7 @@ public void addValue(
private void addValueInFirstPass(
AttributeContainer attributeContainer,
Value value,
PropertyAccessStrategy propertyAccessStrategy,
CompositeMapperBuilder currentMapper,
String entityName,
EntityMappingData mappingData,
@ -95,6 +100,7 @@ private void addValueInFirstPass(
boolean insertable,
boolean processModifiedFlag) {
final Type type = value.getType();
propertyAuditingData.setPropertyAccessStrategy( propertyAccessStrategy );
if ( type instanceof BasicType ) {
basicMetadataGenerator.addBasic(
@ -134,6 +140,7 @@ else if ( type instanceof ComponentType) {
private void addValueInSecondPass(
AttributeContainer attributeContainer,
Value value,
PropertyAccessStrategy propertyAccessStrategy,
CompositeMapperBuilder currentMapper,
String entityName,
EntityMappingData mappingData,

View File

@ -9,6 +9,7 @@
import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
@ -116,6 +117,10 @@ public AuditedPropertiesReader(
}
public void read() {
read(null);
}
public void read(Audited allClassAudited) {
// First reading the access types for the persistent properties.
readPersistentPropertiesAccess();
@ -126,12 +131,35 @@ public void read() {
// Retrieve classes and properties that are explicitly marked for auditing process by any superclass
// of currently mapped entity or itself.
final XClass clazz = persistentPropertiesSource.getXClass();
if ( persistentPropertiesSource.hasCompositeUserType() ) {
for ( String propertyName : fieldAccessedPersistentProperties ) {
final Property property = persistentPropertiesSource.getProperty( propertyName );
final Value propertyValue = property.getValue();
if ( propertyValue instanceof Component ) {
this.addFromComponentProperty( property, "field", (Component) propertyValue, allClassAudited );
}
else {
this.addFromNotComponentProperty( property, "field", allClassAudited );
}
}
for ( String propertyName : propertyAccessedPersistentProperties.keySet() ) {
final Property property = persistentPropertiesSource.getProperty( propertyName );
final Value propertyValue = property.getValue();
if ( propertyValue instanceof Component ) {
this.addFromComponentProperty( property, "property", (Component) propertyValue, allClassAudited );
}
else {
this.addFromNotComponentProperty( property, "property", allClassAudited );
}
}
}
else {
readAuditOverrides( clazz );
// Adding all properties from the given class.
addPropertiesFromClass( clazz );
}
}
}
/**
* Recursively constructs sets of audited and not audited properties and classes which behavior has been overridden
@ -475,7 +503,7 @@ private void addFromComponentProperty(
componentData,
propertyNamePrefix + MappingTools.createComponentPrefix( property.getName() )
);
audPropReader.read();
audPropReader.read( allClassAudited );
if ( isAudited ) {
// Now we know that the property is audited
@ -554,6 +582,82 @@ private boolean fillPropertyData(
return true;
}
private void addFromComponentProperty(
Property property,
String accessType,
Component propertyValue,
Audited allClassAudited) {
final ComponentAuditingData componentData = new ComponentAuditingData();
final boolean isAudited = fillPropertyData( property, componentData, accessType, allClassAudited );
final PersistentPropertiesSource componentPropertiesSource;
if ( propertyValue.isDynamic() ) {
final XClass xClass = metadataBuildingContext.getReflectionManager().toXClass( Map.class );
componentPropertiesSource = PersistentPropertiesSource.forComponent( propertyValue, xClass, true );
}
else {
componentPropertiesSource = PersistentPropertiesSource.forComponent( metadataBuildingContext, propertyValue );
}
final ComponentAuditedPropertiesReader audPropReader = new ComponentAuditedPropertiesReader(
metadataBuildingContext,
componentPropertiesSource,
componentData,
propertyNamePrefix + MappingTools.createComponentPrefix( property.getName() )
);
audPropReader.read( allClassAudited );
if ( isAudited ) {
// Now we know that the property is audited
auditedPropertiesHolder.addPropertyAuditingData( property.getName(), componentData );
}
}
private void addFromNotComponentProperty(Property property, String accessType, Audited allClassAudited) {
final PropertyAuditingData propertyData = new PropertyAuditingData();
final boolean isAudited = fillPropertyData( property, propertyData, accessType, allClassAudited );
if ( isAudited ) {
// Now we know that the property is audited
auditedPropertiesHolder.addPropertyAuditingData( property.getName(), propertyData );
}
}
/**
* Checks if a property is audited and if yes, fills all of its data.
*
* @param property Property to check.
* @param propertyData Property data, on which to set this property's modification store.
* @param accessType Access type for the property.
*
* @return False if this property is not audited.
*/
private boolean fillPropertyData(
Property property,
PropertyAuditingData propertyData,
String accessType,
Audited allClassAudited) {
final String propertyName = propertyNamePrefix + property.getName();
final String modifiedFlagsSuffix = metadataBuildingContext.getConfiguration().getModifiedFlagsSuffix();
if ( !this.checkAudited( property, propertyData,propertyName, allClassAudited, modifiedFlagsSuffix ) ) {
return false;
}
propertyData.setName( propertyName );
propertyData.setBeanName( property.getName() );
propertyData.setAccessType( accessType );
propertyData.setJoinTable( DEFAULT_AUDIT_JOIN_TABLE );
if ( !processPropertyAuditingOverrides( property, propertyData ) ) {
// not audited due to AuditOverride annotation
return false;
}
return true;
}
private void validateLobMappingSupport(XProperty property) {
// HHH-9834 - Sanity check
try {
@ -610,6 +714,30 @@ protected boolean checkAudited(
}
}
protected boolean checkAudited(
Property property,
PropertyAuditingData propertyData, String propertyName,
Audited allClassAudited, String modifiedFlagSuffix) {
// Checking if this property is explicitly audited or if all properties are.
if ( allClassAudited != null ) {
propertyData.setRelationTargetAuditMode( allClassAudited.targetAuditMode() );
propertyData.setRelationTargetNotFoundAction(
allClassAudited == null ?
RelationTargetNotFoundAction.DEFAULT :
allClassAudited.targetNotFoundAction()
);
propertyData.setUsingModifiedFlag( checkUsingModifiedFlag( allClassAudited ) );
propertyData.setModifiedFlagName( ModifiedColumnNameResolver.getName( propertyName, modifiedFlagSuffix ) );
if ( !StringTools.isEmpty( allClassAudited.modifiedColumnName() ) ) {
propertyData.setExplicitModifiedFlagName( allClassAudited.modifiedColumnName() );
}
return true;
}
else {
return false;
}
}
protected boolean checkUsingModifiedFlag(Audited aud) {
// HHH-10468
if ( metadataBuildingContext.getConfiguration().hasSettingForUseModifiedFlag() ) {
@ -730,6 +858,24 @@ private boolean processPropertyAuditingOverrides(XProperty property, PropertyAud
return true;
}
private boolean processPropertyAuditingOverrides(Property property, PropertyAuditingData propertyData) {
// Only components register audit overrides, classes will have no entries.
for ( AuditOverrideData override : auditedPropertiesHolder.getAuditingOverrides() ) {
if ( property.getName().equals( override.getName() ) ) {
// the override applies to this property
if ( !override.isAudited() ) {
return false;
}
else {
if ( override.getAuditJoinTableData() != null ) {
propertyData.setJoinTable( override.getAuditJoinTableData() );
}
}
}
}
return true;
}
protected boolean isOverriddenNotAudited(XProperty property) {
return overriddenNotAuditedProperties.contains( property );
}

View File

@ -32,6 +32,8 @@ public interface PersistentPropertiesSource {
boolean isDynamicComponent();
boolean hasCompositeUserType();
/**
* Get a persistent properties source for a persistent class.
*
@ -60,6 +62,11 @@ public XClass getXClass() {
public boolean isDynamicComponent() {
return false;
}
@Override
public boolean hasCompositeUserType() {
return false;
}
};
}
@ -115,6 +122,11 @@ public XClass getXClass() {
public boolean isDynamicComponent() {
return dynamic;
}
@Override
public boolean hasCompositeUserType() {
return component.getTypeName() != null;
}
};
}
}

View File

@ -20,6 +20,7 @@
import org.hibernate.envers.internal.entities.PropertyData;
import org.hibernate.envers.internal.tools.StringTools;
import org.hibernate.mapping.Value;
import org.hibernate.property.access.spi.PropertyAccessStrategy;
import org.hibernate.type.Type;
/**
@ -50,6 +51,7 @@ public class PropertyAuditingData {
private Value value;
private Type propertyType;
private Type virtualPropertyType;
private PropertyAccessStrategy propertyAccessStrategy;
// Synthetic properties are ones which are not part of the actual java model.
// They're properties used for bookkeeping by Hibernate
private boolean synthetic;
@ -323,6 +325,14 @@ public void setPropertyType(Type propertyType) {
this.propertyType = propertyType;
}
public PropertyAccessStrategy getPropertyAccessStrategy() {
return propertyAccessStrategy;
}
public void setPropertyAccessStrategy(PropertyAccessStrategy propertyAccessStrategy) {
this.propertyAccessStrategy = propertyAccessStrategy;
}
public Type getVirtualPropertyType() {
return virtualPropertyType;
}
@ -349,7 +359,8 @@ public PropertyData resolvePropertyData() {
modifiedFlagName,
synthetic,
propertyType,
virtualPropertyType.getReturnedClass()
virtualPropertyType.getReturnedClass(),
propertyAccessStrategy
);
}
else if ( propertyType != null ) {
@ -360,7 +371,8 @@ else if ( propertyType != null ) {
usingModifiedFlag,
modifiedFlagName,
synthetic,
propertyType
propertyType,
propertyAccessStrategy
);
}
return new PropertyData(
@ -370,7 +382,8 @@ else if ( propertyType != null ) {
usingModifiedFlag,
modifiedFlagName,
synthetic,
null
null,
propertyAccessStrategy
);
}
}

View File

@ -8,6 +8,7 @@
import java.util.Objects;
import org.hibernate.property.access.spi.PropertyAccessStrategy;
import org.hibernate.type.Type;
/**
@ -30,6 +31,7 @@ public class PropertyData {
private boolean synthetic;
private Type propertyType;
private Class<?> virtualReturnClass;
private PropertyAccessStrategy propertyAccessStrategy;
/**
* Copies the given property data, except the name.
@ -91,8 +93,9 @@ public PropertyData(
boolean usingModifiedFlag,
String modifiedFlagName,
boolean synthetic,
Type propertyType) {
this( name, beanName, accessType, usingModifiedFlag, modifiedFlagName, synthetic, propertyType, null );
Type propertyType,
PropertyAccessStrategy propertyAccessStrategy) {
this( name, beanName, accessType, usingModifiedFlag, modifiedFlagName, synthetic, propertyType, null, propertyAccessStrategy );
}
public PropertyData(
@ -103,10 +106,12 @@ public PropertyData(
String modifiedFlagName,
boolean synthetic,
Type propertyType,
Class<?> virtualReturnClass) {
Class<?> virtualReturnClass,
PropertyAccessStrategy propertyAccessStrategy) {
this( name, beanName, accessType, usingModifiedFlag, modifiedFlagName, synthetic );
this.propertyType = propertyType;
this.virtualReturnClass = virtualReturnClass;
this.propertyAccessStrategy = propertyAccessStrategy;
}
public String getName() {
@ -141,6 +146,10 @@ public Class<?> getVirtualReturnClass() {
return virtualReturnClass;
}
public PropertyAccessStrategy getPropertyAccessStrategy() {
return propertyAccessStrategy;
}
@Override
public boolean equals(Object o) {
if ( this == o ) {

View File

@ -19,6 +19,7 @@
import org.hibernate.envers.internal.reader.AuditReaderImplementor;
import org.hibernate.envers.internal.tools.ReflectionTools;
import org.hibernate.internal.util.ReflectHelper;
import org.hibernate.metamodel.spi.EmbeddableInstantiator;
import org.hibernate.property.access.spi.Setter;
/**
@ -31,9 +32,14 @@ public class ComponentPropertyMapper extends AbstractPropertyMapper implements C
private final PropertyData propertyData;
private final MultiPropertyMapper delegate;
private final Class componentClass;
private final EmbeddableInstantiator embeddableInstantiator;
public ComponentPropertyMapper(PropertyData propertyData, Class componentClass) {
public ComponentPropertyMapper(
PropertyData propertyData,
Class componentClass,
EmbeddableInstantiator instantiator) {
this.propertyData = propertyData;
this.embeddableInstantiator = instantiator;
//if class is a map it means that this is dynamic component
if ( Map.class.isAssignableFrom( componentClass ) ) {
this.delegate = new MultiDynamicComponentMapper( propertyData );
@ -51,8 +57,11 @@ public void add(PropertyData propertyData) {
}
@Override
public CompositeMapperBuilder addComponent(PropertyData propertyData, Class componentClass) {
return delegate.addComponent( propertyData, componentClass );
public CompositeMapperBuilder addComponent(
PropertyData propertyData,
Class componentClass,
EmbeddableInstantiator instantiator) {
return delegate.addComponent( propertyData, componentClass, instantiator );
}
@Override
@ -118,9 +127,9 @@ public void mapToEntityFromMap(
doPrivileged( () -> {
try {
final Object subObj = ReflectHelper.getDefaultConstructor( componentClass ).newInstance();
if ( isDynamicComponentMap() ) {
final Object subObj = ReflectHelper.getDefaultConstructor( componentClass ).newInstance();
( (Map) obj ).put( propertyData.getBeanName(), subObj );
delegate.mapToEntityFromMap( enversService, subObj, data, primaryKey, versionsReader, revision );
}
@ -136,9 +145,39 @@ public void mapToEntityFromMap(
setter.set( obj, null );
}
else {
final Object subObj;
if ( embeddableInstantiator != null ) {
final Object[] values = new Object[delegate.properties.size()];
int i = 0;
for ( Map.Entry<PropertyData, PropertyMapper> entry : delegate.properties.entrySet() ) {
values[i] = entry.getValue().mapToEntityFromMap(
enversService,
data,
primaryKey,
versionsReader,
revision
);
i++;
}
subObj = embeddableInstantiator.instantiate(
() -> values,
versionsReader.getSessionImplementor()
.getSessionFactory()
);
}
else {
subObj = ReflectHelper.getDefaultConstructor( componentClass ).newInstance();
delegate.mapToEntityFromMap(
enversService,
subObj,
data,
primaryKey,
versionsReader,
revision
);
}
// set the component
setter.set( obj, subObj );
delegate.mapToEntityFromMap( enversService, subObj, data, primaryKey, versionsReader, revision );
}
}
}
@ -150,6 +189,72 @@ public void mapToEntityFromMap(
} );
}
@Override
public Object mapToEntityFromMap(
final EnversService enversService,
final Map data,
final Object primaryKey,
final AuditReaderImplementor versionsReader,
final Number revision) {
if ( data == null || propertyData.getBeanName() == null ) {
// If properties are not encapsulated in a component but placed directly in a class
// (e.g. by applying <properties> tag).
return null;
}
return doPrivileged( () -> {
try {
final Object subObj;
if ( isDynamicComponentMap() ) {
subObj = ReflectHelper.getDefaultConstructor( componentClass ).newInstance();
delegate.mapToEntityFromMap( enversService, subObj, data, primaryKey, versionsReader, revision );
}
else {
if ( isAllPropertiesNull( data ) ) {
// single property, but default value need not be null, so we'll set it to null anyway
subObj = null;
}
else {
if ( embeddableInstantiator != null ) {
final Object[] values = new Object[delegate.properties.size()];
int i = 0;
for ( Map.Entry<PropertyData, PropertyMapper> entry : delegate.properties.entrySet() ) {
values[i] = entry.getValue().mapToEntityFromMap(
enversService,
data,
primaryKey,
versionsReader,
revision
);
i++;
}
subObj = embeddableInstantiator.instantiate(
() -> values,
versionsReader.getSessionImplementor()
.getSessionFactory()
);
}
else {
subObj = ReflectHelper.getDefaultConstructor( componentClass ).newInstance();
delegate.mapToEntityFromMap(
enversService,
subObj,
data,
primaryKey,
versionsReader,
revision
);
}
}
}
return subObj;
}
catch ( Exception e ) {
throw new AuditException( e );
}
} );
}
private boolean isAllPropertiesNull(Map data) {
for ( Map.Entry<PropertyData, PropertyMapper> property : delegate.getProperties().entrySet() ) {
final Object value = data.get( property.getKey().getName() );

View File

@ -9,12 +9,15 @@
import java.util.Map;
import org.hibernate.envers.internal.entities.PropertyData;
import org.hibernate.metamodel.spi.EmbeddableInstantiator;
/**
* @author Adam Warski (adam at warski dot org)
*/
public interface CompositeMapperBuilder extends SimpleMapperBuilder {
CompositeMapperBuilder addComponent(PropertyData propertyData, Class componentClass);
CompositeMapperBuilder addComponent(
PropertyData propertyData,
Class componentClass, EmbeddableInstantiator instantiator);
void addComposite(PropertyData propertyData, PropertyMapper propertyMapper);

View File

@ -122,4 +122,14 @@ public void mapToEntityFromMap(
mapper.mapToEntityFromMap( enversService, obj, data, primaryKey, versionsReader, revision );
}
}
@Override
public Object mapToEntityFromMap(
EnversService enversService,
Map data,
Object primaryKey,
AuditReaderImplementor versionsReader,
Number revision) {
return null;
}
}

View File

@ -19,6 +19,7 @@
import org.hibernate.envers.internal.tools.ReflectionTools;
import org.hibernate.envers.internal.tools.Tools;
import org.hibernate.envers.tools.Pair;
import org.hibernate.metamodel.spi.EmbeddableInstantiator;
import org.hibernate.property.access.spi.Getter;
/**
@ -45,7 +46,9 @@ public void add(PropertyData propertyData) {
}
@Override
public CompositeMapperBuilder addComponent(PropertyData propertyData, Class componentClass) {
public CompositeMapperBuilder addComponent(
PropertyData propertyData,
Class componentClass, EmbeddableInstantiator instantiator) {
if ( properties.get( propertyData ) != null ) {
// This is needed for second pass to work properly in the components mapper
return (CompositeMapperBuilder) properties.get( propertyData );
@ -53,7 +56,8 @@ public CompositeMapperBuilder addComponent(PropertyData propertyData, Class comp
final ComponentPropertyMapper componentMapperBuilder = new ComponentPropertyMapper(
propertyData,
componentClass
componentClass,
instantiator
);
addComposite( propertyData, componentMapperBuilder );
@ -198,6 +202,16 @@ public void mapToEntityFromMap(
}
}
@Override
public Object mapToEntityFromMap(
EnversService enversService,
Map data,
Object primaryKey,
AuditReaderImplementor versionsReader,
Number revision) {
return null;
}
private Pair<PropertyMapper, String> getMapperAndDelegatePropName(String referencingPropertyName) {
// Name of the property, to which we will delegate the mapping.
String delegatePropertyName;

View File

@ -51,6 +51,13 @@ void mapToEntityFromMap(
AuditReaderImplementor versionsReader,
Number revision);
Object mapToEntityFromMap(
EnversService enversService,
Map data,
Object primaryKey,
AuditReaderImplementor versionsReader,
Number revision);
/**
* Maps collection changes.
*

View File

@ -120,6 +120,21 @@ public void mapToEntityFromMap(
}
}
@Override
public Object mapToEntityFromMap(
final EnversService enversService,
final Map data,
Object primaryKey,
AuditReaderImplementor versionsReader,
Number revision) {
// synthetic properties are not part of the entity model; therefore they should be ignored.
if ( data == null || propertyData.isSynthetic() ) {
return null;
}
return data.get( propertyData.getName() );
}
private boolean isPrimitive(Setter setter, PropertyData propertyData, Class<?> cls) {
if ( cls == null ) {
throw new HibernateException( "No field found for property: " + propertyData.getName() );

View File

@ -16,6 +16,7 @@
import org.hibernate.envers.boot.internal.EnversService;
import org.hibernate.envers.internal.entities.PropertyData;
import org.hibernate.envers.internal.reader.AuditReaderImplementor;
import org.hibernate.metamodel.spi.EmbeddableInstantiator;
/**
* A mapper which maps from a parent mapper and a "main" one, but adds only to the "main". The "main" mapper
@ -87,6 +88,16 @@ public void mapToEntityFromMap(
main.mapToEntityFromMap( enversService, obj, data, primaryKey, versionsReader, revision );
}
@Override
public Object mapToEntityFromMap(
EnversService enversService,
Map data,
Object primaryKey,
AuditReaderImplementor versionsReader,
Number revision) {
return null;
}
@Override
public List<PersistentCollectionChangeData> mapCollectionChanges(
SessionImplementor session, String referencingPropertyName,
@ -120,8 +131,10 @@ public List<PersistentCollectionChangeData> mapCollectionChanges(
}
@Override
public CompositeMapperBuilder addComponent(PropertyData propertyData, Class componentClass) {
return main.addComponent( propertyData, componentClass );
public CompositeMapperBuilder addComponent(
PropertyData propertyData,
Class componentClass, EmbeddableInstantiator instantiator) {
return main.addComponent( propertyData, componentClass, instantiator );
}
@Override

View File

@ -276,6 +276,25 @@ public void mapToEntityFromMap(
final Object primaryKey,
final AuditReaderImplementor versionsReader,
final Number revision) {
final Object collectionProxy = mapToEntityFromMap( enversService, data, primaryKey, versionsReader, revision );
final PropertyData collectionPropertyData = commonCollectionMapperData.getCollectionReferencingPropertyData();
if ( isDynamicComponentMap() ) {
final Map<String, Object> map = (Map<String, Object>) obj;
map.put( collectionPropertyData.getBeanName(), collectionProxy );
}
else {
setValueOnObject( collectionPropertyData, obj, collectionProxy, enversService.getServiceRegistry() );
}
}
@Override
public Object mapToEntityFromMap(
EnversService enversService,
Map data,
Object primaryKey,
AuditReaderImplementor versionsReader,
Number revision) {
final String revisionTypePropertyName = enversService.getConfig().getRevisionTypePropertyName();
// construct the collection proxy
@ -294,16 +313,7 @@ public void mapToEntityFromMap(
catch ( Exception e ) {
throw new AuditException( "Failed to construct collection proxy", e );
}
final PropertyData collectionPropertyData = commonCollectionMapperData.getCollectionReferencingPropertyData();
if ( isDynamicComponentMap() ) {
final Map<String, Object> map = (Map<String, Object>) obj;
map.put( collectionPropertyData.getBeanName(), collectionProxy );
}
else {
setValueOnObject( collectionPropertyData, obj, collectionProxy, enversService.getServiceRegistry() );
}
return collectionProxy;
}
/**

View File

@ -45,6 +45,17 @@ public void nullSafeMapToEntityFromMap(
Object primaryKey,
AuditReaderImplementor versionsReader,
Number revision) {
Object value = nullSafeMapToEntityFromMap( enversService, data, primaryKey, versionsReader, revision );
setPropertyValue( obj, value );
}
@Override
public Object nullSafeMapToEntityFromMap(
EnversService enversService,
Map data,
Object primaryKey,
AuditReaderImplementor versionsReader,
Number revision) {
final EntityInfo referencedEntity = getEntityInfo( enversService, referencedEntityName );
Object value;
@ -60,8 +71,7 @@ public void nullSafeMapToEntityFromMap(
"." + getPropertyData().getBeanName() + ".", e
);
}
setPropertyValue( obj, value );
return value;
}
/**

View File

@ -58,6 +58,16 @@ public void mapToEntityFromMap(
}
}
@Override
public Object mapToEntityFromMap(
EnversService enversService,
Map data,
Object primaryKey,
AuditReaderImplementor versionsReader,
Number revision) {
return nullSafeMapToEntityFromMap( enversService, data, primaryKey, versionsReader, revision );
}
@Override
public List<PersistentCollectionChangeData> mapCollectionChanges(
SessionImplementor session,
@ -115,6 +125,13 @@ public abstract void nullSafeMapToEntityFromMap(
AuditReaderImplementor versionsReader,
Number revision);
public abstract Object nullSafeMapToEntityFromMap(
EnversService enversService,
Map data,
Object primaryKey,
AuditReaderImplementor versionsReader,
Number revision);
/**
* Simple descriptor of an entity.
*/

View File

@ -149,6 +149,17 @@ public void nullSafeMapToEntityFromMap(
Object primaryKey,
AuditReaderImplementor versionsReader,
Number revision) {
Object value = nullSafeMapToEntityFromMap( enversService, data, primaryKey, versionsReader, revision );
setPropertyValue( obj, value );
}
@Override
public Object nullSafeMapToEntityFromMap(
EnversService enversService,
Map data,
Object primaryKey,
AuditReaderImplementor versionsReader,
Number revision) {
final Object entityId = delegate.mapToIdFromMap( data );
Object value = null;
if ( entityId != null ) {
@ -183,8 +194,7 @@ public void nullSafeMapToEntityFromMap(
}
}
}
setPropertyValue( obj, value );
return value;
}
public void addMiddleEqualToQuery(

View File

@ -17,6 +17,7 @@
import org.hibernate.envers.internal.entities.mapper.PropertyMapper;
import org.hibernate.envers.internal.entities.mapper.relation.ToOneIdMapper;
import org.hibernate.envers.internal.tools.query.Parameters;
import org.hibernate.metamodel.spi.EmbeddableInstantiator;
/**
* @author Kristoffer Lundberg (kristoffer at cambio dot se)
@ -125,8 +126,10 @@ else if ( nestedMapper instanceof ToOneIdMapper ) {
}
@Override
public CompositeMapperBuilder addComponent(PropertyData propertyData, Class componentClass) {
return delegate.addComponent( propertyData, componentClass );
public CompositeMapperBuilder addComponent(
PropertyData propertyData,
Class componentClass, EmbeddableInstantiator instantiator) {
return delegate.addComponent( propertyData, componentClass, instantiator );
}
@Override

View File

@ -47,8 +47,24 @@ private static PropertyAccessStrategy getAccessStrategy(Class<?> cls, ServiceReg
}
public static Getter getGetter(Class cls, PropertyData propertyData, ServiceRegistry serviceRegistry) {
if ( propertyData.getPropertyAccessStrategy() == null ) {
return getGetter( cls, propertyData.getBeanName(), propertyData.getAccessType(), serviceRegistry );
}
else {
final String propertyName = propertyData.getName();
final Pair<Class, String> key = Pair.make( cls, propertyName );
Getter value = GETTER_CACHE.get( key );
if ( value == null ) {
value = propertyData.getPropertyAccessStrategy().buildPropertyAccess(
cls,
propertyData.getBeanName(), false
).getGetter();
// It's ok if two getters are generated concurrently
GETTER_CACHE.put( key, value );
}
return value;
}
}
public static Getter getGetter(Class cls, String propertyName, String accessorType, ServiceRegistry serviceRegistry) {
final Pair<Class, String> key = Pair.make( cls, propertyName );

View File

@ -6,23 +6,14 @@
*/
package org.hibernate.envers.test.integration.customtype;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.function.Supplier;
import org.hibernate.HibernateException;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.usertype.UserType;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.usertype.CompositeUserType;
import jakarta.persistence.Lob;
/**
* Custom type used to persist binary representation of Java object in the database.
@ -31,16 +22,31 @@
*
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
public class ObjectUserType implements UserType {
private static final int[] TYPES = new int[] {Types.VARCHAR, Types.BLOB};
public class ObjectUserType implements CompositeUserType<Object> {
@Override
public int[] sqlTypes() {
return TYPES;
public Object getPropertyValue(Object component, int property) throws HibernateException {
switch ( property ) {
case 0:
return component;
case 1:
return component.getClass().getName();
}
return null;
}
@Override
public Class returnedClass() {
public Object instantiate(Supplier<Object[]> values, SessionFactoryImplementor sessionFactory) {
return values.get()[0];
}
@Override
public Class<?> embeddable() {
return TaggedObject.class;
}
@Override
public Class<Object> returnedClass() {
return Object.class;
}
@ -60,77 +66,6 @@ public int hashCode(Object x) throws HibernateException {
return x.hashCode();
}
@Override
public Object nullSafeGet(ResultSet rs, int position, SharedSessionContractImplementor session, Object owner) throws SQLException {
throw new NotYetImplementedFor6Exception(
"See https://github.com/hibernate/hibernate-orm/discussions/3960"
);
}
@Override
public void nullSafeSet(PreparedStatement st, Object value, int index, SharedSessionContractImplementor session)
throws HibernateException, SQLException {
if ( value == null ) {
st.setNull( index, TYPES[0] );
st.setNull( index + 1, TYPES[1] );
}
else {
st.setString( index, value.getClass().getName() );
st.setBinaryStream( index + 1, convertObjectToInputStream( value ) );
}
}
private InputStream convertObjectToInputStream(Object value) {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = null;
try {
objectOutputStream = new ObjectOutputStream( byteArrayOutputStream );
objectOutputStream.writeObject( value );
objectOutputStream.flush();
return new ByteArrayInputStream( byteArrayOutputStream.toByteArray() );
}
catch (IOException e) {
throw new RuntimeException( e );
}
finally {
closeQuietly( objectOutputStream );
}
}
private Object convertInputStreamToObject(InputStream inputStream) {
ObjectInputStream objectInputStream = null;
try {
objectInputStream = new ObjectInputStream( inputStream );
return objectInputStream.readObject();
}
catch (Exception e) {
throw new RuntimeException( e );
}
finally {
closeQuietly( objectInputStream );
}
}
private void closeQuietly(OutputStream stream) {
if ( stream != null ) {
try {
stream.close();
}
catch (IOException e) {
}
}
}
private void closeQuietly(InputStream stream) {
if ( stream != null ) {
try {
stream.close();
}
catch (IOException e) {
}
}
}
@Override
public Object deepCopy(Object value) throws HibernateException {
return value; // Persisting only immutable types.
@ -155,4 +90,10 @@ public Object assemble(Serializable cached, Object owner) throws HibernateExcept
public Object replace(Object original, Object target, Object owner) throws HibernateException {
return original;
}
public static class TaggedObject {
String type;
@Lob
Serializable object;
}
}

View File

@ -7,14 +7,16 @@
package org.hibernate.envers.test.integration.customtype;
import java.io.Serializable;
import org.hibernate.annotations.CompositeType;
import org.hibernate.envers.Audited;
import jakarta.persistence.AttributeOverride;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import org.hibernate.annotations.Columns;
import org.hibernate.envers.Audited;
/**
* Entity encapsulating {@link Object} property which concrete type may change during subsequent updates.
*
@ -29,8 +31,10 @@ public class ObjectUserTypeEntity implements Serializable {
private String buildInType;
// @Type(type = "org.hibernate.envers.test.integration.customtype.ObjectUserType")
@Columns(columns = {@Column(name = "OBJ_TYPE"), @Column(name = "OBJ_VALUE")})
@Audited
@CompositeType(ObjectUserType.class)
@AttributeOverride( name = "type", column = @Column(name = "OBJ_TYPE"))
@AttributeOverride( name = "object", column = @Column(name = "OBJ_VALUE"))
private Object userType;
public ObjectUserTypeEntity() {

View File

@ -10,12 +10,10 @@
import java.util.Map;
import jakarta.persistence.EntityManager;
import org.hibernate.dialect.Oracle8iDialect;
import org.hibernate.envers.configuration.EnversSettings;
import org.hibernate.orm.test.envers.BaseEnversJPAFunctionalTestCase;
import org.hibernate.orm.test.envers.Priority;
import org.hibernate.testing.RequiresDialect;
import org.hibernate.testing.TestForIssue;
import org.junit.Assert;
import org.junit.Test;
@ -24,7 +22,6 @@
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
@TestForIssue(jiraKey = "HHH-7870")
@RequiresDialect(Oracle8iDialect.class)
public class ObjectUserTypeTest extends BaseEnversJPAFunctionalTestCase {
private int id;

View File

@ -198,6 +198,32 @@ identifiers cannot use constructor injection.
See https://docs.jboss.org/hibernate/orm/6.0/userguide/html_single/Hibernate_User_Guide.html#embeddable-instantiator for details.
==== CompositeUserType changes
The `CompositeUserType` interface was re-implemented to be able to model user types as proper embeddable types.
A major difference to 5.x is the introduction of an "embeddable projection" that is used to determine the mapping structure.
Previously, a `CompositeUserType` had to provide property names and types through dedicated accessor methods,
but this was complicated for non-basic mappings and required quite some knowledge about Hibernate internals.
With 6.0 these methods are replaced with a method that returns an "embeddable projection" class.
The class is like a regular `@Embeddable` class and is used to determine the mapping structure for the `CompositeUserType`.
Component values of a user type object are accessed by property index. The property index is 0-based and can be determined
by sorting the persistent attribute names lexicographically ascending and using the 0-based position as property index.
For example, the following component:
```java
public class MonetaryAmountEmbeddable {
BigDecimal value;
Currency currency;
}
```
will assign property index 0 to `currency` and index 1 to `value`.
Note that it is not possible anymore to use `@Columns` to specify the names of columns of a composite user type mapping.
Since a `CompositeUserType` now constructs a proper component, it is necessary to use the `@AttributeOverride` annotation.
=== Plural attributes
@ -758,3 +784,53 @@ when loaded by-id i.e. through `Session#get`/`EntityManager#find`, even though f
Starting with Hibernate 6.0, the laziness of such associations is properly respected, regardless of the fetch mechanism.
Backwards compatibility can be achieved by specifying `lazy="false"` or `@ManyToOne(fetch = EAGER)`/`@OneToOne(fetch = EAGER)`/`@OneToMany(fetch = EAGER)`/`@ManyToMany(fetch = EAGER)`.
== hbm.xml <return-join/> behavior change
As of Hibernate 6.0, a `<return-join/>` will cause a fetch of an association, rather than adding a selection item.
Consider the following example:
```xml
<sql-query name="organizationreturnproperty">
<return alias="org" class="Organization">
<return-property name="id" column="ORGID"/>
<return-property name="name" column="NAME"/>
</return>
<return-join alias="emp" property="org.employments">
<return-property name="key" column="EMPLOYER"/>
<return-property name="element" column="EMPID"/>
<return-property name="element.employee" column="EMPLOYEE"/>
</return-join>
...
</sql-query>
```
Prior to 6.0, a query would return a list of tuples [`Organization`, `Employee`],
but now this will return a list of `Organization` with an initialized `employments` collection.
== hbm.xml multiple <column/> now disallowed
In 6.0 the support for basic property mappings with multiple columns was removed. The only use case for that was when a
`CompositeUserType` was in use, which was reworked to now work on top of components.
Uses like:
```xml
<property name="salary" type="org.hibernate.orm.test.sql.hand.MonetaryAmountUserType">
<column name="CURRENCY"/>
<column name="AMOUNT" sql-type="float"/>
</property>
```
have to be migrated to proper components:
```xml
<component name="salary" class="org.hibernate.orm.test.sql.hand.MonetaryAmountUserType">
<property name="value" column="AMOUNT">
<type name="float"/>
</property>
<property name="currency" column="CURRENCY"/>
</component>
```
The component class attribute now supports interpreting a `CompositeUserType` class properly.