Announcement

Collapse
No announcement yet.

Hibernate Preload Pattern

Collapse
X
  • Filter
  • Time
  • Show
Clear All
new posts

  • #16
    Die Idee mit dem Preload-Pattern ist zwar sehr nett. Leider hat es, wenn ich das richtig sehe, einen eklatanten Nachteil: mit dem schleifenartigen Durchlaufen durch die Preload-Anforderungen werden immer neue SQLs erzeugt. D.h.: man landet bei dem bekannten "n+1 Select"-Problem!

    Comment


    • #17
      Eine Frage hätte trotzdem ich an den Autor dieses interessantes Pattern.
      Obwohl es schon stimmt, dass man bei der vorgeschlagenen Implementierung beim n+1 Select Problem landet, hat es an sich nichts mit dem Pattern selbst zu tun. Eben nur an der Implementierung.
      Zum Pattern selbst möchte ich Folgendes fragen: es ist zwar schön, wenn ich Autoren, Länder und Bücher habe, also keine reflexive Beziehungen. Wie schaut es aber aus, wenn ich z.B. User und Groups habe, wo meine Groups selbst subGroups haben können? Ein Preload wie Preload myPreload = new Preload(Group.class, "subGroups") würde ja per Definition rekursiv laufen müssen, ohne dass ich die Tiefe einschränken kann, oder?
      MfG,
      Nicolas Damour.

      Comment


      • #18
        Originally posted by nicolas.damour View Post
        Eine Frage hätte trotzdem ich an den Autor dieses interessantes Pattern.
        Obwohl es schon stimmt, dass man bei der vorgeschlagenen Implementierung beim n+1 Select Problem landet, hat es an sich nichts mit dem Pattern selbst zu tun. Eben nur an der Implementierung.
        ...

        Das Problem ist, dass man das n+1-Problem kaum umgehen kann, wenn man dieses Preload-Pattern verwendet. Ich bin inzwischen wieder dazu übergegangen, in den DAOs spezialisierte HQLs (JOIN FETCH) zu schreiben, wenn es möglich ist, dass an der Stelle ein großer Objektbaum abgefragt werden könnte. Und das ist halt meistens der Fall.

        Comment


        • #19
          Das n+1 Select Problem lässt sich umgehen, indem das Preload-Pattern etwas abgeändert wird.
          Die Idee ist folgende:
          Bevor crit.list() aufgerufen wird, werden vorher die nötigen joins angegeben, die über eine vorher definierte JoinSpezifikation dem GenericDao mitgegeben wird.
          Die JoinSpezifikation ist damit sehr ähnlich zur Preload-Klasse:

          Code:
          /**
           * Join specification for pre load functionality.
           * 
           * @author roli.hof
           * @date 19.02.2009
           * 
           */
          public class JoinSpec {
            String associationPath;
            String joinProperty;
            String alias;
            int criteriaSpec;
            FetchMode fetchMode;
          
            /**
             * Static definition of JoinSpec-Array for joining Country to Author
             * and Author to Book.
             */
            public static final JoinSpec[] BOOK_AUTHOR_COUNTRY =
                {new JoinSpec("", Book.COLUMNS.AUTHOR, Author.ALIAS,
                    CriteriaSpecification.LEFT_JOIN, FetchMode.JOIN),
                 new JoinSpec(Author.ALIAS + ".", Author.COLUMNS.COUNTRY, Country.ALIAS,
                    CriteriaSpecification.LEFT_JOIN, FetchMode.JOIN) };
          
            /**
             * Constructor.
             * 
             * @param associationPath
             *          the path to the joinProperty (inclusive the ending dot ".")
             * @param joinProperty
             *          the property to join
             * @param alias
             *          the generated alias for further joins
             * @param criteriaSpec
             *          the criteria specification (leftjoin, innerjoin, ...)
             * @param fetchMode
             *          the fetch mode (Eager, lazy, join, ...)
             */
            public JoinSpec(String associationPath, String joinProperty, String alias,
                int criteriaSpec, FetchMode fetchMode) {
              super();
              this.associationPath = associationPath;
              this.joinProperty = joinProperty;
              this.alias = alias;
              this.criteriaSpec = criteriaSpec;
              this.fetchMode = fetchMode;
            }
           .
           .
           // Setter and Getter
           .
           .
            /**
             * Adds the entries of the JoinSpec-array to the given criteria.
             * 
             * @param crit
             *          the criteria to extend
             * @param joinSpecs
             *          the JoinSpec array to extend to the criteria
             * @return the criteria for method chaining
             */
            public static Criteria addCriteria(Criteria crit, JoinSpec[] joinSpecs) {
              for (JoinSpec spec : joinSpecs) {
                crit.createAlias(spec.getAssociationPath() + spec.getJoinProperty(),
                    spec.getAlias(), spec.getCriteriaSpec());
                crit.setFetchMode(spec.getJoinProperty(), spec.getFetchMode());
              }
              return crit;
            }
          
            /**
             * Adds the entries of the JoinSpec-array to the given detached criteria.
             * 
             * @param crit
             *          the criteria to extend
             * @param joinSpecs
             *          the JoinSpec array to extend to the criteria
             * @return the criteria for method chaining
             */
            public static DetachedCriteria addCriteria(DetachedCriteria crit,
                  JoinSpec[] joinSpecs) {
              for (JoinSpec spec : joinSpecs) {
                crit.createAlias(spec.getAssociationPath() + spec.getJoinProperty(), 
                    spec.getAlias(), spec.getCriteriaSpec());
                crit.setFetchMode(spec.getJoinProperty(), spec.getFetchMode());
              }
              return crit;
            }
          }
          Der GenericDao sieht dann für die findById folgendermaßen aus:
          Code:
          public <T extends AbstractPersistentObject> T findById(final Class<T>
                persistentClass, final long id, final JoinSpec[] joinSpecs) {
              return (T)getHibernateTemplate().execute(new HibernateCallback() {
                @Override
                public Object doInHibernate(Session session) throws HibernateException,
                    SQLException {
                  Criteria crit = session.createCriteria(persistentClass);
                  crit.add(Restrictions.idEq(id));
                  if (joinSpecs != null) {
                    crit = JoinSpec.addCriteria(crit, joinSpecs);
                    crit.setFetchSize(100);
                    crit.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
                  }
                  return crit.uniqueResult();
                }
              });
            }
          Damit lässt sich das n+1 Select Problem bereinigen.
          Achtung, dies funktioniert nicht für Verschachtellungen mit undefinierter Tiefe.

          mfg Roland Hofmann

          Comment

          Working...
          X