JPA Hibernate - 数据库和注释中的级联删除

Posted

技术标签:

【中文标题】JPA Hibernate - 数据库和注释中的级联删除【英文标题】:JPA Hibernate - cascade delete in both database and annotation 【发布时间】:2014-04-12 18:11:58 【问题描述】:

简介

我想知道我应该怎么做,因为我已经阅读了许多试图理解这一点的文章,包括许多 SO 问题。我所读过的任何一篇文章都没有一针见血。

我想知道当使用级联规则以及应用程序定义数据库时会发生什么,因为这将定义我是否应该采用以下方法。

示例表

create table foo(
  id int unsigned not null auto_increment,
  primary key(id)
);

create table bar(
  id int unsigned not null auto_increment,
  foo_id int unsigned not null,
  primary key(id),
  foreign key(foo_id) references foo(id) on delete cascade on update cascade
)

示例类

@Entity
@Table(name = "foo")
public class Foo 

  private int id;
  private List<Bar> bars;

  @Id
  @GeneratedValue
  @Column(name = "id")
  public int getId() 
    return id;
  

  @OneToMany(mappedBy = "foo", cascade = CascadeType.ALL)
  public List<Bar> getBars() 
    return bars;
  

  public void setId() 
    this.id = id;
  

  public void setBars(List<Bar> bars) 
    this.bars = bars;
  



@Entity
@Table(name = "bar")
public class Bar 

  private int id;
  private Foo foo;

  @Id
  @GeneratedValue
  @Column(name = "id")
  public int getId() 
    return id;
  

  @ManyToOne
  @JoinColumn(name = "foo_id", nullable = false)
  public getFoo() 
    return foo;
  

  public void setId(int id) 
    this.id = id;
  

  public void setFoo(Foo foo) 
    this.foo = foo;
  


问题

如果我现在对Foo 对象调用删除操作(通过EntityManagerFactorySessionFactory),会发生以下哪项?

    休眠操作会删除bar表中的所有记录 其外键是Foofoo_id的外键,然后删除 Foo 记录。

    休眠操作将删除所有已加载到会话缓存中的对应Bar记录(其中 可能是也可能不是实际数据库中存在的所有bar 记录)和 然后删除Foo 记录(然后数据库级联规则将删除所有剩余的bar 记录)。

    休眠操作将尝试 首先删除Foo 记录,如果数据库失败,则执行以下操作之一 上述步骤。

    发生了一些我没有考虑过的事情,如果是这样,那又是什么?

考虑到以下两难假设,最好的方法是什么?

困境

如果 1 为真,则建议:

A) 仅在数据库中定义级联规则。请务必从应用程序的对象中删除bars,这样它们就不会与数据库分离(因为数据库将删除它们的记录),然后调用删除foo

B) 仅在应用程序中定义级联规则,因为它将彻底管理数据库的完整性。

不是

C) 在两者中定义级联规则,因为每个都实现了预期的结果,而另一个则浪费了处理。

如果 2 为真,则建议:

在数据库和应用程序中定义级联规则,以便 Hibernate 可以负责管理其实体,并且数据库可以在之后清理,因为不能保证应用程序删除所有 bar 记录。

如果 3 为真,则建议:

在数据库和应用程序中定义级联规则,因为 Hibernate 似乎支持已经在数据库级别定义的级联规则。

如果 4 为真,那么它会建议:

这个问题更重要,因为我错过了一些基本的东西!

编辑:添加我读过的文章...

相关文章

数据库、应用程序或两者的视图冲突:

SO - should-i-let-jpa-or-the-database-cascade-deletions

数据库或应用程序的冲突视图:

SO - cascading-deletes-updates-using-jpa-or-inside-of-database

本文阐明了 JPA 提供者的实际工作(尽管应该注意,他们使用 OpenJPA 提供者来进行操作证明):

jpa-tutorial

它指出:

删除和持久操作的级联也适用于那些 尚未加载的实体。它甚至通过它们到达 其他实体,可能会遍历整个对象图。

接着说:

刷新、合并和分离的级联只通过 已加载的实体。

这意味着提议的流程 2 不正确。

【问题讨论】:

