Basic problem: I’ve got a many to many relationship (Stores, Products) with a lookup entity named ProductOffering. In the situation where I have a persistent StoreLocation and try to attach transient Products, hibernate complains EVEN THOUGH I DON’T WANT TO SAVE YET.
Questions:
- Why is grails/gorm/Hibernate trying to save my entity?
- Is there a better way to approach this problem?
My domain classes look like this:
class StoreLocation {
String name
List offerings
static hasMany = [offerings:ProductOffering]
static constraints = {
}
}
class Product {
String name
static hasMany = [offeredBy:ProductOffering]
static constraints = {
name(unique:true)
offeredBy(nullable:true)
}
}
class ProductOffering {
static belongsTo = [product:Product, store:StoreLocation]
static constraints = {
}
}
And my StoreLocationController “edit” action looks like this.
def edit = {
//get the store we want to edit
def storeLocation = StoreLocation.get(params.id)
//create a transient product and add it to the store
// by attaching it to a transient productOffering
def product = new Product()
def offering = new ProductOffering()
offering.product = product
storeLocation.addToOfferings(offering)
//render the edit page
render(view:"edit", model:[storeLocation:storeLocation])
}
Assume I’ve got a storeLocation with ID=1. I go to edit the StoreLocation
localhost:8080/myapp/storeLocation/edit/1
It doesn’t matter what my view looks like. It could be
hello
I get the following error.
Error 500: not-null property references a null or transient value: ProductOffering.product; nested exception is org.hibernate.PropertyValueException: not-null property references a null or transient value: ProductOffering.product
The stack trace is not much more useful:
org.springframework.dao.DataIntegrityViolationException: not-null property references a null or transient value
Grails registers an OpenSessionInView interceptor around all requests. This ensures that a Hibernate session is open for the duration of each request, and the session is flushed and closed at the end of the request. This has the benefit of avoid lazy loading exceptions.
Since the session is flushed at the end of the request, any dirty persistent instances are pushed to the database, and that’s what’s happening here. You load the
storeLocationinstance and change it, so that change is flushed. You don’t need to save theProductOfferingsince that will be transitively saved when thestoreLocationis flushed, but theProductinstance isn’t, so you get this exception.The simple fix if you don’t want any changes to be persisted is to remove the changed instance from the persistence context – do this by calling
storeLocation.discard()before the render call. See http://grails.org/doc/latest/ref/Domain%20Classes/discard.html for information on thediscardmethod.