Spring form:select multiple selected value? - spring

I have this edit form:
I want that the roles of the user get selected. If this were one-to-many relation I know that I can do something like this:
<form:label path="roles">Roles:</form:label>
<form:select multiple="true" path="roles">
<c:forEach items="${roles}" var="rol">
<c:choose>
<c:when
test="${usuarioEdit.rol.id ==rol.id}">
<option value="${rol.id}" selected="selected">${rol.nombre}</option>
</c:when>
<c:otherwise>
<option value="${rol.id}">${rol.nombre}</option>
</c:otherwise>
</c:choose>
</c:forEach>
</form:select>
<form:errors cssStyle="color:red" path="roles"></form:errors>
But this is a many-to-many relation. How can I get selected the options in an edit form?. Is there an easy form?
This code works, but I wonder if spring gives any facilities:
<form:select multiple="true" path="roles">
<c:forEach items="${roles}" var="rol">
<c:set var="isSelected" value="false" />
<c:forEach items="${rolesUsu}" var="rolUsu">
<c:if test="${rolUsu.getRol().getId()==rol.id}">
<c:set var="isSelected" value="true" />
</c:if>
</c:forEach>
<c:choose>
<c:when test="${isSelected}">
<option value="${rol.id}" selected="selected">${rol.nombre}</option>
</c:when>
<c:otherwise>
<option value="${rol.id}">${rol.nombre}</option>
</c:otherwise>
</c:choose>
</c:forEach>
</form:select>
Edit:
In my controller I have:
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(Set.class, "roles",
new RolCollectionEditor(Set.class, rolDao));
}
RolCollectionEditor:
public class RolCollectionEditor extends CustomCollectionEditor {
private final RolDAO rolDao;
public RolCollectionEditor(Class<?> collectionType, RolDAO rolDao) {
super(collectionType);
this.rolDao = rolDao;
}
#Override
protected Object convertElement(Object element) {
String rolId = (String) element;
Rol rol = rolDao.findById(rolId);
Usuario_Rol usuRol = new Usuario_Rol();
//Agregamos un usuario vacio temporal
//y lo sobreescribimos en el controlador
Usuario usuario = new Usuario();
usuRol.setUsuario(usuario);
usuRol.setRol(rol);
usuRol.setFechaCreacion(new Date());
usuRol.setFechaModificacion(new Date());
usuRol.setStatus("activo");
return usuRol;
}
}
Here Usuario_Rol is an intermediate table for the many to many relation, that have other attributes besides the userId and rolId.
Edit2:
Rol class:
#Entity
#Table(name = "rol", uniqueConstraints = { #UniqueConstraint(columnNames = "nombre") })
public class Rol implements Serializable{
#Id
#Column(name = "_id")
private String id;
#Column(name = "nombre")
#NotNull
private String nombre;
#Column(name = "descripcion")
private String descripcion;
#Column(name = "status")
private String status;
#Column(name = "fechaCreacion")
private Date fechaCreacion;
#Column(name = "fechaModificacion")
private Date fechaModificacion;
#Column(name = "fechaSincronizacion")
private Date fechaSincronizacion;
#OneToMany(fetch = FetchType.EAGER, mappedBy = "usuarioRol_pk.rol", orphanRemoval = true, cascade=CascadeType.ALL)
private Set<Usuario_Rol> usuarios = new HashSet<Usuario_Rol>(0);
//getters and setters
#Override
final public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((nombre == null) ? 0 : nombre.hashCode());
return result;
}
#Override
final public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (!(obj instanceof Rol))
return false;
Rol other = (Rol) obj;
if (nombre == null) {
if (other.nombre != null)
return false;
} else if (!nombre.equals(other.nombre))
return false;
return true;
}
Class Usuario:
#Entity
#Table(name = "usuario", uniqueConstraints = {
#UniqueConstraint(columnNames = "login"),
#UniqueConstraint(columnNames = "correo") })
public class Usuario implements Serializable {
#Id
#Column(name = "_id")
private String id;
#Column(name = "nombre")
#NotEmpty
private String nombre;
#Column(name = "apellido")
#NotEmpty
private String apellido;
#Column(name = "login")
#Size(min = 4)
#NotEmpty
private String login;
#Column(name = "password")
#NotEmpty
#Size(min = 4)
private String password;
#Column(name = "salt")
private String salt;
#Column(name = "correo")
#NotEmpty
#Email
private String correo;
#Column(name = "token")
private String token;
#Column(name = "status")
private String status;
#Column(name = "fechaUltimoLogin")
private Date fechaUltimoLogin;
#Column(name = "fechaCreacion")
private Date fechaCreacion;
#Column(name = "fechaModificacion")
private Date fechaModificacion;
#Column(name = "fechaSincronizacion")
private Date fechaSincronizacion;
#NotEmpty
#OneToMany(fetch = FetchType.EAGER, mappedBy = "usuarioRol_pk.usuario", orphanRemoval = true, cascade = CascadeType.ALL)
private Set<Usuario_Rol> roles = new HashSet<Usuario_Rol>(0);
//constructor, getters and setters.
#Override
final public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((login == null) ? 0 : login.hashCode());
return result;
}
#Override
final public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (!(obj instanceof Usuario))
return false;
Usuario other = (Usuario) obj;
if (login == null) {
if (other.login != null)
return false;
} else if (!login.equals(other.login))
return false;
return true;
}
Intermediate class:
#Entity
#Table(name = "usuario_rol")
#AssociationOverrides({
#AssociationOverride(name = "usuarioRol_pk.usuario", joinColumns = #JoinColumn(name = "idUsuario")),
#AssociationOverride(name = "usuarioRol_pk.rol", joinColumns = #JoinColumn(name = "idRol"))
})
public class Usuario_Rol implements Serializable{
#EmbeddedId
private Usuario_RolId usuarioRol_pk = new Usuario_RolId();
#Temporal(TemporalType.DATE)
#Column(name = "fechaCreacion")
private Date fechaCreacion;
#Temporal(TemporalType.DATE)
#Column(name = "fechaModificacion")
private Date fechaModificacion;
#Temporal(TemporalType.DATE)
#Column(name = "fechaSincronizacion")
private Date fechaSincronizacion;
#Column(name = "status")
private String status;
//gettters, setters
#Override
final public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((usuarioRol_pk == null) ? 0 : usuarioRol_pk.hashCode());
return result;
}
#Override
final public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (!(obj instanceof Usuario_Rol))
return false;
Usuario_Rol other = (Usuario_Rol) obj;
if (usuarioRol_pk == null) {
if (other.usuarioRol_pk != null)
return false;
} else if (!usuarioRol_pk.equals(other.usuarioRol_pk))
return false;
return true;
}
Usuario_RolId:
#Embeddable
public class Usuario_RolId implements Serializable{
#ManyToOne
private Usuario usuario;
#ManyToOne
private Rol rol;
public Usuario getUsuario() {
return usuario;
}
public void setUsuario(Usuario usuario) {
this.usuario = usuario;
}
public Rol getRol() {
return rol;
}
public void setRol(Rol rol) {
this.rol = rol;
}
#Override
final public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((rol == null) ? 0 : rol.hashCode());
result = prime * result + ((usuario == null) ? 0 : usuario.hashCode());
return result;
}
#Override
final public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (!(obj instanceof Usuario_RolId))
return false;
Usuario_RolId other = (Usuario_RolId) obj;
if (rol == null) {
if (other.rol != null)
return false;
} else if (!rol.equals(other.rol))
return false;
if (usuario == null) {
if (other.usuario != null)
return false;
} else if (!usuario.equals(other.usuario))
return false;
return true;
}
This last class is used for the trick of simulating a many to many relation. I have followed this tutorial: http://www.mkyong.com/hibernate/hibernate-many-to-many-example-join-table-extra-column-annotation/

