-->
These old forums are deprecated now and set to read-only. We are waiting for you on our new forums!
More modern, Discourse-based and with GitHub/Google/Twitter authentication built-in.

All times are UTC - 5 hours [ DST ]



Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 13 posts ] 
Author Message
 Post subject: Eager Fetching bei Vererbung und Annotationen?
PostPosted: Wed Feb 04, 2009 6:36 am 
Beginner
Beginner

Joined: Thu Oct 04, 2007 12:22 pm
Posts: 48
Ich nutze eine Klasse als Basis für Ableitungen und mappe sie via Annotationen als

Code:
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
@org.hibernate.annotations.GenericGenerator(
        name = "folder_hilo",
        strategy = "hilo"
        )


Eine weitere Auszeichnung der erbenden Klassen ist ja nicht mehr nötig, die Ableitung via extends erledigt den Rest. Nun bin ich allerdings über das Lazy Loading gestolpert, da Hibernate scheinbar die Vererbung selbst bei TablePerClass als zwei verschiedene Entitäten begreift und somit bei einem Zugriff bsp. auf die ID nach Schließen der Session eine LazyException wirft.
Allerdings habe ich nirgendwo in der Dokumentation gefunden, über welche Annotation ich die Vererbungsbeziehung auf Eager Fetching stellen kann. Hat jemand eine Idee, wie das funktioniert?


Top
 Profile  
 
 Post subject:
PostPosted: Wed Feb 04, 2009 6:48 am 
Expert
Expert

Joined: Thu Jan 08, 2009 6:16 am
Posts: 661
Location: Germany
Kannst du dein mapping und deinen Beispielcode, in dem es passiert posten?

_________________
-----------------
Need advanced help? http://www.viada.eu


Top
 Profile  
 
 Post subject:
PostPosted: Wed Feb 04, 2009 8:03 am 
Beginner
Beginner

Joined: Thu Oct 04, 2007 12:22 pm
Posts: 48
Gern, ich hab es der Übersicht halber etwas verkürzt.

die Metaklasse

Code:
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
@org.hibernate.annotations.GenericGenerator(
        name = "folder_hilo",
        strategy = "hilo"
        )
public abstract class Folder implements Serializable
{
    @Id
    @GeneratedValue(generator = "folder_hilo")
    private long   id;

    @Column(nullable = false)
    private String name = "";

    public void setId(long id)
    {
        this.id = id;
    }

    public long getId()
    {
        return this.id;
    }

    public void setName(String name)
    {
        this.name = name;
    }

    public String getName()
    {
        return this.name;
    }
}


Eine der ableitenden Klassen
Code:
@Entity
@Table(name = "customerfolder")
public class CustomerFolder extends Folder
{
    // void 
}


Laden eines Folders
Code:
public Folder fetchFolder(Long id)
{
   Session session = null;
   Transaction tk = null;
   Folder result = null;
   
   try
   {
       session = sessionFactory.openSession();
       tk = session.beginTransaction();
      
       result = (Folder)getSession().load(Folder.class, id);
   
       tk.commit();
       return result;
   }
   catch(HibernateException e)
   {
       if(tk != null)
       {
           try
           {
               tk.rollback();
           }
           catch(Throwable t)
           {
              // logging
           }
       }
      
       throw new ...
   }
   finally
   {
       if(session != null)
       {
           try
           {
               session.close();
           }
           catch(Throwable t)
           {
               // logging
           }
              
       }
   }
}

// späterer Zugriff:
Folder fetchedFolder = getFolder(id);
fetchedFolder.getId(); // Exception


Der Zugriff auf die ID erzeugt dann die Exception:
Code:
org.hibernate.LazyInitializationException: could not initialize proxy - the owning Session was closed


Top
 Profile  
 
 Post subject:
PostPosted: Wed Feb 04, 2009 8:07 am 
Expert
Expert

Joined: Thu Jan 08, 2009 6:16 am
Posts: 661
Location: Germany
Du benutzt session.load zum Laden. Dies bewirkt aber kein laden aus der Datenbank, sondern erstellt nur einen Proxy, der beim ersten Zugriff instanziiert wird, wofür eine Session nötig ist.

Nimm session.get(..), um das Objekt aus der DB zu laden.

_________________
-----------------
Need advanced help? http://www.viada.eu


Top
 Profile  
 
 Post subject:
PostPosted: Wed Feb 04, 2009 9:10 am 
Beginner
Beginner

