Reputation: 4425
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:
Dnar
is inserted into the database@PostConstuct
is invoked and the WorkLocationSuburb
gets a reference to the parent Dnar
. The parent Dnar
has been assigned an ID correctly.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? :)
Upvotes: 2
Views: 9775
Reputation: 691635
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.
Upvotes: 2