HHH-4751 - Fix and test
This commit is contained in:
parent
7120b85bbc
commit
773d0793f1
|
@ -88,4 +88,21 @@ public final class BasicMetadataGenerator {
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
boolean addKeyManyToOne(Element parent, PropertyAuditingData propertyAuditingData, Value value,
|
||||
SimpleMapperBuilder mapper) {
|
||||
Type type = value.getType();
|
||||
|
||||
Element manyToOneElement = parent.addElement("key-many-to-one");
|
||||
manyToOneElement.addAttribute("name", propertyAuditingData.getName());
|
||||
manyToOneElement.addAttribute("class", type.getName());
|
||||
MetadataTools.addColumns(manyToOneElement, value.getColumnIterator());
|
||||
|
||||
// A null mapper means that we only want to add xml mappings
|
||||
if (mapper != null) {
|
||||
mapper.add(propertyAuditingData.getPropertyData());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,6 +41,7 @@ import org.hibernate.envers.entities.mapper.id.SingleIdMapper;
|
|||
import org.hibernate.mapping.Component;
|
||||
import org.hibernate.mapping.PersistentClass;
|
||||
import org.hibernate.mapping.Property;
|
||||
import org.hibernate.type.ManyToOneType;
|
||||
import org.hibernate.type.Type;
|
||||
|
||||
/**
|
||||
|
@ -61,11 +62,17 @@ public final class IdMetadataGenerator {
|
|||
Property property = properties.next();
|
||||
Type propertyType = property.getType();
|
||||
if (!"_identifierMapper".equals(property.getName())) {
|
||||
// Last but one parameter: ids are always insertable
|
||||
boolean added = mainGenerator.getBasicMetadataGenerator().addBasic(parent,
|
||||
getIdPersistentPropertyAuditingData(property),
|
||||
property.getValue(), mapper, true, key);
|
||||
|
||||
boolean added = false;
|
||||
if (propertyType instanceof ManyToOneType) {
|
||||
added = mainGenerator.getBasicMetadataGenerator().addKeyManyToOne(parent,
|
||||
getIdPersistentPropertyAuditingData(property),
|
||||
property.getValue(), mapper);
|
||||
} else {
|
||||
// Last but one parameter: ids are always insertable
|
||||
added = mainGenerator.getBasicMetadataGenerator().addBasic(parent,
|
||||
getIdPersistentPropertyAuditingData(property),
|
||||
property.getValue(), mapper, true, key);
|
||||
}
|
||||
if (!added) {
|
||||
// If the entity is audited, and a non-supported id component is used, throwing an exception.
|
||||
// If the entity is not audited, then we simply don't support this entity, even in
|
||||
|
|
|
@ -22,16 +22,22 @@
|
|||
* Boston, MA 02110-1301 USA
|
||||
*/
|
||||
package org.hibernate.envers.entities;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.hibernate.envers.configuration.AuditConfiguration;
|
||||
import org.hibernate.envers.entities.mapper.id.IdMapper;
|
||||
import org.hibernate.envers.entities.mapper.id.MultipleIdMapper;
|
||||
import org.hibernate.envers.entities.mapper.relation.lazy.ToOneDelegateSessionImplementor;
|
||||
import org.hibernate.envers.exception.AuditException;
|
||||
import org.hibernate.envers.reader.AuditReaderImplementor;
|
||||
import org.hibernate.envers.tools.reflection.ReflectionTools;
|
||||
import org.hibernate.internal.util.ReflectHelper;
|
||||
import org.hibernate.proxy.HibernateProxy;
|
||||
import org.hibernate.proxy.LazyInitializer;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author Adam Warski (adam at warski dot org)
|
||||
|
@ -70,6 +76,11 @@ public class EntityInstantiator {
|
|||
IdMapper idMapper = verCfg.getEntCfg().get(entityName).getIdMapper();
|
||||
Map originalId = (Map) versionsEntity.get(verCfg.getAuditEntCfg().getOriginalIdPropName());
|
||||
|
||||
// Fixes HHH-4751 issue (@IdClass with @ManyToOne relation mapping inside)
|
||||
// Note that identifiers are always audited
|
||||
// Replace identifier proxies if do not point to audit tables
|
||||
replaceNonAuditIdProxies(originalId, revision);
|
||||
|
||||
Object primaryKey = idMapper.mapToIdFromMap(originalId);
|
||||
|
||||
// Checking if the entity is in cache
|
||||
|
@ -106,6 +117,25 @@ public class EntityInstantiator {
|
|||
return ret;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
private void replaceNonAuditIdProxies(Map originalId, Number revision) {
|
||||
for (Object key : originalId.keySet()) {
|
||||
Object value = originalId.get(key);
|
||||
if (value instanceof HibernateProxy) {
|
||||
HibernateProxy hibernateProxy = (HibernateProxy) value;
|
||||
LazyInitializer initializer = hibernateProxy.getHibernateLazyInitializer();
|
||||
final String entityName = initializer.getEntityName();
|
||||
final Serializable entityId = initializer.getIdentifier();
|
||||
if (verCfg.getEntCfg().isVersioned(entityName)) {
|
||||
final String entityClassName = verCfg.getEntCfg().get(entityName).getEntityClassName();
|
||||
final ToOneDelegateSessionImplementor delegate = new ToOneDelegateSessionImplementor(versionsReader, ReflectionTools.loadClass(entityClassName), entityId, revision, verCfg);
|
||||
originalId.put(key,
|
||||
versionsReader.getSessionImplementor().getFactory().getEntityPersister(entityName).createProxy(entityId, delegate));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public void addInstancesFromVersionsEntities(String entityName, Collection addTo, List<Map> versionsEntities, Number revision) {
|
||||
for (Map versionsEntity : versionsEntities) {
|
||||
|
|
|
@ -53,8 +53,6 @@ public abstract class AbstractOneToOneMapper extends AbstractToOneMapper {
|
|||
protected abstract Object queryForReferencedEntity(AuditReaderImplementor versionsReader, EntityInfo referencedEntity,
|
||||
Serializable primaryKey, Number revision);
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void mapModifiedFlagsToMapFromEntity(SessionImplementor session, Map<String, Object> data, Object newObj, Object oldObj) {
|
||||
}
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
package org.hibernate.envers.test.integration.ids.idclass;
|
||||
|
||||
import java.io.Serializable;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Table;
|
||||
|
||||
import org.hibernate.envers.Audited;
|
||||
|
||||
/**
|
||||
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
|
||||
*/
|
||||
@Audited
|
||||
@Entity
|
||||
public class ClassType implements Serializable {
|
||||
@Id
|
||||
@Column(name = "Name")
|
||||
private String type;
|
||||
|
||||
private String description;
|
||||
|
||||
public ClassType() {
|
||||
}
|
||||
|
||||
public ClassType(String type, String description) {
|
||||
this.type = type;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof ClassType)) return false;
|
||||
|
||||
ClassType classType = (ClassType) o;
|
||||
|
||||
if (type != null ? !type.equals(classType.type) : classType.type != null) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ClassType(type = " + type + ", description = " + description + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return type != null ? type.hashCode() : 0;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
package org.hibernate.envers.test.integration.ids.idclass;
|
||||
|
||||
import junit.framework.Assert;
|
||||
import org.hibernate.envers.test.BaseEnversJPAFunctionalTestCase;
|
||||
import org.hibernate.envers.test.Priority;
|
||||
import org.hibernate.testing.TestForIssue;
|
||||
import org.junit.Test;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
|
||||
*/
|
||||
@TestForIssue(jiraKey = "HHH-4751")
|
||||
public class IdClassWithRelationTest extends BaseEnversJPAFunctionalTestCase {
|
||||
private RelationalClassId entityId = null;
|
||||
private String typeId = null;
|
||||
|
||||
@Override
|
||||
protected Class<?>[] getAnnotatedClasses() {
|
||||
return new Class<?>[]{SampleClass.class, RelationalClassId.class, ClassType.class};
|
||||
}
|
||||
|
||||
@Test
|
||||
@Priority(10)
|
||||
public void initData() {
|
||||
EntityManager em = getEntityManager();
|
||||
|
||||
// Revision 1
|
||||
em.getTransaction().begin();
|
||||
ClassType type = new ClassType("type", "initial description");
|
||||
SampleClass entity = new SampleClass();
|
||||
entity.setType(type);
|
||||
entity.setSampleValue("initial data");
|
||||
em.persist(type);
|
||||
em.persist(entity);
|
||||
em.getTransaction().commit();
|
||||
|
||||
typeId = type.getType();
|
||||
entityId = new RelationalClassId(entity.getId(), new ClassType("type", "initial description"));
|
||||
|
||||
// Revision 2
|
||||
em.getTransaction().begin();
|
||||
type = em.find(ClassType.class, type.getType());
|
||||
type.setDescription("modified description");
|
||||
em.merge(type);
|
||||
em.getTransaction().commit();
|
||||
|
||||
// Revision 3
|
||||
em.getTransaction().begin();
|
||||
entity = em.find(SampleClass.class, entityId);
|
||||
entity.setSampleValue("modified data");
|
||||
em.merge(entity);
|
||||
em.getTransaction().commit();
|
||||
|
||||
em.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRevisionsCounts() {
|
||||
Assert.assertEquals(Arrays.asList(1, 2), getAuditReader().getRevisions(ClassType.class, typeId));
|
||||
Assert.assertEquals(Arrays.asList(1, 3), getAuditReader().getRevisions(SampleClass.class, entityId));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHistoryOfEntity() {
|
||||
// given
|
||||
SampleClass entity = new SampleClass(entityId.getId(), entityId.getType(), "initial data");
|
||||
|
||||
// when
|
||||
SampleClass ver1 = getAuditReader().find(SampleClass.class, entityId, 1);
|
||||
|
||||
// then
|
||||
Assert.assertEquals(entity.getId(), ver1.getId());
|
||||
Assert.assertEquals(entity.getSampleValue(), ver1.getSampleValue());
|
||||
Assert.assertEquals(entity.getType().getType(), ver1.getType().getType());
|
||||
Assert.assertEquals(entity.getType().getDescription(), ver1.getType().getDescription());
|
||||
|
||||
// given
|
||||
entity.setSampleValue("modified data");
|
||||
entity.getType().setDescription("modified description");
|
||||
|
||||
// when
|
||||
SampleClass ver2 = getAuditReader().find(SampleClass.class, entityId, 3);
|
||||
|
||||
// then
|
||||
Assert.assertEquals(entity.getId(), ver2.getId());
|
||||
Assert.assertEquals(entity.getSampleValue(), ver2.getSampleValue());
|
||||
Assert.assertEquals(entity.getType().getType(), ver2.getType().getType());
|
||||
Assert.assertEquals(entity.getType().getDescription(), ver2.getType().getDescription());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHistoryOfType() {
|
||||
// given
|
||||
ClassType type = new ClassType(typeId, "initial description");
|
||||
|
||||
// when
|
||||
ClassType ver1 = getAuditReader().find(ClassType.class, typeId, 1);
|
||||
|
||||
// then
|
||||
Assert.assertEquals(type, ver1);
|
||||
Assert.assertEquals(type.getDescription(), ver1.getDescription());
|
||||
|
||||
// given
|
||||
type.setDescription("modified description");
|
||||
|
||||
// when
|
||||
ClassType ver2 = getAuditReader().find(ClassType.class, typeId, 2);
|
||||
|
||||
// then
|
||||
Assert.assertEquals(type, ver2);
|
||||
Assert.assertEquals(type.getDescription(), ver2.getDescription());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
package org.hibernate.envers.test.integration.ids.idclass;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
|
||||
*/
|
||||
public class RelationalClassId implements Serializable {
|
||||
private Long id;
|
||||
private ClassType type;
|
||||
|
||||
public RelationalClassId() {
|
||||
}
|
||||
|
||||
public RelationalClassId(Long id, ClassType type) {
|
||||
this.id = id;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof RelationalClassId)) return false;
|
||||
|
||||
RelationalClassId that = (RelationalClassId) o;
|
||||
|
||||
if (id != null ? !id.equals(that.id) : that.id != null) return false;
|
||||
if (type != null ? !type.equals(that.type) : that.type != null) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = id != null ? id.hashCode() : 0;
|
||||
result = 31 * result + (type != null ? type.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "RelationalClassId(id = " + id + ", type = " + type + ")";
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public ClassType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(ClassType type) {
|
||||
this.type = type;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
package org.hibernate.envers.test.integration.ids.idclass;
|
||||
|
||||
import java.io.Serializable;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.FetchType;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.IdClass;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.ManyToOne;
|
||||
|
||||
import org.hibernate.envers.Audited;
|
||||
|
||||
/**
|
||||
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
|
||||
*/
|
||||
@Audited
|
||||
@Entity
|
||||
@IdClass(RelationalClassId.class)
|
||||
public class SampleClass implements Serializable {
|
||||
@Id
|
||||
@GeneratedValue
|
||||
private Long id;
|
||||
|
||||
@Id
|
||||
@ManyToOne(fetch = FetchType.LAZY, optional = false)
|
||||
@JoinColumn(name = "ClassTypeName", referencedColumnName = "Name",
|
||||
insertable = true, updatable = true, nullable = false)
|
||||
private ClassType type;
|
||||
|
||||
private String sampleValue;
|
||||
|
||||
public SampleClass() {
|
||||
}
|
||||
|
||||
public SampleClass(ClassType type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public SampleClass(Long id, ClassType type) {
|
||||
this.id = id;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public SampleClass(Long id, ClassType type, String sampleValue) {
|
||||
this.id = id;
|
||||
this.type = type;
|
||||
this.sampleValue = sampleValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof SampleClass)) return false;
|
||||
|
||||
SampleClass sampleClass = (SampleClass) o;
|
||||
|
||||
if (id != null ? !id.equals(sampleClass.id) : sampleClass.id != null) return false;
|
||||
if (type != null ? !type.equals(sampleClass.type) : sampleClass.type != null) return false;
|
||||
if (sampleValue != null ? !sampleValue.equals(sampleClass.sampleValue) : sampleClass.sampleValue != null) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = id != null ? id.hashCode() : 0;
|
||||
result = 31 * result + (type != null ? type.hashCode() : 0);
|
||||
result = 31 * result + (sampleValue != null ? sampleValue.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public ClassType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(ClassType type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getSampleValue() {
|
||||
return sampleValue;
|
||||
}
|
||||
|
||||
public void setSampleValue(String sampleValue) {
|
||||
this.sampleValue = sampleValue;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue