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

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;

Related

Soft delete but cannot prevent delete cascade with foreign key table

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

Application of ManyToMany - Spring does not work

I am implementing many to many relationship between Users and Permissions and I cannot conclude where am I wrong, it is not working (Unable to map collection rs.raf.demo.model.User.permissions):
#Data
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "user_id")
private Long userId;
#Column
private String username;
#Column
private String password;
#ManyToMany
#JoinTable(
name = "USERS_PERMISSIONS",
joinColumns = #JoinColumn(name = "USER_ID", referencedColumnName = "ID"),
inverseJoinColumns = #JoinColumn(name = "PERMISSION_ID", referencedColumnName = "ID")
)
private List<Permission> permissions = new ArrayList<>();
}
=================================================================================
#Data
#Entity
public class Permission {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "permission_id")
private Long userId;
private String type;
#ManyToMany
#JoinTable(
name = "USERS_PERMISSIONS",
joinColumns = #JoinColumn(name = "PERMISSION_ID", referencedColumnName = "ID"),
inverseJoinColumns = #JoinColumn(name = "USER_ID", referencedColumnName = "ID")
)
#JsonIgnore
private List<User> users = new ArrayList<>();
}
I implemented Users and Permissions with looking in this example, this is working (and I cannot see difference with Users and Permission):
#Data
#Entity
#Table(name = "STUD")
public class Student {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String lastName;
private String firstName;
#ManyToMany
#JoinTable(
name = "STUDENTS_COURSES",
joinColumns = #JoinColumn(name = "STUDENT_ID", referencedColumnName = "ID"),
inverseJoinColumns = #JoinColumn(name = "COURSE_ID", referencedColumnName = "ID")
)
private List<Course> courses = new ArrayList<>();
}
================================================================================
#Data
#Entity
public class Course {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
#ManyToMany
#JoinTable(
name = "STUDENTS_COURSES",
joinColumns = #JoinColumn(name = "COURSE_ID", referencedColumnName = "ID"),
inverseJoinColumns = #JoinColumn(name = "STUDENT_ID", referencedColumnName = "ID")
)
#JsonIgnore
private List<Student> students = new ArrayList<>();
public void addStudent(Student student) {
students.add(student);
student.getCourses().add(this);
}
public void removeStudent(Student student) {
students.remove(student);
student.getCourses().remove(this);
}
}
JPA uses (and creates in your case probably) intermediate join table for many to many relationship and you cannot use different reference column name from name you specified in entity.
So you need to set:
in User class
#ManyToMany
#JoinTable(
name = "USERS_PERMISSIONS",
joinColumns = #JoinColumn(name = "USER_ID", referencedColumnName = "USER_ID"),
inverseJoinColumns = #JoinColumn(name = "PERMISSION_ID", referencedColumnName = "PERMISSION_ID")
)
in Permission
#ManyToMany
#JoinTable(
name = "USERS_PERMISSIONS",
joinColumns = #JoinColumn(name = "PERMISSION_ID", referencedColumnName = "PERMISSION_ID"),
inverseJoinColumns = #JoinColumn(name = "USER_ID", referencedColumnName = "USER_ID")
)
And same for other tables.

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.

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