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.
Related
What is the different in GORM between
class Books {
Author author
}
and
class Books {
static belongsTo = [author: Author]
}
Does cascading rules changes in these two approaches? Also, when to use belongsTo and more importantly, when not to use belongsTo in Grails ?
Yes, belongsTo is intended for controlling cascades of saves and deletes. You may reference the full documentation here http://docs.grails.org/latest/ref/Domain%20Classes/belongsTo.html but to summarize (and in case the URL dies someday):
Use belongsTo to indicate ownership. Saves or deletes to the parent will cascade to the child. In your example, if the Author is deleted, his Books will be too (assuming Author hasMany Books
Do not use belongsTo if you just want to indicate a relationship with no ownership on either side and no automatic cascading of saves or deletes.
Say we have something like the standard Book domain object and bookCategory object. In my controller I want to return a subset of list of books to the view. That subset is not achievable using a find query. When I try to filer the return object, it deletes relationships from the database!
I tried this:
class BookCategory{
String name
static hasMany = [books:Book]
}
class Book{
String title
}
def myController() {
def categories
categories = BookCategory.list()
def user = getCurrentUser()
categories.each { category ->
category.books.removeAll { book ->
!isBookBannedForThisUser(book.title, user)
}
[bookCategories: categories]
}
}
The problem is that it permanently removes these books from the categories for all users from the database!!!
I tried putting the method in a service and using a readonly transaction, but this did not help.
I assume that even if I copy all the categories and books into new list, they will still update the DB as they will still have the book IDs (which I need)
Saving to the database when you dont say save() is very dangerous. is there a way to disable this feature completely?
There is a fundamental flaw in your approach. Do not modify your domain instances if you don't intend to have the changes persisted. Doing so is going to cause you headaches.
Your domain model is suppose to be your system of record. Any changes to it are suppose to be persisted.
If you need to gather up data and manipulate it without having it reflected in your domain model then use a DTO (data transfer object) or similar pattern.
Simply calling .discard() will discard the changes you have made from being persisted when the session automatically flushes.
Instead of working against the framework, and disabling behavior, change your approach to be correct.
I am a beginner in GRAILS so i am hoping some help on the issue i am facing.
I have read the documentation but i am still vague on the idea of relationships in grails. In grails, you could have 4 types of relationship between domain classes.
1 to 1
1 to many
many to 1
many to many
Grails has three constructs to define relationships
static hasMany =
static belongsTo =
static hasOne =
My question and dilemma is why do we need these three constructs to define a relation when we could just specify what type of objects each class has that would automatically define relationship between domain classes.
for example
To define many to many i could have two classes designed this way
class Author{
Set<Book> books
}
class Book{
Set<Author> authors
}
For 1 to many and many to 1
class Author{
Set<Book> books
}
class Book{
String title
}
for one to one
class Author{
Book book
}
class Book{
Author author
}
I appreciate it if anyone can give me a clear, easy to understand explanation. Thank you!
Everything you defined there should work fine. You don't have to use any of the other stuff that you mentioned that GORM offers, but there are reasons that you might want to. For example, you can write a class like this:
class Author{
Set<Book> books
}
That is not the same thing as this:
class Author {
static hasMany = [books: Book]
}
When you use hasMany, Grails generates this for you...
class Author {
Set<Book> books
def addToBooks(Book b) {
books.add(b)
this
}
def addToBooks(Map m) {
books.add(new Book(m))
this
}
def removeFromBooks(Book b) {
books.remove(b)
this
}
}
That isn't exactly what is generated, but that is some of the stuff that you might care about.
There is more to it than is represented there. For example, if the Book has a reference back to the Author, the addToBooks methods will hook that back reference up for you.
There are other behaviors associated with the other properties you mentioned. For example, the hasOne property switches the direction in which the foreign key points on the persistence model. The belongsTo property enforces cascading of certain events. etc.
Take a look at the GORM docs at http://grails.org/doc/latest/guide/GORM.html for more information.
UPDATED
I have a domain classes as below
class Training{
// has one createdBy object references User domain and
// has one course object references Course domain
// has One Trainer1 and Trainer2 objects refernces Trainer Object.
}
class Trainer{
String name
//can have many trainings.
//If Trainer gets deleted, the trainings of him must be deleted
}
Class User{
String name
// can have many trainings.
}
class Course{
String name
//If Course gets deleted, Trainings of this course must be deleted
// can have many trainings.
}
I have got a training create page, Where I have to populate already saved Course, User, Trainer1 and Trainer2. I am not saving them while Creating the training.
So, How to specify the relationship in grails
You did not put any effort to searching answer for yourslef. There are plenty basic examples and blog posts how to map relations in Grails. You should start with Grails documentation of GORM - Grails' object relational mapping. You can find it here.
I see some minor flaws in yout initial design: ie why should training be deleted if user is deleted when trainings will obviously tie with many users. Can training exists without trainers or vice versa ?
I would start with something like this:
Class Training {
static hasMany = [users: User, trainers: Trainer]
static belongsTo = Course
}
Class Trainer {
String name
}
Class User {
String name
}
Class Course {
String name
static hasMany = [trainings: Training]
}
EDIT: I have to agree with Tomasz, you have jumped here too early without searching for answers yourself. Grails.org has good documentation about GORM with examples too.
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)