JPA CascadeType.All doesn't delete parent and child rows

吃可爱长大的小学妹 提交于 2019-12-02 13:19:48

Specifying orphanRemoval=true in JPA 2.0 (Hibernate CascadeType.DELETE_ORPHAN) tells JPA to remove the child records when the parent is deleted.

Can you update your @OneToMany mappings to use this attribute and try? For e.g. for Brand it would look like

@Entity
@Table(name = "brand")
public class Brand implements Serializable {
    @ManyToOne
    @JoinColumn(name = "customer_id")
    private Customer customer;

    @OneToMany(mappedBy = "brand", cascade = CascadeType.ALL, orphanRemoval=true)
    private Collection<Product> products;
}

After debugging and getting into source code of jpa and hibernate I've figured that out.

I want to delete brand and it childs but it also have a parent(Customer). When you call JPARepository.delete function after some delegations it comes to AbstractEntityManagerImpl class and this code runs :

@Override
public void remove(Object entity) {
    checkOpen();
    try {
        internalGetSession().delete( entity );
    }
    catch ( MappingException e ) {
        throw convert( new IllegalArgumentException( e.getMessage(), e ) );
    }
    catch ( RuntimeException e ) {
        //including HibernateException
        throw convert( e );
    }
}

Here internalGetSession() function actually implemented in EntityManagerImpl class as :

@Override
protected Session internalGetSession() {
    if ( session == null ) {
        SessionBuilderImplementor sessionBuilder = internalGetEntityManagerFactory().getSessionFactory().withOptions();
        sessionBuilder.owner( this );
        if (sessionInterceptorClass != null) {
            try {
                Interceptor interceptor = (Interceptor) sessionInterceptorClass.newInstance();
                sessionBuilder.interceptor( interceptor );
            }
            catch (InstantiationException e) {
                throw new PersistenceException("Unable to instantiate session interceptor: " + sessionInterceptorClass, e);
            }
            catch (IllegalAccessException e) {
                throw new PersistenceException("Unable to instantiate session interceptor: " + sessionInterceptorClass, e);
            }
            catch (ClassCastException e) {
                throw new PersistenceException("Session interceptor does not implement Interceptor: " + sessionInterceptorClass, e);
            }
        }
        sessionBuilder.autoJoinTransactions( getTransactionType() != PersistenceUnitTransactionType.JTA );
        session = sessionBuilder.openSession();
    }
    return session;
}

Here we can observe session object. Without removing an brands parent customer, session object contains PersistenceContext as

entityKeys=[
    EntityKey[com....model.Customer#101],
    EntityKey[com....model.Brand#102], 
    EntityKey[com....model.Product#104]]
]

And with this context it just doesn't remove Brand entity. To be able to remove brand we should set it's parent null first and persist to db, then remove brand. After setting brand's parent(customer) null PersistentContext becomes :

entityKeys=[
    EntityKey[com.....model.Brand#102], 
    EntityKey[com.....model.Product#104]
]

So after then, it deletes brand.

And also orphanRemovel=true is required to be able to delete Brand's child entities as mentioned in the other answer.

So after all what i changed in my code is as below :

I added orphanRemoval = true to my Brand Entity

@Entity
@Table(name = "brand")
public class Brand implements Serializable {
    @ManyToOne
    @JoinColumn(name = "customer_id")
    private Customer customer;

    @OneToMany(mappedBy = "brand", cascade = CascadeType.ALL, orphanRemoval=true)
    private Collection<Product> products;
}

And I modified my deletion logic as below :

Brand brand = brandRepository.findOne(id);
brand.setCustomer(null); // set customer null

brandRepository.save(brand); // persist

brandRepository.delete(brand); // then delete

And also, I don't know why in such a case deleting an entity doesn't work. It should be something with how hibernate and jpa works internally.

As Andy Dufresne stated, to remove the Brand.products when you delete a Brand, you need the CascadeType.REMOVE and orphanRemoval=true. Also, if you want to delete the Brand.customerwhen you remove the Brand, you need also to Cascade the remove event to the Customer Entity. Don't forget that you need a transaction, since you're using Spring, have the @Transactional annotation at your method or Controller class.

@Entity
@Table(name = "brand")
public class Brand implements Serializable {
    @ManyToOne( cascade = CascadeType.REMOVE )
    @JoinColumn(name = "customer_id")
    private Customer customer;

    @OneToMany(mappedBy = "brand", cascade = CascadeType.ALL, orphanRemoval=true)
    private Collection<Product> products;
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!