eclipseLink saving new objects in manyToOne relationship even when no Cascade.PERSIST - jpa

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);

Related

Can't store OneToMany relationsship in my Database

I have a problem with the relationsship #OneToMany in JPA. I want to save a relationsship between a Customer and a Message Object but i got a NullPointerException. I don't know why, because i thought that the follwoing code will work smoothly.
Here's what i trie to do:
Customer new = new Customer();
new.setEmail(email);
new.setUserId(userId);
new.setLastname(lastname);
new.setFirstname(firstname);
new.setPhone(phone);
quick.customerNew(new);
Messages msg = new Messages ();
msg.setMessage(message);
quick.newMessage(msg);
//Here i got the NullPointerException
new.getCustomerMessages.add(msg);
quick.customerUpdate(new);
The Customer Object and the Message Object are stored in the DB. But the relationsship dosen't exists and i got, as i said, the NullPointerException
public class Customer implements Serializable {
[...]
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "_id_info", referencedColumnName = "_id")
private Set<Messages> customerMessages;
[getter/setter]
}
//Here i got the NullPointerException
new.getCustomerMessages.add(msg);
If this line throws the NullPointerException, it can mean two things: either "new" (geez, it hurts just to type it as a variable name) is null, or getCustomerMessages() returns null.
Since your code reaches this point, by accessing "new" multiple times before, I assume that "new" isn't the culprit here.
Since you never call setCustomerMessages() in your code, and there are no signs that any other calls would set your customerMessages attribute, I assume that this will be member you need to set.

How to set value for the linked object of the entity in JPA

I have an Entity "A" which has a #ManyToOne link to Entity "B".
#Entity
public class A{
private B b;
...
#ManyToOne(fetch = FetchType.LAZY, targetEntity = B.class)
#JoinColumn(name = "B_ID", insertable = false, updatable = false)
public B getB() {
return b;
}
}
I have an A object and it's linked B object both already queried from the database (by separate queries) and now i need to set B to the A.
I merely do aObj.setB(bObj) and see in the logs that it leads to querying B object from the database again (it is stated in logs that ReadObjectQuery is called. maybe it queries object from cache - that doesn't matter - the issue is that some redundand actions happen).
It happens despite the state of A and B, they can be managed or detached, query occurs always.
I understand that i could query A with already set B in one query, etc, but due to legacy code i have an A instance and B instance separately and need to set B to A. Is it possible to set it without redundand database query?
I'm using EclipseLink 2.4.2

StackOverflowError when persisting entities with bidirectional relation

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?

cascading on a target entity with other relationships

I have a manytomany relation mapped with these 3 entities :
#Entity
public class ApplicatifDo {
.....
#OneToMany(cascade = CascadeType.ALL, mappedBy = "applicatifDo", fetch = FetchType.EAGER, orphanRemoval = true)
private Set<ApplicatifTerminalDo> applicatifTerminalSet;
.....
}
#Entity
public class ApplicatifTerminalDo {
......
#ManyToOne
#JoinColumn(name = "idApplicatif", nullable = false)
private ApplicatifDo applicatifDo;
#ManyToOne
#JoinColumn(name = "idTerminal", nullable = false)
private TerminalDo terminalDo;
#Column
private String remarques;
......
}
#Entity
public class TerminalDo {
......
#OneToMany(cascade = CascadeType.ALL, mappedBy = "terminalDo", fetch = FetchType.EAGER, orphanRemoval = true)
private Set<ApplicatifTerminalDo> applicatifTerminalSet;
......
}
I try to do many cascading tests on the join table ApplicatifTerminalDo from the two entities ApplicatifDo and TerminalDo. When I create or update a ApplicatifTerminalDo in the Set the cascading works well, but when it comes to orphanRemoval or delete It's not working
First :
For the delete I get the error :
Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Cannot delete or update a parent row: a foreign key constraint fails (`softtwo`.`applicatifterminal`, CONSTRAINT `FK_295tcnx7wjuvv5se1g3vldxxn` FOREIGN KEY (`idApplicatif`) REFERENCES `applicatif` (`id`))
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source)
at java.lang.reflect.Constructor.newInstance(Unknown Source)
at com.mysql.jdbc.Util.handleNewInstance(Util.java:409)
at com.mysql.jdbc.Util.getInstance(Util.java:384)
.....
I would like that when I delete an entity ApplicatifDo or TerminalDo all rows related to them in the ApplicatifTerminal join table get deleted as well.
Second :
For the orphanRemoval, when I delete an element from the Set of ApplicatifTerminalSet in my ApplicatifDo entity and do a merge, then to test it I do a find by his Id of parent entity to get a new exact same entity and count the number of elements in the Set I get the good number (the number at the biginning with one less). But in my database I still have all my datas of my Set.
The code :
//My applicatifDo1 has 4 elements in the ApplicatifTerminalSet here
Assert.assertEquals(applicatifDo1.getApplicatifTerminalSet().size(), 4);
Iterator<ApplicatifTerminalDo> iterator = applicatifDo1
.getApplicatifTerminalSet().iterator();
boolean first = true;
while (iterator.hasNext()) {
ApplicatifTerminalDo element = iterator.next();
if (!first) {
element.setRemarques("remarques updated");
} else {
iterator.remove();
first = false;
}
}
// updateApplicatifDo do just a merge
applicatifDao.updateApplicatifDo(applicatifDo1.getId(), applicatifDo1);
ApplicatifDo applicatifDo = applicatifDao
.findApplicatifDo(applicatifDo1.getId());
Assert.assertEquals(applicatifDo.getApplicatifTerminalSet().size(), 3);
When I do that the update of setRemarques() works well. I have no error in the console when I do that. Then the remove seems to work because I retrieve the same object by his Id it still says I have 3 elements, then the fourth has been deleted : BUT, when I look in phpmyadmin my 4 elements/relations in my appplicationterminal table are still there.
If I do another TestNg later, just retrieving my applicatifDo by his Id this time it gets the four elements.
Then there a big integrity problem here, and still I always use eagerly fetching. Any idea why such problem here ? And how could I make my cascading works ?
Third :
More globally, I have another cascading + orphanRemoval rules on other two entities and that works very well, but the entity target of these cascades does not have other relations with other entities.
Clearly, there is specific rules (or maybe limitations) when cascading on entities with other relationships.
Please do you know tutorials/rules explaining the best practices for such mappings ?
Thanks in advance. I'm stuck on this for too long.

Merging an object with FetchType.EAGER relation leads to "FailedObject"

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.