繁体   English   中英

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

[英]grails NullPointerException saving bi-directional one-to-many relationship model

我有以下两个简单的模型在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类中删除了EmiratesTo属性,一切按预期进行...

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

这似乎可行,但是它很冗长和脆弱。 是否没有办法通过自动绑定或通过其他一些常规魔术来获得相同的结果?

您可以通过Team域中的beforeSave()函数在播放器上进行迭代并保存每个播放器来解决此问题。 这应该避免休眠状态尝试保存瞬态播放器对象,并且还可以避免您必须自己在控制器中对其进行编码,这有点奇怪。

另外,使用GSON,您可能可以避免休眠所需的任何addTo关联。

https://github.com/robfletcher/grails-gson#readme

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

def team = new Team()
team.players = [new Player(), new Player()]
team.save()

即直接设置播放器,而不是调用addToPlayers。 如果在测试应用程序中运行此程序,则将获得与版本中收到的异常完全相同的异常。 这是因为(如您所知),每个玩家都没有对Team集的反向引用(即team.addToPlayers()所做的事情)。 这(奇怪地)仍然保存了播放器对象,因为控制器完成时休眠会话会刷新,从而保存所有未保存的实例。

如果从Player类中删除belongsTo属性确实可以解决问题,我会感到惊讶-它是否确实保持了TeamPlayers关系? 还是因为您正在使用有效地手动设置players属性的JSON对象进行响应而使它看起来像正常工作?

我意识到这已经有一年多了,但是我要发表评论的原因是要给其他人留下笔记:存在一个(可能相关的)已知的Grails错误,该错误在多个级联关系中引发相同的异常(例如A-> hasMany-> B hasMany-> C),其中子代/孙代具有beforeInsert()或beforeUpdate()方法。 可能在Grails 3.x中修复了该问题,但看起来在2.x中不会修复。 -看到这里这里

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM