jpa - delete OneToMany violates foreign key constraint - spring

I have two mapped entities, AssignmentOpsEntity and ConventionalOptionEntity. I want to remove an AssignmentOpsEntity with all conventionalOption. So i removed all conventionalOption from AssignmentOpsEntity, then i deleted the conventionalOption from database, finally i removed the object AssignmentOpsEntity. But i have the below error.
ERROR: update or delete on table "assignmentopsentity" violates
foreign key constraint "fkll31qdog9ye067ybhltjey6u7" on table
"conventionaloptionentity" Détail : Key
(assignmentopsid)=(8bf4a6b3-c09e-4da1-a88d-d49d9f7b63f6) is still
referenced from table "conventionaloptionentity".
#Entity
public class AssignmentOpsEntity {
#OneToMany(mappedBy = "assignmentOps", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private Set<ConventionalOptionEntity> conventionalOption;
public void removeConventionalOption(Set<ConventionalOptionEntity> conventionalOption) {
this.conventionalOption.removeAll(conventionalOption);
conventionalOption.forEach(item -> item.assignmentOps(null));
}
}
#Entity
public class ConventionalOptionEntity {
#JsonIgnore
#ManyToOne(fetch = FetchType.LAZY)
private AssignmentOpsEntity assignmentOps;
}
public void on(OpsDetachedFromAgreementEvent event) {
Optional<AssignmentOpsEntity> assignmentOpsEntityOptional = assignmentOpsRepository.findById(event.assignmentOpsId);
if (assignmentOpsEntityOptional.isPresent()) {
AssignmentOpsEntity assignmentOpsEntity = assignmentOpsEntityOptional.get();
assignmentOpsEntity.removeConventionalOption(assignmentOpsEntity.getConventionalOption());
conventionalOptionRepository.deleteAll(assignmentOpsEntity.getConventionalOption());
assignmentOpsRepository.delete(assignmentOpsEntity);
}
}

#OneToMany(mappedBy = "assignmentOps", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private Set<ConventionalOptionEntity> conventionalOption;
CascadeType.ALL means here that conventionalOption entities are deleted when you delete a parent entity
So here the code you need
public void on(OpsDetachedFromAgreementEvent event) {
Optional<AssignmentOpsEntity> assignmentOpsEntityOptional = assignmentOpsRepository.findById(event.assignmentOpsId);
if (assignmentOpsEntityOptional.isPresent()) {
assignmentOpsRepository.delete(assignmentOpsEntity);
}
}

Related

Having difficulties mapping OneToOne relation of 1 parent class with 3 child classes

I have a parent class called FoodInfo, and 3 more child classes called Ingredient, Tag and MiscellaneousData. The relation between FoodInfo and each of those classes is OneToOne.
Currently, this is how I defined the classes:
FoodInfo:
#Entity
#Table(name="food")
public class FoodInfo {
#Id
#Column(name="code")
private Long code;
#OneToOne(mappedBy = "foodInfo", cascade = CascadeType.ALL)
private Tag tag;
#OneToOne(mappedBy = "foodInfo", cascade = CascadeType.ALL)
private Ingredient ingredient;
#OneToOne(mappedBy = "foodInfo", cascade = CascadeType.ALL)
private MiscellaneousData misc;
//And the getters and setters for all attributes including:
public Ingredient getIngredient() {
return ingredient;
}
public MiscellaneousData getMisc() {
return misc;
}
public String getProduct_name() {
return product_name;
}
public void setTag(Tag tag) {
this.tag = tag;
}
public void setIngredient(Ingredient ingredient) {
this.ingredient = ingredient;
}
public void setMisc(MiscellaneousData misc) {
this.misc = misc;
}
}
In Ingredient's class:
#Entity
#Table(name="ingredient")
public class Ingredient {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#JoinColumn(name = "code")
private FoodInfo foodInfo;
public FoodInfo getFoodInfo() {
return foodInfo;
}
public void setFoodInfo(FoodInfo foodInfo) {
this.foodInfo = foodInfo;
}
}
The other two child classes are the same as Ingredient.
And finally, to Insert all the data I did like so:
FoodInfo food = new FoodInfo();
Ingredient ing = new Ingredient();
MiscellaneousData misc = new MiscellaneousData();
Tag tag = new Tag();
//And after setting all their attributes...
food.setTag(tag);
food.setMisc(misc);
food.setIngredient(ing);
tag.setFoodInfo(food);
misc.setFoodInfo(food);
ing.setFoodInfo(food);
foodRepository.save(food);
Now, when I try to run the program, an error says:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource
[org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]:
Invocation of init method failed; nested exception is org.hibernate.AnnotationException: Referenced property not a (One|Many)ToOne: com.guillermo.off.model.Ingredient.foodInfo in mappedBy of com.guillermo.off.model.FoodInfo.ingredient
............
Caused by: org.hibernate.AnnotationException: Referenced property not a (One|Many)ToOne: com.guillermo.off.model.Ingredient.foodInfo in mappedBy of com.guillermo.off.model.FoodInfo.ingredient
In previous attempts, using annotations in a different way I managed to insert the data in the database, but when I tried to fetch all this data, the program got into an endless loop.
Any help will be very appreciated! Thanks in advance!!
EDIT:
After doing what #Hülya suggested, the information in the database seems to be right:
But when requesting the info, I ran into what looks an inifinite loop.
My code for requesting the data is:
#GetMapping("/food")
public List<FoodInfo> findFood(HttpServletResponse response) {
List<FoodInfo> food = foodService.findAll();
return food;
}
...and in the console, I can only see the folloging a thousand times:
at
com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:728)
~[jackson-databind-2.11.4.jar:2.11.4] at
com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:755)
~[jackson-databind-2.11.4.jar:2.11.4]
#OneToOne annotation should be used on both parent and child side to create a bidirectional one-to-one mapping.
As the error says: Referenced property not a (One|Many)ToOne There is no mapping on the Ingredient side.
You should specify the entity association for foodInfo field with #OneToOne:
#Entity
#Table(name="ingredient")
public class Ingredient {
// ...
#OneToOne
#JoinColumn(name = "code")
private FoodInfo foodInfo;
}
Update for com.fasterxml.jackson.databind exception:
When serializing bidirectional relationships with jackson, cyclic dependency leads to an endless loop. To break the cycle you should add #JsonManagedReference and #JsonBackReference annotations:
FoodInfo class:
#Entity
#Table(name="food")
public class FoodInfo {
// ...
#OneToOne(mappedBy = "foodInfo", cascade = CascadeType.ALL)
#JsonManagedReference
private Ingredient ingredient;
}
Ingredient class:
#Entity
#Table(name="ingredient")
public class Ingredient {
//...
#OneToOne
#JoinColumn(name = "code")
#JsonBackReference
private FoodInfo foodInfo;
}

