Uploaded image for project: 'Configuration Persistence Service'
  1. Configuration Persistence Service
  2. CPS-1673

Replace OneToMany mapping with ManyToOne in FragmentEntity to improve performance

XMLWordPrintable

      Hibernate is currently issuing two SQL operations when saving fragments which is leading to a 2x performance degradation in write operations. This is the behaviour we are seeing - note the updates of parent_id after inserts:

      insert into fragment (anchor_id,attributes,xpath,id) values (?,?,?,?)
      insert into fragment (anchor_id,attributes,xpath,id) values (?,?,?,?)
      insert into fragment (anchor_id,attributes,xpath,id) values (?,?,?,?)
      update fragment set parent_id = ? where id = ?
      update fragment set parent_id = ? where id = ?
      update fragment set parent_id = ? where id = ?
      

      It is related to our use of a unidirectional @OneToMany mapping for the childFragments in the FragmentEntity class. Hibernate has poor performance in this case. This article https://vladmihalcea.com/the-best-way-to-map-a-onetomany-association-with-jpa-and-hibernate/ gives a good explanation of the cause:

      What’s the purpose of those update statements?
      If you take a look at Hibernate flush order, you’ll see that the persist action is executed before the collection elements are handled. This way, Hibernate inserts the child records first without the Foreign Key since the child entity does not store this information. During the collection handling phase, the Foreign Key column is updated accordingly.

      The solution is to implement a bidirectional OneToMany/ManyToOne mapping. However, care must be taken to maintain both sides of the mapping. FragmentEntity should be changed to prevent direct changes to the childFragments collection. Instead, helper methods should be used:

      public class FragmentEntity implements Serializable {
          // ...
      
          @ManyToOne(fetch = FetchType.LAZY)
          @JoinColumn(name = "parent_id")
          private FragmentEntity parent;
      
          @Setter(AccessLevel.NONE)
          @OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
          private Set<FragmentEntity> childFragments;
      
          public Set<FragmentEntity> getChildFragments() {
              return Collections.unmodifiableSet(this.childFragments);
          }
      
          public void addChildFragment(final FragmentEntity newChildFragment) {
              newChildFragment.parent = this;
              childFragments.add(newChildFragment);
          }
      }
      

            danielhanrahan Daniel Hanrahan
            danielhanrahan Daniel Hanrahan
            Votes:
            0 Vote for this issue
            Watchers:
            1 Start watching this issue

              Created:
              Updated:
              Resolved: