Thymeleaf: print a Set in the template - spring-boot

I have a basic SpringBoot 2.0.4.RELEASE app. using Spring Initializer, JPA, embedded Tomcat, Thymeleaf template engine, and package as an executable JAR file.
I have this entity:
#Entity
#Table(name="t_user")
public class User implements Serializable, UserDetails {
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JsonIgnore
private Set<UserRole> userRoles = new HashSet<>();
public Set<UserRole> getUserRoles() {
return userRoles;
}
public void setUserRoles(Set<UserRole> userRoles) {
this.userRoles = userRoles;
}
}
and this:
#Entity
#Table(name = "t_user_role")
public class UserRole implements Serializable {
/** The Serial Version UID for Serializable classes. */
private static final long serialVersionUID = 1L;
public UserRole() {
}
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
public UserRole(User user, Role role) {
this.user = user;
this.role = role;
}
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "user_id")
private User user;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "role_id")
private Role role;
...
}
and want to print all the roles of a User, so I use this code:
<td class="col_name" th:text="${user.userRoles.role}"></td><!-- ROLES -->
but what I see in the browser is literally this:
com.tdk.backend.persistence.domain.backend.User.userRoles

You will want to iterate through the set of objects to print the role values. You can use Thymeleaf's th:each syntax to do so:
<td class="col_name">
<span th:each="userRole : ${user.userRoles}" th:text="${userRole.role}">[role]</span><!-- format with line breaks as needed -->
</td>
You can alternatively look at the lists utility and call the toString() to output for a quick and dirty way:
<td class="col_name">
<span th:text="${#lists.toString(user.userRole.role)}">[role]</span>
</td>
You can optionally remove the <span> tags by using th:remove="tag".

Related

JPA OneToOne and shared primary key need manual assignment

I'm using Springboot and JPA to create two tables sharing the same primary key.
For the first table I write:
public class UserAccount implements Serializable
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToOne(mappedBy ="user", cascade = {CascadeType.REMOVE, CascadeType.MERGE,
CascadeType.REFRESH}, fetch=FetchType.LAZY)
#PrimaryKeyJoinColumn
private UserLogin login;
}
For the second table I write:
public class UserLogin implements Serializable
{
#Id
private Long user_id;
#OneToOne(cascade = {CascadeType.MERGE, CascadeType.REFRESH},
fetch=FetchType.LAZY)
#MapsId("user_id")
#JoinColumn(name = "user_id", referencedColumnName = "id")
#Setter(AccessLevel.NONE)
private UserAccount user;
public void setUser(UserAccount user)
{
this.user = user;
this.user_id = user.getId();
}
}
Other stuff are omitted for conciseness. The code works because I manually set the id of UserLogin by writing the statement
this.user_id = user.getId();
otherwise I get the error:
Hibernate error: ids for this class must be manually assigned before calling save():
I guess that the ids can be manually managed but I cannot get the right configuration.
UPDATE:
I found the solution thanks (see the accepted answer). Now I just would get rid of the findById() when setting the user login.
//these methods are defined within a dedicated #Service
#Transactional
public void createLoginInfo(UserAccount user)
{
UserLogin userlogin=new UserLogin();
this.addLoginToUser(userlogin,user);
loginService.save(userlogin);
}
#Transactional
public void addLoginToUser(UserLogin login, UserAccount account)
{
//whit this commented line works
//UserAccount acc= this.findById(account.getId());
login.setUser(account);
account.setLogin(login);
}
//In a transactional test method I first create the user then I call
userService.save(theuser);
userService.createLoginInfo(theuser);
You have a bidirectional relationship, but have mapped it with a few competing options that don't work well together. First, in UserAccount, it isn't clear why you have an ID that is generated, yet try to also map it with the relationship (specifically using a PrimaryKeyJoinColumn). If you want it generated, it can't also be a foreign key value in a reference - and you've already got this relationship setup as the 'other' side via the 'mappedBy' setting. It should just be:
public class UserAccount implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToOne(mappedBy ="user", cascade = {CascadeType.REMOVE, CascadeType.MERGE,
CascadeType.REFRESH}, fetch=FetchType.LAZY)
private UserLogin login;
}
User login then should just be:
public class UserLogin implements Serializable {
#Id
private Long user_id;
#OneToOne(cascade = {CascadeType.MERGE, CascadeType.REFRESH},
fetch=FetchType.LAZY)
#MapsId("user_id")
#JoinColumn(name = "user_id", referencedColumnName = "id")
#Setter(AccessLevel.NONE)
private UserAccount user;
public void setUser(UserAccount user) {
this.user = user;
}
}
Note because you have the mapsId annotation on the user relationship, JPA will set the user_id property for you once the ID is assigned - there is no need to manually set it yourself. You can, but if you do you must insure it was assigned previously - which requires a save/flush on the UserAccount. If you don't actually use the Long user_id property, you don't really even need to map it; you can just mark the user property as the ID:
public class UserLogin implements Serializable {
#Id
#OneToOne(cascade = {CascadeType.MERGE, CascadeType.REFRESH},
fetch=FetchType.LAZY)
#JoinColumn(name = "user_id", referencedColumnName = "id")
#Setter(AccessLevel.NONE)
private UserAccount user;
public void setUser(UserAccount user) {
this.user = user;
}
}
The Long ID from UserAccount then can be used to lookup UesrAccounts and UserLogin instances.
Try this :
public class UserLogin implements Serializable
{
#Id
private Long user_id;
#OneToOne(fetch=FetchType.LAZY)
#MapsId
#JoinColumn(name = "user_id")
private UserAccount user;
public UserAccount getUser() {
return user;
}
public void setUser(UserAccount user) {
this.user = user;
}
}
public class UserAccount implements Serializable
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
}
To persist UserLogin :
EntityManager em;
UserAccount user = em.find(UserAccount.class, 1L)
UserLogin login = new UserLogin();
login.setUser(user);
em.persist(login);

Spring boot UserDetailsService Multi-User with extra fields

I have a spring boot project that has 3 types of users (Admin, Expert, Customer) and the application is for Experts that register on site for giving services like fixing computers to Customers that are asking help in site.
I have an inheritance of different kind of User types as following.
#Entity
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "USER_TYPE", discriminatorType = DiscriminatorType.INTEGER)
public abstract class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String username;
private String password;
private Set<String> roles = new HashSet<>();
// getter & setter...
}
#Entity
#DiscriminatorValue("1")
public class Admin extends User {
}
#Entity
#DiscriminatorValue("2")
public class Expert extends User {
private Byte[] expertPhoto;
private String password;
// some other fields & getter & setter...
}
#Entity
#DiscriminatorValue("3")
public class Customer extends User {
private Long credit;
private Set<CustomerOrder> orders = new HashSet<>();
// some other fields & getter & setter...
}
I want to use spring boot security and implement UserDetailsService, my problem is that how to design when I have different User types (Expert, Customer, etc.)?
I want users to be able to have different roles (admin, expert, customer) with one username.
How should I design my system to solve these requirements?
Your role modal seems a bit off. It is better to have a single type of User and fill it with list of a new Role entity. The new User entity will look like the following:
#Table(name = "user")
#Entity
public class User {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "username", unique = true, nullable = false)
private String username;
#Column(name = "password", nullable = false)
private String password;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(
name = "user_role",
joinColumns = {#JoinColumn(name = "user_id")},
inverseJoinColumns = {#JoinColumn(name = "role_id")},
)
private Set<Role> roles;
// getters and setters & other fields user can have
}
And the Role entity will look like this:
#Entity
#Table(name = "role")
public class Role {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "role_name", unique = true, nullable = false)
private String roleName;
#ManyToMany(mappedBy = "roles")
private Set<User> users;
}
Then, you need to implement org.springframework.security.core.userdetails.User interface to use as a concrete implementation of spring security class Useron your UserDetailsService. Notice that this class is also called User and is different than the User class on your system.
public class MyUserDetail extends User {
private String otherFieldsLikePhoto; // you can add different fields like this to keep extra information
public MyUserDetail(String username, String password, Collection<? extends GrantedAuthority> authorities, String otherFieldsLikePhoto) {
super(username, password, authorities);
this.otherFieldsLikePhoto = otherFieldsLikePhoto;
}
}
Then, you can create your UserDetailsService by implementing org.springframework.security.core.userdetails.UserDetailsService of spring security.
What you will achieve UserDetailsService is to load the user in the MyUserDetail format we just created. It will be something like this:
public class MyUserDetailsService implements UserDetailsService {
private final UserReadService userReadService; // put your service to get user from db
public MyUserDetailsService(UserReadService UserReadService) {
this.userReadService = UserReadService;
}
#Override
public UserDetails loadUserByUsername(String username) {
User user = userReadService.getByUsername(username); // get user from db
String otherFieldsLikePhoto = getUserPhotoOrAnythingElse(user); // get your extra fields however you want
return new MyUserDetail(
user.getUsername(),
user.getPassword(),
getAuthoritySetOfUser(user), // notice how we embed roles to UserDetail
otherFieldsLikePhoto
);
}
// this function is not necessary but useful to calculate authority set calculation on helper
private Set<SimpleGrantedAuthority> getAuthoritySetOfUser(User user) {
Set<Role> userRoles = user.getRoles(); // get roles of user like ADMIN, EXPERT etc.
Set<SimpleGrantedAuthority> authorities = roles.stream()
.map(rolex -> new SimpleGrantedAuthority(rolex.getRoleName()))
.collect(Collectors.toSet());
return authorities;
}
}

