Announcement

Collapse
No announcement yet.

Hibernate Preload Pattern

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

  • Hibernate Preload Pattern

    Im JavaMagazin 4.08 wurde das Preload Pattern für Hibernate beschrieben. Das Pattern würde sich für meine Zwecke gut eignen, allerdings habe ich Probleme bei der Umsetzung.

    Ich habe den Code eigentlich 1 zu 1 übernommen wie er im Artikel abgebildet ist. Wenn ich aber nun die Methode findAll() aufrufe dann initialisiert er die Objekte trotzdem nicht so wie erhofft.

    Der Grund liegt in folgenden Codezeilen die eigentlich eine Endlosrekursion verhindern sollen:

    Code:
    if(Hibernate.isInitialized(entity)) {
       return;
    }
    Beim Aufruf von findAll() wird per crit.list() die Liste der Entities geholt und dann damit preload() aufgerufen. Allerdings ist der Aufruf von Hibernate.isInitialized() mit dieser Liste true und preload kehrt unverrichteter Dinge zurück.

    Habe ich einen Denkfehler, oder bin ich da auf einen Fehler im Artikel gestoßen?

    lg, gurks

  • #2
    hi,

    was für Entities holst du aus dieser Liste?
    platziere hir ein bisschen mehr code, dann wird es leichter dein problem zu verstehen.

    Comment


    • #3
      Hallo,

      Ups, im Forum-Enthusiasmus bin ich wohl etwas ungenau geworden Aber kein Problem, dann werd ich mal ein wenig ausführlicher werden.

      Entität:
      Code:
      @Entity
      public class User extends PersistentEntity {
      	
      	private String login = null;
      	
      	private String encryptedPassword = null;
      
      	public User() {
      
      	}
      
      	...Getter und Setter.
      
      }
      Generisches DAO:
      Code:
      public class GenericDAOHibernate<T extends PersistentEntity, ID extends Serializable> {
      
      	...
      
      	public List<T> findAll(Preload[] preloads) {
      		return findByCriteria(preloads);
      	}
      
      	protected List<T> findByCriteria(Preload[] preloads, Criterion... criterion) {
      		Criteria crit = getSession().createCriteria(getPersistentClass());
      		for (Criterion c : criterion) {
      			crit.add(c);
      		}
      
      		List<T> result = crit.list();
      		preload(result, preloads);
      		return result;
      	}
      
      	...
      
      	// Code aus Java-Magazin 4.08
      	protected void preload(Object entity, Preload[] preloads) {
      		if (entity == null) {
      			return;
      		}
      
      		if (Hibernate.isInitialized(entity)) {
      			return;
      		}
      
      		Hibernate.initialize(entity);
      
      		if ((preloads == null) || (preloads.length == 0)) {
      			return;
      		}
      
      		if (entity instanceof Collection) {
      			for (Object resultEntity : (Collection<?>) entity) {
      				preload(resultEntity, preloads);
      			}
      		} else {
      			for (Preload preload : preloads) {
      				if (preload.getModelClass().isInstance(entity)) {
      					Object getterResult = invokeGetter(entity, preload);
      					preload(getterResult, preloads);
      				}
      			}
      		}
      	}
      
      	...
      
      }
      Mein Problem tritt nun bei folgender Verwendung des oben gezeigten Codes auf (Anm.: UserDAOHibernate extended GenericDAOHibernate):

      Code:
      UserDAOHibernate uDAO = new UserDAOHibernate(session);
      List<User> users = uDAO.findAll(preloads);
      Beim Aufruf von findAll wird nach dem Laden der User-Entitäten aus der Datenbank die Methode preload mit der geladenen Liste als Parameter aufgerufen. Bei der fett markierten Stelle kehrt preload jedoch schon zurück, ohne eventuelle Preloads durchzuführen. Ich sehe irgendwie nicht, wo der Fehler liegen könnte. Es scheint, als wäre jede gerade aus der DB geladene Liste bereits initialisiert und deshalb "bricht" preload ab.

      Hoffe mein Problem ist jetzt etwas klarer geworden.

      lg, gurks

      Comment


      • #4
        hi,

        sorry für spätere antwort, ich war im urlaub.

        so wie ich deine klasse 'user' verstehe, hat 'user' keine relationen zu anderen persistenten objekten. ich gehe davon aus (ich bin mir sogar zu 100 % sicher), dass an dieser stelle
        List<T> result = crit.list()
        alles aus db geladen wird bzw. 'user'-objekt und seine attribute werden initialisiert. deswegen wird an dieser stelle
        if (Hibernate.isInitialized(entity)) {
        return;
        }
        korrekterweise abgebrochen.

        angenommen hätte dein 'user' noch ein attribut 'adresse' vom typ Adresse
        public class User extends PersistentEntity {

        private String login = null;

        private String encryptedPassword = null;

        private Adresse adresse;
        dann würde adresse nachgeladen, wenn das nachladen von adresse bereits nicht im hibernate-mapping xml-datei vorkonfiguriert ist.

        Comment


        • #5
          Hallo,

          Danke für Deine Antwort. Soweit wär mir das klar. Aber hab das nun ausprobiert mit der hinzugefügten Liste und Hibernate.isInitialize() reagiert leider immer noch nicht so wie gewünscht. Also obwohl die Liste noch nicht initialisiert ist (auch nachgebrüft durch Debugging), kehrt die preload-Methode zu früh zurück.

          Allerdings habe ich mir jetzt selbst geholfen und hab das Pattern ohne Hilfe des Hibernate.isInitialized() implementiert, indem ich mir in einem Set merke, welche Objekte ich bereits mit preload "besucht" habe. Das funkioniert auch sehr gut. Einziger Nachteil bzw. Unschönheit: Ich muss vor jeder Operation, welche Teile vorausladen soll das Set leeren.

          Hier noch schnell der Code für Interessierte:

          Code:
          public abstract class GenericDAOHibernate<T, ID extends Serializable>
          		implements GenericDAO<T, ID> {
          
          	private Class<T> persistentClass;
          
          	private Session session;
          
          	private Set<Object> visited = null;
          
          	@SuppressWarnings("unchecked")
          	public GenericDAOHibernate() {
          		this.persistentClass = (Class<T>) ((ParameterizedType) getClass()
          				.getGenericSuperclass()).getActualTypeArguments()[0];
          		this.visited = new HashSet<Object>();
          	}
          	
          	...
          	
          	@SuppressWarnings("unchecked")
          	public List<T> findAll(Preload[] preloads) {
          		return findByCriteria(preloads);
          	}
          	
          	@SuppressWarnings("unchecked")
          	protected List<T> findByCriteria(Preload[] preloads, Criterion... criterion) {
          		Criteria crit = getSession().createCriteria(getPersistentClass());
          		for (Criterion c : criterion) {
          			crit.add(c);
          		}
          
          		List<T> result = crit.list();
          		visited.clear();]
          		preload(result, preloads);
          		return result;
          	}
          	
          	...
          	
          	protected void preload(Object entity, Preload[] preloads) {
          		if (entity == null) {
          			return;
          		}
          
          		// solution given in Java Magazin 4.08 ... did not work
          		// if (Hibernate.isInitialized(entity)) {
          		//    return;
          		// }
          
          		if (visited.contains(entity)) {
          			return;
          		}
          
          		Hibernate.initialize(entity);
          		visited.add(entity);
          
          		if ((preloads == null) || (preloads.length == 0)) {
          			return;
          		}
          
          		if (entity instanceof Collection) {
          			for (Object resultEntity : (Collection<?>) entity) {
          				preload(resultEntity, preloads);
          			}
          		} else {
          			for (Preload preload : preloads) {
          				if (preload.getModelClass().isInstance(entity)) {
          					Object getterResult = invokeGetter(entity, preload);
          					preload(getterResult, preloads);
          				}
          			}
          		}
          	}	
          }
          Vielleicht ist es nicht die schönste Lösung, aber sie funktioniert

          lg, gurks

          Comment


          • #6
            Hallo gurks,

            ich denke, Du hättest Dir das mit der Liste sparen können. Es werden eh nur die Preloads durchlaufen, es kommt zu keiner Endlosschleife.

            Die Abfrage "isInitialized" ist denke ich eher aus Performanz-Gründen an der Stelle. Leider werden Collections die man sich per "findAll" oder so holt, als initialisiert angesehen, d.h. der Check liefert true. Das Auskommentieren der folgenden Zeilen hätte also genügt:

            Code:
            // solution given in Java Magazin 4.08 ... did not work
            // if (Hibernate.isInitialized(entity)) {
            //    return;
            // }
            Für mich hat das auf jeden Fall bisher funktioniert. Falls jemand anderer Meinung ist, darf er mich gerne korrigieren.

            Kleiner Zusatz:
            Wenn man sich ein einzelnes Objekt holt, und dieses enthält eine Collection, dann tritt das Problem übrigens von Haus aus auch mit dem Originalcode nicht auf!

            Was mich auch interessiert ist, wieso kommt Code, der so offensichtlich nicht korrekt funktioniert ins Java-Magazin?
            Vielleicht machen ja wir selber was falsch? Kann ich mir an der Stelle allerdings nicht vorstellen.
            Zuletzt editiert von amayr; 07.04.2008, 10:21.

            Comment


            • #7
              hi,

              ich habe zwar dies nicht ausprobiert, aber die vorgestellte implementierung kam mir auch irgendwie nicht ganz sauber vor . muss mal nachmachen

              Wenn man sich ein einzelnes Objekt holt, und dieses enthält eine Collection, dann tritt das Problem übrigens von Haus aus auch mit dem Originalcode nicht auf!
              genau

              ... wieso kommt Code, der so offensichtlich nicht korrekt funktioniert ins Java-Magazin?
              dann brauchen sie neben einem redakteur auch einen tester.
              den job könnte ich gern übernehmen

              Comment


              • #8
                Originally posted by jado View Post
                dann brauchen sie neben einem redakteur auch einen tester.
                den job könnte ich gern übernehmen
                Naja das wäre eigentlich die Aufgabe des Autors.

                Comment


                • #9
                  Originally posted by amayr View Post
                  Das Auskommentieren der folgenden Zeilen hätte also genügt:

                  Code:
                  // solution given in Java Magazin 4.08 ... did not work
                  // if (Hibernate.isInitialized(entity)) {
                  //    return;
                  // }
                  Ich denke, die Zeile ist deshalb drin, um Endlosschleifen beim Preload zu verhindern. So steht es jedenfalls im Artikel. Aber wie gesagt, es hat bei mir halt nicht funktioniert Vielleicht hab ich's auch einfach nicht zu 100% verstanden.

                  Comment


                  • #10
                    Hi amyr,

                    wie es aussieht, verwendest du den EntityManager von Hibernate. Habe mal einen Schnellversuch damit gemacht. Es ergeben sich hier tatsächlich Unterschiede zu Native-Hibernate. Bis jetzt haben wir noch nie JPA eingesetzt.

                    Die Lösung mit dem Set ist übrigens eine unserer Alternativen, die wir auch schon verwendet haben.

                    Das Hibernate.isInitialized() ist nicht nur eine Performanceoptimierung. Etwas in der Art wird immer benötigt, wenn die zu preloadenden Entitys eine geschlossene Schleife bilden können. Dann entsteht eine Endlosschleife.
                    Wie gesagt, das mit dem Set ist auch in Ordnung. Können keine Endlosschleifen auftreten, kann man das auch ganz weglassen.

                    Inzwischen haben wir das Verfahren allerdings ganz umgestellt und arbeiten mit Hibernate Events.
                    Wir klinken einen PostLoadEventListener ein, etwa so:
                    Code:
                    public class PreloadEventListener extends DefaultPostLoadEventListener {
                    
                      private static ThreadLocal<Preload[]> preloadsThreadLocal = new ThreadLocal<Preload[]>();
                    
                      public static void setPreloads(Preload[] preloads) {
                        preloadsThreadLocal.set(preloads);
                      }
                    
                      public static void clearPreloads() {
                        preloadsThreadLocal.set(null);
                      }
                    
                      protected Object callGetter(Object entity, Preload preload) {
                    
                        try {
                          return preload.callGetter(entity);
                        } catch (Exception ex) {
                          String msg = "Can't invoke getter for property: " + preload.getProperty();
                          throw new PreloadException(msg, ex);
                        }
                      }
                    
                      public void onPostLoad(PostLoadEvent event) {
                    
                        Object entity = event.getEntity();
                    
                        Preload[] preloads = preloadsThreadLocal.get();
                    
                        if( preloads != null ) {
                          for (Preload preload : preloads) {
                            if (preload.getModelClass().isInstance(entity)) {
                              Object getterResult = callGetter(entity, preload);
                              Hibernate.initialize(getterResult);
                            }
                          }
                        }
                    
                        super.onPostLoad(event);
                      }
                    }
                    Im DAO wird dann z.B. in findByCriteria folgender Code eingeführt.

                    Code:
                    ...
                    PreloadEventListener.setPreloads(preloads);
                    result = crit.list();
                    PreloadEventListener.clearPreloads();
                    ...
                    Das ganze hat den großen Vorteil, dass sich Hibernate selbst um das Rekursionsproblem kümmert. Allerdings ist der Code bei uns noch nicht produktiv. Bis jetzt sieht's aber gut aus.

                    Gruß

                    Jürgen

                    Comment


                    • #11
                      Ah danke für die Nachbesserung. Werde das bei uns bei Gelegenheit auch versuchen.

                      Comment


                      • #12
                        NHibernate und Preload Pattern

                        Hallo!

                        (Ich weiß die Frage passt hier nicht GANZ genau her da es ja bei mir um
                        NHibernate geht ... aber ich hoffe es wird verziehen )

                        Ich habe den Preload Pattern in NHibernate "nachgebaut"
                        und es funktioniert für Collections auch wunderbar, nur beim Preloaden
                        von Objekten gibt es Probleme.

                        in der "InvokeGetter" Methode verwende ich folgenden C# Code:

                        MethodInfo getMethod = _preload.ModelType.GetProperty(_preload.Property)
                        .GetGetMethod();

                        object obj = getMethod.Invoke(_entity, (Object[])null);

                        Wenn ich jetzt eine generische Collection (Set) Preloade ist "obj" nach dem Aufruf
                        vom Typ der generischen Collection und enthält die erwarteten Objekte.

                        Wenn ich ein Objekt (many-to-one) Preloade ist "obj" vom Typ "INhibernateProxy" und alle Properties sind null.

                        Hatte jemand dieses Problem auch schon mit Java?..
                        oder weiß jemand eine Lösung für mein .net problem??

                        danke im voraus

                        MFG
                        Josef

                        Comment


                        • #13
                          @Jürgen:

                          Ich hatte gestern tatsächlich gleich mal den Fall, dass ohne diese "Hibernate.isInitialzed()"-Prüfung eine Endlosschleife entstand. Daraufhin habe ich unser Preload-Pattern auch auf die Hibernate Events umgestellt, wie von Dir vorgeschlagen.

                          Das funktioniert jetzt prima, allerdings sind auch wir noch weit davon enternt, mit dem Code produktiv zu gehen. Insofern wäre ich dankbar, wenn bei neuen Ideen zu dem Thema, hier ein kleines Update gemacht wird.
                          Danke!

                          Kleine Ergänzung für alle, die das mal testen wollen: Der folgende Eintrag in der hibernate.cfg.xml wird noch benötigt:
                          Code:
                          <session-factory>
                              ...
                              <!-- Klinkt den PreloadEventListener ein. -->
                              <event type="post-load">
                                  <listener class="my.package.PreloadEventListener"/>
                              </event>
                          </session-factory>

                          @Josef:

                          Mit C# kenne ich mich leider nicht aus.
                          Aber ich habe folgendes Code-Snippet gefunden, das Dir eventuell weiterhelfen könnte (Java):
                          Code:
                          if (obj instanceof HibernateProxy) {
                          obj = ((HibernateProxy)obj).getHibernateLazyInitializer().getImplementation();
                          }
                          Vielleicht hilft Dir das an der Stelle als Workaround weiter.
                          (Ich gehe davon aus, du weißt wie Hibernate-Proxies funktionieren.)
                          Zuletzt editiert von amayr; 24.04.2008, 09:32.

                          Comment


                          • #14
                            Hi amayr, freut mich zu hören, dass es funktioniert. Werde wieder hier posten, wenn es was Neues gibt.

                            Comment


                            • #15
                              Danke, das ist gut zu wissen.

                              Gruß,
                              Andreas

                              Comment

                              Working...
                              X