We have three entities with bidirectional many-to-many mappings in a A <-> B <-> C "hierarchy" like so (simplified, of course):
#Entity
Class A {
#Id int id;
#JoinTable(
name = "a_has_b",
joinColumns = {#JoinColumn(name = "a_id", referencedColumnName = "id")},
inverseJoinColumns = {#JoinColumn(name = "b_id", referencedColumnName = "id")})
#ManyToMany
Collection<B> bs;
}
#Entity
Class B {
#Id int id;
#JoinTable(
name = "b_has_c",
joinColumns = {#JoinColumn(name = "b_id", referencedColumnName = "id")},
inverseJoinColumns = {#JoinColumn(name = "c_id", referencedColumnName = "id")})
#ManyToMany(fetch=FetchType.EAGER,
cascade=CascadeType.MERGE,CascadeType.PERSIST,CascadeType.REFRESH})
#org.hibernate.annotations.Fetch(FetchMode.SUBSELECT)
private Collection<C> cs;
#ManyToMany(mappedBy = "bs", fetch=FetchType.EAGER,
cascade={CascadeType.MERGE,CascadeType.PERSIST, CascadeType.REFRESH})
#org.hibernate.annotations.Fetch(FetchMode.SUBSELECT)
private Collection<A> as;
}
#Entity
Class C {
#Id int id;
#ManyToMany(mappedBy = "cs", fetch=FetchType.EAGER,
cascade={CascadeType.MERGE,CascadeType.PERSIST, CascadeType.REFRESH})
#org.hibernate.annotations.Fetch(FetchMode.SUBSELECT)
private Collection<B> bs;
}
There's no conecpt of an orphan - the entities are "standalone" from the application's point of view - and most of the time we're going to have a fistful of A:s, each with a couple of B:s (some may be "shared" among the A:s), and some 1000 C:s, not all of which are always "in use" by any B. We've concluded that we need bidirectional relations, since whenever an entity instance is removed, all links (entries in the join tables) have to be removed too. That is done like this:
void removeA( A a ) {
if ( a.getBs != null ) {
for ( B b : a.getBs() ) { //<--------- ConcurrentModificationException here
b.getAs().remove( a ) ;
entityManager.merge( b );
}
}
entityManager.remove( a );
}
If the collection, a.getBs() here, contains more than one element, then a ConcurrentModificationException is thrown. I've been banging my head for a while now, but can't think of a reasonable way of removing the links without meddling with the collection, which makes underlying the Iterator angry.
Q1: How am I supposed to do this, given the current ORM setup? (If at all...)
Q2: Is there a more reasonable way do design the OR-mappings that will let JPA (provided by Hibernate in this case) take care of everything. It'd be just swell if we didn't have to include those I'll be deleted now, so everybody I know, listen carefully: you don't need to know about this!-loops, which aren't working anyway, as it stands...
This problem has nothing to do with the ORM, as far as I can tell. You cannot use the syntactic-sugar foreach construct in Java to remove an element from a collection.
Note that Iterator.remove is the only safe way to modify a collection during iteration; the behavior is unspecified if the underlying collection is modified in any other way while the iteration is in progress.
Source
Simplified example of the problematic code:
List<B> bs = a.getBs();
for (B b : bs)
{
if (/* some condition */)
{
bs.remove(b); // throws ConcurrentModificationException
}
}
You must use the Iterator version to remove elements while iterating. Correct implementation:
List<B> bs = a.getBs();
for (Iterator<B> iter = bs.iterator(); iter.hasNext();)
{
B b = iter.next();
if (/* some condition */)
{
iter.remove(); // works correctly
}
}
Edit: I think this will work; untested however. If not, you should stop seeing ConcurrentModificationExceptions but instead (I think) you'll see ConstraintViolationExceptions.
void removeA(A a)
{
if (a != null)
{
a.setBs(new ArrayList<B>()); // wipe out all of a's Bs
entityManager.merge(a); // synchronize the state with the database
entityManager.remove(a); // removing should now work without ConstraintViolationExceptions
}
}
If the collection, a.getBs() here, contains more than one element, then a ConcurrentModificationException is thrown
The issue is that the collections inside of A, B, and C are magical Hibernate collections so when you run the following statement:
b.getAs().remove( a );
this removes a from b's collection but it also removes b from a's list which happens to be the collection being iterated over in the for loop. That generates the ConcurrentModificationException.
Matt's solution should work if you are really removing all elements in the collection. If you aren't however another work around is to copy all of the b's into a collection which removes the magical Hibernate collection from the process.
// copy out of the magic hibernate collection to a local collection
List<B> copy = new ArrayList<>(a.getBs());
for (B b : copy) {
b.getAs().remove(a) ;
entityManager.merge(b);
}
That should get you a little further down the road.
Gray's solution worked! Fortunately for us the JPA people seem to have been trying to implement collections as close to official Sun documentation on the proper use of List<> collections has indicated:
Note that Iterator.remove is the only safe way to modify a collection during iteration; the behavior is unspecified if the underlying collection is modified in any other way while the iteration is in progress.
I was all but pulling out my hair over this exception thinking it meant one #Stateless method could not call another #Stateless method from it's own class. This I thought odd as I was sure that I read somewhere that nested transactions are allowed. So when I did a search on this very exception, I found this posting and applied Gray's solution. Only in my case I happened to have two independent collections that had to be handled. As Gray indicated, according the Java spec on the proper way to remove from a member from a Java container, you need to use a copy of the original container to iterate with and then do your remove() on the original container which makes a lot of sense. Otherwise, the original container's link list algorithm gets confused.
for ( Participant p2 : new ArrayList<Participant>( p1.getFollowing() )) {
p1.getFollowing().remove(p2);
getEm().merge(p1);
p2.getFollowers().remove(p1);
getEm().merge(p2);
}
Notice I only make a copy of the first collection (p1.getFollowing()) and not the second collection (p2.getFollowers()). That is because I only need to iterate from one collection even though I need to remove associations from both collections.
Related
I modeled a unidirectional #ManyToMany self-referencing relationship. A test may require other tests in order to be executed:
#Entity
public class Test{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToMany
#JoinTable(
name = "required_",
joinColumns = #JoinColumn(name = "test_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "required_test_id", referencedColumnName = "id")
)
private Set<Test> requiredTests;
}
Each test is described by an XML-file.
In the XML the required tests are referenced by name.
Now i'm trying to import all the tests, but so far the dependency-relationships between the tests are not correctly saved in the DB. I guess I'm doing something wrong.
This is what I did (pseudo-code):
//Each test is imported with this method:
private void import(TestCaseXml testCaseXml) {
Test test= testRepository.findByName(testCaseXml.getName()).orElse(new Test());
test.setRequiredTests(fetchAlreadyExistentOrCreateRequiredTestsDeclaredIn(testCaseXml));
testRepository.save(test);
}
private Set<Test> fetchAlreadyExistentOrCreateRequiredTestsDeclaredIn(
final TestCaseXml testCaseXml) {
final List<String> requiredTestcasesByName = testCaseXml.getNameOfRequiredTests();
return requiredTestcasesByName.stream()
.map(name -> testRepository.findByName(name)
.orElse(testRepository.save(new Test().setName(name))))
.collect(Collectors.toSet());
}
// The import of all the tests is packed in one single transaction.
So far, as result: only one dependency is persisted (let's say Test A requires B, C and D), then the joint table would look like ( A - D ).
Does anyone would have a clue?
Edit after Chris' feedback
I made some more experiments, I'd like to share here since the outcome is really confusing.
First scenario: Let's say I have 3 tests I want to import (A, B and C) and they will be processed in this order.
Test A requires Test B and Test C.
Test B and C have no requirement.
When Test A is being imported, at some point fetchAlreadyExistentOrCreateRequiredTestsDeclaredIn() will be called. I debugged it and can confirm that the method returns a Set containing Test B and Test C, both of them with name and id (The presence of the id is a bit surprising - may be Hibernate flushed before the end of the global transaction?). Anyway, this result do not confirm Chris' hypothesis, since it does return a Set with the 2 expected tests.
Nevertheless: I repeated this first scenario, but this time using List instead of Set, as Chris suggested, and indeed it did work. To be honest, I don't understand why.
Now it gets still a bit more weird:
Second scenario: I have 3 tests I want to import (A, B and C) and they will be processed in this order.
Test A has no requirement
Test B requires Test A and C
Test C has no requirement
This will throw an Exception
java.sql.SQLIntegrityConstraintViolationException: (conn=819) Duplicate entry 'Test A' for key 'XYZ'
Somehow it seems I fixed this by getting rid of the functional syntax in fetchAlreadyExistentOrCreateRequiredTestsDeclaredIn()
I replaced
return requiredTestcasesByName.stream()
.map(name -> testRepository.findByName(name)
.orElse(testRepository.save(new Test().setName(name))))
.collect(Collectors.toList());
with this:
final var requiredTests = new ArrayList<Test>();
for (final String name: requiredTestcasesByName) {
final Test test = testRepository.findByName(testcaseName).isPresent()
? testRepository.findByName(name).get()
: testRepository.save(new Test().setName(name));
requiredTests.add(test);
}
return requiredTests;
After performing these 2 changes (List instead of Set, and get rid of the functional syntax) it seems to work as expected. I'd like to understand what is happening behind the scene.
Edit 27.06.22
I setup a demo project to reproduce this strange behaviour:
https://github.com/JulienDeBerlin/manyToMany/tree/master
I'm pretty confident you have implemented equals and hashcode methods in your entity classes, and that they rely on the ID. Your code can then be broken into the following equivalent sequence:
Set set = new HashSet();
Test b = new Test().setName(b);
set.add(b);
Test c = new Test().setName(c);
set.add(c);
Test d = new Test().setName(d);
set.add(d);
assertEquals(set.size(),1);
Why? If you check what is returned from each testRepository.save call, they do not have their IDs generated. JPA does NOT guarantee sequences are set on persist calls (which are underneath your Spring repository.save call), but does guarantee they will be set on the instance when the transaction is synchronized (flushed or committed) to the database. As they are all in the same transaction, that only happens AFTER they are added to the set. Your hashcode/equality methods have already dealt with all 3 and determined they are the same instance (null id), so replaces the existing one with the latest one added.
Simplest solution is to return a list instead:
private List<Test> fetchAlreadyExistentOrCreateRequiredTestsDeclaredIn(
final TestCaseXml testCaseXml) {
final List<String> requiredTestcasesByName = testCaseXml.getNameOfRequiredTests();
return requiredTestcasesByName.stream()
.map(name -> testRepository.findByName(name)
.orElse(testRepository.save(new Test().setName(name))))
.collect(Collectors.toList());
}
I'd also suggest fixing or just outright removing your Equals/hashcode methods from your entities, betting you don't really need them.
#Entity
#Table(name = "STUDENT")
public class Student {
private long studentId;
private String studentName;
private List<Phone> studentPhoneNumbers;
....
#OneToMany(cascade = CascadeType.ALL)
#JoinTable(name = "STUDENT_PHONE", joinColumns = { #JoinColumn(name = "STUDENT_ID") }, inverseJoinColumns = { #JoinColumn(name = "PHONE_ID") })
public List<Phone> getStudentPhoneNumbers() {
return this.studentPhoneNumbers;
}
public void setStudentPhoneNumbers(List<Phone> studentPhoneNumbers) {
this.studentPhoneNumbers = studentPhoneNumbers;
}
}
1)
Student student = session.loadStudent(123); // pseudocode
List phoneList = student.getStudentPhoneNumbers();
for (Phone p : phoneList) {
...
}
2)
Student student = session.loadStudent(123); // pseudocode
List phoneList = student.getStudentPhoneNumbers();
Iterator itr = phoneList.iterator();
while(itr.hasNext()) {
...
}
I read the answer from here: difference between query.list and query.iterate
Obviously there is difference between list() and iterator() (in Query). What if I use it in the OneToMany list? like the example above, is there difference in term of performance? memory?
It has nothing to do with Hibernate.
When Java Compiler encounters
for (Phone p : phoneList) {
....
}
it automatically generate code equivalent to
for (Iterator<Phone> itr = phoneList.iterator(); itr.hasNext();) {
Phone p = itr.next();
....
}
So it is essentially the same for that two examples you are showing.
I read up this Hibernate chapter which explain the proxy performance in detail.
The entity's mapping FetchType by default is lazy, which, hibernate will create a proxy around the attribute.
Upon calling list.size() (and etc), hibernate will start to load all the children objects.
If we don't want to load all, we can use the new feature called extra lazy. It will issue select statement for specific record only, for example, list.get(3) select fourth row only.
If we annotate the attribute with eager, then hibernate will load all the children objects (use outer join, which will have duplicate issue) upon it load the parent object. In this case, there is no proxy wrapping around the attribute. It will not have performance difference, not matter we use it as a list or iterator.
#Entity
#Table(name = "MATCHES")
public class Match implements Serializable{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "MATCH_ID")
private Long id;
#ManyToMany(mappedBy = "matches", cascade = CascadeType.ALL)
private Set<Team> teams = new HashSet<Team>();
}
#Entity
#Table(name = "Teams")
public class Team implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "TEAM_ID")
private long id;
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(name = "TEAM_MATCH", joinColumns = { #JoinColumn(name = "TEAM_ID") }, inverseJoinColumns = {
#JoinColumn(name = "MATCH_ID") })
private Set<Match> matches = new HashSet<Match>();
}
I got those classes, now I want to get all the matches and let's say, print names of both teams.
public List getAllMatches() {
Session session = HibernateUtil.getSession();
Transaction t = session.beginTransaction();
Criteria criteria = session.createCriteria(Match.class, "match");
criteria.createAlias("match.teams", "mt", JoinType.LEFT_OUTER_JOIN);
List result = criteria.list();
t.commit();
session.close();
return result;
}
But when I invoke that method, result has size 2 when I got only 1 match in my table. Both of those matches in result have 2 teams, which is correct. I have no idea why this happends. What I want is to have one Match object with two Team objects in 'teams' set, but I have two of those Match objects. They are fine, but there are two of them. I'm completely new to this and have no idea how to fix those criterias. I tried deleting 'FetchType.LAZY' from #ManyToMany in Team but it doesn't work. Team also has properties like Players/Trainer etc. which are in their own tables, but I don't want to dig that deep yet, baby steps. I wonder tho if doing such queries is a good idea, should I just return Matches and then if I want to get Teams, get them in another session?
Edit: I added criteria.setResultTransformer(DistinctRootEntityResultTransformer.INSTANCE); and it works, is that how I was suppose to fix that or this is for something completely different and I just got lucky?
I think the duplication is a result of your createAlias call, which besides having this side effect is redundant in the first place.
By calling createAlias with those arguments, you are telling Hibernate to not just return all matches, but to first cross index the MATCHES table with the TEAM_MATCH table and return a result for each matching pair of rows. You get one result for a row in the matches table paired with the many-to-many mapping to the first team, and another result for the same row in the matches table paired with the many-to-many mapping to the second team.
I'm guessing your intent with that line was to tell Hibernate to fetch the association. This is not necessary, Hibernate will fetch associated objects on its own automatically when needed.
Simply delete the criteria.createAlias call, and you should get the result you expected - with one caveat. Because the association is using lazy fetching, Hibernate won't load it until you access it, and if that comes after the session is closed you will get a LazyInitializationException. In general I would suggest you prefer solving this by having the session opened and closed at a higher level of abstraction - getting all matches is presumably part of some larger task, and in most cases you should really use one session for the duration of the entire task unless there are substantial delays (such as waiting for user input) involved. Changing that would likely require significant redesign of your code, however; the quick solution is to simply loop over the result list and call Hibernate.initialize() on the teams collection in each Match. Or you could just change the fetch type to eager, if the performance cost of always loading the association whether or not you need it is acceptable.
I have a tree like structure in a Collection. I have ensured that all nodes in the collection do not make extraneous references and are topologically sorted such that the root node at the head of the collection and all leaves are near the end of it.
My primary abstract node class is something like this:
#Entity
public abstract class Node {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
public long ID;
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name = "parent_id", insertable = false, updatable = false)
Node parent;
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name = "root_id", insertable = false, updatable = false)
Node root;
}
I do not maintain children list, but each node points to its parent. The root reference is a convenience field to refer to the root node of the tree. For example, it will be easier for deleting entire trees. Now I have many descendants from Node such as A, B C, etc.
The problem:
When trying to persist the entire tree, I use the following code.
// Check for extraneous references, and sort them topologically.
Session s = hibernate.openSession();
Transaction tx = s.beginTransaction();
try {
int i = 0;
for (Node p: objects) {
if (p.parent == null) {
throw new IOException("Parent is `null`.");
}
s.persist(p);
if (i % batchSize == 0) {
s.flush();
s.clear();
}
i++;
}
tx.commit();
}
catch (Throwable t) {
log.error(t.getMessage(), t);
tx.rollback();
throw new IOException(t);
}
This method doesn't persist objects correctly. If the batch size is too small, I get a PersistentObjectException with a message:
org.hibernate.PersistentObjectException: detached entity passed to persist: com.example.Node
If the batch size is at least as large as total number of objects I can persist, but PARENT_ID and ROOT_ID in database is all set to null. I am using H2 while testing. Note, class A is always the root node, all other objects can appear at any level below A. I tried s.merge()ing too but that didn't work either. I have implemented equals() and hashCode() according to my business keys.
Is it a problem with my equals/hashCode method? Or is it the way I'm attempting to persist? I don't know what's wrong with my code. Somehow I feel this is a trivial error and that I'm overlooking fundamental aspect. Could someone please help me fix it? I tried reading through different blogs that talk about hierarchical representation using Hibernate, but nothing helped.
Try removing s.clear().
It is basically detaching the objects from the persistence context, which may be causing the exception detached entity passed to persist
Say I have an entity like this
#Entity
Class A{
//fields
#Onetomany
Set<B> b; //
}
Now, how do I limit the number of 'B's in the collection in such a way that, when there is a new entry in the collection, the oldest one is removed, some thing like removeEldestEntry we have in a LinkedHashMap.
I am using MySQL 5.5 DB with Hibernate. Thanks in advance.
EDIT
My goal is not to have more than N number of entries in that table at any point of time.
One solution I have is to use a Set and schedule a job to remove the older entries. But I find it dirty. I am looking for a cleaner solution.
I would use the code to manually enforce this rule. The main idea is that the collection B should be well encapsulated such that client only can change its content by a public method (i.e addB()) . Simply ensure this rule inside this method (addB()) to ensure that the number of entries inside the collection B cannot larger than a value.
A:
#Entity
public class A {
public static int MAX_NUM_B = 4;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
private Set<B> b= new LinkedHashSet<B>();
public void addB(B b) {
if (this.b.size() == MAX_NUM_B) {
Iterator<B> it = this.b.iterator();
it.next();
it.remove();
}
this.b.add(b);
}
public Set<B> getB() {
return Collections.unmodifiableSet(this.b);
}
}
B:
#Entity
public class B{
#ManyToOne
private A a;
}
Main points:
A should be the owner of the relationship.
In A , do not simply return B as client can bypass the checking logic implemented in addB(B b) and change its content freely.Instead , return an unmodifiable view of B .
In #OneToMany , set orphanRemovalto true to tell JPA to remove the B 's DB records after its corresponding instances are removed from the B collection.
There is one API provided by Apache Commons Collection. Here you can use the class CircularFifoBuffer for your reference of the same problem you have, if you want example shown as below that you can achive that
Buffer buf = new CircularFifoBuffer(4);
buf.add("A");
buf.add("B");
buf.add("C");
buf.add("D"); //ABCD
buf.add("E"); //BCDE
I think you will have to do it manually.
One solution that comes to mind is using #PrePersist and #PreUpdate event listeners in entity A.
Within the method annotated with above annotations , you check if size of Set<B> , if it is above the max limit, delete the oldest B entries(which may be tracked by a created_time timestamp property of B)