在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实体也有一个父实体?因为当我使用相同的方法删除客户时,它只是工作。而客户实体没有父母。
答案 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;
}