Why are you writing your own? Spring should be able to do that for you. Instead of a <c:forEach /> replace that whole block with a <form options .. /> tags. Spring will then be able to do the selection itself (you might need a Converter or PropertyEditor for that).
<form:select multiple="true" path="roles" items="${roles}" itemLabel="nombre" itemValue="id" />
Something along these lines...
Links:
Form Options documentation
Form Select documentation
Reference Guide

If you use this:
<form:select multiple="true" path="roles" items="${roles}" itemLabel="nombre" itemValue="id" />
you need to override toString() method of Usuario_Rol, in the right way for your class, to ensure that Spring pre-selects the initial values for you.

If I understand the question correctly, what you want is your Spring tag to generate HTML like this one:
<select id="roles" name="roles multiple="multiple">
<option value="1">Administrador</option>
<option value="2">Usuario avanzado</option>
<option value="3" selected="selected">Usuario </option>
<option value="4" selected="selected">Invitado</option>
</select>
As you can see, two values are selected ("Usuario" and "Invitado").
The "roles" model attribute that "path" refers to in your Spring tag has be an array instead of a single value. It's as easy as that. Please be aware that I set the array by hand in my controller. I am not familiar with the implications on the ORM side of your code.

I think you want to expect output like this:
<select id="roles" name="roles" multiple="multiple">
<option value="1">Administrador</option>
<option value="2" selected="selected">Usuario avanzado</option>
<option value="3" selected="selected">Usuario </option>
</select>
In model class you can create method for "roles" which should return array. Add your business complexity in that method.
public Integer[] getRoles(){
Integer[] selectedRoles = {2,3};
return selectedRoles;
}
In JSP:
<form:select multiple="true" path="roles">
<form:options items="${...}" itemValue="..." itemLabel="..."/>
</form>

