I had following entities in my project:
AccountGroup
AccountItem
AccountSegment
With folowing relations:
AccountGroup has List<AccountItem>
AccountItem had List<AccountSegment>
and everything worked fine.
When I changed last relation to:
AccountItem has Set<AccountSegment>
AccountGroup object read from database looks strange. If given AccountItem had three AccountSegments, then I have three the same AccountItems in AccountGroup.
A shot from debugger can possibly tell it better than I can:
As you can see, accountMapperItems list has four positions instead of two. First pair is a duplicate each having the same variables. (second is similar, not shown on screenshot).
Below I paste entities code fragments:
class AccountGroup {
...
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "group")
private List<AccountItem> accountMapperItems;
....
}
class AccountItem {
...
#ManyToMany(fetch = FetchType.EAGER, cascade = { CascadeType.REFRESH, CascadeType.MERGE })
#JoinTable(
joinColumns={#JoinColumn(name="ACCOUNT_ITEM_ID", referencedColumnName="ID")},
inverseJoinColumns={#JoinColumn(name="SEGMENT_ID", referencedColumnName="ID")})
private Set<Segment> segmentSet;
#ManyToOne
private AccountGroup group;
...
}
AccountSegment does not have any links.
Does anyone know why is it retriving accountMapperItems list one position per AccountSegment?
The problem is not duplicate entries in jointable! I have double checked it.
Update
#Fetch (FetchMode.SELECT)
solved the case, further explanations are available in post mentioned in the answer.
Make sure all your entities implement hashCode() and equals(). These methods are used by some collections (like Sets) to uniquely identify elements.
Edit: If that doesn't solve it, then I think the duplicates are most likely caused by the FetchType.EAGER. This answer explains it well. Try removing the FetchType.EAGER to see if it makes any difference.
Related
I have a ManyToMany-relation between student and teacher in a Student_Teacher-table (Entityless).
Student: Teacher(owning-side): Student_Teacher
1= Tim 50= Mrs. Foo 1= 1 50
2= Ann 51= Mr. Bar 2= 1 51
3= 2 50
4= 2 51
As you see above every Student is currently related to every Teacher.
Now I like to remove Ann and I like to use the database's cascading techique to remove entries from the Student_Teacher-table but I do neither like to remove other Students, nor Teacher, nor other relationship.
This is what I have in the Student-Entity:
#ManyToMany(mappedBy="students")
public Set<Teacher> getTeachers() {
return teachers;
}
This is what I have in the Teacher-Entity:
#ManyToMany
#JoinTable(name="Student_Teacher", joinColumns = {
#JoinColumn(name="StudentID", referencedColumnName = "TeacherID", nullable = false)
}, inverseJoinColumns = {
#JoinColumn(name="TeacherID", referencedColumnName = "StudentID", nullable = false)
})
public Set<Student> getStudents() {
return students;
}
Now I like to use the database's delete cascade functionality. I repeat: The database's delete cascade functionality targeting the Student_Teacher-table only!
The problem:
org.h2.jdbc.JdbcSQLException: Referentielle Integrität verletzt: "FK_43PMYXR2NU005M2VNEB99VX0X: PUBLIC.Student_Teacher FOREIGN KEY(StudentID) REFERENCES PUBLIC.Student(StudentID) (2)"
Referential integrity constraint violation: "FK_43PMYXR2NU005M2VNEB99VX0X: PUBLIC.Student_Teacher FOREIGN KEY(StudentID) REFERENCES PUBLIC.Student(StudentID) (2)"; SQL statement:
delete from "Student" where name='Ann'
at org.h2.message.DbException.getJdbcSQLException(DbException.java:345)
at org.h2.message.DbException.get(DbException.java:179)
at org.h2.message.DbException.get(DbException.java:155)
at org.h2.constraint.ConstraintReferential.checkRow(ConstraintReferential.java:425)
What i can not use is the
#ManyToMany(cascade={CascadeType.REMOVE})
Because of the documetation tells me:
(Optional) The operations that must be cascaded to the target of the association.
The "target" is the Teacher, so this cascade would remove the Teacher (what I do not like to remove).
Question:
How to configure the entitys to remove Ann and the relation only using the database's cascade functionality?
Proof of Concept:
I tried another feature, I have noticed the possibility to configure the foreign-key nativly like this:
#ManyToMany(cascade = { CascadeType.REMOVE })
#JoinTable(name="Student_Teacher", joinColumns = {
#JoinColumn(name="StudentID", referencedColumnName = "TeacherID", nullable = false, foreignKey=#ForeignKey(foreignKeyDefinition="FOREIGN KEY (StudentID) REFERENCES Student ON DELETE NO ACTION"))
}, inverseJoinColumns = {
#JoinColumn(name="TeacherID", referencedColumnName = "StudentID", nullable = false, foreignKey=#ForeignKey(foreignKeyDefinition="FOREIGN KEY (TeacherID) REFERENCES Teacher ON DELETE NO ACTION"))
})
public Set<Student> getStudents() {
return students;
}
The problem is: This works fine but to trigger the removal of the entries in Student_Teacher I have to specify #ManyToMany(cascade = { CascadeType.REMOVE }) on both sides. Hibernate do not parse the foreignKeyDefinition and only see the CascadeType.REMOVE and drops the target-entitys (and the referenced Student Tim) out of the cache, but they are still in the database!!! So I have to clear the hibernate-session immendentelly after drop to re-read the existence of the Teachers Mrs. Foo and Mr. Bar and the Student Tim.
Now I like to use the database's delete cascade functionality. I
repeat: The database's delete cascade functionality targeting the
Student_Teacher-table only!
Simply define the cascade deletion on the database schema level, and the database would do it automatically. However, if the owning side of the association is loaded/manipulated in the same persistence context instance, then the persistence context will obviously be in an inconsistent state resulting in issues when managing the owning side, as Hibernate can't know what is done behind its back. Things get even more complicated if second-level caching is enabled.
So you can do it and take care not to load Teachers in the same session, but I don't recommend this and I write this only as an answer to this part of the question.
How to configure the entities to remove Ann and the relation only
using the database's cascade functionality?
There is no such configuration on JPA/Hibernate level. Most DDL declarations in mappings are used only for automatic schema generation, and are ignored when it comes to entity instances lifecycle and association management.
What i can not use is the
#ManyToMany(cascade={CascadeType.REMOVE})
Cascading of entity lifecycle operations and association management are two different notions that are completely independent of each other. Here you considered the former while you need the latter.
The problem you're facing is that you want to break the association from the Student (inverse side marked with mappedBy) when the Teacher is the owning side. You can do it by removing the student from all teachers to which it is associated, but that could lead to loading lots of data (all associated teachers with all their students). That's why introducing a separate entity for the association table could be a good compromise, as already suggested by #Mark, and as I suggested as well in some of my previous answers on similar topics together with some other potential improvements.
You may create a new entity TeacherStudent for the relationship, and then use CascadeType.REMOVE safely:
#Entity
public class Student {
#OneToMany(mappedBy="student",cascade={CascadeType.REMOVE})
public Set<TeacherStudent> teacherStudents;
}
#Entity
public class Teacher {
#OneToMany(mappedBy="teacher",cascade={CascadeType.REMOVE})
public Set<TeacherStudent> teacherStudents;
}
#Entity
public class TeacherStudent {
#ManyToOne
public Teacher teacher;
#ManyToOne
public Student student;
}
You'll have to take care of the composite foreign key for TeacherStudent. You may take a look at https://stackoverflow.com/a/29116687/3670143 for that.
Another relevant thread about ON DELETE CASCADE is JPA + Hibernate: How to define a constraint having ON DELETE CASCADE
As you see above every Student is related to every Teacher.
The point is when in a situation where "every A is related to every B", then there is no need to have a many to many table to keep such relationship. Since logically A and B is independent to each other in this situation. Adding/Deleting/Modifying A makes no effect on B and vice versa. This behaviour is exactly what you are after, because you want the cascading operations stop at the relation table:
delete cascade functionality targeting the Student_Teacher-table only!
Relationship table is only useful when in situation where "every A is related to a subset of B".
So to solve your problem is actually a fairly one: Drop the Student_Teacher table.
As we had a similar problem but finally solved it another way, here is our solution :
We replaced
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
in our relationship with
#ManyToMany(fetch = FetchType.LAZY, cascade = {
CascadeType.DETACH,
CascadeType.MERGE,
CascadeType.REFRESH,
CascadeType.PERSIST
})
and it successfully removed the association without removing linked entity.
I have a convenient relation set up in which an entity has a one-to-many relationship with another, and that has a many-to-one with another. So, a LISTING has many LISTING_LINE_ITEMS, and those LISTING_LINE_ITEMS have one SERVICE_PERIOD, but a SERVICE_PERIOD has many LISTING_LINE_ITEMS. I have attempted to describe this relationship using JPA's #JoinTable as follows:
LISTING
#OneToMany
#JoinTable (name = "LISTING_LINE_ITEM", joinColumns = #JoinColumn (name = "listing_id"), inverseJoinColumns = #JoinColumn (name = "service_period_id"))
Set<ServicePeriod> servicePeriods;
LISTING_LINE_ITEM
#ManyToOne (fetch = FetchType.EAGER)
#JoinColumn (name = "listing_id", nullable = false)
Listing listing;
#ManyToOne (fetch = FetchType.EAGER, cascade = CascadeType.PERSIST)
#JoinColumn (name = "service_period_id")
ServicePeriod servicePeriod;
SERVICE_PERIOD
#ManyToOne
#JoinTable (name = "LISTING_LINE_ITEM", joinColumns = #JoinColumn (name = "service_period_id"), inverseJoinColumns = #JoinColumn (name = "listing_id"))
Listing listing;
The obvious goal is to be able to easily obtain a list of ServicePeriods for a Listing or a single Listing for a ServicePeriod. Currently the way this is set up I'm getting an exception:
org.hibernate.HibernateException: More than one row with the given identifier was found: 361951, for class: com.gonfind.entity.ServicePeriod
I believe this is because a listing has ListingLineItems that refer to the same ServicePeriod. I'm sure that there is a way to accomplish what I'm after but I don't know what it is.
You do appear to have some problems there. On the technical / JPA side:
you cannot use LISTING_LINE_ITEM both as a join table and as an entity table. There are several reasons for this, but the main reason is that you will confuse JPA: it will try to use that table in different, incompatible ways for those two purposes.
in JPA, a bidirectional relationship is owned by exactly one side; the other side uses the mappedBy attribute of its relationship annotation to reference the owning side.
But you also have data design problems. Your constraint that line items' service periods be restricted to one of those separately associated with the same listing constitutes either
a functional dependency between non-key fields, if the listing id is not part of the line item key, or otherwise
a functional dependency on a subset of a key.
In the first case, your data fail to be in third normal form; in the second case they fail to be even in second normal form. Your trouble modeling this with JPA arises in part from the low level of normalization.
Normalizing your data properly would make things a lot easier on multiple levels. To do that, you need to remove the direct association between listings and line items, and instead associate them through service periods. You then would have:
Listing <-- one to many --> ServicePeriod <-- one to many --> LineItem
Of course, that would have implications on the structure of your application, but it's likely to be a long-term development and maintenance win, and maybe even a usability win, for the application to be aligned with the natural structure of your data like that. If you wish, you could put methods on your Listing entity to allow ListingLineItems to be managed to some extent as if they belonged directly to Listings, and vise versa.
That data organization would look something like this:
LISTING
#OneToMany(mappedBy = "listing",
fetch = FetchType.EAGER,
cascade = CascadeType.PERSIST)
Set<ServicePeriod> servicePeriods;
SERVICE_PERIOD
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "listing_id")
Listing listing;
#OneToMany(mappedBy = "servicePeriod",
fetch = FetchType.EAGER,
cascade = CascadeType.PERSIST)
Set<ListingLineItem> lineItems;
LISTING_LINE_ITEM
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "service_period_id")
ServicePeriod servicePeriod;
If you cannot restructure your data more or less that way, then you're stuck jerry-rigging something that cannot fully be described to JPA. I'm imagining a separate join table for Listing <-> ServicePeriod, a non-JPA FK constraint to that table from the entity table for line items, and, of course, proper form for the various bidirectional relationships.
I have some entities with#ManyToMany relation:
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "buses_drivers",
joinColumns = #JoinColumn (name = "driver_id_inner", referencedColumnName = "driver_id"),
inverseJoinColumns = #JoinColumn (name = "bus_id_inner", referencedColumnName = "bus_id"))
private List<Bus> buses;
and
#ManyToMany(mappedBy = "buses", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private List<Driver> drivers;
When execute saving Driver model with some Bus models, all ok. Tables buses_drivers store all keys those entities. But when saving Bus model with drivers, table doesn't change. I think problem with inverseJoinColmns mapping.
That is the expected behaviour. In a bidirectional many-to-many association one side has to be the inverse side. In your case it is the Bus side because it contains mappedBy:
The field that owns the relationship. Required unless the relationship
is unidirectional.
That means that Driver is the owner of the association and Hibernate will only check that side when maintaining the association.
You should definitely redesign your relations.
Without even getting into the problems with your current save scenario, with bidirectional #ManyToMany + CascadeType.ALL, you're destined to get even more troubles.
For example, deleting one bus will due to cascade, delete all its drivers, which due to cascade again, will delete all its buses. You'll basically end up deleting much more than you probably want. Also, check the SQL generated by these mappings, you'll most likely notice that its far from ideal.
For people doesn't understand from the accepted answer. This is more appropriate : Java: saving entities with ManyToMany association
I came across with this problem in test cases when filling test data.
When there is an owning side you just can save child just with owner.
I have two entities connected with a bidirectional OneToMany/ManyToOne relationship.
#Entity
class One {
#OneToMany(mappedBy = "one", orphanRemoval = true, fetch = FetchType.EAGER)
private Set<Many> manies;
// ...
}
#Entity
class Many {
#ManyToOne
#JoinColumn(name = "one_id", nullable = false)
private One one;
// ...
}
When I want to remove a Many instance, I remove it from its One's manies Set and delete it from the database. If I take the same One instance and save it again (because I changed anything, it doesn't have to be related to the relationship), I get an exception:
javax.persistence.EntityNotFoundException: Unable to find test.Many with id 12345
The ID (in this example 12345) is the ID of the just removed entity. Note that removal of the entity 12345 succeeded: The transaction commits successfully and the row is removed from the database.
Adding and removing instances is done with Spring Data repositories. Removing looks more or less like this:
one.getManies().remove(manyToRemove);
oneDao.save(one);
manyDao.delete(manyToRemove);
I debugged a little and found out that the Set is a Hibernate PersistentSet which contains a field storedSnapshot. This in turn is another Set that still references the removed entity. I have no idea why this reference is not removed from the snapshot but I suspect this is the problem: I think Hibernate tries to remove the entity a second time because it's in the snapshot but not in the actual collection. I searched for quite a while but I didn't encounter others with a similar problem.
I use Hibernate 4.2.2 and Spring Data 1.6.0.
Am I doing something inherently wrong? How can I fix this?
I'm having the same problem.
A workaround is to replace the entire collection.
Set<Many> maniesBkp = one.getManies(); // Instance of PersistentSet
maniesBkp.remove(manyToRemove);
Set<Many> manies = new HashSet<Many>();
manies.addAll(maniesBkp);
one.setManies(manies);
...
manyDao.delete(manyToRemove);
...
oneDao.save(one);
As your #OneToMany relation has orphanRemoval = true, you don't have to explicitly remove the child element from the database - just remove it from the collection and save the parent element.
Try add CascadeType.REMOVE and orphanRemoval
#OneToMany(mappedBy = "one", orphanRemoval = true, fetch = FetchType.EAGER, cascade = CascadeType.REMOVE, orphanRemoval = true)
Than perform delete following
one.getManies().remove(manyToRemove);
oneDao.save(one);
Edited:
I have created POC, look on the UserRepositoryIntegrationTest.departmentTestCase (code)
I have two classes:
class TrainingCourse {
Integer id;
#OneToMany(cascade = CascadeType.ALL, fetch=FetchType.EAGER)
#JoinTable(name = "TrainingCourseClass", joinColumns = { #JoinColumn(name = "CourseID") }, inverseJoinColumns = { #JoinColumn(name = "ClassID") })
private Set<TrainingClass> trainingClasses;
}
class TrainingClass {
Integer id;
}
In the database they are mapped using a join table. So this is a unidirectional relationship.
From the UI, when a TrainingCourse is created, a list of previously created TrainingClasses are selected from the UI.
Now if I create the TrainingCourse, then it automatically updates the associated TrainingClasses also. But trainingClass is independent of TrainingCourse and can exist independently. So TrainingClasses are created and updated separately from the TrainingCourse. So saving the TrainingCourse should save data in the TrainingCourse table and it will also save the association in the join Table TrainingCourseClass. Nothing should happen in the table TrainingClass.
However if I add these to the columns:
nullable=false, updatable=false and CascadeType.REMOVE
#OneToMany(cascade = CascadeType.REMOVE, fetch=FetchType.EAGER)
#JoinTable(name = "TrainingCourseClass", joinColumns = { #JoinColumn(name = "CourseID", nullable=false, updatable=false) }, inverseJoinColumns = { #JoinColumn(name = "ClassID", nullable=false, updatable=false) })
private Set<TrainingClass> trainingClasses;
Then the problem is fixed ie creating trainingCourse doesn't update the trainingClass table. Now I am not 100% sure whether it is the right solution or how it is working to solve the problem. There is also another thing called MappedBy. I am not sure whether this is relevant here.
I just used it as a guess and it is working. Moreover, this seems to be really a many-to-many relationship ie The same class can belong to many courses and one course can include many classes. But one-to-many relationship is also working. This is not very convincing. The trainingclass is really unaware of what training courses include it. It looks like the difference between one-to-many and many-to-many is like whether or not to have bidirectional pointers to each other.
Hence please suggest whether the above approach is correct to prevent updating the trainingclass while creating the trainingcourse.
Thanks
Your first mapping uses cascade = ALL. That means that every operation you make on a TrainingCourse (persist, merge, remove, etc.) will also be applied on the associated TrainingClass. That's precisely what you don't want, if I understand correctly. So just don't set any cascade to this association.
Regarding OneToMany vs. ManyToMany: if what you really want is a OneToMany (i.e. a TraningClass should not be associated with more than one TrainingCourse), then you should have a unique contraint on the TrainingCourseClass.ClassID column. That's what guarantees that the association is a OneToMany and not a ManyToMany.