Joined: Thu Oct 04, 2007 12:22 pm
Posts: 48
Gut, das würde natürlich gehen, damit würde ich das Lazy Loading Verhalten aber auch fest in den Code brennen. Der code von mir ist ja nur ein Auszug,
in Wirklichkeit stehen zwischen Nutzung und Hibernate noch DAOs etc.
Daher würde ich die Entscheidung, ob Lazy Loading genutzt wird oder nicht (bzw. Proxies) feingranular auf Ebene der Annotationen für jedes Entity einzeln bestimmen können.
Nur eben diese Annotation - falls es sie gibt - finde ich nicht ..


Top
 Profile  
 
 Post subject:
PostPosted: Wed Feb 04, 2009 9:17 am 
Expert
Expert

Joined: Thu Jan 08, 2009 6:16 am
Posts: 661
Location: Germany
Ich glaube, du verstehst etwas falsch: Prinzipiell ist das kein LazyLoading, was du tust. Du benutzt Session.load: diese Methode benutzt man meist nur, um zum Beispiel Assoziationen zu dem Objekt zu setzen. Beispielsweise wenn du eine Datei hast, die du dem Folder zuordnen willst. du weißt, dass es das Folder gibt, willst es aber nicht aus der DB laden. Dann benutzt du session.load, um ein proxy darauf zu kriegen und setzt diesen bei der Datei. Willst du aber auf den Properties von dem Folder etwas machen, vor allem, wenn das Objekt detached ist (wie in deinem Fall), benutz session.get.

Mit Vererbung hat dieses Problem nichts zu tun, du würdest es auch ohne vererbung haben.

_________________
-----------------
Need advanced help? http://www.viada.eu


Top
 Profile  
 
 Post subject:
PostPosted: Wed Feb 04, 2009 9:48 am 
Beginner
Beginner

Joined: Thu Oct 04, 2007 12:22 pm
Posts: 48
Ersteinmal Danke, dass Du Dir Gedanken um mein Problem machst. Aber ich denke schon, dass es ein Lazy Loading Problem ist. Genau hier ist ja der Clue der Proxies: Nur Daten nachladen, wenn sie wirklich gebraucht werden.

Selbst der User Guide gibt ja die Empfehlung, zwischen get() und load() anhand der bevorzugten Fehlerbehanldung zu wählen: Wird kein Eintrag gefunden, gibt get() halt null zurück, load() eine ObjectNotFoundException sobald darauf zugegriffen wird.
Natürlich ist es für Hibernate nur möglich, null bei get() zurückzuliefern, indem es einen vollständigen select ausführt.

Ich habe das DAO Pattern von Hibernate übernommen (http://www.hibernate.org/328.html), hier sieht man anhand der Methode findById(..) auch ziemlich gut, dass die bevorzugte Methode load() ist, solange man auf eine null-Prüfung verzichten kann.

Der Grund, warum ich das Problem in der Vererbungshierarchie suche ist recht einfach: Bei allen anderen Zugriffen, die ebenfalls via load() (bzw. dem Pendant findById() des DAOs), aber nicht einer Hierarchie unterliegen, klappt es ja auch: Ich lade die Entity, schließe die Session und greife ohne Probleme auf das initialisierte Objekt zu.


Top
 Profile  
 
 Post subject:
PostPosted: Wed Feb 04, 2009 9:58 am 
Expert
Expert

Joined: Thu Jan 08, 2009 6:16 am
Posts: 661
Location: Germany
Serethos wrote:
Ich lade die Entity, schließe die Session und greife ohne Probleme auf das initialisierte Objekt zu.

Das Zauberwort ist hierbei: das initialisierte. Der Folder-Proxy ist aber nicht initialisiert. Kannst du Beispiel-Code einer Entity posten, bei dem es funktioniert?

Zum DAO-Pattern. In dem Pattern von http://www.hibernate.org/328.html werden die Transaktionen nicht innerhalb der find-Methoden geschlossen, im Gegensatz zu deiner fetch-Folder-Methode. In dem Beispiel:
Code:
// EJB3 CMT: @TransactionAttribute(TransactionAttributeType.REQUIRED)
public void execute() {

    // JTA: UserTransaction utx = jndiContext.lookup("UserTransaction");
    // JTA: utx.begin();

    // Plain JDBC: HibernateUtil.getCurrentSession().beginTransaction();

    DAOFactory factory = DAOFactory.instance(DAOFactory.HIBERNATE);
    ItemDAO itemDAO = factory.getItemDAO();
    UserDAO userDAO = factory.getUserDAO();

    Bid currentMaxBid = itemDAO.getMaxBid(itemId);
    Bid currentMinBid = itemDAO.getMinBid(itemId);

    Item item = itemDAO.findById(itemId, true);

    newBid = item.placeBid(userDAO.findById(userId, false),
                            bidAmount,
                            currentMaxBid,
                            currentMinBid);

    // JTA: utx.commit(); // Don't forget exception handling

    // Plain JDBC: HibernateUtil.getCurrentSession().getTransaction().commit(); // Don't forget exception handling

}
sind die auskommentierten Stellen dafür gedacht, dass du alle operationen, die du an dem geladenen Objekt durchführst, innerhalb einer Transaktion tust. Die Transaktion wird also erst ganz am Ende und nicht nach dem find geschlossen.
In deinem Beispiel greifst du aber erst nach der Transaktion auf das Objekt zu, es kann also nicht initialisiert werden.

Ich hoffe du belohnst meine Hilfe mit ein paar Ratings. ;-)

