HHH-11797 Add support for @MapKeyEnumerated mappings

This commit is contained in:
Chris Cranford 2019-09-18 17:24:06 -04:00 committed by Andrea Boriero
parent 7355fc5131
commit 4b8c81908c
6 changed files with 338 additions and 3 deletions

View File

@ -20,6 +20,7 @@ import org.hibernate.envers.configuration.internal.metadata.reader.ComponentAudi
import org.hibernate.envers.configuration.internal.metadata.reader.PropertyAuditingData;
import org.hibernate.envers.internal.EnversMessageLogger;
import org.hibernate.envers.internal.tools.MappingTools;
import org.hibernate.envers.internal.tools.StringTools;
import org.hibernate.mapping.Component;
import org.hibernate.mapping.List;
import org.hibernate.mapping.PersistentClass;
@ -112,6 +113,12 @@ public class ClassesAuditingData {
}
}
if ( propertyAuditingData.getMapKeyEnumType() != null ) {
final String referencedEntityName = MappingTools.getReferencedEntityName( property.getValue() );
final ClassAuditingData referencedAuditingData = entityNameToAuditingData.get( referencedEntityName );
addMapEnumeratedKey( property.getValue(), property.getPropertyAccessorName(), referencedAuditingData );
}
// HHH-9108
// Added support to handle nested property calculations for components.
// This is useful for AuditMappedBy inside an Embeddable that holds a collection of entities.
@ -166,6 +173,30 @@ public class ClassesAuditingData {
}
}
private void addMapEnumeratedKey(Value value, String propertyAccessorName, ClassAuditingData classAuditingData) {
if ( value instanceof org.hibernate.mapping.Map ) {
final Value indexValue = ( (org.hibernate.mapping.Map) value ).getIndex();
if ( indexValue != null && indexValue.getColumnIterator().hasNext() ) {
final String indexColumnName = indexValue.getColumnIterator().next().getText();
if ( !StringTools.isEmpty( indexColumnName ) ) {
final PropertyAuditingData propertyAuditingData = new PropertyAuditingData(
indexColumnName,
propertyAccessorName,
ModificationStore.FULL,
RelationTargetAuditMode.AUDITED,
null,
null,
true,
true,
indexValue
);
classAuditingData.addPropertyAuditingData( indexColumnName, propertyAuditingData );
}
}
}
}
private void forcePropertyInsertable(
ClassAuditingData classAuditingData, String propertyName,
String entityName, String referencedEntityName) {

View File

@ -16,6 +16,7 @@ import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import javax.persistence.EnumType;
import javax.persistence.JoinColumn;
import org.dom4j.Element;
@ -42,6 +43,7 @@ import org.hibernate.envers.internal.entities.mapper.relation.ListCollectionMapp
import org.hibernate.envers.internal.entities.mapper.relation.MapCollectionMapper;
import org.hibernate.envers.internal.entities.mapper.relation.MiddleComponentData;
import org.hibernate.envers.internal.entities.mapper.relation.MiddleIdData;
import org.hibernate.envers.internal.entities.mapper.relation.MiddleMapKeyEnumeratedComponentMapper;
import org.hibernate.envers.internal.entities.mapper.relation.SortedMapCollectionMapper;
import org.hibernate.envers.internal.entities.mapper.relation.SortedSetCollectionMapper;
import org.hibernate.envers.internal.entities.mapper.relation.ToOneIdMapper;
@ -64,7 +66,6 @@ import org.hibernate.envers.internal.tools.MappingTools;
import org.hibernate.envers.internal.tools.ReflectionTools;
import org.hibernate.envers.internal.tools.StringTools;
import org.hibernate.envers.internal.tools.Tools;
import org.hibernate.mapping.Bag;
import org.hibernate.mapping.Collection;
import org.hibernate.mapping.Component;
import org.hibernate.mapping.IndexedCollection;
@ -215,7 +216,8 @@ public final class CollectionMetadataGenerator {
// in a join table, so the prefix value is arbitrary).
final MiddleIdData referencedIdData = createMiddleIdData(
referencedIdMapping,
null, referencedEntityName
null,
referencedEntityName
);
// Generating the element mapping.
@ -523,7 +525,8 @@ public final class CollectionMetadataGenerator {
if ( propertyValue instanceof IndexedCollection ) {
final IndexedCollection indexedValue = (IndexedCollection) propertyValue;
final String mapKey = propertyAuditingData.getMapKey();
if ( mapKey == null ) {
final EnumType mapKeyEnumType = propertyAuditingData.getMapKeyEnumType();
if ( mapKey == null && mapKeyEnumType == null ) {
// This entity doesn't specify a javax.persistence.MapKey. Mapping it to the middle entity.
return addValueToMiddleTable(
indexedValue.getIndex(),
@ -534,6 +537,15 @@ public final class CollectionMetadataGenerator {
true
);
}
else if ( mapKeyEnumType != null ) {
final IdMappingData referencedIdMapping = mainGenerator.getEntitiesConfigurations()
.get( referencedEntityName ).getIdMappingData();
final int currentIndex = queryGeneratorBuilder == null ? 0 : queryGeneratorBuilder.getCurrentIndex();
return new MiddleComponentData(
new MiddleMapKeyEnumeratedComponentMapper( propertyAuditingData.getName() ),
currentIndex
);
}
else {
final IdMappingData referencedIdMapping = mainGenerator.getEntitiesConfigurations()
.get( referencedEntityName ).getIdMappingData();

View File

@ -19,6 +19,7 @@ import javax.persistence.ElementCollection;
import javax.persistence.JoinColumn;
import javax.persistence.Lob;
import javax.persistence.MapKey;
import javax.persistence.MapKeyEnumerated;
import javax.persistence.OneToMany;
import javax.persistence.Version;
@ -640,6 +641,12 @@ public class AuditedPropertiesReader {
if ( mapKey != null ) {
propertyData.setMapKey( mapKey.name() );
}
else {
final MapKeyEnumerated mapKeyEnumerated = property.getAnnotation( MapKeyEnumerated.class );
if ( mapKeyEnumerated != null ) {
propertyData.setMapKeyEnumType( mapKeyEnumerated.value() );
}
}
}
private void addPropertyJoinTables(XProperty property, PropertyAuditingData propertyData) {

View File

@ -9,6 +9,8 @@ package org.hibernate.envers.configuration.internal.metadata.reader;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.EnumType;
import org.hibernate.envers.AuditJoinTable;
import org.hibernate.envers.AuditOverride;
import org.hibernate.envers.AuditOverrides;
@ -28,6 +30,7 @@ public class PropertyAuditingData {
private String beanName;
private ModificationStore store;
private String mapKey;
private EnumType mapKeyEnumType;
private AuditJoinTable joinTable;
private String accessType;
private final List<AuditOverride> auditJoinTableOverrides = new ArrayList<>( 0 );
@ -126,6 +129,14 @@ public class PropertyAuditingData {
this.mapKey = mapKey;
}
public EnumType getMapKeyEnumType() {
return mapKeyEnumType;
}
public void setMapKeyEnumType(EnumType mapKeyEnumType) {
this.mapKeyEnumType = mapKeyEnumType;
}
public AuditJoinTable getJoinTable() {
return joinTable;
}

View File

@ -0,0 +1,55 @@
/*
* 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;
import java.util.Map;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.envers.internal.entities.EntityInstantiator;
import org.hibernate.envers.internal.entities.mapper.relation.component.MiddleComponentMapper;
import org.hibernate.envers.internal.tools.query.Parameters;
/**
* A {@link MiddleComponentMapper} specifically for {@link javax.persistence.MapKeyEnumerated}.
*
* @author Chris Cranford
*/
public class MiddleMapKeyEnumeratedComponentMapper implements MiddleComponentMapper {
private final String propertyName;
public MiddleMapKeyEnumeratedComponentMapper(String propertyPrefix) {
this.propertyName = propertyPrefix + "_KEY";
}
@Override
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) {
idData.put( propertyName, obj );
}
@Override
public void addMiddleEqualToQuery(
Parameters parameters,
String idPrefix1,
String prefix1,
String idPrefix2,
String prefix2) {
throw new UnsupportedOperationException( "Cannot use this mapper with a middle table!" );
}
}

View File

@ -0,0 +1,219 @@
/*
* 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.test.integration.collection.mapkey;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.MapKeyEnumerated;
import javax.persistence.OneToMany;
import org.hibernate.envers.Audited;
import org.hibernate.envers.test.BaseEnversJPAFunctionalTestCase;
import org.hibernate.envers.test.Priority;
import org.hibernate.envers.test.tools.TestTools;
import org.junit.Test;
import org.hibernate.testing.TestForIssue;
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
import static org.junit.Assert.assertEquals;
/**
* @author Chris Cranford
*/
@TestForIssue(jiraKey = "HHH-11797")
public class MapKeyEnumeratedTest extends BaseEnversJPAFunctionalTestCase {
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] { TestEntity.class, MapEntity.class };
}
@Test
@Priority(10)
public void initData() {
// Revision 1
doInJPA( this::entityManagerFactory, entityManager -> {
final MapEntity map = new MapEntity( "Map1" );
map.setId( 1 );
final TestEntity test = new TestEntity();
test.setId( 1 );
test.addMapKeyAssociation( TestEnum.ONE, map );
entityManager.persist( test );
entityManager.persist( map );
} );
// Revision 2
doInJPA( this::entityManagerFactory, entityManager -> {
final MapEntity map = new MapEntity( "Map2" );
map.setId( 2 );
final TestEntity test = entityManager.find( TestEntity.class, 1 );
test.addMapKeyAssociation( TestEnum.TWO, map );
entityManager.persist( map );
entityManager.merge( test );
} );
// Revision 3
doInJPA( this::entityManagerFactory, entityManager -> {
final TestEntity test = entityManager.find( TestEntity.class, 1 );
final MapEntity map = test.removeMapKeyAssociation( TestEnum.ONE );
entityManager.remove( map );
entityManager.merge( test );
} );
// Revision 4
doInJPA( this::entityManagerFactory, entityManager -> {
final TestEntity test = entityManager.find( TestEntity.class, 1 );
final MapEntity map = test.removeMapKeyAssociation( TestEnum.TWO );
entityManager.remove( map );
entityManager.merge( test );
} );
}
@Test
public void testRevisionNumberHistory() {
assertEquals( Arrays.asList( 1, 2, 3, 4 ), getAuditReader().getRevisions( TestEntity.class, 1 ) );
assertEquals( Arrays.asList( 1, 3 ), getAuditReader().getRevisions( MapEntity.class, 1 ) );
assertEquals( Arrays.asList( 2, 4 ), getAuditReader().getRevisions( MapEntity.class, 2 ) );
}
@Test
public void testRevisionHistory() {
final TestEntity rev1 = getAuditReader().find( TestEntity.class, 1, 1 );
assertEquals( 1, rev1.getMapEntityMap().size() );
assertEquals( TestEnum.ONE, rev1.getMapEntityMap().keySet().iterator().next() );
final TestEntity rev2 = getAuditReader().find( TestEntity.class, 1, 2 );
assertEquals( 2, rev2.getMapEntityMap().size() );
assertEquals( TestTools.makeSet( TestEnum.ONE, TestEnum.TWO ), rev2.getMapEntityMap().keySet() );
assertEquals( TestTools.makeSet( 1, 2 ), rev2.getMapEntityMap().values().stream().map( MapEntity::getId ).collect( Collectors.toSet() ) );
final TestEntity rev3 = getAuditReader().find( TestEntity.class, 1, 3 );
assertEquals( 1, rev3.getMapEntityMap().size() );
assertEquals( TestTools.makeSet( TestEnum.TWO ), rev3.getMapEntityMap().keySet() );
assertEquals( TestTools.makeSet( 2 ), rev3.getMapEntityMap().values().stream().map( MapEntity::getId ).collect( Collectors.toSet() ) );
final TestEntity rev4 = getAuditReader().find( TestEntity.class, 1, 4 );
assertEquals( 0, rev4.getMapEntityMap().size() );
}
public enum TestEnum {
ONE,
TWO
}
@Entity(name = "TestEntity")
@Audited
public static class TestEntity {
@Id
private Integer id;
@OneToMany(mappedBy = "testEntity")
@MapKeyEnumerated(EnumType.STRING)
private Map<TestEnum, MapEntity> mapEntityMap = new HashMap<>();
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Map<TestEnum, MapEntity> getMapEntityMap() {
return mapEntityMap;
}
public void setMapEntityMap(Map<TestEnum, MapEntity> mapEntityMap) {
this.mapEntityMap = mapEntityMap;
}
@Override
public String toString() {
return "TestEntity{" +
"id=" + id +
", mapEntityMap=" + mapEntityMap +
'}';
}
public void addMapKeyAssociation(TestEnum key, MapEntity value) {
mapEntityMap.put( key, value );
value.setTestEntity( this );
}
public MapEntity removeMapKeyAssociation(TestEnum key) {
final MapEntity value = mapEntityMap.get( key );
value.setTestEntity( null );
mapEntityMap.remove( key );
return value;
}
}
@Entity(name = "MapEntity")
@Audited
public static class MapEntity {
@Id
private Integer id;
@ManyToOne(optional = false)
private TestEntity testEntity;
private String data;
MapEntity() {
}
MapEntity(String data) {
this.data = data;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public TestEntity getTestEntity() {
return testEntity;
}
public void setTestEntity(TestEntity testEntity) {
this.testEntity = testEntity;
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
@Override
public String toString() {
return "MapEntity{" +
"id=" + id +
", data=" + data +
", testEntity=" + ( testEntity == null ? null : testEntity.getId() ) +
'}';
}
}
}