Manage JPA Entity with Set data structure - using add method - spring-boot

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));

Related

I cannot remove the association in Many To Many bidirectional hibernate

I can't delete the association in the courses_student table of course and student when trying to delete a course, even if I want to cascade delete it does not work for me since there is a foreign key in courses_student, I don't know what the problem is.
I have also tried to remove the association in the courses_student table doing a update.but nothing happened.
DAO
#Override
public boolean deleteCourse(int id) {
Session currentSession = entityManager.unwrap(Session.class);
Courses course = currentSession.load(Courses.class, id);
for(Student student : course.getEstudiantes()) {
course.removeStudent(student);
}
currentSession.delete(course);
if(course.getId() == null)
return true;
else
return false;
}
Courses entity
#Entity
#Table(name = "courses")
public class Courses {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="id")
private Integer id;
#Column
private String nombre;
#Column
private String descripcion;
#ManyToMany(mappedBy = "courses")
private Set<Student> Estudiantes = new HashSet<Student>();
public Courses() {
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getNombre() {
return nombre;
}
public void setNombre(String nombre) {
this.nombre = nombre;
}
public String getDescripcion() {
return descripcion;
}
public void setDescripcion(String descripcion) {
this.descripcion = descripcion;
}
public Set<Student> getEstudiantes() {
return Estudiantes;
}
public void setEstudiantes(Set<Student> estudiantes) {
Estudiantes = estudiantes;
}
public void removeStudent(Student student) {
this.Estudiantes.remove(student);
student.getCourses().remove(this);
}
}
Student entity
#Entity
#Table(name = "students")
public class Student {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="id")
private Integer id;
#Column
private String nombre;
#Column
private String apellido;
#Column
private String dni;
#ManyToMany(fetch=FetchType.LAZY,
cascade= {CascadeType.PERSIST, CascadeType.MERGE,
CascadeType.DETACH, CascadeType.REFRESH})
#JoinTable(
name="courses_students",
joinColumns=#JoinColumn(name="id_student"),
inverseJoinColumns=#JoinColumn(name="id_course")
)
private Set<Courses> courses = new HashSet<Courses>();
public Student() {
}
public Student(String nombre, String apellido, String dni) {
this.nombre = nombre;
this.apellido = apellido;
this.dni = dni;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getNombre() {
return nombre;
}
public void setNombre(String nombre) {
this.nombre = nombre;
}
public String getApellido() {
return apellido;
}
public void setApellido(String apellido) {
this.apellido = apellido;
}
public String getDni() {
return dni;
}
public void setDni(String dni) {
this.dni = dni;
}
public Set<Courses> getCourses() {
return courses;
}
public void setCourses(Set<Courses> courses) {
this.courses = courses;
}
}
EDIT:
apparently it works for me, trying to update since owner side.
#Override
public boolean deleteCourse(int id) {
Session currentSession = entityManager.unwrap(Session.class);
Courses course = currentSession.load(Courses.class, id);
for(Student student : course.getEstudiantes()) {
student.removeCourse(course);
}
currentSession.update(course);
if(course.getId() == null)
return true;
else
return false;
}
It seems to me that you are missing a cascade configuration of your #ManyToMany annotation on Courses which is actually the one you are updating / deleting. Try the following:
#ManyToMany(mappedBy = "courses", cascade = {CascadeType.PERSIST, CascadeType.MERGE})
private Set<Student> Estudiantes = new HashSet<Student>();
Also, given that you have a bi-directional relationship, you should also remove the Course from each Student courses property.

Spring JPA hibernate how to persist children (remove, add, or update) from #OneToMany parent column?

