Soft delete but cannot prevent delete cascade with foreign key table - spring

My entity user with #SQLDelete
#Data
#Entity
#Table(name = "`user`")
#SQLDelete(sql = "UPDATE `user` SET status = 0 WHERE id = ?")
public class User {
private Integer id;
private String username;
private String password;
#ManyToMany
#JoinTable(
name = "`user_role`",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "role_id"))
private List<Role> roles = new ArrayList<>();
}
When I delete by method userRepository.deleteAllById(ids). My row user_role table also deleted. I only want soft delete a row at user table and no effect to user_role

Related

ManyToMany relation use in service

Job entity
#Column(name = "name")
private String name;
#ManyToMany
#JoinTable(name = "user_job",
joinColumns = #JoinColumn(name = "job_id"),
inverseJoinColumns = #JoinColumn(name = "user_id")
)
private List<User> user;
User entity
#Column(name = "email")
private String email;
#ManyToMany
#JoinTable(name = "user_job",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "role_id")
)
private Set<Role> roles;
Role entity
#Column(name = "name")
private String name;
#ManyToMany(mappedBy = "roles")
private Set<User> users;
Here we have a table user_job with 3 ids and I want to insert data in service layer. How I can do it and what repository I should implement or use existent like user/role/job?
class UserJobService{
public void setUserJob(User user, Job job, Role role){
}
}
The problem with #ManyToMany association is you can't delete a record directly from user_job table, using Hibernate. To delete the record, you need to load a user with all his jobs. So better to add UserJobEntity
#Entity
#Table(name = "USER_JOBS")
class UserJobEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "USER_ID")
private User user;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "JOB_ID")
private Job job;
}
public UserJobEntity createUserJob(UserEntity user, JobEntity job) {
UserJobEntity userJob = new UserJobEntity();
userJob.setUser(user);
userJob.setJob(job);
return userJobRepository.save(userJob);
}
Probably you will want to add (user, job) unique constraint to user_jobs table.
Some advices
Use plurals for table names. user_jobs in place of user_job
Role is tabular data. So it shouldn't have a users List.
Don't use Set for associated collections. Definitely you will encounter "multiple bugs fetch exception" and this exception will help you to change queries. With Set you can have large cross products and even don't notice them.

Cascade.Type = ALL does not delete all "child rows" before trying to delete its own row

I'm trying to delete a user but every row in the database that references the user does NOT get deleted before hibernate tries to remove the user.
I have the same structure for every other entity in the application and it works just fine, all the child rows get deleted first and then the row itself get deleted, but as you see below this is not the case when trying to delete a user. Hibernate goes to this statement :
Hibernate: delete from users where user_id=?
before all comment_votes are deleted. (Posts should also be deleted before as well but I guess the comment_votes error shows up first).
This sequence of sql statements are executed before the error according to the console:
Hibernate: delete from comment_vote where vote_id=?
Hibernate: delete from comment where comment_id=?
Hibernate: delete from comment_vote where vote_id=?
Hibernate: delete from comment where comment_id=?
Hibernate: delete from comment_vote where vote_id=?
Hibernate: delete from comment where comment_id=?
Hibernate: delete from users where user_id=?
This is the error I'm getting:
org.postgresql.util.PSQLException: ERROR: update or delete on table "users" violates foreign key constraint "fkjf73ixvt1jv3wdv4ah0hkpewf" on table "comment_vote"
Detail: Key (user_id)=(2) is still referenced from table "comment_vote".
User.java :
#Entity
#Table(name = "users") // because User is a keyword in some DBs
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "user_id", columnDefinition = "serial")
private Long id;
#NotEmpty
#Column(unique = true)
private String username;
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
#JsonIgnore
private List<Post> posts = new ArrayList<>();
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
#JsonIgnore
private List<Comment> comments = new ArrayList<>();
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
#JsonIgnore
private List<CommentVote> comment_votes = new ArrayList<>();
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
#JsonIgnore
private List<PostVote> post_votes = new ArrayList<>();
// getters and setters
}
This is CommentVote.java :
#Entity
#Table(name = "comment_vote")
public class CommentVote {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "vote_id", columnDefinition = "serial")
private Long id;
#Min(value = -1, message = "A vote can not be less than -1")
#Max(value = 1, message = "A vote can not be greater than 1")
#Column(name = "actual_vote")
private int actualVote;
#ManyToOne()
#JoinColumn(name="user_id", nullable=false)
#JsonIgnore
private User user;
// getters and setters
}
I tried with orphanRemoval = true on every child field in User.java but that does not seem to change anything.
You can try to use #OnDelete. As it's stated in the documentation:
... the #OnDelete cascade is a DDL-level FK feature which allows you to remove a child record whenever the parent row is deleted.
So, when annotating the #ManyToOne association with #OnDelete(action = OnDeleteAction.CASCADE), the automatic schema generator will apply the ON DELETE CASCADE SQL directive to the Foreign Key declaration.
Taken this in mind, you can correct your mapping in the following way:
#Entity
#Table(name = "comment_vote")
public class CommentVote {
// ...
#ManyToOne()
#JoinColumn(name="user_id", nullable=false)
#OnDelete(action = OnDeleteAction.CASCADE)
#JsonIgnore
private User user;
// ...
}

