Design entities to join three tables using a mapping table Using JPA - java

Please help me write entities the proper way, so that it can be easily fetched using JPA. I have a DB design as below image:
Table Design Structure
I have created entities
#Entity
#Table(name = "ROLE")
public class Role {
#Id
#GeneratedValue()
#GenericGenerator(name = "uuid2", strategy = "uuid2")
#Column(name = "ROLE_ID")
private UUID roleId;
#Column(name = "ROLE_NAME")
private String roleName;
#ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE })
#JoinTable(name = "ROLE_MODULE_PERMISSION_MAP",
joinColumns = #JoinColumn(name = "ROLE_ID"),
inverseJoinColumns = #JoinColumn(name = "MODULE_ID"))
private List<Module> modules;
}
#Entity
#Table(name = "MODULE")
public class Module {
#Id
#GeneratedValue(generator = "uuid2")
#GenericGenerator(name = "uuid2", strategy = "uuid2")
#Column(name = "MODULE_ID", columnDefinition = "BINARY(16)")
private UUID uuid;
#Column(name = "MODULE_NAME")
private String moduleName;
#ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE })
#JoinTable(name = "ROLE_MODULE_PERMISSION_MAP",
joinColumns = #JoinColumn(name = "MODULE_ID"),
inverseJoinColumns = #JoinColumn(name = "PERMISSION_ID"))
private List<Permission> permission;
}
#Entity
#Table(name = "PERMISSION")
public class Permission {
#Id
#GeneratedValue(generator = "uuid2")
#GenericGenerator(name = "uuid2", strategy = "uuid2")
#Column(name = "PERMISSION_ID", columnDefinition = "BINARY(16)")
private UUID permissionId;
#Column(name = "PERMISSION_TYPE")
private String permissionType;
#ManyToMany
#JoinTable(name = "ROLE_MODULE_PERMISSION_MAP",
joinColumns = #JoinColumn(name = "PERMISSION_ID"),
inverseJoinColumns = #JoinColumn(name = "ROLE_ID"))
#MapKeyJoinColumn(name="MODULE_ID")
#ElementCollection
private Map<Role, Module> modulePermissions;
}
#Entity
#Table(name = "ROLE_MODULE_PERMISSION_MAP")
public class RoleModulePermissionMap implements Serializable {
#Id
#Column(name = "ROLE_ID", columnDefinition = "BINARY(16)")
private UUID roleId;
#Id
#Column(name = "MODULE_ID", columnDefinition = "BINARY(16)")
private UUID moduleId;
#Id
#Column(name = "PERMISSION_ID", columnDefinition = "BINARY(16)")
private UUID permissionId;
}
I am trying to fetch using:
Role role = roleRepository.findByroleName(roleName)
Where roleRepository is
#Repository
public interface RoleRepository extends JpaRepository<Role, UUID> {
Role findByroleName(String roleName);
}
I want to fetch the Module and Permissions for a specific Role. something like:
{
"roleName": "Development",
"roleAcronym": "DEV",
"permissionGroup": "AdminUser",
"modules": [
{
"moduleName": "Agreement",
"permission": [
{
"permissionName": "CREATE",
"permissionType": "C"
},
{
"permissionName": "UPDATE",
"permissionType": "U"
},
{
"permissionName": "READ",
"permissionType": "R"
}
]
},
{
"moduleName": "Reports",
"permission": [
{
"permissionName": "DELETE",
"permissionType": "C"
},
{
"permissionName": "UPDATE",
"permissionType": "U"
},
{
"permissionName": "READ",
"permissionType": "R"
}
]
}
]
}
I am using Spring Boot Starter JPA - 2.6.2 version.

Related

Violation of PRIMARY KEY With User Registration in Authorization Server

