2016-08-26 1 views
2

Я смущен относительно того, как реализовать это, или если это действительно возможно. Мой коллега и я строим веб-приложение для клиента с использованием Grails 3. Он создал начальные домены, которые я угадываю, где копия «один-к-одному» из моделей Realm из мобильных приложений. С тех пор я модифицировал их, пытаясь получить некоторую форму глубокого клонирования, поскольку три домена имеют отношения «один ко многим».Глубокая копия доменов «один-ко-многим» в Grails 3

Проблема

Как бы я идти о создании глубокой копии домена? Я попытался предложенные ответы с небольшим успехом:

собирание идей из разных мест, я пришел к разработке метода clone(Domain) показано ниже. Это почти работает (я думаю), но имеет проблемы с коллекциями, бросающими HibernateException - Found shared references to a collection: 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] 
} 

class Location { 
    int id 
    String name 
    String[] hazardsPresent 
    HazardType[] hazardTypes 
    ExposureArea[] exposureArea 
    RiskLevel exposureLevel 
    String comments 
    byte[] picture 

    static Location clone(Location self) { 
     Location clone = new Location() 
     String[] excludes = ['equipment', 'products'] 

     clone.properties = self.properties.findAll { 
      !(it.key in excludes) 
     } 

     self.equipments.each { 
      Equipment copy = Equipment.clone it 
      self.addToEquipments copy 
     } 

     self.products.each { 
      RecommendedProduct copy = new RecommendedProduct() 
      copy.properties = it.properties 
      copy.save() 
      clone.addToProducts copy 
     } 

     clone.save() 
    } 

    static transients = ['clone'] 
    static belongsTo = Survey 
    static hasMany = [equipments: Equipment, products: RecommendedProduct] 
    static constraints = { 
     picture(maxSize: 1024 * 1024) 
    } 
} 

class Equipment { 
    int id 
    EquipmentType type 
    String name 
    Brand brand 

    // Redacted 26 boolean properties 
    // ... 

    static Equipment clone(Equipment self) { 
     Equipment clone = new Equipment() 
     String exclude = "extras" 

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

     self.extras.each { 
      EquipmentQuestionExtra copy = new EquipmentQuestionExtra() 
      copy.properties = it.properties 
      copy.save() 
      clone.addToExtras copy 
     } 

     clone.save() 
    } 

    static transients = ['clone'] 
    static belongsTo = Location 
    static hasMany = [extras: EquipmentQuestionExtra] 
} 

class RecommendedProduct { 
    int productId 
    int quantityChosen 
    String comment 

    static belongsTo = Location 
} 

class EquipmentQuestionExtra { 
    int id 
    String questionText 
    String comment 
    byte[] picture 

    static belongsTo = Equipment 
    static constraints = { 
     picture(maxSize: 1024 * 1024) 
    } 
} 
+0

клонирование, что вы делаете должно быть клонирование каждого объекта независимо от его типа. Следовательно, клонировать коллекции также. –

ответ

0

Прошел почти год, и с тех пор я завершил этот проект с решением этой проблемы.

В решении, которое я придумал, использовалось service layer. Я определил службу для каждого домена. Любой домен, который необходим для глубокой копии коллекции, называется связанным с ним методом службы. Я только размещаю источник двух сервисов, поскольку другие методы по существу одинаковы.

Течение это:

  1. Создать новый пустой экземпляр домена.
  2. Скопируйте все «примитивные» свойства, такие как String, Boolean и т. Д. Через duplicate.properties = original.properties.
  3. Поскольку вышеупомянутое также устанавливает отношения collection/has-many, это приведет к HibernateException об общих коллекциях. Поэтому установите коллекцию в null.
  4. Вызовите связанный метод службы, чтобы скопировать коллекцию/has-many.
  5. Сохраните и верните дублированный домен.

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 
} 
} 

service/LocationService.groovy

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 
    } 
} 
1

Возможно, возникла проблема с вашим методом клонирования: вы клонируете все свойства, включая идентификатор, что является плохой идеей с глубоким клонированием. This thread объясняет, что ваша ошибка возникает, когда объект имеет те же свойства, что и другой, в кэше гибернации, но с другой ссылкой.

Итак, вам просто нужно установить свойство id ваших объектов на null (или исключить его из копии свойств), чтобы заставить hibernate обнаружить, что это новый объект. Если он все еще не работает, вызовите метод discard на свой объект до save.

0

Вы сохраняете сущности детей. Не делайте этого, сохраняйте сущность Survey (root). Другие будут спасены каскадом.

В другой руке, как @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() у вас есть пример того, как связать определенные свойства от другого объекта.

+0

Какова цель проверки нуля с блоком 'if'? Это не имеет смысла для меня, поскольку это единственный метод с подписями 'duplication (Domain)'. Я играю с вашими предложениями, однако он бросает нулевые исключения в коллекциях. –

+0

Извините, что я очистил настоящий код, и я забыл удалить эту строку. – quindimildev

+0

Я видел, как вы спасли детей. Не делайте этого, только сохраняйте сущность Survey. Другие будут спасены каскадом – quindimildev

Смежные вопросы