[SOLVED ]Problems adding items in Many to Many relationship in Spring - spring

I'm a bit desperate with this, because I assume I'm doing something wrong, but I can't find what is it. I've looked for the answer, but none of them can resolve it.
I have 2 entities with a many to many relatioship, but when I try to add an item to one of them, it is impossible. Symply it is not being added to the DB.
One of them id Formador, and the other is Proveedor. Just wanting to asign one (ore more) Proveedor items to Formador (formador.proveedores)
The entities are as follows:
Formador (only the relationship and constructor)
#ManyToMany (mappedBy = "formadores")
private List <Proveedor> proveedores;
//CONSTRUCTORES
public Formador() {}
public Formador(Long idFormador, String nifFormador, String passFormador, String nombreFormador, String ap1Formador,
String ap2Formador, String direccionFormador, int cpFormador, String localidadFormador,
String provinciaFormador, String tel1Formador, String tel2Formador, String mail1Formador,
String mail2Formador, String cuentaBancariaFormador, List<Visita> visitas, List<Zona> zonas, Rol rol,
List<Formacion> formaciones, List<Comercial> comerciales, List<Proveedor> proveedores)
Proveedor (same)
#ManyToMany (fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinTable(
name = "formadores_proveedores",
joinColumns = #JoinColumn(name = "id_proveedor"),
inverseJoinColumns = #JoinColumn(name = "id_formador"))
private List <Formador> formadores;
//CONSTRUCTORES
public Proveedor() {}
public Proveedor(Long idProveedor, String nombreProveedor, List<Campagna> campagnas, List<Franquicia> franquicias,
List<Zona> zonas, List<Tienda> tiendas, List<Formacion> formaciones, List<Formador> formadores)
Formador Controller
#GetMapping("/formadorAsignaProveedor/{idFormador}")
public String asignarProveedorFormador(#PathVariable("idFormador") Long idFormador, Model model) {
Formador formador = null;
if (idFormador != null) {
formador = formadorService.findOneFormador(idFormador);
}
else{
return "redirect:/index";
}
Proveedor proveedor = new Proveedor();
List <Proveedor> listaProveedores = formadorService.findAllProveedores();
model.addAttribute("proveedores", listaProveedores);
model.addAttribute("proveedor", proveedor);
model.addAttribute("formador", formador);
return "/formadorAsignaProveedor";
}
#PostMapping("/formadorAsignaProveedor/{idFormador}")
public String formadorGuardaAsignacionProveedor (#PathVariable("idFormador") Long idFormador, Proveedor proveedor, Model model) {
Formador formador = null;
if (idFormador != null) {
formador = formadorService.findOneFormador(idFormador);
}
else{
return "redirect:/index";
}
formador.getProveedores().add(proveedor);
formadorService.saveFormador(formador);
model.addAttribute("formador", formador);
return "redirect:/formadorVer/" + idFormador;
}
The web:
<form th:action="#{/formadorAsignaProveedor/} + ${formador.idFormador}"
th:object="${proveedor}" th:method="POST">
<div class="form-row mt-2">
<div class="col-4 mt-2">
<select id="proveedorFormador" class="form-control"
th:field="*{idProveedor}">
<option th:each="proveedor:${proveedores}"
th:text="${proveedor.nombreProveedor}"
th:value="${proveedor.idProveedor}"></option>
</select>
</div>
</div>
<div class="form-row mt-2">
<div class="col-4 mt-2">
<input type="submit" class="btn btn-success" value="Asignar">
</div>
</div>
</form>
Thanks in advance for your help!
SOLUTION:
The solution was not obvious for me: as I was working woth the entity Formador (in which I was making the changes), I put it as the owner of the relationship.
So i change the model like this:
Formador
#ManyToMany (fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinTable(
name = "formadores_proveedores",
joinColumns = #JoinColumn(name = "id_formador"),
inverseJoinColumns = #JoinColumn(name = "id_proveedor"))
private List <Proveedor> proveedores;
Proveedor
#ManyToMany (mappedBy = "proveedores")
private List <Formador> formadores;
Thank You all!

Related

Can you render jsp/html elements (i.e. a form:form) dynamically in one page?