A simple application is created by using oauth2 password flow. The Authorization Server is working as the identity provider and when a new user is registered then the jwt token should be received as a response.
Relationships between entities
User (M) ------------------ Role (M)
Role (M) ------------------ Permission (M)
when I signup with a new user the following error has thrown
Caused by: java.sql.SQLSyntaxErrorException: ORA-02275: such a referential constraint already exists in the table
How can I resolve the primary key violation with JPA? If I use Native Hibernate API then we can use session.merge() but in JPA there is no option like that.
Request
{
"userName": "Nafaz Benzema",
"password": "stackoverflow",
"email": "benz#gmail.com",
"active": "y",
"accNonExpired": "y",
"credentialNonExpired": "y",
"accNonLocked": "y",
"roles" : [
{
"id": 101,
"name": "ROLE_USER",
"permissions": [
{
"id": 10,
"name": "CAN_CREATE"
},
{
"id": 40,
"name": "CAN_READ"
}
]
},
{
"id": 102,
"name": "ROLE_ADMIN",
"permissions": [
{
"id": 10,
"name": "CAN_CREATE"
},
{
"id": 40,
"name": "CAN_READ"
},
{
"id": 20,
"name": "CAN_UPDATE"
},
{
"id": 40,
"name": "CAN_DELETE"
}
]
}
]
}
Entity classes
User class
#Entity
#Table(name = "USER90",schema = Schema.TESTDB,uniqueConstraints = {
#UniqueConstraint(name = "userName",columnNames = "USER_NAME"),
#UniqueConstraint(name = "email",columnNames = "EMAIL")
})
#Getter
#Setter
public class User {
#Id
#SequenceGenerator(name = "USER_ID_GEN",sequenceName = Schema.TESTDB+".USER_ID_SEQ",initialValue = 1003,allocationSize = 1)
#GeneratedValue(generator = "USER_ID_GEN",strategy = GenerationType.SEQUENCE)
#Column(name = "USER_ID")
private int userId;
#Column(name = "USER_NAME",nullable = false)
private String userName;
#Column(name = "PASSWORD",nullable = false)
private String password;
#Column(name = "EMAIL",nullable = false)
private String email;
#Column(name = "ACTIVE",nullable = false)
private String active;
#Column(name = "ACC_NON_EXPIRED")
private String accNonExpired;
#Column(name = "CREDENTIAL_NON_EXPIRED")
private String credentialNonExpired;
#Column(name = "ACC_NON_LOCKED")
private String accNonLocked;
#ManyToMany(cascade = CascadeType.ALL,fetch = FetchType.EAGER)
#JoinTable(name = "USER_ROLE",joinColumns = {#JoinColumn(name = "USER_ID", referencedColumnName = "USER_ID")},
inverseJoinColumns = {#JoinColumn(name = "ROLE_ID",referencedColumnName = "ID")})
private Set<Role> roles;
}
Role class
#Entity
#Table(name = "ROLE",schema = Schema.TESTDB,uniqueConstraints = {
#UniqueConstraint(name = "name",columnNames = "NAME")
})
#Getter
#Setter
public class Role {
#Id
#Column(name = "ID")
private long id;
#Enumerated(EnumType.STRING)
#Column(name = "NAME")
private ERole name;
// bi-directional
/* #ManyToMany(cascade = CascadeType.ALL,fetch = FetchType.EAGER)
private Set<User> users;*/
// uni-directional
#ManyToMany(cascade = CascadeType.ALL,fetch = FetchType.EAGER)
#JoinTable(name = "PERMISSION_ROLE",joinColumns = {#JoinColumn(name = "ROLE_ID",referencedColumnName = "ID")},
inverseJoinColumns = {#JoinColumn(name = "PERMISSION_ID",referencedColumnName = "ID")})
private Set<Permission> permissions;
}
Permission class
#Entity
#Table(name = "PERMISSION",schema = Schema.TESTDB,uniqueConstraints = {
#UniqueConstraint(name = "name",columnNames = "NAME")
})
#Getter
#Setter
public class Permission {
#Id
#Column(name = "ID")
private long id;
#Enumerated(EnumType.STRING)
#Column(name = "NAME")
private EPermission name;
}
Service class
public Response userRegistration(SignupRequest signup) {
if(!Objects.isNull(userDAO.findUserByEmail(signup.getEmail()).orElse(null)))
throw new UserIsExistedException(String.format("User is existed with %s",signup.getEmail()));
try {
User user=new User();
String password="{bcrypt}";
password = password.concat(BCrypt.hashpw(signup.getPassword(),BCrypt.gensalt(12)));
user.setUserName(signup.getUserName());
user.setEmail(signup.getEmail());
user.setPassword(password);
user.setActive("y");
user.setAccNonExpired("y");
user.setCredentialNonExpired("y");
user.setAccNonLocked("y");
user.setRoles(signup.getRoles());
Set<Role> roles = new HashSet<>();
user.getRoles().forEach(role->{
roles.add(role);
Set<Permission> permissions=new HashSet<>();
role.getPermissions().forEach(perm->{
permissions.add(perm);
});
role.setPermissions(permissions);
});
user.setRoles(roles);
userDAO.save(user);
LOGGER.info("user is saved and response is returned successfully");
return new Response(user.getEmail(), authenticationProvider.obtainToken(user.getEmail(), user.getPassword()).toString());
}catch (NumberFormatException ex){
LOGGER.error("NumberFormat Exception");
throw new NumberFormatException("NumberFormat Exception");
}
catch (Exception ex){
LOGGER.error("invalid username or password");
throw new BadCredentialsException("invalid username or password",ex);
}
}
Note - If you need more resource here GitHub link
github_link
config file
spring:
datasource:
url: jdbc:oracle:thin:#localhost:1521:orcl
username: TESTDB
password: 14292
driver-class-name: oracle.jdbc.OracleDriver
jpa:
database-platform: org.hibernate.dialect.Oracle10gDialect
hibernate:
naming:
physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
ddl-auto: update
I have solved it by changing the CascadeType.
#ManyToMany(cascade = CascadeType.ALL,fetch = FetchType.EAGER)
#JoinTable(name = "USER_ROLE",joinColumns = {#JoinColumn(name = "USER_ID", referencedColumnName = "USER_ID")},
inverseJoinColumns = {#JoinColumn(name = "ROLE_ID",referencedColumnName = "ID")})
private Set<Role> roles;
when I use CascadeType.ALL then it gives priority to PERSIST (save) which throws Primary Key Violation in my case. When roles assigned to the user and if it is available in the role table then I need to Merge it rather than Save. So I have changed the CascadeType from ALL to MERGE.
#ManyToMany(cascade = CascadeType.MERGE,fetch = FetchType.EAGER)
#JoinTable(name = "USER_ROLE",joinColumns = {#JoinColumn(name = "USER_ID", referencedColumnName = "USER_ID")},
inverseJoinColumns = {#JoinColumn(name = "ROLE_ID",referencedColumnName = "ID")})
private Set<Role> roles;

many-to-many relationship springboot mysql

i have 2 models User and roles , its a many to many relation ship.
i need to add a user and give him a specific role already present in my data base.
------User------
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#ToString
#Entity
#Table(name = "user")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="user_Id")
private int userId;
#Column(name="name")
private String name;
#Column(name="lastname")
private String lastname;
#Column(name="email")
private String email;
#Column(name="password")
private String password;
#Column(name="isActive")
private boolean isActive;
#Column(name="lastActive")
private String lastActive;
#Column(name="createdDate")
private String createdDate;
#Column(name="isBlocked")
private boolean isBlocked;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "institution_id", nullable = false)
#JsonIgnoreProperties(value = {"user"})
private Institution institution;
#ManyToMany(fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.DETACH,CascadeType.MERGE,CascadeType.REFRESH})
#JoinTable(name = "user_has_role",
joinColumns = {
#JoinColumn(name = "user_id", referencedColumnName = "user_id",
nullable = false, updatable = true)},
inverseJoinColumns = {
#JoinColumn(name = "role_id", referencedColumnName = "role_id",
nullable = false, updatable = true)})
#JsonIgnoreProperties(value = {"users"})
private Set<Role> roles = new HashSet<>();
}
--------Roles--------
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#ToString
#Entity
#Table(name = "role")
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="role_Id")
private int roleId;
#Column(name="name")
private String name;
#Column(name="description")
private String description;
#ManyToMany(mappedBy = "roles", fetch = FetchType.LAZY)
#JsonIgnoreProperties(value = {"roles"})
private Set<User> users = new HashSet<>();
}
and the application's controller
#PostMapping("/addUser")
public String addUser(#RequestBody User user) {
userrepository.save(user);
return "user saved with name: " + user.getName();
}
and this is the json body i send with the api request.
{
"userId" : 7,
"name": "test",
"lastname": "testlast",
"email": "testtest#yahoo.com",
"password": "test123",
"lastActive": "04/05/21",
"createdDate": "02/04/20",
"institution": {
"institutionId": 4
},
"roles": [
{
"roleId": 2
}
],
"active": false,
"blocked": true
}
everything worls just fine to my user-has-role table a record is added with the userId = 7 and roleId=2
but the problem is that the table role is getting updated and the fields name and description are getting erased and replaced by null values...
any ideas please
You have added CascadeType.PERSIST to User and Role #ManyToMany join.
When the User entity is persisted to the EntityManager, it will also persist the Role entity. As you are passing the primary key in the request payload for Role it will create/update the Role table.
You need to remove the CascadeType.PERSIST from joining and it will work as expected.
#ManyToMany(fetch = FetchType.LAZY, cascade = {CascadeType.DETACH, CascadeType.MERGE, CascadeType.REFRESH })
#JoinTable(name = "user_has_role",
joinColumns = {
#JoinColumn(name = "user_id", referencedColumnName = "user_id",
nullable = false, updatable = true)},
inverseJoinColumns = {
#JoinColumn(name = "role_id", referencedColumnName = "role_id",
nullable = false, updatable = true)})
#JsonIgnoreProperties(value = {"users"})
private Set<Role> roles = new HashSet<>();

