【问题标题】:Spring JPA OneToOne FK as PK Cascade.RemoveSpring JPA OneToOne FK 作为 PK Cascade.Remove
【发布时间】:2019-06-13 13:02:53
【问题描述】:

我有两张桌子,ba

  • 它们具有一对一的双向关系
  • a 有一个到 b 的外键来定义这种关系
  • 此外键也被视为a 的主键和JPA @ID
  • 我想要一个级联删除,在删除 a 时删除相关的 b
  • 在 MySQL 中,ab_idNOT NULL

问题是,当我使用 JPA 存储库删除我的 A 对象时,我在其外键上得到一个 ConstraintViolationException。 我希望ab 行都被删除(巧妙地从a 的行开始)。

如果知道我想保留,我怎么能解决这个问题:

  • 我的数据库架构相同
  • ab 的级联删除
  • b id 是 a 的 JPA @Id
CREATE TABLE `b` (
  `dbid` int(11) NOT NULL AUTO_INCREMENT,
  PRIMARY KEY (`dbid`),
);

CREATE TABLE `a` (
  `b_id` int(11) NOT NULL,
  KEY `b_fk` (`b_id`),
  CONSTRAINT `b_fk` FOREIGN KEY (`b_id`) REFERENCES `b` (`dbid`),
);

@Entity
@Table(name = "a")
public class A {

    @Id
    @Column(name = "b_id")
    @GeneratedValue(generator = "gen")
    @GenericGenerator(name = "gen", strategy = "foreign", parameters = @Parameter(name="property", value="b"))
    private Integer bId;

    @OneToOne(cascade = CascadeType.REMOVE)
    @PrimaryKeyJoinColumn
    private B b;
}
@Entity
@Table(name = "b")
public class B {

    @Id
    @GeneratedValue(strategy= GenerationType.IDENTITY)
    @Column(name = "dbid")
    private Integer id;

    @OneToOne(mappedBy = "b")
    private A a;
}

[编辑] 在回答 cmets 进行所有讨论并重新阅读我的问题之后,orphanRemoval 的提案确实在范围内并且有效。

【问题讨论】:

  • 为了帮助诊断问题,请为这两个表分别提供SHOW CREATE TABLE
  • 你是对的@RickJames,它更清楚了,完成了。
  • 谢谢。现在,请证明需要在一对表之间进行 1:1 映射。在开发的早期,几乎总是“糟糕的模式设计”。在开发的后期完成时,它通常是权宜之计或性能的一个组合。
  • @RickJames 我不想解释这个设计的完整背景(不是在开发的早期),无论如何这是问题的假设之一。鉴于此,我能否实现我所针对的 JPA 设计?
  • 我更喜欢在自己的代码中进行级联删除(等)。这样我就不必怀疑 FK 是否会“做正确的事”或“不能做这个复杂的级联”。

标签: java mysql spring hibernate spring-boot


【解决方案1】:
@OneToOne(mappedBy = "b",cascade = CascadeType.ALL,fetch = FetchType.LAZY,orphanRemoval=true )
private A a;

【讨论】:

  • 不,重新阅读问题,我想要cascade removal from a to borphanRemoval 不在图片范围内。
  • 在下面的答案中,您的回答正好相反。你能发布删除代码吗?
  • 另请阅读 link 关于 Cascade.RemoveophanRemoval 的使用。您可能需要使用orphanRemoval,具体取决于您执行删除操作的方式。
  • -orphanRemoval 对我来说非常清楚。我的一些B 可以在没有A 的情况下存在,所以我不想要孤儿删除。 - 在另一个答案中,我在评论中犯了一个错误,我的问题文本是有效的,我想要从AB 的级联删除,这意味着删除A 应该删除关联的B跨度>
  • 我相信使用您当前的代码,如果您在表 B 中保留数据,您不会在表 A 中创建值。现在遇到问题,您仍然可以通过使用 orphanRemoval 来实现您的要求表 A 末尾的属性,因为它只会删除您需要的内容。它只会从表 B 中删除表 A 的关联数据。表 B 仍然可以拥有自己的数据,因为我们没有在表 A 中创建新条目。
【解决方案2】:

为了实现您的要求,我对您的表格进行了如下调整:

    CREATE TABLE b (
       dbid INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY
    );

    CREATE TABLE a ( 
       b_id int(11) NOT NULL PRIMARY KEY REFERENCES b(dbid) ON DELETE CASCADE
    );

CASCADE DELETE 未添加到您的 DDL 中。

这将启用级联删除。为了在删除a 时删除b 记录,我在A 类中进行了以下更改:

@Entity
@Table(name = "a")
public class A {

    @Id
    @Column(name = "b_id")
    @GeneratedValue(generator = "gen")
    @GenericGenerator(name = "gen", strategy = "foreign", parameters = @Parameter(name="property", value="b"))
    private Integer bId;

    @OneToOne(cascade = CascadeType.REMOVE, orphanRemoval = true)
    @PrimaryKeyJoinColumn
    private B b;
}

在此处找到工作 solution 的链接。

