I have two kinds of entities with a bidirectional ManyToMany relation between them. I don't want to post their structure here, because I don't think it would help. Any changes in that relation didn't help, however the relation ist annotated as follows
// relation in the first entity class
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "Entity1_Entity2",
joinColumns = { #JoinColumn(name = "entity1") },
inverseJoinColumns = { #JoinColumn(name = "entity2") })
private List<Entity2> subEntities = new ArrayList<>();
// relation in the second entity class
#ManyToMany(mappedBy = "relatedEntities", fetch = FetchType.LAZY)
private List<Entity1> owningEntities = new ArrayList<>();
So there are possibly circular dependencies. But as far as I know EclipseLink as well as most other JPA implementations can manage this. However while the persisting of few related entities works wothout problems, I get a StackOverflowError when there are too much of them:
Exception in thread "main" java.lang.StackOverflowError
at org.eclipse.persistence.internal.descriptors.ObjectBuilder.extractPrimaryKeyFromObject(ObjectBuilder.java:3011)
at org.eclipse.persistence.internal.sessions.AbstractSession.getCacheKeyFromTargetSessionForMerge(AbstractSession.java:2702)
at org.eclipse.persistence.internal.sessions.MergeManager.getTargetVersionOfSourceObject(MergeManager.java:196)
at org.eclipse.persistence.internal.queries.ContainerPolicy.createWrappedObjectFromExistingWrappedObject(ContainerPolicy.java:728)
at org.eclipse.persistence.mappings.CollectionMapping.mergeIntoObject(CollectionMapping.java:1648)
at org.eclipse.persistence.internal.descriptors.ObjectBuilder.mergeIntoObject(ObjectBuilder.java:4132)
at org.eclipse.persistence.internal.descriptors.ObjectBuilder.mergeChangesIntoObject(ObjectBuilder.java:4065)
at org.eclipse.persistence.internal.sessions.MergeManager.mergeChangesOfWorkingCopyIntoOriginal(MergeManager.java:839)
at org.eclipse.persistence.internal.sessions.MergeManager.mergeChangesOfWorkingCopyIntoOriginal(MergeManager.java:698)
at org.eclipse.persistence.internal.sessions.MergeManager.mergeChanges(MergeManager.java:309)
at org.eclipse.persistence.mappings.CollectionMapping.mergeIntoObject(CollectionMapping.java:1638)
at org.eclipse.persistence.internal.descriptors.ObjectBuilder.mergeIntoObject(ObjectBuilder.java:4132)
at org.eclipse.persistence.internal.descriptors.ObjectBuilder.mergeChangesIntoObject(ObjectBuilder.java:4065)
at org.eclipse.persistence.internal.sessions.MergeManager.mergeChangesOfWorkingCopyIntoOriginal(MergeManager.java:839)
at org.eclipse.persistence.internal.sessions.MergeManager.mergeChangesOfWorkingCopyIntoOriginal(MergeManager.java:698)
at org.eclipse.persistence.internal.sessions.MergeManager.mergeChanges(MergeManager.java:309)
at org.eclipse.persistence.mappings.CollectionMapping.mergeIntoObject(CollectionMapping.java:1638)
at org.eclipse.persistence.internal.descriptors.ObjectBuilder.mergeIntoObject(ObjectBuilder.java:4132)
at org.eclipse.persistence.internal.descriptors.ObjectBuilder.mergeChangesIntoObject(ObjectBuilder.java:4065)
at org.eclipse.persistence.internal.sessions.MergeManager.mergeChangesOfWorkingCopyIntoOriginal(MergeManager.java:839)
at org.eclipse.persistence.internal.sessions.MergeManager.mergeChangesOfWorkingCopyIntoOriginal(MergeManager.java:698)
at org.eclipse.persistence.internal.sessions.MergeManager.mergeChanges(MergeManager.java:309)
at org.eclipse.persistence.mappings.CollectionMapping.mergeIntoObject(CollectionMapping.java:1638)
...
I already removed cascaded persisting as you can see in the annotations above as I could imagine that this caused the depth of recursion. But again I got this error. When I increase the allowed stack it works again, but in my opinion the problem isn't solved then. But it tells me that the recursion isn't endless. Also the error only occurs because of the bidirectional relation because is isn't thrown when i make it unidirectional.
So the questions are: Why is the recursion depth increased when more entities are persisted? And how can this be avoided without making the relation unidirectional?
Related
I'm writing some entity relationships using Spring Data and Java. I have this pair of classes (edited):
Subject:
#Entity
#Table(name = "SUBJECT")
// Lombok, etc., attributes removed
public class Subject {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
#Column(name = "ID", updatable = false, nullable = false)
#JsonProperty("id")
private Long id;
#OneToMany(targetEntity = SubjectResource.class, mappedBy = "subject", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private List<SubjectResource> resources;
}
SubjectResource:
#Entity
#Table(name = "SUBJECT_RESOURCE")
// Lombok, etc., attributes removed
public class SubjectResource {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
#Column(name = "ID", updatable = false, nullable = false)
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "SUBJECT_ID")
private Subject subject;
}
I'm trying to solve these issues:
Question 1: Can I manipulate #OneToMany or #ManyToOne to NOT have the child class recurse its parent?
Fetch of resources returns subject data:
/subject/101:
{"id":101,"resources":[{"id":1001,"subject":101},{"id":1002,"subject":101},{"id":1003,"subject":101},{"id":1004,"subject":101}]}
/subjectResource/1001:
{id:1001,subject:{"id":101,"resources":[{"id":1001,"subject":101},{"id":1002,"subject":101},{"id":1003,"subject":101},{"id":1004,"subject":101}]}}
That is, /subjectResource/1001 returns its ID and the entire /subject/101 query.
How can I have just the subjectResource data, without its parent?
Question 2: Through #OneToMany or #ManyToOne can I get Hibernate to fetch on a "1" (O(1)) basis?
When /subjects does its thing, it works with Hibernate on a "n+1" (O(n)) basis: 1 fetch of subjects, n fetches of resources, one for each subject ID.
I could force a single fetch through a fancy repository #Query annotation ("select s from subject s left join fetch s.resources"). But that means putting the subject : subject_resource definitions in two places, etc.
Can JPA implementation / Hibernate be forced to do a join, and thus make only one database call, through annotation within an entity class?
Question 3: How do I get my Spring Data / Spring Repository to cooperate with Ignite, and have the cache return the data it already had on the first call?
I'm usng FetchType.LAZY, as all good pupils do. I'm also storing things in Apache Ignite. For /subject/101 the initial call fetches everything OK, returning it in JSON. But the second call gets from the Ignite cache, which complains about being out of transaction.
How do get my LAZY fetches to cooperate with Ignite?
Thanks,
Jerome.
I have this situation
public class A {
#ManyToOne(cascade = { CascadeType.REFRESH })
#JoinColumn(name = "COLUMN_B_ID", insertable = false, updatable = false)
private B testB;
}
I would think that no matter what changes are made to the property testB in class A, it would not be persisted. However, when I do the following
A testA = new A();
a.setTestB(new B());
save(testA);
eclipseLink tries to persist testB first before persisting testA. I would think that since the ManyToOne mapping on testB is not set to CascadeType.PERSIST and since insertable and updateable are false, any attempt to set a new instance of B on A would not work while saving A but I am seeing the contrary. Any idea what could be wrong? Is there a way to prevent this?
I'm testing code below against Eclipselink 2.2.1 and it throws an exception demanding CascadeType.PERSIST to be present:
Caused by: java.lang.IllegalStateException: During synchronization a new object
was found through a relationship that was not marked cascade PERSIST: eclipselin
k.saving.neww.objects.in.manytoone.relationship.even.when.no.cascade.B#109f188a.
With Eclipselink 2.5.1 there is the same exception:
Caused by: java.lang.IllegalStateException: During synchronization a new object was found through a relationship that was not marked cascade PERSIST: test.B#6db66c18.
at org.eclipse.persistence.internal.sessions.RepeatableWriteUnitOfWork.discoverUnregisteredNewObjects(RepeatableWriteUnitOfWork.java:310)
at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.calculateChanges(UnitOfWorkImpl.java:723)
at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.commitToDatabaseWithChangeSet(UnitOfWorkImpl.java:1516)
at org.eclipse.persistence.internal.sessions.RepeatableWriteUnitOfWork.commitRootUnitOfWork(RepeatableWriteUnitOfWork.java:277)
at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.commitAndResume(UnitOfWorkImpl.java:1169)
at org.eclipse.persistence.internal.jpa.transaction.EntityTransactionImpl.commit(EntityTransactionImpl.java:132)
A a = new A();
a.setTestB(new B());
em.persist(a);
I have two entity classes user and device.
User entity:
public class User {
private Long userId;
#OneToMany( mappedBy = "userId", fetch = FetchType.LAZY)
private Collection<Device> deviceCollection;
and device entity:
public class Device implements Serializable {
#JoinColumn(name = "user_id", referencedColumnName = "user_id")
#ManyToOne(optional = false, fetch = FetchType.LAZY)
private User userId;
When I merge a previously detached device entity into the entity manager after the parent user has been deleted, both the (previously removed) user and the device are re-inserted into the database. There is no cascade annotation on user or device entity; therefore, I don't expect the user entity to be reinserted but it did;
How do I prevent the merge operation to cascade to the user entity?
Thanks in advance.
Any changes you do in detached state there is no possible way for Session Manager to know it so for it the changes are always new objects that needs to be merged (If you are calling merge)
So when you call merge it will load it from database so your object will have Prev+ new changes. So that is why mentioned behavior is happening.
What you can do is first load entity in the session apply changes and then call merge.
What you can do is something like below I have used similar relationship in one of my project with Eclipse Link
Query query = entityManager
.createNamedQuery("User.FindByUserId");
User fromDatabase = null;
try {
query.setParameter("userId", device.getUser().getUserId());
fromDatabase = (User) query.getSingleResult();
} catch (NoResultException noResultException) {
// There is no need to do anything here.
}
if (fromDatabase == null) {
User user= entityManager.merge(device.getUser());
device.setUser(user);
} else {
device.setUser(user);
}
entityManager.persist(device);
Try adding insertable=false, updatable=false to your JoinColumn, e.g.
#JoinColumn(name = "user_id", referencedColumnName = "user_id", insertable=false, updatable=false)
You should be using a version number to prevent entities from being mistakenly resurected. This will force an exception, where as the specification is a bit unclear on what should happen when merging over a relation that isn't marked cascade all or merge. The spec states that managed entities will be synchronized to the database, while the section dealing with merge implies that even entities referenced by relations without the cascade merge/all options will be managed afterward. This behavior is probably not what was intended, but shouldn't be relied on until clarified.
I had the same problem
and I found a bug about this: EntityManager.merge() cascading by default
but I really don't understand why this behaviour was never fix. It is one of reasons among others that I don't use EclipseLink (But it's not the point here)
Edit:
Chris, the comment which begin with "I'm not an expert" the argument that is put in head is not right, I think. What I understand, it's just that entity with a relation without cascade=MERGE or cascade=ALL, you can just navigate, that's all.
Otherwise why use Merge annotation ? It doesn't make sense.
I have the Entities as follows;
Claimant:
#OneToMany(mappedBy = "payTo", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#NotAudited
private Set<Payment> payments = new HashSet<Payment>();
Payment:
#OneToMany(mappedBy = "rofOf", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED)
private Set<Payment> returnOfFundings = new HashSet<Payment>();
When doing claimant.merge()
getting the following exception "collection [com.bfds.saec.domain.Payment.returnOfFundings] was not processed by flush()"
for the above one solution i found that just reload the Claimant with joining with Payment before merge.
But Please let me know,Is there any other way is there to solve the above problem as i don't want to reload the Claimant with payment.
What may be the root-cause for the problem ?? Is there any problem the relation I have defined there??
Root cause of problem seems to be HHH-273 bug in Hibernate. According comments it also pops up when Envers touches collection. Because it is fixed in version 4.0.1, best way to get rid of it is to update Hibernate.
I have an entity VM with a relationship to another entity BP. The relationship is eagerly fetched. First I load a VM. After loading the VM is detached, serialized and changed at the client side. Now I want to update the changed entity so I use the EntityManager.merge() method from JPA. Now I run into the following error from OpenJPA:
"Encountered new object in persistent field "Vm.bp" during attach. However, this field does not allow cascade attach. Set the cascade attribute for this field to CascadeType.MERGE or CascadeType.ALL (JPA annotations) or "merge" or "all" (JPA orm.xml). You cannot attach a reference to a new object without cascading."
Why do I have to add a Cascade.MERGE to a relationship to another entity that will never change? And why does JPA think that BP is a new object ("...cannot attach reference to a new object...")?
When using ManyToOne relationships do I always have to add Cascade.MERGE in order to update the entity or is this because of the EAGER fetch type?
Here's my entity:
#Entity
#Table(name = "VM")
public class Vm extends BaseEntity implements Serializable {
public static final long serialVersionUID = -8495541781540291902L;
#Id
#SequenceGenerator(name = "SeqVm", sequenceName = "SEQ_VM")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SeqVm")
#Column(name = "ID")
private Long id;
// lots of other fields and relations
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "BP_ID")
private Bp bp;
// ...
}
I found the reason why this error message comes up: The #Version annotated database field of the related Bp entity was initialized with "0". Apparently OpenJPA (1.2.3) is not able to cope with entity versions of zero.
Setting the version to 1 solved my issue.