I have an issue with a delete to a Many side of a ManyToOne relationship. I've already removed all CascadeTypes from the relationship but the issue still remains. The entry won't be removed (only selects are executed and no delete query). I'm trying to delete it through a CRUD repository call to delete. It calls the method and executes successfully but nothing happens.
The relationship goes as follows: an Activity has an assigned Course, a course can have many activities assigned to it. An Activity has a specific ActivityType.
The classes are as below.
Activity
public class Activity implements Item, Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
...
#ManyToOne
#JoinColumn(name = "type_id", nullable = false)
private ActivityType type;
#ManyToOne
#JoinColumn(name = "course_id", nullable = false)
#JsonSerialize(using = CustomCourseSerializer.class)
private Course course;
...
}
Course
public class Course implements Item, Serializable {
...
#OneToMany(mappedBy = "course", fetch = FetchType.EAGER, targetEntity = Activity.class) //cascade = { CascadeType.MERGE, CascadeType.REFRESH, CascadeType.DETACH, CascadeType.REMOVE}
#Fetch(value = FetchMode.SUBSELECT)
private List<Activity> activities;
...
}
Activity Type (has no reference to Activity)
public class ActivityType implements Item, Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name", nullable = false, unique = true)
private String name;
...
}
Any ideas how can I solve this issue or at least debug it? Thank you.
Add orphanRemoval = true attribute in the #OneToMany annotation in your Course entity.
public class Course implements Item, Serializable {
...
#OneToMany(mappedBy = "course", fetch = FetchType.EAGER, targetEntity = Activity.class, orphanRemoval = true, cascade = CascadeType.REMOVE )
#Fetch(value = FetchMode.SUBSELECT)
private List<Activity> activities;
...
}
Try to delete reference to Activity from Course. It seems unnecessary to me
Related
I am facing a weird issue where even though all fields are set in the java object, when I save the object hibernate tries to insert null values in the fields.
When I further debugged, I saw that while merging the new entity at this line hibernate generates an empty object and sets to the target instead of setting given entity to the target. This results in insert query with null values.
Am I missing some configuration here? Below are the example entities having associations similar to my case.
class Vehicle {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Column(nullable = false)
#Enumerated(EnumType.STRING)
#EqualsAndHashCode.Include
private VehicleType vehicleType;
#OneToOne(mappedBy="vehicle", fetch=FetchType.LAZY)
private Car car;
#OneToOne(mappedBy="vehicle", fetch=FetchType.LAZY)
private Truck truck;
}
class Car {
#Id
private Integer id;
#OneToOne(fetch = FetchType.LAZY, optional = false)
#MapsId
#JoinColumn(name = "vehicle_id")
private Vehicle vehicle;
...
}
class Truck {
#Id
private Integer id;
#OneToOne(fetch = FetchType.LAZY, optional = false)
#MapsId
#JoinColumn(name = "vehicle_id")
private Vehicle vehicle;
...
}
I encountered the same problem, in my case I have an application with:
public class Claim extends BaseEntity<Integer> implements Serializable {
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "claimdetailsid", referencedColumnName = "id")
private ClaimDetails claimDetails;
#OneToOne(cascade = CascadeType.PERSIST)
#JoinColumn(name = "beneficiaryid", referencedColumnName = "id")
private Beneficiary beneficiary;
....
}
When I saved the Claim entity, the Claim and ClaimDetails objects were inserted correctly. The other entities had all the fields null, except the id and the creation date.
I tried changing CascadeType.PERSIST to CascadeType.ALL, that solved my insert problem.
But the delete cascade doesn't work now.
I know this question has been asked many times but none of the solution is working for me.
So I have a Parent class :
class User{
#Id
#NotNull
#Column(name = "`UserId`", nullable = false)
private Long userId;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
#JoinColumn(name = "`UserId`")
private Set<Phone> phoneList;
}
And a child class:
class Phone {
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "`UserId`")
private User user;
}
Now when I received a update for User class with new phone list, I want to remove all the old phones and add new phones. Please note that this all operation is happening in same #Transactional.
Solution I tried:
user.getPhoneList().clear()
user.getPhoneList().addAll(new phone list)
When I try the above logic, Hibernate is trying to set old phone with userId as null. At this position I am getting DataIntegrityViolation as userId in Phone table is non null column.
Please provide any appropriate solution which can work here.
Hhhmmm... I have the exact same logic and it works fine by me. Here are my classes
#Data
#Entity
public class ProductReference {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#OneToMany(mappedBy = "productReference", fetch = FetchType.EAGER, cascade = CascadeType.REMOVE, orphanRemoval = true)
private Set<Attribute> attributes = new HashSet<>();
}
The only difference I see is the CascadeType.REMOVE
#Data
#Entity
public class Attribute {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#ManyToOne
private ProductReference productReference;
}
My deletion:
productReference.getAttributes().clear();
Which Hibernate version you have? by me it is org.hibernate.Version - HHH000412: Hibernate Core {5.4.10.Final}
The scenario is the following
I have 2 tables, Company and Activity. A company can have one or more activities. One of these activities is a "primary" activity, and all others become secondary.
To handle this, I created 2 entities (Activity, Company) and a third entity for the join table, which is CompanyActivity
I used this tutorial as a starting point
Below my code (getters and setters omitted)
Company.java
#Entity
#Table(name = "T_COMPANY")
public class Company {
#Id
#Column(name = "COM_ID")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "company")
private List<CompanyActivity> activities = new ArrayList<>();
}
Activity.java
#Entity
#Table(name = "T_ACTIVITY")
public class Activity {
#Id
#Column(name = "ACT_ID")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String code;
private String description;
private boolean availableOnline;
}
CompanyActivity.java
#Entity
#Table(name = "T_COMPANY_ACTIVITY")
public class CompanyActivity {
#Id
#Column(name = "COM_ACT_ID")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "COM_ID")
private Company company;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "ACT_ID")
private Activity activity;
private boolean primary;
}
Adding activities for a company works without a problem. The children collection contains the newly added activities, and there is always one marked as primary as expected.
The problem happens when updating a company.
When I add a new activity, all previous existing activities are persisted again.
When I remove an activity, it is not removed from the table.
I'm using this code to update a company' activities
company.getActivities().clear();
company.getActivities().addAll(newActivities);
company = repository.save(company);
In this code, newActivities have the new activities that should be considered (this collection does not have the previous ones, I just replace them all)
I tried adding orphanRemoval=true to the #OneToMany(cascade = CascadeType.ALL, mappedBy = "company") on Company, but this deletes the activity type when no other company is using it, which is wrong as they should be available always.
Can you please help me sync the activities collection on Company without removing elements from Activity table ?
Thanks a lot!
I solved it. Here are the steps I followed.
First, I changed my Join table entity cascade types as follows
#Entity
#Table(name = "T_COMPANY_ACTIVITY")
public class CompanyActivity {
#Id
#Column(name = "COM_ACT_ID")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinColumn(name = "COM_ID")
private Company company;
#ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinColumn(name = "ACT_ID")
private Activity activity;
private boolean primary;
}
Then, I added again the "orphanRemoval" property to Company mapping, and changed my CascadeTypes too, as follows
#OneToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, mappedBy = "empresa", orphanRemoval = true)
private List<CompanyActivity> activities = new ArrayList<>();
With these changes, my mapping works as expected with the same code I used to replace the relationships.
company.getActivities().clear();
company.getActivities().addAll(newActivities);
company = repository.save(company);
Thanks :)
The way you created your entities is not correct. You don't need to create an entity for your join table (CompanyActivity/T_COMPANY_ACTIVITY). Instead you should be using the #JoinTable on your activities entity. Something like below:
#OneToMany(cascade = CascadeType.ALL, mappedBy = "company")
#JoinTable(
name = "T_COMPANY_ACTIVITY",
joinColumns = #JoinColumn(name = "COM_ID"),
inverseJoinColumns = #JoinColumn(name = "ACT_ID")
)
private List<CompanyActivity> activities = new ArrayList<>();
for more detailed explanation on how One-to-Many/Many-to-One with Join tables work here: http://www.codejava.net/frameworks/hibernate/hibernate-one-to-many-association-on-join-table-annotations-example
I'm having trouble persists the following entities:
#Entity
#Table(name="entityOne")
public class EntityOne implements Serializable {
#Id
#Column(name = "id", nullable = false)
private Integer id;
#OneToMany(fetch = FetchType.LAZY, mappedBy="entityOne")
private List<EntityTwo> entities;
}
#Entity
#Table(name="entityTwo")
public class EntityTwo implements Serializable {
#Id
#Column(name = "id", nullable = false)
private Integer id;
#Inject
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="entityOne", referencedColumnName="id")
private EntityOne entityOne;
}
In EntityOneDAO:
em.merge(entityOne);
And it is only persisted to EntityOne and not the list of EntityTwo ... How do I persist the list ?
Thanks all
You need to take care of both:
transitive persistence (using Cascade)
synchronizing both end of the bi-directional association.
So EntityOne should Cascade Persist and Merge to EntityTwo:
#OneToMany(fetch = FetchType.LAZY, mappedBy="entityOne", cascade = { CascadeType.PERSIST, CascadeType.MERGE})
private List<EntityTwo> entities = new ArrayList<>();
As you can see, you should always initialize your collection classes to avoid unnecessary null checks.
And it's always better to add the following helper child adding utility in your parent classes (e.g. EntityOne)
public void addChild(EntityTwo child) {
if(child != null) {
entities.add(child);
child.setEntityOne(this);
}
}
Then you can simply call:
EntityOne entityOne = new EntityOne();
entityOne.setProperty("Some Value");
EntityTwo entityTwo_1 = new EntityTwo();
entityTwo_1.setName("Something");
EntityTwo entityTwo_2 = new EntityTwo();
entityTwo_2.setName("Something");
entityOne.addChild(entityTwo_1);
entityOne.addChild(entityTwo_2);
entityManager.persist(entityOne);
P.S.
Please remove the #Inject annotation from the EntityTwo class. Entities are not Components.
And persist is much more efficient than merge, when you want to insert new entities.
You should explicitly set each entityTwo objects' entityOne field.
Such that:
entityTwo_1.setEntityOne(entityOne);
entityTwo_2.setEntityOne(entityOne);
entityOne.entities.add(entityTwo_1);
entityOne.entities.add(entityTwo_2);
em.merge(entityOne);
Try this:
public class EntityOne implements Serializable {
#Id
#Column(name = "id", nullable = false)
private Integer id;
#OneToMany(fetch = FetchType.LAZY, mappedBy="entityOne",
cascade = { CascadeType.ALL})
private List<EntityTwo> entities;
}
#Entity
#Table(name="entityTwo")
public class EntityTwo implements Serializable {
#Id
#Column(name = "id", nullable = false)
private Integer id;
#Inject
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="entityOne", referencedColumnName="id")
private EntityOne entityOne;
}
You can read here, about the CascadeType.
edited.
I am trying to solve a problem regarding category with child categories and parent category on same entity. My database is already set and I can't change it. So, I have mapped my entity this way:
public class Category implements Serializable {
private static final long serialVersionUID = -3432724244623524272L;
#Id
#Column(name = "id")
private Long id;
#Column(name = "key", nullable = false)
private String key;
#Column(name = "category_name", nullable = false)
private String name;
#Column(name = "description")
private String description;
#ManyToOne(targetEntity = Category.class, cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "parent_key", referencedColumnName = "key")
private Category parentCategory;
#OneToMany(mappedBy = "parentCategory", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private List<Category> childCategories;
//getters and setters ommited
}
Note, that the child categories and parent category is not mapped using the ID attribute, but the "key" attribute. This "key" is not a FK. When JPA is trying to get the data, my application crash. But this crash is look like an infinite loop. No exception is thrown.
What am I doing wrong?
Possible infinite loop :
You load an object A
This object has a child B, which is loaded as well since you use FetchType.EAGER
B has a parent, which is A, which is loaded again
etc.
Try using FetchType.LAZY.