Unique constraint violation with ordered Hibernate list - java

I have the following entities and get an exception if I try to remove a Task from the TaskList via removeTask method.
#Entity
public class TaskList extends GenericModel {
#OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
#OrderColumn(name="position", nullable=false)
public List<Task> tasks = new ArrayList<>();
// ...
public void removeTask(Task task) {
tasks.remove(task);
}
}
#Entity
public class Task extends Model {
#OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
#JoinColumn(name="task_id")
private List<Reservation> reservations = new ArrayList<>();
#ManyToOne
public TaskList taskList;
// ...
}
#Entity
public class Reservation extends GenericModel {
#Id
private String id = Token.generate(8);
#ManyToOne
private Task task;
// ...
}
The exception is:
"CONSTRAINT_INDEX_F ON PUBLIC.TASKLIST_TASK(TASKS_ID)"
Unique index or primary key violation: "CONSTRAINT_INDEX_F ON PUBLIC.TASKLIST_TASK(TASKS_ID)"; SQL statement:
update TaskList_Task set tasks_id=? where TaskList_id=? and position=? [23001-149]
I'm using JPA 2 with Hibernate 3.6.1. Is something wrong with my mapping or is it a Hibernate bug?
UPDATE
It seems to be a Hibernate problem. Something with the order of delete and update statements. The following hack solved the problem (partly):
#Entity
public class TaskList extends GenericModel {
// ....
public void removeTask(Task task) {
tasks.remove(task);
tasks = new ArrayList<>(tasks); // only for Hibernate
task.taskList = null;
}
}
Hibernate - clearing a collection with all-delete-orphan and then adding to it causes ConstraintViolationException lead my in the right direction.
But orphanRemoval=true doesn't work with my workaround. It leads to the next exception: "A collection with cascade="all-delete-orphan" was no longer referenced by the owning entity instance". So the problem is not really solved.

Your bidirectional association is mapped twice: once in TaskList, and once in Task. The mapping annotations in TaskList should be
#OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER, mappedBy="task")
#OrderColumn(name="position", nullable=false)
public List<Task> tasks = new ArrayList<>();
Without the mappedBy attribute, Hibernate considers that you have two separate associations (and I doubt that's what you want), rather than a bidirectional one.
Moreover, when you modify a bidirectional association, both sides of the association should be modified. So the removeTask method should be
public void removeTask(Task task) {
tasks.remove(task);
task.setTaskList(null);
}
That's particularly important, because the owning side of the association is Task (because that's where ther is no mappedBy attribute). So this is the side that Hibernate inspects to know that the association must be removed.

Related

Spring data JPA #PreRemove ConcurrentModificationException when removing from parent enity