jpa - list OneToMany not saved

I have the below room entity which has many disponibilities, when i add a room with a list of disponibilities, the room is saved but the list is not. what am i missing in the relationship ?
#Entity
public class RoomEntity {
#Id
private String classRoomId;
private String label;
#OneToMany(mappedBy = "room", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private List<DisponibilityEntity> disponibilities;
public void addDisponibilities(List<DisponibilityEntity> disponibilityEntities) {
if (CollectionUtils.isEmpty(disponibilities)) {
disponibilities = new ArrayList<>();
}
disponibilities.addAll(disponibilityEntities);
disponibilityEntities.forEach(item -> item.setRoom(this));
}
}
#Entity
public class DisponibilityEntity {
#Id
private String disponibilityId;
#JsonIgnore
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "fk_room")
private RoomEntity room;
}
roomEntity.addDisponibilities(classRoomEntity.getDisponibilities());
roomRepository.save(roomEntity);

Spring-Data-Jpa bidirectional association with EmbeddedId. Foreign key is null when merging

I am running the latest version of Hibernate with 2 entities: Project and ProjectShare, which have a one-to-many relation set up bidirectional.
A ProjectShare is uniquely identified by a composite ID containing project_id and user_id. Besides the key, a ProjectShare contains a boolean flag whether the user gets read or write access on the project.
#Entity
#Table(name = "projects")
public class Project {
#Id
#GeneratedValue // UUID generator
#Column(name = "project_id")
private String id;
#Column(name = "name")
private String name;
#OneToMany(mappedBy = "project", cascade = CascadeType.ALL, orphanRemoval = true)
private List<ProjectShare> shares = new ArrayList<>();
public Project(String name) {
this.name = name;
}
public void addShare(ProjectShare share) {
shares.add(share);
share.setProject(this);
}
public void removeShare(ProjectShare share) {
shares.remove(share);
share.setProject(null);
}
}
#Entity
#Table(name = "project_shares")
public class ProjectShare {
#EmbeddedId
private ProjectShareId id;
#Column(name = "has_write_access")
private boolean hasWriteAccess;
#MapsId("projectId")
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "project_id", nullable = false, updatable = false)
private Project project;
public ProjectShare(ProjectShareId id, boolean hasWriteAccess) {
this.id = id;
this.hasWriteAccess = hasWriteAccess;
}
public void setProject(Project project) {
this.project = project;
}
}
#Embeddable
public class ProjectShareId implements Serializable {
#Column(name = "project_id")
private String projectId;
#Column(name = "user_id")
private String userId;
public ProjectShareId(String userId) {
this.userId = userId;
}
// equals and hashCode go here...
}
If I create a new Project and assign new ProjectShare associations to it, everything works fine:
Project project = new Project("my_project");
project.addShare(new ProjectShare(new ProjectShareId("user1"), false));
project.addShare(new ProjectShare(new ProjectShareId("user2"), false));
projectRepository.save(project); // assume project id is '123'
Since all objects are new and not yet persisted, it executes 1 insert for the Project itself and 1 insert for each ProjectShare. Lets assume the project is inserted with id '123'.
Now, if I load this existing project, and add new ProjectShares to it, things go wrong:
Project project = projectRepository.findById("123");
project.addShare(new ProjectShare(new ProjectShareId("user2"), true));
project.addShare(new ProjectShare(new ProjectShareId("user3"), true));
projectRepository.save(project);
For every ProjectShare, this executes a SELECT on the values in ProjectShareId (project_id and user_id), followed by either an INSERT of UPDATE, depending on whether the record was found. This is the basic merge strategy of Hibernate and this is what we want.
The desired outcome of this should be:
Leave ProjectShare for user1 untouched
Update the ProjectShare for user2 (from false to true)
Create a new ProjectShare for user3
However, when the SELECT is executed for the ProjectShare, the foreign key project_id is always null. This means that existing records are never found, an INSERT is attempted instead of and UPDATE, and a DB-level Constraint violation is triggered.
How should I solve this issue? Should I manually go through the project.getShares() collection, find existing records and update them instead? I was hoping Hibernate would do this through its merge strategy.
Or could this be a bug in Hibernate related to associations with foreign keys in Embbedded IDs?

