HHH-6825 - Fix and test

This commit is contained in:
Lukasz Antoniak 2012-01-04 21:10:53 +01:00
parent e18d087592
commit 5c80268664
9 changed files with 709 additions and 2 deletions

View File

@ -49,6 +49,7 @@ import org.hibernate.envers.tools.StringTools;
import org.hibernate.envers.tools.Triple;
import org.hibernate.mapping.Collection;
import org.hibernate.mapping.Join;
import org.hibernate.mapping.OneToOne;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Property;
import org.hibernate.mapping.Table;
@ -193,8 +194,15 @@ public final class AuditMetadataGenerator {
} else if (type instanceof OneToOneType) {
// only second pass
if (!firstPass) {
toOneRelationMetadataGenerator.addOneToOneNotOwning(propertyAuditingData, value,
currentMapper, entityName);
OneToOne oneToOne = (OneToOne) value;
if (oneToOne.getReferencedPropertyName() != null) {
toOneRelationMetadataGenerator.addOneToOneNotOwning(propertyAuditingData, value,
currentMapper, entityName);
} else {
// @OneToOne relation marked with @PrimaryKeyJoinColumn
toOneRelationMetadataGenerator.addOneToOnePrimaryKeyJoinColumn(propertyAuditingData, value,
currentMapper, entityName, insertable);
}
}
} else if (type instanceof CollectionType) {
// only second pass

View File

@ -32,6 +32,7 @@ import org.hibernate.envers.entities.PropertyData;
import org.hibernate.envers.entities.mapper.CompositeMapperBuilder;
import org.hibernate.envers.entities.mapper.id.IdMapper;
import org.hibernate.envers.entities.mapper.relation.OneToOneNotOwningMapper;
import org.hibernate.envers.entities.mapper.relation.OneToOnePrimaryKeyJoinColumnMapper;
import org.hibernate.envers.entities.mapper.relation.ToOneIdMapper;
import org.hibernate.envers.tools.MappingTools;
import org.hibernate.mapping.OneToOne;
@ -41,6 +42,7 @@ import org.hibernate.mapping.Value;
/**
* Generates metadata for to-one relations (reference-valued properties).
* @author Adam Warski (adam at warski dot org)
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
public final class ToOneRelationMetadataGenerator {
private final AuditMetadataGenerator mainGenerator;
@ -134,4 +136,26 @@ public final class ToOneRelationMetadataGenerator {
mapper.addComposite(propertyData, new OneToOneNotOwningMapper(owningReferencePropertyName,
referencedEntityName, propertyData));
}
@SuppressWarnings({"unchecked"})
void addOneToOnePrimaryKeyJoinColumn(PropertyAuditingData propertyAuditingData, Value value,
CompositeMapperBuilder mapper, String entityName, boolean insertable) {
String referencedEntityName = ((ToOne) value).getReferencedEntityName();
IdMappingData idMapping = mainGenerator.getReferencedIdMappingData(entityName, referencedEntityName,
propertyAuditingData, true);
String lastPropertyPrefix = MappingTools.createToOneRelationPrefix(propertyAuditingData.getName());
// Generating the id mapper for the relation
IdMapper relMapper = idMapping.getIdMapper().prefixMappedProperties(lastPropertyPrefix);
// Storing information about this relation
mainGenerator.getEntitiesConfigurations().get(entityName).addToOneRelation(propertyAuditingData.getName(),
referencedEntityName, relMapper, insertable);
// Adding mapper for the id
PropertyData propertyData = propertyAuditingData.getPropertyData();
mapper.addComposite(propertyData, new OneToOnePrimaryKeyJoinColumnMapper(entityName, referencedEntityName, propertyData));
}
}

View File

@ -0,0 +1,99 @@
package org.hibernate.envers.entities.mapper.relation;
import org.hibernate.NonUniqueResultException;
import org.hibernate.collection.spi.PersistentCollection;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.envers.Audited;
import org.hibernate.envers.configuration.AuditConfiguration;
import org.hibernate.envers.entities.EntityConfiguration;
import org.hibernate.envers.entities.PropertyData;
import org.hibernate.envers.entities.mapper.PersistentCollectionChangeData;
import org.hibernate.envers.entities.mapper.PropertyMapper;
import org.hibernate.envers.exception.AuditException;
import org.hibernate.envers.query.AuditEntity;
import org.hibernate.envers.reader.AuditReaderImplementor;
import org.hibernate.envers.tools.reflection.ReflectionTools;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.property.Setter;
import javax.persistence.NoResultException;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
/**
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
public class OneToOnePrimaryKeyJoinColumnMapper implements PropertyMapper {
private final String entityName;
private final String referencedEntityName;
private final PropertyData propertyData;
public OneToOnePrimaryKeyJoinColumnMapper(String entityName, String referencedEntityName, PropertyData propertyData) {
this.entityName = entityName;
this.referencedEntityName = referencedEntityName;
this.propertyData = propertyData;
}
public boolean mapToMapFromEntity(SessionImplementor session, Map<String, Object> data, Object newObj, Object oldObj) {
return false;
}
public void mapToEntityFromMap(AuditConfiguration verCfg, Object obj, Map data, Object primaryKey, AuditReaderImplementor versionsReader, Number revision) {
if (obj == null) {
return;
}
EntityConfiguration entCfg = verCfg.getEntCfg().get(referencedEntityName);
boolean isRelationAudited = true;
if (entCfg == null) {
// a relation marked as RelationTargetAuditMode.NOT_AUDITED
entCfg = verCfg.getEntCfg().getNotVersionEntityConfiguration(referencedEntityName);
isRelationAudited = false;
}
Class<?> entityClass = ReflectionTools.loadClass(entCfg.getEntityClassName());
Object value = null;
try {
if (isRelationAudited) {
value = versionsReader.createQuery().forEntitiesAtRevision(entityClass, referencedEntityName, revision)
.add(AuditEntity.id().eq(primaryKey)).getSingleResult();
} else {
value = createNotAuditedEntityReference(versionsReader, entityClass, referencedEntityName,
(Serializable) primaryKey);
}
} catch (NoResultException e) {
value = null;
} catch (NonUniqueResultException e) {
throw new AuditException("Many versions results for one-to-one relationship: (" + entityName +
", " + propertyData.getBeanName() + ")");
}
Setter setter = ReflectionTools.getSetter(obj.getClass(), propertyData);
setter.set(obj, value, null);
}
public List<PersistentCollectionChangeData> mapCollectionChanges(String referencingPropertyName,
PersistentCollection newColl,
Serializable oldColl,
Serializable id) {
return null;
}
/**
* Create Hibernate proxy or retrieve the complete object of referenced, not audited entity. According to
* {@link Audited#targetAuditMode()}} documentation, reference shall point to current (non-historical) version
* of an entity.
*/
private Object createNotAuditedEntityReference(AuditReaderImplementor versionsReader, Class<?> entityClass,
String entityName, Serializable primaryKey) {
EntityPersister entityPersister = versionsReader.getSessionImplementor().getFactory().getEntityPersister(entityName);
if (entityPersister.hasProxy()) {
// If possible create a proxy. Returning complete object may affect performance.
return versionsReader.getSession().load(entityClass, primaryKey);
} else {
// If proxy is not allowed (e.g. @Proxy(lazy=false)) construct the original object.
return versionsReader.getSession().get(entityClass, primaryKey);
}
}
}