In Spring Boot 2, is it possible to auto-generate a JoinTable with a unique constraint?

I'm using Spring Boot 2 and PostGres 10. I have created the following entities, Roles and Privileges,
#Data
#Entity
#Table(name = "Roles")
public class Role {
public enum Name {
USER,
ADMIN
}
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private UUID id;
#Column(unique=true)
#Enumerated(EnumType.STRING)
private Name name;
#ManyToMany
#JoinTable(
name = "roles_privileges",
joinColumns = #JoinColumn(
name = "role_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(
name = "privilege_id", referencedColumnName = "id"))
private Collection<Privilege> privileges;
}
Privilege is
#Data
#Entity
#Table(name = "Privileges")
public class Privilege {
public enum PrivilegeName {
SUPER_ADMIN,
USER
}
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private UUID id;
#Column(unique=true)
#Enumerated(EnumType.STRING)
private PrivilegeName name;
#ManyToMany(mappedBy = "privileges")
private Collection<Role> roles;
}
Then I have this in my application.properties
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect
spring.jpa.hibernate.ddl-auto=update
spring.jpa.hibernate.show-sql=true
spring.datasource.url=jdbc:postgresql://${PG_DB_HOST:localhost}:5432/${PG_DB_NAME}
spring.datasource.username=${PG_DB_USER}
spring.datasource.password=${PG_DB_PASS}
When my tables are generated, the roles_privileges table is generated like so ...
cardmania=# \d roles_privileges ;
Table "public.roles_privileges"
Column | Type | Modifiers
--------------+------+-----------
role_id | uuid | not null
privilege_id | uuid | not null
Foreign-key constraints:
"fk5duhoc7rwt8h06avv41o41cfy" FOREIGN KEY (privilege_id) REFERENCES privileges(id)
"fk629oqwrudgp5u7tewl07ayugj" FOREIGN KEY (role_id) REFERENCES roles(id)
Are there any other annotations or other Java code I can add so that when my join table is created, the role_id/privilege_id has a unique key generated?
To force Hibernate to create a primary key with both columns, you have to change Collection by Set
public class Role {
#ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinTable(
name = "roles_privileges",
joinColumns = #JoinColumn(
name = "role_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(
name = "privilege_id", referencedColumnName = "id"))
private Set<Privilege> privileges;
}
And:
public class Privilege {
#ManyToMany(mappedBy = "privileges")
private Set<Role> roles;
}
You will not see a unique constraint but you will see the following table
create table roles_privileges (
role_id binary not null,
privilege_id binary not null
)
But you can explicitly declare your unique constraint
#ManyToMany
#JoinTable(
name = "roles_privileges",
joinColumns = #JoinColumn(name = "role_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(
name = "privilege_id", referencedColumnName = "id"),
uniqueConstraints = #UniqueConstraint(columnNames = {"role_id",
"privilege_id"},
name = "rolesPrivilegesConstraint")
)
private Collection<Privilege> privileges;

How to store userid, roleid and privilegeid in single table using #JoinTable?

