Could someone explain the difference to me between these code samples (Grails 3.3.11)
Session session = sessionFactory.openSession()
Person person = new Person()
person.firstName = "John"
person.lastName = "Roy"
person.address = "New York"
session.save(person)
and
Person person = new Person()
person.firstName = "John"
person.lastName = "Roy"
person.address = "New York"
person.save(person)
One difference is that session.save(person) will work with any entity that is mapped in that Hibernate session where person.save() only works with GORM entities (which are also mapped in the Hibernate session for you).
When using GORM there really aren't good reasons to use session.save(person).
I know that you didn't ask about best practice or about GORM Data Service but related to your question...
With recent versions of GORM best practice would be to have an abstract GORM Data Service like this...
import grails.gorm.services.Service
#Service(Person)
interface PersonService {
Person save(Person p)
}
And then inject that wherever you want to do the save. For example, in a controller:
class SomeController {
PersonService personService
def someAction() {
Person p = new Person()
// ...
personService.save p
// ...
}
}
Related
I have a domain model like Twitter where a USER can FOLLOW other USERs. My class looks like this,
#NodeEntity
public class UserNodeEntity {
#GraphId
Long id;
#Property(name = "firstName")
private String firstName;
#Property(name = "lastName")
private String lastName;
#Relationship(type = "FOLLOWS", direction = Relationship.INCOMING)
private Set<UserNodeEntity> followers = new HashSet<>();
#Relationship(type = "HAS_ROLE", direction = Relationship.OUTGOING)
private Set<UserRole> roles = new HashSet<>();
// getters and setters
...
}
My code to add a new follower looks like this,
public void createNewFollower(String id, String followerId) {
UserNodeEntity usr = getUserById(id); //fetch the user by id from Neo4j
UserNodeEntity follower = getUserById(followerId);
if (usr != null) {
usr.getFollowers().add(follower);
userRepo.save(usr);
}
}
And code to add new Role looks like this,
public void assignNewRole(String id, UserRole role) {
UserNodeEntity usr = getUserById(id); //fetch the user by id from Neo4j
if (usr != null) {
usr.getRoles().add(role);
userRepo.save(usr);
}
}
When I call the createNewFollower() method with the ids of follower and followee one relationship (FOLLOWEE <- FOLLOWS <- FOLLOWER) is created as expected. But when I call the assignNewRole() method with the id of user and his role a new relationship is created by name HAS_ROLE and one more FOLLOWS relationship is created between the previous follower and followee. Now the two users follow each other instead of one relationship (FOLLOWEE <- FOLLOWS <- FOLLOWER)
Can anyone help me in understanding why this is happening?
My Neo4j version is 2.3.3 and spring-data-neo4j version is 4.0.0.RELEASE
What is the correct way to handle domain save errors on domain classes under the hasMany relationship? It seems that calling save() on the owning side of the relation will return true even if there are validation errors on the owned objects.
How should I test this in running code or integration test?
I have reduced my problem to the following simple case.
class User {
static hasMany = [emails: Email]
static constraints = { }
}
.
class Email {
static belongsTo = [user: User]
String emailAddress
static constraints = {
emailAddress unique: true
}
}
Here are two suggestions on how to do this. Neither is really elegant.
First one calls save() individually to each on the hasMany relationship. What is good here is that we get the exact error out of the test case. This pretty cumbersome.
#Test
void testHasManyConstraintsOwned(){
def john = new User(login: 'johnDoe')
def email = new Email(emailAddress: 'john#gmail.com')
def duplicateEmail = new Email(emailAddress: 'john#gmail.com')
john.save() // otherwise: NULL not allowed for column "USER_ID" is thrown for email.save()
john.addToEmails(email).addToEmails(duplicateEmail)
assert email.save()
assert !duplicateEmail.save()
assert "unique"== duplicateEmail.errors.getFieldError("emailAddress").code
}
Another approach uses try/catch to detect the excepted fail. Problem here is that we have no way of knowing what went wrong and thus cannot actually test that the domain constraints are working as we expect.
#Test
void testHasManyConstraintsOwningExcp(){
def john = new User(login: 'johnDoe')
def email = new Email(emailAddress: 'john#gmail.com')
def duplicateEmail = new Email(emailAddress: 'john#gmail.com')
john.addToEmails(email).addToEmails(duplicateEmail)
try {
john.save(flush: true, failOnError: true)
assert false // should not reach here
}catch(Exception e){
}
}
What is the correct way to test and to react in the application code?
I have the classes Person and Dog.
A dog belongs to one Person and a Person can have different dogs (so one-to-many).
I have a test method that gets 2 persond out of the db. If a person does not exist in the db, I make it. Then I want to save 4 dogs.
This is the test:
Person person1= personResourceAccess.GetPersonByName("Person1");
if(person1==null)
{
Person person = new Person()
{
Name = "Person1"
};
person1= personResourceAccess.CreatePerson(person);
}
Person person2= personResourceAccess.GetPersonByName("Person2");
if(person2==null)
{
Person person = new Person()
{
Name = "Person2"
};
person2= personResourceAccess.CreatePerson(person);
}
Dog dog1 = new Dog(){name="Dog1", owner = person1};
Dog dog2 = new Dog(){name="Dog2", owner = person1};
Dog dog3 = new Dog(){name="Dog3", owner = person1};
Dog dog4 = new Dog(){name="Dog4", owner = person2};
dog1 = dogResourceAccess.CreateDog(dog1);
dog2 = dogResourceAccess.CreateDog(dog2);
dog3 = dogResourceAccess.CreateDog(dog3 );
dog4 = dogResourceAccess.CreateDog(dog4 );
This is the code of the resource access:
public Dog CreateDog(Dog dog)
{
try
{
db.Dogs.AddObject(dog);
db.SaveChanges();
return dog;
}
catch(Exception ex)
{
return null
}
}
When I save the first dog, all other dogs are created in the db. Why is this and how can I prevent it from happening?
I think it might have something to do with the context but I can't seem to resolve the problem.
This is correct behavior. SaveChanges always save all changes currently tracked by the context. There is no way to avoid it - it is called unit of work. The reason why this happens is assigning owner in initialization of the new dog. Person is already tracked by the context so assigning it to any other entity will immediately start tracking of that entity.
If you want to save changes in sequence you must do it different way:
Dog dog1 = new Dog(){name="Dog1"};
dog1 = dogResourceAccess.CreateDog(dog1);
dog1.owner = person1; // Attach the person after you added a new dog.
I'm having an issue with grails. I have a domain that looks like:
class Book {
static belongsTo = Author
String toString() { title }
Author bookAuthor
String title
String currentPage
static constraints = {
bookAuthor()
title(unique:true)
currentPage()
}
}
The main thing to note is that I have title(unique:true) to avoid from adding the same book twice. However, this is causing issues. In the controller I have created:
def populate = {
def bookInstance = new Book()
def dir = 'C:/currentBooks.txt'
def bookList
bookList = readFile(dir) //read file and push values into bookList
int numOfBooks = bookList.size()
numOfBooks.times {
bookInstance.setBookAuthor(bookList.author[it])
bookInstance.setTitle(bookList.title[it])
bookInstance.setCurrentPage(bookList.title[it])
bookInstance.save()
}
}
I call populate to read a file and populate the database with new Books. The problem is that I want to update it with new values. For instance, lets say that the book already exists in the database but I have read farther into the book and want to change the currentPage so the data is changed in the file and populate is called but doesn't update the page because the title already exists.
Can someone explain how to update the results with the new values?
First of all, you need a key for your Book domain object. You have the title marked as unique, which suggests you want to use that to uniquely identify a Book. I'd recommend against that (what happens when two books have the same title?) and use the id grails provides by default. That means you'll have to store the id in your currentBooks.txt in addition to your other fields.
Once you've got an id, you can try loading an existing record from the database. If not, create a new one. For Example:
def dir = 'C:/currentBooks.txt'
def bookList
bookList = readFile(dir) //read file and push values into bookList
int numOfBooks = bookList.size()
numOfBooks.times {
def bookInstance.get(bookList.id[it])
if (!bookInstance) {
bookInstance = new Book()
}
bookInstance.setBookAuthor(bookList.author[it])
bookInstance.setTitle(bookList.title[it])
bookInstance.setCurrentPage(bookList.title[it])
bookInstance.save()
}
Alternatively, you could use the title as the id. This is a bad idea as indicated above, but it saves having to keep track of a separate id and change the format of currentBooks.txt. With Book defined as below, you could call Book.get(bookList.title[it]):
class Book {
static belongsTo = Author
String toString() { title }
Author bookAuthor
String title
String currentPage
static constraints = {
bookAuthor()
title(unique:true)
currentPage()
}
static mapping = {
id name: 'title', generator: 'assigned'
}
}
I'm struggling to get association right on Grails. Let's say I have two domain classes:
class Engine {
String name
int numberOfCylinders = 4
static constraints = {
name(blank:false, nullable:false)
numberOfCylinders(range:4..8)
}
}
class Car {
int year
String brand
Engine engine = new Engine(name:"Default Engine")
static constraints = {
engine(nullable:false)
brand(blank:false, nullable:false)
year(nullable:false)
}
}
The idea is that users can create cars without creating an engine first, and those cars get a default engine. In the CarController I have:
def save = {
def car = new Car(params)
if(!car.hasErrors() && car.save()){
flash.message = "Car saved"
redirect(action:index)
}else{
render(view:'create', model:[car:car])
}
}
When trying to save, I get a null value exception on the Car.engine field, so obviously the default engine is not created and saved. I tried to manually create the engine:
def save = {
def car = new Car(params)
car.engine = new Engine(name: "Default Engine")
if(!car.hasErrors() && car.save()){
flash.message = "Car saved"
redirect(action:index)
}else{
render(view:'create', model:[car:car])
}
}
Didn't work either. Is Grails not able to save associated classes? How could I implement such feature?
I think you need a belongsTo in your Engine ie
static belongsTo = [car:Car]
Hope this helps.
For what is worth, I finally nailed it.
The exception I got when trying to save a car was
not-null property references a null or
transient value
It was obvious that the engine was null when trying to save, but why? Turns out you have to do:
def car = new Car(params)
car.engine = new Engine(name: "Default Engine")
car.engine.save()
Since engine doesn't belongs to a Car, you don't get cascade save/update/delete which is fine in my case. The solution is to manually save the engine and then save the car.