Multiple representations of the same entity are being merged JPA
hibernate, java, jpa
Solution
Address can be made an `@Embeddable` rather than an `@Entity`. In this scenario an Address has no persistent identity of its own which reflects your database: an address cannot exist without an associated person.
The simplified mappings then look like:
@Entity
@Getter
@Setter
class Person implements Serializable {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
@Column(name="id", unique = true, nullable = false)
private Long id;
@ElementCollection
@CollectionTable(name = "address", joinColumns = @JoinColumn(name = "person_id"))
private Set<Address> Addresses = new HashSet<>();
protected Person(){}
}
@Embeddable
@Getter
@Setter
class Address implements Serializable {
private State state;
private String postcode;
protected Address(){}
// override equals and hascode based on post code only
// this will give you the desired behaviour
}
If you override equals() and hashcode() based on post code then everything should work as expected.
https://en.wikibooks.org/wiki/Java_Persistence/ElementCollection
See also:
Hibernate - @ElementCollection - Strange delete/insert behavior
Problem
Schema definition ``` CREATE TABLE person ( id bigint(20) NOT NULL AUTO_INCREMENT, PRIMARY KEY (id) ) ENGINE=InnoDB CREATE TABLE address ( person_id bigint(20) not null, postcode varchar(255) not null, state int(11), constraint `PRIMARY` primary key (address_id, postcode), constraint FK_dq8j32idfdnwny42fiovenqwo foreign key (person_id) references person (id) ) ENGINE=InnoDB ``` Ie a person should be able to have multiple addresses as long as the postcode is different. Classes ``` @Entity @Getter @Setter class Person implements Serializable { @Id @GeneratedValue(strategy=GenerationType.AUTO) @Column(name="id", unique = true, nullable = false) private Long id; @OneToMany(cascade= CascadeType.ALL) private Set<Address> Addresses = new HashSet<>(); protected Person(){} } @Entity @Getter @Setter class Address implements Serializable { @EmbeddedId private AddressId addressId; private State state; protected Address(){} } @Embeddable @Getter @Setter public class AddressId implements Serializable { @Column(name = "person_id") private Long personId; @Column(name = "postcode") private String postcode; } ``` This works in the sense that it allows adding more than one address to the same person as long as the two addresses have different postcodes. ``` Person person = personRepo.findOne(personId); AddressId addressId = new AddressId(); addressId.setPersonId(personId); addressId.setPostcode("4000"); Address address = new Address(); address.setAddressId(addressId); address.setState(State.QLD); person.getAddresses().add(address); AddressId addressId2 = new AddressId(); addressId2.setPersonId(personId); addressId2.setPostcode("4001"); Address address2 = new Address(); address2.setAddressId(addressId2); address.setState(State.VIC); person.getAddresses().add(address2); person = personRepo.save(person); ``` But when trying to update one of them (eg changing the state) ``` Person person = personRepo.findOne(personId); AddressId addressId = new AddressId(); addressId.setPersonId(personId); addressId.setPostcode("4000"); //person already has an address with this postcode Address address = new Address(); address.setAddressId(addressId); address.setState(State.TAS); //but I want to change the state from QLD to TAS person.getAddresses().add(address); person = personRepo.save(person); ``` the below is generated. I think the below basically means "Hey, you're trying to add an address with a person_id and postcode that already exists, I don't care if the state is different, you can't do that". How can it be made to work? Caused by: java.lang.IllegalStateException: Multiple representations of the same entity [Address#AddressId@5047ce78] are being merged. Managed: [Address@5c220478]; Detached: [Address@79804699] at org.hibernate.event.internal.EntityCopyNotAllowedObserver.entityCopyDetected(EntityCopyNotAllowedObserver.java:51) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final] at org.hibernate.event.internal.MergeContext.put(MergeContext.java:262) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final] at org.hibernate.event.internal.DefaultMergeEventListener.entityIsPersistent(DefaultMergeEventListener.java:216) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final] at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:192) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final] at org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:886) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final] at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:868) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final] at org.hibernate.engine.spi.CascadingActions$6.cascade(CascadingActions.java:277) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final] at org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:350) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final] at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:293) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final] at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:161) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final] at org.hibernate.engine.internal.Cascade.cascadeCollectionElements(Cascade.java:379) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final] at org.hibernate.engine.internal.Cascade.cascadeCollection(Cascade.java:319) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final] at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:296) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final] at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:161) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final] at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:118) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final] at org.hibernate.event.internal.DefaultMergeEventListener.cascadeOnMerge(DefaultMergeEventListener.java:474) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final] at org.hibernate.event.internal.DefaultMergeEventListener.entityIsPersistent(DefaultMergeEventListener.java:218) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final] at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:192) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final] at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:85) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final] at org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:876) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final] at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:858) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final] at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:863) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final] at org.hibernate.jpa.spi.AbstractEntityManagerImpl.merge(AbstractEntityManagerImpl.java:1196) ~[hibernate-entitymanager-4.3.11.Final.jar:4.3.11.Final] at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_102] at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_102] at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_102] at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_102] However, if I do ``` Person person = personRepo.findOne(personId); new ArrayList<>(person.getAddresses()).get(0).setState(State.QLD); //change state of existing address row person = personRepo.save(person); ``` I don't get the exception. I think Hibernate detects that in that case, an existing row is being updated, whereas if trying to ADD another Address with the same personId and postcode as one that already exists, it tries to do an INSERT and fails. Is there no way to make Hibernate figure out to do an update in date case as well?