Implement support for CompositeUserType and re-enable tests that make use of it
This commit is contained in:
parent
277f10d987
commit
c520b48487
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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;
|
||||
|
||||
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
|
|
|
@ -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 ) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 ) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 ) {
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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 ) );
|
||||
|
|
|
@ -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() );
|
||||
|
|
|
@ -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() );
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
);
|
||||
}
|
||||
|
|
|
@ -213,7 +213,7 @@ public class BasicValuedCollectionPart
|
|||
|
||||
@Override
|
||||
public String getFetchableName() {
|
||||
return nature == Nature.ELEMENT ? "{value}" : "{key}";
|
||||
return nature.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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 );
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -66,6 +66,10 @@ public class CompleteFetchBuilderEntityValuedModelPart
|
|||
return modelPart;
|
||||
}
|
||||
|
||||
public List<String> getColumnAliases() {
|
||||
return columnAliases;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Fetch buildFetch(
|
||||
FetchParent parent,
|
||||
|
|
|
@ -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<>();
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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() );
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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() );
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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" };
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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"};
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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" };
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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" };
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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" };
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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" };
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 );
|
||||
}
|
|
@ -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(),
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 ) {
|
||||
|
|
|
@ -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() );
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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() );
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in New Issue