How to use UUID Generator in Spring DATA JPA?

I want to join two models, both are using org.hibernate.id.UUIDGenerator for primary key.
But on startup, I get the following error:
org.hibernate.tool.schema.spi.CommandAcceptanceException: Error
executing DDL "alter table user_role add constraint
FK5scdquo6f12cpstqai86x4biw foreign key (roles_role_id) references
role (role_id)" via JDBC Statement
Do you know, what I'm doing wrong?
My code:
User Model:
#Entity
#Table
public class User implements Serializable {
#Id
#GeneratedValue(generator = "uuid2")
#GenericGenerator(name = "uuid2", strategy = "org.hibernate.id.UUIDGenerator")
#Column(name = "user_id", columnDefinition = "VARCHAR(255)")
private String userId;
#Column(name = "name")
private String name;
#JoinTable(name = "user_role", joinColumns = #JoinColumn(name = "userId"), inverseJoinColumns = #JoinColumn(name = "roleId"))
#ManyToMany
private List<Role> roles;
public User(){
this.roles = new ArrayList<>();
}
// Getter & Setter
}
Role Model:
#Entity
#Table
public class Role implements Serializable {
#Id
#GeneratedValue(generator = "uuid2")
#GenericGenerator(name = "uuid2", strategy = "org.hibernate.id.UUIDGenerator")
#Column(name = "role_id", columnDefinition = "VARCHAR(255)")
private String roleId;
#Column(name = "role_name")
private String name;
#Column(name = "description")
private String description;
#ManyToMany(mappedBy = "roles")
private List<User> users;
public Role(){
this.users = new ArrayList<>();
}
// Getter & Setter
}
User DAO:
public interface UserDAO extends JpaRepository<User, String > {
}
Role DAO:
public interface RoleDAO extends JpaRepository<Role, String > {
}
Your join column should have a name similar to the column name and not the model variable name. In your case you should use
joinColumns = #JoinColumn(name = "user_id")
and
inverseJoinColumns = #JoinColumn(name = "role_id"))
NOT
joinColumns = #JoinColumn(name = "userId")
and
inverseJoinColumns = #JoinColumn(name = "roleId"))
Also do this for all join columns

