短:
是否可以编辑项目资源的值实体集合(“替换集合”)?
假设有以下模型:
+---------+ +------------------+ +--------+
| Student | <-1----n-> | CourseMembership | <-m----1-> | Course |
+---------+ +------------------+ +--------+
Student
和
Course
通过Spring Data Rest和相应的存储库导出(
Students
和
Courses
,两者
JpaRepository
)存在。
CourseMembership
不应该导出(不应该存在它的端点),因此,相应的存储库也不存在/不必要。
以下是三个实体/表的三个类:
Entity
@Table(name = "students")
@Getter
@Setter
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class Course extends AbstractPersistable<Long> {
@Column(name = "name", nullable = false)
private String name;
}
@Entity
@Table(name = "course_memberships")
@Getter
@Setter
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class CourseMembership extends AbstractPersistable<Long> {
@ManyToOne
@JoinColumn(name = "student")
private Student student;
@ManyToOne
@JoinColumn(name = "course")
private Course course;
}
@Entity
@Table(name = "students")
@Getter
@Setter
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class Student extends AbstractPersistable<Long> {
@Column(name = "name", nullable = false)
private String name;
@OneToMany(mappedBy = "student", cascade = { CascadeType.ALL })
// @OneToMany(mappedBy = "student", cascade = { CascadeType.ALL }, orphanRemoval = true)
@Setter(AccessLevel.NONE)
private List<CourseMembership> courseMemberships = new ArrayList<>();
public void setCourseMemberships(List<CourseMembership> courseMemberships) {
this.courseMemberships.clear();
this.courseMemberships.addAll(courseMemberships);
this.courseMemberships.forEach(courseMembership -> courseMembership.setStudent(this));
}
}
当课程会员资格第一次被标记时,一切正常,元素被插入:
~$ curl -X PATCH -H "Content-Type: application/json" "http://localhost:8080/students/3" -d '{"courseMemberships":[{"course":"/courses/1"},{"course":"/courses/2"}]}'
{
"name" : "John Doe",
"courseMemberships" : [ {
"new" : false,
"_links" : {
"student" : {
"href" : "http://localhost:8080/students/3"
},
"course" : {
"href" : "http://localhost:8080/courses/1"
}
}
}, {
"new" : false,
"_links" : {
"student" : {
"href" : "http://localhost:8080/students/3"
},
"course" : {
"href" : "http://localhost:8080/courses/2"
}
}
} ],
"new" : false,
"_links" : {
"self" : {
"href" : "http://localhost:8080/students/3"
},
"student" : {
"href" : "http://localhost:8080/students/3"
}
}
}
但在接下来的PATCH操作中,我遇到了困难:
问题/疑问:
当我试图替换列表时(例如,用空列表或由其他元素组成的另一个列表),要么什么都不会发生(没有
orphanRemoval = true
)或出现异常:
没有
孤儿删除=true
:
在PATCH响应中,集合看起来已修改,但在PATCH后使用GET,响应将显示原始集合。
(预设:两个元素所在之处)
~$ curl -X PATCH -H "Content-Type: application/json" "http://localhost:8080/students/3" -d '{"courseMemberships":[]}'
{
"name" : "John Doe",
"courseMemberships" : [ ],
"new" : false,
"_links" : {
"self" : {
"href" : "http://localhost:8080/students/3"
},
"student" : {
"href" : "http://localhost:8080/students/3"
}
}
}
~$ curl -X GET "http://localhost:8080/students/3"
{
"name" : "John Doe",
"courseMemberships" : [ {
"new" : false,
"_links" : {
"student" : {
"href" : "http://localhost:8080/students/3"
},
"course" : {
"href" : "http://localhost:8080/courses/1"
}
}
}, {
"new" : false,
"_links" : {
"student" : {
"href" : "http://localhost:8080/students/3"
},
"course" : {
"href" : "http://localhost:8080/courses/2"
}
}
} ],
"new" : false,
"_links" : {
"self" : {
"href" : "http://localhost:8080/students/3"
},
"student" : {
"href" : "http://localhost:8080/students/3"
}
}
}
具有
孤儿删除=true
:
清除现有列表是可能的,但“用另一个列表替换”会导致异常。
(假设:有一个元素)
~$ curl -X PATCH -H "Content-Type: application/json" "http://localhost:8080/students/3" -d '{"courseMemberships":[{"course":"/courses/1"},{"course":"/courses/2"}]}'
{"cause":{"cause":null,"message":"not-null property references a null or transient value : io.gitlab.hjoeren.foo.CourseMembership.student"},"message":"not-null property references a null or transient value : io.gitlab.hjoeren.foo.CourseMembership.student; nested exception is org.hibernate.PropertyValueException: not-null property references a null or transient value : io.gitlab.hjoeren.foo.CourseMembership.student"}
2021-05-01 20:15:55.344 ERROR 23606 --- [nio-8080-exec-4] o.s.d.r.w.RepositoryRestExceptionHandler : not-null property references a null or transient value : io.gitlab.hjoeren.foo.CourseMembership.student; nested exception is org.hibernate.PropertyValueException: not-null property references a null or transient value : io.gitlab.hjoeren.foo.CourseMembership.student
org.springframework.dao.DataIntegrityViolationException: not-null property references a null or transient value : io.gitlab.hjoeren.foo.CourseMembership.student; nested exception is org.hibernate.PropertyValueException: not-null property references a null or transient value : io.gitlab.hjoeren.foo.CourseMembership.student
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:294) ~[spring-orm-5.3.6.jar:5.3.6]
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:233) ~[spring-orm-5.3.6.jar:5.3.6]
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:551) ~[spring-orm-5.3.6.jar:5.3.6]
at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:61) ~[spring-tx-5.3.6.jar:5.3.6]
at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:242) ~[spring-tx-5.3.6.jar:5.3.6]
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:152) ~[spring-tx-5.3.6.jar:5.3.6]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.6.jar:5.3.6]
at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:174) ~[spring-data-jpa-2.4.8.jar:2.4.8]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.6.jar:5.3.6]
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97) ~[spring-aop-5.3.6.jar:5.3.6]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.6.jar:5.3.6]
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:215) ~[spring-aop-5.3.6.jar:5.3.6]
at com.sun.proxy.$Proxy97.save(Unknown Source) ~[na:na]
at org.springframework.data.repository.support.CrudRepositoryInvoker.invokeSave(CrudRepositoryInvoker.java:101) ~[spring-data-commons-2.4.8.jar:2.4.8]
at org.springframework.data.rest.core.support.UnwrappingRepositoryInvokerFactory$UnwrappingRepositoryInvoker.invokeSave(UnwrappingRepositoryInvokerFactory.java:181) ~[spring-data-rest-core-3.4.8.jar:3.4.8]
at org.springframework.data.rest.webmvc.RepositoryEntityController.saveAndReturn(RepositoryEntityController.java:446) ~[spring-data-rest-webmvc-3.4.8.jar:3.4.8]
at org.springframework.data.rest.webmvc.RepositoryEntityController.patchItemResource(RepositoryEntityController.java:395) ~[spring-data-rest-webmvc-3.4.8.jar:3.4.8]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na]
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:197) ~[spring-web-5.3.6.jar:5.3.6]
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:141) ~[spring-web-5.3.6.jar:5.3.6]
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:106) ~[spring-webmvc-5.3.6.jar:5.3.6]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:894) ~[spring-webmvc-5.3.6.jar:5.3.6]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808) ~[spring-webmvc-5.3.6.jar:5.3.6]
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.3.6.jar:5.3.6]
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1060) ~[spring-webmvc-5.3.6.jar:5.3.6]
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:962) ~[spring-webmvc-5.3.6.jar:5.3.6]
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) ~[spring-webmvc-5.3.6.jar:5.3.6]
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:880) ~[spring-webmvc-5.3.6.jar:5.3.6]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:733) ~[tomcat-embed-core-9.0.45.jar:4.0.FR]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227) ~[tomcat-embed-core-9.0.45.jar:9.0.45]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.45.jar:9.0.45]
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) ~[tomcat-embed-websocket-9.0.45.jar:9.0.45]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.45.jar:9.0.45]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.45.jar:9.0.45]
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-5.3.6.jar:5.3.6]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.6.jar:5.3.6]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.45.jar:9.0.45]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.45.jar:9.0.45]
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-5.3.6.jar:5.3.6]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.6.jar:5.3.6]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.45.jar:9.0.45]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.45.jar:9.0.45]
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-5.3.6.jar:5.3.6]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.6.jar:5.3.6]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.45.jar:9.0.45]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.45.jar:9.0.45]
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202) ~[tomcat-embed-core-9.0.45.jar:9.0.45]
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97) ~[tomcat-embed-core-9.0.45.jar:9.0.45]
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542) ~[tomcat-embed-core-9.0.45.jar:9.0.45]
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:143) ~[tomcat-embed-core-9.0.45.jar:9.0.45]
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) ~[tomcat-embed-core-9.0.45.jar:9.0.45]
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78) ~[tomcat-embed-core-9.0.45.jar:9.0.45]
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:357) ~[tomcat-embed-core-9.0.45.jar:9.0.45]
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:374) ~[tomcat-embed-core-9.0.45.jar:9.0.45]
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) ~[tomcat-embed-core-9.0.45.jar:9.0.45]
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:893) ~[tomcat-embed-core-9.0.45.jar:9.0.45]
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1707) ~[tomcat-embed-core-9.0.45.jar:9.0.45]
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) ~[tomcat-embed-core-9.0.45.jar:9.0.45]
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) ~[na:na]
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) ~[na:na]
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-9.0.45.jar:9.0.45]
at java.base/java.lang.Thread.run(Thread.java:829) ~[na:na]
Caused by: org.hibernate.PropertyValueException: not-null property references a null or transient value : io.gitlab.hjoeren.foo.CourseMembership.student
at org.hibernate.engine.internal.Nullability.checkNullability(Nullability.java:111) ~[hibernate-core-5.4.30.Final.jar:5.4.30.Final]
at org.hibernate.engine.internal.Nullability.checkNullability(Nullability.java:55) ~[hibernate-core-5.4.30.Final.jar:5.4.30.Final]
at org.hibernate.action.internal.AbstractEntityInsertAction.nullifyTransientReferencesIfNotAlready(AbstractEntityInsertAction.java:116) ~[hibernate-core-5.4.30.Final.jar:5.4.30.Final]
at org.hibernate.action.internal.AbstractEntityInsertAction.makeEntityManaged(AbstractEntityInsertAction.java:125) ~[hibernate-core-5.4.30.Final.jar:5.4.30.Final]
at org.hibernate.engine.spi.ActionQueue.addResolvedEntityInsertAction(ActionQueue.java:289) ~[hibernate-core-5.4.30.Final.jar:5.4.30.Final]
at org.hibernate.engine.spi.ActionQueue.addInsertAction(ActionQueue.java:263) ~[hibernate-core-5.4.30.Final.jar:5.4.30.Final]
at org.hibernate.engine.spi.ActionQueue.addAction(ActionQueue.java:250) ~[hibernate-core-5.4.30.Final.jar:5.4.30.Final]
at org.hibernate.event.internal.AbstractSaveEventListener.addInsertAction(AbstractSaveEventListener.java:338) ~[hibernate-core-5.4.30.Final.jar:5.4.30.Final]
at org.hibernate.event.internal.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:287) ~[hibernate-core-5.4.30.Final.jar:5.4.30.Final]
at org.hibernate.event.internal.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:193) ~[hibernate-core-5.4.30.Final.jar:5.4.30.Final]
at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:135) ~[hibernate-core-5.4.30.Final.jar:5.4.30.Final]
at org.hibernate.event.internal.DefaultMergeEventListener.saveTransientEntity(DefaultMergeEventListener.java:271) ~[hibernate-core-5.4.30.Final.jar:5.4.30.Final]
at org.hibernate.event.internal.DefaultMergeEventListener.entityIsTransient(DefaultMergeEventListener.java:243) ~[hibernate-core-5.4.30.Final.jar:5.4.30.Final]
at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:175) ~[hibernate-core-5.4.30.Final.jar:5.4.30.Final]
at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:104) ~[hibernate-core-5.4.30.Final.jar:5.4.30.Final]
at org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:813) ~[hibernate-core-5.4.30.Final.jar:5.4.30.Final]
at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:786) ~[hibernate-core-5.4.30.Final.jar:5.4.30.Final]
at org.hibernate.engine.spi.CascadingActions$6.cascade(CascadingActions.java:261) ~[hibernate-core-5.4.30.Final.jar:5.4.30.Final]
at org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:499) ~[hibernate-core-5.4.30.Final.jar:5.4.30.Final]
at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:423) ~[hibernate-core-5.4.30.Final.jar:5.4.30.Final]
at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:220) ~[hibernate-core-5.4.30.Final.jar:5.4.30.Final]
at org.hibernate.engine.internal.Cascade.cascadeCollectionElements(Cascade.java:532) ~[hibernate-core-5.4.30.Final.jar:5.4.30.Final]
at org.hibernate.engine.internal.Cascade.cascadeCollection(Cascade.java:463) ~[hibernate-core-5.4.30.Final.jar:5.4.30.Final]
at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:426) ~[hibernate-core-5.4.30.Final.jar:5.4.30.Final]
at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:220) ~[hibernate-core-5.4.30.Final.jar:5.4.30.Final]
at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:153) ~[hibernate-core-5.4.30.Final.jar:5.4.30.Final]
at org.hibernate.event.internal.DefaultMergeEventListener.cascadeOnMerge(DefaultMergeEventListener.java:519) ~[hibernate-core-5.4.30.Final.jar:5.4.30.Final]
at org.hibernate.event.internal.DefaultMergeEventListener.entityIsPersistent(DefaultMergeEventListener.java:204) ~[hibernate-core-5.4.30.Final.jar:5.4.30.Final]
at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:178) ~[hibernate-core-5.4.30.Final.jar:5.4.30.Final]
at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:70) ~[hibernate-core-5.4.30.Final.jar:5.4.30.Final]
at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:93) ~[hibernate-core-5.4.30.Final.jar:5.4.30.Final]
at org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:793) ~[hibernate-core-5.4.30.Final.jar:5.4.30.Final]
at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:780) ~[hibernate-core-5.4.30.Final.jar:5.4.30.Final]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na]
at org.springframework.orm.jpa.ExtendedEntityManagerCreator$ExtendedEntityManagerInvocationHandler.invoke(ExtendedEntityManagerCreator.java:362) ~[spring-orm-5.3.6.jar:5.3.6]
at com.sun.proxy.$Proxy90.merge(Unknown Source) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na]
at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:311) ~[spring-orm-5.3.6.jar:5.3.6]
at com.sun.proxy.$Proxy90.merge(Unknown Source) ~[na:na]
at org.springframework.data.jpa.repository.support.SimpleJpaRepository.save(SimpleJpaRepository.java:560) ~[spring-data-jpa-2.4.8.jar:2.4.8]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na]
at org.springframework.data.repository.core.support.RepositoryMethodInvoker$RepositoryFragmentMethodInvoker.lambda$new$0(RepositoryMethodInvoker.java:289) ~[spring-data-commons-2.4.8.jar:2.4.8]
at org.springframework.data.repository.core.support.RepositoryMethodInvoker.doInvoke(RepositoryMethodInvoker.java:137) ~[spring-data-commons-2.4.8.jar:2.4.8]
at org.springframework.data.repository.core.support.RepositoryMethodInvoker.invoke(RepositoryMethodInvoker.java:121) ~[spring-data-commons-2.4.8.jar:2.4.8]
at org.springframework.data.repository.core.support.RepositoryComposition$RepositoryFragments.invoke(RepositoryComposition.java:524) ~[spring-data-commons-2.4.8.jar:2.4.8]
at org.springframework.data.repository.core.support.RepositoryComposition.invoke(RepositoryComposition.java:285) ~[spring-data-commons-2.4.8.jar:2.4.8]
at org.springframework.data.repository.core.support.RepositoryFactorySupport$ImplementationMethodExecutionInterceptor.invoke(RepositoryFactorySupport.java:531) ~[spring-data-commons-2.4.8.jar:2.4.8]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.6.jar:5.3.6]
at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.doInvoke(QueryExecutorMethodInterceptor.java:156) ~[spring-data-commons-2.4.8.jar:2.4.8]
at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.invoke(QueryExecutorMethodInterceptor.java:131) ~[spring-data-commons-2.4.8.jar:2.4.8]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.6.jar:5.3.6]
at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:80) ~[spring-data-commons-2.4.8.jar:2.4.8]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.6.jar:5.3.6]
at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123) ~[spring-tx-5.3.6.jar:5.3.6]
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:388) ~[spring-tx-5.3.6.jar:5.3.6]
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) ~[spring-tx-5.3.6.jar:5.3.6]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.6.jar:5.3.6]
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:137) ~[spring-tx-5.3.6.jar:5.3.6]
... 59 common frames omitted
2021-05-01 20:15:55.350 WARN 23606 --- [nio-8080-exec-4] .m.m.a.ExceptionHandlerExceptionResolver : Resolved [org.springframework.dao.DataIntegrityViolationException: not-null property references a null or transient value : io.gitlab.hjoeren.foo.CourseMembership.student; nested exception is org.hibernate.PropertyValueException: not-null property references a null or transient value : io.gitlab.hjoeren.foo.CourseMembership.student]
也许有一个有用的提示:当集合中最初只有一个元素,并且一个PATCH包含一个由另一个元素组成的集合时,该元素会被更新,而不是被删除和插入:
(前提:之前没有课程会员资格的地方)
~$ curl -X PATCH -H "Content-Type: application/json" "http://localhost:8080/students/3" -d '{"courseMemberships":[{"course":"/courses/1"}]}'
{
"name" : "John Doe",
"courseMemberships" : [ {
"new" : false,
"_links" : {
"student" : {
"href" : "http://localhost:8080/students/3"
},
"course" : {
"href" : "http://localhost:8080/courses/1"
}
}
} ],
"new" : false,
"_links" : {
"self" : {
"href" : "http://localhost:8080/students/3"
},
"student" : {
"href" : "http://localhost:8080/students/3"
}
}
}
Hibernate: select student0_.id as id1_2_0_, student0_.name as name2_2_0_ from students student0_ where student0_.id=?
Hibernate: select coursememb0_.student as student3_0_0_, coursememb0_.id as id1_0_0_, coursememb0_.id as id1_0_1_, coursememb0_.course as course2_0_1_, coursememb0_.student as student3_0_1_, course1_.id as id1_1_2_, course1_.name as name2_1_2_ from course_memberships coursememb0_ inner join courses course1_ on coursememb0_.course=course1_.id where coursememb0_.student=?
Hibernate: select course0_.id as id1_1_0_, course0_.name as name2_1_0_ from courses course0_ where course0_.id=?
Hibernate: call next value for hibernate_sequence
Hibernate: insert into course_memberships (course, student, id) values (?, ?, ?)
~$ curl -X PATCH -H "Content-Type: application/json" "http://localhost:8080/students/3" -d '{"courseMemberships":[{"course":"/courses/2"}]}'
{
"name" : "John Doe",
"courseMemberships" : [ {
"new" : false,
"_links" : {
"student" : {
"href" : "http://localhost:8080/students/3"
},
"course" : {
"href" : "http://localhost:8080/courses/2"
}
}
} ],
"new" : false,
"_links" : {
"self" : {
"href" : "http://localhost:8080/students/3"
},
"student" : {
"href" : "http://localhost:8080/students/3"
}
}
}
Hibernate: select student0_.id as id1_2_0_, student0_.name as name2_2_0_ from students student0_ where student0_.id=?
Hibernate: select coursememb0_.student as student3_0_0_, coursememb0_.id as id1_0_0_, coursememb0_.id as id1_0_1_, coursememb0_.course as course2_0_1_, coursememb0_.student as student3_0_1_, course1_.id as id1_1_2_, course1_.name as name2_1_2_ from course_memberships coursememb0_ inner join courses course1_ on coursememb0_.course=course1_.id where coursememb0_.student=?
Hibernate: select course0_.id as id1_1_0_, course0_.name as name2_1_0_ from courses course0_ where course0_.id=?
Hibernate: update course_memberships set course=?, student=? where id=?