Grails-将关联的域对象分配给 List 与 ArrayList

Grails- Assigning associated domain objects into List vs ArrayList

我有这样的关系:

User{
...
hasMany = [tags: Tag]

}

Tag{
...
}

在我的服务中有些地方有这个代码:

List<Tag> tags = user.tags

但这不起作用,我收到此错误:


org.codehaus.groovy.runtime.typehandling.GroovyCastException: Cannot cast object '[com.app.ext.Tag : 1]' with class 
'org.hibernate.collection.PersistentSet' to class 'java.util.List' due to: groovy.lang.GroovyRuntimeException: Could not find matching constructor for: java.util.List(com.app.ext.Tag)
    at ConsoleScript15.run(ConsoleScript15:6)

如果我将代码更改为:

ArrayList<Tag> tags = user.tags

它按预期工作!尽管 ArrayListList 的子类。 有什么解释吗?

Grails 版本:2.3.0

看看 Groovy 的 casting rules

当您尝试转换的值是集合时,将调用您要转换为的类型的构造函数。因此,对于 ArrayList 它隐式调用:

ArrayList<Tag> tags = new ArrayList(users.tags)

对于 List 它将类似于 new List(users.tags) 这是不正确的,因为 List 是接口。

下面的代码片段显示了同样的问题:

Set<String> mySet = new HashSet<>(['A', 'B'])
ArrayList<String> myList = mySet; // works okay
List<String> myList = mySet; // fails with GroovyCastException

但您可以显式转换为 ArrayList:

List<String> myList = mySet as ArrayList; 

定义hasMany属性时,格式为Map。您的伪代码不正确,使用 { } 而不是 [ ]。真正的代码可能看起来更像

static hasMany = [tags: Tag]

Tag指定集合成员类型为Tag域class,tags可以是任何合法的变量名。通常它是 class 名称的小写和复数形式,但这只是一种约定。

AST 转换使用此信息编译成您的 class 一个名为 tagsSet 属性,本质上是这样的:

Set<Tag> tags

如果您使用了不同的名称,例如

static hasMany = [puppies: Tag]

那么你会

Set<Tag> puppies

Set 不是 List,反之亦然 - 设置保证唯一性,并列出保证顺序。

您可以在 Groovy 中非常轻松地将一个转换为另一个,如果这就是您想要做的,那么另一个答案应该会有所帮助。但是考虑到原始类型是 Set,将项目添加到 List 是没有意义的,因为顺序是随机的。

你可以将集合的类型声明为列表,Hibernate会增加一个额外的列来存储元素索引,以确保列表中的顺序在数据库中得以保留。为此,明确添加 List 声明

List tags
static hasMany = [tags: Tag]

还建议您查看 grails 指南,那里说明 hasMany 关系默认是一个 Map http://grails.github.io/grails-doc/3.0.x/guide/GORM.html#domainClasses,如果您否则,您必须明确地将变量声明为 Set

  public class Car{
      Set<Tyre> tyres

      static has many = [tyres:Tyre, seats:Seat]
 }

在这种情况下,轮胎可以确保唯一性,因为它是一个集合,而座椅则不能,因为它默认是一个地图

希望这也有帮助