HHH-11797 Add support for @MapKeyEnumerated mappings
This commit is contained in:
parent
7355fc5131
commit
4b8c81908c
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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!" );
|
||||
}
|
||||
}
|
|
@ -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() ) +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue