Ruhezustand: Best Practice, um alle faulen Sammlungen abzurufen

89

Was ich habe:

@Entity
public class MyEntity {
  @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
  @JoinColumn(name = "myentiy_id")
  private List<Address> addreses;

  @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
  @JoinColumn(name = "myentiy_id")
  private List<Person> persons;

  //....
}

public void handle() {

   Session session = createNewSession();
   MyEntity entity = (MyEntity) session.get(MyEntity.class, entityId);
   proceed(session); // FLUSH, COMMIT, CLOSE session!

   Utils.objectToJson(entity); //TROUBLES, because it can't convert to json lazy collections
}

Was für ein Problem:

Das Problem ist, dass ich nach Abschluss der Sitzung keine Lazy Collection mehr abrufen kann. Ich kann aber auch eine Sitzung in der Methode " Fortfahren" nicht schließen .

Was für eine Lösung (grobe Lösung):

a) Erzwingen Sie vor dem Schließen der Sitzung den Ruhezustand, um verzögerte Sammlungen abzurufen

entity.getAddresses().size();
entity.getPersons().size();

....

b) Vielleicht ist es elleganter, @Fetch(FetchMode.SUBSELECT)Anmerkungen zu verwenden

Frage:

Was ist eine bewährte Methode / ein allgemeiner Weg / ein elleganterer Weg, dies zu tun? Bedeutet, mein Objekt in JSON zu konvertieren.

VB_
quelle

Antworten:

99

Verwenden Sie diese Hibernate.initialize()Option @Transactional, um faule Objekte zu initialisieren.

 start Transaction 
      Hibernate.initialize(entity.getAddresses());
      Hibernate.initialize(entity.getPersons());
 end Transaction 

Außerhalb der Transaktion können Sie jetzt faule Objekte erhalten.

entity.getAddresses().size();
entity.getPersons().size();
Prabhakaran Ramaswamy
quelle
1
Es sieht attraktiv aus). Soweit ich weiß, wenn ich @Fetch (FetchMode.SUBSELECT) verwende, kann ich Hibernate.initialize nur einmal aufrufen, um alle Sammlungen abzurufen. Habe ich recht?
VB_
4
Und wie gehen Sie vor, wenn Sie eine Sammlung von MyEntity abrufen?
Alexis Dufrenoy
1
Wenn Sie eine Methode wie "size ()" für eine Sammlung in einer Transaktion aufrufen, wird diese auch initialisiert, sodass Ihr Beispiel nach der Initialisierung nicht das beste ist. "Hibernate.initialize (...)" ist semantisch besser als collection.size (), sodass Sie den besten Rat haben.
Tristan
7

Sie können die Getters des Hibernate-Objekts in derselben Transaktion durchlaufen, um sicherzustellen, dass alle faulen untergeordneten Objekte mit der folgenden generischen Hilfsklasse eifrig abgerufen werden :

HibernateUtil.initializeObject (myObject, "my.app.model");

package my.app.util;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Set;

import org.aspectj.org.eclipse.jdt.core.dom.Modifier;
import org.hibernate.Hibernate;

public class HibernateUtil {

public static byte[] hibernateCollectionPackage = "org.hibernate.collection".getBytes();

public static void initializeObject( Object o, String insidePackageName ) {
    Set<Object> seenObjects = new HashSet<Object>();
    initializeObject( o, seenObjects, insidePackageName.getBytes() );
    seenObjects = null;
}

private static void initializeObject( Object o, Set<Object> seenObjects, byte[] insidePackageName ) {

    seenObjects.add( o );

    Method[] methods = o.getClass().getMethods();
    for ( Method method : methods ) {

        String methodName = method.getName();

        // check Getters exclusively
        if ( methodName.length() < 3 || !"get".equals( methodName.substring( 0, 3 ) ) )
            continue;

        // Getters without parameters
        if ( method.getParameterTypes().length > 0 )
            continue;

        int modifiers = method.getModifiers();

        // Getters that are public
        if ( !Modifier.isPublic( modifiers ) )
            continue;

        // but not static
        if ( Modifier.isStatic( modifiers ) )
            continue;

        try {

            // Check result of the Getter
            Object r = method.invoke( o );

            if ( r == null )
                continue;

            // prevent cycles
            if ( seenObjects.contains( r ) )
                continue;

            // ignore simple types, arrays und anonymous classes
            if ( !isIgnoredType( r.getClass() ) && !r.getClass().isPrimitive() && !r.getClass().isArray() && !r.getClass().isAnonymousClass() ) {

                // ignore classes out of the given package and out of the hibernate collection
                // package
                if ( !isClassInPackage( r.getClass(), insidePackageName ) && !isClassInPackage( r.getClass(), hibernateCollectionPackage ) ) {
                    continue;
                }

                // initialize child object
                Hibernate.initialize( r );

                // traverse over the child object
                initializeObject( r, seenObjects, insidePackageName );
            }

        } catch ( InvocationTargetException e ) {
            e.printStackTrace();
            return;
        } catch ( IllegalArgumentException e ) {
            e.printStackTrace();
            return;
        } catch ( IllegalAccessException e ) {
            e.printStackTrace();
            return;
        }
    }

}

private static final Set<Class<?>> IGNORED_TYPES = getIgnoredTypes();

private static boolean isIgnoredType( Class<?> clazz ) {
    return IGNORED_TYPES.contains( clazz );
}

private static Set<Class<?>> getIgnoredTypes() {
    Set<Class<?>> ret = new HashSet<Class<?>>();
    ret.add( Boolean.class );
    ret.add( Character.class );
    ret.add( Byte.class );
    ret.add( Short.class );
    ret.add( Integer.class );
    ret.add( Long.class );
    ret.add( Float.class );
    ret.add( Double.class );
    ret.add( Void.class );
    ret.add( String.class );
    ret.add( Class.class );
    ret.add( Package.class );
    return ret;
}

private static Boolean isClassInPackage( Class<?> clazz, byte[] insidePackageName ) {

    Package p = clazz.getPackage();
    if ( p == null )
        return null;

    byte[] packageName = p.getName().getBytes();

    int lenP = packageName.length;
    int lenI = insidePackageName.length;

    if ( lenP < lenI )
        return false;

    for ( int i = 0; i < lenI; i++ ) {
        if ( packageName[i] != insidePackageName[i] )
            return false;
    }

    return true;
}
}
Florian Sager
quelle
Vielen Dank für diese Antwort. Ich weiß, es ist eine Weile her, aber ich habe versucht, dies zu lösen, und es ging langsam voran, bis ich Ihren Code hier gelesen habe. Ich habe auch ifs am Anfang der zweiten Methode initializeObject (Objekt, SeenObjects, insidePackageName) if (object instanceof List) { for(Object item : (List<Object>) object) { initializeObject(item, seenObjects, insidePackageName); } return; } else if (object instanceof Set) { for(Object item : (Set<Object>) object) { initializeObject(item, seenObjects, insidePackageName); } return; } hinzugefügt : Iterieren Sie Listen, die sonst ignoriert werden.
Chip
Was passiert, wenn SecurityException bei o.getClass (). GetMethods () ausgelöst wird?
Oleksii Kyslytsyn
6

Nicht die beste Lösung, aber hier ist was ich habe:

1) Kommentieren Sie den Getter, den Sie mit dieser Annotation initialisieren möchten:

@Retention(RetentionPolicy.RUNTIME)
public @interface Lazy {

}

2) Verwenden Sie diese Methode (kann in eine generische Klasse eingefügt werden oder Sie können T mit der Objektklasse ändern) für ein Objekt, nachdem Sie es aus der Datenbank gelesen haben:

    public <T> void forceLoadLazyCollections(T entity) {

    Session session = getSession().openSession();
    Transaction tx = null;
    try {

        tx = session.beginTransaction();
        session.refresh(entity);
        if (entity == null) {
            throw new RuntimeException("Entity is null!");
        }
        for (Method m : entityClass.getMethods()) {

            Lazy annotation = m.getAnnotation(Lazy.class);
            if (annotation != null) {
                m.setAccessible(true);
                logger.debug(" method.invoke(obj, arg1, arg2,...); {} field", m.getName());
                try {
                    Hibernate.initialize(m.invoke(entity));
                }
                catch (Exception e) {
                    logger.warn("initialization exception", e);
                }
            }
        }

    }
    finally {
        session.close();
    }
}
Damian
quelle
Ich verwende session.refresh in einer Iteration, um lazyCollections zu laden. und jedes Mal, wenn ich mein Programm nur für eine meiner Entitäten ausführe, wurden LazyInitializationException und andere Sammlungen nach dem Aufruf von session.refresh geladen. Wie konnte das passieren
Saba Safavi
5

Platzieren Sie die Utils.objectToJson (Entität); Anruf vor dem Schließen der Sitzung.

Oder Sie können versuchen, den Abrufmodus einzustellen und mit Code wie diesem zu spielen

Session s = ...
DetachedCriteria dc = DetachedCriteria.forClass(MyEntity.class).add(Expression.idEq(id));
dc.setFetchMode("innerTable", FetchMode.EAGER);
Criteria c = dc.getExecutableCriteria(s);
MyEntity a = (MyEntity)c.uniqueResult();
StanislavL
quelle
FetchMode.EAGER ist veraltet. Der Javadoc empfiehlt, jetzt FetchMode.JOIN zu verwenden.
Alexis Dufrenoy
4

Mit Hibernate 4.1.6 wird eine neue Funktion eingeführt, um diese faulen Assoziationsprobleme zu lösen. Wenn Sie die Eigenschaft hibernate.enable_lazy_load_no_trans in hibernate.properties oder in hibernate.cfg.xml aktivieren, haben Sie keine LazyInitializationException mehr.

Weitere Informationen finden Sie unter: https://stackoverflow.com/a/11913404/286588

Bauernhof
quelle
3
Dies ist eigentlich ein Anti-Muster. Für weitere Informationen: vladmihalcea.com/…
Ph03n1x
3

Wenn Sie mehrere Sammlungen abrufen müssen, müssen Sie:

  1. JOIN FETCH eine Sammlung
  2. Verwenden Sie die Hibernate.initializefür die verbleibenden Sammlungen.

In Ihrem Fall benötigen Sie also eine erste JPQL-Abfrage wie diese:

MyEntity entity = session.createQuery("select e from MyEntity e join fetch e.addreses where e.id 
= :id", MyEntity.class)
.setParameter("id", entityId)
.getSingleResult();

Hibernate.initialize(entity.persons);

Auf diese Weise können Sie Ihr Ziel mit 2 SQL-Abfragen erreichen und ein kartesisches Produkt vermeiden.

Vlad Mihalcea
quelle
Hallo Vlad, funktioniert es , wenn ich anrufen , Hibernate#initialize(entity.getSubSet())wenn getSubSet zurückkehrt Collections.unmodifyableSet(this.subSet). Ich habe es versucht und es nicht. Die zugrunde liegende Sammlung ist 'PersistentSet'. Gleiche Geschichte mit Anruf#size()
Vadim Kirilchuk
Aber vielleicht ist das Problem, dass ich später enthält enthält und mein Gleicher verwendet direkten Feldzugriff und nicht Getter ..
Vadim Kirilchuk
Es funktioniert, wenn Sie die in meiner Antwort angegebenen Schritte ausführen.
Vlad Mihalcea
2

Es nähert sich wahrscheinlich nirgendwo einer Best Practice, aber ich rufe normalerweise eine SIZEin der Sammlung auf, um die Kinder in derselben Transaktion zu laden, wie Sie vorgeschlagen haben. Es ist sauber, immun gegen Änderungen in der Struktur der untergeordneten Elemente und liefert SQL mit geringem Overhead.

Davek
quelle
0

Versuchen Sie es mit Gson Bibliothek, um Objekte in Json zu konvertieren

Beispiel mit Servlets:

  List<Party> parties = bean.getPartiesByIncidentId(incidentId);
        String json = "";
        try {
            json = new Gson().toJson(parties);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        response.setContentType("application/json");
        response.setCharacterEncoding("UTF-8");
        response.getWriter().write(json);
Mohamed Nagy
quelle
0

Wenn Sie das JPA-Repository verwenden, setzen Sie properties.put ("hibernate.enable_lazy_load_no_trans", true). zu jpaPropertymap

userSait
quelle
0

Du kannst den ... benutzen @NamedEntityGraph Anmerkung zu Ihrer Entität verwenden, um eine ladbare Abfrage zu erstellen, die festlegt, welche Sammlungen Sie in Ihre Abfrage laden möchten.

Der Hauptvorteil dieser Auswahl besteht darin, dass der Ruhezustand eine einzige Abfrage zum Abrufen der Entität und ihrer Sammlungen ausführt, und zwar nur dann, wenn Sie dieses Diagramm wie folgt verwenden:

Entitätskonfiguration

@Entity
@NamedEntityGraph(name = "graph.myEntity.addresesAndPersons", 
attributeNodes = {
    @NamedAttributeNode(value = "addreses"),
    @NamedAttributeNode(value = "persons"
})

Verwendung

public MyEntity findNamedGraph(Object id, String namedGraph) {
        EntityGraph<MyEntity> graph = em.getEntityGraph(namedGraph);

        Map<String, Object> properties = new HashMap<>();
        properties.put("javax.persistence.loadgraph", graph);

        return em.find(MyEntity.class, id, properties);
    }
gutors
quelle
0

Es gibt eine Art Missverständnis über faule Sammlungen in JPA-Hibernate. Lassen Sie uns zunächst klarstellen, warum der Versuch, eine faule Sammlung zu lesen, Ausnahmen auslöst und nicht einfach NULL für die Konvertierung oder weitere Anwendungsfälle zurückgibt..

Dies liegt daran, dass Nullfelder in Datenbanken, insbesondere in verbundenen Spalten, eine Bedeutung haben und nicht einfach den Status "Nicht dargestellt" haben, wie z. B. Programmiersprachen. Wenn Sie versuchen, eine Lazy-Sammlung als Nullwert zu interpretieren, bedeutet dies (auf der Datenspeicherseite), dass zwischen diesen Entitäten keine Beziehungen bestehen und dies nicht der Fall ist. Das Auslösen von Ausnahmen ist also eine Art Best Practice, und Sie müssen sich damit befassen, nicht mit dem Ruhezustand.

Wie oben erwähnt, empfehle ich:

  1. Trennen Sie das gewünschte Objekt, bevor Sie es ändern oder eine zustandslose Sitzung zum Abfragen verwenden
  2. Bearbeiten Sie Lazy Fields auf die gewünschten Werte (Null, Null usw.)

Auch wie in anderen Antworten beschrieben, gibt es viele Ansätze (eifriges Abrufen, Beitreten usw.) oder Bibliotheken und Methoden, um dies zu tun, aber Sie müssen Ihre Sicht auf das Geschehen einrichten, bevor Sie sich mit dem Problem befassen und es lösen.

Mohsen Msr
quelle