Grails
  1. Grails
  2. GRAILS-7086

NonUniqueObjectException with 1:m relationships and afterInsert event

    Details

    • Type: Bug Bug
    • Status: Open
    • Priority: Major Major
    • Resolution: Unresolved
    • Affects Version/s: 1.3.6
    • Fix Version/s: None
    • Component/s: None
    • Labels:
      None
    • Testcase included:
      yes

      Description

      See the attached project for an example: just run it and then try to create a new InventoryItem instance via the scaffolding.

      This kills the Searchable plugin when indexing cascades from the many side to the one side, because the plugin effectively implements an 'afterInsert' handler to mirror changes in the database to the Lucene index. The problem seems to be that when you fetch the one side (InventoryLocation in the attached project), its 'many' collection is populated with the domain instance that triggered the afterInsert. This has a different identity (it's a different object) from the domain instance being saved. Since the Hibernate session guarantees that two domain instances of the same type with the same ID are the same object, this behaviour causes problems.

      Why would the 'many' collection contain a fresh instance that's different to the one that's just been saved?

        Issue Links

          Activity

          Hide
          Francis McKenzie added a comment -

          Found a workaround - need to add the following to the save method of all controllers:

          searchableService.stopMirroring()
          instance.save()
          searchableService.startMirroring()
          instance.reindex()

          Show
          Francis McKenzie added a comment - Found a workaround - need to add the following to the save method of all controllers: searchableService.stopMirroring() instance.save() searchableService.startMirroring() instance.reindex()
          Hide
          Nicholas Vaidyanathan added a comment -

          Verify that Francis' method seems to work. Is there a way to define an interceptor in the plugin itself that stops mirroring before the save action of any controller and restarts/reindexes after?

          Show
          Nicholas Vaidyanathan added a comment - Verify that Francis' method seems to work. Is there a way to define an interceptor in the plugin itself that stops mirroring before the save action of any controller and restarts/reindexes after?
          Hide
          Nicholas Vaidyanathan added a comment -

          Rather than modifying every save closure, this works as well:

          class SearchableWorkaroundController {

          def searchableService
          def beforeInterceptor = [action:this.&disableIndexing, only:['save']]
          def afterInterceptor = [action:this.&enableIndexing, only:['save']]

          def disableIndexing()

          { searchableService.stopMirroring() }

          def enableIndexing(model)

          { searchableService.stopMirroring() model.reindex() }

          }

          You can then have every controller extend that controller. I'd love to make it abstract, but abstract classes don't participate in dependency injection so you get a null searchableService(). This still feels like a hack though, and it's something the plugin itself should take care of. Probably by defining such a controller and hooking into generate-controller such that and domain classes that have static searchable automatically extend this guy. Or finding a better way, of course.

          Show
          Nicholas Vaidyanathan added a comment - Rather than modifying every save closure, this works as well: class SearchableWorkaroundController { def searchableService def beforeInterceptor = [action:this.&disableIndexing, only: ['save'] ] def afterInterceptor = [action:this.&enableIndexing, only: ['save'] ] def disableIndexing() { searchableService.stopMirroring() } def enableIndexing(model) { searchableService.stopMirroring() model.reindex() } } You can then have every controller extend that controller. I'd love to make it abstract, but abstract classes don't participate in dependency injection so you get a null searchableService(). This still feels like a hack though, and it's something the plugin itself should take care of. Probably by defining such a controller and hooking into generate-controller such that and domain classes that have static searchable automatically extend this guy. Or finding a better way, of course.
          Show
          Graeme Rocher added a comment - Caused by http://opensource.atlassian.com/projects/hibernate/browse/HHH-3843

            People

            • Assignee:
              Unassigned
              Reporter:
              Peter Ledbrook
            • Votes:
              6 Vote for this issue
              Watchers:
              9 Start watching this issue

              Dates

              • Created:
                Updated:
                Last Reviewed:

                Development