首页 > 解决方案 > 如何在 JPA 中对多级子级执行所有级联操作(PERSIST、UPDATE、REMOVE)

问题描述

我有 3 个表(表 1、表 2、表 3)。表 1 通过 @onetomany 使用主键与表 2 相关联。表 2 与 @manytoone 的表 3 相关。表 2 具有 EmbeddedId。

当我使用表 1 的主键获取详细信息时,我能够获取表 2 和表 3 中的数据。但我无法保存和删除。表 1 的子表(即表 2)上发生保存和删除,但不影响表 3(表 2 的子表)

下面是所有三个表的实体模型

 @Entity
    @Table(name = "FEATUREMASTER")
    @JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})

    public class FeatureMaster implements Serializable {
        /**
         * 
         */
        private static final long serialVersionUID = 1L;

        @Id 
        @Column(name = "FGID")
        private String featureid;

        @Column(name = "FEATURENAME", nullable = false, unique = false)
        private String featurename;

        @Column(name = "DESCRIPTION", nullable = true, unique = false)
        private String description;

        @Column(name = "LIBNAME", nullable = true, unique = false)
        private String libname;

        @Column(name = "ISENABLED", nullable = false, unique = false)
        private String isenabled;

        @Column(name = "EDRULEGRP", nullable = true, unique = false)
        private String edrulegrp;

        // Do Not use - [orphanRemoval = true & CascadeType.ALL]- If used, deletion is not happening 
        @OneToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
        @JoinColumn(name = "FGID")
        private List<CfgMaster> parameters;

// Getters and Setters
}


    @Entity
    @Table(name = "CFGMASTER")
    public class CfgMaster implements Serializable {

        /**
         * 
         */
        private static final long serialVersionUID = 1L;

        @EmbeddedId
        private CfgMasterPK id;

        @Column(name = "CONFIGNAME", length = 45, nullable = true, unique = false)
        private String parameter_name;

        @Column(name = "CONFIGTYPE", length = 20, nullable = true, unique = false)
        private String type;

        @Column(name = "SUBPARAM", nullable = true, unique = false)
        private Integer subparam;

        @Column(name = "CONFIGDESCRIPTION", nullable = true, unique = false)
        private String description;

        @Column(name = "CONFIGLIMITFROM", nullable = true, unique = false)
        private String from;

        @Column(name = "CONFIGLIMITTO", nullable = true, unique = false)
        private String to;

        @ManyToOne(cascade = {CascadeType.ALL}, optional = true, fetch = FetchType.LAZY )
    //  @ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.LAZY)
        @NotFound(action=NotFoundAction.IGNORE) // This is required to handle when no CfgData is found
        @JoinColumns({
                @JoinColumn(name = "FGID", insertable = false, updatable = false),
                @JoinColumn(name = "DATAKEY", insertable = false, updatable = false) 
                })
        private CfgData cfgData;

//Getters and Setters
    }

@Entity
@Table(name = "CFGDATA")
public class CfgData implements Serializable {

    /**
     * 
     */
    private static final long serialVersionUID = 1L;


    /*@EmbeddedId
    private CfgDataPK id;*/

    @Id
    @Column(name = "FGID")
    private String fgid;

    @Id
    @Column(name = "DATAKEY")
    private String datakey;

    @Column(name = "EPID", nullable = false, unique = false)
    private int epid;


    @Column(name = "RESERVED1", length = 45, nullable = true, unique = false)
    private String reserved1;

    @Column(name = "VALUE1", length = 100, nullable = true, unique = false)
    private String value1;

    @Column(name = "VALUE2", length = 100, nullable = true, unique = false)
    private String value2;
     //Getters and Setters
}

我面临的问题是,我无法通过传递 FeatureMaster 的主 ID 来删除/保存 CfgData 的实体。我做的任何操作都只影响父母和孩子,而不是孙子(CfgData)我尝试了很多谷歌搜索,但我找不到解决方案。

标签: spring-bootjpa

解决方案


(这里假设 PKCfgMasterFGID- 希望这是正确的。如果是这样......我想我可以解释发生了什么,尽管用当前的表映射解决它很棘手)

看起来问题与insertable = false, updatable = false外键是否存在有关。

如果同一实体上有两个属性映射到同一列,则使用这些属性的通常原因。Hibernate 需要知道从哪个属性设置列值,因此最多可以有一个属性是可写的。

看起来这是这里的问题,但有两次:


首先FeatureMaster,该parameters集合使用 的外键连接列FGID。因为 this is a @OneToManythis 实际上是 上的一列CFGMASTER,它(假设是)已经被id属性映射,所以第二个映射需要是只读的。

通过此更改,级联删除 from FeatureMastertoCfgMaster开始工作:

@OneToMany(cascade = { CascadeType.ALL }, orphanRemoval = true)
@JoinColumn(name = "FGID", insertable = false, updatable = false)
private List<CfgMaster> parameters = new ArrayList<>();

其次CfgMaster,该cfgData属性使用的是只读的@JoinColumns。我认为其原因是(假设的)FGID列上的重叠?

不幸的是,因为这些是 上的外键列CFGMASTER,这实际上也使该CfgMaster.cfgData属性成为只读的。例如,切换到非重叠、可写列也启用了级联删除:

@ManyToOne(cascade = {
        CascadeType.ALL }, optional = true, fetch = FetchType.LAZY)
//  @ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.LAZY)
@NotFound(action = NotFoundAction.IGNORE) // This is required to handle when no CfgData is found
@JoinColumns({
        @JoinColumn(name = "FGID2"),
        @JoinColumn(name = "DATAKEY") })
private CfgData cfgData;

这甚至适用于孙子级联删除。

然而,显然这引发了下一个问题——有没有办法让 PKCfgData也成为两部分外键的一部分?我见过外键是主键的例子,但以前没有额外的列。显然你已经在这里管理它,但副作用是关系也是只读的,至少对于级联。

尽管这不是您希望听到的,但从 Hibernate 的角度来看,这确实有些道理。例如,如果@ManyToOne 属性为空,Hibernate 会希望将两列都清空,这对主键来说是个问题。除非其他人知道得更好,否则我认为选择是更改数据库映射,或者如果这不是一个选项,则需要编写级联删除的CfgData's.


推荐阅读