many-to-many relationship springboot mysql - java

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<>();

Related

Got java "stackoverflow" when join two tables

I have two java entity classes :
#Table(name = "user")
public class UserEntity
{
#Id
#Column(name = "id", unique = true, nullable = false)
private Long id;
#OneToOne(cascade = CascadeType.PERSIST, fetch = FetchType.EAGER)
#JoinColumn(name = "opportunity_id")
private OpportunityEntity opportunity;
}
and
#Table(name = "opportunity")
public class OpportunityEntity
{
#Id
#Column(name = "id", unique = true, nullable = false)
private Long id;
#OneToMany
#JoinColumn(name = "opportunity_id")
private List<UserEntity> users;
#OneToOne
#JoinColumn(name = "mainuser_id")
private UserEntity mainUser;
}
When i search for a list of Users [find users], i've got a "stackoverflow" when mapping User.opportunity.
the bug was clear that the opportunity.mainUser refer to User which itself refer to the same opportunity.
Is there another way to design my models ?
For example create a boolean isMain in User Model ?
Try to specify relationship to UserEntity by adding mappedBy to annotatation
#Table(name = "opportunity")
public class OpportunityEntity
{
#Id
#Column(name = "id", unique = true, nullable = false)
private Long id;
#OneToMany
#JoinColumn(name = "opportunity_id")
private List<UserEntity> users;
#OneToOne(mappedBy="opportunity")
#JoinColumn(name = "mainuser_id")
private UserEntity mainUser;
}

How do POST with JSON with OneToMany?

I am using SpringBoot, so lets say first, I want to make a Country, and after doing that POST with JSON how can I do other POST to create a City and adding it to the Country created?
Or I cant do it with JSON?
And idk if is a good idea having the FK pointing the name instead of the ID, in my head it works the same bc is an unique key, right?
Thanks!
Country code:
#Entity
#Table(uniqueConstraints = {
#UniqueConstraint(name = "country_name",columnNames = "name")
})
#Getter #Setter #RequiredArgsConstructor #NoArgsConstructor #ToString
public class Country implements Serializable {
#Id
#Column(updatable = false, nullable = false, unique = true)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NonNull
private String name;
#OneToMany(mappedBy = "country", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<City> cities = new HashSet<>();
}
City code:
#Entity
#Table(uniqueConstraints = {
#UniqueConstraint(name = "city_name",columnNames = "name")
})
#Getter #Setter #RequiredArgsConstructor #NoArgsConstructor #ToString
public class City implements Serializable {
#Id
#Column(updatable = false, nullable = false, unique = true)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NonNull
private String name;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "country_name", referencedColumnName = "name", nullable = false,
foreignKey=#ForeignKey(name = "FK_country_city"))
private Country country;
#OneToMany(mappedBy = "city", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<Neighborhood> neighborhoods = new HashSet<>();
}
Neiborhood code:
#Entity
#Table(uniqueConstraints = {
#UniqueConstraint(name = "neighborhood_name",columnNames = "name")
})
#Getter #Setter #RequiredArgsConstructor #NoArgsConstructor #ToString
public class Neighborhood implements Serializable {
#Id
#Column(updatable = false, nullable = false, unique = true)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NonNull
private String name;
#NonNull
private String neighborhoodType;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "city_name", referencedColumnName = "name", nullable = false,
foreignKey=#ForeignKey(name = "FK_city_neighborhood"))
private City city;
}
So first you need to do the POST request to create Country object:
{
"name": "USA",
"cities": []
}
Second you need to do the POST request to create the City object and put the field country with the Primary Key (PK):
{
"name": "Huston",
"country": 1,
"neighborhoods": []
}
That's pretty much it actually.

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

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.

how to add user to team to admin page

