HHH-4751 - Fix and test

This commit is contained in:
Lukasz Antoniak 2012-08-16 10:34:16 +02:00
parent 7120b85bbc
commit 773d0793f1
8 changed files with 402 additions and 10 deletions

View File

@ -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;
}
}

View File

@ -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

View File

@ -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) {

View File

@ -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) {
}

View File

@ -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;
}
}

View File

@ -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());
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}