JPA - Cascade Delete failing - jpa

I have Table A ...
id:bigint - primary key NOT NULL
name:string
... and i have Table B that has a foreign key to table A ...
id:bigint - primary key NOT NULL
name:string
a_id:bigint - foreign key to table A.id NOT NULL
When i generate the jpa entities and jpacontrollers the foreign key looks like this in Table A entity
#OneToMany(mappedBy = "aId", cascade = CascadeType.ALL)
private List<B> bList;
and it looks like this in table B entity
#JoinColumn(name = "a_id", referencedColumnName = "id")
#ManyToOne(optional = false)
private A aId;
However when delete an entry from table A that has a linked entry in table B that should be cascade deleted, it hits an error in the table A jpacontroller destroy method complaining that it cannot remove the a_id from table B as the field is not null (i.e. it is trying to remove the link but preserve the record rather than cascade delete). The code in the destroy method in the jpa controller where it hits this error is here
List<B> bList = a.getBList();
for (B bListB : bList) {
bListB.setAId(null);
bListB = em.merge(bListB);
}
if i remove that code, the cascade delete works, but when the jpa controller is next regenerated i will lose my changes.
could someone advise what am i doing wrong here please.
thank you.

Related

There is a problem with deleting many to many association

#entity
#table(name = "users")
public class User {
#Id
private Long id;
private String name;
#ManyToMany(cascade = {PERSIST, MERGE})
#JoinTable(name = "user_following",
joinColumns = {#JoinColumn(name = "user_id")},
inverseJoinColumns = {#JoinColumn(name = "user_following_id")})
private List<User> following;
#ManyToMany(cascade = {PERSIST, MERGE})
#JoinTable(name = "user_followers",
joinColumns = {#JoinColumn(name = "user_id")},
inverseJoinColumns = {#JoinColumn(name = "user_follower_id")})
private List<User> followers;
//some other fields, getters and setters
public void follow(User user) {
following.add(user);
user.acceptFollower(this);
}
public void acceptFollower(User user) {
followers.add(user);
}
}
We have three tables:
users
user_following
user_followers
Imagine having this entity and you are implementing following & followers functionality.
We have to users - rick & tom.
User rick = new User(1, "rick");
User tom = new User(2, "tom");
rick.follow(tom);
After calling method - rick.follow(tom);
We will populate our tables in the following way
Table - "users"
id
name
1
rick
2
tom
Table - "user_following"
user_id
user_following_id
1
2
Table - "user_followers"
user_id
user_follower_id
2
1
So far, everything works just great but the problem starts when i try to delete users.
For example:
userRepository.deleteById(1)
JPA gives an error -
update or delete on table "users" violates foreign key constraint "fk1yg7xqw2kfx6n2196o0gr3obc" on table "user_followers"
Подробности: Key (id)=(1) is still referenced from table "user_followers".
JPA cannot delete because it generates a wrong sql.
Hibernate:
select
user0_.id as id1_5_,
user0_.first_name as first_na5_5_,
from
users user0_
where
user0_.username=?
Hibernate:
delete
from
user_followers
where
user_id=?
Hibernate:
delete
from
user_following
where
user_id=?
Hibernate:
delete
from
users
where
id=?
I want to delete user - rick whose id = 1
The error happens due to wrong sql.
When we delete user - 'rick',
we first delete 'rick' from "user_following" and "user_followers" tables.
Look at the this statement.
Hibernate:
delete
from
user_followers
where
user_id=?
The error happens due to this statement because the
user we are deleting corresponds to the (user_following_id) column of the "user_followers" table,
not "user_id" column.
The right sql statement must be:
Hibernate:
delete
from
user_followers
where
user_following_id=?
Is there a way how to fix this?
Do not create user_followers as a table, instead create it as a view on the table user_following. The view definition merely reverses the column names. I do not know your obscurification manager so I will just supply the needed SQL. (see demo).
create table users( id integer primary key
, name text
) ;
create table user_following( user_id integer
references users(id)
on delete cascade
, user_following_id integer
references users(id)
on delete cascade
, constraint user_following_pk
primary key (user_id,user_following_id)
) ;
create view user_followers as
select user_following_id as user_id
, user_id as user_follower_id
from user_following;

org.postgresql.util.PSQLException: ERROR: update or delete on table "skills" violates foreign key constraint on table "employee_has"

I am developing a CRUD application using Angular, SpringBoot and PostgreSQL. There I have two tables named "skills" and "employees" in the database. The table created by joining both "skills" and "employee" tables is "employee_has" table. They are mapped using Many-to-Many relationship. An employee can have many skills. A skill can have many employees.
I need the functionality as when I delete a skill in the "skill" table, it should be removed from employees' who have that skill. But When I delete a skill, it does not getting deleted form the skill table and the relationship does not get deleted in the "employee_has" table and gives the below error.
org.postgresql.util.PSQLException: ERROR: update or delete on table "skills" violates foreign key constraint "fkoq05nk3xfqd4rl68fdpt17vvc" on table "employee_has"
Detail: Key (id)=(5) is still referenced from table "employee_has".
Here is my code part for Skill model in the backend.
public class Skill {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#NotNull
#NaturalId
private String skill_name;
#JsonIgnore
#ManyToMany(fetch = FetchType.LAZY,
cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
},
mappedBy = "skills")
private List<Employee> employees = new ArrayList<>();
Here is my code part for Employee model in the backend.
#ManyToMany(fetch = FetchType.LAZY,
cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
})
#JoinTable(name = "employee_has",
joinColumns = { #JoinColumn(name = "emp_id") },
inverseJoinColumns = { #JoinColumn(name = "skill_id") })
private List<Skill> skills = new ArrayList<>();
Please help me to solve this issue.
This error occurs because you are trying to delete a "skill" that is a reference to the join table ("employee_has"). This can fix by the database side.
Instead of using,
PostgreSQL create script
CREATE TABLE IF NOT EXISTS public.employee_has
(
employee_id bigint NOT NULL,
skill_id bigint NOT NULL,
CONSTRAINT employee_has_pkey PRIMARY KEY (employee_id, skill_id),
CONSTRAINT fkam2psf41jwoy33ge3uvxep8tl FOREIGN KEY (skill_id)
REFERENCES public.skill (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE NO ACTION,
CONSTRAINT fkkd8xx37dlmjryoas0d91hri6c FOREIGN KEY (employee_id)
REFERENCES public.employee (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE CASCADE
)
Use this- Replacing "ON DELETE NO ACTION" to "ON DELETE CASCADE",
Fix it by replacing No Action to Cascade
CREATE TABLE IF NOT EXISTS public.employee_has
(
employee_id bigint NOT NULL,
skill_id bigint NOT NULL,
CONSTRAINT employee_has_pkey PRIMARY KEY (employee_id, skill_id),
CONSTRAINT fkam2psf41jwoy33ge3uvxep8tl FOREIGN KEY (skill_id)
REFERENCES public.skill (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE CASCADE,
CONSTRAINT fkkd8xx37dlmjryoas0d91hri6c FOREIGN KEY (employee_id)
REFERENCES public.employee (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE CASCADE
)

Child entity is not getting inserted/updated on parent entity update with onetomany

I am using #OneToMany annotation to save parent and child entities but I am facing issues while saving child entity in a particular case.
Child entity is getting saved in two cases:
During first insert of a parent with child.
During update of a parent with child when there was no child inserted/saved in database
in first insert
But When parent is inserted with child 1 and then during update of a parent I try to insert child 2 then I am not able to save the child 2
it is failing with below exception:
o.h.e.jdbc.spi.SqlExceptionHelper - ORA-01407: cannot update ("app_Name"."Child_entity"."REFERENCE_ID") to NULL\n
23:22:06.068 ERROR o.h.i.ExceptionMapperStandardImpl - HHH000346: Error during managed flush [org.hibernate.exception.ConstraintViolationException: could not execute statement]
Please see my code as below:
#Data
#Entity
#Table("Parent_table")
public class Parent_entity implements Serializable {
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval =true)
#JoinColumn(name="REFERENCE_ID")
private Set<Child_Entity> childrens ;
}
#Data
#Entity
#Table("child_table")
public class Child_entity implements Serializable {
#Id
#GeneratedValue(generator = "seq_gen", strategy = GenerationType.SEQUENCE)
#SequenceGenerator(name = "seq_gen", sequenceName = "child_SEQ",allocationSize=1)
#Column(name ="col_name")
private Integer asSeq;
#Column(name ="REFERENCE_ID")
private String referenceid;
}
In mapper class, I am explicitly setting primary key of the parent table.
Oracle database side I have below foreign key constraint added
ALTER TABLE child_table
ADD CONSTRAINT FK_parent_table
FOREIGN KEY (REFERENCE_ID)
REFERENCES Parent_table(REFERENCE_ID);
I have browsed through similar question on stackoverflow, which suggests that if you are using existing column for foreign key then existing values for that column should not be null.
But in my case column "REFERENCE_ID" is already non nullable.
Please let me know or suggest if I need to add something else to make it work.
Thank you.
Edit:
In update scenario, Hibernate is generating below query:
update child_table set reference_id=null where reference_id=? and child_seq=?
where reference_id is Parent's primary key and child_seq is Child's primary key
Any idea why hibernate is trying to update Parent's primary key
I am explicitly setting Parent Primary key's value in Child's entity
There are actually three problems here:
Apparently "update scenario" inserts two new children instead of keeping one and adding one.
Unidirectional OneToMany relationship with a non-nullable join column
Lombok-generated equals and hashCode
TL;TR: GOTO 2
1. Update scenario
Hibernate is trying to update reference_id to NULL because it wants to "detach" a child entity from the parent. That means, that during update, you are adding two new children instead of keeping one and adding one. I haven't seen the relevant piece of code of yours, but I assume it might look more or less like this:
ParentEntity parent = new ParentEntity();
parent.setId("test");
ChildEntity child1 = new ChildEntity();
child1.setReferenceid(parent.getId());
parent.setChildrens(new HashSet<>(Arrays.asList(child1)));
repository.save(parent);
ChildEntity child2 = new ChildEntity();
child2.setReferenceid(parent.getId());
parent.getChildrens().add(child2);
repository.save(parent);
It ends up with a ConstraintViolationException. In the second save call, child1 is still a "detached" instance,
its id is NULL and Hibernate treats both children as they were new. So first it adds them to the child_table and later tries to remove the "old" one,
by setting its referenceId to NULL (orphan removal hapens later, and is kind of unrelated).
It could be easily fixed:
// ...
parent = repository.save(parent); // <- save(parent) returns updated object
ChildEntity child2 = new ChildEntity();
child2.setReferenceid(parent.getId());
parent.getChildrens().add(child2);
repository.save(parent);
No exceptions anymore but it doesn't solve the problem. Sooner or later you are going to remove a child from the children set and
it will always result in an exception.
2. Unidirectional OneToMany relationship with a non-nullable join column
The canonical way of modeling it would be as follows:
ParentEntity
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
#JoinColumn(name = "REFERENCE_ID", nullable = false)
private Set<ChildEntity> childrens;
ChildEntity
#Column(name = "REFERENCE_ID", insertable = false, updatable = false)
private String referenceid;
It should work but Hibernate will generate unnecessary 'update' queries:
select parententi0_.parent_id as parent_i1_1_1_, childrens1_.reference_id as referenc3_0_3_, childrens1_.id as id1_0_3_, childrens1_.id as id1_0_0_, childrens1_.name as name2_0_ ...
select nextval ('child_seq')
select nextval ('child_seq')
insert into child_table (name, reference_id, id) values (?, ?, ?)
insert into child_table (name, reference_id, id) values (?, ?, ?)
update child_table set reference_id=? where id=?
update child_table set reference_id=? where id=?
delete from child_table where id=?
Not a big deal with one or two items, but with 100?
This happens because the ParentEntity is the
owner of the relationship (due to the #JoinTable annotation). It knows nothing about child_table foreign key and doesn't know how to deal with it.
2b. "Half of" bidirectional OneToMany relationship
Alternatively, we can try to make ChildEntity the owner of the relationship by removing #JoinColumn and adding mappedBy. In theory, there should be a corresponding #ManyToOne on the other side of the relationship, but it seems to work without it. This solution is optimal, might be not portable though (to different JPA providers or different Hibernate versions).
ParentEntity
#OneToMany(mappedBy = "referenceid", cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
private Set<ChildEntity> childrens;
ChildEntity (no changes, same as in the question)
#Column(name = "REFERENCE_ID")
private String referenceid;
On update Hibernate generates following queires:
select parententi0_.parent_id as parent_i1_1_1_, childrens1_.reference_id as referenc3_0_3_, childrens1_.id as id1_0_3_, childrens1_.id as id1_0_0_, childrens1_.name as name2_0_ ...
select nextval ('child_seq')
select nextval ('child_seq')
insert into child_table (name, reference_id, id) values (?, ?, ?)
insert into child_table (name, reference_id, id) values (?, ?, ?)
delete from child_table where id=?
3. Lombok-generated equals and hashCode
This is not directly related to your question but I think you will face this problem sooner or later. You are using #Data annotations (I assume they are Lombok's, if not, ignore this section). They will generate equals and hashCode methods from all the fields by default, including ids. It is fine in the ParentEntity, where the id is set manually. But in the ChildEntity, where the id (aka asSeq) is generated by the database, it breaks the hashCode()/equals() contract. It may lead to really sneaky bugs. From Hibernate documentation:
The issue here is a conflict between the use of the generated identifier, the contract of Set, and the equals/hashCode implementations. Set says that the equals/hashCode value for an object should not change while the object is part of the Set. But that is exactly what happened here because the equals/hasCode are based on the (generated) id, which was not set until the JPA transaction is committed.
You may want to read more about it here:
Implementing equals() and hashCode()
The JPA hashCode() / equals() dilemma

#UniqueConstraints not working on #JoinTable

I'm using Playframework 1.2.4 and PostgresSQL 9.1.2. I have the following entities: Recipe and RecipeItem. A Recipe has a set of RecipeItems. I've annotated the set of recipe items in the Recipe class as follows:
#Required
#MinSize(1)
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinTable( name = "RecipeItemForIngredients",
joinColumns = #JoinColumn(name = "recipeId"),
inverseJoinColumns = #JoinColumn(name = "recipeItemId"),
uniqueConstraints = #UniqueConstraint(name = "uq_recipeItemPerRecipe",
columnNames = {"recipeId", "recipeItemId"}))
public Set<RecipeItem> items = Sets.newHashSet();
But when I check the PgAdmin to see if the constraint has been applied to the RecipeItemForIngredients table I cannot find it. This is what PgAdmin shows.
CREATE TABLE recipeitemforingredients
(
recipeid bigint NOT NULL,
recipeitemid bigint NOT NULL,
CONSTRAINT recipeitemforingredients_pkey PRIMARY KEY (recipeid, recipeitemid),
CONSTRAINT fk5ac547a883708db FOREIGN KEY (recipeid)
REFERENCES recipe (id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT fk5ac547ad6e1da8f FOREIGN KEY (recipeitemid)
REFERENCES "recipe$recipeitem" (id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT recipeitemforingredients_recipeitemid_key UNIQUE (recipeitemid)
)
Does anyone have an idea why this could be happening? Maybe this annotation is not supported by the ORM used by Playframework.
This constraint doesn't make much sense, and you have a stronger constraint by default anyway:
CONSTRAINT recipeitemforingredients_recipeitemid_key UNIQUE (recipeitemid)
If recipeItemId is unique, then of course, the tuple (recipeId, recipeItemId) is also unique.
Moreover, since the PK of the table is (recipeId, recipeItemId), the constraint is already applied by the PK constraint.

Java EE 6 JPA 2 ManyToOne Relation Creates Invalid Foreign Key

I am trying to create two entities where both entities have embeddedIds. One of the entities have 2 references to the other entity, where both of those references are related as ManyToOne.
Example codes are written below;
#Embeddable
public class ItemPK {
#Column(nullable = false, length = 100)
private String itemId;
#Column(name = "item_client_id", nullable = false)
private int clientId;
...
}
#Entity
#Table(name = "item")
public class Item {
#EmbeddedId
private ItemPK id;
#ManyToOne
#JoinColumn(name = "item_client_id")
private Client client;
#OneToMany(mappedBy="item", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<RelatedItem> relatedItems;
#OneToMany(mappedBy="relatedItem", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<RelatedItem> relatedItemsRHS;
...
}
#Embeddable
public class RelatedItemPK {
#Column(name = "itemId", length = 100, nullable = false)
private String itemId;
#Column(name = "item_client_id", nullable = false)
private int clientId;
#Column(name = "relatedItemId", length = 100, nullable = false)
private String relatedItemId;
#Column(name = "related_item_client_id", nullable = false)
private int relatedItemClientId;
...
}
#Entity
#Table(name = "related_item")
public class RelatedItem {
#EmbeddedId
private RelatedItemPK id;
#ManyToOne(cascade = CascadeType.ALL, optional = false)
#JoinColumns({
#JoinColumn(name="itemId", referencedColumnName="itemId", insertable=false, updatable=false),
#JoinColumn(name="item_client_id", referencedColumnName="item_client_id", insertable=false, updatable=false)
})
private Item item;
#ManyToOne(cascade = CascadeType.ALL, optional = false)
#JoinColumns({
#JoinColumn(name="related_item_client_id", referencedColumnName="item_client_id", insertable=false, updatable=false),
#JoinColumn(name="relatedItemId", referencedColumnName="itemId", insertable=false, updatable=false)
})
private Item relatedItem;
...
}
The problem is while creating foreign keys for RelatedItem entity, I got an SQLException. It is the second ManyToOne relation that fails. The foreign key generation sql is below,
ALTER TABLE related_item ADD CONSTRAINT FK_related_item_related_item_client_id FOREIGN KEY (related_item_client_id, relatedItemId) REFERENCES item (item_client_id, itemId)
Since item table is indexed first by itemId then by item_client_id, this statement causes MySQL to produce an error.
I would like to switch the places of columns so that the SQL should look like the following,
ALTER TABLE related_item ADD CONSTRAINT FK_related_item_relatedItemId FOREIGN KEY (relatedItemId, related_item_client_id) REFERENCES item (itemId,item_client_id)
I tried changing the order of "JoinColumn"s but the result didn't change. I also tried renaming the fields to check if persistence provider choses the order by column name but again the result didn't change.
So, is there a way to enforce the column ordering?
p.s. I use following stuff:
MySQL 5.1
EclipseLink 2.0.0
Java EE 6
JPA 2
GlassFish v3
Edit: EclipseLink produces following SQL, which fails to run;
CREATE TABLE related_item (SIMILARITY DOUBLE, widget_id INTEGER NOT NULL, relatedItemId VARCHAR(100) NOT NULL, itemId VARCHAR(100) NOT NULL, related_item_client_id INTEGER NOT NULL, item_client_id INTEGER NOT NULL, PRIMARY KEY (widget_id, relatedItemId, itemId, related_item_client_id, item_client_id));
CREATE TABLE item (IMAGEURL VARCHAR(2048), STATUS VARCHAR(64), URL VARCHAR(2048), PRICE DOUBLE, STOCK INTEGER, DESCRIPTION TEXT(64000), NAME VARCHAR(255), ITEMID VARCHAR(100) NOT NULL, item_client_id INTEGER NOT NULL, PRIMARY KEY (ITEMID, item_client_id));
ALTER TABLE related_item ADD CONSTRAINT FK_related_item_itemId FOREIGN KEY (itemId, item_client_id) REFERENCES item (itemId, item_client_id);
ALTER TABLE related_item ADD CONSTRAINT FK_related_item_related_item_client_id FOREIGN KEY (related_item_client_id, relatedItemId) REFERENCES item (item_client_id, itemId);
ALTER TABLE item ADD CONSTRAINT FK_item_item_client_id FOREIGN KEY (item_client_id) REFERENCES client (ID);
Please include the stack trace. However, I strongly recommend you skip the #JoinColumn tags unless you have a VERY good reason for specifying the foreign keys yourself. By specifying the mappedBy attribute in one of the directions, JPA can figure out what to do by itself.
Java EE 6 and JPA put a lot of effort into enabling Convention over Configuration, which means that most of the time, things will work out of the box. It's desirable for you, the programmer because you have less boiler plate code to worry about, and it's desirable for the JPA and Jave EE container implementors because it gives them freedom to chose the best performing solutions. By declaring the foreign key relationships yourself, you rob both you and JPA of this advantage.
Edit: In fact, I suspect that both specifying mappedBy and specifying the #JoinTable could be the root cause of your problem. But I need to see the stack trace to tell for sure.
The order of the columns should not matter. If it does, then you could change the order in your index to match, or change the order you list your primary key in, or just use your scripts to generate your DDL.