I can’t understand how to implement adding a user to Team on the admin page.
I wrote the add method in the controller, I can’t understand how to show it all in the interface.
Need two lists, one list of all Teams and a second list of all users and then save?
began to learn thymeleaf and a lot of strange things.
admin.html
</head>
<body>
<h1>Admin page </h1>
<!--
<form action="#" th:action="#{/admin}" th:object="${team}" method="post">
<p>Add Team: <input type="text" th:field="*{name}" /></p>
<p><input type="submit" value="addTeam" />
</form>
-->
<form th:action="#{/logout}" method="post">
<input type="submit" value="Sign Out"/>
</form>
</body>
</html>
Users
#Entity
#Table(name="users")
public class Users {
#Id
#Column(name="email",unique = true, nullable = false,length = 200)
String email;
#Column(name="name",nullable = false,length = 200)
String name;
#Column(name="password",nullable = false,length = 128)
#JsonIgnore
String password;
#Column(name = "avatar", nullable = true)
String avatar;
#ManyToOne
#JoinColumn(name="team_id", nullable=true)
Team team;
#ManyToOne
#JoinColumn(name="role", nullable=false)
#JsonIgnore
Role role;
public Users() {
}
get und set
}
Team
Entity
#Table(name="team")
public class Team {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
#Column
String name;
#Column
String url;
#Lob
#Column(name = "avatar",nullable = true,columnDefinition="BLOB")
String avatar;
#OneToMany(mappedBy="team",cascade = CascadeType.ALL, orphanRemoval = true)
#JsonIgnore
Set<Users> users = new HashSet<>();
public Team() {
}
get und set
AdminController
#Controller//RestController
public class AdminController {
.....
#GetMapping("/admin/team")
List<Team> allTeams() {
return teamRepository.findAll();
}
#RequestMapping(value = "/admin/team/{id}/user/{email}", method = RequestMethod.POST,produces = { MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE })
public Users addUserToTeam(
#PathVariable long id,#PathVariable String email) {
Team team = teamRepository.findById(id).orElseThrow(() -> new NoSuchTeamException("Team not found"));
Users user = userRpRepository.findById(email).orElseThrow(() -> new NoSuchUserException("User not found"));
user.setTeam(team);
user = userRpRepository.save(user);
return user;
#RequestMapping(value = "/admin", method = RequestMethod.GET)
public String adminPage(Model model) {
model.addAttribute("admin",new Team());
return "admin";
}
}
Ideologically from RMDB structure, the better way is creating the linkage table between User and Team.
User
#Entity
#Table(name = "user")
public class User {
#Id
#Column(name = "email", length = 200) //#Id controls nullable and unique
private String email;
#Column(name = "name", nullable = false, length = 200)
private String name;
#Column(name = "password", nullable = false, length = 128)
#JsonIgnore
private String password;
#Column(name = "avatar", nullable = true)
private String avatar;
#ManyToMany(cascade = CascadeType.ALL) //by default fetch - LAZY
#JoinTable(name = "user_team", joinColumn = #JoinColumn(name = "user_id",
foreignKey = #ForeignKey(name = "fk_user_team__user"), nullable = false),
inverseJoinColumns = #JoinColumn(name = "team_id",
foreignKey = #ForeignKey(name = "fk_user_team_team"), nullable = false))
private Set<Team> teams;
}
Team
#Entity
#Table(name = "team")
public class Team {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column
private String name;
#Column
private String url;
#Lob
#Column(name = "avatar", nullable = true, columnDefinition = "BLOB")
private String avatar;
#ManyToMany(cascade = CascadeType.ALL) //by default fetch - LAZY
#JoinTable(name = "user_team", joinColumn = #JoinColumn(name = "team_id",
foreignKey = #ForeignKey(name = "fk_user_team__team"), nullable = false),
inverseJoinColumns = #JoinColumn(name = "user_id",
foreignKey = #ForeignKey(name = "fk_user_team_user"), nullable = false))
private Set<User> users;
}
UserTeam
#Entity
#Table(name = "user_team", uniqueConstraints =
#UniqueConstraints(columnNames = {"user_id", "team_id"}, name = "uniq_some")
public class UserTeam {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id; //it's easier to create new Long Id key then composite key with user_id and team_id
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "user_id", foreignKey = #ForeignKey(name = "fk_user_team__user"), nullable = false)
private User user;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "team_id", foreignKey = #ForeignKey(name = "fk_user_team__team"), nullable = false)
private Team team;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "role_id", nullable = false) //I don't see Role entity but think it has id field
#JsonIgnore
private Role role;
}
With this structure, you can get all users for the Team and all teams for the User. Collections are lazy so you need to use #Transactional, for example, for appropriate service methods.
And this structure is bi-directional: if you add new User into users collection in Team object, JPA will create new User. But ... linkage table contains one more required field role_id, so on such addition you will get an exception. So better first create User and Team objects, and after that create UserTeam linkage object with required Role (or set default Role and all new objects will be created with this Role).

spring-boot-starter-data-jpa#ManyToMany collection not populating

I'm getting a problem with the #ManyToMany collections not populating on data load. I've tried FetchType.LAZY and FetchType.EAGER with no changes in the result.
When I am printing the User Object the collection Object of Roles is empty.
User [userId=2, firstName=Ajay, lastName=C, email=admin.demo#gmail.com, password=12345, roles=[]]
Also tried by adding referenced columns. But not worked.
Please assist in this.
User and Roles Entities as follows.
#Entity
#Table(name = "\"USER\"", schema = "\"PLATFORM_PROD_IOT\"", uniqueConstraints = {
#UniqueConstraint(columnNames = { "\"EMAIL_ID\"" }) })
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Size(min = 1, max = 50)
#Column(name = "\"USER_ID\"")
private Long userId;
#NotBlank
#Size(min = 3, max = 50)
#Column(name = "\"FIRST_NAME\"")
private String firstName;
#NotBlank
#Size(min = 3, max = 50)
#Column(name = "\"LAST_NAME\"")
private String lastName;
#NaturalId
#NotBlank
#Size(max = 50)
#Email
#Column(name = "\"EMAIL_ID\"")
private String email;
#NotBlank
#Size(min = 3, max = 100)
#Column(name = "\"PASSWORD\"")
private String password;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "\"USER_ROLE_MAPPING\"", schema = "\"\PLATFORM_PROD_IOT\"", joinColumns = #JoinColumn(name = "\"USER_ID\""), inverseJoinColumns = #JoinColumn(name = "\"ROLE_ID\""))
private Set<Role> roles = new HashSet<>();
//Getters and Setters
}
#Entity
#Table(name = "\"ROLE\"",schema="\"PLATFORM_PROD_IOT\"")
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="\"ROLE_ID\"")
private Long roleId;
#Column(name="\"ROLE_NAME\"")
private RoleName name;
//Getters and Setters
}
You could try this -
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(name = "\"USER_ROLE_MAPPING\"", catalog = "\"PLATFORM_PROD_IOT\"", joinColumns = {
#JoinColumn(name = "\"USER_ID\"", nullable = false, updatable = false) },
inverseJoinColumns = { #JoinColumn(name = "\"ROLE_ID\"",
nullable = false, updatable = false) })
private Set<Role> roles = new HashSet<>();
public Set<Role> getRoles() {
return roles;
}
public void setRoles(Set<Role> roles) {
this.roles = roles;
}
Here I have added
cascade = CascadeType.ALL
catalog = "\"PLATFORM_PROD_IOT\"" instead of schema = "\"PLATFORM_PROD_IOT\""
nullable = false, updatable = false in #JoinColumn
Also have found an related -
collection not populating in many to many relationship

Categories

Resources