2

我有一个 ContactEntity(父)和 PhoneNumberComponent(子)关系。当我尝试 EntityManager.remove() 孩子时,我得到下面的异常。

另外,我注意到,如果我在 AbstractContactEntity 的 phoneNumbers 映射中删除 CascadeType.Persist 或将 FetchType.EAGER 更改为 LAZY,我的 JUnit 就会通过。

有人能告诉我我错过了什么吗?

使用:休眠,hsqldb

堆栈跟踪

javax.persistence.EntityNotFoundException: deleted entity passed to persist: [com.bb.sc.contact.jpa.entity.PhoneNumberComponent#<null>]
    at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1329)
    at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1280)
    at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1286)
    at org.hibernate.ejb.AbstractEntityManagerImpl.flush(AbstractEntityManagerImpl.java:969)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:240)
    at $Proxy35.flush(Unknown Source)
    at test.bb.sc.contact.jpa.entity.PhoneNumberComponentJUnit.testCRUD(PhoneNumberComponentJUnit.java:98)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28)
    at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:74)
    at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:82)
    at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:72)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:240)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:180)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)

联系实体(父)

@Entity
@Table (name="CONTACT")
@Inheritance (strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn (name="CONTACT_DISCRIMANATOR")
public abstract class AbstractContactEntity implements Auditable {
    @Column (name="CONTACT_ID", nullable=false)
    @Id @GeneratedValue (strategy=GenerationType.IDENTITY)
    private Long id;
    @OneToMany (mappedBy="contact", cascade=CascadeType.ALL, fetch=FetchType.LAZY, targetEntity=PostalAddressComponent.class)
    private Collection<PostalAddressComponent> postalAddresses;
    @OneToMany (mappedBy="contact", targetEntity=PhoneNumberComponent.class, cascade={CascadeType.DETACH, CascadeType.MERGE, CascadeType.REFRESH, CascadeType.REMOVE, CascadeType.PERSIST}, fetch=FetchType.EAGER)
    private Collection<PhoneNumberComponent> phoneNumbers;
    @OneToMany (mappedBy="contact", cascade=CascadeType.ALL, fetch=FetchType.LAZY, targetEntity=EmailAddressComponent.class)
    private Collection<EmailAddressComponent> emailAddresses;
    @Embedded
    private DbAudit dbAudit;

ContactDetail(抽象的孩子)

@MappedSuperclass
public abstract class AbstractContactDetail implements Auditable{
    @Id @GeneratedValue (strategy=GenerationType.IDENTITY)
    private Long id;
    @OneToOne @JoinColumn (name="CONTACT_TYPE_ID", nullable=false)
    private ContactDetailTypeEntity type;
    @ManyToOne (targetEntity=AbstractContactEntity.class) @JoinColumn (name="CONTACT_ID", nullable=false)
    private AbstractContactEntity contact;
    @Embedded
    private DbAudit dbAudit;

ContactDetail impl(具体的 impl child)

@Entity
@Table (name="PHONE_NUMBER")
@AttributeOverride (name="id", column=@Column(name="PHONE_NUMBER_ID"))
public class PhoneNumberComponent extends AbstractContactDetail {

    @Column (name="AREA_CODE", nullable=false)
    private Integer areaCode;
    @Column (name="PREFIX", nullable=false)
    private Integer prefix;
    @Column (name="SUFFIX", nullable=false)
    private Integer suffix;
    @Column (name="EXTENSION")
    private Integer ext;

JUnit

@Before
public void setup () {
    contact = new ContactPersonEntity();
    type = new ContactDetailTypeEntity();

    type.setCode("CODE");
    type.setType("TYPE");
    em.persist(contact);
    em.persist(type);
    em.flush();

    // Before each test, make sure the EntityManager has a clean persistence context.
    em.clear();
}   
@Test
public void testDelete () {
    PhoneNumberComponent p = new PhoneNumberComponent(111, 222, 3333, 44, contact, type);
    em.persist(p);
    em.flush();
    assertNotNull (p.getId());

    em.clear();
    PhoneNumberComponent found = em.find(PhoneNumberComponent.class, p.getId());
    assertNotNull (found);
    em.remove(found);
    em.flush();
}

您在我的 JUnit 中看到的最后一个 em.flush 上的 JUnit 炸弹。另一个观察结果是,如果我用下面的代码替换 JUnit 中的 em.remove(foundUpd) 行,则测试通过。但是,我宁愿不必加载父对象图、遍历它并显式断开父子链接以删除记录。谢谢。

    AbstractContactEntity c = em.find(AbstractContactEntity.class, 1L);
    c.getPhoneNumbers();
    Iterator<PhoneNumberComponent> it = c.getPhoneNumbers().iterator();
    while (it.hasNext()) {
        PhoneNumberComponent p = it.next();
        em.remove(p);
        it.remove();
    }
4

2 回答 2

3

在 FetchType.EAGER 映射中,在持久性上下文中加载子级也会加载父级。在我的 JUnit 中,我在找到孩子之前执行了 EntityManager.clear 。认为孩子是持久性上下文中唯一的实体是一个糟糕的假设。

在调试中,我执行了

PhoneNumberComponent found = em.find(PhoneNumberComponent.class, p.getId());

并注意到它生成的一对SQL。

Hibernate: select phonenumbe0_.PHONE_NUMBER_ID as PHONE1_3_2_, phonenumbe0_.CONTACT_ID as CONTACT10_3_2_, phonenumbe0_.CREATE_TIMESTAMP as CREATE2_3_2_, phonenumbe0_.CREATE_USER as CREATE3_3_2_, phonenumbe0_.UPDATE_TIMESTAMP as UPDATE4_3_2_, phonenumbe0_.UPDATE_USER as UPDATE5_3_2_, phonenumbe0_.CONTACT_DETAIL_TYPE_ID as CONTACT11_3_2_, phonenumbe0_.AREA_CODE as AREA6_3_2_, phonenumbe0_.EXTENSION as EXTENSION3_2_, phonenumbe0_.PREFIX as PREFIX3_2_, phonenumbe0_.SUFFIX as SUFFIX3_2_, abstractco1_.CONTACT_ID as CONTACT2_0_0_, abstractco1_.CREATE_TIMESTAMP as CREATE3_0_0_, abstractco1_.CREATE_USER as CREATE4_0_0_, abstractco1_.UPDATE_TIMESTAMP as UPDATE5_0_0_, abstractco1_.UPDATE_USER as UPDATE6_0_0_, abstractco1_.ORGANIZATION_NAME as ORGANIZA7_0_0_, abstractco1_.FRIST_NAME as FRIST8_0_0_, abstractco1_.LAST_NAME as LAST9_0_0_, abstractco1_.CONTACT_DISCRIMANATOR as CONTACT1_0_0_, contactdet2_.CONTACT_DETIAL_TYPE_ID as CONTACT1_1_1_, contactdet2_.CODE as CODE1_1_, contactdet2_.DESCRIPTION as DESCRIPT3_1_1_, contactdet2_.TYPE as TYPE1_1_ from PHONE_NUMBER phonenumbe0_ inner join CONTACT abstractco1_ on phonenumbe0_.CONTACT_ID=abstractco1_.CONTACT_ID inner join CONTACT_TYPE contactdet2_ on phonenumbe0_.CONTACT_DETAIL_TYPE_ID=contactdet2_.CONTACT_DETIAL_TYPE_ID where phonenumbe0_.PHONE_NUMBER_ID=?
Hibernate: select phonenumbe0_.CONTACT_ID as CONTACT10_0_2_, phonenumbe0_.PHONE_NUMBER_ID as PHONE1_2_, phonenumbe0_.PHONE_NUMBER_ID as PHONE1_3_1_, phonenumbe0_.CONTACT_ID as CONTACT10_3_1_, phonenumbe0_.CREATE_TIMESTAMP as CREATE2_3_1_, phonenumbe0_.CREATE_USER as CREATE3_3_1_, phonenumbe0_.UPDATE_TIMESTAMP as UPDATE4_3_1_, phonenumbe0_.UPDATE_USER as UPDATE5_3_1_, phonenumbe0_.CONTACT_DETAIL_TYPE_ID as CONTACT11_3_1_, phonenumbe0_.AREA_CODE as AREA6_3_1_, phonenumbe0_.EXTENSION as EXTENSION3_1_, phonenumbe0_.PREFIX as PREFIX3_1_, phonenumbe0_.SUFFIX as SUFFIX3_1_, contactdet1_.CONTACT_DETIAL_TYPE_ID as CONTACT1_1_0_, contactdet1_.CODE as CODE1_0_, contactdet1_.DESCRIPTION as DESCRIPT3_1_0_, contactdet1_.TYPE as TYPE1_0_ from PHONE_NUMBER phonenumbe0_ inner join CONTACT_TYPE contactdet1_ on phonenumbe0_.CONTACT_DETAIL_TYPE_ID=contactdet1_.CONTACT_DETIAL_TYPE_ID where phonenumbe0_.CONTACT_ID=?

然后我添加了以下行,并注意到控制台中没有 SQL。不需要 DB 操作,因为 EntityManager 能够在持久性上下文中找到父级。

AbstractContactEntity c = em.find(AbstractContactEntity.class, 1L);

如果我将 AbstractContactEntity 中的 phoneNumbers 映射更改为 FetchType.LAZY,我只会得到第一个 SQL。

编辑

我不知道为什么 Hibernate会这样做,但现在我只是接受这种行为并进行相应的设计。

这是 Hibernate 中可能发生的情况的说明。

当我 find() 我的子实体时,父实体也加载到持久性上下文中。然后 remove() 将删除指令标记给子节点,并将更新指令标记给父节点。最后,当我 flush() 时,子级被删除,父级被推回数据库。但是由于该父级仍然具有对已删除子级的引用,因此我被 EntityNotFoundException 踢了:已删除的实体传递给了持久化。

于 2012-06-16T11:59:27.190 回答
0

ContactEntity(父)和 PhoneNumberComponent(子)

您在 ContactEntity 中有 PhoneNumberComponent 的列表。要删除孩子,请按照下列步骤操作:

  1. 从数据库中获取 PhoneNumberComponent。
  2. 从 DB 中获取相应的 ContactEntity。
  3. 从 ContactEntity 列表中删除相应的 PhoneNumberComponent。
  4. 现在删除 PhoneNumberComponent
于 2012-06-24T05:27:17.170 回答