架构定义
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
即只要邮政编码不同,一个人就应该能够拥有多个地址。
类
@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;
}
这意味着它允许向同一个人添加多个地址,只要这两个地址具有不同的邮政编码即可。
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);
但是当试图更新其中一个时(例如改变状态)
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);
生成以下内容。我认为下面的内容基本上意味着“嘿,你试图用person_id和已经存在的邮政编码添加地址,我不在乎状态是否不同,你不能这样做”。如何才能工作?
引起:java.lang.IllegalStateException:多个表示 正在合并同一实体[Address#AddressId @ 5047ce78]。 管理:[地址@ 5c220478];分离:[地址@ 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(原生方法) 〜[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]
然而,如果我这样做
Person person = personRepo.findOne(personId);
new ArrayList<>(person.getAddresses()).get(0).setState(State.QLD); //change state of existing address row
person = personRepo.save(person);
我没有得到例外。我认为Hibernate检测到在这种情况下,现有行正在更新,而如果尝试使用相同的personId添加另一个Address并且邮政编码已经存在,则会尝试执行INSERT并失败。有没有办法让Hibernate在日期的情况下做出更新?
答案 0 :(得分:2)
您必须浏览每个人的地址以检查其是否存在并更新是否存在,如果不存在则进行更新。
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 = null;
for (Address a : person.getAddresses()) {
if (a.getAddressId().equals(addressId)) {
address = a;
break;
}
}
if (address == null) {
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);
}
else {
address.setState(State.TAS);
}
person = personRepo.save(person);
此代码示例假定您已根据需要覆盖了Embeddable组合键上的equals
。
答案 1 :(得分:2)
地址可以是@Embeddable
而不是@Entity
。在这种情况下,地址没有自己的持久性标识,这反映了您的数据库:没有关联人员,地址就不存在。
简化的映射看起来像:
@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
}
如果你根据邮政编码覆盖equals()和hashcode(),那么一切都应该按预期工作。
https://en.wikibooks.org/wiki/Java_Persistence/ElementCollection
另见:
Hibernate - @ElementCollection - Strange delete/insert behavior
答案 2 :(得分:1)
您正在使用相同的AddressId键创建新的Address对象。
由于未覆盖equals和hashCode,因此将新的重复地址插入到集合中,而不是替换旧的Address对象。
这会创建具有相同AddressId的地址重复。
因此,要么在地址中实现equals以便Set替换重复,或者只是从Set中提取正确的地址并直接更改其状态。
Address的equals和hashCode应该调用AddressId的equals和hashCode。 addressId equald和hashCode应该检查personId和postCode。
使用现代IDEAs(例如intelliJ或eclipse)将允许您自动生成与postCode和personId相关的方法