【问题标题】:Multiple representations of the same entity are being merged JPA [duplicate]同一实体的多个表示正在合并JPA [重复]
【发布时间】:2017-02-26 00:17:11
【问题描述】:

架构定义

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]在 org.hibernate.event.internal.EntityCopyNotAllowedObserver.entityCopyDetected(EntityCopyNotAllowedObserver.java:51) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final] 在 org.hibernate.event.internal.MergeContext.put(MergeContext.java:262) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final] 在 org.hibernate.event.internal.DefaultMergeEventListener.entityIsPersistent(DefaultMergeEventListener.java:216) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final] 在 org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:192) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final] 在 org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:886) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final] 在 org.hibernate.internal.SessionImpl.merge(SessionImpl.java:868) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final] 在 org.hibernate.engine.spi.CascadingActions$6.cascade(CascadingActions.java:277) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final] 在 org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:350) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final] 在 org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:293) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final] 在 org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:161) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final] 在 org.hibernate.engine.internal.Cascade.cascadeCollectionElements(Cascade.java:379) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final] 在 org.hibernate.engine.internal.Cascade.cascadeCollection(Cascade.java:319) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final] 在 org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:296) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final] 在 org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:161) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final] 在 org.hibernate.engine.internal.Cascade.cascade(Cascade.java:118) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final] 在 org.hibernate.event.internal.DefaultMergeEventListener.cascadeOnMerge(DefaultMergeEventListener.java:474) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final] 在 org.hibernate.event.internal.DefaultMergeEventListener.entityIsPersistent(DefaultMergeEventListener.java:218) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final] 在 org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:192) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final] 在 org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:85) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final] 在 org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:876) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final] 在 org.hibernate.internal.SessionImpl.merge(SessionImpl.java:858) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final] 在 org.hibernate.internal.SessionImpl.merge(SessionImpl.java:863) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final] 在 org.hibernate.jpa.spi.AbstractEntityManagerImpl.merge(AbstractEntityManagerImpl.java:1196) ~[hibernate-entitymanager-4.3.11.Final.jar:4.3.11.Final] 在 sun.reflect.NativeMethodAccessorImpl.invoke0(本机方法) ~[na:1.8.0_102] 在 sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_102] 在 sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_102] 在 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 和邮政编码的地址,它会尝试执行 INSERT 并失败。有没有办法让 Hibernate 在日期情况下也进行更新?

【问题讨论】:

  • 将地址添加到集合中的代码在哪里?你是否正确实现了equals和hashCode?​​span>
  • @aviad 添加了生成异常的代码。至于equals和hashcode,它们没有被任何类覆盖,我不确定什么是“正确”,因为对此有很多不同的意见。

标签: java hibernate jpa


【解决方案1】:

您正在使用相同的 AddressId 键创建一个新的 Address 对象。

由于 equals 和 hashCode 没有被覆盖,新的重复地址被插入到集合中,而不是替换旧的 Address 对象。

这会创建具有相同 AddressId 的地址重复。

因此,要么在 Address 中实现 equals 以使 Set 替换重复项,要么直接从 Set 中提取正确的地址并直接更改其状态。

Address的equals和hashCode应该调用AddressId的equals和hashCode。 addressId 等于和 hashCode 应该检查 personId 和 postCode。

使用诸如 intelliJ 或 eclipse 之类的现代 IDEA 将允许您自动生成与 postCode 和 personId 相关的方法

【讨论】:

  • 如果我按照描述实现equals和hashcode,在将“已编辑”地址添加到Person(即与现有地址具有相同personId和postcode但状态不同的地址)时,集合的大小没有增加(好),但集合中的条目没有改变。 (不是期望的行为)
  • 之前删除
  • 我知道这将如何工作,但没有更惯用、更优雅的方式来实现最终目标吗? (包括可能更改架构定义等?)
  • 这些重复项是在 java 级别输入的,我看不出如何操作数据库会更优雅。
  • 我希望将多个答案标记为已接受。
【解决方案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

【讨论】:

  • 我知道这将如何工作,但没有更惯用、更优雅的方式来实现最终目标吗? (包括可能更改架构定义等?)
  • 如果您使用 Postgres,您可以创建一个使用 INSERT ... ON CONFLICT UPDATE ... WHERE 语法的本机查询,但仅此而已。这里列出了其他数据库的其他语法wiki.postgresql.org/wiki/UPSERT
  • 我希望将多个答案标记为已接受。
【解决方案3】:

地址可以是@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

【讨论】:

  • 这是有希望的,但是当我添加到人员地址时,第二个地址与已添加的 personId 和邮政编码相同,但例如状态不同,什么也没有发生。 (因为equals方法只看邮政编码,所以新状态没有被拾取)
  • 好的。现在明白了。您想将一个项目添加到集合中,jvm 会猜测它是更新项目还是新项目。它是否正确?你不能同时拥有它。
  • JVM当然不能变魔术。非常感谢您的回答。
  • 如果 Person 有一个 addAddress 方法,该方法首先删除人员地址集中的参数地址,然后添加它,它可以正常工作。
猜你喜欢
  • 1970-01-01
  • 2016-03-14
  • 1970-01-01
  • 1970-01-01
  • 2019-06-08
  • 2017-06-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多