grails-data-mapping icon indicating copy to clipboard operation
grails-data-mapping copied to clipboard

NullPointerException when validating associated domain

Open bysouleater opened this issue 8 years ago • 12 comments

Steps to Reproduce

Using Grails 3.2.8 1.- Create domain class A

@Resource(readOnly = false, formats = ['json', 'xml'], uri = "/a")
class A {
    B b
}

2.- Create domain class B

class B {
    String field1
    String field2
}

3.- Make POST call ignoring 1 property from associated class B on purpose

POST http://localhost:8080/a
{
    "b": {
        "field1": "test"
    }
}

Expected Behaviour

400 Bad Request
{
     "message": "Property field2 can't be null"
}

Actual Behaviour

500 Internal Server Error
2017-04-25 15:42:43.003 ERROR --- [nio-8080-exec-2] o.g.web.errors.GrailsExceptionResolver   : NullPointerException occurred when processing request: [POST] /a
Stacktrace follows:

java.lang.reflect.InvocationTargetException: null
	at org.grails.core.DefaultGrailsControllerClass$ReflectionInvoker.invoke(DefaultGrailsControllerClass.java:211)
	at org.grails.core.DefaultGrailsControllerClass.invoke(DefaultGrailsControllerClass.java:188)
	at org.grails.web.mapping.mvc.UrlMappingsInfoHandlerAdapter.handle(UrlMappingsInfoHandlerAdapter.groovy:90)
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:963)
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:897)
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)
	at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:872)
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846)
	at org.springframework.boot.web.filter.ApplicationContextHeaderFilter.doFilterInternal(ApplicationContextHeaderFilter.java:55)
	at org.grails.web.servlet.mvc.GrailsWebRequestFilter.doFilterInternal(GrailsWebRequestFilter.java:77)
	at org.grails.web.filters.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:67)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.NullPointerException: null
	at org.hibernate.engine.internal.StatefulPersistenceContext.addEntity(StatefulPersistenceContext.java:468)
	at org.hibernate.action.internal.AbstractEntityInsertAction.makeEntityManaged(AbstractEntityInsertAction.java:126)
	at org.hibernate.engine.spi.ActionQueue.addResolvedEntityInsertAction(ActionQueue.java:279)
	at org.hibernate.engine.spi.ActionQueue.addInsertAction(ActionQueue.java:254)
	at org.hibernate.engine.spi.ActionQueue.addAction(ActionQueue.java:299)
	at org.hibernate.event.internal.AbstractSaveEventListener.addInsertAction(AbstractSaveEventListener.java:318)
	at org.hibernate.event.internal.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:275)
	at org.hibernate.event.internal.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:182)
	at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:113)
	at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.saveWithGeneratedOrRequestedId(DefaultSaveOrUpdateEventListener.java:192)
	at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.java:177)
	at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.performSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:97)
	at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:73)
	at org.grails.orm.hibernate.support.ClosureEventTriggeringInterceptor.onSaveOrUpdate(ClosureEventTriggeringInterceptor.java:81)
	at org.hibernate.internal.SessionImpl.fireSaveOrUpdate(SessionImpl.java:651)
	at org.hibernate.internal.SessionImpl.saveOrUpdate(SessionImpl.java:643)
	at org.hibernate.engine.spi.CascadingActions$5.cascade(CascadingActions.java:218)
	at org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:391)
	at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:316)
	at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:155)
	at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:104)
	at org.hibernate.event.internal.AbstractSaveEventListener.cascadeBeforeSave(AbstractSaveEventListener.java:414)
	at org.hibernate.event.internal.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:252)
	at org.hibernate.event.internal.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:182)
	at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:113)
	at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.saveWithGeneratedOrRequestedId(DefaultSaveOrUpdateEventListener.java:192)
	at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.java:177)
	at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.performSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:97)
	at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:73)
	at org.grails.orm.hibernate.support.ClosureEventTriggeringInterceptor.onSaveOrUpdate(ClosureEventTriggeringInterceptor.java:81)
	at org.hibernate.internal.SessionImpl.fireSaveOrUpdate(SessionImpl.java:651)
	at org.hibernate.internal.SessionImpl.saveOrUpdate(SessionImpl.java:643)
	at org.hibernate.internal.SessionImpl.saveOrUpdate(SessionImpl.java:638)
	at org.grails.orm.hibernate.AbstractHibernateGormInstanceApi$_performSave_closure3.doCall(AbstractHibernateGormInstanceApi.groovy:242)
	at org.grails.orm.hibernate.GrailsHibernateTemplate.doExecute(GrailsHibernateTemplate.java:286)
	at org.grails.orm.hibernate.GrailsHibernateTemplate.execute(GrailsHibernateTemplate.java:230)
	at org.grails.orm.hibernate.GrailsHibernateTemplate.execute(GrailsHibernateTemplate.java:116)
	at org.grails.orm.hibernate.AbstractHibernateGormInstanceApi.performSave(AbstractHibernateGormInstanceApi.groovy:241)
	at org.grails.orm.hibernate.AbstractHibernateGormInstanceApi.save(AbstractHibernateGormInstanceApi.groovy:158)
	at org.grails.datastore.gorm.GormEntity$Trait$Helper.save(GormEntity.groovy:151)
	at grails.rest.RestfulController.saveResource(RestfulController.groovy:297)
	at grails.rest.RestfulController.$tt__save(RestfulController.groovy:103)
	at grails.transaction.GrailsTransactionTemplate$2.doInTransaction(GrailsTransactionTemplate.groovy:96)
	at org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate.java:133)
	at grails.transaction.GrailsTransactionTemplate.execute(GrailsTransactionTemplate.groovy:93)
	... 14 common frames omitted