I have a case where a participant can register courses.
Basically I have the following entity configuration (getters and setters omitted as well as other useless properties) :
#Entity
#Table(name = "course")
public class Course {
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "course")
private Set<Registration> registrations;
}
#Entity
#Table(name = "participant")
public class Participant {
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "participant")
private Set<Registration> registrations;
}
#Entity
#Table(name = "registration")
public class Registration {
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "course_id")
private Course course;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "participant_id")
private Participant participant;
#PreRemove
private void removeRegistrationFromHolderEntities() {
course.getRegistrations().remove(this);
participant.getRegistrations().remove(this);
}
}
Then I can from my viewmodel delete a registration or a course (I have also removed unnecessary stuff) :
#Command
public void deleteRegistration(Registration reg) {
registrationMgr.delete(reg);
}
#Command
public void deleteCourse(Course crs) {
courseMgr.delete(crs);
}
Problem :
If I delete a registration, I need the #PreRemove function so I can remove the references. Without this the remove is ignored (no error, simply ignored)
If I delete a course, I have to remove the #PreRemove function else I get a ConcurrentModificationException (evidently...)
I also cannot remove references from the deleteRegistration method (instead of #PreRemove) because participant registrations are lazily loaded (would raise failed to lazily initialize a collection of role: ..., could not initialize proxy - no Session exception).
What is the best approach here ?
I use Java 11 with Spring Boot 1.0.4 (and spring-boot-starter-data-jpa).
EDIT :
The managers/repositories or defined this way (same for registration and participant) so it should be transactional (I don't have #EnableTransactionManagement on my main class but it should not be required as I don't use transactions outside of repositories) :
#Transactional
#Component("courseMgr")
public class CourseManager {
#Autowired
CourseRepository courseRepository;
public void saveOrUpdate(Course course) {
courseRepository.save(course);
}
public void delete(Course course) {
courseRepository.delete(course);
}
}
public interface CourseRepository extends CrudRepository<Course, Long> {
...
}
EDIT2 :
I think I have found a pretty simple solution :
I have removed the #PreRemove method from the entity, then instead of removing the references like this in the deleteRegistration method (which I had tried but was causing failed to lazily initialize a collection of role exception) :
#Command
public void deleteRegistration(Registration reg) {
reg.getCourse().getRegistrations().remove(reg);
reg.getParticipant.getRegistrations().remove(reg);
registrationMgr.delete(reg);
}
I simply set parents to null, I don't care as it will be deleted...
#Command
public void deleteRegistration(Registration reg) {
reg.setCourse(null);
reg.setParticipant(null);
registrationMgr.delete(reg);
}
So now I can also delete a course without triggering the ConcurrentModificationException in the #PreRemove.
EDIT3 : My bad, registration was not removed with the solution above (still no error but nothing happens). I ended with this instead, which finally works :
#Command
public void deleteRegistration(Registration reg) {
// remove reference from course, else delete does nothing
Course c = getRegistration().getCourse();
c.getRegistrations().remove(getRegistration());
courseMgr.saveOrUpdate(c);
// delete registration from the database
registrationMgr.delete(reg);
}
No need to remove reference from participant...
You have setup your repositories incorrectly. You need a composite PK for Registration and you need to understand that bidirectional mappings are really for query only. Further, bidirectional mappings in Course and Participate present challenges because the ManyToOne relationship through the Registration entity is FetchType.EAGER by default. With all the cascade and fetch annotations you have you are asking for a complicated combination of things from JPA and it seems like you really haven't sorted it all out yet. Start with the basics, be sure to print your SQL statements, and proceed from there if you want to try to finesse more from JPA.
#Entity
#Data
public class Course {
#Id
private Integer id;
private String name;
}
#Entity
#Data
public class Participant {
#Id
private Integer id;
private String name;
}
#Entity
#Data
public class Registration {
#EmbeddedId
private RegistrationPK id;
#ManyToOne
#MapsId("participant_id")
private Participant participant;
#ManyToOne
#MapsId("course_id")
private Course course;
}
#Embeddable
#Data
public class RegistrationPK implements Serializable {
private static final long serialVersionUID = 1L;
private Integer course_id;
private Integer participant_id;
}
Is your basic Entities. The RegistrationRepository needs an additional query.
public interface RegistrationRepository extends JpaRepository<Registration, RegistrationPK> {
Set<Registration> findByCourse(Course c);
}
And to use all this in an example:
#Override
public void run(String... args) throws Exception {
create();
Course c = courseRepo.getOne(1);
Set<Registration> rs = read(c);
System.out.println(rs);
deleteCourse(c);
}
private void create() {
Course c1 = new Course();
c1.setId(1);
c1.setName("c1");
courseRepo.save(c1);
Participant p1 = new Participant();
p1.setId(1);
p1.setName("p1");
participantRepo.save(p1);
Registration r1 = new Registration();
r1.setId(new RegistrationPK());
r1.setCourse(c1);
r1.setParticipant(p1);
registrationRepo.save(r1);
}
private Set<Registration> read(Course c) {
return registrationRepo.findByCourse(c);
}
private void deleteCourse(Course c) {
registrationRepo.deleteAll( registrationRepo.findByCourse(c) );
courseRepo.delete(c);
}
OK solution was pretty simple.
I indeed need to remove the references from the deleteRegistration method. This is what I had tried but was causing failed to lazily initialize a collection of role exception :
#Command
public void deleteRegistration(Registration reg) {
reg.getCourse().getRegistrations().remove(reg);
reg.getParticipant.getRegistrations().remove(reg);
registrationMgr.delete(reg);
}
The trick is that I also have to save the course entity before trying to delete the registration.
This works :
#Command
public void deleteRegistration(Registration reg) {
// remove reference from course, else delete does nothing
Course c = getRegistration().getCourse();
c.getRegistrations().remove(getRegistration());
courseMgr.saveOrUpdate(c);
// delete registration from the database
registrationMgr.delete(reg);
}
No need to remove reference from participant...
#PreRemove was doing the job, but that way I can now also delete a course without triggering the ConcurrentModificationException.

How to delete relation between two tables with one-to-many unidirectional

I have two entities bound with one-to-many relationship. But one entity can exist without the other. So the relationship is uni-directional. As this;
#Entity
public class TransportationOrderProduct {
#OneToMany(fetch = FetchType.EAGER,cascade = CascadeType.ALL)
private List<WarehousePackage> selectedWarehousePackages;
}
#Entity
public class WarehousePackage{
}
And hibernate created these tables;
TransportationOrderProduct
id
TransportationOrderProductSelectedWarehousePackages
transportationOrderProductId
selectedWarehousePackageId
WarehousePackage
id
When fetching the collection (selectedWarehousePackages) everything works fine.
But when I clear the TransportationOrderProduct.selectedWarehousePackages list, and add new ones, Hibernate throws DuplicateEntry exception. Saying that transportationOrderProductId in the TransportationOrderProductSelectedWarehousePackages table cannot be inserted twice.
I think this is because Hibernate doesn't delete the relation in the TransportationOrderProductSelectedWarehousePackages table when I call;
TransportationOrderProduct.selectedWarehousePackages.clear()
And add some entities after ;
TransportationOrderProduct.selectedWarehousePackages.add(newPackage)
TransportationOrderProduct.selectedWarehousePackages.add(newPackage)
.
.
Can somebody help?
Its sounds the relation is one-Many as I understand
Don’t use unidirectional one-to-many associations
avoid unidirectional one-to-many associations in your domain model. Otherwise, Hibernate might create unexpected tables and execute more SQL statements than you expected and this certainly described why hibernate create 3 entities while your implementation should be 2 entities with relation one-2-many.
The definition of uni-directional sounds not an issue in our case if we use directional it's OKAY it will serve our purpose you just need attribute to map our association
using annotation
#Entity
public class TransportationOrderProduct {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private long id;
#OneToMany(fetch = FetchType.EAGER,cascade = CascadeType.ALL)
private Set<WarehousePackage> packages= new HashSet<>();
public Set<WarehousePackage> getPackages() {
return packages;
}
public void setPackages(Set<WarehousePackage> packages) {
this.packages = packages;
}
public void addPackages (WarehousePackage value) {
this.packages.add(value);
}
public void clearPackages (WarehousePackage value) {
this.packages.clear();
}
...
}
#Entity
public class WarehousePackage{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private long id;
}
You can save transportation order with association you need
TransportationOrderProduct transportation = new TransportationOrderProduct ();
Set<WarehousePackage> packages = new HashSet <> () ;
WarehousePackage package1 = new WarehousePackage () ;
WarehousePackage package2 = new WarehousePackage () ;
packages.add(package1);
packages.add(package2);
transportation.setPackages(packages) ;
session.save(transportation);

OpenJPA: Cascading delete throws PersistenceException

I have been away from the Java world for a while, and I am new to JPA. I have a data model where WindTurbines can have several PerformanceCurves, which in turn will contain several PerfCurvePoints. I have set cascade=CascadeType.ALL and orphanRemoval=true on the relationships.
When I remove a PerformanceCurve from a WindTurbine, I get a PersistenceException because there are PerfCurvePoint records in the database that reference the PerformanceCurve.
I expected that the PerfCurvePoint records would be automatically deleted along with the orphaned PerformanceCurve, since I specified CascadeType.ALL on that relationship.
I know that I can work around this by clearing the PerfCurvePoints collection on the PerformanceCurve object and calling EntityManager.flush() before I remove the PerformanceCurve from the WindTurbine, but I don't like having to reference the EntityManager in that layer of our application. Is there a better way to handle this?
#Entity
public class WindTurbine implements Serializable {
#OneToMany(mappedBy="turbine", fetch=FetchType.EAGER, cascade=CascadeType.ALL, orphanRemoval=true)
private ArrayList<PerformanceCurve> perfCurves = new ArrayList<>();
...
}
#Entity
public class PerformanceCurve implements Serializable {
#ManyToOne(optional=false)
#JoinColumn(name="TURBINE_ID", referncedColumnName="TURBINE_ID")
private WindTurbine turbine;
#OneToMany(mappedBy("perfCurve", fetch=FetchType.EAGER, cascade=CascadeType.ALL, orphanRemoval=true)
private ArrayList<PerfCurvePoint> points = new ArrayList<>();
...
}
#Entity
public class PerfCurvePoint implements Serializable {
#ManyToOne(optional=false)
#JoinColumn(name="PERF_CURVE_ID", referncedColumnName="PERF_CURVE_ID")
private PerformanceCurve perfCurve;
...
}
public class Foo {
public void Bar(WindTurbine turbine) {
// The following line causes a PersistenceException to be thrown
// on flush/commit if the performance curve contains any points.
turbine.getPerformanceCurves().remove(1);
}
public void Workaround(EntityManager em, WindTurbine turbine) {
// This works, but it requires a call to
// EntityManager.flush() that I would like to avoid.
PerformanceCurve curve = turbine.getPerformanceCurves().get(1);
curve.getPoints().clear();
em.flush();
turbine.getPerformanceCurves().remove(curve);
}
}
EDIT: I am actually getting a PersistenceException on all cascading deletes, whether they are a result of orphan removal or not.
It turns out that OpenJPA needs to be configured so it is aware of existing foreign key constraints in the database, or else it may try to delete the parent before it deletes the children. The solution was to add this line to persistence.xml:
<property name="openjpa.jdbc.SchemaFactory" value="native(ForeignKeys=true)" />

Hibernate delete doesn't cascade

I have a few entities linked as follows:
#Entity
#Table(name = "distribution_activity")
public class DistributionActivity extends AbstractActivity {
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "activity", orphanRemoval = true)
protected Set<DistributionTask> tasks = new TreeSet<>();
...
}
and
#Entity
#Table(name = "distribution_task")
public class DistributionTask extends AbstractTask {
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "activity_id")
protected DistributionActivity activity;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "store_id")
protected Store store;
...
}
and
#Entity
#Table(name = "store")
public class Store extends AbstractAuditableEntity {
#OneToMany(cascade = CascadeType.REMOVE, fetch = FetchType.EAGER, mappedBy = "store", orphanRemoval = true)
protected Set<DistributionTask> distributionTasks = new TreeSet<>();
...
}
The repositories are as follows:
#Repository
public interface DistributionActivityRepository extends PagingAndSortingRepository<DistributionActivity, Long> {
}
and
#Repository
public interface StoreRepository extends PagingAndSortingRepository<Store, Long> {
}
I'm using MySQL and the tables are generated WITHOUT any cascade options on the foreign keys. When I delete a DistributionActivity everything works fine and Hibernate actually issues delete statements for each of the linked tasks.
hibernate.SQL:109 - delete from distribution_task where id=? and version=?
hibernate.SQL:109 - delete from distribution_activity where id=? and version=?
When I delete a Store however, no delete statements are generated for the linked tasks and a MySQLIntegrityConstraintViolationException exception is thrown referring to a foreign key violation.
hibernate.SQL:109 - delete from store where id=? and version=?
Any clues?
Can you post the snippet of code around session.delete() where the deletion happens?
I cannot say it for sure but I have ran in similar issues before related to bidirectional relationships. Briefly speaking, I needed to make sure the objects are in sync when using bidirectional relationships.
It looks to me that you want to delete a Store that still have a reference in DistributionTask.
Example, if you have something like this:
session.delete(store);
Then, try changing for something like this:
distributionTask.setStore(null);
session.save(distributionTask);
session.delete(store);
So; you need control the consistency of your objects manually when dealing with bidirectional relationship.
I hope it helps. Cheers,

