Spring ブートで RESTful なインターフェースを実現しようとしましたが、問題が発生しました。
次の JSON を POST すると、サブオブジェクトが保存されていないことを示す org.hibernate.TransientObjectException 例外 (以下を参照) が発生します。
手でトレースすると、JSON はドメイン オブジェクトに正しくレンダリングされているように見えますが、save() メソッド (SimpleJpaRepository) が呼び出されると、サブオブジェクトを再帰的に再帰しようとしません。これは意図的なものですか?もしそうなら、サブオブジェクトを保存するように @RepositoryRestResource を構成する正しいアプローチは何ですか?
最小限の spring.boot アプリケーションがあり、診断に役立つ場合は喜んで提供します。
サブオブジェクト (関係) を持つ実際の例を探していますが、まだ見つかりません。spring.io の例は、私を動かすのに最適でしたが、それを拡張するのに少し行き詰まりました。
コード スニペットは次のとおりです。
JSON being posted:
{
"name" : "Test Sample Group",
"description" : null,
"projectCode" : null,
"creator" : "user001",
"createdDate" : 1395130128971,
"lastModifiedDate" : 1395130128971,
"samples" : [ {
"userPreferredId" : "S00012223434",
"wtsiUID" : "99997853483845",
"synonyms" : [ "ABC12345", "Humgen-0011232233", "1200088132734888234" ]
}, {
"userPreferredId" : "S000634734588",
"wtsiUID" : "34583934085358",
"synonyms" : [ "4875345993599934", "Humgen-004537682", "ABC674534" ]
} ]
}
次のコマンドを使用して投稿されます。
curl -i -X POST -H "Content-Type:application/json" --data @postdata.txt http://localhost:8080/samplegroup/
スローされる例外は次のとおりです。
org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: uk.ac.sanger.mig.samplegroup.domain.Sample
at** org.hibernate.engine.internal.ForeignKeys.getEntityIdentifierIfNotUnsaved(ForeignKeys.java:294)
at org.hibernate.type.EntityType.getIdentifier(EntityType.java:510)
at org.hibernate.type.ManyToOneType.nullSafeSet(ManyToOneType.java:165)
at org.hibernate.persister.collection.AbstractCollectionPersister.writeElement(AbstractCollectionPersister.java:899)
at org.hibernate.persister.collection.AbstractCollectionPersister.recreate(AbstractCollectionPersister.java:1308)
at org.hibernate.persister.collection.OneToManyPersister.recreate(OneToManyPersister.java:184)
at org.hibernate.action.internal.CollectionRecreateAction.execute(CollectionRecreateAction.java:67)
at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:453)
at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:345)
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:1218)
at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:421)
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)
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.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:136)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.data.jpa.repository.support.LockModeRepositoryPostProcessor$LockModePopulatingMethodIntercceptor.invoke(LockModeRepositoryPostProcessor.java:92)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:207)
at com.sun.proxy.$Proxy67.save(Unknown Source)
at org.springframework.data.rest.core.invoke.CrudRepositoryInvoker.invokeSave(CrudRepositoryInvoker.java:106)
at org.springframework.data.rest.webmvc.RepositoryEntityController.createAndReturn(RepositoryEntityController.java:339)
at org.springframework.data.rest.webmvc.RepositoryEntityController.postEntity(RepositoryEntityController.java:177)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
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:690)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:83)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:945)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:876)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:961)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:863)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:646)
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.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:77)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:108)
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.valves.RemoteIpValve.invoke(RemoteIpValve.java:683)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:170)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:98)
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.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1721)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1679)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:744)
RESTful リポジトリの定義は次のとおりです。
package uk.ac.sanger.mig.samplegroup.repository;
import java.util.List;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
import uk.ac.sanger.mig.samplegroup.domain.SampleGroup;
@RepositoryRestResource(collectionResourceRel = "samplegroup", path = "samplegroup")
public interface SampleGroupRepository extends PagingAndSortingRepository<SampleGroup, Long> {
List<SampleGroup> findByName(@Param("name") String name);
List<SampleGroup> findByCreator(@Param("name") String name);
}
エンティティの定義は次のとおりです。
package uk.ac.sanger.mig.samplegroup.domain;
import java.util.Date;
import java.util.Set;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;
import javax.persistence.SequenceGenerator;
@Entity
public class SampleGroup {
// Database primary key use only.
@Id
@GeneratedValue(strategy=GenerationType.SEQUENCE, generator="SAMPLE_GROUP_SEQ")
@SequenceGenerator(
name="SAMPLE_GROUP_SEQ",
sequenceName="SAMPLE_GROUP_SEQ",
allocationSize=1
)
private Long id;
@Column(unique=true, nullable=false)
private String name;
private String description;
private String projectCode;
private String creator;
private Date createdDate;
private Date lastModifiedDate;
@OneToMany
@JoinColumn(name="SAMPLE_FK")
private Set<Sample> samples;
public SampleGroup() {}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCreator() {
return creator;
}
public void setCreator(String creator) {
this.creator = creator;
}
public Date getCreatedDate() {
return createdDate;
}
public void setCreatedDate(Date createdDate) {
this.createdDate = createdDate;
}
public Date getLastModifiedDate() {
return lastModifiedDate;
}
public void setLastModifiedDate(Date lastModifiedDate) {
this.lastModifiedDate = lastModifiedDate;
}
public Set<Sample> getSamples() {
return samples;
}
public void setSamples(Set<Sample> samples) {
this.samples = samples;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getProjectCode() {
return projectCode;
}
public void setProjectCode(String projectCode) {
this.projectCode = projectCode;
}
@Override
public String toString() {
return "SampleGroup [id=" + id + ", name=" + name + ", description="
+ description + ", projectCode=" + projectCode + ", creator="
+ creator + ", createdDate=" + createdDate
+ ", lastModifiedDate=" + lastModifiedDate + ", samples="
+ samples + "]";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result
+ ((projectCode == null) ? 0 : projectCode.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
SampleGroup other = (SampleGroup) obj;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
if (projectCode == null) {
if (other.projectCode != null)
return false;
} else if (!projectCode.equals(other.projectCode))
return false;
return true;
}
}
package uk.ac.sanger.mig.samplegroup.domain;
import java.util.Set;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.SequenceGenerator;
@Entity
public class Sample {
// Database primary key use only.
@Id
@GeneratedValue(strategy=GenerationType.SEQUENCE, generator="SAMPLE_SEQ")
@SequenceGenerator(
name="SAMPLE_SEQ",
sequenceName="SAMPLE_SEQ",
allocationSize=1
)
private Long id;
private String userPreferredId; // What the buisiness user prefers to call the sample.
@Column(unique=true, nullable=false)
private String wtsiUID; // must be unique within WTSI (primary key)
@ElementCollection
private Set<String> synonyms;
public Sample() {}
public String getUserPreferredId() {
return userPreferredId;
}
public void setUserPreferredId(String userPreferredId) {
this.userPreferredId = userPreferredId;
}
public String getWtsiUID() {
return wtsiUID;
}
public void setWtsiUID(String wtsiUID) {
this.wtsiUID = wtsiUID;
}
public Set<String> getSynonyms() {
return synonyms;
}
public void setSynonyms(Set<String> synonyms) {
this.synonyms = synonyms;
}
@Override
public String toString() {
return "Sample [id=" + id + ", userPreferredId=" + userPreferredId
+ ", wtsiUID=" + wtsiUID + ", synonyms=" + synonyms + "]";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((wtsiUID == null) ? 0 : wtsiUID.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Sample other = (Sample) obj;
if (wtsiUID == null) {
if (other.wtsiUID != null)
return false;
} else if (!wtsiUID.equals(other.wtsiUID))
return false;
return true;
}
}