apache / apache/grails-core

Reusing a persisted entity by using just its Id to persist the relationship to another Entity causes an exception

Open
#11,112 8 comments 0 reactions 0 assignees View on GitHub
Dominant language
Groovy
Stars
2.9k
Forks
975
Avg merge
1d 22h
Merged PRs (30d)
92

Description

### Steps to Reproduce

1. Create a RESTful endpoint that receives a JSON object. The method in the action of the controller that receives the JSON has an argument that is of a class that has the same properties as the JSON received
2. Create a Service that persists the received object
3. Send a JSON that has a property which is an object that is in a relationship with the main class. For example, send a JSON object that represents a many-to-one between RouteType to Route, so that a RouteType has many routes. The RouteType object, in the JSON, has just an id property used to stablish the relationship between a new Route and a previously RouteType that was persisted by way of a cascade save in a previously persisted RouteType

### Expected Behaviour

The main object of the JSON should be persisted, along with establishing the relationship between the main object and the related object that is embedded in the JSON, using just the id of the object that was persisted in a previous save cascade

### Actual Behaviour

The whole object graph is persisted when you add all the properties of the embedded object. You can then use the id of the embedded object that was previously persisted in a new JSON object but, you can do it only once. If you do it more than one time, an exception is thrown saying that the Instance is in an invalid state.

### Environment Information

- **Operating System**: Debian Stretch
- **Grails Version:** 3.3.8
- **JDK Version:** 8

My intention with the Grails restful services I am implementing is to minimize the amount of data that I put on the wire. My goal is to not send entire object graphs in order to persist or query information, if I can afford to do so.

Having this in mind, let's go back to the Route object. I have established a many-to-one relationship from the Route entity to a RouteType entity. Let's imagine that this RouteType object has about 100 properties. So, what I do, is that the very first time, I decide to construct this JSON object:

```
{
"id": null,
"routeName": "Los Albinos",
"routeType": {
"description": "Most beautiful route in the world",
.
.
.
.
.
}
}
```

The dots represent the 99 other properties.

Let's say I send this JSON to my restful endpoint, it gets persisted, and the RouteType object gets one (1) as its ID in the database. Now, I would also like to be able to re-use this same RouteType in another Route. So I send this time another Route JSON to my endpoint - one like this:

```
{
"id": null,
"routeName": "The Ruskies",
"routeType": {
"id": 1
}
}
```

With this, I do not have to send every property of the RouteType, just its ID, so that when it reaches my restful endpoint, GORM can associate this new Route with the already existing RouteType.

I have created an scenario like this, though at a much lesser scale, and the issue is this: the first time, the entire object graph is persisted and, after both objects of the relation are persisted (Route and RouteType), I can successfully use the id, just like in the second JSON I posted (the one that only has the id for the RouteType), to have GORM persist the new Route and make the relation with the RouteType through its id. The problem is that when I do it more than one time, it blows up with an exception. I get "Instance is in an invalid state" message in my stacktrace. After that, it only works if I actually fill all the properties of the RouteType embedded object. I can make new RouteTypes along with the Routes, but I have not yet tested if supplying the id of the persisted RouteType in the json file, along with its other properties, does not result in my server blowing up.

By the way, I am using IntelliJ and the H2 database and Tomcat that comes in the template for the gradle.build file for a new Grails project, if it can throw some light into the matter. I am ulsing Gradle 4.10.2

This is the link to a simple project I have in GitHub that demonstrates the issue: https://github.com/el-duderino5/jsonservice

I have also provided some screenshots of my database, along with the screenshots of the requests and responses from Postman, a program I use to send data to my restful endpoints:

This is a screenshot of my database freshly made after running the application for the first time. Here you can see the tables and the relationship between Route and RouteType through the ROUTE_TYPE_ID column:

![screenshot from 2018-10-12 13-39-14](https://user-images.githubusercontent.com/6808300/46885781-005de500-ce27-11e8-80c4-910f69224476.png)

This screenshot of my database shows how I have successfully persisted two routes with their route types:

![screenshot from 2018-10-12 13-40-42](https://user-images.githubusercontent.com/6808300/46886170-0f916280-ce28-11e8-8195-c6403208fb54.png)

![screenshot from 2018-10-12 13-39-54](https://user-images.githubusercontent.com/6808300/46886262-626b1a00-ce28-11e8-88f4-4f5db126a7a5.png)

Both have the same route type, as you can see. I forgot to take a screenshot of the first POST request, in which I sent the description of the route type. Nonetheless, you can see that it was correctly persisted in the database.

This picture shows how I managed to persist the second route, and associate the route type that I persisted when sending the first route, just using the id. See how I receive the whole object graph in the response:

![screenshot from 2018-10-12 13-40-33](https://user-images.githubusercontent.com/6808300/46886394-cee61900-ce28-11e8-85bd-00d7c36b859f.png)

The following screenshot show how a third route, with the same route type fails when I send it to my restful endpoint:

![screenshot from 2018-10-12 13-41-32](https://user-images.githubusercontent.com/6808300/46886598-5b90d700-ce29-11e8-8e45-be78197a4898.png)

Notice how it fails, getting a 500 response status. Here is the stacktrace of the exception:
```

|Running application...
Grails application running at http://localhost:8080 in environment: development
2018-10-12 13:41:27.896 ERROR --- [io-8080-exec-10] o.g.web.errors.GrailsExceptionResolver : HibernateException occurred when processing request: [POST] /routes/registerroute
instance was not in a valid state. 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:967)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:901)
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:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
Caused by: org.hibernate.HibernateException: instance was not in a valid state
at org.hibernate.engine.internal.AbstractEntityEntry.isReadOnly(AbstractEntityEntry.java:384)
at org.hibernate.engine.internal.StatefulPersistenceContext.isReadOnly(StatefulPersistenceContext.java:1349)
at org.hibernate.engine.internal.StatefulPersistenceContext.setReadOnly(StatefulPersistenceContext.java:1359)
at org.hibernate.internal.SessionImpl.setReadOnly(SessionImpl.java:2139)
at org.grails.orm.hibernate.cfg.GrailsHibernateUtil.setObjectToReadyOnly(GrailsHibernateUtil.java:309)
at org.grails.orm.hibernate.HibernateGormInstanceApi.setObjectToReadOnly(HibernateGormInstanceApi.groovy:159)
at org.grails.orm.hibernate.AbstractHibernateGormInstanceApi.handleValidationError(AbstractHibernateGormInstanceApi.groovy:397)
at org.grails.orm.hibernate.AbstractHibernateGormInstanceApi.save(AbstractHibernateGormInstanceApi.groovy:132)
at org.grails.datastore.gorm.GormEntity$Trait$Helper.save(GormEntity.groovy:151)
at jsonservice.RouteService.$tt__registerNewRoute(RouteService.groovy:9)
at grails.gorm.transactions.GrailsTransactionTemplate$2.doInTransaction(GrailsTransactionTemplate.groovy:94)
at org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate.java:133)
at grails.gorm.transactions.GrailsTransactionTemplate.execute(GrailsTransactionTemplate.groovy:91)
at jsonservice.RouteController.registerRoute(RouteController.groovy:12)
... 14 common frames omitted
```

Contributor guide

Open the contributing guide

Research direction

Use the linked jsonservice reproduction and start with RouteService.groovy line 9 and RouteController.groovy line 12, then follow the save path through the Hibernate GORM classes shown in the stacktrace. Reproduce repeated POST requests that reference the same RouteType by ID; done means each new Route persists and maintains the relationship without the invalid-state exception.

Written by the indexing model from the issue text.

Assessment

Tech stack
groovy
Domain
api, backend, database
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.