I am new to web-dev, and coding in general, so I apologize if this question doesn't make sense.
Background: I am working on a web application utilizing the Spring Framework. My application allows users to register a user account, post pictures, make comments on pictures, like pictures etc. (think of a mini Pintrest clone). Users and comments (along with some other non-relevant entities) are all saved as in table entities in a MySQL database.
Problem: I would like users be able to edit their comments after they post them. For the best user experience I do not want users to be taken to a separate page with a form to edit their comment. I would like user to be able to edit their comment in a single page/the same page, without rendering a new one. Flow goes something like this: User goes to their comment on a picture, presses an "edit" button/link and the comment is put in a text box (form:form) which is then editable.
So far I have tried conditional rendering with a boolean switch of "editPressed" but I can't seem to work out the logic. Is this the correct approach or should something like JS be used here? (not sure how js handles form:forms) Below are some code snippets of the models and the .jsp where the comment would be edited.
.jsp:
<c:forEach var="eachComment" items="${allCommentsByPhotoId}">
<div class="card-text d-flex gap-2 mb-4">
<img class="user-comment-card" src="https://cdn-icons-png.flaticon.com/512/1053/1053244.png" alt="" />
<div>
<div class="mb-2">
<c:choose>
<c:when test="${ editPressed == false }">
<c:out value="${eachComment.user.getFirstName()} says: ${ eachComment.getComment() }"></c:out>
</c:when>
<c:otherwise>
<span class="username">
<c:out value="${eachComment.user.getFirstName()}"></c:out>:
</span>
<c:out value="${ eachComment.getComment()}"></c:out>
</c:otherwise>
</c:choose>
</div>
<div class="d-flex align-items-center gap-3">
<!-- -->
<c:if test="${ eachComment.user.getId() == currentUser.id }">
<form:form action="/delete/${eachComment.getId()}" method="delete">
<p class="submit">
<input class="btn btn-danger del-cmt-btn" type="submit" value="Delete" />
</p>
</form:form>
<form:form action="/edit/comment/${eachComment.getId()}">
<p class="submit">
<input class="btn btn-secondary edit-cmt-btn" type="submit" value="Edit" />
</p>
</form:form>
</c:if>
</div>
</div>
</div>
</c:forEach>
Comment Model:
#Entity
#Table(name="user_comments_on_photo")
public class Comment {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String comment;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="user_id")
private User user;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="photo_id")
private Photo photo;
#ManyToMany(fetch=FetchType.LAZY)
#JoinTable(
name="user_likes_comment",
joinColumns = #JoinColumn(name="comment_id"),
inverseJoinColumns = #JoinColumn(name="user_id")
)
private List<User> usersWhoLikeComment;
#Column(updatable=false)
#DateTimeFormat(pattern="yyyy-MM-dd")
private Date createdAt;
#DateTimeFormat(pattern="yyyy-MM-dd")
private Date updatedAt;
USER MODEL:
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
#Size(min=3)
private String firstName;
#Size(min=3)
private String lastName;
#Email
private String email;
#Size(min=8)
private String password;
#Transient
private String passwordConfirmation;
#Column(updatable=false)
private Date createdAt;
private Date updatedAt;
//RELATIONSHIPS
#OneToMany(mappedBy="user", fetch = FetchType.LAZY)
private List<Photo> photos;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(
name = "user_likes_photo",
joinColumns = #JoinColumn(name="user_id"),
inverseJoinColumns = #JoinColumn(name="photo_id")
)
private List<Photo> likedPhotos;
//NEW ONE-TO-MANY
#OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
private List<Comment> userComments;
#ManyToMany(fetch=FetchType.LAZY)
#JoinTable(
name="user_likes_comment",
joinColumns = #JoinColumn(name="user_id"),
inverseJoinColumns = #JoinColumn(name="comment_id")
)
private List<Comment> commentsUserLikes;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(
name = "users_roles",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "role_id"))
private List<Role> roles;
PHOTO MODEL:
#Entity
#Table(name="photos")
public class Photo {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String photoURL;
private String photoFileName;
#NotEmpty
private String photoTitle;
#NotEmpty
private String photoDescription;
//will add tags later
//RELATIONSHIPS
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="user_id")
private User user;
#ManyToMany(fetch=FetchType.LAZY)
#JoinTable(
name="user_likes_photo",
joinColumns = #JoinColumn(name="photo_id"),
inverseJoinColumns = #JoinColumn(name="user_id")
)
private List<User> usersWhoLikePhoto;
//NEW ONE-TO-MANY
#OneToMany(mappedBy="photo", fetch = FetchType.LAZY)
private List<Comment> comments;
//CREATED AND UPDATED AT
#Column(updatable=false)
#DateTimeFormat(pattern="yyyy-MM-dd")
private Date createdAt;
#DateTimeFormat(pattern="yyyy-MM-dd")
private Date updatedAt;

