Json grails NullPointerException保存双向一对多关系模型

Json grails NullPointerException保存双向一对多关系模型,json,hibernate,grails,data-binding,one-to-many,Json,Hibernate,Grails,Data Binding,One To Many,我有以下两个简单的模型在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] s

我有以下两个简单的模型在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有什么问题吗?为什么会出现这个错误?是因为玩家对象中的团队引用为空吗?为什么玩家对象中的团队引用为空

出于好奇,我从Player类中删除了belongsTo属性,一切正常

{"class":"team.test.Team","id":2,"name":"team a","players":[{"class":"team.test.Player","id":2}]}
为什么它在双向情况下不起作用?双向和单向在表关系、数据绑定等方面有什么不同的含义?我理解在一对多关系中使用belongsTo与否时的不同级联策略,但我认为两者都应该级联保存

我知道我问了很多问题。我需要帮助使这项工作,但也希望了解正在发生的原则。非常感谢你的帮助

编辑: 根据@th3morg的建议,我尝试手动构建团队和玩家模型,并通过Team.addToPlayers()将它们关联起来

产生:

{"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"}}]}

这似乎是可行的,但它冗长而脆弱。没有办法通过自动绑定或其他一些常规魔法来实现相同的结果吗?

您可以在团队域中使用beforeSave()函数来解决这个问题,在该函数中迭代玩家并保存每个玩家。这应该可以避免hibernate试图保存暂时的播放器对象,也可以避免您自己在控制器中对其进行编码,这有点奇怪

此外,使用GSON,您可能能够避免hibernate想要的任何addTo关联


我认为在您的情况下,原因很简单-在保存之前,您需要显式地调用team.addToPlayers(玩家)。我猜用JSON构造函数实例化团队等同于Map构造函数,这基本上等同于执行以下操作:

def team = new Team()
team.players = [new Player(), new Player()]
team.save()
i、 直接正确设置玩家,而不是调用addToPlayers。 如果您在测试应用程序中运行此操作,您将获得与您在版本中收到的完全相同的异常。这是因为(正如您所认识到的),每个玩家都没有对团队集的反向引用(这是Team.addToPlayers()所做的)。奇怪的是,这仍然会保存播放器对象,因为当控制器完成时,Hibernate会话会刷新,从而保存所有未保存的实例

如果从Player类中删除belongsTo属性实际上解决了这个问题,我会感到惊讶——它真的保持了TeamPlayers的关系吗?或者只是因为您使用了一个JSON对象进行响应,而您已经有效地手动设置了players属性,所以它看起来很有效


我意识到这已经过去一年了,但我之所以要发表评论是为了给其他人留下一个注意:有一个(可能相关的)已知的Grails bug,它在多个级联关系(例如a->hasMany->B hasMany->C)中抛出相同的异常,其中子/孙具有beforeInsert()或beforeUpdate()方法。这可能在Grails3.x中得到修复,但在2.x中似乎不会得到修复见和

您的player对象尚未保存,因此我认为您可能会得到一个暂时的异常,但通过嵌套的json创建player仍然可能是问题的一部分。作为测试,首先尝试显式创建玩家,然后创建团队并添加玩家。谢谢。根据你的建议进行更新,虽然有效,但不是一种理想的方法,但还是要感谢你的回答。不再从事那个项目,也不记得我是如何处理这个问题的。我相信你是对的,尽管提供json确实有点神奇,可以制作一个映射,然后使用映射构造函数——看起来,因为它们支持双向关系,也许grails应该检查一下,在这种情况下,在幕后调用addTo。
def team = new Team()
team.players = [new Player(), new Player()]
team.save()