Environment Information

  • Operating System: Mac OSX Sierra
  • Grails Version: 3.2.8
  • JDK Version: 1.8

Example Application

For some reason, null validation of a missing field on the request body make this fail. If instead, you declare a property Double field3 and in the request body you send e.g. "field3": "something", the correct 400 Bad Request is triggered. Only fails when you omit one field of the sub-domain.

bysouleater avatar Apr 25 '17 19:04 bysouleater

I'm experiencing kind of the same NullPointer Issue on another scenario:

Steps to Reproduce

1.- Create domain class A with a beforeInsert method

@Resource(readOnly = false, formats = ['json', 'xml'], uri = "/a")
class A {
    B b

    def beforeInsert() {
        return false
    }
}

2.- Create domain class B

class B {
    String field1
    String field2
}

3.- Make POST call

POST http://localhost:8080/a
{
    "b": {
        "field1": "test",
        "field2": "test"
    }
}

Expected Behaviour

400 Bad Request
{
     "message": "Some error message"
}

Actual Behaviour

java.lang.reflect.InvocationTargetException: null
	at org.grails.core.DefaultGrailsControllerClass$ReflectionInvoker.invoke(DefaultGrailsControllerClass.java:211)
	at org.grails.core.DefaultGrailsControllerClass.invoke(DefaultGrailsControllerClass.java:188)
	at org.grails.web.mapping.mvc.UrlMappingsInfoHandlerAdapter.handle(UrlMappingsInfoHandlerAdapter.groovy:90)
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:963)
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:897)
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)
	at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:872)
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846)
	at org.springframework.boot.web.filter.ApplicationContextHeaderFilter.doFilterInternal(ApplicationContextHeaderFilter.java:55)
	at org.grails.web.servlet.mvc.GrailsWebRequestFilter.doFilterInternal(GrailsWebRequestFilter.java:77)
	at org.grails.web.filters.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:67)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.NullPointerException: null
	at org.hibernate.engine.internal.StatefulPersistenceContext.addEntity(StatefulPersistenceContext.java:468)
	at org.hibernate.action.internal.AbstractEntityInsertAction.makeEntityManaged(AbstractEntityInsertAction.java:126)
	at org.hibernate.engine.spi.ActionQueue.addResolvedEntityInsertAction(ActionQueue.java:279)
	at org.hibernate.engine.spi.ActionQueue.addInsertAction(ActionQueue.java:254)
	at org.hibernate.engine.spi.ActionQueue.addAction(ActionQueue.java:299)
	at org.hibernate.event.internal.AbstractSaveEventListener.addInsertAction(AbstractSaveEventListener.java:318)
	at org.hibernate.event.internal.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:275)
	at org.hibernate.event.internal.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:182)
	at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:113)
	at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.saveWithGeneratedOrRequestedId(DefaultSaveOrUpdateEventListener.java:192)
	at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.java:177)
	at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.performSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:97)
	at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:73)
	at org.grails.orm.hibernate.support.ClosureEventTriggeringInterceptor.onSaveOrUpdate(ClosureEventTriggeringInterceptor.java:81)
	at org.hibernate.internal.SessionImpl.fireSaveOrUpdate(SessionImpl.java:651)
	at org.hibernate.internal.SessionImpl.saveOrUpdate(SessionImpl.java:643)
	at org.hibernate.engine.spi.CascadingActions$5.cascade(CascadingActions.java:218)
	at org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:391)
	at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:316)
	at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:155)
	at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:104)
	at org.hibernate.event.internal.AbstractSaveEventListener.cascadeBeforeSave(AbstractSaveEventListener.java:414)
	at org.hibernate.event.internal.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:252)
	at org.hibernate.event.internal.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:182)
	at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:113)
	at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.saveWithGeneratedOrRequestedId(DefaultSaveOrUpdateEventListener.java:192)
	at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.java:177)
	at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.performSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:97)
	at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:73)
	at org.grails.orm.hibernate.support.ClosureEventTriggeringInterceptor.onSaveOrUpdate(ClosureEventTriggeringInterceptor.java:81)
	at org.hibernate.internal.SessionImpl.fireSaveOrUpdate(SessionImpl.java:651)
	at org.hibernate.internal.SessionImpl.saveOrUpdate(SessionImpl.java:643)
	at org.hibernate.internal.SessionImpl.saveOrUpdate(SessionImpl.java:638)
	at org.grails.orm.hibernate.AbstractHibernateGormInstanceApi$_performSave_closure3.doCall(AbstractHibernateGormInstanceApi.groovy:242)
	at org.grails.orm.hibernate.GrailsHibernateTemplate.doExecute(GrailsHibernateTemplate.java:286)
	at org.grails.orm.hibernate.GrailsHibernateTemplate.execute(GrailsHibernateTemplate.java:230)
	at org.grails.orm.hibernate.GrailsHibernateTemplate.execute(GrailsHibernateTemplate.java:116)
	at org.grails.orm.hibernate.AbstractHibernateGormInstanceApi.performSave(AbstractHibernateGormInstanceApi.groovy:241)
	at org.grails.orm.hibernate.AbstractHibernateGormInstanceApi.save(AbstractHibernateGormInstanceApi.groovy:158)
	at org.grails.datastore.gorm.GormEntity$Trait$Helper.save(GormEntity.groovy:151)
	at grails.rest.RestfulController.saveResource(RestfulController.groovy:297)
	at grails.rest.RestfulController.$tt__save(RestfulController.groovy:103)
	at grails.transaction.GrailsTransactionTemplate$2.doInTransaction(GrailsTransactionTemplate.groovy:96)
	at org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate.java:133)
	at grails.transaction.GrailsTransactionTemplate.execute(GrailsTransactionTemplate.groovy:93)
	... 14 common frames omitted