Thymeleaf how to use model atrribute correctly its throwing null error

public class Customer {
#Id
#GeneratedValue
private Integer id;
private String email;
private Long Apprvamount;
#JsonIgnore
#OneToMany(mappedBy="customer", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private Set<MyTransaction> transactions;
#Transient
private Long rewardPoints;
public Long getRewardPoints() {
function to calculate rewardPoints;
}
Contoller adding model attribute
#GetMapping("/customers/{id}")
public String getCustomer(#PathVariable Integer id,Model model) throws RecordNotFoundException {
Customer customer = rewardsService.getCustomerById(id);
model.addAttribute("customer", customer);
return "profile";
}
Thymeleaf
<body>
<div class="card-body">
<h2 th:text="${customer.getEmail()}"></h2>
<p th:text="${customer.getApprvamount()}"></p>
<p th:text="${customer.getRewardPoints()}"></p>
<p class="my-5">
<a href="/{id}(id=${customer.getId()})/emi" class="btn btn-primary">
<i> All</i></a>
</p>
Its sometime throwing null error for getRewardPoints its working for email and approvamount.And url is not getting calculated.Getters and setters are present.What is wrong with the approach?
You may have misspelt customer:
model.addAttribute("costumer", customer);

How to prevent user from injecting field into form backing bean?

An user upload his comment via this form.
Thymeleaf
<form th:action="#{/comment}" th:id="form" method="post">
<input type="hidden" th:name="productId.id" th:value="${product.id}">
<textarea th:field="${comment.message}" class="comment"
placeholder="Write comment here"></textarea>
<input type="submit" id="submit" value="comment">
</form>
Actual HTML
<form action="/comment" id="form" method="post" class="">
<input type="hidden" name="_csrf" value="f6b3f296-3284-4d2d-a2b2-0a9975f5e071">
<input type="hidden" name="productId.id" value="38">
<textarea class="comment" placeholder="Write comment here" id="message" name="message"></textarea>
<input type="submit" id="submit" value="comment">
</form>
However if user overwrites the actual HTML like this, the product's name will be changed to "ABCD"
<form action="/comment" id="form" method="post" class=""><input type="hidden" name="_csrf" value="f6b3f296-3284-4d2d-a2b2-0a9975f5e071">
<input type="hidden" name="productId" value="38">
<input type="hidden" name="productId.name" value="ABCD">
<textarea class="comment" placeholder="Write comment here" id="message" name="message"></textarea>
<input type="submit" id="submit" value="comment">
</form>
I think what happened here is Spring queried the productId and it became managed Entity, and when the user set the name to be "ABCD", it would be saved.
Here is my solution:
Basically just use #Validated with a bunch of groups and put constraint with appropriate groups (UploadCommentValidation in this case) on every single field, which works but seems really messy especially when it gets big.
Example with upload comment above:
Comment Entity: productId and message must be #Not Null, productId must be #Valid,other fields must be #Null
Product Entity: Id must be #NotNull, other fields must be #Null
Comment entity
public class Comment implements Comparable<Comment> {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Null(groups = {UploadCommentValidation.class})
#NotNull(groups = {DeleteCommentValidation.class, UpdateCommentValidation.class})
private Integer id;
#ManyToOne
#JoinColumn(name = "product_id", referencedColumnName = "id")
#JsonBackReference
#Valid
#NotNull(groups = {UploadCommentValidation.class})
#Null(groups = {DeleteCommentValidation.class, UpdateCommentValidation.class})
private Product productId;
#ManyToOne
#JoinColumn(name = "user_id", referencedColumnName = "id")
#JsonBackReference
#Null(groups = {UploadCommentValidation.class, DeleteCommentValidation.class, UpdateCommentValidation.class})
private User userId;
#Column(name = "message")
#NotBlank(message = "please write a comment", groups = {UploadCommentValidation.class, UpdateCommentValidation.class})
#Null(groups = {DeleteCommentValidation.class})
private String message;
#Column(name = "created_at", insertable = false, columnDefinition = "timestamp with time zone not null")
#Temporal(TemporalType.TIMESTAMP)
#Null(groups = {UploadCommentValidation.class, DeleteCommentValidation.class, UpdateCommentValidation.class})
private Calendar createdAt;
#Column(name = "updated_at", columnDefinition = "timestamp with time zone not null")
#Temporal(TemporalType.TIMESTAMP)
#Null(groups = {UploadCommentValidation.class, DeleteCommentValidation.class, UpdateCommentValidation.class})
private Calendar updatedAt;
#Override
public int compareTo(Comment o) {
return this.getId().compareTo(o.getId());
}
}
Product entity
public class Product implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#NotNull(message = "product id null", groups = {AddOrderValidation.class, UploadCommentValidation.class})
#Null(message = "bad request", groups = {ProductRegisterValidation.class})
private Integer id;
#NotBlank(message = "please fill in product name", groups = {ProductRegisterValidation.class})
#Length(max = 255, message = "too long", groups = {ProductRegisterValidation.class})
#Null(groups = {AddOrderValidation.class, UploadCommentValidation.class})
#Column(name = "name")
private String name;
#Column(name = "price")
#Positive(message = "the price must be non-negative", groups = {ProductRegisterValidation.class})
#NotNull(message = "please fill in price", groups = {ProductRegisterValidation.class})
#Null(groups = {AddOrderValidation.class, UploadCommentValidation.class})
private Integer price;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "category_id", referencedColumnName = "id")
#Valid
#NotNull(message = "please select category name", groups = {ProductRegisterValidation.class})
#Null(groups = {AddOrderValidation.class, UploadCommentValidation.class})
private Category categoryId;
#NotBlank(message = "please fill in description", groups = {ProductRegisterValidation.class})
#Length(max = 10000, message = "too long", groups = {ProductRegisterValidation.class})
#Null(groups = {AddOrderValidation.class, UploadCommentValidation.class})
#Column(name = "description")
private String description;
#OneToMany(mappedBy = "product", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#Null(groups = {ProductRegisterValidation.class, AddOrderValidation.class, UploadCommentValidation.class})
private List<ProductImage> productImages;
#OneToOne(mappedBy = "product", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#Null(groups = {ProductRegisterValidation.class, AddOrderValidation.class, UploadCommentValidation.class})
private Thumbnail thumbnail;
#OneToMany(mappedBy = "productId", fetch = FetchType.LAZY)
#JsonManagedReference
#Null(groups = {ProductRegisterValidation.class, AddOrderValidation.class, UploadCommentValidation.class})
private List<Comment> comments;
#OneToMany(mappedBy = "product", fetch = FetchType.LAZY)
#Null(groups = {ProductRegisterValidation.class, AddOrderValidation.class, UploadCommentValidation.class})
private List<Order> orders;
}
Any ideas how to do it the right way? This seems super messy!
UPDATE 1: This is my rest controller
#PostMapping("/comment")
public ResponseEntity<Map<String, String>> commentResponseEntity(#Validated({UploadCommentValidation.class}) Comment comment, BindingResult result) {
if (result.hasErrors()) {
result.getAllErrors().forEach(System.out::println);
return ResponseEntity.noContent().build();
}
User user = getUser();
comment.setUserId(user);
commentRepository.saveAndFlush(comment);
Map<String, String> response = new HashMap<>();
response.put("comment", comment.getMessage());
response.put("user", user.getName());
response.put("commentId", comment.getId().toString());
return ResponseEntity.ok().body(response);
}
You can do this by registering an #InitBinder method
You can do this at the individual controller level or by registering a #ControllerAdvice to be applied to all, or a subset of all, controllers.
#InitBinder()
public void initBinder(WebDataBinder binder) {
binder.setDisallowedFields(new String[] { "id", "version" });
}

