Grails GORM 默认排序字段也是复合键的字段

Grails GORM default sort field which is also a field of a composite key

我的 Grails 是 2.5.0 版

我有两个域名类:

import org.apache.commons.lang.builder.HashCodeBuilder

class TemplateHeader implements  Serializable {
  int headerId
  int ver
  String templateName
  String extraDescription
  String serviceType
  String createdBy
  boolean isActive
  String updatedBy
  Date dateCreated
  Date lastUpdated

  static hasMany = [templateItems: TemplateItem]

  static mapping = {
    id composite: ['headerId', 'ver']
    ver             comment:''
    templateName    length: 40
    serviceType     length: 20
    extraDescription length: 60
    isActive        defaultValue:false
    templateItems   sort:'itemNo', order:'asc'
  }
  static constraints = {
    headerId unique: 'ver'
    templateName nullable: false
    serviceType nullable: false
  }

  boolean equals(other) {
    if (!(other instanceof TemplateItem)) {
        return false
    }
    other.id == id && other.ver == ver
  }

  int hashCode() {
    def builder = new HashCodeBuilder()
    builder.append id
    builder.append ver
    builder.toHashCode()
  }

}

============================================= ==========

import org.apache.commons.lang.builder.HashCodeBuilder

class TemplateItem implements Serializable {
    int itemNo
    String itemName;
    String unitName;
    int unitPrice;
    double defaultValue
    boolean allowChange
    String extraComment
    String createdBy
    String updatedBy
    Date dateCreated 
    Date lastUpdated


    static belongsTo = [templateHeader:TemplateHeader]
    static mapping = {
        id composite: [ 'templateHeader',  'itemNo']
        itemNo      comment:''
        itemName     length: 60
        unitName     length: 4
        unitPrice    comment:''
        extraComment length: 60
        defaultValue comment:''
        allowChange  comment:''
    }
    static constraints = {
        itemName    nullable: false
        unitName    nullable: false
        extraComment    nullable: true
        defaultValue    nullable: false
    }

    boolean equals(other) {
        if (!(other instanceof TemplateItem)) {
            return false
        }
        other.itemNo == itemNo && other.templateHeaderId == templateHeaderId
    }

    int hashCode() {
        def builder = new HashCodeBuilder()
        builder.append templateHeaderId
        builder.append itemNo
        builder.toHashCode()
    }
}

当我运行 grails 应用程序时,它在构建表时显示以下异常:

|Running Grails application
context.GrailsContextLoaderListener Error initializing the application: Error creating bean with name 'transactionManagerPostProcessor': Initialization of bean failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'transactionManager': Cannot resolve reference to bean 'sessionFactory' while setting bean property 'sessionFactory'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sessionFactory': Invocation of init method failed; nested exception is org.codehaus.groovy.grails.exceptions.GrailsDomainException: property from sort clause not found: accudata_portal.TemplateItem.itemNo
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'transactionManagerPostProcessor': Initialization of bean failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'transactionManager': Cannot resolve reference to bean 'sessionFactory' while setting bean property 'sessionFactory'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sessionFactory': Invocation of init method failed; nested exception is org.codehaus.groovy.grails.exceptions.GrailsDomainException: property from sort clause not found: accudata_portal.TemplateItem.itemNo
    at java.util.concurrent.FutureTask.run(FutureTask.java:262)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
    at java.lang.Thread.run(Thread.java:745)
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'transactionManager': Cannot resolve reference to bean 'sessionFactory' while setting bean property 'sessionFactory'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sessionFactory': Invocation of init method failed; nested exception is org.codehaus.groovy.grails.exceptions.GrailsDomainException: property from sort clause not found: accudata_portal.TemplateItem.itemNo
    ... 4 more
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sessionFactory': Invocation of init method failed; nested exception is org.codehaus.groovy.grails.exceptions.GrailsDomainException: property from sort clause not found: accudata_portal.TemplateItem.itemNo
    ... 4 more
Caused by: org.codehaus.groovy.grails.exceptions.GrailsDomainException: property from sort clause not found: accudata_portal.TemplateItem.itemNo
    ... 4 more
Error |
Forked Grails VM exited with error

有人告诉我如何解决吗?谢谢

我想知道为什么您在映射闭包中使用的字段没有收到错误,并且您没有为其定义任何映射,如 itemNo、unitPrice、defaultValue 等。

此外,您的 TemapletHeader 域中的 equals 条件应为:

if (!(other instanceof TemplateHeader))

现在回答您的问题,您收到此错误是因为 itemNo 属性 在 TemplateItem 域中TemplateHeader 域。 sort 关键字也接受映射。

要根据 itemNo 字段对 TemplateItem 对象进行排序,您必须在 TemplateItem 域的映射闭包中定义排序 属性

static mapping = {
    .
    .
    .
    sort 'itemNo': 'asc'
}

现在,每当您查询 TemplateItem 时,默认排序都会在 itemNo 字段上。 所以如果你使用

TemplateItem.findAllByTemplateHeader(templateHeaderInstance)

它将根据项目编号给出排序的模板项目。

通过在 TemplateItem 域中定义排序 属性 将不会对 TemplateHeader 域中的 templateItems 字段进行排序。原因是在获取域对象的关联时,Grails 忽略了该域的排序字段。如果您查看在获取 TemplateHeader 对象及其关联时生成的 sql,您将看到不同之处。

显然没有直接的方法可以实现您想要实现的目标,具体取决于您是想根据模板项对 TemapleHeader 对象进行排序,还是只对单个 TemplateHeader 对象中的模板项进行排序

要根据 TemplateItems 对 TemplateHeader 对象进行排序,请将以下排序 属性 添加到 TemplateHeader 域的映射闭包中:

static mapping = {
    .
    .
    .
    sort 'templateItems.itemNo': 'asc'
}

以及何时执行此 GORM

TemplateHeader header = TemplateHeader.list()

它生成以下 sql

select this_.header_id as header_i1_0_1_, this_.ver as ver2_0_1_, .... from template_header this_ inner join template_item templateit1_ on this_.header_id=templateit1_.template_header_header_id and this_.ver=templateit1_.template_header_ver order by templateit1_.item_no asc

现在这个查询的问题是它的执行成本非常高。它在 TemplateHeader 和 TemplateItem 域之间使用内部连接。因此,如果您在 TemplateHeader 域中有 2 条记录,在 TemplateItem 域中有 10 条记录,它将获取 10 条记录,而您只需要 2 条记录。另一个问题是 templateItems 属性 仍将被延迟获取。所以当你做 header.templateItems 时,它会再次查询数据库,结果不会根据 itemNo 排序。 sql 看起来像这样:

select templateit0_.template_header_header_id as template1_0_0_, templateit0_.template_header_ver as template2_0_0_, ... from template_item templateit0_ where templateit0_.template_header_header_id=? and templateit0_.template_header_ver=?

因此,如果您想对 TemplateHeader 对象的 templateItems 进行排序,您可以覆盖 templateItems 的 getter 并在那里对结果进行排序:

Set<TemplateItem> getTemplateItems() {
    return templateItems?.sort { item_1, item_2 -> item_1?.itemNo <=> item_2?.itemNo }
}

以防万一在将 TemplateItem 对象添加到 TemplateHeader 对象时遇到这样的错误:

No signature of method: TemplateHeader.addToTemplateItems() is applicable for argument types: (TemplateItem)

然后尝试将 templateItems 声明为列表。