You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Grails 5.3 GSP问题:语法错误与循环加载缓慢求助

两个Grails升级及性能问题的解决方案

问题1:下拉框onchange事件抛出语法错误(Uncaught Syntax Error: expected expression got '}')

问题分析

升级到Grails 5.3后,remoteFunction的输出格式与GSP表达式解析逻辑发生了变化。原代码在onSuccess中直接拼接多个remoteFunction调用,导致生成的JavaScript出现引号不匹配、大括号错位的语法问题——旧版本Grails对这种嵌套拼接的兼容性更好,但新版本模板引擎对字符串转义和表达式拼接的校验更严格。另外原代码中存在DOM ID拼写错误:ControlCroup应为ControlGroup,虽不是直接报错原因,但会导致后续DOM操作失效。

解决方案

避免在remoteFunction参数中嵌套另一个remoteFunction,将多步异步操作封装为独立JS函数,再在onSuccess中调用:

  1. 在页面<script>块定义处理函数:
function handleResponseSuccess(beanId) {
    jQuery("#reviewSaveNotice").stop(true).fadeIn().delay(1500).fadeOut();
    jQuery('#questionResponse' + beanId + 'ControlGroup').removeClass('error');
    updateQuestionScore(data);
    // 调用评分渲染接口
    ${remoteFunction(controller: 'review', action: 'renderScore', id: "${bean.reviewSection.review.id}", update: 'scoreTotal')};
    // 调用章节评分渲染接口
    ${remoteFunction(controller: 'review', action: 'renderSectionScore', id: "${bean.reviewSection.id}", update: "sectionScore${bean.reviewSection.id}")};
}
  1. 修改g:select的onchange属性,简化remoteFunction配置:
<div id="questionResponse${bean?.id}ControlGroup" class="control-group">
<div class="controls">
    <g:hiddenField name="reviewQuestionResponse.${bean?.id}.id" value="${bean?.id}" />
    <g:select class="input-block-level" name="reviewQuestionResponse.${bean?.id}.response" from="${com.moo.qwr.enums.QuestionResponseType.values()}" value="${value}"
              onchange="${remoteFunction(controller: 'reviewQuestionResponse', action: 'update', id: "${bean?.id}",
                      params: '\'response=\' + this.value',
                      onSuccess: "handleResponseSuccess('${bean?.id}')",
                      onFailure: "jQuery('#questionResponse${bean?.id}ControlGroup').addClass('error')"
              )}"
    />
</div>

问题2:编辑表单加载极慢(8-10分钟),创建表单正常

问题分析

核心原因是N+1查询问题:

  • reviewInstance.fields是hasMany关联的SortedSet,默认懒加载,g:each遍历时每次迭代都会触发一次数据库查询加载单个ReviewField实例;
  • 如果ReviewField的渲染模板中还访问了其关联对象,会触发更多嵌套查询,累积大量数据库请求导致耗时剧增;
  • 创建表单时reviewInstance.fields为空,不会触发这些查询,因此加载速度正常。
    另外,SortedSet的内存排序操作(未通过数据库排序)在数据量较大时也会增加耗时。

解决方案

  1. 优化关联查询,消除N+1
    在获取reviewInstance的Controller/Service方法中,使用fetch join一次性加载关联数据:
// 示例:ReviewController的edit方法
def edit(Long id) {
    def reviewInstance = Review.createCriteria().get {
        eq('id', id)
        fetchMode('fields', FetchMode.JOIN) // 一次性加载fields集合
        // 若ReviewField还有关联对象,需在此添加对应fetchMode
    }
    [reviewInstance: reviewInstance]
}

也可在领域类中配置批量加载:

// Review领域类
static mapping = {
    fields batchSize: 50 // 批量加载减少查询次数
}
  1. 优化SortedSet排序逻辑
    确保排序在数据库层面完成,避免内存排序:
// Review领域类
static mapping = {
    fields sort: 'sortField', order: 'asc' // sortField为ReviewField的排序字段
}
  1. 简化模板渲染逻辑
  • 避免在模板中调用ClassUtils.getShortNameAsProperty(it?.class)这类反射操作,提前在Service中处理好模板路径并传入;
  • 检查模板内是否有重复查询、耗时计算,尽量将数据预处理后传入模板。
  1. 开启日志排查
    在logback.groovy中开启Hibernate SQL日志,确认是否存在大量重复查询:
logger('org.hibernate.SQL', DEBUG, ['STDOUT'], false)
logger('org.hibernate.type.descriptor.sql.BasicBinder', TRACE, ['STDOUT'], false)

内容的提问来源于stack exchange,提问作者Daniel Geiselman

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 17:08:12