Does Hibernate allow to load/save linked entities?

Suppose I have self linked Category entity defined as follows:
public class Category {
#Id
#Access(AccessType.FIELD)
public String Url;
#ManyToOne(optional=true)
#Access(AccessType.FIELD)
public Category Parent;
#OneToMany
private Set<Category> subs;
public void addSub(Category sub) {
subs.add(sub);
}
public void removeSub(Category sub) {
subs.remove(sub);
}
#Access(AccessType.FIELD)
public String Title;
#Access(AccessType.FIELD)
public boolean Done;
I wonder, will it work correctly if I create new Category and add it with my addSub method? Will Category be persisted correctly? Will subcategories be persisted automatically and in correct order?
In the current state of your code - no. To make it work as you want you need to do the followng:
Connect sides of bidirectional relationship with mappedBy on #OneToMany, otherwise Hibernate would think that you have two different relationships:
#OneToMany(mappedBy = "Parent")
It's your responsibility to keep both sides of your relationship in consistent state:
public void addSub(Category sub) {
sub.setParent(this);
subs.add(sub);
}
Hibernate looks at #ManyToOne side when it stores the foreign key.
If you want subcategories of persistent Category to be persisted automatically, you need to configure cascading:
#OneToMany(..., cascade = CascadeType.ALL)

Categories