我只会使用代码。 @djb 那将是选项 1. B) 并且可能是最安全的,因为该选项的唯一潜在不良影响是如果过程 2 为真(尽管我认为 2 的可能性最小)。但是,如果过程 1 为真,那么它表明不应该存在数据库级联规则,如果是这样,那么为什么其他相关的 SO 问题不这么说呢?他们要么暗示两者都存在级联规则,要么没有提及级联含义的这一方面 我所知道的是它必须以某种方式避免违反键约束。所以在实践中,它必须创建并遍历一棵树,删除父母直到最多叶的孩子。 是的,我认为流程 1 是正确的,并且它的执行方式正如您所建议的那样。然而,这肯定意味着级联规则应该定义为一个或另一个,而不是两个。我开始倾向于仅在数据库中定义,因为我想保持标准而不是混合,但是复杂的关系/大量的孩子在 JPA 中可能会变得昂贵 还有一个 Non-JPA Hibernate 注释 @OnDelete 允许您优化删除:eddii.wordpress.com/2006/11/16/… 【参考方案1】:

如果您在数据库中声明级联并休眠,如果数据库支持它,它将始终首先删除,并且休眠调用不会真正删除任何内容,但无论如何都会运行。但是,由于您使用的是 hibernate,因此它的主要优点是可以轻松过渡到可能不支持数据库端级联能力的新数据库。因此,即使您的数据库支持级联并且休眠下划线 jdbc 语句当前没有做任何事情(它们将来可能会做一些事情),您也希望将它们留在那里

【讨论】:

这是我正在寻找的关键信息。但是,如果数据库总是先删除,那一定意味着 hibernate 会首先调用删除父级,如问题的场景 3 中所示。如果这是真的,那么在没有在数据库中设置级联的情况下,调用将失败,因为数据库中存在该父级的子级。这意味着 Hibernate 必须先请求删除子节点,然后再请求删除父节点,如场景 3 中所示。 这就像一个 catch 22,所以我正在寻找对此的澄清。如果先对子级调用de​​lete,就不会失败,也不需要数据库级联规则,但是如果先对父级调用de​​lete,那么在没有数据库规则的情况下就有失败的可能。从您所说的来看,您是在建议它必须先在父级上调用删除? 如果您使用查询而不是附加对象进行删除,那么它不会抛出任何错误并且您不会遇到问题。现在,如果该对象在数据库中不存在并且仍然附加,您将不得不检查休眠代码或运行测试,我认为它不应该抛出错误,因为休眠正在生成 jdbc 代码并且希望它不会尝试选择删除前的对象 - 如果是我认为这将是一个休眠错误,但我自己没有尝试过。如果您坚持不使用附加对象进行删除,则不会有问题 通过附加对象删除是我想做的事情。我已经以这种方式实现了我当前的大部分应用程序。目前,我还没有在 hibernate 中定义级联规则,只将其留给数据库。不理想,因为对象可能会在不小心的情况下分离。但是如果不知道这个问题的答案,我就找不到理想的解决方案。我想证明是在 Hibernate 中设置级联规则,打开数据库中的语句日志记录并查看执行了什么。也许有一天我会找到时间,我会这样做...... 是的,Hibernate 有一个优点,即更容易转换到另一个数据库,但也有缺点,可以很容易地说:“由于您使用的数据库具有对级联使用的本机支持,因此,而不是浪费 Hibernate 的性能并冒着有人手动/通过任何其他方式而不是您的 hibernate 支持应用程序来更改数据库的风险。”这始终取决于您的一般要求/目标。【参考方案2】:

你为什么会考虑它?最好坚持使用休眠级联选项。在两侧都有级联的另一侧将运行级联删除两次。一次来自休眠,一次由数据库管理。

示例 189。来自 hibernate 5.2 文档。生成下面的sql。

@Entity(name = "Person")
public static class Person 
    @ManyToMany(cascade = CascadeType.DELETE)
    private List<Address> addresses = new ArrayList<>();
    ...


Person person1 = entityManager.find( Person.class, personId );
entityManager.remove( person1 );
DELETE FROM Person_Address
WHERE  Person_id = 1

DELETE FROM Person
WHERE  id = 1

现在您看到 hibernate 在删除父实体之前删除子实体。数据库级联将在 sql person delete 上运行,但是当之前删除子项时,它现在没有什么可删除的了。

【讨论】:

同意,但是当有传递关系(比如深度 3 以上)时,休眠很快就会变慢,我在这里谈论的是秒。

以上是关于JPA Hibernate - 数据库和注释中的级联删除的主要内容,如果未能解决你的问题,请参考以下文章

使用带有 Hibernate 的 JPA 注释来描述外键仅在子表中的 @OneToMany 关系

Hibernate/JPA 注释中的多列连接

如何将继承策略与 JPA 注释和 Hibernate 混合使用?

Spring Security 3 身份验证与 Hibernate 3(JPA) 注释的集成

要使用的Hibernate或JPA注释

@Transactional 与 JPA 和 Hibernate 有啥用?