【问题标题】:JPA / Hibernate orphan removal using joining entity使用加入实体的 JPA / Hibernate 孤儿删除
【发布时间】:2015-08-17 16:27:34
【问题描述】:

首先,我对此进行了大量研究,但还没有找到这个例子,所以我希望答案不是“重新设计你的数据库”。

使用 Spring 和 JPA(和休眠)删除一个实体,并自动删除它的引用(或“孤儿”?)。

这是错误: DELETE 语句与 REFERENCE 约束“FK_ItemContainer_Item”冲突。冲突发生在数据库“MyDatabase2”、表“dbo.ItemContainer”、列“itemId”中。

这是场景:

@Entity
@Table(name = "Item")
public class Item implements java.io.Serializable {

  private Integer itemId; //Auto Genenerated Unique ID
  private String item; //Item description
  private Set<ItemContainer> itemContainers = new HashSet<ItemContainer>(0); //Set of ItemContainers

  @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "item", orphanRemoval = true)
  public Set<ItemContainer> getItemContainers() {
    return this.itemContainers;
  }
}

@Entity
@Table(name = "ItemContainer")
public class ItemContainer implements java.io.Serializable {

  private Integer itemContainerId; //Auto Genenerated Unique ID
  private Item item; //Item link
  private Container container; //Container link

  @ManyToOne(fetch = FetchType.EAGER)
  @JoinColumn(name = "itemId")
  public Item getItem() {
    return this.item;
  }

  @ManyToOne(fetch = FetchType.EAGER)
  @JoinColumn(name = "containerId")
  public Container getContainer() {
    return this.container;
  }
}

@Entity
@Table(name = "Container")
public class Container implements java.io.Serializable {

  private Integer containerId; //Auto Genenerated Unique ID
  private String container; //description
  private Set<ItemContainer> itemContainers = new HashSet<ItemContainer>(0);

  @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "container", orphanRemoval = true)
  public Set<ItemContainer> getItemContainers() {
    return this.itemContainers;
  }
}

Container 和 Item 都可以独占。 ItemContainer 只能存在包含对现有项目和容器的引用

我想要做的是能够删除一个项目,并删除它对应的 ItemContainer 行(使用孤立删除)。任何容器都应该仍然存在。

这是我尝试删除项目的方式:

@Override
public void delete(Item item) {
  EntityManager em = getEntityManager();
  em.remove(em.contains(item) ? item: em.merge(item));
}

我知道我可能必须先手动删除 ItemContainers,但我一直在遵循指南并帮助尝试让自动孤儿删除工作正常进行,并希望完成它。

感谢任何帮助。

干杯,
史蒂夫。

编辑 1: 按照 JB 的要求:这是正在使用的相关代码。

请求来自一个jsp到

/deleteItem?itemId=${item.itemId}

值得一提的是,我试图在我的 jUnit 测试中做同样的事情(如果需要,我可以发布测试代码和任何相关的类),但是我没有收到错误(它通过而没有抛出FK 约束错误,但 ItemContainer 表仍包含对已删除 Item 的引用)。

Item Controller、Service 和 Dao 实现:

@Controller
public class ItemController {

  @Autowired
  private ItemsService itemsService;

  @RequestMapping(value = "/deleteItem")
  public String deleteItem(@RequestParam() int itemId) {

    itemsService.delete(itemId);

    return "redirect:systemSettings/items";
  }
}

@Service("itemsService")
public class ItemsService {

  @Autowired
  private ItemsDAO itemsDao;

  public void delete(int itemId) {

    itemsDao.delete(itemsDao.getItem(itemId));
  }
}

@Repository
@Transactional
@Component("itemsDao")
public class ItemsDAOImpl implements ItemsDAO {

  @PersistenceContext
  private EntityManager entityManager;

  @Override
  public EntityManager getEntityManager() {
    return entityManager;
  }

  @Override
  public void setEntityManager(EntityManager entityManager) {
    this.entityManager = entityManager;
  }

  @Override
  public Item getItem(int itemId) {
    String queryString = "SELECT i FROM Item i "
        + "LEFT JOIN FETCH i.itemMachines "
        + "WHERE i.itemId=:itemId";
    Query query = getEntityManager().createQuery(queryString);
    query.setParameter("itemId", itemId);

    Item item = (Item) query.getSingleResult();
    System.out.println(item.getItemMachines().size());

    return item;
  }

  @Override
  public void delete(Item item) {
    EntityManager em = getEntityManager();
    em.remove(em.contains(item) ? item : em.merge(item));
  } 
}

这是堆栈跟踪:

org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint [null]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement
    at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:189)
    at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:155)
    at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:519)
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:757)
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:726)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:478)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:272)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:95)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:207)
    at com.sun.proxy.$Proxy29.delete(Unknown Source)
    at com.home.myproject.service.ItemsService.delete(ItemsService.java:45)
    at com.home.myproject.service.ItemsService$$FastClassBySpringCGLIB$$788e7765.invoke(<generated>)
    at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:640)
    at com.home.myproject.service.ItemsService$$EnhancerBySpringCGLIB$$296a7299.delete(<generated>)
    at com.home.myproject.controllers.ItemController.deleteItem(ItemController.java:121)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:483)
    at org.springframework.web.method.support.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:215)
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:132)
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:104)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandleMethod(RequestMappingHandlerAdapter.java:749)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:689)
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:83)
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:938)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:870)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:961)
    at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:852)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:620)
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:837)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:727)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:303)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:220)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:122)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:501)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:170)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:98)
    at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:950)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:116)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:408)
    at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1040)
    at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:607)
    at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:315)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:744)
