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?