View File

@ -0,0 +1,86 @@
package org.hibernate.envers.test.integration.onetoone.bidirectional.primarykeyjoincolumn;
import org.hibernate.annotations.Proxy;
import org.hibernate.envers.Audited;
import org.hibernate.envers.RelationTargetAuditMode;
import javax.persistence.*;
import java.io.Serializable;
/**
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
@Entity
@Audited
public class Account implements Serializable {
@Id
@GeneratedValue
private Long accountId;
private String type;
@OneToOne(optional = false)
@PrimaryKeyJoinColumn
private Person owner;
public Account() {
}
public Account(String type) {
this.type = type;
}
public Account(Long accountId, String type) {
this.accountId = accountId;
this.type = type;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Account)) return false;
Account account = (Account) o;
if (accountId != null ? !accountId.equals(account.accountId) : account.accountId != null) return false;
if (type != null ? !type.equals(account.type) : account.type != null) return false;
return true;
}
@Override
public int hashCode() {
int result = accountId != null ? accountId.hashCode() : 0;
result = 31 * result + (type != null ? type.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "Account(accountId = " + accountId + ", type = " + type + ")";
}
public Long getAccountId() {
return accountId;
}
public void setAccountId(Long accountId) {
this.accountId = accountId;
}
public Person getOwner() {
return owner;
}
public void setOwner(Person owner) {
this.owner = owner;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}

View File

@ -0,0 +1,97 @@
package org.hibernate.envers.test.integration.onetoone.bidirectional.primarykeyjoincolumn;
import org.hibernate.envers.Audited;
import org.hibernate.envers.RelationTargetAuditMode;
import javax.persistence.*;
import java.io.Serializable;
/**
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
@Entity
@Audited
public class AccountNotAuditedOwners implements Serializable {
@Id
@GeneratedValue
private Long accountId;
private String type;
@OneToOne(mappedBy = "account", optional = false)
@Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED)
private NotAuditedNoProxyPerson owner;
@OneToOne(mappedBy = "account", optional = false, fetch = FetchType.LAZY)
@Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED)
private NotAuditedProxyPerson coOwner;
public AccountNotAuditedOwners() {
}
public AccountNotAuditedOwners(String type) {
this.type = type;
}
public AccountNotAuditedOwners(Long accountId, String type) {
this.accountId = accountId;
this.type = type;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof AccountNotAuditedOwners)) return false;
AccountNotAuditedOwners account = (AccountNotAuditedOwners) o;
if (accountId != null ? !accountId.equals(account.accountId) : account.accountId != null) return false;
if (type != null ? !type.equals(account.type) : account.type != null) return false;
return true;
}
@Override
public int hashCode() {
int result = accountId != null ? accountId.hashCode() : 0;
result = 31 * result + (type != null ? type.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "AccountNotAuditedOwners(accountId = " + accountId + ", type = " + type + ")";
}
public Long getAccountId() {
return accountId;
}
public void setAccountId(Long accountId) {
this.accountId = accountId;
}
public NotAuditedNoProxyPerson getOwner() {
return owner;
}
public void setOwner(NotAuditedNoProxyPerson owner) {
this.owner = owner;
}
public NotAuditedProxyPerson getCoOwner() {
return coOwner;
}
public void setCoOwner(NotAuditedProxyPerson coOwner) {
this.coOwner = coOwner;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}

View File

@ -0,0 +1,84 @@
package org.hibernate.envers.test.integration.onetoone.bidirectional.primarykeyjoincolumn;
import org.hibernate.annotations.Proxy;
import javax.persistence.*;
import java.io.Serializable;
/**
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
@Entity
@Proxy(lazy = false)
public class NotAuditedNoProxyPerson implements Serializable {
@Id
@GeneratedValue
private Long personId;
private String name;
@OneToOne(optional = false)
@PrimaryKeyJoinColumn
private AccountNotAuditedOwners account;
public NotAuditedNoProxyPerson() {
}
public NotAuditedNoProxyPerson(String name) {
this.name = name;
}
public NotAuditedNoProxyPerson(Long personId, String name) {
this.personId = personId;
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof NotAuditedNoProxyPerson)) return false;
NotAuditedNoProxyPerson person = (NotAuditedNoProxyPerson) o;
if (personId != null ? !personId.equals(person.personId) : person.personId != null) return false;
if (name != null ? !name.equals(person.name) : person.name != null) return false;
return true;
}
@Override
public int hashCode() {
int result = personId != null ? personId.hashCode() : 0;
result = 31 * result + (name != null ? name.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "NotAuditedNoProxyPerson(personId = " + personId + ", name = " + name + ")";
}
public Long getPersonId() {
return personId;
}
public void setPersonId(Long personId) {
this.personId = personId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public AccountNotAuditedOwners getAccount() {
return account;
}
public void setAccount(AccountNotAuditedOwners account) {
this.account = account;
}
}

View File

@ -0,0 +1,84 @@
package org.hibernate.envers.test.integration.onetoone.bidirectional.primarykeyjoincolumn;
import org.hibernate.annotations.Proxy;
import javax.persistence.*;
import java.io.Serializable;
/**
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
@Entity
@Proxy(lazy = true)
public class NotAuditedProxyPerson implements Serializable {
@Id
@GeneratedValue
private Long personId;
private String name;
@OneToOne(optional = false, fetch = FetchType.LAZY)
@PrimaryKeyJoinColumn
private AccountNotAuditedOwners account;
public NotAuditedProxyPerson() {
}
public NotAuditedProxyPerson(String name) {
this.name = name;
}
public NotAuditedProxyPerson(Long personId, String name) {
this.personId = personId;
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof NotAuditedProxyPerson)) return false;
NotAuditedProxyPerson person = (NotAuditedProxyPerson) o;
if (personId != null ? !personId.equals(person.personId) : person.personId != null) return false;
if (name != null ? !name.equals(person.name) : person.name != null) return false;
return true;
}
@Override
public int hashCode() {
int result = personId != null ? personId.hashCode() : 0;
result = 31 * result + (name != null ? name.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "NotAuditedProxyPerson(personId = " + personId + ", name = " + name + ")";
}
public Long getPersonId() {
return personId;
}
public void setPersonId(Long personId) {
this.personId = personId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public AccountNotAuditedOwners getAccount() {
return account;
}
public void setAccount(AccountNotAuditedOwners account) {
this.account = account;
}
}

View File

@ -0,0 +1,138 @@
package org.hibernate.envers.test.integration.onetoone.bidirectional.primarykeyjoincolumn;
import org.hibernate.ejb.Ejb3Configuration;
import org.hibernate.envers.RevisionType;
import org.hibernate.envers.query.AuditEntity;
import org.hibernate.envers.test.AbstractEntityTest;
import org.hibernate.envers.test.Priority;
import org.hibernate.proxy.HibernateProxy;
import org.hibernate.testing.TestForIssue;
import org.junit.Assert;
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-6825")
public class OneToOneWithPrimaryKeyJoinTest extends AbstractEntityTest {
private Long personId = null;
private Long accountId = null;
private Long proxyPersonId = null;
private Long noProxyPersonId = null;
private Long accountNotAuditedOwnersId = null;
public void configure(Ejb3Configuration cfg) {
cfg.addAnnotatedClass(Person.class);
cfg.addAnnotatedClass(Account.class);
cfg.addAnnotatedClass(AccountNotAuditedOwners.class);
cfg.addAnnotatedClass(NotAuditedNoProxyPerson.class);
cfg.addAnnotatedClass(NotAuditedProxyPerson.class);
}
@Test
@Priority(10)
public void initData() {
EntityManager em = getEntityManager();
// Revision 1
em.getTransaction().begin();
Person person = new Person("Robert");
Account account = new Account("Saving");
person.setAccount(account);
account.setOwner(person);
em.persist(person);
em.persist(account);
em.getTransaction().commit();
// Revision 2
em.getTransaction().begin();
NotAuditedNoProxyPerson noProxyPerson = new NotAuditedNoProxyPerson("Kinga");
NotAuditedProxyPerson proxyPerson = new NotAuditedProxyPerson("Lukasz");
AccountNotAuditedOwners accountNotAuditedOwners = new AccountNotAuditedOwners("Standard");
noProxyPerson.setAccount(accountNotAuditedOwners);
proxyPerson.setAccount(accountNotAuditedOwners);
accountNotAuditedOwners.setOwner(noProxyPerson);
accountNotAuditedOwners.setCoOwner(proxyPerson);
em.persist(accountNotAuditedOwners);
em.persist(noProxyPerson);
em.persist(proxyPerson);
em.getTransaction().commit();
personId = person.getPersonId();
accountId = account.getAccountId();
accountNotAuditedOwnersId = accountNotAuditedOwners.getAccountId();
proxyPersonId = proxyPerson.getPersonId();
noProxyPersonId = noProxyPerson.getPersonId();
}
@Test
public void testRevisionsCounts() {
Assert.assertEquals(Arrays.asList(1), getAuditReader().getRevisions(Person.class, personId));
Assert.assertEquals(Arrays.asList(1), getAuditReader().getRevisions(Account.class, accountId));
Assert.assertEquals(Arrays.asList(2), getAuditReader().getRevisions(AccountNotAuditedOwners.class, accountNotAuditedOwnersId));
}
@Test
public void testHistoryOfPerson() {
Person personVer1 = new Person(personId, "Robert");
Account accountVer1 = new Account(accountId, "Saving");
personVer1.setAccount(accountVer1);
accountVer1.setOwner(personVer1);
Object[] result = ((Object[]) getAuditReader().createQuery().forRevisionsOfEntity(Person.class, false, true)
.add(AuditEntity.id().eq(personId))
.getResultList().get(0));
Assert.assertEquals(personVer1, result[0]);
Assert.assertEquals(personVer1.getAccount(), ((Person)result[0]).getAccount());
Assert.assertEquals(RevisionType.ADD, result[2]);
Assert.assertEquals(personVer1, getAuditReader().find(Person.class, personId, 1));
}
@Test
public void testHistoryOfAccount() {
Person personVer1 = new Person(personId, "Robert");
Account accountVer1 = new Account(accountId, "Saving");
personVer1.setAccount(accountVer1);
accountVer1.setOwner(personVer1);
Object[] result = ((Object[]) getAuditReader().createQuery().forRevisionsOfEntity(Account.class, false, true)
.add(AuditEntity.id().eq(accountId))
.getResultList().get(0));
Assert.assertEquals(accountVer1, result[0]);
Assert.assertEquals(accountVer1.getOwner(), ((Account)result[0]).getOwner());
Assert.assertEquals(RevisionType.ADD, result[2]);
Assert.assertEquals(accountVer1, getAuditReader().find(Account.class, accountId, 1));
}
@Test
public void testHistoryOfAccountNotAuditedOwners() {
NotAuditedNoProxyPerson noProxyPersonVer1 = new NotAuditedNoProxyPerson(noProxyPersonId, "Kinga");
NotAuditedProxyPerson proxyPersonVer1 = new NotAuditedProxyPerson(proxyPersonId, "Lukasz");
AccountNotAuditedOwners accountNotAuditedOwnersVer1 = new AccountNotAuditedOwners(accountNotAuditedOwnersId, "Standard");
noProxyPersonVer1.setAccount(accountNotAuditedOwnersVer1);
proxyPersonVer1.setAccount(accountNotAuditedOwnersVer1);
accountNotAuditedOwnersVer1.setOwner(noProxyPersonVer1);
accountNotAuditedOwnersVer1.setCoOwner(proxyPersonVer1);
Object[] result = ((Object[]) getAuditReader().createQuery().forRevisionsOfEntity(AccountNotAuditedOwners.class, false, true)
.add(AuditEntity.id().eq(accountNotAuditedOwnersId))
.getResultList().get(0));
Assert.assertEquals(accountNotAuditedOwnersVer1, result[0]);
Assert.assertEquals(RevisionType.ADD, result[2]);
// Checking non-proxy reference
Assert.assertEquals(accountNotAuditedOwnersVer1.getOwner(), ((AccountNotAuditedOwners)result[0]).getOwner());
// Checking proxy reference
Assert.assertTrue(((AccountNotAuditedOwners)result[0]).getCoOwner() instanceof HibernateProxy);
Assert.assertEquals(proxyPersonVer1.getPersonId(), ((AccountNotAuditedOwners)result[0]).getCoOwner().getPersonId());
Assert.assertEquals(accountNotAuditedOwnersVer1, getAuditReader().find(AccountNotAuditedOwners.class, accountNotAuditedOwnersId, 2));
}
}

View File

@ -0,0 +1,87 @@
package org.hibernate.envers.test.integration.onetoone.bidirectional.primarykeyjoincolumn;
import org.hibernate.annotations.Proxy;
import org.hibernate.envers.Audited;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToOne;
import java.io.Serializable;
/**
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
@Entity
@Audited
public class Person implements Serializable {
@Id
@GeneratedValue
private Long personId;
private String name;
@OneToOne(mappedBy = "owner")
private Account account;
public Person() {
}
public Person(String name) {
this.name = name;
}
public Person(Long personId, String name) {
this.personId = personId;
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Person)) return false;
Person person = (Person) o;
if (personId != null ? !personId.equals(person.personId) : person.personId != null) return false;
if (name != null ? !name.equals(person.name) : person.name != null) return false;
return true;
}
@Override
public int hashCode() {
int result = personId != null ? personId.hashCode() : 0;
result = 31 * result + (name != null ? name.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "Person(personId = " + personId + ", name = " + name + ")";
}
public Long getPersonId() {
return personId;
}
public void setPersonId(Long personId) {
this.personId = personId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Account getAccount() {
return account;
}
public void setAccount(Account account) {
this.account = account;
}
}