Caused by: org.hibernate.exception.ConstraintViolationException: could not execute statement
    at org.hibernate.exception.internal.SQLStateConversionDelegate.convert(SQLStateConversionDelegate.java:129)
    at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:49)
    at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:126)
    at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:112)
    at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:211)
    at org.hibernate.engine.jdbc.batch.internal.NonBatchingBatch.addToBatch(NonBatchingBatch.java:62)
    at org.hibernate.persister.entity.AbstractEntityPersister.delete(AbstractEntityPersister.java:3400)
    at org.hibernate.persister.entity.AbstractEntityPersister.delete(AbstractEntityPersister.java:3630)
    at org.hibernate.action.internal.EntityDeleteAction.execute(EntityDeleteAction.java:114)
    at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:463)
    at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:349)
    at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:350)
    at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:56)
    at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1222)
    at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:425)
    at org.hibernate.engine.transaction.internal.jdbc.JdbcTransaction.beforeTransactionCommit(JdbcTransaction.java:101)
    at org.hibernate.engine.transaction.spi.AbstractTransactionImpl.commit(AbstractTransactionImpl.java:177)
    at org.hibernate.jpa.internal.TransactionImpl.commit(TransactionImpl.java:77)
    at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:515)
    ... 50 more
Caused by: com.microsoft.sqlserver.jdbc.SQLServerException: The DELETE statement conflicted with the REFERENCE constraint "FK_ItemContainer_Item". The conflict occurred in database "MyDatabase2", table "dbo.ItemContainer", column 'itemId'.
    at com.microsoft.sqlserver.jdbc.SQLServerException.makeFromDatabaseError(Unknown Source)
    at com.microsoft.sqlserver.jdbc.SQLServerStatement.getNextResult(Unknown Source)
    at com.microsoft.sqlserver.jdbc.SQLServerPreparedStatement.doExecutePreparedStatement(Unknown Source)
    at com.microsoft.sqlserver.jdbc.SQLServerPreparedStatement$PrepStmtExecCmd.doExecute(Unknown Source)
    at com.microsoft.sqlserver.jdbc.TDSCommand.execute(Unknown Source)
    at com.microsoft.sqlserver.jdbc.SQLServerConnection.executeCommand(Unknown Source)
    at com.microsoft.sqlserver.jdbc.SQLServerStatement.executeCommand(Unknown Source)
    at com.microsoft.sqlserver.jdbc.SQLServerStatement.executeStatement(Unknown Source)
    at com.microsoft.sqlserver.jdbc.SQLServerPreparedStatement.executeUpdate(Unknown Source)
    at org.apache.tomcat.dbcp.dbcp.DelegatingPreparedStatement.executeUpdate(DelegatingPreparedStatement.java:105)
    at org.apache.tomcat.dbcp.dbcp.DelegatingPreparedStatement.executeUpdate(DelegatingPreparedStatement.java:105)
    at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:208)
    ... 64 more

干杯。
史蒂夫。

编辑 2:

发现这个问题JPA Cascade remove orphans - James 的回答证实了 JB 所说的,但仍然不知道为什么它不起作用:(

【问题讨论】:

  • 这应该可以,应该级联 = ALL。 orphanRemoval 与该场景无关。 orphanRemoval 告诉 Hibernate 在您从项目中删除 ItemContainer 时删除 ItemContainer。发布一个重现问题的完整示例,以及异常的完整堆栈跟踪。我的猜测是,您要删除的 Item 的 ItemContainers 集实际上并不包含该 Item 的所有 ItemContainer。
  • 添加了堆栈跟踪和代码,还在 itemDao.getItem() 中打印出来以检查集合是否包含正确的信息,它正确打印出“1”。

标签: hibernate spring-mvc jpa


【解决方案1】:

首先想到(结果是错误的,检查编辑以获取解决方案)

好的,所以我已经解决了这个问题。

让我先介绍一下解决方案(以及问题,因为我仍然有点困惑)。

我项目中的 POJO 是通过将数据库逆向工程到 java 中创建的(使用 eclipse 中的 hibernate 工具插件)。

这需要大量的尝试和错误,但最终还是成功了。

生成的 POJO 包含 CascadeType.ALL 注释。

它不起作用的原因是什么?

ItemContainer 表的外键没有设置级联删除(尽管在 Java 中生成注解)。

(图片是我从谷歌那里得到的,不是我的图片;))

通过 MS SQLServer Management Studio 将两个外键设置为级联删除修复了该问题。我现在可以删除一个 Item 并让它正确删除链接它们的 ItemContainer 记录。

编辑 1:
-------------------------------------------------- ----------------------------------------

我想我会更新这个,因为新信息已经曝光。

级联数据库选项准确地解决了问题,而不是休眠进行级联,数据库正在这样做。

这是不正确的!!!

这个问题的罪魁祸首实际上是服务层和 DAO 层之间的交互。我的 DAO 是一个 Transactional 类,而我的 Service 不是(经过大量研究,反过来似乎很常见)。

这是我的功能

public void delete(int itemId) {

  itemsDao.delete(itemsDao.getItem(itemId));
}

注意:
1.服务请求来自DAO的Item
2. DAO 将 Item 返回给 Service
3.Service将Item传递给DAO

@Transactional 添加到此方法(或将其完全从 DAO 中删除并放入服务类)可以解决问题。

这背后的原因(根据我的理解)是@Transactional 通知持久性引擎持久化(或保留)其数据。一旦实体落在@Transactional 范围之外,它就会被刷新(或可能被刷新),因此,传递回 DAO 的项目不再存在于实体管理器中(如果我的条款不准确,请原谅我)。

我希望这次是真正的解决方案;)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-02-09
    • 2021-11-17
    • 2010-09-23
    • 2013-06-05
    • 2012-08-16
    • 1970-01-01
    • 2018-03-31
    相关资源
    最近更新 更多