Spring 4 MVC Form: Create Object with list of sub objects

I looking for help in understanding how to create a new object through a form which has the user select multiple sub-objects (which will come pre-populated) and available to select with a checkbox.
OrderController.java
#RequestMapping(value = { "/order" }, method = RequestMethod.GET)
public String order(ModelMap model) {
List<Exam> exams = examService.findAllExams();
List<Document> documents = documentService.findAllDocuments();
model.addAttribute("exams", exams);
model.addAttribute("documents", documents);
return "order"; // jsp page reference
}
Order.java
#Entity
#Table(name="\"order\"")
public class Order implements Serializable
{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name = "order_id", unique = true, nullable = false)
private Integer id;
#GeneratedValue(generator = "uuid")
#GenericGenerator(name = "uuid", strategy = "uuid2")
#Column(name = "uuid", unique = true, nullable = false)
private String uuid;
#Temporal(TemporalType.DATE)
#Column(name = "order_date", unique = true, nullable = false)
private Date orderDate;
#Column(name="order_status", nullable=false)
private String orderStatus;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id", nullable = false)
private User user;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "order")
private Set<OrderExam> orderExams = new HashSet<OrderExam>(0);
#OneToMany(fetch = FetchType.LAZY, mappedBy = "order")
private Set<OrderDocument> orderDocuments = new HashSet<OrderDocument(0);
//getters & setters
}
OrderExam.java
#Entity
#Table(name="order_exam")
public class OrderExam implements Serializable
{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name = "order_exam_id", unique = true, nullable = false)
private Integer id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "order_id", nullable = false)
private Order order;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "exam_id", nullable = false)
private Exam exam;
#Column(name="exam_amount", nullable=true)
private Integer examAmount;
#Column(name="answer_sheet_amount", nullable=true)
private String answerSheetName;
#Column(name="students_per_csv", nullable=true)
private String studentsPerCSV;
#Column(name="pas", nullable=true)
private Boolean pearsonAnswerSheet;
//getters & setters
}
Exam.java
#Entity
#Table(name="exam")
public class Exam implements Serializable
{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name = "exam_id", unique = true, nullable = false)
private Integer id;
#NotEmpty
#Column(name="name", unique=true, nullable=false)
private String name;
#NotEmpty
#Column(name="code", unique=true, nullable=false)
private String code;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "exam")
private Set<OrderExam> exams = new HashSet<OrderExam>(0);
//getters & setters
}
As you can see I am passing in a list of exams and documents which will populate a form with available options (can be seen in the image below (exams anyway)). The user needs to be able to select multiple rows, so that a single order has multiple exams and documents associated to it.
My order.jsp is a little much to post the entire thing here but here is the part I have which is displayed in the image above.
Order.jsp
<form:form method="POST" modelAttribute="order" class="form-horizontal form-label-left">
<c:forEach items="${exams}" var="exam">
<tr>
<th scope="row"><input type="checkbox" class="flat"></th>
<td><input id="middle-name" type="text" name="middle-name" readonly="readonly" value="${exam.name} - ${exam.code}" class="form-control col-md-7 col-xs-12"></td>
<td><input id="middle-name" type="text" name="middle-name" value="0" class="form-control col-md-3 col-xs-12"></td>
<td><input id="middle-name" type="text" name="middle-name" value="0" class="form-control col-md-3 col-xs-12"></td>
<td><input id="middle-name" type="text" name="middle-name" value="0" class="form-control col-md-3 col-xs-12"></td>
<c:choose>
<c:when test="${exam.name == 'Algebra 2 (Common Core)'}">
<th scope="row"><input type="checkbox" class="flat"></th>
</c:when>
<c:otherwise>
<th scope="row"></th>
</c:otherwise>
</c:choose>
</tr>
</c:forEach>
<!-- Other Stuff Goes Here -->
</form:form>
So in short, would someone be willing to show me how to set up the form in the way I described above? Thanks in advance.
Your question is a bit broad however you could try this as below. I have only covered exams. Principal for documents will be the same.
You will need a couple of new classes to capture the submitted form inputs:
Order Form to Capture Selections
public class OrderForm{
private List<ExamWrapper> allAvailableExams = new ArrayList<>();
private XOptionPrintWrapper selectedWrapper;
public OrderForm(){
}
//getters and setters
}
Exam Wrapper : Decorates an Exam with a 'selected' property
public class ExamWrapper{
private boolean selected;
private Exam exam;
public ExamWrapper(Exam exam){
this.exam = exams;
}
//getters and setters
}
Change Contoller to
public class OrderController{
//Exams model populated by the method below
//moved as we also need it populated on POST
#RequestMapping(value = { "/order" }, method = RequestMethod.GET)
public String order(ModelMap modelMap) {
//only needed on GET so put in model here
List<XOptionPrintWrapper> availableWrappers = //someList;
modelMap.put("availableWrappers", availableWrappers);
return "order";
}
//handles for submit
//model atribute is automatically populated by the framework
#RequestMapping(value = { "/order" }, method = RequestMethod.POST)
public String order(#ModelAttribute("orderForm") OrderForm orderForm) {
//process selected exams
return "nextView";
}
//on get populates the initial model for display
//on post create an instance which the form params will be bound to
#ModelAttribute("orderForm")
public OrderForm getOrderForm(){
OrderForm orderForm = new OrderForm();
List<Exam> exams = examService.findAllExams();
for(Exam exam : exams){
orderForm.getAllAvailableExams.add(new ExamWrapper(exam));
}
return orderForm;
}
}
In JSP use Sping support for binding to indexed properties:
<form:form method="POST" modelAttribute="orderForm" class="form-horizontal form-label-left">
<c:forEach items="${orderForm.allAvailableExams}" var="exam" varStatus="status">
<tr>
<th scope="row"><input name="allAvailableExams[${status.index}].selected"
type="checkbox" class="flat"></th>
</tr>
</c:forEach>
<form:select path="selectedWrapper">
<form:options items="${availableWrappers}"
itemValue="somePropertyOfXOptionPrintWrapper "
itemLabel="somePropertyOfXOptionPrintWrapper " />
</form:select>
</form>
I obviously haven't been able to try all of this but think it should all be okay.

