diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/metadata/CollectionMetadataGenerator.java b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/metadata/CollectionMetadataGenerator.java index 3d9f7b156b..f90fa5c96a 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/metadata/CollectionMetadataGenerator.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/metadata/CollectionMetadataGenerator.java @@ -153,7 +153,7 @@ public final class CollectionMetadataGenerator { boolean oneToManyAttachedType = type instanceof BagType || type instanceof SetType || type instanceof MapType || type instanceof ListType; boolean inverseOneToMany = (value instanceof OneToMany) && (propertyValue.isInverse()); - boolean owningManyToOneWithJoinTableBidirectional = (value instanceof ManyToOne) && (propertyAuditingData.getAuditMappedBy() != null); + boolean owningManyToOneWithJoinTableBidirectional = (value instanceof ManyToOne) && (propertyAuditingData.getRelationMappedBy() != null); boolean fakeOneToManyBidirectional = (value instanceof OneToMany) && (propertyAuditingData.getAuditMappedBy() != null); if (oneToManyAttachedType && (inverseOneToMany || fakeOneToManyBidirectional || owningManyToOneWithJoinTableBidirectional)) { diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/metadata/reader/AuditedPropertiesReader.java b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/metadata/reader/AuditedPropertiesReader.java index e280663f6f..b9067cec8e 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/metadata/reader/AuditedPropertiesReader.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/metadata/reader/AuditedPropertiesReader.java @@ -410,8 +410,9 @@ public class AuditedPropertiesReader { if (!processPropertyAuditingOverrides(property, propertyData)) { return false; // not audited due to AuditOverride annotation } - addPropertyMapKey(property, propertyData); + addPropertyMapKey(property, propertyData); setPropertyAuditMappedBy(property, propertyData); + setPropertyRelationMappedBy(property, propertyData); return true; } @@ -441,11 +442,14 @@ public class AuditedPropertiesReader { globalCfg.isGlobalWithModifiedFlag() : aud.withModifiedFlag(); } - private void setPropertyAuditMappedBy(XProperty property, PropertyAuditingData propertyData) { + private void setPropertyRelationMappedBy(XProperty property, PropertyAuditingData propertyData) { OneToMany oneToMany = property.getAnnotation(OneToMany.class); if (oneToMany != null && !"".equals(oneToMany.mappedBy())) { - propertyData.setAuditMappedBy(oneToMany.mappedBy()); + propertyData.setRelationMappedBy(oneToMany.mappedBy()); } + } + + private void setPropertyAuditMappedBy(XProperty property, PropertyAuditingData propertyData) { AuditMappedBy auditMappedBy = property.getAnnotation(AuditMappedBy.class); if (auditMappedBy != null) { propertyData.setAuditMappedBy(auditMappedBy.mappedBy()); diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/metadata/reader/PropertyAuditingData.java b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/metadata/reader/PropertyAuditingData.java index 04408be7ed..68ccce4243 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/metadata/reader/PropertyAuditingData.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/metadata/reader/PropertyAuditingData.java @@ -47,6 +47,7 @@ public class PropertyAuditingData { private final List auditJoinTableOverrides = new ArrayList(0); private RelationTargetAuditMode relationTargetAuditMode; private String auditMappedBy; + private String relationMappedBy; private String positionMappedBy; private boolean forceInsertable; private boolean usingModifiedFlag; @@ -134,6 +135,14 @@ public class PropertyAuditingData { this.auditMappedBy = auditMappedBy; } + public String getRelationMappedBy() { + return relationMappedBy; + } + + public void setRelationMappedBy(String relationMappedBy) { + this.relationMappedBy = relationMappedBy; + } + public String getPositionMappedBy() { return positionMappedBy; } diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/onetomany/embeddedid/Constant.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/onetomany/embeddedid/Constant.java new file mode 100644 index 0000000000..14ed0b0665 --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/onetomany/embeddedid/Constant.java @@ -0,0 +1,70 @@ +package org.hibernate.envers.test.integration.onetomany.embeddedid; + +import org.hibernate.envers.Audited; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import java.io.Serializable; + +/** + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + */ +@Entity +@Audited +public class Constant implements Serializable { + @Id + @Column(length = 3) + private String id; + + private String name; + + public Constant() { + } + + public Constant(String id, String name) { + this.id = id; + this.name = name; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Constant)) return false; + + Constant constant = (Constant) o; + + if (id != null ? !id.equals(constant.id) : constant.id != null) return false; + if (name != null ? !name.equals(constant.name) : constant.name != null) return false; + + return true; + } + + @Override + public int hashCode() { + int result = id != null ? id.hashCode() : 0; + result = 31 * result + (name != null ? name.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "Constant(id = " + id + ", name = " + name + ")"; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/onetomany/embeddedid/MapsIdTest.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/onetomany/embeddedid/MapsIdTest.java new file mode 100644 index 0000000000..b5964d4fd0 --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/onetomany/embeddedid/MapsIdTest.java @@ -0,0 +1,130 @@ +package org.hibernate.envers.test.integration.onetomany.embeddedid; + +import javax.persistence.EntityManager; + +import org.hibernate.envers.test.Priority; +import org.hibernate.testing.TestForIssue; +import org.junit.Assert; +import org.junit.Test; + +import org.hibernate.envers.test.BaseEnversJPAFunctionalTestCase; + +import java.util.Arrays; + +/** + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + */ +@TestForIssue(jiraKey = "HHH-7157") +public class MapsIdTest extends BaseEnversJPAFunctionalTestCase { + private PersonTuple tuple1Ver1 = null; + private PersonTuple tuple2Ver1 = null; + private PersonTuple tuple2Ver2 = null; + private Person personCVer1 = null; + private Person personCVer2 = null; + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[]{Person.class, PersonTuple.class, Constant.class}; + } + + @Test + @Priority(10) + public void initData() { + EntityManager em = getEntityManager(); + + // Revision 1 + em.getTransaction().begin(); + Person personA = new Person("Peter"); + Person personB = new Person("Mary"); + em.persist(personA); + em.persist(personB); + Constant cons = new Constant("USD", "US Dollar"); + em.persist(cons); + PersonTuple tuple1 = new PersonTuple(true, personA, personB, cons); + em.persist(tuple1); + em.getTransaction().commit(); + + tuple1Ver1 = new PersonTuple(tuple1.isHelloWorld(), tuple1.getPersonA(), tuple1.getPersonB(), tuple1.getConstant()); + + // Revision 2 + em.getTransaction().begin(); + cons = em.find(Constant.class, cons.getId()); + Person personC1 = new Person("Lukasz"); + em.persist(personC1); + PersonTuple tuple2 = new PersonTuple(true, personA, personC1, cons); + em.persist(tuple2); + em.getTransaction().commit(); + + tuple2Ver1 = new PersonTuple(tuple2.isHelloWorld(), tuple2.getPersonA(), tuple2.getPersonB(), tuple2.getConstant()); + personCVer1 = new Person(personC1.getId(), personC1.getName()); + personCVer1.getPersonBTuples().add(tuple2Ver1); + + // Revision 3 + em.getTransaction().begin(); + tuple2 = em.find(PersonTuple.class, tuple2.getPersonTupleId()); + tuple2.setHelloWorld(false); + em.merge(tuple2); + em.getTransaction().commit(); + + tuple2Ver2 = new PersonTuple(tuple2.isHelloWorld(), tuple2.getPersonA(), tuple2.getPersonB(), tuple2.getConstant()); + + // Revision 4 + em.getTransaction().begin(); + Person personC2 = em.find(Person.class, personC1.getId()); + personC2.setName("Robert"); + em.merge(personC2); + em.getTransaction().commit(); + + personCVer2 = new Person(personC2.getId(), personC2.getName()); + personCVer2.getPersonBTuples().add(tuple2Ver1); + + em.close(); + } + + @Test + public void testRevisionsCounts() { + Assert.assertEquals(Arrays.asList(1), getAuditReader().getRevisions(PersonTuple.class, tuple1Ver1.getPersonTupleId())); + Assert.assertEquals(Arrays.asList(2, 3), getAuditReader().getRevisions(PersonTuple.class, tuple2Ver1.getPersonTupleId())); + Assert.assertEquals(Arrays.asList(2, 4), getAuditReader().getRevisions(Person.class, personCVer1.getId())); + } + + @Test + public void testHistoryOfTuple1() { + PersonTuple tuple = getAuditReader().find(PersonTuple.class, tuple1Ver1.getPersonTupleId(), 1); + + Assert.assertEquals(tuple1Ver1, tuple); + Assert.assertEquals(tuple1Ver1.isHelloWorld(), tuple.isHelloWorld()); + Assert.assertEquals(tuple1Ver1.getPersonA().getId(), tuple.getPersonA().getId()); + Assert.assertEquals(tuple1Ver1.getPersonB().getId(), tuple.getPersonB().getId()); + } + + @Test + public void testHistoryOfTuple2() { + PersonTuple tuple = getAuditReader().find(PersonTuple.class, tuple2Ver2.getPersonTupleId(), 2); + + Assert.assertEquals(tuple2Ver1, tuple); + Assert.assertEquals(tuple2Ver1.isHelloWorld(), tuple.isHelloWorld()); + Assert.assertEquals(tuple2Ver1.getPersonA().getId(), tuple.getPersonA().getId()); + Assert.assertEquals(tuple2Ver1.getPersonB().getId(), tuple.getPersonB().getId()); + + tuple = getAuditReader().find(PersonTuple.class, tuple2Ver2.getPersonTupleId(), 3); + + Assert.assertEquals(tuple2Ver2, tuple); + Assert.assertEquals(tuple2Ver2.isHelloWorld(), tuple.isHelloWorld()); + Assert.assertEquals(tuple2Ver2.getPersonA().getId(), tuple.getPersonA().getId()); + Assert.assertEquals(tuple2Ver2.getPersonB().getId(), tuple.getPersonB().getId()); + } + + @Test + public void testHistoryOfPersonC() { + Person person = getAuditReader().find(Person.class, personCVer1.getId(), 2); + + Assert.assertEquals(personCVer1, person); + Assert.assertEquals(personCVer1.getPersonATuples(), person.getPersonATuples()); + Assert.assertEquals(personCVer1.getPersonBTuples(), person.getPersonBTuples()); + + person = getAuditReader().find(Person.class, personCVer2.getId(), 4); + + Assert.assertEquals(personCVer2, person); + } +} \ No newline at end of file diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/onetomany/embeddedid/Person.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/onetomany/embeddedid/Person.java new file mode 100644 index 0000000000..f38fea9ee8 --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/onetomany/embeddedid/Person.java @@ -0,0 +1,96 @@ +package org.hibernate.envers.test.integration.onetomany.embeddedid; + +import org.hibernate.envers.Audited; + +import javax.persistence.*; +import java.io.Serializable; +import java.util.HashSet; +import java.util.Set; + +/** + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + */ +@Entity +@Audited +public class Person implements Serializable { + @Id + @GeneratedValue + private long id; + + private String name; + + @OneToMany(mappedBy = "personA") + private Set personATuples = new HashSet(); + + @OneToMany(mappedBy = "personB") + private Set personBTuples = new HashSet(); + + public Person() { + } + + public Person(String name) { + this.name = name; + } + + public Person(long id, String name) { + this.id = id; + 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 (id != person.id) return false; + if (name != null ? !name.equals(person.name) : person.name != null) return false; + + return true; + } + + @Override + public int hashCode() { + int result = (int) (id ^ (id >>> 32)); + result = 31 * result + (name != null ? name.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "Person(id = " + id + ", name = " + name + ")"; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Set getPersonBTuples() { + return personBTuples; + } + + public void setPersonBTuples(Set personBTuples) { + this.personBTuples = personBTuples; + } + + public Set getPersonATuples() { + return personATuples; + } + + public void setPersonATuples(Set personATuples) { + this.personATuples = personATuples; + } +} \ No newline at end of file diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/onetomany/embeddedid/PersonTuple.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/onetomany/embeddedid/PersonTuple.java new file mode 100644 index 0000000000..47c2d9bbd7 --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/onetomany/embeddedid/PersonTuple.java @@ -0,0 +1,174 @@ +package org.hibernate.envers.test.integration.onetomany.embeddedid; + +import org.hibernate.envers.Audited; + +import javax.persistence.*; +import java.io.Serializable; + +/** + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + */ +@Entity +@Audited +public class PersonTuple implements Serializable { + @Embeddable + public static class PersonTupleId implements Serializable { + @Column(nullable = false) + private long personAId; + + @Column(nullable = false) + private long personBId; + + @Column(nullable = false) + private String constantId; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof PersonTupleId)) return false; + + PersonTupleId that = (PersonTupleId) o; + + if (personAId != that.personAId) return false; + if (personBId != that.personBId) return false; + if (constantId != null ? !constantId.equals(that.constantId) : that.constantId != null) return false; + + return true; + } + + @Override + public int hashCode() { + int result = (int) (personAId ^ (personAId >>> 32)); + result = 31 * result + (int) (personBId ^ (personBId >>> 32)); + result = 31 * result + (constantId != null ? constantId.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "PersonTupleId(personAId = " + personAId + ", personBId = " + personBId + ", constantId = " + constantId + ")"; + } + + public long getPersonAId() { + return personAId; + } + + public void setPersonAId(long personAId) { + this.personAId = personAId; + } + + public long getPersonBId() { + return personBId; + } + + public void setPersonBId(long personBId) { + this.personBId = personBId; + } + + public String getConstantId() { + return constantId; + } + + public void setConstantId(String constantId) { + this.constantId = constantId; + } + } + + @EmbeddedId + private PersonTupleId personTupleId = new PersonTupleId(); + + @MapsId("personAId") + @ManyToOne(optional = false) + @JoinColumn(insertable = false, updatable = false) + private Person personA; + + @MapsId("personBId") + @ManyToOne(optional = false) + @JoinColumn(insertable = false, updatable = false) + private Person personB; + + @MapsId("constantId") + @ManyToOne(optional = false) + @JoinColumn(insertable = false, updatable = false) + private Constant constant; + + @Column(nullable = false) + private boolean helloWorld = false; + + public PersonTuple() { + } + + public PersonTuple(boolean helloWorld, Person personA, Person personB, Constant constant) { + this.helloWorld = helloWorld; + this.personA = personA; + this.personB = personB; + this.constant = constant; + + this.personTupleId.personAId = personA.getId(); + this.personTupleId.personBId = personB.getId(); + this.personTupleId.constantId = constant.getId(); + + personA.getPersonATuples().add(this); + personB.getPersonBTuples().add(this); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof PersonTuple)) return false; + + PersonTuple that = (PersonTuple) o; + + return personTupleId.equals(that.personTupleId); + } + + @Override + public int hashCode() { + return personTupleId.hashCode(); + } + + @Override + public String toString() { + return "PersonTuple(id = " + personTupleId + ", helloWorld = " + helloWorld + ")"; + } + + public PersonTupleId getPersonTupleId() { + return personTupleId; + } + + public void setPersonTupleId(PersonTupleId personTupleId) { + this.personTupleId = personTupleId; + } + + public Person getPersonA() { + return personA; + } + + public void setPersonA(Person personA) { + this.personA = personA; + } + + public Person getPersonB() { + return personB; + } + + public void setPersonB(Person personB) { + this.personB = personB; + } + + public Constant getConstant() { + return constant; + } + + public void setConstant(Constant constant) { + this.constant = constant; + } + + public boolean isHelloWorld() { + return helloWorld; + } + + public void setHelloWorld(boolean helloWorld) { + this.helloWorld = helloWorld; + } +} \ No newline at end of file