Spring JPA OneToOne FK as PK Cascade.Remove - java

I've got two tables, b and a:
they have a one-to-one bidirectional relationship
a has a foreign key to b that defines this relationship
this foreign key is also considered as a primary key for a, and a JPA #ID
I want a cascade removal that deletes the related b when a is deleted
in MySQL, a's b_id is NOT NULL
The problem is that when I delete my A object with JPA repository, I get a ConstraintViolationException on its foreign key.
I would expect that both a and b rows are deleted (cleverly starting with a's one).
How could I work around this knowing that I want to keep:
my DB schema the same
the cascade removal from a to b
the b id being the JPA #Id for a
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;
}
[EDIT] After all discussions in answer comments and re-reading my question, the proposals with orphanRemoval indeed are in scope and work.

If you want to delete object of B, whenever the associated A is deleted (it's the fourt point of your wishlist:
I want a cascade removal that deletes the related b when a is deleted
then you need to change your mapping in A to:
#OneToOne(cascade = CascadeType.REMOVE, orphanRemoval = true)
#PrimaryKeyJoinColumn
private B b;

In terms of just the MySQL side of your implementation, the records in table B have no 'knowledge' of any record in table A. In the database the relationship is unidirectional
The native cascade functionality exists to prevent foreign key errors, by telling the DB what to do when deleting a record would leave a foreign key pointing nowhere. Deleting a table A record would not cause a foreign key error in any table B records, so any native cascade functionality would not be triggered
To reiterate; You cannot keep the schema the same, and the cascade removal from a to b, because you don't actually have the cascade removal from a to b
You also mentioned in the comments that some table B records can exist without a table A records which isn't in the original question
To obtain the automatic deletion of table B records you describe, you have a few options with regards to the DB:
Swap the relation over - Remove the current foreign key and add a nullable foreign key column in table B that references the primary key of table A. You can then put a cascade delete on this foreign key. Keep the new column null for the table B records that do not 'belong' to a table A record. You could also add a unique index to this column to secure a one to one relationship
Add a DB trigger - On deletion of a table A record, add a DB trigger that removes the referenced table B record
Add a DB procedure - Add a procedure that deletes a table A record and then the referenced table B record in turn, probably within a transaction. Going forwards, only delete table A records using the procedure
Don't solve the problem at the DB level - Basically the same as option 3, but move the procedure logic out of the DB layer into the application logic
There may be something in JPA that solves your dilemma out of the box, but under the hood it will be doing one of the above (not option 1 and probably option 4)

In order to achieve what you have asked, I have tweaked your tables as follows:
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 wasn't added in your DDL.
This will enable cascade delete. To delete the b record on deletion of a I made following changes in class 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;
}
Find link here to the working solution.

Can you try in class B to add the following
#OneToOne(mappedBy = "b", cascade = CascadeType.REMOVE)
private A a;
In addition, if in the database you have only a foreign key "a has a foreign key to b" can you also make a foreign key from b to a as well.

#OneToOne(mappedBy = "b",cascade = CascadeType.ALL,fetch = FetchType.LAZY,orphanRemoval=true )
private A a;

Related

Why isn't the foreign key field of a Many-to-One relationship being set on insert?

