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.Currency;
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 class ColumnTransformerTest extends BaseEntityManagerFunctionalTestCase {
//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 class ColumnTransformerTest extends BaseEntityManagerFunctionalTestCase {
@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 class ColumnTransformerTest extends BaseEntityManagerFunctionalTestCase {
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.query.sqm.function.SqmFunctionDescriptor;
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 InFlightMetadataCollectorImpl implements InFlightMetadataCollector
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.BasicType;
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 @@ public class ModelBinder {
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 class HbmResultSetMappingDescriptor implements NamedResultSetMappingDescr
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 class HbmResultSetMappingDescriptor implements NamedResultSetMappingDescr
+ " 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.CollectionClassification;
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 @@ public interface InFlightMetadataCollector extends Mapping, MetadataImplementor
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 jakarta.persistence.MappedSuperclass;
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 @@ public abstract class AbstractPropertyHolder implements PropertyHolder {
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 @@ public abstract class AbstractPropertyHolder implements PropertyHolder {
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 abstract class AbstractPropertyHolder implements PropertyHolder {
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 @@ public abstract class AbstractPropertyHolder implements PropertyHolder {
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 @@ public abstract class AbstractPropertyHolder implements PropertyHolder {
|| 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 @@ public abstract class AbstractPropertyHolder implements PropertyHolder {
}
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 @@ public abstract class AbstractPropertyHolder implements PropertyHolder {
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 @@ public class AnnotatedColumn {
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.annotation.Repeatable;
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.CollectionTypeRegistration;
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.ToOne;
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.JavaType;
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 final class AnnotationBinder {
handleTypeDescriptorRegistrations( pckg, context );
bindEmbeddableInstantiatorRegistrations( pckg, context );
bindCompositeUserTypeRegistrations( pckg, context );
bindGenericGenerators( pckg, context );
bindQueries( pckg, context );
@ -651,6 +660,7 @@ public final class AnnotationBinder {
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 @@ public final class AnnotationBinder {
);
}
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 @@ public final class AnnotationBinder {
true,
false,
null,
null,
context,
inheritanceStatePerClass
);
@ -2191,9 +2228,13 @@ public final class AnnotationBinder {
|| 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 @@ public final class AnnotationBinder {
inheritanceStatePerClass,
referencedEntityName,
determineCustomInstantiator(property, returnedClass, context),
compositeUserType,
isOverridden ? ( AnnotatedJoinColumn[] ) columns : null
);
}
@ -2829,6 +2871,29 @@ public final class AnnotationBinder {
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 @@ public final class AnnotationBinder {
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 @@ public final class AnnotationBinder {
isIdentifierMapper,
false,
customInstantiatorImpl,
compositeUserTypeClass,
buildingContext,
inheritanceStatePerClass
);
@ -3128,6 +3195,7 @@ public final class AnnotationBinder {
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 final class AnnotationBinder {
isIdentifierMapper,
inSecondPass,
customInstantiatorImpl,
compositeUserTypeClass,
buildingContext,
inheritanceStatePerClass
);
@ -3157,6 +3226,7 @@ public final class AnnotationBinder {
boolean isIdentifierMapper,
boolean inSecondPass,
Class<? extends EmbeddableInstantiator> customInstantiatorImpl,
Class<? extends CompositeUserType<?>> compositeUserTypeClass,
MetadataBuildingContext buildingContext,
Map<XClass, InheritanceState> inheritanceStatePerClass) {
/*
@ -3190,7 +3260,22 @@ public final class AnnotationBinder {
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 final class AnnotationBinder {
handleTypeDescriptorRegistrations( property, buildingContext );
bindEmbeddableInstantiatorRegistrations( property, buildingContext );
bindCompositeUserTypeRegistrations( property, buildingContext );
}
else {
Map<String, IdentifierGeneratorDefinition> localGenerators =
@ -3310,6 +3396,24 @@ public final class AnnotationBinder {
}
}
}
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 @@ public final class AnnotationBinder {
false,
false,
null,
null,
buildingContext,
inheritanceStatePerClass
);

View File

@ -11,6 +11,7 @@ import jakarta.persistence.ForeignKey;
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 @@ public interface PropertyHolder {
return null;
}
ColumnTransformer getOverriddenColumnTransformer(String logicalColumnName);
/**
* return
* - null if no join table is present,

View File

@ -26,6 +26,7 @@ import org.hibernate.annotations.CollectionIdJavaType;
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.ListIndexJdbcType;
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.metamodel.spi.EmbeddableInstantiator;
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 @@ public abstract class CollectionBinder {
}
}
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 @@ public abstract class CollectionBinder {
false,
true,
resolveCustomInstantiator(property, elementClass, buildingContext),
compositeUserType,
buildingContext,
inheritanceStatePerClass
);
@ -2104,6 +2113,27 @@ public abstract class CollectionBinder {
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.AnnotationException;
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.SimpleValue;
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 @@ public class MapBinder extends CollectionBinder {
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 @@ public class MapBinder extends CollectionBinder {
false,
true,
null,
compositeUserType,
buildingContext,
inheritanceStatePerClass
);
@ -371,6 +378,27 @@ public class MapBinder extends CollectionBinder {
}
}
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.SimpleValue;
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 class PropertyBinder {
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 class PropertyBinder {
property.setInsertable( insertable );
property.setUpdateable( updatable );
property.setPropertyAccessStrategy( propertyAccessStrategy );
inferOptimisticLocking(property);

View File

@ -3951,33 +3951,21 @@ public abstract class Dialect implements ConversionContext {
Long length) {
final Size size = new Size();
int jdbcTypeCode = jdbcType.getDefaultSqlTypeCode();
// Set the explicit length to null if we encounter the JPA default 255
if ( length != null && length == Size.DEFAULT_LENGTH ) {
length = null;
}
switch (jdbcTypeCode) {
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 ) {
length = null;
}
size.setLength( javaType.getDefaultSqlLength( Dialect.this, jdbcType ) );
break;
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 abstract class Dialect implements ConversionContext {
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 class Property implements Serializable, MetaAttributable {
propertyAccessorName = string;
}
public PropertyAccessStrategy getPropertyAccessStrategy() {
return propertyAccessStrategy;
}
public void setPropertyAccessStrategy(PropertyAccessStrategy propertyAccessStrategy) {
this.propertyAccessStrategy = propertyAccessStrategy;
}
/**
* Approximate!
*/
@ -355,6 +364,10 @@ public class Property implements Serializable, MetaAttributable {
// 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 class Property implements Serializable, MetaAttributable {
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.internal.PropertyAccessStrategyIndexBackRef
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 class EmbeddableRepresentationStrategyPojo extends AbstractEmbeddableRepr
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 class EmbeddableRepresentationStrategyPojo extends AbstractEmbeddableRepr
: 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 class EntityRepresentationStrategyPojoStandard implements EntityRepresent
.getMappingModelPart().getEmbeddableTypeDescriptor(),
// we currently do not support custom instantiators for identifiers
null,
null,
creationContext
);
}
@ -127,6 +128,7 @@ public class EntityRepresentationStrategyPojoStandard implements EntityRepresent
.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.ManagedTypeRepresentationResolver;
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 class ManagedTypeRepresentationResolverStandard implements ManagedTypeRep
}
}
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 class ManagedTypeRepresentationResolverStandard implements ManagedTypeRep
.getBean( customInstantiatorImpl )
.getBeanInstance();
}
else if ( compositeUserType != null ) {
customInstantiator = new EmbeddableCompositeUserTypeInstantiator( compositeUserType );
}
else {
customInstantiator = null;
}
@ -108,6 +127,7 @@ public class ManagedTypeRepresentationResolverStandard implements ManagedTypeRep
bootDescriptor,
runtimeDescriptorAccess,
customInstantiator,
compositeUserType,
creationContext
);
}

View File

@ -213,7 +213,7 @@ public class BasicValuedCollectionPart
@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.Map;
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 @@ import org.hibernate.sql.results.graph.FetchableContainer;
* @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 class FetchMementoHbmStandard implements FetchMemento, FetchMemento.Paren
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 class FetchMementoHbmStandard implements FetchMemento, FetchMemento.Paren
tableAlias,
navigablePath
);
fetchBuilderMap.forEach( resultBuilder::addFetchBuilder );
}
return new DynamicFetchBuilderLegacy(
tableAlias,

View File

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

View File

@ -19,16 +19,16 @@ import org.hibernate.query.results.FetchBuilder;
*/
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 @@ public abstract class AbstractFetchBuilderContainer<T extends AbstractFetchBuild
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 abstract class AbstractFetchBuilderContainer<T extends AbstractFetchBuild
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 java.util.function.BiFunction;
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 @@ import static org.hibernate.sql.ast.spi.SqlExpressionResolver.createColumnRefere
* @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 class DynamicFetchBuilderLegacy implements DynamicFetchBuilder, NativeQue
tableAlias,
ownerTableAlias,
fetchableName,
List.copyOf( columnNames ),
columnNames == null ? null : List.copyOf( columnNames ),
fetchBuilderMap,
resultBuilderEntity == null ? null : resultBuilderEntity.cacheKeyInstance()
);
@ -193,14 +197,37 @@ public class DynamicFetchBuilderLegacy implements DynamicFetchBuilder, NativeQue
);
}
}
return parent.generateFetchableFetch(
attributeMapping,
parent.resolveNavigablePath( attributeMapping ),
FetchTiming.IMMEDIATE,
true,
null,
domainResultCreationState
);
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 ),
FetchTiming.IMMEDIATE,
true,
null,
domainResultCreationState
);
}
finally {
creationState.popExplicitFetchMementoResolver();
}
}
private void resolveSqlSelection(
@ -242,18 +269,37 @@ public class DynamicFetchBuilderLegacy implements DynamicFetchBuilder, NativeQue
}
@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 @@ public class DynamicFetchBuilderStandard
}
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.EntityMappingType;
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 @@ public class DynamicResultBuilderEntityStandard
);
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 @@ public class DynamicResultBuilderEntityStandard
prefix = "";
}
else {
prefix = currentRelativePath.getFullPath() + ".";
prefix = currentRelativePath.getFullPath()
.replace( ELEMENT_PREFIX, "" )
.replace( INDEX_PREFIX, "" ) + ".";
}
creationState.pushExplicitFetchMementoResolver(
relativePath -> {
@ -261,7 +265,7 @@ public class DynamicResultBuilderEntityStandard
}
}
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.CoreLogging;
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.Joinable;
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.complete.CompleteResultBuilderCollectionStand
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 @@ public class ResultSetMappingProcessor implements SQLQueryParser.ParserContext {
columnNames = Arrays.asList( keyColumnAliases );
if ( collectionPersister.hasIndex() ) {
resultBuilderEntity.addProperty(
"{key}", // That's what BasicValuedCollectionPart returns..
CollectionPart.Nature.INDEX.getName(),
collectionPersister.getIndexColumnAliases( collectionSuffix )
);
}
@ -326,30 +329,79 @@ public class ResultSetMappingProcessor implements SQLQueryParser.ParserContext {
for ( String propertyName : loadable.getPropertyNames() ) {
final String[] columnAliases = loadable.getSubclassPropertyColumnAliases( propertyName, suffix );
if ( columnAliases.length == 0 ) {
final Type propertyType = loadable.getPropertyType( propertyName );
if ( propertyType instanceof CollectionType ) {
final CollectionType collectionType = (CollectionType) propertyType;
final String[] keyColumnAliases;
if ( collectionType.useLHSPrimaryKey() ) {
keyColumnAliases = identifierAliases;
}
else {
keyColumnAliases = loadable.getSubclassPropertyColumnAliases(
collectionType.getLHSPropertyName(),
suffix
);
}
resultBuilderEntity.addProperty( propertyName, keyColumnAliases );
}
}
else {
resultBuilderEntity.addProperty( propertyName, columnAliases );
}
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;
if ( collectionType.useLHSPrimaryKey() ) {
keyColumnAliases = identifierAliases;
}
else {
keyColumnAliases = loadable.getSubclassPropertyColumnAliases(
collectionType.getLHSPropertyName(),
suffix
);
}
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;
}
resultBuilderEntity.addFetchBuilder( propertyName, fetchBuilder );
}
else if ( columnAliases.length != 0 ) {
resultBuilderEntity.addProperty( propertyName, columnAliases );
}
}
private CompleteResultBuilderCollectionStandard createSuffixedResultBuilder(
NativeQuery.CollectionReturn collectionReturn,
String suffix,

View File

@ -37,11 +37,13 @@ import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart;
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 class ComponentType extends AbstractType implements CompositeTypeImplemen
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 class ComponentType extends AbstractType implements CompositeTypeImplemen
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 class ComponentType extends AbstractType implements CompositeTypeImplemen
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 class ComponentType extends AbstractType implements CompositeTypeImplemen
@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 class ComponentType extends AbstractType implements CompositeTypeImplemen
@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 class ComponentType extends AbstractType implements CompositeTypeImplemen
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 class ComponentType extends AbstractType implements CompositeTypeImplemen
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 class ComponentType extends AbstractType implements CompositeTypeImplemen
if ( original == null ) {
return null;
}
if ( compositeUserType != null ) {
return compositeUserType.replace( original, target, owner );
}
//if ( original == target ) return target;
@ -574,7 +613,7 @@ public class ComponentType extends AbstractType implements CompositeTypeImplemen
@Override
public boolean isMutable() {
return true;
return compositeUserType == null || compositeUserType.isMutable();
}
@Override
@ -584,6 +623,9 @@ public class ComponentType extends AbstractType implements CompositeTypeImplemen
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 class ComponentType extends AbstractType implements CompositeTypeImplemen
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 @@ package org.hibernate.type.descriptor.java;
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 class CurrencyJavaType extends AbstractClassJavaType<Currency> {
}
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 @@ import org.hibernate.usertype.UserType;
*
* @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 class MonetaryAmountUserType implements UserType {
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 class MonetaryAmountUserType implements UserType {
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 abstract class CustomSQLTestSupport extends BaseCoreFunctionalTestCase {
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 java.util.List;
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 @@ public abstract class CustomStoredProcTestSupport extends CustomSQLTestSupport {
@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 abstract class CustomStoredProcTestSupport extends CustomSQLTestSupport {
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 abstract class CustomStoredProcTestSupport extends CustomSQLTestSupport {
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 abstract class CustomStoredProcTestSupport extends CustomSQLTestSupport {
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

@ -38,11 +38,11 @@
<return-property name="startDate" column="STARTDATE"/>
<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="employmentId" 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-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.dialect.DB2Dialect;
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 @@ import org.junit.Ignore;
* @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"/>
</property>
<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
@ -164,13 +166,11 @@
<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 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-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>
<!-- 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 selectAllEmployments() }
</sql-query>

View File

@ -10,16 +10,11 @@ import org.hibernate.dialect.DerbyDialect;
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 class DerbyStoreProcedures {
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"/>
</property>
<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>
<!-- 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"/>
<!-- 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>
<!-- 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 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"/>
</property>
<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>
<!-- 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"/>
<!-- 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
{}-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="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-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 @@ import org.junit.Ignore;
* @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"/>
</property>
<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
{}-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 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-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.dialect.OracleDialect;
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 @@ import org.junit.Ignore;
* @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"/>
</property>
<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
@ -163,13 +165,11 @@
<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 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-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>
@ -207,13 +207,11 @@
<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
{}-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="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-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.dialect.SQLServerDialect;
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 @@ import org.junit.Ignore;
* @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"/>
</property>
<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
@ -163,13 +165,11 @@
<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 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-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>
@ -207,13 +207,11 @@
<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
{}-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="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-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.dialect.SybaseDialect;
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 @@ import org.junit.Ignore;
* @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"/>
</property>
-->
<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.Speech;
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 @@ import static org.junit.jupiter.api.Assertions.fail;
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 @@ public class NativeSQLQueriesTest {
}
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 class NativeSQLQueriesTest {
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 class NativeSQLQueriesTest {
.addJoin("emp", "org.employments")
.addJoin("pers", "emp.employee")
.list();
assertEquals( l.size(), 1 );
assertEquals( 1, l.size() );
}
);
@ -214,14 +219,21 @@ public class NativeSQLQueriesTest {
" 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 class NativeSQLQueriesTest {
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 class NativeSQLQueriesTest {
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 class NativeSQLQueriesTest {
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 class NativeSQLQueriesTest {
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 class NativeSQLQueriesTest {
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 class NativeSQLQueriesTest {
);
}
@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 class NativeSQLQueriesTest {
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 class NativeSQLQueriesTest {
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 @@ public abstract class AbstractCollectionMetadataGenerator extends AbstractMetada
valueMetadataGenerator.addValue(
entity,
component.getProperty( auditedPropertyName ).getValue(),
component.getProperty( auditedPropertyName ).getPropertyAccessStrategy(),
componentMapper,
prefix,
context.getEntityMappingData(),
@ -351,6 +352,7 @@ public abstract class AbstractCollectionMetadataGenerator extends AbstractMetada
valueMetadataGenerator.addValue(
entity,
component.getProperty( auditedPropertyName ).getValue(),
component.getProperty( auditedPropertyName ).getPropertyAccessStrategy(),
componentMapper,
context.getReferencingEntityName(),
context.getEntityMappingData(),

View File

@ -105,6 +105,7 @@ public final class AuditMetadataGenerator extends AbstractMetadataGenerator {
valueMetadataGenerator.addValue(
attributeContainer,
property.getValue(),
property.getPropertyAccessStrategy(),
currentMapper,
entityName,
mappingData,
@ -370,6 +371,7 @@ public final class AuditMetadataGenerator extends AbstractMetadataGenerator {
valueMetadataGenerator.addValue(
entity,
propertyAuditingData.getValue(),
null,
currentMapper,
entityName,
mappingData,

View File

@ -19,6 +19,10 @@ import org.hibernate.envers.internal.entities.mapper.CompositeMapperBuilder;
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 final class ComponentMetadataGenerator extends AbstractMetadataGenerator
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 final class ComponentMetadataGenerator extends AbstractMetadataGenerator
valueGenerator.addValue(
attributeContainer,
property.getValue(),
property.getPropertyAccessStrategy(),
componentMapper,
entityName,
mappingData,

View File

@ -18,6 +18,7 @@ import org.hibernate.envers.internal.tools.MappingTools;
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 class ValueMetadataGenerator extends AbstractMetadataGenerator {
public void addValue(
AttributeContainer attributeContainer,
Value value,
PropertyAccessStrategy propertyAccessStrategy,
CompositeMapperBuilder currentMapper,
String entityName,
EntityMappingData mappingData,
@ -63,6 +65,7 @@ public class ValueMetadataGenerator extends AbstractMetadataGenerator {
addValueInFirstPass(
attributeContainer,
value,
propertyAccessStrategy,
currentMapper,
entityName,
mappingData,
@ -75,6 +78,7 @@ public class ValueMetadataGenerator extends AbstractMetadataGenerator {
addValueInSecondPass(
attributeContainer,
value,
propertyAccessStrategy,
currentMapper,
entityName,
mappingData,
@ -88,6 +92,7 @@ public class ValueMetadataGenerator extends AbstractMetadataGenerator {
private void addValueInFirstPass(
AttributeContainer attributeContainer,
Value value,
PropertyAccessStrategy propertyAccessStrategy,
CompositeMapperBuilder currentMapper,
String entityName,
EntityMappingData mappingData,
@ -95,6 +100,7 @@ public class ValueMetadataGenerator extends AbstractMetadataGenerator {
boolean insertable,
boolean processModifiedFlag) {
final Type type = value.getType();
propertyAuditingData.setPropertyAccessStrategy( propertyAccessStrategy );
if ( type instanceof BasicType ) {
basicMetadataGenerator.addBasic(
@ -134,6 +140,7 @@ public class ValueMetadataGenerator extends AbstractMetadataGenerator {
private void addValueInSecondPass(
AttributeContainer attributeContainer,
Value value,
PropertyAccessStrategy propertyAccessStrategy,
CompositeMapperBuilder currentMapper,
String entityName,
EntityMappingData mappingData,

View File

@ -9,6 +9,7 @@ package org.hibernate.envers.configuration.internal.metadata.reader;
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 class AuditedPropertiesReader {
}
public void read() {
read(null);
}
public void read(Audited allClassAudited) {
// First reading the access types for the persistent properties.
readPersistentPropertiesAccess();
@ -126,10 +131,33 @@ public class AuditedPropertiesReader {
// 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();
readAuditOverrides( clazz );
// Adding all properties from the given class.
addPropertiesFromClass( clazz );
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 );
}
}
}
@ -475,7 +503,7 @@ public class AuditedPropertiesReader {
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 @@ public class AuditedPropertiesReader {
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 @@ public class AuditedPropertiesReader {
}
}
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 @@ public class AuditedPropertiesReader {
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 interface PersistentPropertiesSource {
public boolean isDynamicComponent() {
return false;
}
@Override
public boolean hasCompositeUserType() {
return false;
}
};
}
@ -115,6 +122,11 @@ public interface PersistentPropertiesSource {
public boolean isDynamicComponent() {
return dynamic;
}
@Override
public boolean hasCompositeUserType() {
return component.getTypeName() != null;
}
};
}
}

View File

@ -20,6 +20,7 @@ import org.hibernate.envers.RelationTargetNotFoundAction;
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 class PropertyAuditingData {
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 class PropertyAuditingData {
modifiedFlagName,
synthetic,
propertyType,
virtualPropertyType.getReturnedClass()
virtualPropertyType.getReturnedClass(),
propertyAccessStrategy
);
}
else if ( propertyType != null ) {
@ -360,7 +371,8 @@ public class PropertyAuditingData {
usingModifiedFlag,
modifiedFlagName,
synthetic,
propertyType
propertyType,
propertyAccessStrategy
);
}
return new PropertyData(
@ -370,7 +382,8 @@ public class PropertyAuditingData {
usingModifiedFlag,
modifiedFlagName,
synthetic,
null
null,
propertyAccessStrategy
);
}
}

View File

@ -8,6 +8,7 @@ package org.hibernate.envers.internal.entities;
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 class 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 class 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 PropertyData {
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.entities.PropertyData;
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 class ComponentPropertyMapper extends AbstractPropertyMapper implements C
}
@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 class ComponentPropertyMapper extends AbstractPropertyMapper implements C
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 class ComponentPropertyMapper extends AbstractPropertyMapper implements C
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 class ComponentPropertyMapper extends AbstractPropertyMapper implements C
} );
}
@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 @@ package org.hibernate.envers.internal.entities.mapper;
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 class MultiDynamicComponentMapper extends MultiPropertyMapper {
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.MappingTools;
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 class MultiPropertyMapper extends AbstractPropertyMapper implements Exten
}
@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 class MultiPropertyMapper extends AbstractPropertyMapper implements Exten
final ComponentPropertyMapper componentMapperBuilder = new ComponentPropertyMapper(
propertyData,
componentClass
componentClass,
instantiator
);
addComposite( propertyData, componentMapperBuilder );
@ -198,6 +202,16 @@ public class MultiPropertyMapper extends AbstractPropertyMapper implements Exten
}
}
@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 @@ public interface PropertyMapper extends ModifiedFlagMapperSupport, DynamicCompon
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 class SinglePropertyMapper extends AbstractPropertyMapper implements Simp
}
}
@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.engine.spi.SessionImplementor;
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 class SubclassPropertyMapper extends AbstractPropertyMapper implements Ex
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 class SubclassPropertyMapper extends AbstractPropertyMapper implements Ex
}
@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 abstract class AbstractCollectionMapper<T> extends AbstractPropertyMapper
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 abstract class AbstractCollectionMapper<T> extends AbstractPropertyMapper
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 abstract class AbstractOneToOneMapper extends AbstractToOneMapper {
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 abstract class AbstractOneToOneMapper extends AbstractToOneMapper {
"." + getPropertyData().getBeanName() + ".", e
);
}
setPropertyValue( obj, value );
return value;
}
/**

View File

@ -58,6 +58,16 @@ public abstract class AbstractToOneMapper extends AbstractPropertyMapper {
}
}
@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 class AbstractToOneMapper extends AbstractPropertyMapper {
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 class ToOneIdMapper extends AbstractToOneMapper {
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 class ToOneIdMapper extends AbstractToOneMapper {
}
}
}
setPropertyValue( obj, value );
return value;
}
public void addMiddleEqualToQuery(

View File

@ -17,6 +17,7 @@ import org.hibernate.envers.internal.entities.mapper.MultiPropertyMapper;
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 @@ public class MiddleEmbeddableComponentMapper extends AbstractMiddleComponentMapp
}
@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,7 +47,23 @@ public abstract class ReflectionTools {
}
public static Getter getGetter(Class cls, PropertyData propertyData, ServiceRegistry serviceRegistry) {
return getGetter( cls, propertyData.getBeanName(), propertyData.getAccessType(), 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) {

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 @@ import org.hibernate.usertype.UserType;
*
* @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 class ObjectUserType implements UserType {
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 class ObjectUserType implements UserType {
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.Arrays;
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 @@ import org.junit.Test;
* @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
@ -757,4 +783,54 @@ Prior to Hibernate 6.0, lazy associations that used `fetch="join"` or `@Fetch(Fe
when loaded by-id i.e. through `Session#get`/`EntityManager#find`, even though for queries the association was treated as lazy.
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)`.
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.