Failed to convert property value of type 'java.lang.String' - spring

What am I doing wrong here?
<select th:field="*{role}">
<option value="#" disabled="disabled" selected="selected">Role...</option>
<option th:each="r : ${roles}" th:value="${r}" th:text="${r.name}">Developer</option>
</select>
I am getting this error:
Field error in object 'collaborator' on field 'role': rejected value
[33]; codes
[typeMismatch.collaborator.role,typeMismatch.role,typeMismatch.com.imprender.instateam.model.Role,typeMismatch];
arguments
[org.springframework.context.support.DefaultMessageSourceResolvable:
codes [collaborator.role,role]; arguments []; default message [role]];
default message [*Failed to convert property value of type
'java.lang.String' to required type
'com.imprender.instateam.model.Role' for property 'role'*; nested
exception is java.lang.IllegalStateException: Cannot convert value of
type 'java.lang.String' to required type
'com.imprender.instateam.model.Role' for property 'role': no matching
editors or conversion strategy found]
It says:
**Failed to convert property value of type 'java.lang.String' to required type 'com.imprender.instateam.model.Role' for property
'role'**
but I don't understand where I am doing that.
I think I am not properly transmiting the value selected in the . I thought the object sent would be the one declared in the valueoption that gets selected, but obviously I got it wrong and can't find the way to do it properly.
The model:
package com.imprender.instateam.model;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
#Entity
public class Collaborator {
#Id
//Todo: check strategy
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotNull
#Pattern(regexp = "([A-Z][a-zA-Z]*\\s*)+")
private String name;
//Todo: check, do we want to create a new table to associate values?
#NotNull
#ManyToOne
private Role role;
public Collaborator() {
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Role getRole() {
return role;
}
public void setRole(Role role) {
this.role = role;
}
}

Your problem is that in your select says the field is a role, entity type Role, but in your options the value is an ID, some primitive value, then doesnt match. You can change to this
<select th:field="*{role.id}">
<option value="#" disabled = "disabled" selected="selected">Role...</option>
<option th:each="r : ${roles}" th:value="${r.id}" th:text="${r.name}">Developer</option>
</select>

Related

Thymeleaf Spring JPA One to Many type mismatch

I have a Spring Jpa project with a One to Many relationship.
The error when submitting form is:
Field error in object 'product' on field 'category': rejected value [2]; codes [typeMismatch.product.category,typeMismatch.category,typeMismatch.com.example.demo.category.Category,typeMismatch];
Here the reject value [2] is the category_id. Why is thymeleaf sending id in the form.
I also tried changing th:value=${cat}
Product
#Entity
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(length = 128, nullable = false, unique = true)
private String name;
private float price;
#ManyToOne
#JoinColumn(name = "category_id")
private Category category;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public float getPrice() {
return price;
}
public void setPrice(float price) {
this.price = price;
}
public Category getCategory() {
return category;
}
public void setCategory(Category category) {
this.category = category;
}
}
Category Class
#Entity
public class Category {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Column(length = 45, nullable = false, unique = true)
private String name;
#OneToMany(mappedBy="category")
private Set<Product> products;
public Category() {
}
public Category(Integer id) {
this.id = id;
}
public Category(String name) {
this.name = name;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set<Product> getProducts() {
return products;
}
public void setProducts(Set<Product> products) {
this.products = products;
}
}
Product Controller
#Controller
public class ProductController {
#Autowired
private ProductRepository productRepo;
#Autowired
private CategoryRepository categoryRepo;
#GetMapping("/products/new")
public String showNewProductForm(Model model) {
List<Category> listCategories = categoryRepo.findAll();
model.addAttribute("product", new Product());
model.addAttribute("listCategories", listCategories);
return "product_form";
}
#PostMapping("/products/save")
public String saveProduct(Product product) {
productRepo.save(product);
return "redirect:/";
}
}
Product Form Page
<body>
<div class="container text-center">
<div><h1>Create New Product</h1></div>
<form th:action="#{/products/save}" th:object="${product}" method="post" style="max-width: 600px; margin:0 auto;">
<div class="m-3">
<div class="form-group row">
<label class="col-form-label col-sm-4">Product Name: </label>
<div class="col-sm-8">
<input type="text" th:field="*{name}" class="form-control" required />
</div>
</div>
<div class="form-group row">
<label class="col-form-label col-sm-4">Product Price: </label>
<div class="col-sm-8">
<input type="number" step="0.1" th:field="*{price}" class="form-control" required />
</div>
</div>
<div class="form-group row">
<label class="col-form-label col-sm-4">Category: </label>
<div class="col-sm-8">
<select th:field="*{category}" class="form-control" required>
<th:block th:each="cat: ${listCategories}">
<option th:text="${cat.name}" th:value="${cat.id}" />
**//I also tried changing to th:value="${cat}" but still get the same error //**
</th:block>
</select>
</div>
</div>
<div class="text-center p-3">
<button type="submit" class="btn btn-primary">Save</button>
</div>
</div>
</form>
</div>
</body>
The error
WARN 8636 --- [nio-8080-exec-4] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 1 errors
Field error in object 'product' on field 'category': rejected value [2]; codes [typeMismatch.product.category,typeMismatch.category,typeMismatch.com.example.demo.category.Category,typeMismatch];
arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [product.category,category]; arguments []; default message [category]];
default message [Failed to convert property value of type 'java.lang.String' to required type 'com.example.demo.category.Category' for property 'category';
nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [#javax.persistence.ManyToOne #javax.persistence.JoinColumn com.example.demo.category.Category] for value '2';
nested exception is org.springframework.dao.InvalidDataAccessApiUsageException: Provided id of the wrong type for class com.example.demo.category.Category. Expected: class java.lang.Integer, got class java.lang.Long;
nested exception is java.lang.IllegalArgumentException: Provided id of the wrong type for class com.example.demo.category.Category. Expected: class java.lang.Integer, got class java.lang.Long]]
I found where I made my mistake. The issue was in my Category Repository class. Category class has an ID of Integer. However in the Category Repository, I defined the ID type as Long.
The mistake code
public interface CategoryRepository extends JpaRepository<Category, Long> {
}
The correct code
public interface CategoryRepository extends JpaRepository<Category, Integer> {
}
Just one thing is that the error stracktrace is a bit counter intuitive.
Please give any suggestions as to how one could have traced back the error code to the repository class without going line by line of all codes.
The error stacktrace shows that there is a type mismatch for the id field in Category entity :
default message [Failed to convert property value of type 'java.lang.String' to required type 'com.example.demo.category.Category' for property 'category';
nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [#javax.persistence.ManyToOne #javax.persistence.JoinColumn com.example.demo.category.Category] for value '2';
nested exception is org.springframework.dao.InvalidDataAccessApiUsageException: Provided id of the wrong type for class com.example.demo.category.Category. Expected: class java.lang.Integer, got class java.lang.Long;
Thymeleaf might be sending this value as you have provided th:value="${cat.id}" as the value. But when spring tries to map the value to the Product entity it expects an Integer value I think from the stacktrace.

Non bean property throwing Bean property is not readable or has an invalid getter method

So my issue is a bit different from others. The error that it is throwing is not a field name but rather the input in a form. I've never encountered this error before
Error. The contents of '' is what I key for password
org.springframework.beans.NotReadablePropertyException: Invalid
property 'Yijian#123' of bean class [com.Alex.UserPackage.User]: Bean
property 'Yijian#123' is not readable or has an invalid getter method:
Does the return type of the getter match the parameter type of the
setter? at
org.springframework.beans.AbstractNestablePropertyAccessor.getPropertyValue(AbstractNestablePropertyAccessor.java:622)
~[spring-beans-5.2.7.RELEASE.jar:5.2.7.RELEASE] at
org.springframework.beans.AbstractNestablePropertyAccessor.getPropertyValue(AbstractNestablePropertyAccessor.java:612)
~[spring-beans-5.2.7.RELEASE.jar:5.2.7.RELEASE] at
org.springframework.validation.AbstractPropertyBindingResult.getActualFieldValue(AbstractPropertyBindingResult.java:104)
~[spring-context-5.2.7.RELEASE.jar:5.2.7.RELEASE]
Entity class
#Entity
#ValidPassword
public class User {
#Pattern(regexp="[a-zA-Z]+", message = "Enter letters only!")
private String firstName;
#Pattern(regexp="[a-zA-Z]+", message = "Enter letters only!")
private String lastName;
private String password;
private String matchingPassword;
private String passportNumber;
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getMatchingPassword() {
return matchingPassword;
}
public void setMatchingPassword(String matchingPassword) {
this.matchingPassword = matchingPassword;
}
}
#ValidPassword custom annotation. The error started to occur after I used the getters for password and matchingPassword
private String message;
#Override
public boolean isValid(User user, ConstraintValidatorContext context) {
String password = user.getPassword();
String matchingPassword = user.getMatchingPassword();
if (password== null || matchingPassword == null) {
return false;
}
System.out.println("PASSWORDS: " + password + matchingPassword);
boolean flag = Pattern.matches("^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!#$%^&*-]).{8,}$", password);
boolean flag1 = password.equals(matchingPassword);
if ( !flag1 ) {
message = "Passwords do not match!";
}
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(message)
.addPropertyNode(password).addConstraintViolation();
return flag && flag1;
}
//Show default message if no special message is set
#Override
public void initialize(ValidPassword validPassword) {
message = validPassword.message();
}
Form password portion
<div class = "row">
<div class="col-sm-6 form-group">
<label>Password : </label> <Input type="password"
th:field="*{password}" th:required="required" class="form-control" />
<p th:if="${#fields.hasErrors('password')}"
th:errors="*{password}" class="alert alert-danger"></p>
</div>
You are passing the property value instead of the property name to addPropertyNode(password).
Replace the following:
context.buildConstraintViolationWithTemplate(message)
.addPropertyNode(password).addConstraintViolation();
with:
context.buildConstraintViolationWithTemplate(message)
.addPropertyNode("password").addConstraintViolation();

Annotation #AssertTrue is not working properly

My poblem is connected with #AssertTrue annotation and some how with Thymeleaf. Actualy I should scheck password for the equality on a registration web page, for this I hava a parameters in my Registrartion Form class, they are Name,password, check_password,check_password_condition, adress. Actually I have made several changes in y code since I have aske my question here. I have used equals() method instead of == and acording this post
Spring validation #AssertTrue
have set a validation property boolean check_password_condition but my code still does not work. This way I use Errors interface to ask my page for validation rules. I think that using of the annotation #AssertTue for my method sould automatically call my method isCheckpassword() from RegistrationForm class and then in #PostMapping method of the Controller asked for the validation rule this.password.equals(this.check_password)
Am I right or not????
#AssertTrue(message = "{RegistrationForm.check_password.AssertTrue}")
public boolean isCheckpassword(){
if(this.password.equals(this.check_password)){
return this.check_password_condition=true;
}
else return this.check_password_condition=false;
}
#PostMapping("/register")
String registerNew(#Valid RegistrationForm form, Errors result) {
if (result.hasErrors()) {
return "register";
}
customerManagement.createCustomer(form);
return "redirect:/";
}
But I get whitepage error when the conditions for the creating new user are not met.
here additionally I provide my Thymeleaf code:
<div class="field">
<label th:text="#{register.check_password}" for="check_password">Repasswort</label>
<input id="check_password" name="check_password" th:field="*{check_password}" th:errorclass="fieldError" type="password"
required="required"/><br/>
<p th:if="${#fields.hasErrors('check_password')}" th:errors="*{check_password}">Das Passwort darf
nicht leer sein.</p>
</div>
This is my Registration From class
class RegistrationForm {
#NotEmpty(message = "{RegistrationForm.name.NotEmpty}") //
private final String name;
#Size(min = 2, max = 14, message = "{RegistrationForm.password.Size}")
#NotEmpty(message = "{RegistrationForm.password.NotEmpty}") //
private final String password;
#NotEmpty(message = "{RegistrationForm.check_password.NotEmpty}") //
private String check_password;
private boolean check_password_condition;
#NotEmpty(message = "{RegistrationForm.address.NotEmpty}") // s
private final String address;
public RegistrationForm(String name, String password,String check_password, String address) {
this.name = name;
this.password = password;
this.check_password=check_password;
this.address = address;
}
#AssertTrue(message = "{RegistrationForm.check_password.AssertTrue}")
public boolean isCheckpassword(){
if(this.password.equals(this.check_password)){
return this.check_password_condition=true;
}
else return this.check_password_condition=false;
}
//return this.password != null && this.password.equals(this.check_password) : this.setCheck_password(); }
public String getName() {
return name;
}
public String getPassword() { return password; }
public String getCheck_password(){return check_password;}
public String getAddress() {
return address;
}
}
Please help to solve this problem when
error info from Whitelabel errorpage is:
Caused by: org.thymeleaf.exceptions.TemplateProcessingException: Exception evaluating SpringEL expression: "#fields.hasErrors('*')" (template: "register" - line 29, col 42)
at org.thymeleaf.spring5.expression.SPELVariableExpressionEvaluator.evaluate(SPELVariableExpressionEvaluator.java:290)
at org.thymeleaf.standard.expression.VariableExpression.executeVariableExpression(VariableExpression.java:166)
at org.thymeleaf.standard.expression.SimpleExpression.executeSimple(SimpleExpression.java:66)
at org.thymeleaf.standard.expression.Expression.execute(Expression.java:109)
at org.thymeleaf.standard.expression.Expression.execute(Expression.java:138)
at org.thymeleaf.standard.expression.Expression.execute(Expression.java:125)
It may not fully solve your issue, but looks like your String comparison is incorrect, as you shouldn't use ==.
Instead, use the String#equals() method or even Objects.equals(). This answer provider a great explanation on this.
Here's what your code can be like:
#AssertTrue
public boolean checkPasswod() {
return Objects.equals(check_password, password);
}

Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Error while committing the transaction

I have a error when I try to insert a user in the database.
I made i custom annotation which verify if the password match with the confirmation password it works when the field not matches , but when the passowrd matches i have this error :
This is my code This is my field match #Annotation :
package mereuta.marian.tennis01.annotations;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Constraint(validatedBy = FieldsValueMatchValidator.class)
#Target({ ElementType.TYPE })
#Retention(RetentionPolicy.RUNTIME)
public #interface FieldsValueMatch {
String message() default "Fields values don't match!";
String field();
String fieldMatch();
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
#Target({ ElementType.TYPE })
#Retention(RetentionPolicy.RUNTIME)
#interface List {
FieldsValueMatch[] value();
}
}
This is the Field Validator :
package mereuta.marian.tennis01.annotations;
import mereuta.marian.tennis01.model.Utilisateur;
import org.springframework.beans.BeanWrapperImpl;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class FieldsValueMatchValidator implements ConstraintValidator<FieldsValueMatch , Object> {
private String field;
private String fieldMatch;
#Override
public void initialize(FieldsValueMatch fieldsValueMatch) {
this.field=fieldsValueMatch.field();
this.fieldMatch=fieldsValueMatch.fieldMatch();
}
#Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
Object fieldValue = new BeanWrapperImpl(value)
.getPropertyValue(field);
Object fieldMatchValue = new BeanWrapperImpl(value)
.getPropertyValue(fieldMatch);
if (fieldValue != null) {
return fieldValue.equals(fieldMatchValue);
} else {
return fieldMatchValue == null;
}
}
}
This is my Entity :
#FieldsValueMatch(field = "password", fieldMatch = "confirmPassword",
message = "Password do not match!")
#Entity(name = "utilisateurs")
public class Utilisateur {
#Id #GeneratedValue
#Column(name = "id_utilisateur")
private Integer id;
#NotNull
#Size(min = 4, max = 255)
#Column(name = "password")
private String password;
#Transient
#NotNull
private String confirmPassword;
This is the Controller :
#PostMapping("/addUtilisateur")
public String addUtilisateur(#Valid #ModelAttribute("utilisateur") Utilisateur utilisateur, BindingResult bindingResult, Model model) {
if (bindingResult.hasErrors() ) {
model.addAttribute("message", "le mot de passe ne correspond pas");
return "utilisateur/formRegister";
}
utilisateurMetier.creerUtilisateur(utilisateur);
return "utilisateur/utilisateurAjoute";
}
And finally the View :
<div class="container">
<form id="contact" th:action="#{addUtilisateur}" method="post" th:object="${utilisateur}">
<h3>Créer compte</h3>
<input placeholder="password" type="password" th:field="*{password}" tabindex="2" required/>
<span class="text text-danger" th:if="${#fields.hasErrors('password')}" th:errors="*{password}"></span>
</fieldset>
<fieldset>
<input placeholder="password" type="password" th:field="*{confirmPassword}" tabindex="2" required/>
<span class="text text-danger" th:if="${#fields.hasErrors('confirmPassword')}"
th:errors="*{confirmPassword}" th:text="${message}"></span>
</fieldset>
For the custom annotations I find a example on : https://www.baeldung.com/spring-mvc-custom-validator
#Override
public void creerUtilisateur(Utilisateur utilisateur) {
Role role;
float credit = 0;
boolean actif = true;
role = roleRepository.getOne(3);
System.out.println(role);
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
utilisateur.setPassword(encoder.encode(utilisateur.getPassword()));
utilisateur.setRole(role);
utilisateur.setCredit(credit);
utilisateur.setActif(actif);
utilisateurRepository.save(utilisateur);
}
Thank you in advance for your help
As already mentioned the ContraintViolationException is thrown inside the 'creerUtilisateur' method. So the validation of your Utilisateur bean at the time it's passed to your Spring MVC controller method (addUtilisateur(#Valid #ModelAttribute("utilisateur")...) works correctly when both fields (password, confirmPassword) have the same value. Later, you encode the password and change the value of your Utilitsateur's 'password' instance variable:
utilisateur.setPassword(encoder.encode(utilisateur.getPassword()));
Now, 'password' and 'passwordConfirm' are not equal anymore! When persisting this entity in utilisateurRepository.save(utilisateur); JPA will again bean-validate your entity before saving it to database (pre-persist). The validation gets automatically executed when JPA/Hibernate triggers a pre-persist, pre-update or pre-remove lifecycle event. And then the ContraintViolationException is thrown!
In your creerUtilisateur method simply set the encoded password for both, 'password' and 'passwordConfirm', instance variables and hereby ensure that they still pass your equality check in FieldsValueMatchValidator.isValid(Object value, ConstraintValidatorContext context):
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
final String encodedPassword = encoder.encode(utilisateur.getPassword());
utilisateur.setPassword(encodedPassword);
utilisateur.setPasswordConfirm(encodedPassword);
//...
utilisateurRepository.save(utilisateur);
You could also try to customize JPA's bean validation behaviour:
https://www.thoughts-on-java.org/automatically-validate-entities-with-hibernate-validator/
Disable Hibernate validation for Merge/Update

How can get the object in thymeleaf select option?

Here in autocomplete I get the Product name as expected.
I want to do some calculation based on the product selected. But in doCalculation function i'm getting id instead of 'price'. So calculation not working as expected.
Suppose if i change String idExpression = "#{price}"; then calculation works as expected but Order not saved. Since getting error as below
Failed to convert property value of type [java.lang.String] to required type [com.myapp.domain.Product] for property product; nested exception is
org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [#javax.persistence.OneToOne
#io.springlets.format.EntityFormat com.myapp.domain.Product] for value 2500; nested exception is java.lang.IllegalStateException: Parsers are not allowed to return null: io.springlets.format.EntityParser#2201ba1c
So I want to get the price for calculation at the same time the save feature should not be broken. For now either 1st or 2nd is working for me.
ProductsCollectionThymeleafController.java
#GetMapping(produces = MediaType.APPLICATION_JSON_VALUE, name = "select2", value = "/s2")
#ResponseBody
public ResponseEntity<Select2DataSupport<Product>> select2(GlobalSearch search, Pageable pageable,
Locale locale) {
Page<Product> products = getProductService().findAll(search, pageable);
String idExpression = "#{id}";
Select2DataSupport<Product> select2Data =
new Select2DataWithConversion<Product>(products, idExpression, getConversionService());
return ResponseEntity.ok(select2Data);
}
OrderCollectionThymeleafController.java
#PostMapping(name = "create")
public ModelAndView create(#Valid #ModelAttribute Order order, BindingResult result,
Model model) {
if (result.hasErrors()) {
populateForm(model);
return new ModelAndView("/order/create");
}
Order newOrder = getOrderService().save(order);
UriComponents showURI = getItemLink().to(OrderItemThymeleafLinkFactory.SHOW)
.with("order", newOrder.getId()).toUri();
return new ModelAndView("redirect:" + showURI.toUriString());
}
orderview.html
<form class="form-horizontal validate" method="POST" data-th-object="${order}" data-th-action="#{${collectionLink.to('create').with('order', order.id)}}">
<fieldset id="containerFields">
<div class="form-group has-error has-feedback" data-z="3c00987d" id="servicio-product-field" data-th-classappend="${#fields.hasErrors('product')}? 'has-error has-feedback'" data-th-class="form-group" data-th-with="collectionLink=${#linkBuilder.of('ProductsCollectionThymeleafController')}">
<label for="product" class="col-md-3 control-label" data-th-text="#{label_servicio_product}">Product</label>
<div class="col-md-6">
<!-- Select2 -->
<select data-th-field="*{product}" onChange="doCalculation()" class="form-control dropdown-select-ajax" data-allow-clear="true" data-data-ajax--url="${collectionLink.to('select2')}" data-ajax--cache="true" data-ajax--delay="250" data-ajax--data-type="json" data-data-placeholder="#{info_select_an_option}">
<option data-th-unless="*{product} == null" data-th-value="*{product.id}" data-th-text="*{{product}}" selected="selected">Product</option>
</select>
<span data-th-classappend="${#fields.hasErrors('product')}? 'glyphicon glyphicon-remove form-control-feedback'" class="glyphicon glyphicon-remove form-control-feedback" data-th-if="${#fields.hasErrors('product')}" aria-hidden="true"></span>
<span id="product-error" class="help-block" data-th-if="${#fields.hasErrors('product')}" data-th-errors="*{product}">Error message.</span>
</div>
</div>
<script>
function doCalculation() {
var price = document.getElementById("product").value;
alert("price: " + price);
// Do some calculation
}
doCalculation();
</script>
</fieldset>
</form>
Product.java
#RooJavaBean
#RooToString
#RooJpaEntity
#RooEquals(isJpaEntity = true)
#Entity
#EntityFormat
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String productName;
#Min(1L)
#NumberFormat
private Integer price;
#OneToOne(fetch = FetchType.LAZY)
#EntityFormat
private Order order;
public static final String ITERABLE_TO_ADD_CANT_BE_NULL_MESSAGE = "The given Iterable of items to add can't be null!";
public static final String ITERABLE_TO_REMOVE_CANT_BE_NULL_MESSAGE = "The given Iterable of items to add can't be null!";
public Long getId() {
return this.id;
}
public void setId(Long id) {
this.id = id;
}
public Integer getPrice() {
return this.price;
}
public void setPrice(Integer price) {
this.price = price;
}
public String getProductName() {
return this.productName;
}
public void setProductName(String productName) {
this.productName = productName;
}
public Order getOrder() {
return this.order;
}
public void setOrder(Order order) {
this.order= order;
}
}
Order.java
#RooJavaBean
#RooToString
#RooJpaEntity
#RooEquals(isJpaEntity = true)
#Entity
#EntityFormat
public class Order {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Version
private Integer version;
#OneToOne(cascade = { javax.persistence.CascadeType.MERGE,
javax.persistence.CascadeType.PERSIST }, fetch = FetchType.LAZY, mappedBy = "order")
#RooJpaRelation(type = JpaRelationType.AGGREGATION)
#EntityFormat
private Product product;
public static final String ITERABLE_TO_ADD_CANT_BE_NULL_MESSAGE = "The given Iterable of items to add can't be null!";
public static final String ITERABLE_TO_REMOVE_CANT_BE_NULL_MESSAGE = "The given Iterable of items to add can't be null!";
/**
* This `equals` implementation is specific for JPA entities and uses the
* entity identifier for it, following the article in
* https://vladmihalcea.com/2016/06/06/how-to-implement-equals-and-hashcode-using-the-jpa-entity-identifier/
*
* #param obj
* #return Boolean
*/
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
// instanceof is false if the instance is null
if (!(obj instanceof Order)) {
return false;
}
return getId() != null && Objects.equals(getId(), ((Order) obj).getId());
}
/**
* This `hashCode` implementation is specific for JPA entities and uses a
* fixed `int` value to be able to identify the entity in collections after
* a new id is assigned to the entity, following the article in
* https://vladmihalcea.com/2016/06/06/how-to-implement-equals-and-hashcode-using-the-jpa-entity-identifier/
*
* #return Integer
*/
public int hashCode() {
return 31;
}
public Long getId() {
return this.id;
}
public void setId(Long id) {
this.id = id;
}
public Integer getVersion() {
return this.version;
}
public void setVersion(Integer version) {
this.version = version;
}
public Product getProduct() {
return this.product;
}
public void setProduct(Product product) {
this.product = product;
}
public void addToProduct(Product product) {
if (product == null) {
removeFromProduct();
} else {
this.product = product;
product.setOrder(this);
}
}
public void removeFromProduct() {
if (this.product != null) {
product.setOrder(null);
}
this.product = null;
}
}
By default, the Select2DataWithConversion data type only returns the identifier that will be set as value attribute of the option element and the representation of the object (in your case the product name) as the text attribute of the option element.
That is the minimum info that the select2 component needs to be constructed.
https://select2.org/data-sources/formats
However, as you described in your answer, it's really common to need more info in your Select2 component. For that reason, we overloaded the constructor of Select2DataWithConversion including a boolean parameter to return the entire information of the object.
Check this overloaded constructor here:
https://github.com/DISID/springlets/blob/master/springlets-data/springlets-data-commons/src/main/java/io/springlets/data/web/select2/Select2DataWithConversion.java#L76
So, you just need to change your ProductsCollectionThymeleafController.java to use it like:
Select2DataSupport<Product> select2Data = new Select2DataWithConversion<Product>(products, idExpression, getConversionService(), true);
Now that yor select2 component is going to receive extra information, you need to store it in a data-* attribute of your select2 option during the option creation. To do that use the templateSelection function that offers the select2 component.
https://select2.org/programmatic-control/retrieving-selections#using-a-jquery-selector
Now, your doCalculation should obtain the selected option and after that, the data-price attribute.
<script>
function doCalculation() {
var price = $('#product').find(':selected').data('price');
alert("price: " + price);
//Do some calculation
}
doCalculation();
</script>
And that's all!
EDIT: I've just create the following project where you could find your desired behaviour: https://github.com/jcagarcia/proofs/tree/master/select2-with-extra-info
Just check the necessary changes in the following commit: https://github.com/jcagarcia/proofs/commit/105c18f7ad0da4d1e2089fbf71d4f27ccdb60689
Hope it helps,

Resources