I've recently started using Spring Boot (I mostly come from a python/flask and node background) with JPA and thymeleaf and I'm trying to create a Project in my database. Everything was going well, I was able to add, delete, etc.. Projects.
I added a variable Set users to my Project entity which looks like this:
#Entity
#Table(name = "project")
public class Project {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name = "project_id")
private int id;
#Column(name = "title")
#NotEmpty(message = "*Please provide a title")
private String title;
#Column(name = "description")
private String description;
#OneToOne(cascade = CascadeType.ALL)
#JoinTable(name = "project_lead", joinColumns = #JoinColumn(name = "project_id"), inverseJoinColumns = #JoinColumn(name = "user_id"))
private User projectLead;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "project_user", joinColumns = #JoinColumn(name = "project_id"), inverseJoinColumns = #JoinColumn(name = "user_id"))
private Set<User> users;
...
}
The user class looks like this:
#Entity
#Table(name = "user")
public class User {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name = "user_id")
private int id;
#Column(name = "email")
#Email(message = "*Please provide a valid Email")
#NotEmpty(message = "*Please provide an email")
private String email;
#Column(name = "username")
#NotEmpty(message = "*Please provide a username")
private String username;
#Column(name = "password")
#Length(min = 5, message = "*Your password must have at least 5 characters")
#NotEmpty(message = "*Please provide your password")
#Transient
private String password;
#Column(name = "enabled")
private int enabled;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "user_role", joinColumns = #JoinColumn(name = "user_id"), inverseJoinColumns = #JoinColumn(name = "role_id"))
private Set<Role> roles;
...
}
I'm able to create new projects when I don't specify the users for the project. But I'm having trouble creating a project when I specify the users using a multiple select. In my form I have:
<form th:action="#{/secure/projects}" th:object="${project}" method="POST">
<div class="form-group">
<label th:if="${#fields.hasErrors('title')}" th:errors="*{title}" class="validation-message"></label>
<input type="text" class="form-control" th:field="*{title}" id="title" th:placeholder="Title" />
</div>
<div class="form-group">
<input type="text" class="form-control" th:field="*{description}" id="description" th:placeholder="Description" />
</div>
<div class="form-group">
<select th:field="*{users}" class="users-select form-control" multiple="multiple">
<option th:each="user : ${allUsers}" th:value="${user}" th:text="${user.username}"></option>
</select>
</div>
<button name="Submit" value="Submit" type="Submit" th:text="Create"></button>
</form>
I keep getting 'Failed to convert property value of type 'java.lang.String' to required type 'java.util.Set' for property 'users'' for the th:field="*{users}". I don't understand why th:value="${user}" is being considered as a String when it's supposed to be of class User. Is there a way for me to simply get the results of the select, loop through it and manually add it in the controller to my object project?
My controller looks like this:
#RequestMapping(value = "/projects", method=RequestMethod.GET)
public ModelAndView showProjectForm() {
ModelAndView modelAndView = new ModelAndView();
// Get authenticated user
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
User user = userService.findByEmail(auth.getName());
modelAndView.addObject("userName", "Welcome " + user.getUsername() + " (" + user.getEmail() + ")");
modelAndView.addObject("project", new Project());
modelAndView.addObject("allUsers", userService.findAll());
modelAndView.setViewName("project_creation");
return modelAndView;
}
#RequestMapping(value = "/projects", method=RequestMethod.POST)
public ModelAndView processProjectForm(#Valid Project project, BindingResult bindingResult) {
ModelAndView modelAndView = new ModelAndView();
// Get authenticated user
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
User user = userService.findByEmail(auth.getName());
// Get all projects
List<Project> allProjects = projectService.findAll();
// Check if project already exists
Project projectExist = projectService.findByTitle(project.getTitle());
if(projectExist != null) {
bindingResult
.rejectValue("title", "error.project",
"There is already a project with this title");
}
// Get all users
List<User> allUsers = userService.findAll();
if(bindingResult.hasErrors()) {
modelAndView.addObject("userName", "Welcome " + user.getUsername() + " (" + user.getEmail() + ")");
modelAndView.addObject("project", new Project());
modelAndView.addObject("allUsers", allUsers);
modelAndView.setViewName("project_creation");
} else {
// Create project
project.setProjectLead(user);
projectService.saveProject(project);
modelAndView.addObject("userName", "Welcome " + user.getUsername() + " (" + user.getEmail() + ")");
modelAndView.addObject("success", "Project successfully created!");
modelAndView.addObject("project", new Project());
modelAndView.addObject("projects", allProjects);
modelAndView.setViewName("redirect:/secure/dashboard");
}
return modelAndView;
}
I was able to fix the 'Failed to convert property value of type 'java.lang.String' to required type 'java.util.Set' for property 'users''. I simply had to add a converter class telling how to convert from string to User object.
#Component
public class StringToUser implements Converter<String, User> {
#Autowired
private UserService userService;
#Override
public User convert(String arg0) {
Integer id = new Integer(arg0);
return userService.findOne(id);
}
}
And changed the option in my form to have a value of user.id instead of a user object. The converter would take care of the rest:
<option th:each="user : ${allUsers}" th:value="${user.getId()}" th:text="${user.getUsername()}"></option>
Related
I have this table:
#Entity
#Table(name = "transaction")
public class Transaction {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "transaction_id")
private Long id;
#Column(name = "user_id", nullable = false)
private Long userId;
#Column(name = "wallet_name", nullable = false)
private String walletName;
#NotNull(message = "Please, insert a amount")
#Min(value = 0, message = "Please, insert a positive amount")
private Double amount;
private String note;
#DateTimeFormat(pattern = "yyyy-MM-dd")
#Column(name = "date")
private LocalDate date;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "wallet_id", nullable = false)
private Wallet wallet;
#Enumerated(EnumType.STRING)
#Column(name = "transaction_type", columnDefinition = "ENUM('EXPENSE', 'INCOME')")
private TransactionType transactionType;
#Nullable
#Enumerated(EnumType.STRING)
#Column(name = "expense_categories", columnDefinition = "ENUM('FOOD_AND_DRINK', 'SHOPPING', 'TRANSPORT', 'HOME'," +
" 'BILLS_AND_FEES', 'ENTERTAINMENT', 'CAR', 'TRAVEL', 'FAMILY_AND_PERSONAL', 'HEALTHCARE'," +
" 'EDUCATION', 'GROCERIES', 'GIFTS', 'BEAUTY', 'WORK', 'SPORTS_AND_HOBBIES', 'OTHER')")
private ExpenseCategories expenseCategories;
#Nullable
#Enumerated(EnumType.STRING)
#Column(name = "income_categories", columnDefinition = "ENUM('SALARY', 'BUSINESS', 'GIFTS', 'EXTRA_INCOME', 'LOAN', 'PARENTAL_LEAVE', 'INSURANCE_PAYOUT', 'OTHER')")
private IncomeCategories incomeCategories;
Because, I want to display transactions by date on page I created a separated class like this:
public class TransactionGroup {
private LocalDate date;
private List<Transaction> transactions;
public LocalDate getDate() {
return date;
}
public void setDate(LocalDate date) {
this.date = date;
}
public List<Transaction> getTransactions() {
return transactions;
}
public void setTransactions(List<Transaction> transactions) {
this.transactions = transactions;
}
public void setTransactions(String walletName, Double amount, String note, LocalDate date, TransactionType transactionType, ExpenseCategories expenseCategories, IncomeCategories incomeCategories) {
}
}
So, as you can see I have a list of transactions. In my controller where I'm saving transaction I have this:
#PostMapping("/saveIncome/{walletId}")
public String saveIncome(#PathVariable(value = "walletId") long walletId, #Valid Transaction transaction, BindingResult result, Model model) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
UserDetailsImpl user = (UserDetailsImpl) authentication.getPrincipal();
long userId = user.getId();
Wallet wallet = walletService.getWalletById(walletId);
TransactionGroup transactionGroup = new TransactionGroup();
boolean thereAreErrors = result.hasErrors();
if (thereAreErrors) {
model.addAttribute("incomeCategories", IncomeCategories.values());
return "income_transaction";
}
transaction.setWallet(wallet);
transaction.setUserId(userId);
transaction.setWalletName(wallet.getWalletName());
transactionGroup.setTransactions(transaction.getWalletName(), transaction.getAmount(), transaction.getNote(), transaction.getDate(), transaction.getTransactionType(), transaction.getExpenseCategories(), transaction.getIncomeCategories());
transactionService.saveIncome(transaction, walletId, userId);
return "redirect:/api/wallet/userWallet/balance/" + userId;
}
This is the thing:
transactionGroup.setTransactions(transaction.getWalletName(), transaction.getAmount(), transaction.getNote(), transaction.getDate(), transaction.getTransactionType(), transaction.getExpenseCategories(), transaction.getIncomeCategories());
And that works fine, transaction is saved. Now when I want to show data from that entity like this:
<div th:each="group : ${transactionGroup}">
<h1 th:text="${group.date}"/>
<div th:each="transaction : ${group.transactions}">
<h2>Amount: <span th:text="${transactions.amount}"></span></h2><br>
<h2>Note: <span th:text="${transactions.note}"></span></h2><br>
<h2>Wallet name: <span th:text="${transactions.walletName}"></span></h2><br>
<h2>Expense Category: <span th:text="${transactions.expenseCategories}"></span></h2><br>
<h2>IncomeCategory: <span th:text="${transactions.incomeCategories}"></span></h2>
<div>
</div>
</div>
</div>
I'm getting a blank page without any transaction data.
I already asked a question similar to this, you can see it from my profile. Idk what to try more and why data is not displayed so far.
I am trying to edit the Roles of an user using html and thymeleaf. The point is that everything works ok, I can edit the username, the password, etc but when it comes to the roles, I get null when saving the List. I can't find anything useful on the internet and I'm struggling with this for a couple of weeks now, I need a way to edit those roles, so the page will return a List containing those roles that are checked.
Here I have the UserDetails:
#Getter
#Setter
public class MyUserDetails implements UserDetails {
private final User user;
public MyUserDetails(User user) {
this.user = user;
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return user.getRoles().stream()
.map(role->new SimpleGrantedAuthority(role.getName()))
.collect(Collectors.toList());
}
public int getId(){return this.user.getId();}
public User getUser(){
return this.user;
}
#Override
public String getPassword() {
return user.getPassword();
}
#Override
public String getUsername() {
return user.getUsername();
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return user.isEnabled();
}
}
Here is the User:
#Builder
#Getter
#Setter
#Entity
#Table(name="user",schema = "public")
#NoArgsConstructor
#AllArgsConstructor
public class User {
#Id
#Column(name="user_id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#NotEmpty(message = "You must provide a username!")
#Size(min=5, max=30)
private String username;
#NotEmpty(message = "You must provide a password!")
#ValidPassword
#JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private String password;
#JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private boolean enabled;
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name="user_roles",
joinColumns = #JoinColumn(name="user_id"),
inverseJoinColumns = #JoinColumn(name = "role_id")
)
#JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private List<Role> roles;
#JoinColumn(name="last_played")
private LocalDate lastPlayed;
#JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
public List<String> getRoleNames(){
return roles.stream()
.map(Role::getName)
.collect(Collectors.toList());
}
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
private List<Reward> rewards;
#Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", enabled=" + enabled +
", roles=" + roles +
", lastPlayed=" + lastPlayed +
", rewards=" + rewards +
'}';
}
}
Here is the Role class:
#Getter
#Setter
#Entity
#Table(name="role",schema = "public")
public class Role {
#Id
#Column(name="role_id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String name;
}
The controller:
#Controller
#RequiredArgsConstructor
public class EditUserController {
private final IUserDetailsService userService;
private final RoleService roleService;
#PostMapping(USERS_SAVE)
public String saveEdit( #ModelAttribute(AttributeNames.USER) User user,
BindingResult bindingResult){
if(bindingResult.hasErrors()){
return EDIT_USER;
}
userService.updateUser(user);
return REDIRECT+ USERS;
}
#GetMapping("/users/{id}")
public String editUser(#PathVariable int id, Model model){
List<Role> roleList = roleService.findAll();
UserDetails user = userService.findUserById(id);
model.addAttribute(AttributeNames.USER, user);
model.addAttribute(AttributeNames.ROLE_LIST, roleList);
return EDIT_USER;
}
#DeleteMapping(Mappings.USERS_DELETE_ID)
public String deleteUser(#PathVariable int id){
userService.deleteUser(id);
return REDIRECT+USERS;
}
}
And finally the html part for the roles :
<div class="form-group row">
<label class="col-form-label col-4">Roles</label>
<div class="col-8">
<th:block th:each="role:${roleList}">
<input type="checkbox"
name="roles"
th:field="*{user.roles}"
th:value="${role.id}"
th:text="${role.name}" class="m-2"/>
</th:block>
</div>
</div>
Here is a picture with the form, this user has both roles of user and admin, and they are both checked when entering the page, but if I go for the update, the roles will be modified to null.
Can you help me figure this out?
https://i.stack.imgur.com/DWpRw.png
The mapping does not need to be many to many, Use a uni directional as this:
#OneToMany(fetch = FetchType.EAGER,cascade = CascadeType.PERSIST)
#JoinTable(
name = "user_role",
joinColumns = #JoinColumn(
name = "user_id",
referencedColumnName = "id"
),
inverseJoinColumns = #JoinColumn(
name = "role_id",
referencedColumnName = "id"
))
private List<Role> roles;
Because a role does not need to have references for corresponding users it's being used by. Then have the table created 'user_role'. After that if you change roles and save the user it should be updated in user_role table.
UPDATE
Could you try this when returning user:
#GetMapping(path = "/test")
#ResponseStatus(HttpStatus.OK)
public User test() {
User user = // get user here by id
//set roles there
return user;
}
Apparently my problem was that I was adding the MyUserInterface class to the Model
insted Of User. So in the Controller I added this:
MyUserDetails userDetails=userService.findUserById(id);
User user = userDetails.getUser();
And now thymeleaf can get access to the list of the User directly.
<th:block th:each="role:${roleList}">
<input type="checkbox"
name="roles"
th:field="*{roles}"
th:value="${role.id}"
th:text="${role.name}" class="m-2"/>
</th:block>
I'm working on a JPA project in which I have two models: TableDecor and Product Tag, which look like this:
#Entity(name="table_decor")
public class TableDecor {
#Id
#IdConstraint
private String id;
#Positive
#Column(nullable = false)
private int width;
#Positive
#Column(nullable = false)
private int height;
#NotNull
#Enumerated(EnumType.STRING)
#Column(nullable = false)
private Color color;
#NotNull
#Enumerated(EnumType.STRING)
#Column(nullable = false)
private Fabric fabric;
#ManyToMany
#JoinTable(
name = "seller",
joinColumns = #JoinColumn(name = "table_decor_id"),
inverseJoinColumns = #JoinColumn(name = "seller_id"))
private List<Seller> sellers;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="producer_id")
private Producer producer;
#OneToOne(cascade=CascadeType.ALL, fetch = FetchType.LAZY)
#JoinColumn(name = "product_tag_id", referencedColumnName = "id")
private ProductTag productTag;
// getters, setters
#Entity(name="product_tag")
#Table
public class ProductTag {
#Id
#GeneratedValue
private UUID id;
#NotNull
#Enumerated
#Column(nullable = false)
private ProductState productState;
#Column(nullable = false)
#NotNull
private float price;
#OneToOne(cascade=CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "table_decor_id", referencedColumnName = "id")
private TableDecor tableDecor;
// getters, setters
As you can see they're connected with each other with bidirectional one to one relationship. Since one can't exist without another, how can I create a Thymeleaf add form that could create both objects at the same time? The controller function currently looks like this:
#GetMapping("/table-decor/add")
public String showAddForm(TableDecor decorToAdd, ProductTag productTag) {
return "table-decor-add";
}
#PostMapping("/table-decor/add-new")
public String addTableDecor(#Valid TableDecor decorToAdd, #Valid ProductTag productTag, BindingResult bindingResult, Model model){
if (bindingResult.hasErrors()) {
System.out.println("Error");
return "table-decor-add";
}
decorToAdd.setProductTag(productTag);
tableDecorService.addTableDecor(decorToAdd);
model.addAttribute("allTableDecor", tableDecorService.getAllTableDecor());
return "redirect:/table-decor";
and the form:
<form action="#" method="post" th:action="#{/table-decor/add-new}">
<div><input name="id" type="text" th:value="${tableDecor.id}"></div>
<span th:if="${#fields.hasErrors('tableDecor.id')}" th:errors="*{tableDecor.id}"></span>
<div><input name="width" type="text" th:value="${tableDecor.width}"></div>
<span th:if="${#fields.hasErrors('tableDecor.width')}" th:errors="*{tableDecor.width}"></span>
<div><input name="height" type="text" th:value="${tableDecor.height}"></div>
<span th:if="${#fields.hasErrors('tableDecor.height')}" th:errors="*{tableDecor.height}"></span>
<select name="color">
<option th:each="color : ${T(jee.labs.lab05.domain.Color).values()}"
th:text="${color}"
th:value="${tableDecor.color}">
</option>
</select>
<select name="fabric">
<option th:each="fabric : ${T(jee.labs.lab05.domain.Fabric).values()}"
th:text="${fabric}"
th:value="${tableDecor.fabric}">
</option>
</select>
<select name="productState">
<option th:each="productState : ${T(jee.labs.lab05.domain.ProductState).values()}"
th:text="${productState}"
th:value="${productTag.productState}">
</option>
</select>
<div><input name="price" type="text" th:value="${productTag.price}"></div>
<span th:if="${#fields.hasErrors('productTag.price')}" th:errors="*{productTag.price}"></span>
<div><input type="submit" th:value="Submit"></div>
</form>
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).
I have 3 objects: User, Comment and StatusUpdate(news). This is the User...
#Entity
#Table(name = "users")
#PasswordMatch(message = "{register.repeatpassword.mismatch}")
public class SiteUser {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
#Column(name = "email", unique = true)
#Email(message = "{register.email.invalid}")
#NotBlank(message = "{register.email.invalid}")
private String email;
#Transient
#Size(min = 5, max = 15, message = "{register.password.size}")
private String plainPassword;
#Column(name = "password", length = 60)
private String password;
#Column(name = "enabled")
private Boolean enabled = false;
#NotNull
#Column(name = "firstname", length = 20)
#Size(min = 2, max = 20, message = "{register.firstname.size}")
private String firstname;
#NotNull
#Column(name = "surname", length = 25)
#Size(min = 2, max = 25, message = "{register.surname.size}")
private String surname;
#Transient
private String repeatPassword;
#Column(name = "role", length = 20)
private String role;
public SiteUser() {
}
Here comes the StatusUpdate(you can call it piece of news or article). That has a site user that is the one who has created that article.
#Entity
#Table(name = "status_update")
public class StatusUpdate {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Size(min=5, max=255, message="{addstatus.title.size}")
#Column(name = "title")
private String title;
#Size(min=5, max=5000, message="{addstatus.text.size}")
#Column(name = "text")
private String text;
#Column(name = "added")
#Temporal(TemporalType.TIMESTAMP)
#DateTimeFormat(pattern="yyyy/MM/dd hh:mm:ss")
private Date added;
#OneToOne(targetEntity = SiteUser.class)
#JoinColumn(name="user_id")
private SiteUser siteUser;
#PrePersist
protected void onCreate() {
if (added == null) {
added = new Date();
}
}
public StatusUpdate() {
}
And the Comment which can be done by any registered user, right? As you will notice the Comment has no User object to avoid circular references.
#Entity
#Table(name = "comments")
public class Comment {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne
#JoinColumn(name = "statusupdateid")
private StatusUpdate statusUpdate;
#Column(name = "commenttext")
private String commenttext;
#Column(name = "commentdate")
#Temporal(TemporalType.TIMESTAMP)
#DateTimeFormat(pattern = "yyyy/MM/dd hh:mm:ss")
private Date commentdate;
#Column(name = "userid")
private Long userid;
public Comment() {
}
Now I would like to show in my JSP an article, with all the related comments and each of them belong to a different user. Can I use a HashMap to relate the users and their comments? I do not see how.
#RequestMapping(value ="/viewonestatus/{id}")
public ModelAndView viewOneStatus(#PathVariable("id") Long id) {
StatusUpdate status = statusUpdateService.get(id);
int countComments = commentService.countStatusComments(status);
List<Comment> comments = commentService.readAllComments(status);
ModelAndView modelAndView = new ModelAndView();
for (Comment comment: comments){
SiteUser user = userService.get(comment.getUserid());
modelAndView.getModel().put("user", user);
}
modelAndView.getModel().put("commentscounter", countComments);
modelAndView.getModel().put("status", status);
modelAndView.getModel().put("comments", comments); //!!
modelAndView.setViewName("app.viewonestatus");
return modelAndView;
}
As you expect, when my JSP shows just one user (the last one) for all the comments, but I can not relate all the Comments with the corresponding Users
<table class="table table-hover">
<c:forEach var="comment" items="${comments}">
<tr>
<td>
<div class="col-sm-2 sm-margin-bottom-40">
<img class="img-responsive profile-img margin-bottom-20" id="profilePhotoImage" src="/profilephoto/${comment.userid}" />
</div>
<h4>
${user.firstname} ${user.surname}
<span>
<!-- <span>${counterUserMap[comment.key]}</span> -->
5 hours ago / Reply
</span>
</h4>
<p>
<fmt:formatDate pattern="EEEE d MMMM y 'at' H:mm:ss" value="${comment.commentdate}" />
</p>
<p>${comment.commenttext}</p>
</td>
</tr>
</c:forEach>
I do not want to use JSON. I'm thinking about an anonymous class with all the stuff inside. Well, I'm open to your thoughts. Thanks.
Shokulei answer was the solution:
Since you have the userid, you can link it using the #ManyToOne annotation. This would be the most ideal way. But if you really don't want to link them, then you can create a new #Transient SiteUser siteUser; attribute in Comment class. And then in your for loop, you can use comment.setSiteUser(user); instead of modelAndView.getModel().put("user", user);. Hope this will help.
Thanks Shokulei