Spring Boot, Thymeleaf, ManyToMany checkboxes evaluation

I've seen a lot of examples on the Internet and looks like the solution should work fine. But still could not make my code working.
User:
#Entity
#Table(name = "users")
public class User implements Serializable{
private static final long serialVersionUID = 1L;
...
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinTable(name = "user_usertypes", joinColumns = #JoinColumn(name = "user_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "usertype_id", referencedColumnName = "id"))
private Set<UserType> userTypes;
}
UserType:
#Entity
#Table(name = "usertypes")
public class UserType implements Serializable{
private static final long serialVersionUID = 1L;
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#Version
#Column(name = "version")
private Integer version;
#Column(name = "name")
private String name;
#ManyToMany(mappedBy = "userTypes")
private Set<User> users;
#Override
public int hashCode() {
int hash = 5;
hash = 83 * hash + Objects.hashCode(this.id);
return hash;
}
#Override
public boolean equals(Object obj) {
System.out.println("comparing objects");
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()){
return false;
}
final UserType other = (UserType) obj;
return Objects.equals(this.id, other.id);
}
}
User Controller:
#Controller
public class UserController {
#RequestMapping(value = "/user", method = RequestMethod.POST)
public String saveUser(#Valid #ModelAttribute("user") User user, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return "users/userform";
}
System.out.println(user.getUserTypes());
userService.saveUser(user);
return "redirect:/user/" + user.getId();
}
#InitBinder
private void initBinder(ServletRequestDataBinder binder) {
binder.registerCustomEditor(Set.class, "userTypes", new CustomCollectionEditor(Set.class) {
protected Object convertElement(Object element) {
if (element != null) {
System.out.println("From Controller: " + element.toString());
return userTypeService.findOne(Integer.parseInt(element.toString()));
}
return null;
}
});
}
userform:
<form th:object="${user}" th:action="#{/user}" method="post">
<input type="hidden" th:field="*{id}"/>
<ul>
<li th:each="type : ${types}">
<input type="checkbox" th:id="${type.id}" th:field="*{userTypes}" th:value="${type.id}"/>
<label th:for="${type.id}" th:text="${type.name}">name</label>
</li>
</ul>
<form>
The initBinder isn't called on submit. Only on page load.
So, my controller cannot get the userTypes objects. What is missing? Thank you!
I found an easy and quick solution. Probably, not the best one, but it works as expected. Hope, it will help someone.
User Entity:
private List<UserType> userTypes = new ArrayList<>();
In the controller, I created a helper that creates a new List for the current user to match the indexes on the form:
public String edit(#PathVariable Integer id, Model model) {
model.addAttribute("user", updatedTypes(userService.getUserById(id)));
model.addAttribute("types", userTypeService.getAllUserTypes());
return "users/userform";
}
private User updatedTypes(User user) {
List<UserType> userTypes = new ArrayList<>();
for (long i = 0; i < userTypeService.count(); i++) {
userTypes.add(new UserType());
}
for (UserType type : user.getUserTypes()) {
userTypes.add(type.getId() - 1, type);
}
user.setTypes(userTypes);
return user;
}
Template:
<li th:each="type, stat : ${types}">
<input type="checkbox" th:field="*{userTypes[__${stat.index}__]}"
th:value="${type.id}"/>
<label th:for="|userTypes${stat.index}|+1" th:text="${type.name}">
name
</label>
</li>
Also, I got rid of the initBinder method. I don't know why, but it absolutely useless.

Resources