I have two domain classes - Person has many Books. If I create a
Person, call save(), then create+add Books to the Person, everything
is persisted to the database. If I create a Person, then create+add
Books, followed by save(), nothing get persisted.
Example code:
class Person {
...
static hasMany = [books: Book]
}
class Book {
...
static belongsTo = [person: Person]
}
Works, saves to database:
def person = new Person(...).save()
def book = new Book(...)
person.addToBooks(book)
Doesn't not work, doesn't save to database:
def person = new Person(...)
def book = new Book(...)
person.addToBooks(book)
person.save()
Why is this?
I'm running this from the Groovy console. I've tried calling ctx.sessionFactory.currentSession.clear() which doesn't help.
Resolved: The Book I was attaching to the Person had errors in it. By calling person.hasErrors() and getErrors() I was able to see that my Book was failing validation. This was being run from the Grails console so there wasn't any validation error messages.
You need to manually flush the hibernate session like this:
person.save(flush:true)
I don't know for sure but I presume it's because save() sets the ID of the Person which would be required to be able to add a Book to it's book collection.
I'd suggest doing some googling on Hibernate collection behaviour, it could be the way Hibernate works or maybe it's a bug in the version of Grails you're using.
Another thing to try is forcing a flush with person.save(flush:true)
Related
I was away from Grails for some time so I tried to create demo rest-api app with some basic domain relationship (one-to-many, many-to-many) and faced some wierd issues.
In short, I have 4 domain classes as follows:
class Publisher {
String name
static hasMany = [books:Book]
}
class Author {
String name
static hasMany = [books: Book]
}
class Category {
String name
static belongsTo = Book
static hasMany = [books:Book]
}
class Book {
String title
Publisher publisher
static hasMany = [categories: Category]
}
I'm trying to insert some demo data within bootrap.groovy (with multiple different approaches) but, data that should go into joint tables is not persisted (empty). For example, even when using cascade-create, 'edge' records are persisted (for example categories created from boook) but, there is no data in join table between two of them):
Bootstrap.groovy with different approaches to insert records:
// Stand-alone category
def science = new Category(name: 'science').save(failOnError: true)
// publishers
def manning = new Publisher(name: 'manning').save(failOnError: true)
def amazon = new Publisher(name: 'amazon').save(failOnError: true)
// Create single author
def johnDoe = new Author(name: 'John Doe').save(failOnError: true)
// Create-cascade from Author-Book-Category with explicid 'new' and 'save' Book
def jackDanields = new Author(name: 'Jack Daniels')
.addToBooks(new Book(title: 'Hate book', publisher: manning).addToCategories(name: 'love').save())
.addToBooks(new Book(title: 'Fiction book', publisher: manning).addToCategories(name: 'fiction').save())
.save(failOnError: true)
// Create-cascade from Author-Book without explicit save of book
def zoran = new Author(name: 'Zoran')
.addToBooks(title: 'First book', publisher: manning)
.addToBooks(title: 'Second book', publisher: manning)
.addToBooks(title: 'Third book', publisher: manning)
.save(failOnError: true)
I tried with both H2 and MariaDb and result is the same.
Full project available at github: https://github.com/zbubric/grails4-rest-sample
So, it there any catch that I missed or it is some known issue/feature?
zbubric
Answering to your question
So, it there any catch that I missed or it is some known
issue/feature?
I totally agree with Olav's comment that you need to wrap all inserts in BootStrap.groovy into a new transaction to fix the issue.
Let me explain why transaction will enable saving of data in join tables.
In BootStrap.groovy by default there is an open hibernate session (with COMMIT flush mode) but there is no active hibernate transaction.
COMMIT flush mode is important here because with this mode the flush of session is only done on commit of transaction. But in your case no session flush will be done afterwards because there is no active transaction in BootStrap.groovy as I sad before. And no session flush means no saving of entities associations that were added by cascade (also checked it on practice with raw sql queries inside a transaction to the database - had data in join tables only after session flush).
To check if there is a session in BootStrap.groovy (and get more details about it) you can reference
Holders.applicationContext.getBean("sessionFactory").getCurrentSession()
in your code.
To check if there is an active transaction in BootStrap.groovy you can add flush: true to any of the save method calls.
For example:
def science = new Category(name: 'science').save(failOnError: true, flush: true)
In this case you should get an exception:
javax.persistence.TransactionRequiredException: no transaction is in progress
It behaves like that because by default in grails 4.0.0 sessions are configured to not allow flushes without an active transaction.
I believe you have your Category>Book relationship wrong as well.
Book has one Category
Category can have oneorMany Books
Thus Book can either have a 'Category cat' field or 'belongsTo'; belongsTo should not be in the same place as the 'hasMany'
Say we have the following two domain classes:
class Book {
static belongsTo = [author: Author]
}
class Author {
static hasMany = [books: Book]
}
No if an Author is initialized with several books and Author.save() is called then the save cascades to Book and both Author and Book instances are saved into db.
However I can't find anywhere in documentation if the mentioned operation will be done transactionally or not.
Any idea?
Any resource to check ?
The answer depends on where the save is done. Is it done in a controller action marked as transactional? Is it in a service which uses transactions by default? Or is it done somewhere else where there is no transaction.
If the save is done somewhere that supports transaction (two examples above) then yes, it will be. Otherwise, no it won't be.
I'm still new to Grails and GORM and I got stumped on this and wasn't able to figure out what I am doing wrong. The intent is to automatically relate the record to the logged in user through the Shiro plugin for Grails.
Class User { static hasMany = [stuff: Stuff] }
Class Stuff { static belongsTo = [user:User] }
Class StuffController {
def create = {
params.put('user', User.createCriteria().get{eq('username',SecurityUtils.subject.principal)}.id)
def stuffInstance = new Stuff(params)
stuffInstance.save()
}
}
I saw in the generate-views version of the create scaffold that the relevant field was referred to as name="user.id", but neither it nor variants (such as user_id) seems to work. The query to the Users domain returns the record id necessary, and params.put in this context seems to correctly append the params object with the new value when I render to a test page (so I'm guessing it's not immutable), but this is what I get from the save():
Property [user] of class [class org.stuffing.Stuff] cannot be null
I've even tried flipping it around and going the other way, with the same result:
User.createCriteria().get{eq('username',SecurityUtils.subject.principal)}
.addToStuff(new Stuff(params))`
.save()
Anyone able to enlighten me on what I'm missing here?
Thanks!
EDIT:
Apparently I was being braindead; I was overriding the "create" method, but the default action is "save" in the _form.gsp template, so it wasn't executing that branch.
On the plus side, I did learn about dynamic finders via Burt below, so it wasn't a total wash.
Thanks for your time, guys!
Your code can be a lot cleaner - there's no reason to use createCriteria here. If you're searching by username, use a dynamic finder:
def stuffInstance = new Stuff(params)
def user = User.findByUsername(SecurityUtils.subject.principal)
stuffInstance.user = user
if (!stuffInstance.save()) {
// inspect stuffInstance.errors
}
I'm making a script using some already created (not by me) domain classes from grails.
class Person extends OAP {
static hasMany = [addresses: Address]
(...)
}
class Address {
static belongsTo = [oap: OAP]
(...)
}
class OAP has no reference to Address.
So I was trying to do:
p.save()
a.oap = p
println a.oap
a.save()
with p being Person and a being Address, but although it prints the correct person on the console, the reference is not saved on the address table (oap_id stays null)
P.S.: It may not be the best relationship set-up in grails, but that's what I have to work with
Try p.addToAddresses(a) and then p.save(). It should save both p and a.
See http://grails.org/doc/latest/guide/5.%20Object%20Relational%20Mapping%20%28GORM%29.html#5.2.4%20Sets,%20Lists%20and%20Maps
I have no idea how GORM will behave in this situation because you have essentially entered into this weird zone where you have a unidirectional hasMany on Person which results in a SAVE-UPDATE cascade behavior from Person and a NONE on Address. Then you also have a unidirectional one-to-one between Person and OAP which results in an ALL cascade behavior on the OAP side, and a NONE on the Address side. So I'm not sure what to even expect here. You need to fix the relationship to either:
Make it so OAP and not Person hasMany = [address:Address]
Make it so Address belongsTo = [person:Person]
Or, give some additional explanation as to what you're trying to do in your relationship and we can go from there.
Please try with this, it resolved my problem
p.addToAddresses(a);
p.save(flush:true)
Given the following two domain classes:
class Book {
String title
static hasMany = [authors: Author]
static belongsTo = Author
static constraints = {
title(nullable: false)
}
}
class Author {
static hasMany = [books: Books]
}
We create and persist domain objects in services and make use of the data binding feature of Grails. Such a method looks like the following one:
def createAndPersistBook(params) throws ValidationException {
log.debug("Attempt to create and persist book")
Book book = new Book(params)
book.save(flush: true, failOnError: true)
log.debug("Created: ${book}")
book
}
When we pass the params map
params = ["authors": "[2]"]
to the service method (there is no title defined thus validation will fail) the association from the newly created book to the already existing author (and vice-versa) is done by data binding. But since the title is nullable: false and not defined a ValidationException is thrown and the transaction is rolled back.
What we expected now is that the book is not being saved, but Book.list().isEmpty() returns false. We think that this is because of the dirty-check by hibernate, meaning the books collection of the existing author has changed and will be persisted and this save gets cascaded to the book instance.
What is the best way to prevent grails from saving the book in this scenario? Or why is the association done by data binding not properly rolled back when validation fails?
If you've specified that your service is transactional, any uncaught exception will cause a transaction to rollback within a service method. The only thing that might stand in your way is if your RDBMS does not support true transactions/rollback.
Have you specified whether the service is transactional? You should have a statement such as below to declare the service is transactional.
def transactional = true