Requirement : Create table using #JoinColumn annotation in User entity class, which contain columns like, user_id, authority_id(means role), privilege_id.
Condition: Authority and Privileges also have one to many relationship.
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "pm_authority_privilege",
joinColumns = #JoinColumn(name = "authority_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "privilege_id", referencedColumnName = "id"))
private Set<Privilege> privileges = new HashSet<>();
As per above configuration i got privilege as part of authority json.
Issue : I'm confused what should be variable type if i take two value in 'inverseJoinColumns'.
When i try to do same thing using following way it gives error of transactional object because privilege is exist on two place.
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "pm_user_authority_privilege",
joinColumns = #JoinColumn(name = "user_id", referencedColumnName = "id"),
inverseJoinColumns = {
#JoinColumn(name = "authority_id", referencedColumnName = "id")
// #JoinColumn(name = "privilege_id", referencedColumnName = "id")
})
private Set<Authority> authorities = new HashSet<>();
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "pm_user_authority_privilege",
joinColumns = #JoinColumn(name = "user_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "privilege_id", referencedColumnName = "id"))
private Set<Privilege> privileges = new HashSet<>();
Note : There is page which have no. of roles and all relevant privilege of those role.(handle using code define in issue)
And from that i select some of role and save them when user created.
{
"authorities": [
{
"authorityId": "ec071816-31e6-11ea-bae1-0242ac110005",
"authorityName": "MANAGER",
"description": null,
"privileges": [
{
"description": "",
"privilege": "CREATE_USER",
"privilegeId": "ba9a4952-4d53-42e9-94fe-8373d334819e"
},
{
"description": null,
"privilege": "SHOW_DATA",
"privilegeId": "ec06de1b-31e6-11ea-bae1-0242ac110005"
}
]
}
],
"email": "test#gmail.com",
"password": "test#123",
"phoneNo": "8575456595",
"status": true,
"userId": null,
"username": "test"
}
i found something. It is not up to the mark but it help solve your problem.
As per i understand you need two kind of relationship,
Authority & Privileges
User & Authority & Privileges (Privileges also exist in Authority object)
In such case you have to follow this,
Create one UserDTO which access user info and authority JSON (So privilege not show two times).
Based on that JSON create two entity and their object. One for User and another for UserAuthorityPrivilege.
UserAuthorityPrivilege is the table which contain your three required data and as obvious all three are required so define them as embedded key.
Give the relation between user and UserAuthorityPrivilege table
Embaded Class :
#Data
#Embeddable
#NoArgsConstructor
#AllArgsConstructor
public class EmbeddedUserAccess implements Serializable {
#Column(name = "user_id") private UUID userId;
#Column(name = "authority") private String authority;
#Column(name = "privilege") private String privilege; }
Entity Class
#Entity
#Table(name = "pm_user_authority_privilege")
#Data
#NoArgsConstructor
#AllArgsConstructor
public class UserAuthorityPrivilege {
#EmbeddedId
private EmbeddedUserAccess id;
#Null
#Column(name = "store_id", nullable = true)
private UUID storeId;
}
DTO:
#Data
#NoArgsConstructor
#AllArgsConstructor
public class UserDTO {
private UUID userId;
private String username;
private String email;
private String phoneNumber;
private String password;
private Boolean status;
private Authority authority;
}
Fetch:
UserAuthorityPrivilege access = null;
EmbeddedUserAccess embedded = null;
Set<UserAuthorityPrivilege> accessibilities = new HashSet<>();
Authority authority = userDTO.getAuthority();
for (Privilege privilege : authority.getPrivileges()) {
access = new UserAuthorityPrivilege();
embedded = new EmbeddedUserAccess();
embedded.setUserId(user.getUserId());
embedded.setAuthority(authority.getAuthority());
embedded.setPrivilege(privilege.getPrivilege());
access.setId(embedded);
accessibilities.add(access);
}
You have to update code similerly.
I have one query, your user relate with single authority or multiple.
For more brief description add comment.

Foreign key must have same number of columns as the referenced primary key for manyToMany

here job has a set of employees, and employee have a set of jobs, but Spring give me this exception
Caused by: org.hibernate.MappingException: Foreign key
(FK1kec5bwba2rl0j8garlarwe3d:account [employee_id])) must have same
number of columns as the referenced primary key (employee
[job_id,employee_id])
this is my employee class :
#Entity
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id ;
private String firstname ;
private String lastname ;
private String email ;
private String phone ;
private String address ;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "account_id")
private Account account ;
public Account getAccount() {
return account;
}
public void setAccount(Account account) {
this.account = account;
}
#ManyToOne
#JoinColumn(name = "departement_id")
#JsonIgnore
private Departement departement ;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "jobs", joinColumns = #JoinColumn(name = "employee_id", referencedColumnName = "id"), inverseJoinColumns = #JoinColumn(name = "job_id", referencedColumnName = "id"))
private Set<Job>jobs ;
....
}
and here is my job class :
#Entity
#Inheritance
public abstract class Job {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id ;
private String status ; // (pending or done)
private Date date ;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "employee", joinColumns = #JoinColumn(name = "job_id", referencedColumnName = "id"), inverseJoinColumns = #JoinColumn(name = "employee_id", referencedColumnName = "id"))
private Set<Employee> employee ;
#ManyToOne
#JoinColumn(name = "BusinnesPartner_id")
#JsonIgnore
private BusineesPartner busineesPartner ;
}
Please can you explain to me why I get this exception.
#JoinTable annotation should be used in the owning entity side , in the other side you should not have #JoinTable , you need to have mappedBy to define the reverse relation since you are establishing a bidirectional relation is if the Job is the owning entity you need to modify Employee pojo
#ManyToMany(cascade = CascadeType.ALL, mappedBy="employee")
private Set<Job>jobs ;

Resources