My Spring web application allows users to update "Employee" records to change the fields or add new "Phone" records related to this "Employee" record. However, when the "Employee" record is submitted for update after adding a new "Phone" record, it's throwing a SQL error exception.
The problem is that the "employee_id" foreign key on "Phone" table to the "Employee" table isn't set in the eventual SQL insert statement submitted to the database. However, in the "PhoneEntity" JPA entity object that is referenced by the updated/merged "EmployeeEntity" object, the property associated with the employee_id database field isn't null, it's set to the "EmployeeEnity" Object being updated/merged.
From my understanding of JPA, having the entity property associated with a database field should set it when the insert statement for the entity's record is submitted to the database, but in this case it isn't which is causing this error.
I've tried stepping through with a debugger, and I have verified that the created PhoneEntity object is a member of EmployeeEntity's phones property, and that the same PhoneEntity's employee property is set to the same EmployeeEntity object (with the same object IDs) in a bidirectional relationship.
I've also set the hibernate.show_sql=true to see the SQL statement being submitted to the database and it includes the statement (with the ellipses being more fields):
Hibernate:
insert
into
phone
(id, employee_id, ...)
values
(?, ?, ...)
Which means that it is inserting a new phone for the new PhoneEntity object.
After trying to running this insert statement it gives the SQL error "Column 'employee_id' cannot be null". However like I said before, I've checked with the debugger and the employee property is indeed set to the EmployeeEntity object.
this is a simplified example of what my code looks like:
#Entity
#Table(name = "employee")
public class EmployeeEntity implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", unique = true, nullable = false)
private Integer id;
#OneToMany(mappedBy="employee", cascade = {CascadeType.PERSIST})
private Set<PhoneEntity> phones = new HashSet<>();
...
}
#Entity
#Table(name = "phone")
public class PhoneEntity implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", unique = true, nullable = false)
private Integer id;
#ManyToOne
#JoinColumn(name = "employee_id", nullable = false)
private EmployeeEntity employee;
...
}
With tables that have the structure created by the following SQL statements.
CREATE TABLE employee (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
...
);
CREATE TABLE phone (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
employee_id INT NOT NULL,
...
FOREIGN KEY(employee_id) REFERENCES employee(id)
);
And the following is where it actually submits the updates to the entity manager to make updates to the database.
public void update(EmployeeDomain employee) {
EmployeeEntity entity = employeeDomainToEntity.transform(employee)
getEntityManager().merge(entity);
}
The EmployeeEntity and PhoneEntity objects are created by converting similar domain objects that were in turn deserialized from a http request. I'd include more of this section of the code but, as I've mentioned, I've already confirmed with my debugger that the actual entity objects being submitted to the merge are already in the form that we expected with the phones fields and employee fields being set correctly, so the end entities should be correct.
In the official JPA specification document (version 2.1) in section "3.2.7.1 Merging Detached Entity State" (page 85) we find:
For all entities Y referenced by relationships from X having the cascade element value cascade=MERGE or cascade=ALL, Y is merged recursively as Y'. For all such Y referenced by X, X' is set to reference Y'. (Note that if X is managed then X is the same object as X'.)
This explains that you are lacking cascade=MERGE for the annotation of the phones field.
As proposed in thanh ngo's answer, the aforementioned definition (or: explanation) thus translates to:
#OneToMany(mappedBy="employee", cascade = {CascadeType.PERSIST, CascadeType.MERGE})
private Set<PhoneEntity> phones = new HashSet<>();
Alternatively, you could also make use of cascade=CascadeType.ALL. However, this would also include operations such as CascadeType.REMOVE which might not always be intended.
Hope it helps.
I think the problem is that you are using merge.
The cascade type setting for the entity should be:
#OneToMany(mappedBy="employee", cascade = {CascadeType.PERSIST, CascadeType.MERGE})
private Set<PhoneEntity> phones = new HashSet<>();

JPA/Hibernate using shared id and cascade=all tries to save child before parent, causing foreign key violation

I am using JPA with hibernate. I have a 1-to-1 parent child relationship (the child is optional), with the id shared between the two and a foreign key relationship on the child table. My entities look like this:
parent:
public class LogItemEntity {
...
#OneToOne(cascade={CascadeType.ALL}, mappedBy = "logItem", orphanRemoval=true, optional=true)
#PrimaryKeyJoinColumn(referencedColumnName="ral_id")
private LogAdditionalRequirement additionalRequirement;
...
}
child:
public class LogAdditionalRequirement {
...
#Id
#GeneratedValue(generator = "foreign")
#GenericGenerator(name = "foreign", strategy = "foreign", parameters = { #Parameter(name = "property", value = "logItem") })
#Column(name = "ral_id")
private Long id;
#OneToOne(optional=false)
#PrimaryKeyJoinColumn(referencedColumnName="id")
private LogItemEntity logItem;
...
}
When inserting a new object, the id for the parent is generated from a sequence and the cascade operation copies it onto the child. But the sql insert for the child is placed in the action queue of the session before the sql insert for the parent, and so it fails with a constraint violation on the foreign key:
ERROR o.h.util.JDBCExceptionReporter - ERROR: insert or update on table "rar_log_additional_requirement" violates foreign key constraint "fk_rar_ral_id"
Detail: Key (ral_id)=(70150) is not present in table "ral_log".
So how can I make the insert of the parent happen first?
This must be a pretty common usage, so I assume I'm doing something wrong, but I don't see what it is. I originally had the mappedBy attribute on the child side. I think that's wrong, but swapping it round made no difference.
One solution could be to remove the cascade "cascade={CascadeType.ALL}"
More on this subject here

Hibernate Exception: Missing Column (column exists)

