简体   繁体   中英

Hibernate get parent's id from child entity

a lot of people have posted very similar problems, but I can't seem to find one that addresses my circumstance.

I have a parent entity Dnar and a child entity WorkLocationSuburb . A Dnar has one-to-many WorkLocationSuburbs .

Normally, I would define this relationship as follows:

The child

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

The parent

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

The above example works, and when I manually query the req_work_suburb table, I can see that it has a dnar_id column and that the column is being correctly populated. All good, so far!

In some query code, I create a criteria query that is dependent on the value of 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)));
      }
    };
  }

When I execute the above, I get the following exception:

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

It appears to be failing because Hibernate can't find a property called dnarId on WorkLocationSuburb . My question is, how can I add this information to WorkLocationSuburb without Hibernate complaining about some other problem? It seems I need some code like:

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

Does anyone know how I can get the above additional column to work? This column must be automatically populated with the parent's dnarId .

Thanks, Muel.


Updated with JB Nizet's suggestions

My code is now like so:

The child

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

The parent

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

When I attempt to save a Dnar that has a WorkLocationSuburb attached, I see the following order of events:

  1. The Dnar is inserted into the database
  2. @PostConstuct is invoked and the WorkLocationSuburb gets a reference to the parent Dnar . The parent Dnar has been assigned an ID correctly.
  3. The insertion of child WorkLocationSuburb is attempted, but results in the following exception: org.hibernate.exception.ConstraintViolationException: ORA-01400: cannot insert NULL into ("DNAR"."REQ_WORK_SUBURB"."DNAR_ID")`

What am I still doing incorrectly? :)

You just need to make the association bidirectional:

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

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

Just be aware that in a bidirectional OneToMany association, the owning side of the association is always the many side (the side which doesn't have the mappedBy attribute). This means the Hibernate will only consider the WorkLocationSuburb.parentDnar field to decide if the association exists. This thus means that each time you add a suburb to a dnar, you must also set the suburb's dnar field:

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

Similarly, if you remove a suburb from a dnar, you should set the parentDnar of the suburb to null.

Regarding the query, you can now use the parentDnar field in your query. Remember that JPA queries always work on entities and their persistent fields/properties. Never on table and their columns.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM