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.
2 Answers
Answers 1
(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.
Answers 2
Problem Description
the issue is rooted from where the deleted instance is re-persisted if a PERSIST
operation is applied to it. A common mistake is to ignore that a PERSIST
operation has been applied to a removed instance (usually, because it is cascaded from another instance at the flush time), because the section 3.2.2 of the JPA specification mandates that such instance is to be persisted again in such a case.
In your example, this phenomenon can be seen where you defined a @ManyToOne
association from CfgMaster
to CfgData
. In this case, if the removed CfgData
is referenced by a CfgMaster
, the PERSIST
operation is cascaded from CfgMaster
to CfgData
because the association is marked with cascade = CascadeType.ALL
and the deletion will be unscheduled.
- To verify that this is happening, you can enable trace log level for the
org.hibernate
package and search for entries such as un-scheduling entity deletion.
Alternative workaround
CascadeType.ALL
should not be used on@ManyToOne
as the state transitions of entities should be propagated from Parent entities (the owner side) to Child ones, so as@ManyToOne
is always the Child association, cascading on it should be avoided.
You should set CascadeType.ALL
from the @ManyToOne
association to its corresponding @OneToMany
which mappedBy
is also already set on it.
More Info:
0 comments:
Post a Comment