Grails3中一对多域的深度拷贝

Grails3中一对多域的深度拷贝,grails,gorm,Grails,Gorm,我不知道该如何实现,或者它是否真的可能/合适。我的同事和我正在使用Grails3为客户机构建一个web应用程序。他创建了最初的域名,我猜这几乎是移动应用程序领域模型的一对一副本。后来我对它们进行了修改,试图让某种形式的深度克隆工作起来,因为三个域之间有一对多的关系 问题 我将如何着手创建域的深度副本?我尝试过建议的答案,但收效甚微: 从各个地方收集想法,我来制定一个clone(Domain)方法,如下所示。它几乎可以工作(我想),但在集合抛出一个hibernateeexception-

我不知道该如何实现,或者它是否真的可能/合适。我的同事和我正在使用Grails3为客户机构建一个web应用程序。他创建了最初的域名,我猜这几乎是移动应用程序领域模型的一对一副本。后来我对它们进行了修改,试图让某种形式的深度克隆工作起来,因为三个域之间有一对多的关系

问题

我将如何着手创建域的深度副本?我尝试过建议的答案,但收效甚微:

从各个地方收集想法,我来制定一个
clone(Domain)
方法,如下所示。它几乎可以工作(我想),但在集合抛出一个
hibernateeexception-Found对集合的共享引用时有问题:Location.equipments

在控制器中调用为:

def copy() {
    Survey.clone(Survey.get(params.id))
    redirect action: 'index'
}
有什么想法或指导吗

目前,域如下所示:

class Survey {

    int id
    String name
    String contactName
    String contactEmail
    String facilityAddress
    String facilityCity
    String facilityStateProvince
    String facilityZip
    String distributorName
    String distributorEmail
    String distributorPhoneNumber

    static Survey clone(Survey self) {
        Survey clone = new Survey()
        String exclude = "locations"

        clone.properties = self.properties.findAll {
            it.key != exclude
        }

        self.locations.each {
            Location copy = Location.clone it
            clone.addToLocations copy
        }

        clone.save()
    }

    static transients = ['clone']
    static belongsTo = User
    static hasMany = [locations: Location]
}





克隆方法可能有问题:克隆“所有”属性,包括ID,这对于深度克隆是个坏主意。解释当一个对象与hibernate缓存中的另一个对象具有相同的属性,但具有另一个引用时,会引发错误


因此,您只需将对象的
id
属性设置为
null
(或将其从属性副本中排除),即可强制hibernate检测到它是新对象。如果仍不起作用,请在保存子实体之前,在对象上调用该方法。不要这样做,只保存实体调查(根)。其他将通过级联保存

另一方面,正如@Joch所说,在这种情况下,使用克隆不是正确的方法

您应该为您的实体创建一个重复的方法。下面是一个如何克隆此结构类型的示例。这是一个有n个问题的测试,每个问题有n个答案,每堂课都有一个“复制”方法

class Test {

    String name

    static hasMany = [
            /**
             * Each question of the test
             */
            questions: Question
    ]

    /**
     * Duplicates this test
     */
    Test duplicate(){
         Test test = new Test(name:this.name)
         this.questions.each{ question ->
             test.addToQuestions(question.duplicate(test))
         }
         test
    }
}


class Question {

    Integer questionOrder
    String title

    /**
     * Each question belong to a Test
     */
    static belongsTo = [test:Test]

    static hasMany = [
            /**
             * Each answer of the test
             */
            answers: Answer
    ]

    /**
     * Duplicates this test to another edition
     * @param edition to be duplicated
     * @return duplicated question
     */
    Question duplicate(Test test){
        if(test){
            Question question = new Question(title:this.title)
            this.answers.each{ answer->
                question.addToAnswers(answer.duplicate())
            }
            test.addToQuestions(question)
            question
        }
    }
}

class Answer {

    String title
    boolean correct
    /**
     * Each answer belongs to a question
     */
    static belongsTo = [question:Question]

    /**
     * Duplicates this answer to another question
     * @param edition to be duplicated
     * @return duplicated question
     */
    Answer duplicate(){
        Answer answer = new Answer()
        answer.properties['title','correct'] = this.properties['title','answerOrder','correct']
        answer
    }
}

在Answer.duplicate()中,您有一个如何从其他对象绑定某些属性的示例。

差不多一年了,我已经完成了这个项目,并找到了解决这个问题的方法

我想出的解决办法是利用。我为每个域定义了一个服务。需要深度复制集合的任何域,称为其关联的服务方法。我只是发布了两个服务的来源,因为其他方法基本上是相同的

