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

Grails含复合主键的Domain类无法调用REST API求助

解决Grails复合主键Domain类JSON序列化空指针问题

我之前也碰到过一模一样的问题,这个空指针的根源很明确:Grails默认的JSON DomainClassMarshaller并不支持复合主键的域类序列化。它会尝试去读取域类的id属性(单一主键的场景),但你的CGender用的是budYear和genderCode组成的复合主键,没有这个单一的id字段,所以Marshaller拿到null之后就抛出了NPE。

下面给你两个靠谱的解决方案,你可以根据自己的场景选择:

方案一:自定义JSON Marshaller(推荐)

手动编写一个Marshaller来控制CGender的JSON序列化逻辑,完全规避默认Marshaller的问题。

  1. 创建一个Marshaller类,放在src/main/groovy下:
import grails.converters.JSON
import org.springframework.stereotype.Component

@Component
class CGenderJsonMarshaller {
    void register() {
        JSON.registerObjectMarshaller(CGender) { CGender gender ->
            // 手动定义需要序列化的字段,包括复合主键的两个字段
            def result = [
                budYear: gender.budYear,
                genderCode: gender.genderCode,
                genderNdesc: gender.genderNdesc,
                genderEdesc: gender.genderEdesc,
                disableFlag: gender.disableFlag,
                createdBy: gender.createdBy,
                createdDate: gender.createdDate
            ]
            // 如果需要序列化关联的CProjects,记得手动处理(避免循环引用或同样的复合键问题)
            // if (gender.cProjects) {
            //     result.cProjects = gender.cProjects.collect { project ->
            //         [/* 这里定义CProject需要序列化的字段 */]
            //     }
            // }
            return result
        }
    }
}
  1. 在应用启动时注册这个Marshaller:
    创建一个启动监听类,同样放在src/main/groovy下:
import org.springframework.boot.context.event.ApplicationReadyEvent
import org.springframework.context.ApplicationListener

class CGenderMarshallerStartup implements ApplicationListener<ApplicationReadyEvent> {
    private final CGenderJsonMarshaller cGenderJsonMarshaller

    // 构造注入Marshaller
    CGenderMarshallerStartup(CGenderJsonMarshaller cGenderJsonMarshaller) {
        this.cGenderJsonMarshaller = cGenderJsonMarshaller
    }

    @Override
    void onApplicationEvent(ApplicationReadyEvent event) {
        // 应用启动完成后注册Marshaller
        cGenderJsonMarshaller.register()
    }
}

方案二:添加虚拟的id getter

这个方案更简单,但需要注意可能带来的副作用(比如数据绑定场景):
在你的CGender类里添加一个getId()方法,返回复合主键的Map形式,让默认Marshaller能拿到“id”值:

@Resource(uri="/gender",formats = ['json','xml']) 
class CGender implements Serializable { 
    // ... 原有的字段和方法 ...

    // 添加这个方法
    Map getId() {
        [budYear: budYear, genderCode: genderCode]
    }

    // ... 原有的mapping、constraints等 ...
}

额外注意事项

  • 如果你的关联类(比如CProject、CYear)也使用了复合主键,同样会遇到序列化问题,需要用上述方案一并处理。
  • 测试时要覆盖列表渲染、单个资源渲染等场景,确保没有其他序列化异常。

内容的提问来源于stack exchange,提问作者Arish Nepal

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.06 09:22:37