hibernate - how to correctly define relations between entities?

I'm trying do maintain a code that was developed when I was busy doing other parts of the system and am taking a beat from some entities related to each other.
The scenario is this:
EntityX has one or more EntityA;
EntityA has one or more EntityB;
EntityB has one or more EntityC;
EntityC has one or more EntityD;
My Entities classes, are:
public class EntityX {
#Id
#GeneratedValue(generator = "uuid")
#GenericGenerator(name = "uuid", strategy = "uuid2")
#Column(name = "id", unique = true, insertable = true)
private String id;
#OneToMay(fetch = FetchType.EAGER)
#Fetch(FetchMode.SELECT)
#JoinTable(name = "joinTableX_A",
joinColumns = {#JoinColumn(name = "idX", referencedColumn= "id" ) },
inverseJoinColumns = { #JoinColumn(name = "idA", referencedColumn = "id") })
#Cascade(CascadeType.ALL)
private List<A> aList;
// getters and setters
public class EntityA {
#Id
#GeneratedValue(generator = "uuid")
#GenericGenerator(name = "uuid", strategy = "uuid2")
#Column(name = "id", unique = true, insertable = true)
private String id;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColun(name = "idX")
private X x;
#OneToMay(fetch = FetchType.EAGER)
#Fetch(FetchMode.SELECT)
#JoinTable(name = "joinTableA_B",
joinColumns = {#JoinColumn(name = "idA", referencedColumn= "id" ) },
inverseJoinColumns = { #JoinColumn(name = "idB", referencedColumn = "id") })
#Cascade(CascadeType.ALL)
private List<B> bList;
// getters and setters
public class EntityB {
#Id
#GeneratedValue(generator = "uuid")
#GenericGenerator(name = "uuid", strategy = "uuid2")
#Column(name = "id", unique = true, insertable = true)
private String id;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColun(name = "idA")
private A a;
#OneToMay(fetch = FetchType.EAGER)
#Fetch(FetchMode.SELECT)
#JoinTable(name = "joinTableB_C",
joinColumns = {#JoinColumn(name = "idB", referencedColumn= "id" ) },
inverseJoinColumns = { #JoinColumn(name = "idC", referencedColumn = "id") })
#Cascade(CascadeType.ALL)
private List<C> cList;
// getters and setters
public class EntityC {
#Id
#GeneratedValue(generator = "uuid")
#GenericGenerator(name = "uuid", strategy = "uuid2")
#Column(name = "id", unique = true, insertable = true)
private String id;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColun(name = "idB")
private B b;
#OneToMay(fetch = FetchType.EAGER)
#Fetch(FetchMode.SELECT)
#JoinTable(name = "joinTableC_D",
joinColumns = {#JoinColumn(name = "idC", referencedColumn= "id" ) },
inverseJoinColumns = { #JoinColumn(name = "idD", referencedColumn = "id") })
#Cascade(CascadeType.ALL)
private List<D> dList;
// getters and setters
What I am trying to do is:
When x is deleted, all children a, b, c and d are deleted;
When a is deleted, all children b, c and d are deleted but x is not;
When b is deleted, all children c and d are delete, but x and a are not;
When c is deleted, all children d are deleted, but x, a and b are not.
What am I doing wrong here and how can I get this scenario working?
Try defining your relationships like this, using JPA annotations, you'll even make your code simpler by using the following method:
public class EntityX {
#Id
#GeneratedValue(generator = "uuid")
#GenericGenerator(name = "uuid", strategy = "uuid2")
#Column(name = "id", unique = true, insertable = true)
private String id;
#OneToMay(mappedBy = "x", cascade = CascadeType.ALL)
private List<A> aList;
// getters and setters
}
public class EntityA {
#Id
#GeneratedValue(generator = "uuid")
#GenericGenerator(name = "uuid", strategy = "uuid2")
#Column(name = "id", unique = true, insertable = true)
private String id;
#ManyToOne
#JoinColumn(name = "idX")
private X x;
//and so on
// getters and setters
}
I think its a better Idea to let JPA handle your join column names, unless you have a restriction

Can't write my SQL query in Spring Data JPA custom repository

There is part of SQL i want to realize in my Custom JPA repository
SELECT * FROM users u
JOIN skills_user sku on sku.user_id = u.id
JOIN specs_user spu on spu.user_id = u.id
GROUP BY u.id
HAVING ANY(sku.dictionary_id in (15,20) or spu.dictionary_id in (15,20))
ORDER BY u.id
I tried this:
//Other predicates
if (filterQuery.getSkills() != null && !filterQuery.getSkills().isEmpty()) {
String[] tmp = filterQuery.getSkills().replaceAll(" ", "").split(",");
List<Integer> ids = new ArrayList<>();
for (String s : tmp) {
ids.add(Integer.parseInt(s));
}
List<Predicate> tmpPredicates = new ArrayList<>();
Join<User, Dictionary> skillJoin = root.join("skillList");
Join<User, Dictionary> specsJoin = root.join("specsList");
for (Integer id : ids) {
tmpPredicates.add(builder.or(builder.equal(skillJoin.get("id"), id), builder.equal(specsJoin.get("id"), id)));
}
predicates.add(builder.and(tmpPredicates.toArray(new Predicate[tmpPredicates.size()])));
}
//Other predicates
But it isn't work correctly.
How can i realise this correctly in JPA custom repository?
there is code of User and Dictionary classes:
#Entity
#SequenceGenerator(name = "user_gen", sequenceName = "users_seq")
#Table(name = "users")
public class User {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "user_gen")
private Long id;
#Column(name = "login")
private String login;
#Column(name = "password")
private String password;
#Column(name = "name")
private String name;
#Column(name = "surname")
private String surname;
#Column(name = "middlename")
private String middlename;
#Column(name = "academic_group")
private String academicGroup;
#Column(name = "entrance_year")
private int entranceYear;
#Column(name = "avatar_URL")
private String avatarURL;
#Column(name = "salt")
private String salt;
#Enumerated(EnumType.ORDINAL)
#Column(name = "user_group")
private UserGroup group;
#JsonIgnore
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(name = "SocialRole_User", joinColumns = {
#JoinColumn(name = "user_id", nullable = false, updatable = false) },
inverseJoinColumns = { #JoinColumn(name = "socialRole_id",
nullable = false, updatable = false) })
private List<SocialRole> socialRoleList;
#JsonIgnore
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(name = "specs_user", joinColumns = {
#JoinColumn(name = "user_id", nullable = false, updatable = true)},
inverseJoinColumns = {#JoinColumn(name = "dictionary_id",
nullable = false, updatable = true)})
private List<Dictionary> specsList;
#JsonIgnore
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(name = "skills_user", joinColumns = {
#JoinColumn(name = "user_id", nullable = false, updatable = true)},
inverseJoinColumns = {#JoinColumn(name = "dictionary_id",
nullable = false, updatable = true)})
private List<Dictionary> skillList;
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<Contacts> contactsList;
//Getters and setters
}
Dictionary:
#Entity
#SequenceGenerator(name = "dictionary_gen", sequenceName = "dictionary_seq")
#Table(name = "dictionary")
public class Dictionary {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "dictionary_gen")
private Long id;
#Column(name = "dic_name")
private String name;
#Enumerated(EnumType.STRING)
#Column(name = "dic_type")
private DictionaryType type;
// Getters and Setters
}
Have you tried writing the query using JPQL?
SELECT a FROM User a
INNER JOIN a.specsList b
INNER JOIN a.skillList c
GROUP BY a.id
HAVING ANY(b.id in (15,20) OR c.id in (15,20))
ORDER BY a.id;
This JPQL should work the same as your plain SQL.

Categories

Resources