流程如下:

  • 创建域的新空白实例
  • 通过
    duplicate.properties=original.properties
    复制所有“基本”属性,如
    String
    Boolean
  • 由于上面还设置了集合/具有许多关系,这将导致关于共享集合的
    hibernateeexception
    。因此,将集合设置为
    null
  • 调用关联的服务方法来复制集合/has many
  • 保存并返回重复的域
  • service/SurveyService.groovy

    class SurveyService {
    /**
     * Attempts to perform a deep copy of a given survey
     *
     * @param survey The survey instance to duplicate
     * @return The duplicated survey instance
     */
    Survey duplicateSurvey(Survey originalSurvey) {
        Survey duplicatedSurvey = new Survey()
    
        duplicatedSurvey.properties = originalSurvey.properties
        duplicatedSurvey.locations = null
        duplicatedSurvey.uuid = UUIDGenerator.createUniqueId()
        duplicatedSurvey.dateModified = DateUtil.getCurrentDate()
        duplicatedSurvey.name = "${originalSurvey.name.replace("(copy)", "").trim()} (copy)"
        duplicatedSurvey.save()
        duplicatedSurvey.locations = duplicateLocations originalSurvey.locations, duplicatedSurvey
        duplicatedSurvey.save()
    }
    
    /**
     * Attempts to perform a deep copy of a survey's location
     *
     * @param originalLocations The original location set
     * @param duplicatedSurvey The duplicated survey that each survey will belong to
     * @return The duplicated location set
     */
    Set<Location> duplicateLocations(Set<Location> originalLocations, Survey duplicatedSurvey) {
        Set<Location> duplicatedLocations = []
    
        for (originalLocation in originalLocations) {
            duplicatedLocations << locationService.duplicateLocation(originalLocation, duplicatedSurvey)
        }
    
        duplicatedLocations
    }
    }
    
    class LocationService {
        /**
         * Performs a deep copy of a given location. The duplicated location name is
         * the original location name and the duplicated location ID.
         *
         * @param originalLocation The location to duplicate
         * @param survey The survey that the location will belong to
         * @return The duplicated location
         */
        Location duplicateLocation(Location originalLocation, Survey survey = null) {
            Location duplicatedLocation = new Location()
            duplicatedLocation.properties = originalLocation.properties
            duplicatedLocation.survey = survey ?: duplicatedLocation.survey
            duplicatedLocation.uuid = UUIDGenerator.createUniqueId()
            duplicatedLocation.dateModified = DateUtil.currentDate
            duplicatedLocation.equipments = null
            duplicatedLocation.products = null
            duplicatedLocation.save()
            duplicatedLocation.name = "${originalLocation.name.replace("(copy)", "").trim()} (copy)"
            duplicatedLocation.equipments = duplicateEquipment originalLocation.equipments, duplicatedLocation
            duplicatedLocation.products = duplicateProducts originalLocation, duplicatedLocation
            duplicatedLocation.save()
    
            duplicatedLocation
        }
    
        /**
         * Performs a deep copy of a given locations equipments.
         *
         * @param originalEquipments The original locations equipments
         * @param duplicatedLocation The duplicated location; needed for belongsTo association
         * @return The duplicated equipment set.
         */
        Set<Equipment> duplicateEquipment(Set<Equipment> originalEquipments, Location duplicatedLocation) {
            Set<Equipment> duplicatedEquipments = []
    
            for (originalEquipment in originalEquipments) {
                Equipment duplicatedEquipment = new Equipment()
                duplicatedEquipment.properties = originalEquipment.properties
                duplicatedEquipment.uuid = UUIDGenerator.createUniqueId()
                duplicatedEquipment.dateModified = DateUtil.currentDate
                duplicatedEquipment.location = duplicatedLocation
                duplicatedEquipment.extras = null
                duplicatedEquipment.save()
                duplicatedEquipment.name = "${originalEquipment.name.replace("(copy)", "").trim()} (copy)"
                duplicatedEquipment.extras = duplicateExtras originalEquipment.extras, duplicatedEquipment
                duplicatedEquipments << duplicatedEquipment
            }
    
            duplicatedEquipments
        }
    
        /**
         * Performs a deep copy of a given locations extras.
         *
         * @param originalExtras The original location extras
         * @param duplicatedEquipment The duplicated equipment; needed for belongsTo association
         * @return The duplicated extras set.
         */
        Set<EquipmentQuestionExtra> duplicateExtras(Set<EquipmentQuestionExtra> originalExtras, Equipment duplicatedEquipment) {
            Set<EquipmentQuestionExtra> duplicatedExtras = []
    
            for (originalExtra in originalExtras) {
                EquipmentQuestionExtra duplicatedExtra = new EquipmentQuestionExtra()
                duplicatedExtra.properties = originalExtra.properties
                duplicatedExtra.equipment = duplicatedEquipment
                duplicatedExtra.uuid = UUIDGenerator.createUniqueId()
                duplicatedExtra.dateModified = DateUtil.currentDate
                duplicatedExtra.save()
                duplicatedExtras << duplicatedExtra
            }
    
            duplicatedExtras
        }
    
        /**
         * Performs a deep copy of a given locations products.
         *
         * @param originalLocation The original location
         * @param duplicatedLocation The duplicated location
         * @return The duplicated product set.
         */
        Set<RecommendedProduct> duplicateProducts(Location originalLocation, Location duplicatedLocation) {
            Set<RecommendedProduct> originalProducts = originalLocation.products
            Set<RecommendedProduct> duplicatedProducts = []
    
            for (originalProduct in originalProducts) {
                RecommendedProduct duplicatedProduct = new RecommendedProduct()
                duplicatedProduct.properties = originalProduct.properties
                duplicatedProduct.location = duplicatedLocation
                duplicatedProduct.uuid = UUIDGenerator.createUniqueId()
                duplicatedProduct.dateModified = DateUtil.currentDate
                duplicatedProduct.save()
                duplicatedProducts << duplicatedProduct
            }
    
            duplicatedProducts
        }
    }
    

    您正在进行的克隆应该是克隆每个对象,而不考虑其类型。因此,还要克隆集合。使用
    if
    块检查null的目的是什么?这对我来说没有意义,因为它是唯一具有签名
    复制(域)
    的方法。我一直在玩弄你的建议,但是它会在集合上抛出null异常。很抱歉,我清理了一个真实的代码,并且忘记删除该行。我看到你正在保存子实体。不要这样做,只保存实体调查。其他人会因为为我工作而得救。非常感谢,我在这个问题上浪费了大约半天的时间。
    class EquipmentQuestionExtra {
        int id
        String questionText
        String comment
        byte[] picture
    
        static belongsTo = Equipment
        static constraints = {
            picture(maxSize: 1024 * 1024)
        }
    }
    
    class Test {
    
        String name
    
        static hasMany = [
                /**
                 * Each question of the test
                 */
                questions: Question
        ]
    
        /**
         * Duplicates this test
         */
        Test duplicate(){
             Test test = new Test(name:this.name)
             this.questions.each{ question ->
                 test.addToQuestions(question.duplicate(test))
             }
             test
        }
    }
    
    
    class Question {
    
        Integer questionOrder
        String title
    
        /**
         * Each question belong to a Test
         */
        static belongsTo = [test:Test]
    
        static hasMany = [
                /**
                 * Each answer of the test
                 */
                answers: Answer
        ]
    
        /**
         * Duplicates this test to another edition
         * @param edition to be duplicated
         * @return duplicated question
         */
        Question duplicate(Test test){
            if(test){
                Question question = new Question(title:this.title)
                this.answers.each{ answer->
                    question.addToAnswers(answer.duplicate())
                }
                test.addToQuestions(question)
                question
            }
        }
    }
    
    class Answer {
    
        String title
        boolean correct
        /**
         * Each answer belongs to a question
         */
        static belongsTo = [question:Question]
    
        /**
         * Duplicates this answer to another question
         * @param edition to be duplicated
         * @return duplicated question
         */
        Answer duplicate(){
            Answer answer = new Answer()
            answer.properties['title','correct'] = this.properties['title','answerOrder','correct']
            answer
        }
    }
    
    class SurveyService {
    /**
     * Attempts to perform a deep copy of a given survey
     *
     * @param survey The survey instance to duplicate
     * @return The duplicated survey instance
     */
    Survey duplicateSurvey(Survey originalSurvey) {
        Survey duplicatedSurvey = new Survey()
    
        duplicatedSurvey.properties = originalSurvey.properties
        duplicatedSurvey.locations = null
        duplicatedSurvey.uuid = UUIDGenerator.createUniqueId()
        duplicatedSurvey.dateModified = DateUtil.getCurrentDate()
        duplicatedSurvey.name = "${originalSurvey.name.replace("(copy)", "").trim()} (copy)"
        duplicatedSurvey.save()
        duplicatedSurvey.locations = duplicateLocations originalSurvey.locations, duplicatedSurvey
        duplicatedSurvey.save()
    }
    
    /**
     * Attempts to perform a deep copy of a survey's location
     *
     * @param originalLocations The original location set
     * @param duplicatedSurvey The duplicated survey that each survey will belong to
     * @return The duplicated location set
     */
    Set<Location> duplicateLocations(Set<Location> originalLocations, Survey duplicatedSurvey) {
        Set<Location> duplicatedLocations = []
    
        for (originalLocation in originalLocations) {
            duplicatedLocations << locationService.duplicateLocation(originalLocation, duplicatedSurvey)
        }
    
        duplicatedLocations
    }
    }
    
    class LocationService {
        /**
         * Performs a deep copy of a given location. The duplicated location name is
         * the original location name and the duplicated location ID.
         *
         * @param originalLocation The location to duplicate
         * @param survey The survey that the location will belong to
         * @return The duplicated location
         */
        Location duplicateLocation(Location originalLocation, Survey survey = null) {
            Location duplicatedLocation = new Location()
            duplicatedLocation.properties = originalLocation.properties
            duplicatedLocation.survey = survey ?: duplicatedLocation.survey
            duplicatedLocation.uuid = UUIDGenerator.createUniqueId()
            duplicatedLocation.dateModified = DateUtil.currentDate
            duplicatedLocation.equipments = null
            duplicatedLocation.products = null
            duplicatedLocation.save()
            duplicatedLocation.name = "${originalLocation.name.replace("(copy)", "").trim()} (copy)"
            duplicatedLocation.equipments = duplicateEquipment originalLocation.equipments, duplicatedLocation
            duplicatedLocation.products = duplicateProducts originalLocation, duplicatedLocation
            duplicatedLocation.save()
    
            duplicatedLocation
        }
    
        /**
         * Performs a deep copy of a given locations equipments.
         *
         * @param originalEquipments The original locations equipments
         * @param duplicatedLocation The duplicated location; needed for belongsTo association
         * @return The duplicated equipment set.
         */
        Set<Equipment> duplicateEquipment(Set<Equipment> originalEquipments, Location duplicatedLocation) {
            Set<Equipment> duplicatedEquipments = []
    
            for (originalEquipment in originalEquipments) {
                Equipment duplicatedEquipment = new Equipment()
                duplicatedEquipment.properties = originalEquipment.properties
                duplicatedEquipment.uuid = UUIDGenerator.createUniqueId()
                duplicatedEquipment.dateModified = DateUtil.currentDate
                duplicatedEquipment.location = duplicatedLocation
                duplicatedEquipment.extras = null
                duplicatedEquipment.save()
                duplicatedEquipment.name = "${originalEquipment.name.replace("(copy)", "").trim()} (copy)"
                duplicatedEquipment.extras = duplicateExtras originalEquipment.extras, duplicatedEquipment
                duplicatedEquipments << duplicatedEquipment
            }
    
            duplicatedEquipments
        }
    
        /**
         * Performs a deep copy of a given locations extras.
         *
         * @param originalExtras The original location extras
         * @param duplicatedEquipment The duplicated equipment; needed for belongsTo association
         * @return The duplicated extras set.
         */
        Set<EquipmentQuestionExtra> duplicateExtras(Set<EquipmentQuestionExtra> originalExtras, Equipment duplicatedEquipment) {
            Set<EquipmentQuestionExtra> duplicatedExtras = []
    
            for (originalExtra in originalExtras) {
                EquipmentQuestionExtra duplicatedExtra = new EquipmentQuestionExtra()
                duplicatedExtra.properties = originalExtra.properties
                duplicatedExtra.equipment = duplicatedEquipment
                duplicatedExtra.uuid = UUIDGenerator.createUniqueId()
                duplicatedExtra.dateModified = DateUtil.currentDate
                duplicatedExtra.save()
                duplicatedExtras << duplicatedExtra
            }
    
            duplicatedExtras
        }
    
        /**
         * Performs a deep copy of a given locations products.
         *
         * @param originalLocation The original location
         * @param duplicatedLocation The duplicated location
         * @return The duplicated product set.
         */
        Set<RecommendedProduct> duplicateProducts(Location originalLocation, Location duplicatedLocation) {
            Set<RecommendedProduct> originalProducts = originalLocation.products
            Set<RecommendedProduct> duplicatedProducts = []
    
            for (originalProduct in originalProducts) {
                RecommendedProduct duplicatedProduct = new RecommendedProduct()
                duplicatedProduct.properties = originalProduct.properties
                duplicatedProduct.location = duplicatedLocation
                duplicatedProduct.uuid = UUIDGenerator.createUniqueId()
                duplicatedProduct.dateModified = DateUtil.currentDate
                duplicatedProduct.save()
                duplicatedProducts << duplicatedProduct
            }
    
            duplicatedProducts
        }
    }