@EqualsAndHashCode on Grails domains


I found a strange bug\behavior in Grails.

How to grasefully check domains for equality?
For example, we we have User comain class and we want to check in code equality of its two instances:

class User {
}

currentUser == article.author

In this case equals will be by reference.
But this objects may have different references: one was get from DB another was from session. And equals operator will return false.
So, we need to check them for equality by id field:

currentUser.id == article.author.id

But, you can forget about this and still compare by references, so lets generate equals() and hashCode() methods. In IntelliJ we should set cursor on class name and press Alt+Insert.
1. In the dialog we must check option «Accept subclasses» becouse Hibernate inside Grails always creates a proxy subclass of our User class.
2. We should uncheck all fields and leave only one id field

class User {
	boolean equals(o) {
		if (this.is(o)) return true
		if (!(o instanceof User)) return false
		User user = (User) o
		if (id != user.id) return false
		return true
	}

	int hashCode() {
		return id.hashCode()
	}
}

But in Groovy 2 we have a cool annotation EqualsAndHashCode

@EqualsAndHashCode(includes = 'id')
class User {
}

But! In this case, condition currentUser == article.author is always return true! Always, when they equal or not. I can understood why always true.
It costs me a few hours of deep debug of transformator EqualsAndHashCodeASTTransformation until I understood a root of problem.
The field id is not exists in class User — it’s wired dynamicly by Grails (i.e GORM).
So, this annotation becomes redundant — it doesn’t make any equals checking on id.
You can write any non existent field in includes and transformer will not fail compilation:

@EqualsAndHashCode(includes = ['id', 'ololo', 'trololo'])
class User {
}

I will rise an issue in bugtracker with asking a developers to make transformer more strictly.

Thus solution to fix is simple: you just need to declare id field explicitly like this:

@EqualsAndHashCode(includes = 'id')
class User {
	long id
}

Now all should be fine. Also I remember that had the similar problem with implicit id field with @ToString annotation.
Also the same problem I found with fields like belongsTo:

@EqualsAndHashCode(includes = 'author')
class Article {
    static belongsTo = [author: User]
}

This will not work. Again, explicitly declare field:

@EqualsAndHashCode(includes = 'author')
class Article {
    User author
    static belongsTo = [author: User]
}

This will work as expected with equals.

So rule is simple: always declare field explicitly!

Have a nice coding!

Реклама

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход / Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход / Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход / Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход / Изменить )

Connecting to %s