集合中的 Grails 域类
在集合中使用域对象或作为地图中的键是一种不好的做法吗?
过去我做过很多这样的事情
Set<Book> someBooks = [] as Set
someBooks.addAll (Book.findAllByAuthorLike('%hofstadter%'))
someBooks.add (Book.findByTitleLike ('%eternal%'))
但是我注意到当 findAllByAuthorLike
可能返回 Hibernate 代理对象列表 com.me.Book_$$_javassist_128< 时,我经常遇到问题/code> 但 findByTitleLike 将返回正确的 com.me.Book 对象。这会导致集合中出现重复,因为真实对象和代理被认为不相等。
我发现在使用这样的域对象集时需要非常小心,而且我感觉这可能是我一开始就不应该做的事情。
另一种选择当然是使用 id 的集合/映射,但它使我的代码变得冗长并且容易产生误解
Set<Integer> someBooks = [] as Set // a set of id's for books
@Burt:我认为 Grails 域类已经这样做了,至少这样等于/比较是在类/id 上完成的,而不是在对象实例上完成的。您是指休眠代理的特殊比较器吗?
return (this.class == obj.class && this.id == obj.id) ||
(obj.class == someHibernateProxy && this.id == obj.id)
Is it a bad practice to use domain objects in Sets or as keys in Maps?
In the past I've done things like this a lot
Set<Book> someBooks = [] as Set
someBooks.addAll (Book.findAllByAuthorLike('%hofstadter%'))
someBooks.add (Book.findByTitleLike ('%eternal%'))
However I have noticed that I often encounter problems when findAllByAuthorLike
might return a list of Hibernate Proxy objects com.me.Book_$$_javassist_128
but findByTitleLike
will return a proper com.me.Book
object. This causes duplicates in the set because the real object and the proxy are considered not equal.
I find I need to be extremely careful when using Sets of domain objects like this, and I get the feeling it might be something I should not be doing in the first place.
The alternative is of course to use a set/map of id's, but it makes my code verbose and prone to misunderstanding
Set<Integer> someBooks = [] as Set // a set of id's for books
@Burt: I thought Grails domain classes already did this, at least so that equals/compare was done on class/id's rather than the object instance. Do you mean a special comparator for hibernate proxies?
return (this.class == obj.class && this.id == obj.id) ||
(obj.class == someHibernateProxy && this.id == obj.id)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
这根本不是坏习惯,但就像在非 Grails 应用程序中一样,如果您要将它们放入基于哈希的集合中,则应该覆盖
equals
和hashCode
(< code>HashSet、HashMap
等),如果您打算的话,还可以实现Comparable
(这意味着compareTo
方法)使用TreeSet
/TreeMap
/等。It's not bad practice at all, but just like in a non-Grails application you should override
equals
andhashCode
if you'll be putting them in hash-based collections (HashSet
,HashMap
, etc.) and also implementComparable
(which implies acompareTo
method) if you're going to useTreeSet
/TreeMap
/etc.在 Hibernate 支持的情况下正确实现 equals() 和 hashcode() 绝非易事。 Java 集合要求对象的哈希码和 equals() 的行为不会改变,但是当对象是新创建的对象时,其 id 可能会发生变化,并且其他字段可能会由于各种原因而发生变化。有时您有一个可以使用的良好的、不可更改的企业 ID,但通常情况并非如此。显然,默认的 Java 行为也不适合 Hibernate 情况。
我见过的最佳解决方案如下所述: http://onjava.com/pub/a/onjava/2006/09/13/dont-let-hibernate-steal-your-identity.html?page=2
解决方案描述的是:对象一创建就初始化id。不要等待 Hibernate 分配 id。配置 Hibernate 使用版本来确定它是否是新对象。这样,id 就不可更改,并且可以安全地用于 hashcode() 和 equals()。
Proper implementation of equals() and hashcode() in a Hibernate-backed situation is far from trivial. Java Collections require that hashcodes of objects and behaviour of equals() don't change, but the id of an object can change when it's a newly created object, and other fields can change for a wide variety of reasons. Sometimes you have a good unchangeable business id that you can use, but quite often that's not the case. And obviously default Java behaviour is also not suitable in a Hibernate situation.
The best solution I've seen is described here: http://onjava.com/pub/a/onjava/2006/09/13/dont-let-hibernate-steal-your-identity.html?page=2
The solution it describes is: initialize the id as soon as the object is created. Don't wait for Hibernate to assign an id. Configure Hibernate to use version to determine whether it's a new object. This way, id in unchangeable, and can be safely used for hashcode() and equals().