我有以下两个简单模型在Grails 2.4.3应用程序中创建双向一对多关系:
class Player {
String firstName
String lastName
String position
static belongsTo = [team: Team]
}
class Team {
String name
List players = new ArrayList()
static hasMany = [players: Player]
static mapping= {
players cascade:"all-delete-orphan"
}
}
我希望能够通过嵌套的JSON保存和更新团队及其相关玩家,如下所示:
{
name : "team A",
players : [
{
firstName : "john",
lastName :"doe",
position : "center"
}
]
}
我的TeamController保存操作如下所示:
def save() {
def team = new Team(request.JSON)
team.save()
respond team
}
当我使用该JSON发出请求时,出现错误:
curl -X POST -d '{name:"team a",players:[{firstName:"john",lastName:"doe",position:"center"}]}' http://localhost:8080/team-test/team/save.json --header "Content-Type:application/json"
在控制台中生成此输出:
| Error 2014-11-14 10:32:26,111 [http-bio-8080-exec-6] ERROR errors.GrailsExceptionResolver - NullPointerException occurred when processing request: [POST] /team-test/team/save.json
Stacktrace follows:
Message: null
Line | Method
->> 8 | save in team.test.TeamController$$EOvbjH0K
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
| 198 | doFilter in grails.plugin.cache.web.filter.PageFragmentCachingFilter
| 63 | doFilter in grails.plugin.cache.web.filter.AbstractFilter
| 1145 | runWorker in java.util.concurrent.ThreadPoolExecutor
| 615 | run . . . in java.util.concurrent.ThreadPoolExecutor$Worker
^ 745 | run in java.lang.Thread
因为这是一个相当模糊的消息,我在TeamController中调用team.save()调用try / catch并打印出堆栈跟踪:
def save() {
def team = new Team(request.JSON)
try {
team.save()
}
catch(Exception e)
{
e.printStackTrace()
}
respond team
}
Error java.lang.NullPointerException
| Error at org.hibernate.engine.spi.BatchFetchQueue.removeBatchLoadableEntityKey(BatchFetchQueue.java:163)
| Error at org.hibernate.engine.internal.StatefulPersistenceContext.addEntity(StatefulPersistenceContext.java:388)
| Error at org.hibernate.engine.internal.StatefulPersistenceContext.addEntity(StatefulPersistenceContext.java:461)
| Error at org.hibernate.action.internal.AbstractEntityInsertAction.makeEntityManaged(AbstractEntityInsertAction.java:143)
| Error at org.hibernate.engine.spi.ActionQueue.addResolvedEntityInsertAction(ActionQueue.java:201)
| Error at org.hibernate.engine.spi.ActionQueue.addInsertAction(ActionQueue.java:179)
| Error at org.hibernate.engine.spi.ActionQueue.addAction(ActionQueue.java:214)
| Error at org.hibernate.event.internal.AbstractSaveEventListener.addInsertAction(AbstractSaveEventListener.java:324)
| Error at org.hibernate.event.internal.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:288)
| Error at org.hibernate.event.internal.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:194)
| Error at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:125)
| Error at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.saveWithGeneratedOrRequestedId(DefaultSaveOrUpdateEventListener.java:209)
| Error at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.java:194)
| Error at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.performSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:114)
| Error at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:90)
| Error at org.hibernate.internal.SessionImpl.fireSaveOrUpdate(SessionImpl.java:684)
| Error at org.hibernate.internal.SessionImpl.saveOrUpdate(SessionImpl.java:676)
| Error at org.hibernate.engine.spi.CascadingActions$5.cascade(CascadingActions.java:235)
| Error at org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:350)
| Error at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:293)
| Error at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:161)
| Error at org.hibernate.engine.internal.Cascade.cascadeCollectionElements(Cascade.java:379)
| Error at org.hibernate.engine.internal.Cascade.cascadeCollection(Cascade.java:319)
| Error at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:296)
| Error at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:161)
| Error at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:118)
| Error at org.hibernate.event.internal.AbstractSaveEventListener.cascadeAfterSave(AbstractSaveEventListener.java:460)
| Error at org.hibernate.event.internal.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:294)
| Error at org.hibernate.event.internal.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:194)
| Error at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:125)
| Error at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.saveWithGeneratedOrRequestedId(DefaultSaveOrUpdateEventListener.java:209)
| Error at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.java:194)
| Error at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.performSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:114)
| Error at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:90)
| Error at org.hibernate.internal.SessionImpl.fireSaveOrUpdate(SessionImpl.java:684)
| Error at org.hibernate.internal.SessionImpl.saveOrUpdate(SessionImpl.java:676)
| Error at org.hibernate.internal.SessionImpl.saveOrUpdate(SessionImpl.java:671)
| Error at org.codehaus.groovy.grails.orm.hibernate.metaclass.SavePersistentMethod$1.doInHibernate(SavePersistentMethod.java:58)
| Error at org.codehaus.groovy.grails.orm.hibernate.GrailsHibernateTemplate.doExecute(GrailsHibernateTemplate.java:179)
| Error at org.codehaus.groovy.grails.orm.hibernate.GrailsHibernateTemplate.execute(GrailsHibernateTemplate.java:123)
| Error at org.codehaus.groovy.grails.orm.hibernate.metaclass.SavePersistentMethod.performSave(SavePersistentMethod.java:56)
| Error at org.codehaus.groovy.grails.orm.hibernate.metaclass.AbstractSavePersistentMethod.doInvokeInternal(AbstractSavePersistentMethod.java:215)
| Error at org.codehaus.groovy.grails.orm.hibernate.metaclass.AbstractDynamicPersistentMethod.invoke(AbstractDynamicPersistentMethod.java:68)
| Error at org.codehaus.groovy.grails.orm.hibernate.HibernateGormInstanceApi.save(HibernateGormInstanceApi.groovy:156)
| Error at team.test.Team$$EOvbjH0K.save(Team.groovy)
| Error at team.test.Team$$DOvbjH0K.save(Unknown Source)
| Error at team.test.Team.save(Team.groovy)
| Error at team.test.Team$save.call(Unknown Source)
| Error at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:45)
| Error at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:108)
| Error at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:112)
| Error at team.test.TeamController$$EOvbkj0y.save(TeamController.groovy:9)
| Error at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
| Error at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
| Error at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
| Error at java.lang.reflect.Method.invoke(Method.java:606)
| Error at org.springsource.loaded.ri.ReloadedTypeInvoker$2.invoke(ReloadedTypeInvoker.java:122)
| Error at org.springsource.loaded.ri.ReflectiveInterceptor.jlrMethodInvoke(ReflectiveInterceptor.java:1299)
| Error at org.codehaus.groovy.grails.web.servlet.mvc.MixedGrailsControllerHelper.invoke(MixedGrailsControllerHelper.java:154)
| Error at org.codehaus.groovy.grails.web.servlet.mvc.AbstractGrailsControllerHelper.handleAction(AbstractGrailsControllerHelper.java:375)
| Error at org.codehaus.groovy.grails.web.servlet.mvc.AbstractGrailsControllerHelper.executeAction(AbstractGrailsControllerHelper.java:252)
| Error at org.codehaus.groovy.grails.web.servlet.mvc.AbstractGrailsControllerHelper.handleURI(AbstractGrailsControllerHelper.java:205)
| Error at org.codehaus.groovy.grails.web.servlet.mvc.AbstractGrailsControllerHelper.handleURI(AbstractGrailsControllerHelper.java:126)
| Error at org.codehaus.groovy.grails.web.servlet.mvc.SimpleGrailsController.handleRequest(SimpleGrailsController.java:72)
| Error at org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter.handle(SimpleControllerHandlerAdapter.java:50)
| Error at org.codehaus.groovy.grails.web.servlet.GrailsDispatcherServlet.doDispatch(GrailsDispatcherServlet.java:347)
| Error at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:870)
| Error at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:961)
| Error at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:863)
| Error at javax.servlet.http.HttpServlet.service(HttpServlet.java:646)
| Error at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:837)
| Error at javax.servlet.http.HttpServlet.service(HttpServlet.java:727)
| Error at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:303)
| Error at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
| Error at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
| Error at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
| Error at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
| Error at grails.plugin.cache.web.filter.PageFragmentCachingFilter.doFilter(PageFragmentCachingFilter.java:198)
| Error at grails.plugin.cache.web.filter.AbstractFilter.doFilter(AbstractFilter.java:63)
| Error at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:344)
| Error at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:261)
| Error at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
| Error at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
| Error at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:101)
| Error at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
| Error at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
| Error at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:101)
| Error at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
| Error at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
| Error at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:101)
| Error at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
| Error at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
| Error at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:748)
| Error at org.apache.catalina.core.ApplicationDispatcher.processRequest(ApplicationDispatcher.java:486)
| Error at org.apache.catalina.core.ApplicationDispatcher.doForward(ApplicationDispatcher.java:411)
| Error at org.apache.catalina.core.ApplicationDispatcher.forward(ApplicationDispatcher.java:338)
| Error at org.codehaus.groovy.grails.web.mapping.UrlMappingUtils.forwardRequestForUrlMappingInfo(UrlMappingUtils.java:178)
| Error at org.codehaus.groovy.grails.web.mapping.UrlMappingUtils.forwardRequestForUrlMappingInfo(UrlMappingUtils.java:144)
| Error at org.codehaus.groovy.grails.web.mapping.UrlMappingUtils.forwardRequestForUrlMappingInfo(UrlMappingUtils.java:135)
| Error at org.codehaus.groovy.grails.web.mapping.filter.UrlMappingsFilter.doFilterInternal(UrlMappingsFilter.java:216)
| Error at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
| Error at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
| Error at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
| Error at org.codehaus.groovy.grails.web.servlet.mvc.GrailsWebRequestFilter.doFilterInternal(GrailsWebRequestFilter.java:69)
| Error at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
| Error at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
| Error at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
| Error at org.codehaus.groovy.grails.web.filters.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:67)
| Error at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
| Error at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
| Error at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
| Error at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:88)
| Error at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
| Error at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:344)
| Error at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:261)
| Error at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
| Error at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
| Error at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:220)
| Error at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:122)
| Error at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:171)
| Error at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:103)
| Error at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:116)
| Error at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:408)
| Error at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1070)
| Error at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:611)
| Error at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:314)
| Error at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
| Error at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
| Error at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
| Error at java.lang.Thread.run(Thread.java:745)
为了进一步调查,我在控制器操作中添加了一些日志,以查看对象内发生了什么:
def save() {
JSON.use('deep')
def team = new Team(request.JSON)
println "errors? " + team.getErrors()
println "team: " + team.toString()
println ((team as JSON).toString())
respond team
}
产生以下输出:
errors? grails.validation.ValidationErrors: 0 errors
team: team.test.Team : (unsaved)
{
"class":"team.test.Team",
"id":null,
"name":"team a",
"players":[
{
"class":"team.test.Player",
"id":null,
"firstName":"john",
"lastName":"doe",
"position":"center",
"team":null
}
]
}
我的JSON有问题吗?为什么我会收到此错误?是因为球员参考在球员对象中是空的吗?为什么团队在玩家对象中引用null?
出于好奇,我从Player类中删除了belongsTo属性,事情按预期工作......
{"class":"team.test.Team","id":2,"name":"team a","players":[{"class":"team.test.Player","id":2}]}
为什么它在双向情况下不起作用?在表关系,数据绑定等方面,双向和单向的不同含义是什么?我理解在一对多关系中使用belongsTo与否时的不同级联策略,但两者都应该级联保存我认为。
我知道我问了很多问题。我需要帮助才能完成这项工作,但也希望了解原则上发生了什么。非常感谢帮助。
编辑: 根据@ th3morg的建议我尝试手动构建Team和Player模型并通过Team.addToPlayers()将它们关联起来。
def save() {
JSON.use('deep')
def teamData = request.JSON
def team = new Team()
team.name = teamData.name
teamData.players.each { playerData ->
def player = new Player(playerData)
team.addToPlayers(player)
}
team.save()
println ((team as JSON).toString())
respond team
}
产生
{"class":"team.test.Team","id":3,"name":"team a","players":[{"class":"team.test.Player","id":3,"firstName":"john","lastName":"doe","position":"center","team":{"_ref":"../..","class":"team.test.Team"}}]}
这似乎有效,但它冗长而脆弱。有没有办法通过自动绑定或通过其他传统魔法获得相同的结果?
答案 0 :(得分:1)
您可以使用团队域中的beforeSave()函数解决此问题,您可以在其中迭代玩家并保存每个玩家。这应该避免hibernate尝试保存瞬态播放器对象,也可以防止你自己在控制器中编写代码,这有点奇怪。
此外,使用GSON,您可能可以避免hibernate想要的任何addTo关联。
答案 1 :(得分:1)
我认为在你的情况下,原因很简单 - 你需要在保存前明确调用team.addToPlayers(播放器)。我猜测使用JSON构造函数实例化一个团队等同于Map构造函数,这基本上等同于执行以下操作:
def team = new Team()
team.players = [new Player(), new Player()]
team.save()
即。直接设置播放器,而不是调用addToPlayers。 如果您在测试应用中运行此操作,您将获得与您的版本中收到的完全相同的异常。这是因为(正如您所认识的那样),每个玩家都没有对团队集的反向引用(这是team.addToPlayers()所做的)。这(奇怪地)仍然保存播放器对象,因为Hibernate会话在控制器完成时刷新,保存任何未保存的实例。
如果从Player类中删除belongsTo属性实际修复了问题,我会感到惊讶 - 它是否实际上保持了TeamPlayers关系?或者它只是看起来就像它有效,因为你正在使用JSON对象进行响应,你有效地手动设置了球员属性?
我意识到这已经超过一年了,但是我评论的原因是为其他人留下一个注释:有一个(可能是相关的)已知的Grails错误,它会抛出多个级联关系的相同异常(例如A - > hasMany - > B hasMany - > C)其中子/孙有一个beforeInsert()或beforeUpdate()方法。这可以在Grails 3.x中修复,但看起来不会在2.x中修复。 - 请参阅here和here。