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'
Related
I'm making a model where a User fills out many questionnaires and the responses get saved to Questionresponse. I'm on grails 2.5.2
Test1
So I have two models
class User {
String username
...
static hasMany = [reponse: QuestionResponse]
}
class QuestionResponse {
String question_1
String question_2
...
}
With the above, a new DB table is created: user_questionresponse with two columns user_questionresponses_id and questionresponse_id. This seems like what I want. A user would have many questionresponses and those relationships would be saved in this table. However, I can't find out how to save data to this table.
For example, if I do:
def user = springSecurityService.currentUser
def questionnaire = new QuestionResponse(question_1: "foo", question_2: "bar")
//How do I link the user to this newly created questionnaire?
user.addToResponse(q).save(flush: true) //DOES NOT WORK.
Test2 (just add belongsTo)
class User {
String username
...
static hasMany = [reponse: QuestionResponse]
}
class QuestionResponse {
String question_1
String question_2
static belongsTo = [user: User]
...
}
If I add belongsTo to QuestionResponse a new column, user_id, gets created in the DB. Now if I run the same code as above, this user_id column has the id populated with that of the current user. However, the relationship table, user_questionresponse is still empty.
I am aware of the approach mentioned by Burt but I assume that should be required only for ManyToMany relationship. If that is required for all relationship, why isn't that the default?
In your first case, you have a OneToMany relationship between User and QuestionResponse with no side being the owner of the relationship. In this case to maintain the relationship between User and QuestionResponse, a third table is required. To persist data you need to do the:
userInstance.addToResponse(new QuestionResponse(question_1: "foo", question_2: "bar")).save(flush: true, failOnError: true)
You are doing user.addToReponse(q) instead it should be user.addToReponse(questionnaire), if it's not a typo and the data is actually not being stored, then check by adding the failOnError parameter to save() method. Sometimes grails save() method fails silently, it should tell you if this is the case.
In second case, you have added the parent to the relationship, so that means you don't need the third table to maintain the relationship. Grails will not create and populate the third table in this case.
The second approach (adding belongsTo in QuestionResponse) seems the right thing to do in your case, since QuestionResponse objects cannot exists without a user and cannot belong to different users.
In that case there's no need to use a third table.
When you run the app for the first time, grails created the relation table (because there was no belongsTo). When you run the app again with belongsTo grails adds the user_id field but DOES NOT DROP the relation table. That's why the table is there and is empty: it's not needed, but grails database auto-update feature only adds things, it does not remove anything.
The same applies to fields: if you remove a field from an entity you have to manually remove it from the database.
I am using Grails 2.4.2. I'm saving an instance of a domain class that violates the constraints, so I would expect the save to fail. But it appears to succeed. The Grails documentation states:
Another thing to bear in mind is that Grails validates a domain instance every time you save it. If that validation fails the domain instance will not be persisted to the database.
The domain class:
class Book {
String name
static constraints = {
name unique: true
}
String toString() { "[Book: id=${id} name=${name}]" }
}
The following code modifies a second instance so that it has the same name as the first and tries to save it (book3.save). I expect this to fail, and it does return null. But then I load up the same object by Id (Book.get(book2.id)) and find that it has the new name. Finally, it prints the entire list of books, and we see that they both have the same name.
def book1 = new Book(name: "Cats").save()
def book2 = new Book(name: "Dogs").save()
log.info "The books: ${Book.list()}"
def book3 = Book.get(book2.id)
book3.name = "Cats"
def book3save = book3.save() // Should fail?
def book4 = Book.get(book2.id)
log.info "The result of saving book ${book2.id} with name 'Cats' is ${book3save}"
log.info "The book with ID ${book2.id} is now named ${book4.name}"
log.info "The books are: ${Book.list()}"
The output:
The books: [[Book: id=1 name=Cats], [Book: id=2 name=Dogs]]
The result of saving book 2 with name 'Cats' is null
The book with ID 2 is now named Cats
The books are: [[Book: id=1 name=Cats], [Book: id=2 name=Cats]]
Grails will not throw exceptions on validation errors by default. You can do
book3.save(failOnError: true)
which will throw an exception.
or you need to check the return value of save(). A null means the validation failed. You can also do a book3.validate() to see if the instance is valid before attempting a save.
I think what you are seeing in the appearance of the book being updated is due to the hibernate session. Your save failed but a call to Book.get() loads the instance from the current hibernate session not the database. If you check the dirty flag you can confirm this checking isDirty() or call refresh() to refetch the instance from the database.
I have two domain classes one is Game:
class Game {
String name
String description
Double price
static hasMany = [reviews: Review]
}
and the other one is Review:
class Review {
String reviewText
Date reviewDate
static belongsTo = [game: Game]
}
Both are stripped down versions. I have two objects
r1 = new Review([reviewText: "A game review", reviewDate: new Date()])
g = new Game([name:"Angry Birds", description:"Parabolic physics like game", 20.00])
r1.game=g
r1.save()
After above call is this statement legal?
g.reviews
Will it return a list of all reviews associated with Game? Actually I have an old Grails code which is fetching list of reviews by g.reviews like calls and on Grails 2.4.4, I am getting a null. Was it legal in older versions of Grails? What is the recommended way to fetch reviews associated with a particular game?
save with flush:true if you want to immediately access the db.
r1.save(flush:true)
then you can say:
g.reviews
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
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)