Hibernate从子实体获取父代的id

时间:2012-11-22 09:38:43

标签: java hibernate jpa-2.0 spring-data-jpa

很多人发布了非常相似的问题,但我似乎无法找到解决我的情况的问题。

我有一个父实体Dnar和一个子实体WorkLocationSuburbDnar有一对多WorkLocationSuburbs

通常,我会按如下方式定义这种关系:

孩子

@Entity
@Table(name = "req_work_suburb", schema = SCHEMA)
public class WorkLocationSuburb {

  @Id
  @GeneratedValue(generator = "req_work_suburb_seq")
  @GenericGenerator(strategy="sequence", name="req_work_suburb_seq", parameters={@Parameter(name="sequence", value=SCHEMA+".req_work_suburb_seq")})
  @Column(name = "id", nullable = false)
  private Long id;

  @Column(name="suburb", length=255, nullable=false)
  private String suburb;

  //remainder omitted
}

父母

@Entity
@Table(name = "req_dnar", schema = SCHEMA)
@Inheritance(strategy = JOINED)
@DiscriminatorColumn(name = "form_type", discriminatorType = STRING, length = 64)
public abstract class Dnar {

  @Id
  @GeneratedValue(generator = "req_dnar_seq")
  @GenericGenerator(strategy = "sequence", name = "req_dnar_seq", parameters = { @Parameter(name = "sequence", value = SCHEMA + ".req_dnar_seq") })
  @Column(name = C_DNAR_ID, nullable = false)
  private Long dnarId;

  @Column(name = "original_id")
  private Long originalId;


  @OneToMany(cascade = ALL, fetch = EAGER, orphanRemoval=true)
  @JoinColumn(name = "dnar_id", nullable=false)
  private Set<WorkLocationSuburb> workLocationSuburbs;

  //remainder omitted
}

上面的示例有效,当我手动查询req_work_suburb表时,我可以看到它有一个dnar_id列,并且正确填充了该列。一切都很好,到目前为止!

在某些查询代码中,我创建了一个依赖于req_work_suburb.dnar_id的值的条件查询:

  private static Specification<SubmittedDnarSummary> suburbIs(final String suburb) {
    return new Specification<SubmittedDnarSummary>(){
      @Override
      public Predicate toPredicate(Root<SubmittedDnarSummary> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
        Subquery<Long> subquery = query.subquery(Long.class);
        Root<WorkLocationSuburb> suburbRoot = subquery.from(WorkLocationSuburb.class);
        return cb.in(root.get("dnarId")).value(subquery.select(suburbRoot.<Long>get("dnarId")).where(cb.equal(suburbRoot.get("suburb"), suburb)));
      }
    };
  }

当我执行上述操作时,我得到以下异常:

org.springframework.dao.InvalidDataAccessApiUsageException: Unable to resolve attribute [dnarId] against path; nested exception is java.lang.IllegalArgumentException: Unable to resolve attribute [dnarId] against path
         at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:301)
         at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:106)
         at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:403)
         at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:58)
         at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:213)
         at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:163)
         at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
         at org.springframework.data.jpa.repository.support.LockModeRepositoryPostProcessor$LockModePopulatingMethodIntercceptor.invoke(LockModeRepositoryPostProcessor.java:91)
         at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
         at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:90)
         at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
         at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)
         at $Proxy253.findAll(Unknown Source)
         at au.com.wpcorp.sys.dnar.spring.manager.impl.SearchManagerImpl.search(SearchManagerImpl.java:66)
         at au.com.wpcorp.sys.dnar.spring.mvc.controller.search.DnarSearchController.results(DnarSearchController.java:97)
         at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
         at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
         at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
         at java.lang.reflect.Method.invoke(Method.java:597)
         at org.springframework.web.method.support.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:213)
         at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:126)
         at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:96)
         at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:617)
         at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:578)
         at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:80)
         at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:923)
         at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:852)
         at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:882)
         at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:778)
         at javax.servlet.http.HttpServlet.service(HttpServlet.java:668)
         at javax.servlet.http.HttpServlet.service(HttpServlet.java:770)
         at org.apache.catalina.core.StandardWrapper.service(StandardWrapper.java:1550)
         at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:281)
         at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:175)
         at org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:655)
         at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:595)
         at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:161)
         at org.apache.catalina.connector.CoyoteAdapter.doService(CoyoteAdapter.java:331)
         at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:231)
         at com.sun.enterprise.v3.services.impl.ContainerMapper$AdapterCallable.call(ContainerMapper.java:317)
         at com.sun.enterprise.v3.services.impl.ContainerMapper.service(ContainerMapper.java:195)
         at com.sun.grizzly.http.ProcessorTask.invokeAdapter(ProcessorTask.java:860)
         at com.sun.grizzly.http.ProcessorTask.doProcess(ProcessorTask.java:757)
         at com.sun.grizzly.http.ProcessorTask.process(ProcessorTask.java:1056)
         at com.sun.grizzly.http.DefaultProtocolFilter.execute(DefaultProtocolFilter.java:229)
         at com.sun.grizzly.DefaultProtocolChain.executeProtocolFilter(DefaultProtocolChain.java:137)
         at com.sun.grizzly.DefaultProtocolChain.execute(DefaultProtocolChain.java:104)
         at com.sun.grizzly.DefaultProtocolChain.execute(DefaultProtocolChain.java:90)
         at com.sun.grizzly.http.HttpProtocolChain.execute(HttpProtocolChain.java:79)
         at com.sun.grizzly.ProtocolChainContextTask.doCall(ProtocolChainContextTask.java:54)
         at com.sun.grizzly.SelectionKeyContextTask.call(SelectionKeyContextTask.java:59)
         at com.sun.grizzly.ContextTask.run(ContextTask.java:71)
         at com.sun.grizzly.util.AbstractThreadPool$Worker.doWork(AbstractThreadPool.java:532)
         at com.sun.grizzly.util.AbstractThreadPool$Worker.run(AbstractThreadPool.java:513)
         at java.lang.Thread.run(Thread.java:662)
Caused by: java.lang.IllegalArgumentException: Unable to resolve attribute [dnarId] against path
         at org.hibernate.ejb.criteria.path.AbstractPathImpl.unknownAttribute(AbstractPathImpl.java:118)
         at org.hibernate.ejb.criteria.path.AbstractPathImpl.locateAttribute(AbstractPathImpl.java:223)
         at org.hibernate.ejb.criteria.path.AbstractPathImpl.get(AbstractPathImpl.java:194)
         at au.com.wpcorp.sys.dnar.spring.manager.impl.SearchManagerImpl$3.toPredicate(SearchManagerImpl.java:141)
         at org.springframework.data.jpa.repository.support.SimpleJpaRepository.applySpecificationToCriteria(SimpleJpaRepository.java:480)
         at org.springframework.data.jpa.repository.support.SimpleJpaRepository.getQuery(SimpleJpaRepository.java:436)
         at org.springframework.data.jpa.repository.support.SimpleJpaRepository.getQuery(SimpleJpaRepository.java:421)
         at org.springframework.data.jpa.repository.support.SimpleJpaRepository.findAll(SimpleJpaRepository.java:303)
         at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
         at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
         at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
         at java.lang.reflect.Method.invoke(Method.java:597)
         at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.executeMethodOn(RepositoryFactorySupport.java:334)
         at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:319)
         at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
         at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:110)
         at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
         at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:155)
         ... 49 more

似乎失败了,因为Hibernate在dnarId上找不到名为WorkLocationSuburb的属性。我的问题是,如果没有Hibernate抱怨其他问题,如何将此信息添加到WorkLocationSuburb?我似乎需要一些代码:

@Entity
@Table(name = "req_work_suburb", schema = SCHEMA)
public class WorkLocationSuburb {

