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

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.

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.

Delete just one side of a manytomany relationship Hibernate

I have two tables that have a manytomany relationship:
first one is ad ( represents all the products)
#Entity
#Table(name = "ad")
public class Ad {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#ManyToOne(cascade = CascadeType.PERSIST)
#JoinColumn(name = "admin_id")
private Admin admin;
#ManyToMany(mappedBy = "ads", fetch = FetchType.LAZY)
private List<Order> orders = new ArrayList<>();
Second one is order:
#Entity
#Table(name = "`order`")
public class Order {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#ManyToOne( cascade=CascadeType.
#JoinColumn(name = "buyer_id")
private Buyer buyer;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "order_ad", joinColumns = {
#JoinColumn(name = "order_id", referencedColumnName = "id", nullable = false, updatable = false) }, inverseJoinColumns = {
#JoinColumn(name = "ad_id", referencedColumnName = "id", nullable = false, updatable = false) })
private List<Ad> ads = new ArrayList<>();
when I delete order using its repository that is representing a cancellation so I don't want the ads to be deleted as well.
How can I do that?
PS: I can't find a replacement for the orphanRemoval of the onetomany relationship

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;

Spring boot domain class required for mapping table

I m new to Spring Boot. I have a table (Team) that has resources, am storing in a separate table (Resources) and have team_resource mapping table (with fields teamid, resourceid). My question is should I have a domain class for the mapping_table too ?
When I m inserting a new team (POST) with resources I create entry in all 3 tables. I m using facade/dao pattern for writing/ reading to the DB. I have to handle when the team is modified/ deleted. Should I have a domain class for the mapping_table?
There are multiple approaches you can handle it
Approach 1
Define #ManyToMany between your Team and Resources entity like this:
In Team Entity
#ManyToMany(fetch = FetchType.LAZY,
cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
})
#JoinTable(name = "resources",
joinColumns = { #JoinColumn(name = "id") },
inverseJoinColumns = { #JoinColumn(name = "id") })
private Set<Resources> resources= new HashSet<>();
In your resources entity:
#ManyToMany(fetch = FetchType.LAZY,
cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
},
mappedBy = "resources")
private Set<Team> teams= new HashSet<>();
Approach 2
#Entity
#Table(name = "team_resources")
public class TeamResources implements Serializable {
#EmbeddedId
private TeamResourcesId id;
#ManyToOne
#JoinColumn(name = "team_id", referencedColumnName = "id", insertable = false, updatable = false)
private Team team;
#ManyToOne
#JoinColumn(name = "resources_id", referencedColumnName = "id", insertable = false, updatable = false)
private Resources resources;
public TeamResources (Team u, Resources r) {
// create primary key
this.id = new TeamResourcesId (u.getUserId(), q.getQuestionId());
// initialize attributes
this.user = u;
this.question = q;
}
#Embeddable
public static class TeamResourcesId implements Serializable {
#Column(name = "team_id")
protected Long teamId;
#Column(name = "resources_id")
protected Long resourcesId;
public TeamResourcesId () {
}
public TeamResourcesId (Long teamId, Long resourcesId) {
this.teamId= teamId;
this.resourcesId= resourcesId;
}
//Getter , setter. equal and hash
}
so to answer your question, follow second approach and its good to not define bidirectional approach as it can lead to some run time problem if not handled properly.

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