_________________
-----------------
Need advanced help? http://www.viada.eu


Top
 Profile  
 
 Post subject:
PostPosted: Wed Feb 04, 2009 10:24 am 
Beginner
Beginner

Joined: Thu Oct 04, 2007 12:22 pm
Posts: 48
Quote:
Das Zauberwort ist hierbei: das initialisierte. Der Folder-Proxy ist aber nicht initialisiert. Kannst du Beispiel-Code einer Entity posten, bei dem es funktioniert?


Genau auf den Punkt wollte ich hinaus: Das Objekt ist tatsächlich initialisiert, obwohl es auf die gleiche Art geladen wird. Natürlich mit dem Unterschied, dass es nicht einer Vererbungshierarchie unterworfen ist.
Für mich macht das auch weitgehend Sinn: Ohne Vererbung handelt es sich bei meinem Pojo um ein einfache Sammlung (nicht referentieller) Datenobjekte. Bei der Vererbung kommt plötzlich das Zusammenspiel referenzierender Objekte zusammen (Meta und Subklasse), als gibt es einen Ansatz für Lazy-Loading.

Ein funktionierendes Beispiel, diesmal etwas ausführlicher mit DAO-Code:

Die generische DAO Klasse, leicht modifiziert zum Original und gekürzt:
Code:
public abstract class GenericHibernateDAO<T, ID extends Serializable> implements GenericDAO<T, ID>
{
    private Class<T> persistentClass;
    private Session  session;

    @SuppressWarnings("unchecked")
    public GenericHibernateDAO()
    {
        persistentClass = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
    }
   
    public GenericHibernateDAO(Session session)
    {
        this();
        setSession(session);
    }

    public void setSession(Session session)
    {
        this.session = session;
    }

    protected Session getSession()
    {
        if (session == null)
        {
            // TODO
            //            session = HibernateUtil.getSessionFactory().getCurrentSession();
            throw new IllegalStateException("The hibernate session is not initialized.");
        }

        return session;
    }
   
    @SuppressWarnings("unchecked")
    public T findById(ID id)
    {
        return (T) getSession().load(getPersistenceClass(), id);
    }
}


Eine konkrete Ableitung des DAOs
Code:
public class TokenHibernateDAO extends
      GenericHibernateDAO<Token, Long> implements TokenDAO
{
  // void
}


Das Token Model:
Code:
@Entity
@Table(name = "tokens")
public class Token
{
    @Id
    @GeneratedValue
    private long      userId;
    @Column(nullable = false)
    private String    token        = "";

    @Override
    public int hashCode()
    {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((token == null) ? 0 : token.hashCode());
        result = prime * result + (int) (userId ^ (userId >>> 32));
        return result;
    }

    @Override
    public boolean equals(Object obj)
    {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (!(obj instanceof Token))
            return false;
           
        Token other = (Token) obj;

        if (token == null)
        {
            if (other.token != null)
                return false;
        }
        else if (!token.equals(other.token))
            return false;

        if (userId != other.userId)
            return false;

        return true;
    }

    public long getUserId()
    {
        return userId;
    }

    public void setUserId(long userId)
    {
        this.userId = userId;
    }

    public String getToken()
    {
        return token;
    }

    public void setToken(String token)
    {
        this.token = token;
    }
}