  @Id
  @GeneratedValue(generator = "req_work_suburb_seq")
  @GenericGenerator(strategy="sequence", name="req_work_suburb_seq", parameters={@Parameter(name="sequence", value=SCHEMA+".req_work_suburb_seq")})
  @Column(name = "id", nullable = false)
  private Long id;

  @Column(name="suburb", length=255, nullable=false)
  private String suburb;

  @Column(name="dnar_id", nullable=false)  // column definition added here
  private Long dnarId;

  //remainder omitted
}

有谁知道如何让上述附加列工作?此列必须自动填充父级dnarId

谢谢, 道穆埃尔。


更新了JB Nizet的建议

我的代码现在是这样的:

孩子

@Entity
@Table(name = "req_work_suburb", schema = SCHEMA)
public class WorkLocationSuburb {

  @Id
  @GeneratedValue(generator = "req_work_suburb_seq")
  @GenericGenerator(strategy="sequence", name="req_work_suburb_seq", parameters={@Parameter(name="sequence", value=SCHEMA+".req_work_suburb_seq")})
  @Column(name = "id", nullable = false)
  private Long id;

  @Column(name="suburb", length=255, nullable=false)
  private String suburb;

  @ManyToOne
  @JoinColumn(name="dnar_id", nullable=false)
  private Dnar dnar;

  //remainder omitted
}

父母

@Entity
@Table(name = "req_dnar", schema = SCHEMA)
@Inheritance(strategy = JOINED)
@DiscriminatorColumn(name = "form_type", discriminatorType = STRING, length = 64)
public abstract class Dnar {

  @Id
  @GeneratedValue(generator = "req_dnar_seq")
  @GenericGenerator(strategy = "sequence", name = "req_dnar_seq", parameters = { @Parameter(name = "sequence", value = SCHEMA + ".req_dnar_seq") })
  @Column(name = C_DNAR_ID, nullable = false)
  private Long dnarId;

  @Column(name = "original_id")
  private Long originalId;

  @OneToMany(cascade = ALL, fetch = EAGER, orphanRemoval=true, mappedBy="dnar")
  private Set<WorkLocationSuburb> workLocationSuburbs = new LinkedHashSet<WorkLocationSuburb>();


  @PostPersist
  void setChildRefs() {
    for (WorkLocationSuburb suburb : workLocationSuburbs) {
      suburb.setDnar(this);
    }
  }

  //remainder omitted
}

当我尝试保存附加Dnar的{​​{1}}时,我会看到以下事件顺序:

  1. WorkLocationSuburb已插入数据库
  2. 调用
  3. Dnar@PostConstuct获取对父WorkLocationSuburb的引用。已为父Dnar正确分配了ID。
  4. 尝试插入子Dnar,但会导致以下异常:org.hibernate.exception.ConstraintViolationException:ORA-01400:无法插入NULL(“DNAR”。“REQ_WORK_SUBURB”。“DNAR_ID” )`
  5. 我还在做错什么? :)

1 个答案:

答案 0 :(得分:2)

您只需要将关联设为双向:

public class WorkLocationSuburb {
    @ManyToOne
    @JoinColumn
    private Dnar parentDnar;
    // ...
}

public class Dnar {
    @OneToMany(mappedBy = "parentDnar", ...)
    private Set<WorkLocationSuburb> suburbs;
    // ...
}

请注意,在双向OneToMany关联中,关联的拥有方始终是多方(没有mappedBy属性的方)。这意味着Hibernate只会考虑WorkLocationSuburb.parentDnar字段来决定关联是否存在。这意味着每次将一个郊区添加到dnar时,您还必须设置郊区的dnar字段:

public void addSuburb(WorkLocationSuburb suburb) {
    this.suburbs.add(suburb);
    suburb.setParentDnar(this);
}

同样,如果从dnar中删除郊区,则应将郊区的parentDnar设置为null。

关于查询,您现在可以在查询中使用parentDnar字段。请记住,JPA查询始终适用于实体及其持久字段/属性。从不在桌子和他们的柱子上。