Grails Tip # 1: Transient Values


Not Null Property References A Null Or Transient Value

Introduction

I recently started working on Grails applications and as expected, I’ve seen the same problems come up over and over again. So, I thought it would be a good idea to accumulate the common (and some uncommon) problems we”ve seen and write about them. The domain objects and code used in the posts are purpose built examples simulating the actual problems that we’ve seen developing with Grails.

The goal is not to explain the full underlying details of GORM and Hibernate, but instead, show examples of what caused issues for me and hopefully can save you time in solving your problems. Here we go with tip number 1:

org.hibernate.PropertyValueException: not-null property references a null or transient value

Example 1

The Domain

Say we are managing a store with shelves full of items. Whenever an item on a shelf is empty, we need to create a restock order form. Lets represent this as 3 domain classes: Shelf, Item, RestockOrder

The Code

A restock order is created when an existing item on a shelf runs out, BUT, a order is also created for brand new items. The client has requested that when creating new items, restock orders can also be created.

The Shelf domain object:

		package com.ex
		
		class Shelf {
			int number
			static hasMany = [items:Item]
		}
	

The Item domain object:

		package com.ex
		
		class Item {
			String name
			String description
			double price
		}
	

The RestockOrder domain object:

		package com.ex
		
		class RestockOrder {	
			Item item
			Shelf shelf
		}
	

A Shelf has a list of items and is represented by the hasMnay relationship to Items. A restock order needs an Item, it just doesn’t make sense without one. But an Item does not belong to a restock order. Thus, we do not have a belongsTo RestockOrder relationship on Item. If a restock order is deleted, we certainly do not want to delete the Item record.

The Problem

Somewhere in our RestockController, we have the following code. It creates an instance of a RestockOrder and Item. It assigns the item to the restock order.

		def createOrder = {
				.
				.
				.
				RestockOrder ord = new RestockOrder()
				ord.shelf = currentShelf
				ord.item =  new Item(name:name, description:desc, price:price)
				ord.save(failOnError:true)
			}
		}
	

When the RestockOrder is saved, we get the following error:

          not-null property references a null or transient value: com.ex.RestockOrder.item

and the only way to resolve it is to create AND save an instance of Item and then assign it to RestockOrder. This might be confusing because, you can create and assign an instance of Item to a Shelf, like the following:

			Shelf shelf = new Shelf(number:1)
			shelf.addToItems(new Item(name:name, description:desc, price:price))
			shelf.save(failOnError:true)
		

without any error.

The Solution

A transient object is an object that has been instantiated but is not yet associated to the Hibernate session. When you associate this object to a property of a object that is associated to the session and try to save, Hibernate will complain and give you the
“Not Null Property References A Null Or Transient Value error”

This problem has to do with how GROM (Hibernate) cascades saves. Saves are automatically cascaded from parent to child when there is a hasMany relationship. This is why you can add a new Item to Shelf and save the shelf without explicitly saving the Item first. The save cascades from Shelf to Item via the hasMany relationship.

With RestockOrder, there is no such relationship and Hibernate will not do the cascading automatically. You have two solutions:

1 ) Explicitly save the Item before assigning.

		Item itm = new Item(name:name, description:desc, price:price)
		itm.save(failOnError:true)
		
		RestockOrder ord = new RestockOrder()
		ord.shelf = currentShelf
		ord.item =  itm
		ord.save(failOnError:true)
	

2) Configure the cascading behavior for Item in RestockOrder.

Here we set a save-update cascading behaviour.

		package com.ex
		
		class RestockOrder {	
			Item item
			Shelf shelf
		}
		
		static mapping = {
			item cascade:'save-update'
		}
	

Conclusion

Though this error is relatively simple (especially for this example) problem, it is very problem and can be difficult to track down as your domain model grows. Thus it is extremely helpful to have a understanding when these errors occur so you know what to look for in your application.


Example 2


The Domain

In this example, we’ll use the same domain as in Example 1. The domain objects are slightly diffrent however.

The Code


We’ll use the same domain objects as Example 1, but with slight modifications:
The Shelf domain object:

		package com.ex
		
		class Shelf {
			int number
			static hasMany = [items:Item, restockOrders:RestockOrder]
		}
	

The Item domain object:

		package com.ex
		
		class Item {
			String name
			String description
			double price
		}
	

The RestockOrder domain object:

		package com.ex
		
		Item item
		static belongsTo =[shelf:Shelf]
		static mapping = {
			item cascade:'save-update'
		}	
	

The Problem


We need to allow the user to create a new shelf but during this process we also allow them to assign restock orders to the newly created shelf. To do this, we create a new RestockOrder domain object and assign it’s parent as the newly created shelf.

Somewhere in a controller we have the following code:

		RestockOrder ord = new RestockOrder()
		ord.shelf = newShelf
		ord.item =  new Item(name:name, description:desc, price:price)
		
		newShelf.save(failOnError:true)
	

The problem is, when you run it, you get the “org.hibernate.PropertyValueException” error. This is because at the time of saving the RestockOrder, the instance of Shelf is still transient.

You get a similar exception if you try and add to the list of RestockOrder’s within a Shelf by adding directly to the Shelf’s restockOrders list:

		def currentShelf = Shelf.findByNumber(1)
		RestockOrder ord = new RestockOrder()
		ord.item =  Item.findByName("a")
		currentShelf.restockOrders.add(ord)
		currentShelf.save(failOnError:true)
	

instead of using Shelf’s addToRestockOrder() method. By using native Collection methods to add to the list, Grails will not automatically set the parent on the child entity.

The Solution

To resolve this, instead of assigning the parent of a child, you can use the addTo* method on the parent to add the Item as a child of the parent.

When you add the hasMany relationship, Grails provides an addTo* method. In the example above, you will have a addToRestockOrder() method which accepts a RestockOrder instance. This relationship automatically cascades saves from the parent to child.

  • You do not need to call save on the child.
  • It also updates the child’s parent reference.

Conclusion

In this example, I showed how you can use the addTo* method to avoid some common Grails errors, but as with all other programming problems, there are always several ways to solve the issue.

So, hopefully after reading this article, you have a slightly better understanding why these errors appeared in your code by looking at how it was caused on ours. I will continue to update this article if I find any other notable or interesting causes to this particular error.

References

Leave a Reply

Your email address will not be published. Required fields are marked *