在集合中使用域对象还是在映射中使用键是一种糟糕的做法?
在过去,我做过很多这样的事情
Set<Book> someBooks = [] as Set
someBooks.addAll (Book.findAllByAuthorLike('%hofstadter%'))
someBooks.add (Book.findByTitleLike ('%eternal%'))
然而,我注意到,当findAllByAuthorLike
可能返回Hibernate代理对象列表com.me.Book_$$_javassist_128
,但findByTitleLike
将返回正确的com.me.Book
对象时,我经常遇到问题。这会导致集合中出现重复,因为实际对象和代理被认为不相等。
我发现在使用这样的域对象集时需要非常小心,我觉得这可能是我一开始就不应该做的事情。
当然,另一种选择是使用id的集合/映射,但这会使我的代码变得冗长,并且容易误解
Set<Integer> someBooks = [] as Set // a set of id's for books
@Burt:我认为Grails域类已经做到了这一点,至少equals/compare是在class/id而不是对象实例上完成的。你是说hibernate代理的特殊比较器吗?
return (this.class == obj.class && this.id == obj.id) ||
(obj.class == someHibernateProxy && this.id == obj.id)
这不是一个糟糕的做法,但就像在非Grails应用程序中一样,如果要将equals
和hashCode
放在基于哈希的集合(HashSet
、HashMap
等)中,则应该重写它们;如果要使用TreeSet
/TreeMap
/等,则还应该实现Comparable
(这意味着compareTo
方法)。
在Hibernate支持的情况下正确实现equals()和hashcode()绝非易事。Java Collections要求对象的hashcode和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()。