Okay, so within the database we have a table called distributionCompanies, created like so:
CREATE TABLE `distributionCompanies` (
`distributionCompanyID` INT(11) NOT NULL,
`distributionCompanyName` VARCHAR(255) NOT NULL,
PRIMARY KEY (distributionCompanyID)
);
I'm trying to map this table to a class using Hibernate:
#Entity
#Table(name = "distributionCompanies")
public class DistributionCompany implements DatabaseObject {
#Id
#GeneratedValue
#Column(name = "distributionCompanyID", length = 11, unique = true, nullable = false)
private int distributionCompanyID;
....
However, when running, I hit this issue:
Initial SessionFactory creation failedorg.hibernate.HibernateException: Missing column: distributionCompanyID_distributionCompanyID in database2.distributionCompanies
This isn't the only table in the database, and I've managed to map other classes successfully using the same method, so I'm a little stumped as to why this is causing an issue.
Thank you for your time,
Samuel Smith
EDIT: In response to Xavi's comment, I temporarily removed another mapping for the column, and the error went away, so the bad-egg probably lays in the following code:
#ManyToOne(targetEntity = DistributionCompany.class)
#JoinTable(name = "distributionCompanies", joinColumns = { #JoinColumn(name = "distributionCompanyID", nullable = false) })
private int distributionCompanyID;
Hibernate is looking for a column named distributionCompanyID_distributionCompanyID in your distributionCompanies table.
This is probably due to a ToOne association mapping towards this table without #JoinColum.
From Hibernate Documentation:
The #JoinColumn attribute is optional, the default value(s) is like in one to one, the concatenation of the name of the relationship in the owner side, _ (underscore), and the name of the primary key column in the owned side. In this example company_id because the property name is company and the column id of Company is id.
If you've got a #ManyToOne or #OneToOne association mapping in another entity, this would explain why Hibernate is looking for such a column.
EDIT Seeing the association mapping you posted, it looks like it should be:
#ManyToOne(targetEntity = DistributionCompany.class)
#JoinColumn(name = "distributionCompanyID")
private DistributionCompany distributionCompany;
The #JoinTable annotation is used to specify a join table (that means an intermediate table used to model many-to-many associations). And the point of mapping an association would be to dispose of the mapped object instance (in this case a DistributionCompany, not just a distributionCompanyId).

Hibernate/JPA - Delete table with #OneToMany relationship (one parent- 0 or more child of the same type) violates foreign key constraint

I have a problem when trying to delete a table with #OneToMany relationship to table created
from the same java class using
Query q = getEntityManager().createQuery("DELETE FROM " + entityClass.getSimpleName());
q.executeUpdate();
Here's the table:
CREATE TABLE p_data_group
(
data_group_id bigint NOT NULL,
description character varying(350),
description_eng character varying(350),
multiplicity_max integer,
multiplicity_min integer,
name character varying(35),
name_db character varying(30),
name_eng character varying(35),
name_xml character varying(35),
path character varying(1024),
"position" integer,
is_root boolean,
parent_group_id bigint,
CONSTRAINT p_data_group_pkey PRIMARY KEY (data_group_id ),
CONSTRAINT fk_data_group_parent_group_id FOREIGN KEY (parent_group_id)
REFERENCES p_data_group (data_group_id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
)
WITH (
OIDS=FALSE
);
ALTER TABLE p_data_group
OWNER TO postgres;
And here's the part of the class that represents relations:
#ManyToOne
#JoinColumn(name = "parent_group_id", insertable = false, updatable = false)
#XmlTransient
public DataGroup getParentDataGroup() {
return parentDataGroup;
}
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "parent_group_id")
#ForeignKey(name = "fk_data_group_parent_group_id")
#Index(name = "idx_data_group_parent_group_id")
#LazyCollection(LazyCollectionOption.FALSE)
#XmlElementWrapper
public List<DataGroup> getChildDataGroups() {
return childDataGroups;
}
Now when trying to delete to root table all others should be deleted as well according to CascadeStyle.ALL annotation :
PSQLException: ERROR: update or delete on table "cpdm01" violates foreign key constraint "fk_cpdm01_kodpkd_cpdm01_id" on table
"cpdm01_kodpkd" Detail: Key (cpdm01_id)=(100) is still referenced
from table "cpdm01_kodpkd".
Am I right here?
As per your Error log shows
DETAIL: Key (cpdm01_id)=(100) is still referenced from table "cpdm01_kodpkd".
There is still a record referencing to cpdm01_id 100. You might delete record in cpdm01_kodpkd with cpdm01_id = 100 before , but there might be others as well.
You have to delete all records in cpdm01_kodpkd referencing to cpdm01_id 100 in main_order.
If not, the database protects you from doing harm to your data.

Hibernate makes insert twice and results in unique key constraint violation

hi I'm getting this "javax.persistence.PersistenceException: org.hibernate.exception.ConstraintViolationException: ERROR: duplicate key value violates unique constraint"
I have two tables A and B,
A has id, b_id, B has id,A_id's.
A - oneToMany , B - manyToOne relationship.
on A,
#OneToOne(cascade = { CascadeType.ALL })
#JoinColumn(name = "LATEST_VERSION_ID") #Valid
#EntityProperty(type = "GuidKey", relation = "B.id")
on B,
#ManyToOne(cascade = {CascadeType.ALL})
#JoinColumn(name = "A_ID") #Valid
#EntityProperty(type = "Key", relation = "A.id")
when I create Page A I was able to Do so, But when I try to update I get unique constraint violation on table 'B'.
It says the record already exist.
You have to make a bi-directional relation using the mappedBy property.
See:
one to one bidirectional hibernate mapping
http://www.mkyong.com/hibernate/hibernate-one-to-one-relationship-example-annotation
http://www.codereye.com/2009/04/hibernate-bi-directional-one-to-one.html
Also, #EntityProperty isn't required for this. The foreign key should be in one table in one-to-one.

Categories

Resources