jpa repository delete children without loading parent collection - jpa

I am trying to delete a Child Entity using Child's Repository. I do not want to load the whole Collection of Child in Parent and remove a Child from there because the collection is huge in some cases and can cause memory issues. But after I delete a child when I load the Parent using Parent Repository I get an error that says "Deleted Entity passed to persists".
#Entity
#Table(name="USR")
public class User {
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "user", orphanRemoval=true)
private Set<UserApplication> userApplications = new HashSet<UserApplication>();
}
#Entity
#Table(name="USR_2_APL")
public class UserApplication {
#ManyToOne
#JoinColumn(name = "USR_SK")
private User user;
}
#Test
public void testDeleteUserApp() {
List<UserApplication> removedUserApp = userApplicationRepository.findByUserSkAndApplicationSk(1, 5);
userApplicationRepository.delete(removedUserApp);
//*****This is where I see an error that says
//org.springframework.orm.jpa.JpaObjectRetrievalFailureException: deleted entity passed to persist: [UserApplication#<null>]; nested exception is javax.persistence.EntityNotFoundException
userRepository.findByUserLoginName(loginId);
}

I donot know if this will help you but I have something similar and this is what I do to delete the data...
In the repository I have a method like this:-
#Transactional
public Long deleteByByUserSkAndApplicationSk(int userSk, int applicationSk);
The output of the method is the number of rows deleted.
Then you can directly call the method where ever you want to delete.

Related

Spring JPA - #OneToOne/#MapsId - Parent/Child with shared key: I get this GenerationException: attempted to assign id from null one-to-one property

