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).
Related
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;
}
I have a set of entities user and conference. I have implemented a method in which I assign users to a conference and save it. However, when I save the conferencce the intermediate table attendance_table is not updated.
My database erd diagram example:
My entities:
#Entity
#Table(name = "user_table", schema = "public")
public class User {
#Id
#Column(name = "user_id")
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long user_id;
#Column(name = "name")
private String name;
#Column(name = "username")
private String username;
#Column(name = "password")
private String password;
#Column(name = "email")
private String email;
#Column(name = "confirmed")
private boolean confirmed;
#ManyToMany(cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.DETACH}, fetch = FetchType.LAZY)
#JoinTable(name = "attendance_table",
joinColumns = {
#JoinColumn(name = "user_id", referencedColumnName = "user_id",
nullable = false, updatable = false)},
inverseJoinColumns = {
#JoinColumn(name = "conference_id", referencedColumnName = "conference_id",
nullable = false, updatable = false)})
private Collection<Conference> conferences = new HashSet<>();
#Entity
#Table(name = "conference_table", schema = "public")
public class Conference {
#Id
#Column(name = "conference_id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long conference_id;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "user_id")
private User creator ;
#Column(name = "name")
private String name;
#Column(name = "description")
private String description;
#Column(name = "startConference")
private Date startConference;
#Column(name = "endConference")
private Date endConference;
#ManyToMany(mappedBy = "conferences", fetch = FetchType.LAZY)
private Collection<User> students;
Any ideas? If you need any extra info I can update the question. Thanks in advance!
This was a interesting fix. I was messing around with the user and conference classes and found a solution.
I had to swap the implementations of #ManyToMany of both classes for it to work.
My User class now looks like:
#Entity
#Table(name = "user_table", schema = "public")
public class User {
#Id
#Column(name = "user_id")
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long user_id;
#Column(name = "name")
private String name;
#Column(name = "username")
private String username;
#Column(name = "password")
private String password;
#Column(name = "email")
private String email;
#Column(name = "confirmed")
private boolean confirmed;
#ManyToMany(cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.DETACH}, fetch = FetchType.EAGER)
#JoinTable(
name = "user_role_table",
joinColumns = #JoinColumn(name = "user_id", referencedColumnName = "user_id"),
inverseJoinColumns = #JoinColumn(name = "role_id", referencedColumnName = "role_id"))
private Collection<Role> roles = new HashSet<>();
#ManyToMany(mappedBy = "students", fetch = FetchType.LAZY)
private Collection<Conference> conferences;
And my conference class:
#Entity
#Table(name = "conference_table", schema = "public")
public class Conference {
#Id
#Column(name = "conference_id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long conference_id;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "user_id")
private User creator ;
#Column(name = "name")
private String name;
#Column(name = "description")
private String description;
#Column(name = "startConference")
private Date startConference;
#Column(name = "endConference")
private Date endConference;
#ManyToMany(cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.DETACH}, fetch = FetchType.LAZY)
#JoinTable(name = "attendance_table",
joinColumns = {
#JoinColumn(name = "conference_id", referencedColumnName = "conference_id",
nullable = false, updatable = false)},
inverseJoinColumns = {
#JoinColumn(name = "user_id", referencedColumnName = "user_id",
nullable = false, updatable = false)})
private Collection<User> students = new HashSet<>();
Still not exactly sure why it works like this and not the other way around. If anyone knows please explain!
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<>();
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
I'm developing a project which uses BackboneJS in front-end and Java - Spring Core in back-end. I have a problem about mapping entity(domain) objects to DTO objects. I am getting an error message like that :
org.apache.cxf.interceptor.Fault: Infinite recursion (StackOverflowError) (through reference chain: com.countdown.dto.CategoryDTO["countdownList"]->java.util.ArrayList[0]->com.countdown.dto.CountdownDTO["category"]->.......
User.java
#Entity
#Table(name = "Users")
public class User implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "USER_ID", nullable = false)
private int id;
#Column(name = "EMAIL", nullable = false, unique = true)
private String email;
#Column(name = "NAME_SURNAME", nullable = false)
private String nameSurname;
#Column(name = "PASSWORD", nullable = false)
private String password;
#Column(name = "USERNAME", nullable = false, unique = true)
private String username;
#Column(name = "REGISTER_DATE", nullable = false)
private Date registerDate;
#ManyToOne
#JoinColumn(name = "ROLE_ID")
private Role role;
#OneToMany(mappedBy = "createUser")
private List<Countdown> createCountdownList = new ArrayList<Countdown>();
#OneToMany(mappedBy = "updateUser")
private List<Countdown> updateCountdownList = new ArrayList<Countdown>();
#ManyToMany
#JoinTable(name = "FOLLOWINGS",
joinColumns = #JoinColumn(name = "USER_ID"),
inverseJoinColumns = #JoinColumn(name = "COUNTDOWN_ID"))
private List<Countdown> followings = new ArrayList<Countdown>();
//Getters and setters..
}
Role.java
#Entity
public class Role implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "ROLE_ID")
private int id;
#Column(name = "ROLE_NAME", nullable = false)
private String roleName;
#OneToMany(mappedBy = "role",fetch = FetchType.LAZY)
List<User> userList = new ArrayList<User>();
}
Countdown.java
#Entity
#Table(name = "COUNTDOWN")
public class Countdown implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "COUNTDOWN_ID")
private int id;
#Column(name = "COUNTDOWN_NAME", nullable = false)
private String countdownName;
#Column(name = "COUNTDOWN_DATE", nullable = false)
private Date countdownDate;
#Column(columnDefinition = "varchar(5000)")
private String countdownDescription;
#JoinColumn(name = "CATEGORY_ID", nullable = false)
#ManyToOne
private Category category;
#JoinColumn(name = "CREATE_USER", nullable = false)
#ManyToOne
private User createUser;
#Column(name = "CREATE_DATE", nullable = false)
private Date createDate;
#JoinColumn(name = "UPDATE_USER", nullable = false)
#ManyToOne
private User updateUser;
#Column(name = "UPDATE_DATE", nullable = false)
private Date updateDate;
#Column(name = "CREATE_USER_IP", nullable = false)
private int createIP;
#ManyToMany
private List<User> followers = new ArrayList<User>();
}
Category.java
#Entity
#Table(name="CATEGORY")
public class Category implements Serializable{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="CATEGORY_ID")
private int id;
#Column(name = "CATEGORY_NAME" , nullable = false)
private String categoryName;
#OneToMany(mappedBy = "category")
private List<Countdown> countdownList = new ArrayList<Countdown>();
}
Business Logic : CategoryServiceImpl.java
I'm getting error in forEach loop.
#Transactional(readOnly = true)
public List<CategoryDTO> getAllCategories() {
List<Category> categoryList;
List<CategoryDTO> categoryDTOList = new ArrayList<CategoryDTO>();
logger.debug("getAllCategories called");
try {
categoryList = categoryDAO.findAll();
for(Category category : categoryList){
categoryDTOList.add(mapper.map(category,CategoryDTO.class));
}
}catch (NoResultException e){
logger.error("getAllCategories method : No Category wasn't found");
logger.warn(e,e);
}catch (Exception e){
logger.error("getAllCategories method : Categories wasn't found");
logger.warn(e,e);
}
return categoryDTOList;
}
Also Do I have to use DTO object in Presentation Layer? Can I use entity objects in presentation layer instead of DTO objects?
How can I solve this problem? Sorry my bad english. Thank you!
Please Try :
#Transactional(readOnly = true)
public List<CategoryDTO> getAllCategories() {
List<Category> categoryList;
List<CategoryDTO> categoryDTOList = new ArrayList<CategoryDTO>();
logger.debug("getAllCategories called");
try {
categoryList = categoryDAO.findAll();
for(Category category : categoryList){
if(category.getCountdownList() != null && !category.getCountdownList().isEmpty()){
for(Countdown countdown : category.getCountdownList()){
countdown.setCategory(null);
}
}
categoryDTOList.add(mapper.map(category,CategoryDTO.class));
}
}catch (NoResultException e){
logger.error("getAllCategories method : Hata: No Category wasn't found");
logger.warn(e,e);
}catch (Exception e){
logger.error("getAllCategories method : Hata: Categories wasn't found");
logger.warn(e,e);
}
return categoryDTOList;
}
For those who are struggling with infinite recursion issue in Dozer.
I use mapId to define a leaf object and stops the recursion.
Let assume we have two entities Course and Teacher, which contains a Many-to-Many relationship, and we want to convert the following object graph to one represented by CourseDTO and TeacherDto. And we hope Dozer stops at the 3rd level.
Teacher 1 ---> m Course 1 ---> m Teacher ---> ...
1st level 2nd level 3rd level
We can first define the following definition for Teacher to TeacherDTO conversion.
This first mapping is used for the root Teacher entity.
Include any other fields you need in the mapping.
mapping(Teacher.class, TeacherDTO.class,
TypeMappingOptions.oneWay()
, mapNull(false)
).fields("courses", "courses");
The following mapping will prevent Dozer from going further to map the contained Course. We define a mapId teacherLeaf for it.
Exclude the fields that cause the infinite recursion. (In my example, it's courses)
Include any other fields you need in the mapping.
mapping(Teacher.class, TeacherDTO.class,
TypeMappingOptions.oneWay(), TypeMappingOptions.mapId("teacherLeaf")
, mapNull(false)
).exclude("courses");
The last one is the mapping rule for Course to courseDTO. The key is that we tell the mapper to use the teacherLeaf mapping rule defined previously to convert the contained Teachers.
mapping(Course.class, CourseDTO.class,
TypeMappingOptions.oneWay()
, mapNull(false)
).fields("teachers", "teachers", useMapId("teacherLeaf"));
Hope this helps!
I use Dozer 6.1.0.