Notes

Seems that in the class org.hibernate.engine.internal.StatefulPersistenceContext inside method public EntityEntry addEntity on line 468, the entityKey object is null and fails when trying to execute entityKey.getIdentifier(). This only happens when procesing the related class B from my example with a variable status with value MANAGED.

bysouleater avatar May 02 '17 15:05 bysouleater

This comes up now and again. Generally Hibernate doesn't like it if you use generated identifiers and evict insert operations. That is why it is easily reproducible by returning false from beforeInsert(). Unfortunately there is nothing we can really do about this on the GORM side since this is a Hibernate bug.

You will need to report the issue to the Hibernate team https://hibernate.atlassian.net/projects/HHH

In terms of immediate solutions to the problem you have a few options:

  1. Use assigned or non-generated identifiers
  2. Ensure validation cascades to the associated object this will ensure the object is not saved to hibernate in the first place. You can do this by changing B b to static hasOne = [b:B] (note this won't resolve your second example)
  3. Enable failOnError globally to obtain a validation exception instead of a NPE by setting grails.gorm.failOnError=true then write a controller to handle this exception

If you do report the issue to the Hibernate team please do reference the reported issue here. Thanks.

graemerocher avatar May 03 '17 15:05 graemerocher

https://hibernate.atlassian.net/browse/HHH-11721

bysouleater avatar May 05 '17 12:05 bysouleater

@bysouleater I added a comment on that issue

graemerocher avatar May 05 '17 12:05 graemerocher

@graemerocher thank you very much!

bysouleater avatar May 05 '17 12:05 bysouleater

I'm working on a workaround the beforeInsert issue moving my validation somewhere else, but I'm still experiencing the initial issue. I'm debugging a little more, and my concern now is the following:

1.- Create domain class A

@Resource(readOnly = false, formats = ['json', 'xml'], uri = "/a")
class A {
    B b
}

2.- Create domain class B

class B {
    Long field1
    String field2
}

3.- Make POST call ignoring 1 property from associated class B on purpose

POST http://localhost:8080/a { "b": { "field2": "test" } }

Expected Behaviour

Have a validation error "field1 can't be null"

Actual Behaviour

instance.validate()
// or
instance.validate([deepValidate: true])
// and then
return instance.hasErrors()

Always returns false, and throws the NPE we've been discussing when trying to insert.

Shouldn't it fail before trying to insert the related object?

bysouleater avatar May 05 '17 15:05 bysouleater

See my comment here https://github.com/grails/grails-core/issues/10604#issuecomment-298943022

Validation only cascade from the owning side of a relationship currently

graemerocher avatar May 05 '17 15:05 graemerocher

The issue with using static hasOne = [b:B] is that I can't re-use class B in another domain, because now it's bound only to class A (as the documentation says, A class id column will be placed in B class table). I'm I right?

bysouleater avatar May 05 '17 15:05 bysouleater

I've just tested it with hasOne and I have the same result (when omitting one field from the related entity on the POST body)

java.lang.reflect.InvocationTargetException: null
	at org.grails.core.DefaultGrailsControllerClass$ReflectionInvoker.invoke(DefaultGrailsControllerClass.java:211)
	at org.grails.core.DefaultGrailsControllerClass.invoke(DefaultGrailsControllerClass.java:188)
	at org.grails.web.mapping.mvc.UrlMappingsInfoHandlerAdapter.handle(UrlMappingsInfoHandlerAdapter.groovy:90)
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:963)
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:897)
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)
	at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:872)
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846)
	at org.springframework.boot.web.filter.ApplicationContextHeaderFilter.doFilterInternal(ApplicationContextHeaderFilter.java:55)
	at org.grails.web.servlet.mvc.GrailsWebRequestFilter.doFilterInternal(GrailsWebRequestFilter.java:77)
	at org.grails.web.filters.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:67)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.NullPointerException: null
	at org.hibernate.engine.internal.StatefulPersistenceContext.addEntity(StatefulPersistenceContext.java:468)
	at org.hibernate.action.internal.AbstractEntityInsertAction.makeEntityManaged(AbstractEntityInsertAction.java:126)
	at org.hibernate.engine.spi.ActionQueue.addResolvedEntityInsertAction(ActionQueue.java:279)
	at org.hibernate.engine.spi.ActionQueue.addInsertAction(ActionQueue.java:254)
	at org.hibernate.engine.spi.ActionQueue.addAction(ActionQueue.java:299)
	at org.hibernate.event.internal.AbstractSaveEventListener.addInsertAction(AbstractSaveEventListener.java:318)
	at org.hibernate.event.internal.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:275)
	at org.hibernate.event.internal.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:182)
	at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:113)
	at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.saveWithGeneratedOrRequestedId(DefaultSaveOrUpdateEventListener.java:192)
	at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.java:177)
	at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.performSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:97)
	at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:73)
	at org.grails.orm.hibernate.support.ClosureEventTriggeringInterceptor.onSaveOrUpdate(ClosureEventTriggeringInterceptor.java:81)
	at org.hibernate.internal.SessionImpl.fireSaveOrUpdate(SessionImpl.java:651)
	at org.hibernate.internal.SessionImpl.saveOrUpdate(SessionImpl.java:643)
	at org.hibernate.engine.spi.CascadingActions$5.cascade(CascadingActions.java:218)
	at org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:391)
	at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:316)
	at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:155)
	at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:104)
	at org.hibernate.event.internal.AbstractSaveEventListener.cascadeAfterSave(AbstractSaveEventListener.java:445)
	at org.hibernate.event.internal.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:281)
	at org.hibernate.event.internal.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:182)
	at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:113)
	at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.saveWithGeneratedOrRequestedId(DefaultSaveOrUpdateEventListener.java:192)
	at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.java:177)
	at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.performSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:97)
	at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:73)
	at org.grails.orm.hibernate.support.ClosureEventTriggeringInterceptor.onSaveOrUpdate(ClosureEventTriggeringInterceptor.java:81)
	at org.hibernate.internal.SessionImpl.fireSaveOrUpdate(SessionImpl.java:651)
	at org.hibernate.internal.SessionImpl.saveOrUpdate(SessionImpl.java:643)
	at org.hibernate.internal.SessionImpl.saveOrUpdate(SessionImpl.java:638)
	at org.grails.orm.hibernate.AbstractHibernateGormInstanceApi$_performSave_closure3.doCall(AbstractHibernateGormInstanceApi.groovy:242)
	at org.grails.orm.hibernate.GrailsHibernateTemplate.doExecute(GrailsHibernateTemplate.java:286)
	at org.grails.orm.hibernate.GrailsHibernateTemplate.execute(GrailsHibernateTemplate.java:230)
	at org.grails.orm.hibernate.GrailsHibernateTemplate.execute(GrailsHibernateTemplate.java:116)
	at org.grails.orm.hibernate.AbstractHibernateGormInstanceApi.performSave(AbstractHibernateGormInstanceApi.groovy:241)
	at org.grails.orm.hibernate.AbstractHibernateGormInstanceApi.save(AbstractHibernateGormInstanceApi.groovy:158)
	at org.grails.datastore.gorm.GormEntity$Trait$Helper.save(GormEntity.groovy:151)
	at com.oasis.api.OasisRestfulController.saveResource(OasisRestfulController.groovy:164)
	at com.oasis.api.OasisRestfulController.$tt__save(OasisRestfulController.groovy:87)
	at grails.transaction.GrailsTransactionTemplate$2.doInTransaction(GrailsTransactionTemplate.groovy:96)
	at org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate.java:133)
	at grails.transaction.GrailsTransactionTemplate.execute(GrailsTransactionTemplate.groovy:93)
	... 14 common frames omitted