When adding a parent and child with a #OneToOne relation having the same key, I keep on getting this error. It is advised to use #MapsId.
id.IdentifierGenerationException: attempted to assign id from null
one-to-one property
The usual solutions I tried, but that did not solve the problem.
Having both parent and child point to each other
The right Transactional (being springframework's)
Saving using the owner's repo. This does not work because of the violation of a upper restriction.
My parent Entity is (with names having a purpose):
#Entity
#Table(name = "JOHAN_SHARED_SUPPLIER")
public class EntitySharedSupplier {
#Id
#Column(name = "supplier_shared_id")
private Long javaSharedSupplierId;
#Column(name = "supplier_shared_name")
private String javaSharedSupplierName;
#Column(name = "contact_shared_name")
private String javaSharedSupplierContactName;
#OneToOne(cascade = CascadeType.ALL,fetch = FetchType.LAZY,
mappedBy = "supplierSharedRef", orphanRemoval = true)
private EntitySharedProduct javaSharedProduct;
The child is:
#Entity
#Table(name = "JOHAN_SHARED_PRODUCTS")
public class EntitySharedProduct {
#Id
#Column(name = "shared_supplier_id")
private Long javaSupplierSharedId;
#MapsId
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name="shared_supplier_id")
private EntitySharedSupplier supplierSharedRef;
#Column(name = "prod_supplier_name")
private String javaSharedProductName;
The repo of the parent is:
#Repository
#Transactional
public interface SupplierSharedRepo extends JpaRepository<EntitySharedSupplier, Long> {
}
The service code:
#Transactional
public void saveSupplier(int sup) {
EntitySharedSupplier supplier = new EntitySharedSupplier();
supplier.setJavaSharedSupplierId((long) sup);
supplier.setJavaSharedSupplierName("SharedSupplier-" + sup);
supplier.setJavaSharedSupplierContactName("SharedSupplier-contact-" + sup);
EntitySharedProduct product = new EntitySharedProduct();
product.setJavaSharedProductName("SharedSupplier-Book-" + sup);
product.setSupplierSharedRef(supplier);
supplier.setJavaSharedProduct( product);
supplierSharedRepo.save(supplier);
}
The solution in this case was a bit difficult.
The solution is by changing the set id/key method in the parent object:
public void setJavaSharedSupplierId(Long javaSupplierId) {
this.javaSharedSupplierId = javaSupplierId;
if( javaSharedProduct != null) {
javaSharedProduct.setJavaSupplierSharedId( javaSupplierId);
}
}
NOTICE:
In most cases you have an automatic id/key generation of the parent at a late stage during the hibernate/jpa save process. At that moment the connection between parent and child is done already.
In this case, the solution works because first the connection is made between parent and child and then the manual id/key of the parent is set.
Till then, I kept on getting either the original error (as shown in the question) or that the id of the id was not set.
The solution needs a small change in the creating of the parent/child objects.
#Transactional
public void saveSupplier(int sup) {
EntitySharedSupplier supplier = new EntitySharedSupplier();
supplier.setJavaSharedSupplierName("SharedSupplier-" + sup);
supplier.setJavaSharedSupplierContactName("SharedSupplier-contact-" + sup);
EntitySharedProduct product = new EntitySharedProduct();
product.setJavaSharedProductName("SharedSupplier-Book-" + sup);
product.setSupplierSharedRef(supplier);
supplier.setJavaSharedProduct( product);
// The next statement has to be put after the previous
supplier.setJavaSharedSupplierId((long) sup);
supplierSharedRepo.save(supplier);
}
The solution works and is easy to understand. I also have software that doesn't need this manual setting of the child id in the parent id. So far, I haven't tracked down why that software works.
If you have a better solution, let me/us know!

Entity with CascadeType.ALL in OneToMany not persisting children

I'm working with a 3rd party library provided to our team where one of the entities has a OneToMany relationship to entities of the same type of itself. I've changed the entity name to keep it anonymous.
Probably there's a better way of annotating entities with this type of relationship but as it's provided by a 3rd party I'm avoiding making to many changes so that it's compatible with future patches and updates.
It's using OpenJPA 2.4.0-ep2.0
#Entity
#Table(name = Person.TABLE_NAME)
public class Person {
private Long parentUid;
private List<Person> children = new ArrayList<>();
#OneToMany(targetEntity = Person.class, cascade = { CascadeType.ALL }, fetch = FetchType.LAZY)
#ElementJoinColumn(name = "PARENT_UID")
#ElementForeignKey
#ElementDependent
public List<Person> getChildren() {
return this.children;
}
}
When I try to persist a person with children, only the main entity gets persisted and children ignored.
However, if I change the fetch attribute to FetchType.EAGER it works (it persists both the parent and children). My understanding was that the fetch type only affects the loading, not the inserting. Any ideas why is it happening?
Also, is there a way of making it work while keeping the fetch type to FetchType.LAZY?
I've tried the following (modify the setter):
protected void setChildren(final List<Person> children) {
if (Objects.nonNull(children)) {
for (Person child : children) {
child.setParentUid(parentUid);
}
this.childItems = children;
} else {
this.childItems = new ArrayList<>();
}
}
Problem is in the child entity ,you should use #ManyToOne annotation in child entity.
add following code to Person :
public class person {
.
.
#MantToOne(fetch=FetchType.LAZY)
#JoinClolumn(name="PARENT_UID")
private Person parent;
public void setParent(Person parent){
}
.
.
}
then revise setChildren Code like this:
protected void setChildren(final List<Person> children) {
if (Objects.nonNull(children)) {
for (Person child : children) {
child.setParent(this);
}
this.childItems = children;
} else {
this.childItems = new ArrayList<>();
}
}
one important point is ،always fetch type must be sync in parent and child.

How do I lazy load a nested collection?

I have an entity Parent and a relation #OneToMany to another entity Child. The collection children is set to lazy loading.
#Entity
class Parent{
#Id
String parentId;
#OneToMany(mappedBy="parent",fetch=FetchType.LAZY)
List<Child> children;
}
#Entity
class Child{
#Id
String childId;
#ManyToOne
#JoinColumn(name="parentId")
Parent parent;
}
List<Parent> loadParents() {
QParent qParent = QParent.parent;
List<Parent> parentList = query.select(qParent).from(qParent).fetch();
return parentList;
}
#Transactional
void test(){
List<Parent> parentList = loadParents();
for(Child child:parentList.getChildren()){
child.getChildId();
}
}
I get the famous
org.hibernate.LazyInitializationException: failed to lazily initialize
a collection of role ... could not initialize proxy - no Session
exception in the test() method on the line where I access the children list.
I don't want to change the fetch type annotation in the entity.
How do I access the child entities?
I found the culprit. The transaction management was disabled.
The #Transactional annotation was missing from the test method.
To enable transaction management, put this in application-context.xml:
<tx:annotation-driven />
There is nothing wrong with the code, but the configuration was incomplete. To eagerly load nested collections all we need is an embracing transaction.
Turning on debug logging for org.springframework.orm and org.hibernate helped me to identify the source of the issue.
Similar question and answer: LazyInitializationException in JPA and Hibernate

JPA entity relations are not populated after .persist()

this is a sample of my two entities:
#Entity
public class Post implements Serializable {
#OneToMany(mappedBy = "post", fetch = javax.persistence.FetchType.EAGER)
#OrderBy("revision DESC")
public List<PostRevision> revisions;
#Entity(name="post_revision")
public class PostRevision implements Serializable {
#ManyToOne
public Post post;
private Integer revision;
#PrePersist
private void prePersist() {
List<PostRevision> list = post.revisions;
if(list.size() >= 1)
revision = list.get(list.size() - 1).revision + 1;
else
revision = 1;
}
So, there's a "post" and it can have several revisions. During persisting of the revision, entity takes a look at the list of the existing revisions and finds the next revision number. Problem is that Post.revisions is NULL but I think it should be automatically populated. I guess there's some kind of problem in my source code but I don't know where. Here's my "persistence" code:
Post post = new Post();
PostRevision revision = new PostRevision();
revision.post = post;
em.persist(post);
em.persist(revision);
em.flush();
I think that after persisting "post", it becomes "managed" and all the relations should be populated from now on.
Thanks for help!
(Note: public attributes are just for demonstration)
No. Hibernate will populate the relationships when loading entities from the database. But when you persist or change them, it's your responsibility to maintain the relationships, at both sides.
Since Hibernate entities are also POJOs that you will use in other layers and in unit tests, you should make sure that invariants are OK. For example, the list of revisions should never be null. It should be empty initially.
Convert your Post entity to:
#Entity
public class Post implements Serializable {
#OneToMany(mappedBy = "post", fetch = javax.persistence.FetchType.EAGER)
#OrderBy("revision DESC")
public List<PostRevision> revisions = new ArrayList<PostRevision>();
public void addRevision(PostRevision revision) {
if (post.revisions.isEmpty()) {
revision.setRevision(1);
} else {
revision.setRevision(post.revisions.get(post.revisions.size() - 1));
}
revision.setPost(this);
getRevisions().add(revision);
}
}
And make sure you use addRevision when you want to add a revision to a post. With this solution you should also remove the #PrePersist listener.

JPA2.0: Delete Entity in OneToMany RelationShip

How do I delete an entity in a OneToMany relationship.
#Entity
#NamedQueries({
#NamedQuery(name="User.findByUserNamePassword",
query="select c from User c where c.userName = :userName AND c.password = :password")
})
#Table(name="\"USER\"")
public class User implements Serializable {
#OneToMany(mappedBy="user", cascade=CascadeType.ALL, orphanRemove=true)
private List<Profession> professions;
public List<Profession> getProfessions() {
return professions;
}
public void setProfessions(List<Profession> professions) {
this.professions = professions;
}
public void addProfession(Profession profession){
if(this.professions == null){
this.professions = new ArrayList<Profession>();
}
this.professions.add(profession);
profession.setUser(this);
}
public void removeProfession(Profession profession){
if(this.professions != null){
professions.remove(profession);
profession.setUser(null);
}
}
}
Inside Profession Entity
#Entity
public class Profession implements Serializable {
#ManyToOne
#JoinColumn(name="UserId", nullable=false)
private User user;
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
Then inside my EJB I have this
#Stateless
#LocalBean
#TransactionAttribute(TransactionAttributeType.REQUIRED)
public class ScholarEJB{
/**
* Add a profession to a target user
* #param user
* #param profession
*/
public void addProfession(User user, Profession profession){
//Put the user in a managed state. It is important to do this before
//adding a new profession onto user
user = find(User.class, user.getId());
user.addProfession(profession);
this.create(user); //This is persist action
}
public void removeProfession(User user, Profession profession){
//Put the user in a managed state. It is important to do this before
//adding a new profession onto user
user = find(User.class, user.getId());
user.remove(user);
this.update(user); //merge action
//this.create(user) //also try this as well, but it does not work
}
}
Now addProfession work beautifully, but removeProfession does not work. Not sure why? Help please. Do I need to evict caches?
If professions are only part of this relationship, then you can guarantee that when a profession is removed from the User's set it will also be removed from the database by turning on orphanRemoval on the OneToMany side of the relationship.
#OneToMany(mappedBy="user", cascade=CascadeType.ALL, orphanRemoval=true)
private List<Profession> professions;
This is what the JPA 2.0 specification states
The JPA 2.0 specification states that
Associations that are specified as
OneToOne or OneToMany support use of
the orphanRemoval option. The
following behaviors apply when
orphanRemoval is in effect:
If an entity that is the target of the
relationship is removed from the
relationship (by setting the
relationship to null or removing the
entity from the relationship
collection), the remove operation will
be applied to the entity being
orphaned. The remove operation is
applied at the time of the flush
operation. The orphanRemoval
functionality is intended for entities
that are privately "owned" by their
parent entity. Portable applications
must otherwise not depend upon a
specific order of removal, and must
not reassign an entity that has been
orphaned to another relationship or
otherwise attempt to persist it. If
the entity being orphaned is a
detached, new,or removed entity, the
semantics of orphanRemoval do not
apply.
If the remove operation is applied to
a managed source entity, the remove
operation will be cascaded to the
relationship target in accordance with
the rules of section 3.2.3, (and hence
it is not necessary to specify
cascade=REMOVE for the
relationship)[20].
My guess as to what is occurring is that your User has a OneToMany relationship to Profession and you user object has the profession. When you delete the Profession the user still has the reference. Because the mapping is cascade persist, it re persists the Profession.
You need to ensure that you remove the profession from the user's professions before deleting it.
If you are using EclipseLink there is a property that may also help, but fixing your code to maintain your model correctly is the best solution. You could also remove the cascade persist.
"eclipselink.persistence-context.persist-on-commit"="false"
or,
"eclipselink.persistence-context.commit-without-persist-rules"="true"
I just added orphanRemoval = true in the OneToMany relationship and I resolved it.
Class SolicitudRetorno:
#OneToMany(mappedBy = "solicitud", cascade = CascadeType.ALL, orphanRemoval = true)
#LazyCollection(LazyCollectionOption.FALSE)
#NotAudited
private List<RetornoMenor> hijosRetorno;
Class RetornoMenor:
#OneToMany(mappedBy = "solicitud", cascade = CascadeType.ALL, orphanRemoval = true)
#LazyCollection(LazyCollectionOption.FALSE)
#NotAudited
private List<RetornoMenor> hijosRetorno;
You might try clearing the user field in profession:
public void removeProfession(Profession profession){
if(this.professions != null){
professions.remove(profession);
profession.setUser(null); // disassociate profession from user
}
}
To be on the safe side, I would also check that the passed in profession's current user equals this, just in case someone passes in a profession belonging to another user.
This is the solution to my original question, however, I do not know if this is the best
My EJB bean
#PersistenceContext(unitName="Bridgeye2-ejbPU")
private EntityManager em;
public <T> T create(T t) {
em.persist(t);
return t;
}
public <T> T find(Class<T> type, Object id) {
return em.find(type, id);
}
public <T> void delete(T t) {
t = em.merge(t);
em.remove(t);
}
public <T> void removeAndClearCaches(T t){
this.delete(t);
clearCaches();
}
public <T> T update(T t) {
return em.merge(t);
Now in my Managed Bean, I do this
/**
* Add a new profession
*/
public void addNewProfession(){
Profession profession = new Profession();
newProfessions.add(profession);
}
/**
* Remove the profession
* #param profession
*/
public void removeProfession(Profession profession){
//This will remove the `profession` of the list
//at the presentation layer
this.myProfessions.remove(profession);
//This will remove the `profession` of the list
//at the persistence layer
scholarEJB.removeAndClearCaches(profession);
}