JPA CascadeType.All不会删除父行和子行

时间:2014-12-23 13:32:58

标签: hibernate jpa spring-data spring-data-jpa cascade

在JPA或Hibernate中处理级联总是很麻烦,但我现在真的没有得到它。

我想删除父行品牌和引用它的子项。我使用了CascadeType.ALL,但没有运气。

以下是我的模型(Transact< - TransactProduct - > Product - > Brand - > Customer),为了清晰起见,我只展示了关系:

@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;
}

品牌知识库:

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

在我的控制器中,我想删除这样一个品牌:

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

在findOne之后,它将这些内容写入控制台:

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=?

删除后是:

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=?

它甚至没有运行删除查询。如何删除品牌并将这些删除流程级联到产品并处理引用它的产品?我正在使用CascadeType.All但它不起作用。

感谢。

编辑:我添加了客户模型以进行提问。并且还要注意当我删除客户实体时使用:

customerRepository.delete(id);

确实如此,

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

我将AndphanRemoval添加到Brand实体中,正如Andy Dufresne所说,但它没有用。也许是因为Brand实体也有一个父实体?因为当我使用相同的方法删除客户时,它只是工作。而客户实体没有父母。

3 个答案:

答案 0 :(得分:2)

在JPA 2.0中指定orphanRemoval=true(Hibernate CascadeType.DELETE_ORPHAN)告诉JPA在删除父记录时删除子记录。

您可以更新@OneToMany映射以使用此属性并尝试吗?对于例如对于品牌,它看起来像

@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;
}

答案 1 :(得分:1)

经过调试并获得jpa和hibernate的源代码后,我已经明白了。

我想删除品牌和它的孩子,但它也有一个父母(客户)。当你在一些代表团之后调用JPARepository.delete函数时,它会进入AbstractEntityManagerImpl类并运行这段代码:

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

这里的internalGetSession()函数实际上在EntityManagerImpl类中实现为:

@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;
}

这里我们可以观察会话对象。在不删除品牌父客户的情况下,会话对象包含PersistenceContext为

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

在这种背景下,它不会删除品牌实体。为了能够删除品牌,我们应该首先将其设置为null,然后将其保留为db,然后删除品牌。设置品牌的父母(客户)后,null PersistentContext变为:

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

之后,它删除了品牌。

并且还需要orphanRemovel = true才能删除其他答案中提到的Brand的子实体。

所以我在代码中所做的更改如下:

我将orphanRemoval = true添加到我的品牌实体

@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;
}

我修改了删除逻辑如下:

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

brandRepository.save(brand); // persist

brandRepository.delete(brand); // then delete

而且,我也不知道为什么在这种情况下删除实体并不起作用。它应该是hibernate和jpa如何在内部工作的东西。

答案 2 :(得分:0)

正如Andy Dufresne所说,要删除Brand.products时删除Brand,您需要CascadeType.REMOVE和orphanRemoval = true。此外,如果要在删除Brand.customer时删除Brand,还需要将删除事件级联到Customer实体。不要忘记您需要一个事务,因为您使用的是Spring,在您的方法或Controller类中使用@Transactional注释。

@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;
}