正在合并同一实体的多个表示JPA

时间:2016-10-17 05:40:36

标签: java hibernate jpa

架构定义

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在日期的情况下做出更新?

3 个答案:

答案 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相关的方法