EL1007E: Property or field 'name' cannot be found on null

I am using a Spring framework with thymeleaf and mysql.
I am getting the error
org.springframework.expression.spel.SpelEvaluationException: EL1007E: Property or field 'name' cannot be found on null
This error is being caused by the ${selectProject.client.name} in the html code.
I have written a form to allow a user to add information about a project. As part of the form, there is a dropdown list of the different project names. However, in the dropdown list I also want it to give the name of the client alongside the name of the project.
My html code:
<form action="#" id="informationForm" method="post" th:action="#{/add-information}" th:object="${information}">
<div class="row">
<div class="form-group col-12">
<label class="col-form-label" for="project">Project</label>
<select class="form-control" id="project" th:field="*{project}">
<option value="0">Please select a project</option>
<option th:each="selectProject : ${projectList}"
th:text="${selectProject.client.name} + ' - ' +${selectProject.name}"
th:value="${selectProject.id}"></option>
</select>
Here is the information controller:
#Controller
#Slf4j
#SessionAttributes({"project", "information"})
public class InformationController {
private final InformationService informationService;
private final ProjectsService projectsService;
#Autowired
public InformationController(final ProjectsService projectsService,
final InformationService informationService) {
this.projectsService = projectsService;
this.informationService = informationService;
}
#ModelAttribute("information")
public Information getInformation() {
return informationService.createEmptyInformation();
}
#ModelAttribute("projectList")
public List<Project> getProjects() {
return projectsService.getProjects();
}
#GetMapping("add-information")
public String showAddInformationForm(Model model, #ModelAttribute("information") Information information) {
information = informationService.createEmptyInformation();
model.addAttribute(information);
return "add-information";
}
#PostMapping("add-information")
public String addInformationForm(#Valid Information information, BindingResult result, Model model) {
if (result.hasErrors()) {
return "add-information";
}
Long id = informationService.createInformationFromInput(information);
return "redirect:/homepage";
}
The relevant methods in the information service are:
public Information createEmptyInformation() {
return new Information();
}
public Long createInformationFromInput(Information information) {
try {
informationDao.save(information);
} catch (Exception ex) {
log.error("Failed to save information for {} because of {}", information.getId(), ex.getLocalizedMessage());
}
return information.getId();
}
and in the projects services:
public List<Project> getProjects() {
return projectDao.findAll();
}
Clients have a many to one relationship with projects
Project domain:
#Getter
#Setter
#Entity
#Table(name = "projects")
public class Project {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "client_id")
private Client client;
#Column(name = "name")
private String name;
Client domain:
#Getter
#Setter
#Entity
#Table(name = "clients")
public class Client {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotBlank(message = "Name is required")
#Size(min = 5, max = 80, message = "Name must be between 5 and 80 characters")
#Column(name = "name", nullable = false)
private String name;
#OneToMany(fetch = FetchType.LAZY)
#JoinColumn(name = "client_id")
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
private List<Project> projects;
Information domain:
#Getter
#Setter
#Entity
#Table(name = "information")
public class Information {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne (fetch = FetchType.LAZY)
#JoinColumn(name = "project_id")
private Project project;
#Column(name = "date")
private Date date;

Spring boot JPA many to many with extra column insert and update issue

Here is my initial question.
Spring Data JPA Many to Many with extra column User and Roles
Now I have the right tables created, but can't make it work for the update.
Here is the code:
User.java
#Entity
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
#OneToMany(mappedBy="user", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
private List<UserRole> roles;
// getters and setters
}
Role.java
#Entity
#Table(name = "roles")
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
// getters and setters
}
UserRole.java
#Entity
#Table(name = "users_to_role")
public class UserRole implements Serializable {
#Id
#ManyToOne
#JoinColumn(name = "user_id")
private User user;
#Id
#ManyToOne
#JoinColumn(name = "role_id")
private Role role;
private Date createdAt;
public UserRole(){}
public UserRole(User user, Role role, Date date) {
this.user = user;
this.role = role;
this.createdAt = date;
}
// getters and setters
}
Controller
#RestController
public class APIController {
#Autowired
RoleRepository roleRepository;
#Autowired
UserRepository userRepository;
#ResponseBody
#RequestMapping(value = "create", method = RequestMethod.GET)
public String create(){
//Insert test - WORKING BUT NOT SURE IF ITS RIGHT WAY
List<UserRole> userRoles = new ArrayList<>();
Role role = roleRepository.getOne((long) 1);
//Create user
User user = new User();
user.setUsername("test");
//Create userRole
userRoles.add(new UserRole(user, role, new Date()));
user.setRoles(userRoles);
userRepository.save(user);
return "created";
}
#ResponseBody
#RequestMapping(value = "edit", method = RequestMethod.GET)
public String edit(){
//Edit test - NOT working
List<UserRole> userRoles = new ArrayList<>();
Role role = roleRepository.getOne((long) 2);
//get user from db
User user = userRepository.getOne((long) 1);
//Create userRole
userRoles.add(new UserRole(user, role, new Date()));
// WAS FIRST ATTEMPT using user.setRoles(userRoles); but got error and use
//https://stackoverflow.com/questions/9430640/a-collection-with-cascade-all-delete-orphan-was-no-longer-referenced-by-the-ow
//user.setRoles(userRoles);
user.getRoles().clear();
user.getRoles().addAll(userRoles);
userRepository.save(user);
return "done";
}
}
I am getting this error:
com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Column 'user_id' cannot be null

Spring MVC, load objects attributes in a jsp

I got an issue when trying to load an object attribute in a jsp file.
The model contains a list of objects of type "Evaluation", for each element in the list, all the attributes are correctly loaded except the ones that it has to fetch from another table.
The .jsp file :
<div class="container">
<h1>Liste des Evaluations pour ${etudiant.username}</h1>
<table class="tbl">
<thead>
<th>Module</th>
<th>Note</th>
<th>Remarque</th>
</thead>
<tbody>
<c:forEach items="model.evaluations" var="ev">
<tr>
<td>${ev.examen.module.code}</td> --> Error occurs here
<td>${ev.note}</td>
<td>${ev.remarque}</td>
</tr>.
</c:forEach>
</tbody>
</table>
The Controller :
#RequestMapping(value="/{username}", method=RequestMethod.GET)
public String etudiantEvaluations(
#PathVariable String username, Model model) {
List<Evaluation> evaluations = evalDAO.findAllByEtudiant(username);
Etudiant etudiant = etDAO.findByUsername(username);
model.addAttribute("evaluations", evaluations);
model.addAttribute("etudiant", etudiant);
return "etudiants/listEvaluations";
}
The Evaluation entity :
#Data
#NoArgsConstructor
#EqualsAndHashCode
#Immutable #Entity(name="TEVALUATION")
public class Evaluation {
private enum evaldeliberation {
REUSSITE,
AJOURNEMENT,
REFUS,
ABANDON
}
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
protected Long id;
#NotNull
#Min(0) #Max(100)
#Column(updatable = true)
private Double note;
#Column(name="deliberation")
private evaldeliberation delib;
#Column(name="remarque")
private String remarque;
#Column(name="module_code")
private String moduleCode;
private Long examenId;
private String etudiantUsername;
#ManyToOne(fetch=FetchType.EAGER)
#JoinColumn(name = "FKExamen",
insertable = false,
updatable = false)
protected Examen examen;
#ManyToOne
#JoinColumn(name = "FKEtudiant",
insertable = false,
updatable = false)
protected Etudiant etudiant;
#OneToMany(mappedBy="evaluation")
protected List<EvalComp> evalComps = new ArrayList<>();
public Evaluation(Double note, Examen examen, Etudiant etudiant) {
super();
this.examen = examen;
this.etudiant = etudiant;
examen.getEvaluations().add(this);
etudiant.getEvaluations().add(this);
}
}
Examen:
#Data
#EqualsAndHashCode
#NoArgsConstructor
#Entity(name="TEXAMEN")
public class Examen {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#OneToMany(mappedBy = "examen")
private Set<Evaluation> evaluations = new HashSet<>();
#NotNull
#OneToOne(cascade=CascadeType.PERSIST)
#JoinColumn(name="FKmodule")
protected Module module;
public Examen(Module module) {
this.module = module;
}
}
The Query:
#Query("SELECT ev FROM TEVALUATION ev JOIN FETCH ev.examen ex JOIN FETCH ex.module m WHERE ev.etudiant=?1")
List<Evaluation> findAllByEtudiantId(String username);
The getters and setters are generated by Lombok(also tried without it).
Any idea how can I load the attributes ?
Thanks in advance.

Resources