I'm trying to solve this problem since a while and I haven't achieved a 100% solution.
First of all I have to describe my problem. I'm developping a restaurant application, and amoung the Entities, I have the Entity Ingredient and as you know Ingredient can consist of other Ingredient with a specific quantity. So I created an Entity SubIngredient with an Embedded Id.
And to persist subIngredients list I tried a combinations of Cascade and orphanRemoval, each combination worked for some operation but not for the others.
I started by using CascadeType.ALL and the new subIngredient persisted successfuly from the #OneToMany propertiy, But if I try to remove an subIngredient from the subIngredients list and save this error appear.
java.lang.StackOverflowError: null
at com.mysql.cj.NativeSession.execSQL(NativeSession.java:1109) ~[mysql-connector-java-8.0.23.jar:8.0.23]......
I loked in the net for a solution and I find the I have to use orphanremoval = true I tried it but it didn't work until I changed cascade from CascadeType.ALL to CascadeType.PERSIST. But this one make the persistance of new SubIngredient this error aprear
Caused by: javax.persistence.EntityNotFoundException: Unable to find com.example.Resto.domain.SubIngredient with id com.example.Resto.domain.SubIngredientKey#51b11186........
These are my Enities:
#Entity
public class Ingredient {
#Id
#GeneratedValue( strategy = GenerationType.IDENTITY)
#Column(name="ID")
private long id;
#NotNull
#Column(unique=true)
private String name;
private String photoContentType;
#Lob
private byte[] photo;
#JsonIgnoreProperties({"photoContentType","photo"})
#ManyToOne
private IngredientType ingredientType;
#OneToMany(mappedBy = "embId.ingredientId", fetch = FetchType.EAGER,
cascade = CascadeType.ALL /*or orphanRemoval = true, cascade = CascadeType.PERSIST*/ )
private Set<SubIngredient> subIngredients = new HashSet<SubIngredient>();
getters and setters.....
And
#Entity
#AssociationOverrides({
#AssociationOverride(name = "embId.ingredientId",
joinColumns = #JoinColumn(name = "ING_ID")),
#AssociationOverride(name = "embId.subIngredientId",
joinColumns = #JoinColumn(name = "SUB_ING_ID")) })
public class SubIngredient {
#EmbeddedId
private SubIngredientKey embId = new SubIngredientKey();
private double quantity;
getters and setters....
And
#Embeddable
public class SubIngredientKey implements Serializable{
#ManyToOne(cascade = CascadeType.ALL)
private Ingredient ingredientId;
#ManyToOne(cascade = CascadeType.ALL)
private Ingredient subIngredientId;
getters and setters...
The stackoverflow happen because you use a Set<> with Hibernate. When Hibernate retrieves the entities from your DB, it will fill up the Set<> with each entities. In order to that, hashode/equals will be used to determine wether or not the entitie is already present in the Set<>. By default, when you call the hashcode of Ingredient, this happen:
hashcode Ingredient -> hashcode SubIngredient -> hashcode Ingredient
which will result in an infinite call of hashcode method. That's why you have a stackoverflow error.
The same thing will happen with equals/toString.
So to avoid such an issue, it's best to override hashcode, equals and toString.
I have solved the problem by making some changes to may Entities and override equals/hashcode methods thanks Pilpo.
#Embeddable
public class SubIngredientKey implements Serializable{
private Long ingredientId;
private Long subIngredientId;
/**
* #return the ingredientId
*/
#Override
public int hashCode() {
return Objects.hash(ingredientId, subIngredientId);
}
#Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof SubIngredientKey)) {
return false;
}
SubIngredientKey other = (SubIngredientKey) obj;
return Objects.equals(ingredientId, other.ingredientId)
&& Objects.equals(subIngredientId, other.subIngredientId);
}
}
#Entity
public class SubIngredient {
#EmbeddedId
private SubIngredientKey embId = new SubIngredientKey();
#ManyToOne(fetch = FetchType.LAZY)
#MapsId("ingredientId")
private Ingredient ingredient;
#ManyToOne(fetch = FetchType.LAZY)
#MapsId("subIngredientId")
private Ingredient subIngredient;
private double quantity;
#JsonIgnore
public SubIngredientKey getId() {
return embId;
}
public void setId(SubIngredientKey id) {
this.embId = id;
}
#JsonIgnoreProperties({"subIngredients","photo","photoContentType","ingredientType"})
public Ingredient getIngredient() {
return ingredient;
}
public void setIngredient(Ingredient ingredient) {
this.ingredient = ingredient;
}
#JsonIgnoreProperties({"subIngredients","photo","photoContentType","ingredientType"})
public Ingredient getSubIngredient() {
return subIngredient;
}
public void setSubIngredient(Ingredient subIngredient) {
this.subIngredient = subIngredient;
}
public double getQuantity() {
return quantity;
}
public void setQuantity(double quantity) {
this.quantity = quantity;
}
#Override
public String toString() {
return "subIngredient= " + getSubIngredient().getName() + " , quantity= " + getQuantity();
}
#Override
public int hashCode() {
return Objects.hash(ingredient,subIngredient);
}
#Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof SubIngredient)) {
return false;
}
SubIngredient other = (SubIngredient) obj;
return Objects.equals(ingredient, other.ingredient) && Objects.equals(subIngredient, other.subIngredient);
}
}
#Entity
public class Ingredient {
#Id
#GeneratedValue( strategy = GenerationType.IDENTITY)
#Column(name="ID")
private long id;
#NotNull
#Column(unique=true)
private String name;
private String photoContentType;
#Lob
private byte[] photo;
#JsonIgnoreProperties({"photoContentType","photo"})
#ManyToOne
private IngredientType ingredientType;
#OneToMany(mappedBy = "embId.ingredientId", fetch = FetchType.EAGER, cascade =
CascadeType.ALL, orphanRemoval = true)
private Set<SubIngredient> subIngredients = new HashSet<SubIngredient>();
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public String getPhotoContentType() {
return photoContentType;
}
public void setPhotoContentType(String photoContentType) {
this.photoContentType = photoContentType;
}
public byte[] getPhoto() {
return photo;
}
public void setPhoto(byte[] photo) {
this.photo = photo;
}
public IngredientType getIngredientType() {
return this.ingredientType;
}
public void setIngredientType(IngredientType ingredientType) {
this.ingredientType = ingredientType;
}
public Set<SubIngredient> getSubIngredients() {
return subIngredients;
}
public void setSubIngredients(Set<SubIngredient> subIngredients) {
this.subIngredients = subIngredients;
}
public void addSubIngredient(SubIngredient subIngredient) {
this.subIngredients.add(subIngredient);
}
#Override
public String toString() {
String subIngsText = "";
for(var subIngredient:this.subIngredients) {
subIngsText = subIngsText + ", " + subIngredient.toString();
}
return "{id= "+id+",name=" + name +", ingredients="+subIngsText+"}";
}
#Override
public int hashCode() {
return Objects.hash(name);
}
#Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof Ingredient)) {
return false;
}
Ingredient other = (Ingredient) obj;
return Objects.equals(name, other.name);
}
}