Related

Thymeleaf access object in outerloop from innerloop

i am looping through a list of "roles" , inside each one i loop through a list of "notifications" ( a role can have many notifications )
<div class="row justify-content-center">
<div class="col-sm-3" th:each="role : ${roles}">
<div class="card">
<div class="card-body">
<h5 class="card-title" th:text="${role.name}"></h5>
Notifications :
<div class="input-group mb-3">
<th:block th:each="notif : ${notif_list}">
<div class="custom-control custom-control-inline">
<input type="checkbox" class="custom-control-input"
th:id="${notif.id_notif_type}"
th:value="${notif.id_notif_type}" th:field="${role.notifs}">
<label class="custom-control-label" th:text="${notif.type}"
th:for="${notif.id_notif_type}"></label>
</div>
</th:block>
</div>
</div>
</div>
</div>
</div>
i want access an object "role" inside the notification loop by using th:field="${role.notifs}
here is my Role class :
#Entity
#Table(name = "roles")
#Transactional
public class Role implements Serializable{
public Role() {
}
public Role(Integer id, String name) {
super();
this.id = id;
this.name = name;
}
#Id
#Column(name = "role_id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
#ManyToMany(cascade = CascadeType.MERGE, fetch = FetchType.EAGER)
#JoinTable(name = "roles_notifs", joinColumns = #JoinColumn(name = "role_id"), inverseJoinColumns = #JoinColumn(name = "notif_id"))
private Set<NotifType> notifs = new HashSet<>();
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<NotifType> getNotifs() {
return notifs;
}
public void setNotifs(Set<NotifType> notifs) {
this.notifs = notifs;
}
public void addNotif(NotifType notif) {
notifs.add(notif);
}
public void removeNotif(NotifType notif) {
notifs.remove(notif);
}
#Override
public String toString() {
return name;
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((id == null) ? 0 : id.hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Role other = (Role) obj;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
return true;
}
i got an error on ${role.notifs} , so how can i access the outer object "role" from the inner loop.
i got this error Neither BindingResult nor plain target object for bean name 'role' available as request attribute

Manage JPA Entity with Set data structure - using add method

I see examples where HashSet used in entity, and is treated like other data types. But I am looking to use "add" instead of set whole object
package com.baeldung.manytomany.model;
import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;
#Entity
#Table(name = "student")
public class Student {
#Id
#Column(name = "id")
private Long id;
#ManyToMany
#JoinTable(name = "course_like", joinColumns = #JoinColumn(name = "student_id"), inverseJoinColumns = #JoinColumn(name = "course_id"))
private Set<Course> likedCourses = new HashSet<>();
#OneToMany(mappedBy = "student")
private Set<CourseRating> ratings = new HashSet<>();
#OneToMany(mappedBy = "student")
private Set<CourseRegistration> registrations = new HashSet<>();
// additional properties
public Student() {
}
public Student(Long id) {
this.id = id;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Set<Course> getLikedCourses() {
return likedCourses;
}
public void setLikedCourses(Set<Course> likedCourses) {
this.likedCourses = likedCourses;
}
public Set<CourseRating> getRatings() {
return ratings;
}
public void setRatings(Set<CourseRating> ratings) {
this.ratings = ratings;
}
public Set<CourseRegistration> getRegistrations() {
return registrations;
}
public void setRegistrations(Set<CourseRegistration> registrations) {
this.registrations = registrations;
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((id == null) ? 0 : id.hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Student other = (Student) obj;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
return true;
}
}
Is there a better approach than
public Set<CourseRating> getRatings() {
return ratings;
}
public void setRatings(Set<CourseRating> ratings) {
this.ratings = ratings;
}
I did try
public void addRatings(CourseRating rating) {
this.ratings.add(rating); }
but object is not persisting. I thought save of student should take care of saving rating. What am I missing ?
ok sorted
(1)
public void addRatings(CourseRating rating) {
this.ratings.add(rating); rating.setBlah(this);
}
(2) Updated Entity:
#OneToMany(cascade = CascadeType.ALL, mappedBy = "exam", orphanRemoval = true)
#OrderBy("id")
private Set<CourseRating> ratings = new HashSet<>();
Was using Embeddable, so was getting error - null id generated for:class ... so setting id from available objects
ratings.setId(new ThatKey(some1, some2));

Spring Data save doesn't DELETE

removeRecipe in Cookbook removes a Recipe from a Cookbook. After setting the references to null and removing the entity from the collections the entity is not deleted.
#Entity
public class Cookbook implements Identifiable<Cookbook> {
private static final Logger LOG = LoggerFactory.getLogger(Cookbook.class);
private Long id;
private String title;
private List<CookbookRecipe> cookbookRecipes = new ArrayList<>();
public Cookbook() {}
public Cookbook(String title) {
this.title = title;
}
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
#Basic
#Column(name = "title")
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
#OneToMany(fetch = FetchType.EAGER, mappedBy = "cookbook", cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE}, orphanRemoval = true)
public List<CookbookRecipe> getCookbookRecipes() {
return cookbookRecipes;
}
/**
* The setter is called by hibernate.
* #param cookbookRecipes maybe null, maybe the collection is not even ready for read access.
* Don't do anything with the collection here!
*/
public void setCookbookRecipes(List<CookbookRecipe> cookbookRecipes) {
this.cookbookRecipes = cookbookRecipes;
}
/**
* Returns a List that must remain unchanged.
*/
#Transient
public List<Recipe> getRecipes() {
return Collections.unmodifiableList(getCookbookRecipes().stream().map(CookbookRecipe::getRecipe).collect(Collectors.toList()));
}
public void addRecipe(Recipe recipe, String createdBy, Date createdDate) {
final CookbookRecipe cookbookRecipe = new CookbookRecipe(this, recipe);
cookbookRecipe.setCreatedBy(createdBy);
cookbookRecipe.setCreatedDate(createdDate);
if( !cookbookRecipes.contains(cookbookRecipe) && !recipe.getCookbookRecipes().contains(cookbookRecipe)) {
if( !cookbookRecipes.add(cookbookRecipe) ) {
LOG.error("Failed to add cookbookRecipe " + cookbookRecipe + " to collection cookbookRecipes " + cookbookRecipes);
}
if( !recipe.getCookbookRecipes().add( cookbookRecipe ) ) {
LOG.error("Failed to add cookbookRecipe " + cookbookRecipe + " to collection recipe.getCookbookRecipes " + recipe.getCookbookRecipes());
}
}
}
public void removeRecipe(Recipe recipe) {
for (Iterator<CookbookRecipe> iterator = cookbookRecipes.iterator();
iterator.hasNext(); ) {
CookbookRecipe cookbookRecipe = iterator.next();
if (cookbookRecipe.getCookbook().equals(this) &&
cookbookRecipe.getRecipe().equals(recipe)) {
iterator.remove();
recipe.getCookbookRecipes().remove(cookbookRecipe);
cookbookRecipe.setCookbook(null);
cookbookRecipe.setRecipe(null);
}
}
}
#Override
public String toString() {
return title;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Cookbook that = (Cookbook) o;
return getId() != null && Objects.equals(getId(), that.getId());
}
#Override
public int hashCode() {
return 31;
}
#Override
public boolean equalsByBusinessKey(Cookbook other) {
if (this == other) return true;
if (other == null || getClass() != other.getClass()) return false;
return Objects.equals(getTitle(), other.getTitle());
}
}
#Entity
#Table(name = "cookbook_recipe")
public class CookbookRecipe implements Serializable {
#EmbeddedId
private CookbookRecipePk pk;
#ManyToOne(fetch = FetchType.EAGER, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#MapsId("cookbookId")
private Cookbook cookbook;
#ManyToOne(fetch = FetchType.EAGER, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#MapsId("recipeId")
private Recipe recipe;
private Date createdDate;
private String createdBy;
public CookbookRecipe() {
}
public CookbookRecipe(Cookbook cookbook, Recipe recipe) {
this.cookbook = cookbook;
this.recipe = recipe;
this.pk = new CookbookRecipePk(cookbook.getId(), recipe.getId());
}
public CookbookRecipePk getPk() {
return pk;
}
public void setPk(CookbookRecipePk pk) {
this.pk = pk;
}
#Transient
public Cookbook getCookbook() {
return cookbook;
}
public void setCookbook(Cookbook cookbook) {
this.cookbook = cookbook;
}
#Transient
public Recipe getRecipe() {
return recipe;
}
public void setRecipe(Recipe recipe) {
this.recipe = recipe;
}
#Temporal(TemporalType.DATE)
#Column(name = "CREATED_DATE", nullable = false, length = 10)
public Date getCreatedDate() {
return this.createdDate;
}
public void setCreatedDate(Date createdDate) {
this.createdDate = createdDate;
}
#Column(name = "CREATED_BY", nullable = false, length = 10)
public String getCreatedBy() {
return this.createdBy;
}
public void setCreatedBy(String createdBy) {
this.createdBy = createdBy;
}
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
CookbookRecipe that = (CookbookRecipe) o;
return Objects.equals(getPk(), that.getPk());
}
public int hashCode() {
return 31;
}
#Override
public String toString() {
final StringBuilder sb = new StringBuilder("CookbookRecipe{");
sb.append("pk=")
.append(pk);
sb.append('}');
return sb.toString();
}
}
#Embeddable
public class CookbookRecipePk implements java.io.Serializable {
#Column(name = "cookbook_id")
private Long cookbookId;
#Column(name = "recipe_id")
private Long recipeId;
public CookbookRecipePk() {}
public CookbookRecipePk(Long cookbookId, Long recipeId) {
this.cookbookId = cookbookId;
this.recipeId = recipeId;
}
public Long getCookbookId() {
return cookbookId;
}
public void setCookbookId(Long cookbookId) {
this.cookbookId = cookbookId;
}
public Long getRecipeId() {
return recipeId;
}
public void setRecipeId(Long recipeId) {
this.recipeId = recipeId;
}
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CookbookRecipePk that = (CookbookRecipePk) o;
return null != cookbookId && null != recipeId &&
Objects.equals(cookbookId, that.cookbookId) &&
Objects.equals(recipeId, that.recipeId);
}
public int hashCode() {
return 31;
}
#Override
public String toString() {
final StringBuilder sb = new StringBuilder("CookbookRecipePk{");
sb.append("cookbookId=")
.append(cookbookId);
sb.append(", recipeId=")
.append(recipeId);
sb.append('}');
return sb.toString();
}
}
#Entity
public class Recipe implements Serializable, Identifiable<Recipe> {
private Long id;
private String title;
private Category category;
private List<CookbookRecipe> cookbookRecipes = new ArrayList<>();
public Recipe(String title) {
this.title = title;
}
public Recipe() {}
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
#Basic
#Column(name = "title")
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "category_id", referencedColumnName = "id")
public Category getCategory() {
return category;
}
public void setCategory(Category category) {
this.category = category;
}
#OneToMany(fetch = FetchType.EAGER, mappedBy = "recipe", cascade = {CascadeType.PERSIST, CascadeType.MERGE}, orphanRemoval = true)
public List<CookbookRecipe> getCookbookRecipes() {
return cookbookRecipes;
}
public void setCookbookRecipes(List<CookbookRecipe> cookbookRecipes) {
this.cookbookRecipes = cookbookRecipes;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Recipe that = (Recipe) o;
return getId() != null && Objects.equals(getId(), that.getId());
}
#Override
public int hashCode() {
return 31;
}
#Override
public String toString() {
final StringBuilder sb = new StringBuilder("Recipe{");
sb.append("id=")
.append(id);
sb.append(", title='")
.append(title)
.append('\'');
sb.append(", cookbookRecipes=")
.append(cookbookRecipes);
sb.append('}');
return sb.toString();
}
#Override
public boolean equalsByBusinessKey(Recipe other) {
if (this == other) return true;
if (other == null || getClass() != other.getClass()) return false;
return Objects.equals(getTitle(), other.getTitle());
}
}
Test
#RunWith(SpringRunner.class)
#DataJpaTest
public class CookbookRepositoryIntegrationTest {
#Autowired
RecipeRepository recipeRepository;
#Autowired
CookbookRepository cookbookRepository;
#Autowired
CookbookRecipeRepository cookbookRecipeRepository;
#Test
public void WhenAddingSameAssociationAgain_ThenNoException() {
Recipe recipe = new Recipe();
recipe.setTitle("A Recipe");
recipe = recipeRepository.save(recipe);
Cookbook cookbook = new Cookbook();
cookbook.setTitle("A Cookbook");
cookbook = cookbookRepository.save(cookbook);
cookbook.addRecipe(recipe, "integrationtest", new Date());
cookbook = cookbookRepository.save(cookbook);
cookbook.removeRecipe(recipe);
cookbook = cookbookRepository.save(cookbook);
assertThat(cookbookRecipeRepository.findAll().size(), is(0));
}
}
The assertion fails. I don't understand why.
java.lang.AssertionError:
Expected: is <0>
but: was <1>
I expect JPA to generate a DELETE statement, because orphanRemoval is set to true. Instead the CookbookRecipe.recipeId and CookbookRecipe.cookbookId are set to null in the database but they are not removed.
i think the Entity its not removed because the 'CookbookRecipe' entity its mapped #oneToMany in 2 ways , from 'cookbook' and from 'recipe' in this way you cant delete recipe from cookbook because Spring data accepts commands from 2 entities , u must cancel the second conf oneTOMany from recipe and will working as well , Also u can use the #query in repository (Writing a custom query delete and will work as Well ) , i faced this problem one year ago , hope is useful ,

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.

Spring MVC + Hibernate Cannot get item property

I,m stuck. I have a problem with output of data. I try to make some kind of order-product project. My Entities are following:
#Entity
#Table(name = "sales")
public class Sale implements Serializable {
public Sale() {
}
#Id
#Column
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
#Column(insertable = false, updatable = false)
private Timestamp date;
#OneToMany(fetch = FetchType.EAGER, mappedBy = "sale")
private List<OrderItem> items = new ArrayList<OrderItem>();
#Column
private double cost;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public Timestamp getDate() {
return date;
}
public void setDate(Timestamp date) {
this.date = date;
}
public List<OrderItem> getItems() {
return items;
}
public void setItems(List<OrderItem> items) {
this.items = items;
}
public double getCost() {
return cost;
}
public void setCost(double cost) {
for(OrderItem item : items)
cost += item.getProduct().getPrice() * item.getQuantity();
this.cost = cost;
}
}
#Entity
#Table(name = "products")
public class Product implements Serializable {
public Product() {
}
#Id
#Column
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
#Column
private String name;
#Column
private double price;
#OneToMany(fetch = FetchType.EAGER, mappedBy = "product")
private Set<OrderItem> items = new HashSet<OrderItem>();
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public Set<OrderItem> getItems() {
return items;
}
public void setItems(Set<OrderItem> items) {
this.items = items;
}
public boolean isNew() {
return this.id == 0;
}
}
#Entity
#Table(name = "order_items")
public class OrderItem implements Serializable {
#Id
#GeneratedValue
#Column(name = "id")
private Long id;
#Column
private int quantity;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "product_id")
private Product product;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "sale_id")
private Sale sale;
public int getQuantity() {
return quantity;
}
public void setQuantity(int quantity) {
this.quantity = quantity;
}
public Product getProduct() {
return product;
}
public void setProduct(Product product) {
this.product = product;
}
}
SQL tables create like this:
CREATE TABLE products (
id SERIAL PRIMARY KEY NOT NULL,
name CHARACTER(50) NOT NULL,
price REAL NOT NULL
)
WITH ( OIDS = FALSE );
CREATE TABLE sales (
id SERIAL PRIMARY KEY NOT NULL,
cost REAL NOT NULL,
date TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
)
WITH ( OIDS = FALSE );
CREATE TABLE order_items (
id SERIAL NOT NULL,
sale_id INTEGER NOT NULL,
product_id INTEGER,
quantity INTEGER NOT NULL,
primary key (sale_id, id)
)
WITH ( OIDS = FALSE );
ALTER TABLE order_items
ADD CONSTRAINT order_itemsFK0 FOREIGN KEY (product_id) REFERENCES products(id);
ALTER TABLE order_items
ADD CONSTRAINT order_itemsFK1 FOREIGN KEY (sale_id) REFERENCES sales(id);
My sale form:
<form:hidden path="id" />
<spring:bind path="items">
<div class="form-group ${status.error ? 'has-error' : ''}">
<label class="col-sm-2 control-label">Product</label>
<div class="col-sm-5">
<form:select path="items" class="form-control">
<form:options items="${productMap}" />
</form:select>
<form:errors path="items" class="control-label" />
</div>
<div class="col-sm-5"></div>
</div>
</spring:bind>
<spring:bind path="items">
<div class="form-group ${status.error ? 'has-error' : ''}">
<label class="col-sm-2 control-label">Quantity</label>
<div class="col-sm-10">
<form:radiobuttons path="items" items="${numberList}" element="label class='radio-inline'" />
<br />
<form:errors path="items" class="control-label" />
</div>
</div>
</spring:bind>
<spring:bind path="cost">
<div class="form-group ${status.error ? 'has-error' : ''}">
<label class="col-sm-2 control-label">Cost</label>
<div class="col-sm-10">
<form:input path="cost" type="text" class="form-control" id="cost"
placeholder="Cost" />
<form:errors path="cost" class="control-label" />
</div>
</div>
</spring:bind>
And i have problems on form where I try to add sale. Items is incorrect, doesn`t save. i write jsp code wrong but i have no idea how to get it right. Need help, please!
Do you have some log? make sure that your post is arriving to the server side.
Solved. I remade some stuff in the project, in jsp part. Add additive page and everything works good.

Resources