Hibernate - Can't delete child if parent has cascade set

I try to delete an entity which is a child of another entity (one-to-many).
The problem is: If the parent has set a cascade type, I am not able to delete a child directly. The delete command is ignored (using JpaRepository). Only if I remove the cascade setting I am able to delete child.
Is there a way to do this without a native SQL statement?
Parent Entity:
#Entity
public class ExplorerItem {
...
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "explorerItem")
private Set<UserACL> userAcls = new HashSet<>();
...
}
Child Entity:
#Entity
public class UserACL {
...
#ManyToOne
private ExplorerItem explorerItem;
...
}
I'm using JpaRepositories created by Spring Boot:
public interface UserACLRepository extends JpaRepository<UserACL, Long> {
void deleteByUser(User user);
}
You can set orphanRemoval="true" in your #OneToMany annotation. Setting orphanRemoval to true automatically removes disconnected references of entities. On the other hand, if we specify only CascadeType.Remove, no action is taken as it will only disconnect from the association, which is not equivalent of deleting an object.
Eg.
#Entity
public class ExplorerItem {
...
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval="true", mappedBy = "explorerItem")
private Set<UserACL> userAcls = new HashSet<>();
...
}

HIbernate + JPA OneToMany Lazy loading not working if no foreign key specified in the db

Hibernate lazy loading is not working in my code. It loads the entire data even it is specified as FetchType LAZY
#Transactional(propagation = Propagation.NEVER)
public OrderItem getItem(String itemId) throws Exception {
OrderItem item = itemDao.find(OrderItem.class, Integer.parseInt(itemId));
if (item == null) {
throw new Exception(502, "We are unable to load item for #" + itemId);
}
return item;
}
#NotFound(action = NotFoundAction.IGNORE)
#OneToMany(cascade={CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.LAZY)
#JoinColumn(name = "id_order_detail")
#Fetch(value= FetchMode.JOIN)
#JsonManagedReference
private Set<OrderItemStateChangeEntry> itemStateHistory;
I could not able to lazy load the contents. There is no foreign key constraint set in the db. And its not possible to set as the many parent data not present in the system.
Can somebody help me on this
Update
Added my class and reference. But lazy load work
#Entity
#Table(name = "ps_orders")
#AttributeOverrides({
#AttributeOverride(name="id",column=#Column(name="id_order")),
#AttributeOverride(name="createTime",column=#Column(name="date_add")),
#AttributeOverride(name="updateTime",column=#Column(name="date_upd"))
})
public class Order extends BaseEntity{
#Column(name = "id_carrier")
private Integer carrier = 0;
#NotFound(action = NotFoundAction.IGNORE)
#OneToMany(fetch = FetchType.LAZY,cascade={CascadeType.PERSIST, CascadeType.MERGE}, mappedBy="order")
#Fetch(FetchMode.SELECT)
#JsonManagedReference
private Set<OrderStateChangeEntry> orderHistory;
//Getters and Setters
}
#Entity
#Table(name = "ps_order_history")
#EnableBouncerProfile
public class OrderStateChangeEntry implements java.io.Serializable{
public OrderStateChangeEntry(){}
public OrderStateChangeEntry(Order order){
this.order = order;
}
#Id
#Column(name = "id_order_history")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name="id_order", nullable=false)
#JsonBackReference
private Order order;
//Getters and Setters
}
It is because of your
#Fetch(value= FetchMode.JOIN)
it disables the lazy loading ...
As you specify the fetch mode in your #OnetoMany relationship, i would say that you can simply remove that line above.

Resources