diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/metadata/AuditMetadataGenerator.java b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/metadata/AuditMetadataGenerator.java index 656f7e59ac13..4c8027d3bf17 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/metadata/AuditMetadataGenerator.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/metadata/AuditMetadataGenerator.java @@ -49,6 +49,7 @@ 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 @@ void addValue(Element parent, Value value, CompositeMapperBuilder currentMapper, } 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 diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/metadata/ToOneRelationMetadataGenerator.java b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/metadata/ToOneRelationMetadataGenerator.java index d173dc179bbd..9a8501335e9f 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/metadata/ToOneRelationMetadataGenerator.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/metadata/ToOneRelationMetadataGenerator.java @@ -32,6 +32,7 @@ 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 @@ /** * 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; @@ -131,7 +133,29 @@ void addOneToOneNotOwning(PropertyAuditingData propertyAuditingData, Value value // Adding mapper for the id PropertyData propertyData = propertyAuditingData.getPropertyData(); - mapper.addComposite(propertyData, new OneToOneNotOwningMapper(owningReferencePropertyName, - referencedEntityName, propertyData)); + mapper.addComposite(propertyData, new OneToOneNotOwningMapper(entityName, referencedEntityName, + owningReferencePropertyName, 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)); } } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/AbstractOneToOneMapper.java b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/AbstractOneToOneMapper.java new file mode 100644 index 000000000000..100ee79316c1 --- /dev/null +++ b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/AbstractOneToOneMapper.java @@ -0,0 +1,54 @@ +package org.hibernate.envers.entities.mapper.relation; + +import org.hibernate.NonUniqueResultException; +import org.hibernate.envers.configuration.AuditConfiguration; +import org.hibernate.envers.entities.PropertyData; +import org.hibernate.envers.exception.AuditException; +import org.hibernate.envers.reader.AuditReaderImplementor; + +import javax.persistence.NoResultException; +import java.io.Serializable; +import java.util.Map; + +/** + * Template class for property mappers that manage one-to-one relation. + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + */ +public abstract class AbstractOneToOneMapper extends AbstractToOneMapper { + private final String entityName; + private final String referencedEntityName; + + protected AbstractOneToOneMapper(String entityName, String referencedEntityName, PropertyData propertyData) { + super(propertyData); + this.entityName = entityName; + this.referencedEntityName = referencedEntityName; + } + + @Override + public void nullSafeMapToEntityFromMap(AuditConfiguration verCfg, Object obj, Map data, Object primaryKey, + AuditReaderImplementor versionsReader, Number revision) { + EntityInfo referencedEntity = getEntityInfo(verCfg, referencedEntityName); + + Object value = null; + try { + value = queryForReferencedEntity(versionsReader, referencedEntity, (Serializable) primaryKey, revision); + } catch (NoResultException e) { + value = null; + } catch (NonUniqueResultException e) { + throw new AuditException("Many versions results for one-to-one relationship " + entityName + + "." + getPropertyData().getBeanName() + ".", e); + } + + setPropertyValue(obj, value); + } + + /** + * @param versionsReader Audit reader. + * @param referencedEntity Referenced entity descriptor. + * @param primaryKey Referenced entity identifier. + * @param revision Revision number. + * @return Referenced object or proxy of one-to-one relation. + */ + protected abstract Object queryForReferencedEntity(AuditReaderImplementor versionsReader, EntityInfo referencedEntity, + Serializable primaryKey, Number revision); +} diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/AbstractToOneMapper.java b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/AbstractToOneMapper.java new file mode 100644 index 000000000000..cfe049ec9ef0 --- /dev/null +++ b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/AbstractToOneMapper.java @@ -0,0 +1,103 @@ +package org.hibernate.envers.entities.mapper.relation; + +import org.hibernate.collection.spi.PersistentCollection; +import org.hibernate.engine.spi.SessionImplementor; +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.reader.AuditReaderImplementor; +import org.hibernate.envers.tools.reflection.ReflectionTools; +import org.hibernate.property.Setter; + +import java.io.Serializable; +import java.util.List; +import java.util.Map; + +/** + * Base class for property mappers that manage to-one relation. + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + */ +public abstract class AbstractToOneMapper implements PropertyMapper { + private final PropertyData propertyData; + + protected AbstractToOneMapper(PropertyData propertyData) { + this.propertyData = propertyData; + } + + @Override + public boolean mapToMapFromEntity(SessionImplementor session, Map data, Object newObj, Object oldObj) { + return false; + } + + @Override + public void mapToEntityFromMap(AuditConfiguration verCfg, Object obj, Map data, Object primaryKey, + AuditReaderImplementor versionsReader, Number revision) { + if (obj != null) { + nullSafeMapToEntityFromMap(verCfg, obj, data, primaryKey, versionsReader, revision); + } + } + + @Override + public List mapCollectionChanges(String referencingPropertyName, + PersistentCollection newColl, + Serializable oldColl, Serializable id) { + return null; + } + + /** + * @param verCfg Audit configuration. + * @param entityName Entity name. + * @return Entity class, name and information whether it is audited or not. + */ + protected EntityInfo getEntityInfo(AuditConfiguration verCfg, String entityName) { + EntityConfiguration entCfg = verCfg.getEntCfg().get(entityName); + boolean isRelationAudited = true; + if (entCfg == null) { + // a relation marked as RelationTargetAuditMode.NOT_AUDITED + entCfg = verCfg.getEntCfg().getNotVersionEntityConfiguration(entityName); + isRelationAudited = false; + } + Class entityClass = ReflectionTools.loadClass(entCfg.getEntityClassName()); + return new EntityInfo(entityClass, entityName, isRelationAudited); + } + + protected void setPropertyValue(Object targetObject, Object value) { + Setter setter = ReflectionTools.getSetter(targetObject.getClass(), propertyData); + setter.set(targetObject, value, null); + } + + /** + * @return Bean property that represents the relation. + */ + protected PropertyData getPropertyData() { + return propertyData; + } + + /** + * Parameter {@code obj} is never {@code null}. + * @see PropertyMapper#mapToEntityFromMap(AuditConfiguration, Object, Map, Object, AuditReaderImplementor, Number) + */ + public abstract void nullSafeMapToEntityFromMap(AuditConfiguration verCfg, Object obj, Map data, Object primaryKey, + AuditReaderImplementor versionsReader, Number revision); + + /** + * Simple descriptor of an entity. + */ + protected class EntityInfo { + private final Class entityClass; + private final String entityName; + private final boolean audited; + + public EntityInfo(Class entityClass, String entityName, boolean audited) { + this.entityClass = entityClass; + this.entityName = entityName; + this.audited = audited; + } + + public Class getEntityClass() { return entityClass; } + public String getEntityName() { return entityName; } + public boolean isAudited() { return audited; } + } +} diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/OneToOneNotOwningMapper.java b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/OneToOneNotOwningMapper.java index c12e1071bbf4..978d6138a541 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/OneToOneNotOwningMapper.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/OneToOneNotOwningMapper.java @@ -22,78 +22,35 @@ * Boston, MA 02110-1301 USA */ package org.hibernate.envers.entities.mapper.relation; -import java.io.Serializable; -import java.util.List; -import java.util.Map; -import javax.persistence.NoResultException; -import org.hibernate.NonUniqueResultException; -import org.hibernate.collection.spi.PersistentCollection; -import org.hibernate.engine.spi.SessionImplementor; -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.property.Setter; + +import javax.persistence.OneToOne; +import java.io.Serializable; /** + * Property mapper for not owning side of {@link OneToOne} relation. * @author Adam Warski (adam at warski dot org) * @author Hern�n Chanfreau + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) */ -public class OneToOneNotOwningMapper implements PropertyMapper { - private String owningReferencePropertyName; - private String owningEntityName; - private PropertyData propertyData; +public class OneToOneNotOwningMapper extends AbstractOneToOneMapper { + private final String owningReferencePropertyName; - public OneToOneNotOwningMapper(String owningReferencePropertyName, String owningEntityName, + public OneToOneNotOwningMapper(String notOwningEntityName, String owningEntityName, String owningReferencePropertyName, PropertyData propertyData) { + super(notOwningEntityName, owningEntityName, propertyData); this.owningReferencePropertyName = owningReferencePropertyName; - this.owningEntityName = owningEntityName; - this.propertyData = propertyData; - } - - public boolean mapToMapFromEntity(SessionImplementor session, Map 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(owningEntityName); - if(entCfg == null) { - // a relation marked as RelationTargetAuditMode.NOT_AUDITED - entCfg = verCfg.getEntCfg().getNotVersionEntityConfiguration(owningEntityName); - } - - Class entityClass = ReflectionTools.loadClass(entCfg.getEntityClassName()); - - Object value; - - try { - value = versionsReader.createQuery().forEntitiesAtRevision(entityClass, owningEntityName, revision) - .add(AuditEntity.relatedId(owningReferencePropertyName).eq(primaryKey)).getSingleResult(); - } catch (NoResultException e) { - value = null; - } catch (NonUniqueResultException e) { - throw new AuditException("Many versions results for one-to-one relationship: (" + owningEntityName + - ", " + owningReferencePropertyName + ")"); - } - - Setter setter = ReflectionTools.getSetter(obj.getClass(), propertyData); - setter.set(obj, value, null); } - public List mapCollectionChanges(String referencingPropertyName, - PersistentCollection newColl, - Serializable oldColl, - Serializable id) { - return null; + @Override + protected Object queryForReferencedEntity(AuditReaderImplementor versionsReader, EntityInfo referencedEntity, + Serializable primaryKey, Number revision) { + return versionsReader.createQuery().forEntitiesAtRevision(referencedEntity.getEntityClass(), + referencedEntity.getEntityName(), revision) + .add(AuditEntity.relatedId(owningReferencePropertyName).eq(primaryKey)) + .getSingleResult(); } } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/OneToOnePrimaryKeyJoinColumnMapper.java b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/OneToOnePrimaryKeyJoinColumnMapper.java new file mode 100644 index 000000000000..fbc5794c272a --- /dev/null +++ b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/OneToOnePrimaryKeyJoinColumnMapper.java @@ -0,0 +1,54 @@ +package org.hibernate.envers.entities.mapper.relation; + +import org.hibernate.envers.Audited; +import org.hibernate.envers.entities.PropertyData; +import org.hibernate.envers.query.AuditEntity; +import org.hibernate.envers.reader.AuditReaderImplementor; +import org.hibernate.persister.entity.EntityPersister; + +import javax.persistence.OneToOne; +import javax.persistence.PrimaryKeyJoinColumn; +import java.io.Serializable; + +/** + * Property mapper for {@link OneToOne} with {@link PrimaryKeyJoinColumn} relation. + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + */ +public class OneToOnePrimaryKeyJoinColumnMapper extends AbstractOneToOneMapper { + public OneToOnePrimaryKeyJoinColumnMapper(String entityName, String referencedEntityName, PropertyData propertyData) { + super(entityName, referencedEntityName, propertyData); + } + + @Override + protected Object queryForReferencedEntity(AuditReaderImplementor versionsReader, EntityInfo referencedEntity, + Serializable primaryKey, Number revision) { + if (referencedEntity.isAudited()) { + // Audited relation. + return versionsReader.createQuery().forEntitiesAtRevision(referencedEntity.getEntityClass(), + referencedEntity.getEntityName(), revision) + .add(AuditEntity.id().eq(primaryKey)) + .getSingleResult(); + } else { + // Not audited relation. + return createNotAuditedEntityReference(versionsReader, referencedEntity.getEntityClass(), + referencedEntity.getEntityName(), primaryKey); + } + } + + /** + * 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); + } + } +} \ No newline at end of file diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/ToOneIdMapper.java b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/ToOneIdMapper.java index a3565fa08550..1abf03dc6f63 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/ToOneIdMapper.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/relation/ToOneIdMapper.java @@ -25,36 +25,28 @@ import java.io.Serializable; import java.util.HashMap; -import java.util.List; import java.util.Map; -import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.engine.spi.SessionImplementor; 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.entities.mapper.id.IdMapper; import org.hibernate.envers.entities.mapper.relation.lazy.ToOneDelegateSessionImplementor; import org.hibernate.envers.reader.AuditReaderImplementor; import org.hibernate.envers.tools.Tools; -import org.hibernate.envers.tools.reflection.ReflectionTools; -import org.hibernate.property.Setter; /** * @author Adam Warski (adam at warski dot org) * @author Hern�n Chanfreau */ -public class ToOneIdMapper implements PropertyMapper { +public class ToOneIdMapper extends AbstractToOneMapper { private final IdMapper delegate; - private final PropertyData propertyData; private final String referencedEntityName; private final boolean nonInsertableFake; public ToOneIdMapper(IdMapper delegate, PropertyData propertyData, String referencedEntityName, boolean nonInsertableFake) { + super(propertyData); this.delegate = delegate; - this.propertyData = propertyData; this.referencedEntityName = referencedEntityName; this.nonInsertableFake = nonInsertableFake; } @@ -75,42 +67,22 @@ public boolean mapToMapFromEntity(SessionImplementor session, Map entityClass = ReflectionTools.loadClass(entCfg.getEntityClassName()); + EntityInfo referencedEntity = getEntityInfo(verCfg, referencedEntityName); value = versionsReader.getSessionImplementor().getFactory().getEntityPersister(referencedEntityName). - createProxy((Serializable)entityId, new ToOneDelegateSessionImplementor(versionsReader, entityClass, entityId, revision, verCfg)); + createProxy((Serializable)entityId, new ToOneDelegateSessionImplementor(versionsReader, referencedEntity.getEntityClass(), + entityId, revision, verCfg)); } } - Setter setter = ReflectionTools.getSetter(obj.getClass(), propertyData); - setter.set(obj, value, null); - } - - public List mapCollectionChanges(String referencingPropertyName, - PersistentCollection newColl, - Serializable oldColl, - Serializable id) { - return null; + setPropertyValue(obj, value); } } diff --git a/hibernate-envers/src/matrix/java/org/hibernate/envers/test/integration/onetoone/bidirectional/primarykeyjoincolumn/Account.java b/hibernate-envers/src/matrix/java/org/hibernate/envers/test/integration/onetoone/bidirectional/primarykeyjoincolumn/Account.java new file mode 100644 index 000000000000..2dd777e5d1fe --- /dev/null +++ b/hibernate-envers/src/matrix/java/org/hibernate/envers/test/integration/onetoone/bidirectional/primarykeyjoincolumn/Account.java @@ -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; + } +} diff --git a/hibernate-envers/src/matrix/java/org/hibernate/envers/test/integration/onetoone/bidirectional/primarykeyjoincolumn/AccountNotAuditedOwners.java b/hibernate-envers/src/matrix/java/org/hibernate/envers/test/integration/onetoone/bidirectional/primarykeyjoincolumn/AccountNotAuditedOwners.java new file mode 100644 index 000000000000..46620871e950 --- /dev/null +++ b/hibernate-envers/src/matrix/java/org/hibernate/envers/test/integration/onetoone/bidirectional/primarykeyjoincolumn/AccountNotAuditedOwners.java @@ -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; + } +} \ No newline at end of file diff --git a/hibernate-envers/src/matrix/java/org/hibernate/envers/test/integration/onetoone/bidirectional/primarykeyjoincolumn/NotAuditedNoProxyPerson.java b/hibernate-envers/src/matrix/java/org/hibernate/envers/test/integration/onetoone/bidirectional/primarykeyjoincolumn/NotAuditedNoProxyPerson.java new file mode 100644 index 000000000000..bf8e29941481 --- /dev/null +++ b/hibernate-envers/src/matrix/java/org/hibernate/envers/test/integration/onetoone/bidirectional/primarykeyjoincolumn/NotAuditedNoProxyPerson.java @@ -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; + } +} diff --git a/hibernate-envers/src/matrix/java/org/hibernate/envers/test/integration/onetoone/bidirectional/primarykeyjoincolumn/NotAuditedProxyPerson.java b/hibernate-envers/src/matrix/java/org/hibernate/envers/test/integration/onetoone/bidirectional/primarykeyjoincolumn/NotAuditedProxyPerson.java new file mode 100644 index 000000000000..0c824e21ef55 --- /dev/null +++ b/hibernate-envers/src/matrix/java/org/hibernate/envers/test/integration/onetoone/bidirectional/primarykeyjoincolumn/NotAuditedProxyPerson.java @@ -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; + } +} diff --git a/hibernate-envers/src/matrix/java/org/hibernate/envers/test/integration/onetoone/bidirectional/primarykeyjoincolumn/OneToOneWithPrimaryKeyJoinTest.java b/hibernate-envers/src/matrix/java/org/hibernate/envers/test/integration/onetoone/bidirectional/primarykeyjoincolumn/OneToOneWithPrimaryKeyJoinTest.java new file mode 100644 index 000000000000..507b30016848 --- /dev/null +++ b/hibernate-envers/src/matrix/java/org/hibernate/envers/test/integration/onetoone/bidirectional/primarykeyjoincolumn/OneToOneWithPrimaryKeyJoinTest.java @@ -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)); + } +} diff --git a/hibernate-envers/src/matrix/java/org/hibernate/envers/test/integration/onetoone/bidirectional/primarykeyjoincolumn/Person.java b/hibernate-envers/src/matrix/java/org/hibernate/envers/test/integration/onetoone/bidirectional/primarykeyjoincolumn/Person.java new file mode 100644 index 000000000000..d642180bf835 --- /dev/null +++ b/hibernate-envers/src/matrix/java/org/hibernate/envers/test/integration/onetoone/bidirectional/primarykeyjoincolumn/Person.java @@ -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; + } +}