There is a problem with deleting many to many association - postgresql

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

Related

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
)

JPA - Cascade Delete failing

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.

Hibernate postgres auto increment after manual insert

I have a basic spring application, with a simple entity. I have a flyway script, to create the postgres table, and add some starting data.
create table user (
id serial primary key,
username varchar (50) unique not null,
password varchar (150) not null
);
insert into user (id, username, password) values (1, 'name', 'somehashed');
insert into etc...
I've set up my entity as follows:
#Entity
#Table(name = "user")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", updatable = false, columnDefinition = "serial")
private Long id;
...
other fields, constructor, getters setters etc...
My problem is, that on start-up, the basic entities are persisted by flyway, but upon trying to save a new entity, hibernate tries to give it the ID 1, although it is already given to another one.
I tried it also with SEQUENCE strategy, the problem didn't get solved.
Ok, problem was that I specified explicitly the ID I wanted to give while the insert script, and I didn't let postgres do the magic...

How exactly work the #OneToMany JPA annotation in this example? Is it related to a table column or an entity class field?

I have 2 DB tables named respectivelly T_ACCOUNT and T_ACCOUNT_BENEFICIARY.
These tables have the following structure:
create table T_ACCOUNT (ID integer identity primary key, NUMBER varchar(9), NAME varchar(50) not null, CREDIT_CARD varchar(16), unique(NUMBER));
create table T_ACCOUNT_BENEFICIARY (ID integer identity primary key, ACCOUNT_ID integer, NAME varchar(50), ALLOCATION_PERCENTAGE decimal(5,2) not null, SAVINGS decimal(8,2) not null, unique(ACCOUNT_ID, NAME));
And the T_ACCOUNT table is bound to the T_ACCOUNT_BENEFICIARY table with a one to many relationship, this is the graphical representation:
So this is the first class named Account that map the T_ACCOUNT table:
#Entity
#Table(name="T_ACCOUNT")
public class Account {
#Id
#GeneratedValue
#Column(name="id")
private Long entityId;
#Column(name="NUMBER")
private String number;
#Column(name="NAME")
private String name;
#OneToMany
#JoinColumn(name="ACCOUNT_ID")
private Set<Beneficiary> beneficiaries = new HashSet<Beneficiary>();
#Column(name="CREDIT_CARD")
private String creditCardNumber;
// GETTERS & SETTERS
}
And this is the Beneficiary class that map the T_ACCOUNT_BENEFICIARY table:
/**
* A single beneficiary allocated to an account. Each beneficiary has a name (e.g. Annabelle) and a savings balance
* tracking how much money has been saved for he or she to date (e.g. $1000).
*/
#Entity
#Table(name="T_ACCOUNT_BENEFICIARY")
public class Beneficiary {
#Id
#GeneratedValue
#Column(name="ID")
private Long entityId;
#Column(name="NAME")
private String name;
#Embedded
#AttributeOverride(name="value",column=#Column(name="ALLOCATION_PERCENTAGE"))
private Percentage allocationPercentage;
#Embedded
#AttributeOverride(name="value",column=#Column(name="SAVINGS"))
private MonetaryAmount savings = MonetaryAmount.zero();
As you can see into the Account I have the beneficiaries field that implement the one to may relationship
#OneToMany
#JoinColumn(name="ACCOUNT_ID")
private Set<Beneficiary> beneficiaries = new HashSet<Beneficiary>();
I know that, on the DB, this relationship is implemented by the ACCOUNT_ID field of the T_ACCOUNT_BENEFICIARY table (so multiple row of the T_ACCOUNT_BENEFICIARY table can have the same value of the ACCOUNT_ID field and this means that a single row of the T_ACCOUNT table can be associated to more than one rows of T_ACCOUNT_BENEFICIARY table).
As you can see in the previous sippet there is the #JoinColumn(name="ACCOUNT_ID") annotation.
My doubt is generated by the fact that I have an ACCOUNT_ID column on my T_ACCOUNT_BENEFICIARY table, infact:
create table T_ACCOUNT_BENEFICIARY (ID integer identity primary key, ACCOUNT_ID integer, NAME varchar(50), ALLOCATION_PERCENTAGE decimal(5,2) not null, SAVINGS decimal(8,2) not null, unique(ACCOUNT_ID, NAME));
but this column seems to not be mapped on the Beneficiary that map this T_ACCOUNT_BENEFICIARY table.
So my doubts is: the #JoinColumn(name="ACCOUNT_ID") is working at relational level performing the join operation on the ACCOUNT_ID column of the table mapped by the Beneficiary entity (T_ACCOUNT_BENEFICIARY) or am I missing something? How exactly is performed this join?
If my interpretation is right can I work at entity level and say to join the beneficiaries field of my Account entity class to a new accountId field inserted into my Beneficiary entity class and mapping the ACCOUNT_ID column of the T_ACCOUNT_BENEFICIARY table?
Tnx
It seems is a Unidirectional OneToMany relationship
In JPA 2.0 a #JoinColumn can be used on a OneToMany to define the foreign key
I'm not sure if I understand your question. But what you have done with your #JoinColumn annotation is correct and Hibernate will execute appropriate SQL statements when you have multiple beneficiaries for your account. For example executing multiple INSERTS if you have two Beneficiaries for an Account. And yes using the #JoinColumn annotation is at the hibernate level. If you want to access an Account from a Beneficiary entity you would need to define a Bidirectional relationship in the Beneficiary class like below.
#Entity
#Table("T_ACCOUNT_BENEFICIARY")
public class Beneficiary {
#ManyToOne(mappedBy = "beneficiaries")
Account account;
...
}

JPQL Query working in testing, not in production

I have two Entities related by a ManyToMany and I want to select them via a named Query. This works in my test (with a H2 DB set up) and throws exceptions at runtime (with postgresql set up). Other than the H2 and PG I am hard pressed to find differences between test and production.
The Entities and the Query look like so (abbreviated):
#Entity(name = "Enrichment")
#Table(name = "mh_Enrichment")
NamedQueries({
#NamedQuery(name = "findByLink",
query = "SELECT e FROM Enrichment e INNER JOIN e.links l WHERE l.link in (:links)") })
public class EnrichmentImpl {
#Id
#Column(name = "enrichmentId")
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#ManyToMany
#JoinTable(name = "mh_EnrichmentLinks", joinColumns = { #JoinColumn(name = "EnrichmentId",
referencedColumnName = "enrichmentId") }, inverseJoinColumns = { #JoinColumn(name = "Link",
referencedColumnName = "link") })
private List<Link> links;
}
#Entity(name = "Link")
#Table(name = "mh_enrichment_link")
public class LinksImpl {
#Id
#Column(name = "link", length = 1024)
private String link;
}
Upon running the query with a String value in production I get:
Internal Exception: org.postgresql.util.PSQLException: ERROR: operator does not exist: character varying = bigint
Hinweis: No operator matches the given name and argument type(s). You might need to add explicit type casts.
Position: 215
Error Code: 0
Call: SELECT t1.enrichmentId FROM mh_enrichment_link t0, mh_EnrichmentLinks t2, mh_Enrichment t1 WHERE ((t0.link IN (?)) AND ((t2.EnrichmentId = t1.enrichmentId) AND (t0.link = t2.Link)))
Any ideas what's wrong? It is the query, isn't it?
The query is supposed to retrieve a list of Enrichments that are related to the given link.
Update #1
As requested: the tables in the DB look as follows:
For entity Link
CREATE TABLE mh_enrichment_link
(
link character varying(1024) NOT NULL,
CONSTRAINT mh_enrichment_link_pkey PRIMARY KEY (link)
)
For entity Enrichment
CREATE TABLE mh_enrichment
(
enrichmentid bigint NOT NULL,
CONSTRAINT mh_enrichment_pkey PRIMARY KEY (enrichmentid)
)
For the relation (See answer, this was where it went wrong)
CREATE TABLE mh_enrichmentlinks
(
link character varying(1024) NOT NULL,
CONSTRAINT mh_enrichment_link_pkey PRIMARY KEY (link)
)
The issue was fixed by dropping all related tables and having JPA regenerate them. Table definitions didn't match Entity definitions.
Thats also the quite obviously the reason why the test worked and the production didn't. In testing the tables are generated on runtime, in production they existed already (with an outdated definition).
Side note: The query is correct and does what it should.