【讨论】:

  • JPA 级联删除绝对不需要在数据库级别定义级联删除。
  • 您已经通过一些完全不同的方式解决了这个问题 - 在数据库级别删除 B 而不是通过实体管理器(通过级联)。此类操作可能会产生副作用:会话中的数据不一致,没有从 B 到 C 的下游(JPA 触发)级联,二级缓存中的数据不一致,没有执行为 B 定义的预删除实体侦听器等。跨度>
  • 同意@AlanHay
  • @AlanHay,同意在这种情况下,如果使用二级缓存可能会导致问题。二级缓存的使用总是有争议的。使用二级缓存时必须小心。该解决方案未提供缓存方面的信息。它也不包含在问题中。是的,这里可以避免使用级联。孤儿移除在这里很神奇。干杯!
【解决方案3】:

仅就您实现的 MySQL 方面而言,表 B 中的记录不知道表 A 中的任何记录。在数据库中,这种关系是单向的

存在本机级联功能以防止外键错误,通过告诉数据库在删除记录时要做什么会使外键无处指向。删除表 A 记录不会导致表 B 记录出现外键错误,因此不会触发任何原生级联功能

重申; 您不能保持架构相同,以及从ab 的级联删除,因为您实际上没有从ab 的级联删除

您还在 cmets 中提到,如果没有原始问题中没有的表 A 记录,某些表 B 记录可以存在

要自动删除您描述的表 B 记录,您有几个关于 DB 的选项:

  1. 交换关系 - 删除当前外键并在表 B 中添加一个可以为空的外键列,该列引用表 A 的主键。然后您可以对该外键进行级联删除。对于不“属于”表 A 记录的表 B 记录,将新列保持为空。您还可以向该列添加唯一索引以确保一对一关系
  2. 添加数据库触发器 - 在删除表 A 记录时,添加删除引用的表 B 记录的数据库触发器
  3. 添加数据库过程 - 添加一个过程,删除表 A 记录,然后依次删除引用的表 B 记录,可能在事务中。今后,仅使用该过程删除表 A 记录
  4. 不要在 DB 层解决问题 - 与选项 3 基本相同,但将过程逻辑从 DB 层移到应用程序逻辑中

JPA 中可能有一些东西可以开箱即用地解决您的困境,但在幕后它将执行上述操作之一(不是选项 1,可能是选项 4)

【讨论】:

  • 我不完全确定我们是否相互理解:我想要 JPA 级联而不是 MySQL。你说的是对的,但我的问题是关于使用 JPA 级联的选项 4。
  • @PierreMardon 这很公平,但我猜您在 JPA 级别遇到的任何困惑都是由于您的级联在底层数据库级别上没有真正意义。我怀疑您的违规行为正在发生,因为您的 JPA 级联尝试在删除目标 A 之前级联删除相关的 B(因为这是级联通常的工作方式).. 它不能首先删除相关的 B,因为它被目标 A 引用. 你需要的并不是真正的级联删除,因为你想先删除目标A,然后再删除相关的B。
  • 是的,我清楚地知道设计很糟糕,但我必须处理它;)无论如何,有很多解决方法(只需在业务代码中手动进行级联),我的问题是为了加深我对 JPA 的理解;)
【解决方案4】:

如果你想删除B的对象,只要关联的A被删除(这是你愿望清单的第四点:

我想要一个级联删除,当a 被删除时删除相关的b

那么您需要将A 中的映射更改为:

@OneToOne(cascade = CascadeType.REMOVE, orphanRemoval = true)
@PrimaryKeyJoinColumn
private B b;

【讨论】:

  • 正如@Narendra 的回答评论中已经说明的那样,这不是我想要的,我的一些B 可以在没有A 的情况下存在,所以我不想要orphanRemoval。它也不能解决 ConstraintViolationException 提升的主要问题。
  • 我理解清楚吗 - 您不想要您在问题中指定的内容?
  • @Andronicus,您的解决方案是正确的。唯一遗漏的是表 a 中的 ON DELETE CASCADE。
  • @PierreMardon 请接受您的问题的答案并添加另一个具有正确规范的帖子,您只是在混淆其他人。
  • 我的问题现在没有正确的答案,也许我很快就会尝试 Alan Hay 的答案。您假设 orphanRemoval 是适用的,但这种非常具体的行为完全超出了这个问题的范围。
【解决方案5】:

你可以尝试在B类中添加以下内容

@OneToOne(mappedBy = "b", cascade = CascadeType.REMOVE)
private A a;

另外,如果在数据库中你只有一个外键“a 有一个到 b 的外键”,你也可以创建一个从 b 到 a 的外键。

【讨论】:

  • 也许我的问题的文字不够清楚,我只是编辑了它,但我想要从 B 到 A 的级联删除,而不是相反。我也不想改变我的架构。不过感谢您的关注。
  • 对不起,我在上面评论中的错误,我肯定想要从AB的级联,这意味着删除A应该删除关联的B
猜你喜欢
  • 1970-01-01
  • 2012-07-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-04-16
  • 1970-01-01
  • 2019-08-12
  • 2021-07-14
相关资源
最近更新 更多