bysouleater avatar May 05 '17 16:05 bysouleater

Hi @graemerocher , I decided to test this again in the new release 3.3.0.M2 and it's nice to see that it doesn't throw any Exception now, Thanks for that!

I'm still struggling to make two scenarios to work. I don't know if the framework is not doing what's expected or my scenarios should be revisited. Any help is appreciated.

Scenario 1:

  • A class A that stores a couple of fields
  • A class B that is composed of an A object and other fields
  • A class C that is also composed of an A object and other fields different than class B.

I'm trying to re-use A so A can't/should not be aware of its parent, also, A should not have a life-cycle of its own.

As I won't be exposing A as a resource, I need that resources B and C automatically create the inner A object and join them.

Scenario 2:

  • A class D that has so many fields
  • A class E to store some of those fields to reduce complexity in class D

In this scenario, class E is only used along with class D as it's parent, so E should not have a life-cycle of its own.

Also, I won't be exposing E as a resource, so I need that resource D is able to automatically create the inner E object and join them.

So far, I tried these examples following :

Example 1:

class A {
    String field1
    String field2
}

class B {
    A a
}

class C {
    A a
}

Class B and C are composed of A. An instance of Class A is created when POSTing for resource B or C The instance of Class A is not removed when DELETing a B or C resource This could potentially work for Scenario 1 if only I knew how to remove A in cascade. This could potentially work for Scenario 2 also if only I knew how to remove A in cascade.

