Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to perform ALL Cascade operations (PERSIST, UPDATE, REMOVE) to multilevel child in JPA

I have 3 tables (Table 1, Table 2, Table 3). Table 1 is related to Table 2 by @onetomany using primary key. Table 2 is related to Table 3 by @manytoone. Table 2 has EmbeddedId.

When I get the details using Table 1's primary key, I am able to fetch the data in Table 2 and Table 3. But I can't do save and delete. Save and delete is happening on the Table 1's child table (ie Table 2), but does not impact Table 3(which is child to Table 2)

Below are the entity models of all the three tables

 @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
}

The problem I am facing is, I am not able to delete/save the entities of CfgData by passing FeatureMaster's primary id. Any operation I do is affecting only parent &child, not the grand child (CfgData) I tried a lot googling, but I cant find the solution.

like image 833
Vignesh Avatar asked Oct 16 '22 13:10

Vignesh


1 Answers

(There's an assumption here that the PK for CfgMaster is FGID - hope this right. If so ... I think I can explain what's happening, though solving it with the current table mappings is tricky)

It looks like the problem relates to the presence or otherwise of the insertable = false, updatable = false on the foreign keys.

The usual reason for using these if there are two properties on the same entity that map to the same column(s). Hibernate needs to know which of the properties to set the column value from, so at most one of the properties can be writeable.

It looks like this is the problem here, but twice:


Firstly on FeatureMaster, the parameters collection uses a foreign key join column of FGID. Because this is a @OneToMany this is actually a column on CFGMASTER, which is (assumed to be) already mapped by the id property, this second mapping needs to be read-only.

With this change, a cascade delete from FeatureMaster to CfgMaster started working:

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

Secondly on CfgMaster, the cfgData property is using read-only @JoinColumns. I would think the reason for this is the overlap on the (assumed) FGID column?

Unfortunately, because these are foreign key columns on CFGMASTER, this effectively makes the CfgMaster.cfgData property read-only too. E.g. switching to non-overlapping, writeable columns enabled the cascade delete here too:

@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;

This even worked for the grandchild cascade delete.

However, clearly this raises the next question - is there any way to have the PK of CfgData also be part of a two-part foreign key? I have seen examples where the foreign key was the primary key, but not previously with an extra column. Clearly you have managed it here, but the side-effect is that the relationship is read-only too, at least for cascades.

Although it's not what you'll be hoping to hear, this does make some sense from Hibernate's perspective. E.g. if the @ManyToOne property were to be null'ed, Hibernate would want to blank both columns, which is a problem for the primary key. Unless someone else knows better, I think the choice is to change the DB mappings, or if that's not an option, you'll need to code the cascade delete of the CfgData's.

like image 136
df778899 Avatar answered Oct 21 '22 07:10

df778899