Eine Controller-Methode, welche das DAO nutzt:
Code:
public Token getEntry(long id)
{
         Session session = null;
      Transaction tk = null;
     
      Token result = null;
     
      try
      {
          session = sessionFactory.openSession();
          tk = session.beginTransaction();
         
          TokenHibernateDAO dao = createTokenDao(session);
          result = (Token)dao.findById(Long.valueOf(id));
         
          tk.commit();
         
          return result;
      }
      catch(HibernateException e)
      {
          if(tk != null)
          {
              try
              {
                  tk.rollback();
              }
              catch(Throwable t)
              {
                  // log
              }
          }
          // throw exception
      }
      finally
      {
          if(session != null)
          {
              try
              {
                  session.close();
              }
              catch(Throwable t)
              {
                  // log
              }
                 
          }
      }
}


Nun wird diese Controller-Methode bsp. in einem UnitTest verwendet:
Code:
public void testGetEntry()
{
  Token token = controller.getEntry(id);
  assertEquals(token.getId(), id); // keine Exception, funktioniert!
}


Last edited by Serethos on Wed Feb 04, 2009 10:48 am, edited 1 time in total.

Top
 Profile  
 
 Post subject:
PostPosted: Wed Feb 04, 2009 10:29 am 
Expert
Expert

Joined: Thu Jan 08, 2009 6:16 am
Posts: 661
Location: Germany
Kannst du das Mapping von Token bitte noch posten? Versuch mal auf eine andere property als die Id des Tokens zuzugreifen.

_________________
-----------------
Need advanced help? http://www.viada.eu


Top
 Profile  
 
 Post subject:
PostPosted: Wed Feb 04, 2009 10:52 am 
Beginner
Beginner

Joined: Thu Oct 04, 2007 12:22 pm
Posts: 48
Ich hab das Token Model im oberen Post ergänzt, so bleibt alles zusammen.
Die Unit Tests werden etwas vollständiger von mir geschrieben, ich vergleiche meist ganze Objekte über Equals, indem ich sie einmal über DAO bzw Controller lade und einmal über eine statelessSession, also aus dem Gedächtnis geschrieben etwa:

Code:
Token controllerToken = controller.getEntry(id);
Token statelessToken = statelessSession.createCriteria(Token.class).addCriteria(Restrictions.eq("id", id)).list.get(0);

assertEquals(controllerToken, statelessToken);


So ist auch sichergestellt, dass nicht nur die Id geladen ist.


Top
 Profile  
 
 Post subject:
PostPosted: Wed Feb 04, 2009 10:59 am 
Expert
Expert

Joined: Thu Jan 08, 2009 6:16 am
Posts: 661
Location: Germany
Also das es bei Token funktioniert wundert mich. Es hätte mich nicht gewundert, wenn du bei Token die Getter annotiert hättest, dann würde ein getId() nämlich nicht zu LazyLoading führen, was es in dem Fall eigentlich immer tun würde. Bist du dir sicher, dass du im zweiten Fall keine Session auf hast? Vertrau mir, session.load initialisiert das Objekt nicht, da muss noch etwas anderes zwischenhängen.
Lass dir doch mal das SQL mitloggen und mach in deinen Schichten ein paar System.outs, um zu sehen, ob und wann genau das Select-Statement abgesetzt wird.

_________________
-----------------
Need advanced help? http://www.viada.eu


Top
 Profile  
 
 Post subject:
PostPosted: Wed Feb 04, 2009 1:25 pm 
Beginner
Beginner

Joined: Thu Oct 04, 2007 12:22 pm
Posts: 48
So, ich habe mir Deine Ratschläge nochmal zu Herzen genommen und mit ein bisschen Abstand den code gegengeprüft. Und Du hast Recht, Fehler liegt bei mir. Ich habe ein paar falsche Annahmen gemacht und darüber hinaus aus einigen Stellen schlecht getestet.

Es ist tatsächlich so, die Objekte sind nicht-initialisierte Proxies. Mein Hauptfehler war, dass ich auf diese Weise gegengetestet habe, ob es etwas mit Lazy-Loading zu tun hat:

Code:
result = (Folder)session.createCriteria(dao.getPersistenceClass())
  .setFetchMode("id", FetchMode.JOIN)
  .add(Restrictions.eq("id", Long.valueOf(id))).list().get(0);


Gedanke war, für eine Abfrage das Lazy-Loading zu deaktivieren und tada, das Objekt war initialisiert. Das es aber auch _ohne_ FetchMode.JOIN funktioniert, weil eine criteria scheinbar nicht wie angenommen mit Proxies arbeitet ... das habe ich natürlich erst später bemerkt.
Somit ist Deine Lösung dann auch richtig, dass ich die DAOs standardmäßig über get() die Entitäten laden lasse, da meine Anwendungsfälle eine Benutzung der Objekte auch im detached Zustand benötigen.

Vielen Dank für Deine Mühe!


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 13 posts ] 

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.