2014-11-14 28 views
0

我有以下两种简单的模型创造了一个Grails应用2.4.3双向一个一对多的关系:Grails的NullPointerException异常节约双向one-to-many关系模型

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 

因为这是一个相当模糊的消息,我把一个try/catch围绕team.save()调用TeamController并打印出堆栈跟踪:

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?

出于好奇,我删除从播放器类的属于关联性,工作的事情如预期...

{"class":"team.test.Team","id":2,"name":"team a","players":[{"class":"team.test.Player","id":2}]} 

为什么它不能在双向的情况下工作?在表格关系,数据绑定等方面,双向和单向的不同含义是什么?我理解使用belongsTo或不在一对多关系中的不同级联策略,但都应该级联保存我认为。

我知道我在问很多问题。我需要帮助完成这项工作,但也想了解原则上发生了什么。非常感谢帮助。

编辑: per @ th3morg的建议我尝试通过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

您的播放器对象尚未保存,所以我认为您可能会得到一个暂时的异常,但通过嵌套的json创建播放器仍然可能是问题的一部分。作为测试,请先尝试先明确创建玩家,然后创建团队并添加玩家。 – th3morg 2014-11-14 17:40:36

+0

谢谢。根据你的建议更新,这是可行的,但不是一个理想的方法 – speakingcode 2014-11-14 20:00:54

回答

1

我觉得你的情况,原因很简单 - 你需要显式调用team.addToPlayers(播放器),然后再储存。我猜测,实例化一个团队的JSON构造相当于地图的构造,这基本上等同于执行以下操作:

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中修复。 - 见herehere

+0

确实晚了,但谢谢你的好回答。不再在这个项目上工作,不记得我是如何处理这个问题的。我相信你是正确的,虽然在提供json做一个地图,然后使用地图构造函数有点神奇 - 看起来像,因为他们支持双向关系,也许grails应该检查,并在这种情况下通过调用addTo幕后。 – speakingcode 2016-02-18 20:03:41

1

您可以使用您的团队域中的beforeSave()函数来解决此问题,您可以遍历玩家并保存每个玩家。这应该避免冬眠试图保存瞬态播放器对象,也可以防止你在控制器中自己编码,这有点奇怪。

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

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