向Grails中的域对象添加动态字段

向Grails中的域对象添加动态字段,grails,gorm,Grails,Gorm,我试图找到一种向grails域类添加动态字段的方法。我确实根据伯特的文章找到了动态域类插件,但这对于我们的需求来说太多了 假设我们有一个person的域类: class Person extends DynamicExtendableDomainObject { String firstName String lastName static constraints = { firstName(nullable: false, blank: false,

我试图找到一种向grails域类添加动态字段的方法。我确实根据伯特的文章找到了动态域类插件,但这对于我们的需求来说太多了

假设我们有一个person的域类:

class Person extends DynamicExtendableDomainObject {
    String firstName
    String lastName

    static constraints = {
        firstName(nullable: false, blank: false, maxSize: 50)
        lastName(nullable: false, blank: false)
    }
}
现在,客户a希望在此字段中也有一个生日字段。通过使用某种管理工具,他在数据库中添加了这个额外字段

客户b还希望有一个字段中间名,因此他将字段中间名添加到此人

现在我们实现了一个
DynamicExtendableDomainObject
类,Person类继承自该类。这将向继承自该类的每个域类添加一个自定义字段,以将动态属性存储为
JSON
(有点像Perl中的KiokuDB存储它们)

现在,当Person被实例化时,我们希望将这些动态属性添加到Person类中,以便能够使用标准的Grails getter和setter以及这些动态属性的模板函数

因此,在customera上,我们可以使用脚手架,人员将输出名字、姓氏、生日,在customer b上,脚手架将输出名字、姓氏、中间名

属性的存储将通过使用
saveinterceptor
,将这些属性序列化为JSON并存储在特殊字段中来实现


但我们还没有找到在运行时将这些JSON属性动态添加到域类的方法。有什么好办法处理这个问题吗?如果是这样,如何最好地实现这一点?

您可以尝试在运行时通过扩展元类中的getProperty()、setProperty()、setProperties()来将属性添加到类型为
DynamicExtendableDomainObject
的DomainClass中,然后使用beforeUpdate()、beforeInsert()和afterLoad()钩住持久性

例如,在引导(或服务)中:


我不确定我是否理解你的问题,但是你难道不能使用一个映射,每个人记录都有额外的值,而不是摆弄域类吗?你有没有一个使用映射解决方案的例子(或者一个使用这种方法的插件,这样我就可以看一看)?map解决方案如何与验证一起工作?gorm/hibernate如何存储地图?请参阅:hibernate将创建另一个表来存储地图值,类似于
person\u MapName
。我不知道如何进行验证,但是,您必须实现一个自定义的验证器。使用map解决方案,您只有一个键值存储(至少从我对文档的理解来看),并且您不能拥有复杂的对象或数据结构。使用JSON解决方案,您可以在其中拥有更复杂的数据结构。您是对的,它确实是一个键/值存储,我只是认为它可能比摆弄域类更容易。它实际上可以处理复杂的对象,前提是它们是另一个域类的对象,但我认为这会给您带来同样的问题——只在一个单独的域类中。为什么要使用
委托。“${fieldName}”
而不是
所有者。“${fieldName}”
?谢谢
def yourDynamicFieldDefinitionService

for(GrailsClass c in grailsApplication.getDomainClasses()){
    if(DynamicExtendableDomainObject.isAssignableFrom(c.clazz)){
        Set extendedFields = yourDynamicFieldDefinitionService.getFieldsFor(c.clazz)

        //getProperty()
        c.clazz.metaClass.getProperty = { String propertyName ->
            def result
            if(extendedFields.contains(propertyName)){
                result = delegate.getExtendedField(propertyName)
            } else {
                def metaProperty = c.clazz.metaClass.getMetaProperty(propertyName)
                if(metaProperty) result = metaProperty.getProperty(delegate)
            }
            result
        }

        //setProperty()
        c.clazz.metaClass.setProperty = { propertyName , propertyValue ->
                    if(extendedFields.contains(propertyName)){
                        delegate.setExtendedField(propertyName, propertyValue)
                        delegate.blobVersionNumber += 1
                    } else {
                        def metaProperty = c.clazz.metaClass.getMetaProperty(propertyName)
                        if(metaProperty) metaProperty.setProperty(delegate, propertyValue)
                    }
                }

        //setProperties()
                def origSetProperties = c.clazz.metaClass.getMetaMethod('setProperties',List)
                c.clazz.metaClass.setProperties = { def properties ->
                    for(String fieldName in extendedFields){
                        if(properties."${fieldName}"){
                            delegate."${fieldName}" = properties."${fieldName}"
                        }
                    }
                    origSetProperties.invoke(delegate,properties)
                }
    }
}
abstract DynamicExtendableDomainObject {
    String yourBlobField
    Long blobVersionNumber //field to signal hibernate that the instance is 'dirty'

    Object getExtendedField(String fieldName){
        ...
    }

    void setExtendedField(String fieldName, Object value){
        ...
    }

    def afterLoad(){
        //fill your transient storage to support getExtendedField + setExtendedField 
    }

    def beforeUpdate(){
        //serialize your transient storage to yourBlobField
    }

    def beforeInsert(){
        //serialize your transient storage to yourBlobField
    }
}