move validation to the JPQL query level

I am looking for a way to move the validation method from Service to Repository
One Picture has one PictureData.
This is the method:
// TODO move validation to the JPQL query level
.filter(pic -> pic.getPictureData().getFileName() != null)
This is my Service
#Service
#ConditionalOnProperty(name = "picture.storage.type", havingValue = "file")
public class PictureServiceFileImpl implements PictureService {
private static final Logger logger = LoggerFactory.getLogger(PictureServiceFileImpl.class);
#Value("${picture.storage.path}")
private String storagePath;
private final PictureRepository repository;
#Autowired
public PictureServiceFileImpl(PictureRepository repository) {
this.repository = repository;
}
#Override
public Optional<String> getPictureContentTypeById(long id) {
return repository.findById(id)
// TODO move validation to the JPQL query level
.filter(pic -> pic.getPictureData().getFileName() != null)
.map(Picture::getContentType);
}
#Override
public Optional<byte[]> getPictureDataById(long id) {
return repository.findById(id)
// TODO move validation to the JPQL query level
.filter(pic -> pic.getPictureData().getFileName() != null)
.map(pic -> Path.of(storagePath, pic.getPictureData().getFileName()))
.filter(Files::exists)
.map(path -> {
try {
return Files.readAllBytes(path);
} catch (IOException ex) {
logger.error("Can't open picture file", ex);
throw new RuntimeException(ex);
}
});
}
#Override
public PictureData createPictureData(byte[] picture) {
String fileName = UUID.randomUUID().toString();
try (OutputStream os = Files.newOutputStream(Path.of(storagePath, fileName))) {
os.write(picture);
} catch (IOException ex) {
logger.error("Can't create picture file", ex);
throw new RuntimeException(ex);
}
return new PictureData(fileName);
}
}
The Entities
#Entity
#Table(name = "pictures")
public class Picture {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "name", nullable = false)
private String name;
#Column(name = "content_type", nullable = false)
private String contentType;
#OneToOne(fetch = FetchType.LAZY, cascade= CascadeType.ALL, optional = false, orphanRemoval = true)
#JoinColumn(name="picture_data_id")
private PictureData pictureData;
#ManyToOne
private Product product;
public Picture() {
}
public Picture(String name, String contentType, PictureData pictureData, Product product) {
this.name = name;
this.contentType = contentType;
this.pictureData = pictureData;
this.product = product;
}
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 String getContentType() {
return contentType;
}
public void setContentType(String contentType) {
this.contentType = contentType;
}
public PictureData getPictureData() {
return pictureData;
}
public void setPictureData(PictureData pictureData) {
this.pictureData = pictureData;
}
public Product getProduct() {
return product;
}
public void setProduct(Product product) {
this.product = product;
}
}
#Entity
#Table(name = "pictures_data")
public class PictureData {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Lob
#Type(type="org.hibernate.type.BinaryType") // для правильной работы PostgreSQL
#Column(name = "data", length = 33554430) // для правильной hibernate-валидации в MySQL
private byte[] data;
#Column(name = "file_name")
private String fileName;
public PictureData() {
}
public PictureData(byte[] data) {
this.data = data;
}
public PictureData(String fileName) {
this.fileName = fileName;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public byte[] getData() {
return data;
}
public void setData(byte[] data) {
this.data = data;
}
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
}
I am struggling to get a query working in JPQL.
public interface PictureRepository extends JpaRepository<Picture, Long> {
#Query ("SELECT p FROM Picture p JOIN p.pictureData d WHERE d.data IS NOT NULL ")
Picture filterPictureWherePictureDataIsNotNull ();
}
Since you already have entity level join, you can directly use below method
public interface PictureRepository extends JpaRepository<Picture, Long>
{
#Query ("SELECT p FROM Picture p WHERE p.pictureData.data IS NOT NULL ")
Picture filterPictureWherePictureDataIsNotNull ();
}
Another observation as well,
You repo method might return list of Picture and not a one picture.So, return type should ideally be
#Query ("SELECT p FROM Picture p WHERE p.pictureData.data IS NOT NULL ")
List<Picture> filterPictureWherePictureDataIsNotNull ();

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 ,

Stop hibernate from firing update query on #ManyToOne entities

I have two entities ProductCartItem and Product. A product in my scenario is more of a master record that is never gonna change. Below is the mapping.
#Entity
#DiscriminatorValue(value = "PRODUCT")
public class ProductCartItem extends CartItem {
#ManyToOne(optional = false)
#JoinColumn(name = "product_id", referencedColumnName = "id")
private Product product;
#OneToMany(cascade = CascadeType.REMOVE, mappedBy = "parentProductCartItem",orphanRemoval = true)
#JsonManagedReference
Set<AccessoryCartItem> associatedAccessories = new HashSet<>();
#Column(name="property")
#Type(type = "ProductItemPropertyUserType")
private ProductItemProperty productItemProperty;
#OneToOne
#JoinColumn(name="project_id",referencedColumnName = "id")
private Project project;
public Product getProduct() {
return product;
}
public void setProduct(Product product) {
this.product = product;
}
public Set<AccessoryCartItem> getAssociatedAccessories() {
return associatedAccessories;
}
public void setAssociatedAccessories(Set<AccessoryCartItem> associatedAccessories) {
this.associatedAccessories = associatedAccessories;
}
public void addAccessory(AccessoryCartItem accessoryCartItem) {
this.getAssociatedAccessories().add(accessoryCartItem);
}
public void removeAccessory(AccessoryCartItem accessoryCartItem) {
this.getAssociatedAccessories().remove(accessoryCartItem);
}
public ProductItemProperty getProductItemProperty() {
return productItemProperty;
}
public void setProductItemProperty(ProductItemProperty productItemProperty) {
this.productItemProperty = productItemProperty;
}
public Project getProject() {
return project;
}
public void setProject(Project project) {
this.project = project;
}
}
And here is the Product entity.
#Entity
public class Product extends BaseEntity {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Column(name = "title")
private String title;
#Column(name = "subtitle")
private String subtitle;
#Column(name = "description")
private String description;
#Column(name = "type_name")
private String typeName;
#Column(name = "price")
private Float price;
#Column(name = "image_list")
#Type(type = "MyImageListUserType")
private MyImageList imageList;
#Column(name = "pricing_property")
#Type(type = "PricingProperty")
private Map<String,SizePriceDTO> pricingProperty;
#JoinColumn(name = "product_type")
#ManyToOne
private ProductType productType;
private String orientation;
private Short groupId;
#Column(name = "display_order")
private Short displayOrder;
#Column(name = "base_quantity")
private int baseQuantity;
#Transient
private List<AccessoryDTO> configuredAccessoryDTOList;
public Product(){
}
public int getBaseQuantity() {
return baseQuantity;
}
public void setBaseQuantity(int baseQuantity) {
this.baseQuantity = baseQuantity;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getSubtitle() {
return subtitle;
}
public void setSubtitle(String subtitle) {
this.subtitle = subtitle;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getTypeName() {
return typeName;
}
public void setTypeName(String typeName) {
this.typeName = typeName;
}
public Float getPrice() {
return price;
}
public void setPrice(Float price) {
this.price = price;
}
public MyImageList getImageList() {
return imageList;
}
public void setImageList(MyImageList imageList) {
this.imageList = imageList;
}
public ProductType getProductType() {
return productType;
}
public void setProductType(ProductType productType) {
this.productType = productType;
}
public String getOrientation() {
return orientation;
}
public void setOrientation(String orientation) {
this.orientation = orientation;
}
public Short getGroupId() {
return groupId;
}
public void setGroupId(Short groupId) {
this.groupId = groupId;
}
public Short getDisplayOrder() {
return displayOrder;
}
public void setDisplayOrder(Short displayOrder) {
this.displayOrder = displayOrder;
}
public List<AccessoryDTO> getConfiguredAccessoryDTOList() {
return configuredAccessoryDTOList;
}
public void setConfiguredAccessoryDTOList(List<AccessoryDTO> configuredAccessoryDTOList) {
this.configuredAccessoryDTOList = configuredAccessoryDTOList;
}
public Map<String, SizePriceDTO> getPricingProperty() {
return pricingProperty;
}
public void setPricingProperty(Map<String,SizePriceDTO> pricingProperty) {
this.pricingProperty = pricingProperty;
}
}
Now when I create a new ProductCartItem I associate an already existing Product with it. When I save the productcartitem hibernate for some reasons is firing an update query on the product table too. I have already tried setting the relationship as updatable= false but to no avail. Below is the code for the service.
private ShoppingCart addProductToCartHelper(ProductCartItemDTO productCartItemDTO) throws ShoppingException{
ShoppingCart shoppingCart;
ProductCartItem productCartItem;
Product product = productService.getProductById(productCartItemDTO.getProductDTO().getId().intValue());
if (null == product) {
throw new ShoppingException();
}
Customer currentCustomer = CanveraWebUtil.getCurrentCustomer();
GuestUser guestUser = guestUserService.loadGuestUserByUUID(CanveraWebUtil.getCurrentGuestUserIdentifier());
shoppingCart = fetchShoppingCartForCustomerOrGuestUser();
if (null == shoppingCart) {
if (null != currentCustomer) {
shoppingCart = new ShoppingCart(currentCustomer);
} else {
shoppingCart = new ShoppingCart(guestUser);
}
shoppingCart.setShoppingBagStatus(ShoppingBagStatus.DRAFT);
}
Long productCartItemDTOId = productCartItemDTO.getId();
// we will not update the associated accessories as in our case these never comes from our UI.
if (null == productCartItemDTOId) {
modifyNumberOfPages(productCartItemDTO,product);
productCartItem = new ProductCartItem();
productCartItem.setProductItemProperty(productCartItemDTO.getProductItemProperty());
productCartItem.setQuantity(productCartItemDTO.getQuantity());
productCartItem.setProduct(product);
productCartItem.setPrice(productCartItemDTO.getPrice());
productCartItem.setGiftWrap(shoppingCart.getIsGiftWrap());
//associating project
productCartItem.setProject(productCartItemDTO.getProject());
shoppingCart.addCartItem(productCartItem);
productCartItem.setShoppingCart(shoppingCart);
} else {
for (CartItem cartItem : shoppingCart.getCartItems()) {
if (null != cartItem.getId() && cartItem.getId().equals(productCartItemDTOId)) {
productCartItem = (ProductCartItem) cartItem;
productCartItem.setProductItemProperty(productCartItemDTO.getProductItemProperty());
productCartItem.setPrice(productCartItemDTO.getPrice());
productCartItem.setQuantity(productCartItemDTO.getQuantity());
}
}
}
shoppingCart = shoppingCartRepository.save(shoppingCart);
return shoppingCart;
}
Can anybody point me in the right direction ? At any point of time I do not alter any property of the product object.

Resources