JPA CascadeType.All doesn't delete parent and

2020-05-06 14:19发布

It's always been trouble handling cascades in JPA or Hibernate but i really didn't get it now.

I want to delete a parent row brand and childs that are referencing to it. I used CascadeType.ALL but no luck.

Here are my models (Transact <- TransactProduct -> Product -> Brand -> Customer), I'm showing only relations for clarity:

@Entity
@Table(name = "customer")
public class Customer {
    @OneToMany(fetch = FetchType.EAGER, mappedBy = "customer", cascade = CascadeType.ALL)
    private Collection<Brand> brands;
}

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

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

@Entity
@Table(name = "product")
public class Product {

    @ManyToOne
    @JoinColumn(name = "brand_id")
    private Brand brand;

    @OneToMany(mappedBy = "product", cascade = CascadeType.ALL)
    private Collection<TransactProduct> transactProducts;
}

@Entity
@Table(name = "transact")
public class Transact {

    @OneToMany(mappedBy = "transact", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    private Collection<TransactProduct> transactProducts;

    @ManyToOne
    @JoinColumn(name = "user_id")
    private User user;
}

@Entity
@Table(name = "transact_product")
public class TransactProduct {

    @ManyToOne
    @JoinColumn(name = "product_id")
    private Product product;

    @ManyToOne
    @JoinColumn(name = "transact_id");
    private Transact transact;
}

Brand Repository :

@Repository
public interface BrandRepository extends JpaRepository<Brand, Long> {}

In my controller I want to delete a brand like this :

Brand brand = brandRepository.findOne(id);
brandRepository.delete(brand);

After findOne it writes those to console :

select * from brand brand0_ left outer join customer customer1_ on brand0_.customer_id=customer1_.id where brand0_.id=?
select * from brand brands0_ where brands0_.customer_id=?

And after delete it is :

select * from brand brand0_ left outer join product products1_ on brand0_.id=products1_.brand_id where brand0_.id=?
select * from customer customer0_ left outer join brand brands1_ on customer0_.id=brands1_.customer_id where customer0_.id=?

It doesn't even run a delete query. What should I do to delete brands and cascade those delete process to products and transact products that reference to it? I am using CascadeType.All but it doesn't work.

Thanks.

EDIT : I added my customer model to question. And also want to note that when i delete customer entity using :

customerRepository.delete(id);

it does,

delete from brand where id=?
delete from customer where id=?

I added orphanRemoval to Brand entity as Andy Dufresne mentioned but it didn't work. Maybe it's because Brand entity also has a parent entity? Because when I use same method for removing Customers it simply works. And Customer entity doesn't have parent.

3条回答
迷人小祖宗
2楼-- · 2020-05-06 14:45

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;
}
查看更多
贪生不怕死
3楼-- · 2020-05-06 14:52

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;
}
查看更多
甜甜的少女心
4楼-- · 2020-05-06 14:55

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.

查看更多
登录 后发表回答