Example 2:

class D {
    E e
}

class E {
    static belongsTo = [d: D]
}

Class D is composed of E and E belongs to parent D. When trying to POST for resource D fails with an error message: "Property [d] of class [class E] can't be null" This doesn't work for Scenario 1 because E knows who it's parent is. This could potentially work for Scenario 2 if only did not return that error.

Example 3:

class D {
    static hasOne = [e: E]
}

class E {
    D d
}

Class D has one E and E is composed of D. When trying to POST for resource D fails with an error message: "Property [d] of class [class E] can't be null" (Same as example 2) This doesn't work for Scenario 1 because E knows who it's parent is. This could potentially work for Scenario 2 if only did not return that error.

Thanks again! and sorry for the long issue post.

bysouleater avatar Jun 16 '17 19:06 bysouleater

I'm using grails 3.3.6 with hibernate 5.2.17.final. I find that if beforeInsert returns false, the dateCreated will throw NPE:

def beforeInsert() {
    return false
}
java.lang.NullPointerException: Cannot get property 'dateCreated' on null object
	at org.grails.datastore.mapping.simple.engine.SimpleMapEntityPersister.getEntryValue(SimpleMapEntityPersister.groovy:233)
	at org.grails.datastore.mapping.engine.NativeEntryEntityPersister.refreshObjectStateFromNativeEntry(NativeEntryEntityPersister.java:411)
	at org.grails.datastore.mapping.engine.NativeEntryEntityPersister.refresh(NativeEntryEntityPersister.java:353)
	at org.grails.datastore.mapping.core.AbstractSession.refresh(AbstractSession.java:625)
	at org.grails.datastore.gorm.GormInstanceApi.refresh_closure3(GormInstanceApi.groovy:107)
	at groovy.lang.Closure.call(Closure.java:418)
	at org.grails.datastore.mapping.core.DatastoreUtils.execute(DatastoreUtils.java:319)
	at org.grails.datastore.gorm.AbstractDatastoreApi.execute(AbstractDatastoreApi.groovy:40)
	at org.grails.datastore.gorm.GormInstanceApi.refresh(GormInstanceApi.groovy:106)
	at org.grails.datastore.gorm.GormEntity$Trait$Helper.refresh(GormEntity.groovy:90)

abcfy2 avatar Jul 19 '18 15:07 abcfy2

i do not think that this issue is related to the dirty-checking? the question is rather if/how we can handle an EntityActionVetoException now that https://hibernate.atlassian.net/browse/HHH-11721 made it into hibernate-5.2.13?

zyro23 avatar Apr 11 '19 09:04 zyro23