【问题标题】:Play! framework referential integrity contraint on delete玩!删除的框架参照完整性约束
【发布时间】:2012-03-06 14:26:57
【问题描述】:

我创建了一些模型对象来表示一家有多个客户的公司,以及一个包含公司和客户组合以及多个发票行的发票对象。我创建了以下模型对象:

@Entity
public class Company extends Model {
    @OneToMany(mappedBy="company")
    public Set<Client> clients;
}

@Entity
public class Client extends Model {
    @ManyToOne
    public Company company;
}

@Entity
public class Invoice extends Model {
    @ManyToOne
    public Company company;
    @ManyToOne
    public Client client;
    @OneToMany(mappedBy="invoice", cascade=CascadeType.ALL)
    public Set<InvoiceLine> invoiceLines;
}

@Entity
public class InvoiceLine extends Model {
    @ManyToOne
    public Invoice invoice;
}

测试:

@Test
public void testModels() {
    Client client = createClient();
    Company company = createCompany();
    company.clients = new HashSet<Client>();
    company.clients.add(client);
    company.save();

    Invoice invoice = createInvoice(client, company);
    InvoiceLine invoiceLine1 = createInvoiceLine(invoice);
    InvoiceLine invoiceLine2 = createInvoiceLine(invoice);
    Set<InvoiceLine> invoiceLines = new HashSet<InvoiceLine>();
    invoiceLines.add(invoiceLine1);
    invoiceLines.add(invoiceLine2);
    invoice.invoiceLines = invoiceLines;
    invoice.save();

    Company retrievedCompany = Company.find("byName", company.name).first();
    assertNotNull(retrievedCompany);
    assertEquals(1, retrievedCompany.clients.size());
    assertEquals(2, InvoiceLine.count());

    assertEquals(1, Invoice.deleteAll());
    assertNull(Invoice.all());
    assertNull(InvoiceLine.all());

}

在运行创建包含两个发票行的发票的测试并尝试删除此发票时,我收到以下错误:

org.h2.jdbc.JdbcSQLException:违反参照完整性约束: “FK3004B0A1F4110EF6:PUBLIC.INVOICELINE 外键(INVOICE_ID)参考>PUBLIC.INVOICE(ID)”; SQL 语句:从发票中删除 [23003-149]

我做错了什么?

【问题讨论】:

  • 请添加测试代码查看步骤

标签: java hibernate model playframework associations


【解决方案1】:

你试过invoice.delete()吗?问题是deleteAll() 没有级联删除操作。

deleteAll() 在内部使用javax.persistence.Query,而delete() 使用EntityManagerremove() 方法。 JPA 中的级联由 JPA 处理,而不是由数据库处理,并且 JPA 不会像 deleteAll() 执行的那样级联批量删除。
Check this link for more info on bulk delete/update

另外:如果您已经将 Invoice 设置为 createInvoiceLine() 中的父级,则将 InvoiceLine 实体添加到 Invoice 是多余的。只需在执行断言之前执行invoice.refresh()

也许下面的单元测试可以解决问题。 Parent1 就像你的 Invoice。而Child1 就像你的InvoiceLine

import java.util.*;
import javax.persistence.*;
import org.junit.*;
import play.test.*;
import models.*;

public class Parent1Test extends UnitTest {

    public Parent1 p;
    public Child1 c1;
    public Child1 c2;
    public Child1 c3;

    @Before
    public void setUp() {
        Fixtures.deleteAllModels();

        p = new Parent1();
        c1 = new Child1();
        c2 = new Child1();
        c3 = new Child1();
    }

    public void byAddingParentToChilds() {
        c1.parent = p;
        c2.parent = p;
        c3.parent = p;

        c1.save();
        c2.save();
        c3.save();
        p.refresh();
    }

    @Test
    public void testByAddingParentToChilds() {
        byAddingParentToChilds();
        assertEquals(p.id, c1.parent.id);
        assertEquals(3, Child1.count());
    }

    public void byAddingChildsToParent() {
        p.childs = new ArrayList<Child1>();
        p.childs.add(c1);
        p.childs.add(c2);
        p.childs.add(c3);
        p.save();
    }

    @Test
    public void testByAddingChildsToParent() {
        // By adding childs
        byAddingChildsToParent();

        c1.refresh();
        assertEquals(3, Child1.count());
        // This will be null, because you added the childs to the 
        // parent while the childs are the owning side of the 
        // relation.
        assertNull(c1.parent);
    }

    @Test
    public void testDeletingAfterAddingParentToChilds() {
        byAddingParentToChilds();
        p.delete();
        assertEquals(0, Parent1.count());
        assertEquals(0, Child1.count());
    }

    @Test
    public void testDeletingAfterAddingChildsToParent() {
        byAddingChildsToParent();
        p.delete();
        assertEquals(0, Parent1.count());
        assertEquals(0, Child1.count());
    }

    @Test(expected=PersistenceException.class)
    public void testDeleteAllAfterAddingParentToChilds() {
        byAddingParentToChilds();
        // The cascading doesn't work for deleteAll() so this line
        // will throw an exception because the child elements still
        // reference the parent.
        assertEquals(1, Parent1.deleteAll());
    }

    @Test
    public void testDeleteAllAfterAddingChildsToParent() {
        byAddingChildsToParent();
        assertEquals(1, Parent1.deleteAll());
        assertEquals(0, Parent1.count());
        // Again the cascading doesn't work for deleteAll()
        assertEquals(3, Child1.count());
    }

}

【讨论】:

  • 一个问题,在方法 byAddingParentToChilds() 中,您首先保存父项,然后是子项,然后刷新父项。这似乎有些多余,因为保存父级(假设持久性是级联的)应该足以达到相同的效果,对吗?
  • 正确,并在答案中修复:)
  • 但是需要刷新,因为子节点将自己添加到父节点并更新数据库(通过保存),但父节点状态仍然需要从数据库中更新。
  • 如果您只想保存一次,最好创建一个addChild() 到父级,添加子级并将其设置为父级。这样你就可以在父节点上调用一次save()。哪个更有利于性能。
【解决方案2】:

您正在尝试删除所有发票,但仍有一些 InvoiceLines 与其关联。

尝试先删除发票行,然后再删除发票。

【讨论】:

  • 感谢您的回复。问题似乎与 JPA 而不是 Play!框架。我想要的行为是 invoiceLines 在与之相关的发票被删除时自动删除。我在想,通过添加 CascadeType.ALL 并将 orphanRemoval 设置为 true,我会得到这种行为,但是在删除 invoiceLines 之前删除发票时会违反完整性约束。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-10-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多