HHH-9834 - Fix primary key generation for Map-based ElementCollections that use Lob.

This commit is contained in:
Chris Cranford 2016-12-22 03:11:34 -05:00
parent 4cbb53c17a
commit cc56c9672b
3 changed files with 142 additions and 6 deletions

View File

@ -47,6 +47,7 @@ import org.hibernate.envers.internal.entities.mapper.relation.SortedSetCollectio
import org.hibernate.envers.internal.entities.mapper.relation.ToOneIdMapper;
import org.hibernate.envers.internal.entities.mapper.relation.component.MiddleDummyComponentMapper;
import org.hibernate.envers.internal.entities.mapper.relation.component.MiddleEmbeddableComponentMapper;
import org.hibernate.envers.internal.entities.mapper.relation.component.MiddleMapElementNotKeyComponentMapper;
import org.hibernate.envers.internal.entities.mapper.relation.component.MiddleMapKeyIdComponentMapper;
import org.hibernate.envers.internal.entities.mapper.relation.component.MiddleMapKeyPropertyComponentMapper;
import org.hibernate.envers.internal.entities.mapper.relation.component.MiddleRelatedComponentMapper;
@ -78,6 +79,8 @@ import org.hibernate.type.ComponentType;
import org.hibernate.type.ListType;
import org.hibernate.type.ManyToOneType;
import org.hibernate.type.MapType;
import org.hibernate.type.MaterializedClobType;
import org.hibernate.type.MaterializedNClobType;
import org.hibernate.type.SetType;
import org.hibernate.type.SortedMapType;
import org.hibernate.type.SortedSetType;
@ -467,7 +470,8 @@ public final class CollectionMetadataGenerator {
middleEntityXml,
queryGeneratorBuilder,
referencedPrefix,
propertyAuditingData.getJoinTable().inverseJoinColumns()
propertyAuditingData.getJoinTable().inverseJoinColumns(),
isCollectionElementKeyProperty()
);
// ******
@ -510,7 +514,8 @@ public final class CollectionMetadataGenerator {
middleEntityXml,
queryGeneratorBuilder,
"mapkey",
null
null,
true
);
}
else {
@ -562,7 +567,8 @@ public final class CollectionMetadataGenerator {
Element xmlMapping,
QueryGeneratorBuilder queryGeneratorBuilder,
String prefix,
JoinColumn[] joinColumns) {
JoinColumn[] joinColumns,
boolean key) {
final Type type = value.getType();
if ( type instanceof ManyToOneType ) {
final String prefixRelated = prefix + "_";
@ -673,7 +679,7 @@ public final class CollectionMetadataGenerator {
else {
// Last but one parameter: collection components are always insertable
final boolean mapped = mainGenerator.getBasicMetadataGenerator().addBasic(
xmlMapping,
key ? xmlMapping : xmlMapping.getParent(),
new PropertyAuditingData(
prefix,
"field",
@ -686,16 +692,23 @@ public final class CollectionMetadataGenerator {
value,
null,
true,
true
key
);
if ( mapped ) {
if ( mapped && key ) {
// Simple values are always stored in the first item of the array returned by the query generator.
return new MiddleComponentData(
new MiddleSimpleComponentMapper( mainGenerator.getVerEntCfg(), prefix ),
0
);
}
else if ( mapped && !key ) {
// when mapped but not part of the key, its stored as a dummy mapper??
return new MiddleComponentData(
new MiddleMapElementNotKeyComponentMapper( mainGenerator.getVerEntCfg(), prefix ),
0
);
}
else {
mainGenerator.throwUnsupportedTypeException( type, referencingEntityName, propertyName );
// Impossible to get here.
@ -1008,4 +1021,22 @@ public final class CollectionMetadataGenerator {
return table;
}
}
/**
* Checks whether the collection element should participate in the primary key association. This
* is only applicable for non-component, non-associative collection element types.
*
* @return {@code true} if should be part of the primary key, {@code false} if not.
*/
private boolean isCollectionElementKeyProperty() {
// When List or Set are used, the element will always be part of the primary key.
// When using a Map, the element isn't required as the Map Key will suffice.
if ( propertyValue instanceof org.hibernate.mapping.Map ) {
final Type type = propertyValue.getElement().getType();
if ( !type.isComponentType() && !type.isAssociationType() ) {
return !( type instanceof MaterializedClobType || type instanceof MaterializedNClobType );
}
}
return true;
}
}

View File

@ -14,11 +14,14 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.persistence.ElementCollection;
import javax.persistence.JoinColumn;
import javax.persistence.Lob;
import javax.persistence.MapKey;
import javax.persistence.OneToMany;
import javax.persistence.Version;
import org.hibernate.HibernateException;
import org.hibernate.MappingException;
import org.hibernate.annotations.common.reflection.ClassLoadingException;
import org.hibernate.annotations.common.reflection.ReflectionManager;
@ -35,6 +38,7 @@ import org.hibernate.envers.NotAudited;
import org.hibernate.envers.RelationTargetAuditMode;
import org.hibernate.envers.configuration.internal.GlobalConfiguration;
import org.hibernate.envers.configuration.internal.metadata.MetadataTools;
import org.hibernate.envers.internal.EnversMessageLogger;
import org.hibernate.envers.internal.tools.MappingTools;
import org.hibernate.envers.internal.tools.ReflectionTools;
import org.hibernate.envers.internal.tools.StringTools;
@ -42,6 +46,7 @@ import org.hibernate.loader.PropertyPath;
import org.hibernate.mapping.Component;
import org.hibernate.mapping.Property;
import org.hibernate.mapping.Value;
import org.jboss.logging.Logger;
import static org.hibernate.envers.internal.tools.Tools.newHashMap;
import static org.hibernate.envers.internal.tools.Tools.newHashSet;
@ -58,6 +63,11 @@ import static org.hibernate.envers.internal.tools.Tools.newHashSet;
* @author Lukasz Zuchowski (author at zuchos dot com)
*/
public class AuditedPropertiesReader {
private static final EnversMessageLogger LOG = Logger.getMessageLogger(
EnversMessageLogger.class,
AuditedPropertiesReader.class.getName()
);
protected final ModificationStore defaultStore;
private final PersistentPropertiesSource persistentPropertiesSource;
private final AuditedPropertiesHolder auditedPropertiesHolder;
@ -502,6 +512,8 @@ public class AuditedPropertiesReader {
}
}
validateLobMappingSupport( property );
final String propertyName = propertyNamePrefix + property.getName();
if ( !this.checkAudited( property, propertyData,propertyName, allClassAudited, globalCfg.getModifiedFlagSuffix() ) ) {
return false;
@ -524,6 +536,30 @@ public class AuditedPropertiesReader {
return true;
}
private void validateLobMappingSupport(XProperty property) {
// HHH-9834 - Sanity check
try {
if ( property.isAnnotationPresent( ElementCollection.class ) ) {
if ( property.isAnnotationPresent( Lob.class ) ) {
if ( !property.getCollectionClass().isAssignableFrom( Map.class ) ) {
throw new MappingException(
"@ElementCollection combined with @Lob is only supported for Map collection types."
);
}
}
}
}
catch ( MappingException e ) {
throw new HibernateException(
String.format(
"Invalid mapping in [%s] for property [%s]",
property.getDeclaringClass().getName(),
property.getName()
),
e
);
}
}
protected boolean checkAudited(
XProperty property,

View File

@ -0,0 +1,69 @@
/*
* 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.envers.internal.entities.mapper.relation.component;
import java.util.Map;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.envers.configuration.internal.AuditEntitiesConfiguration;
import org.hibernate.envers.internal.entities.EntityInstantiator;
import org.hibernate.envers.internal.tools.query.Parameters;
/**
* A middle table component mapper which assigns a Map-type's element as part
* of the data-portion of the mapping rather than the identifier.
* <p>
* This is useful for mappings where the database does not support CLOB or NCLOB
* data types as part of the primary key for the table.
* </p>
* An example:
* <pre>
* &#64;ElementCollection
* &#64;Lob
* private Map&lt;String, String&gt; values;
* </pre>
*
* @author Chris Cranford
*/
public class MiddleMapElementNotKeyComponentMapper implements MiddleComponentMapper {
private final String propertyName;
private final AuditEntitiesConfiguration verEntCfg;
public MiddleMapElementNotKeyComponentMapper(AuditEntitiesConfiguration verEntCfg, String propertyName) {
this.propertyName = propertyName;
this.verEntCfg = verEntCfg;
}
@Override
@SuppressWarnings({"unchecked"})
public Object mapToObjectFromFullMap(
EntityInstantiator entityInstantiator,
Map<String, Object> data,
Object dataObject,
Number revision) {
return data.get( propertyName );
}
@Override
public void mapToMapFromObject(
SessionImplementor session,
Map<String, Object> idData,
Map<String, Object> data,
Object obj) {
data.put( propertyName, obj );
}
@Override
public void addMiddleEqualToQuery(
Parameters parameters,
String idPrefix1,
String prefix1,
String idPrefix2,
String prefix2) {
parameters.addWhere( prefix1 + "." + propertyName, false, "=", prefix2 + "." + propertyName, false );
}
}