Hibernate:すべてのレイジーコレクションをプルするためのベストプラクティス


92

私が持っているもの:

@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
}

どのような問題:

問題は、セッションが閉じられた後、レイジーコレクションをプルできないことです。しかし、proceedメソッドでセッションを閉じることもできません。

どのような解決策(粗い解決策):

a)セッションを閉じる前に、休止状態でレイジーコレクションをプルするように強制します

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

...。

b)おそらくもっとエレガントな方法は@Fetch(FetchMode.SUBSELECT)注釈を使うことです

質問:

それを行うためのベストプラクティス/一般的な方法/よりエレガントな方法は何ですか?オブジェクトをJSONに変換する手段。

回答:


102

怠惰なオブジェクトを初期化するためにHibernate.initialize()within@Transactionalを使用します。

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

これで、トランザクションの外で、怠惰なオブジェクトを取得できます。

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

1
魅力的に見えます)。@Fetch(FetchMode.SUBSELECT)を使用するかどうかは理解しているので、Hibernate.initializeを1回だけ呼び出して、すべてのコレクションをプルすることができます。私は正しいですか?
vB_ 2013年

4
また、MyEntityのコレクションを取得する場合、どのように管理しますか?
Alexis Dufrenoy 2014

1
トランザクション内のコレクションで「size()」のようなメソッドを呼び出すと、それも初期化されるため、初期化後の例は最適ではありません。つまり、「Hibernate.initialize(...)」はcollection.size()よりも意味的に優れているため、最善のアドバイスがあります。
トリスタン

7

同じトランザクションでHibernateオブジェクトのゲッターをトラバースして、すべての遅延子オブジェクトが次の汎用ヘルパークラスで熱心にフェッチされるようにすることができます。

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;
}
}

この答えをありがとう。しばらく経ちましたが、これを解決しようとしていたので、ここでコードを読むまでは時間がかかりました。また、2番目のメソッドinitializeObject(object、seenObjects、insidePackageName)の先頭にifを追加しました 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; } 。それ以外の場合は無視されるリストを繰り返します。
チップ

SecurityExceptionがo.getClass()。getMethods();でスローされた場合はどうなりますか?
Oleksii Kyslytsyn 2016

6

最善の解決策ではありませんが、これが私が得たものです:

1)初期化するゲッターにこのアノテーションを付けます。

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

}

2)データベースからオブジェクトを読み取った後、オブジェクトに対してこのメ​​ソッドを使用します(ジェネリッククラスに入れるか、ObjectクラスでTを変更できます)。

    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();
    }
}

反復でsession.refreshを使用してlazyCollectionsをロードします。また、エンティティの1つに対してのみプログラムを実行するたびに、session.refreshを呼び出した後にLazyInitializationExceptionとその他のコレクションが読み込まれました。どうしてこれが起こるのでしょうか
saba safavi

5

Utils.objectToJson(entity);を配置します。セッションを閉じる前に電話してください。

または、フェッチモードを設定して、次のようなコードで遊ぶこともできます

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();

FetchMode.EAGERは非推奨です。javadocは、今すぐFetchMode.JOINを使用することを推奨しています。
Alexis Dufrenoy 2014

4

Hibernate 4.1.6では、これらのレイジーアソシエーションの問題を処理するための新機能が導入されています。hibernate.propertiesまたはhibernate.cfg.xmlでhibernate.enable_lazy_load_no_transプロパティを有効にすると、LazyInitializationExceptionは発生しなくなります。

詳細については、https//stackoverflow.com/a/11913404/286588を参照してください


3
これは実際にはアンチパターンです。:詳細情報についてはvladmihalcea.com/...
Ph03n1x

3

複数のコレクションをフェッチする必要がある場合は、次のことを行う必要があります。

  1. JOIN FETCHoneコレクション
  2. Hibernate.initialize残りのコレクションにはを使用します。

したがって、あなたの場合、次のような最初のJPQLクエリが必要です。

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);

このようにして、2つのSQLクエリで目標を達成し、デカルト積を回避できます。


こんにちはVlad、Hibernate#initialize(entity.getSubSet())getSubSetがを返す場合に呼び出すと機能しますか Collections.unmodifyableSet(this.subSet)。試しましたが、しませんでした。基盤となるコレクションは「PersistentSet」です。呼び出しと同じ話#size()
Vadim Kirilchuk

しかし、おそらく問題は..私は後で呼び出しが含まれていると私の等号は、直接フィールドアクセスではなくゲッターを使用することである
ヴァディムKirilchuk

あなたが私の答えで提供されたステップに従うならば、それは働きます。
ヴラッドミハルセア

2

おそらくベストプラクティスに近づくことはどこにもありませんが、私は通常SIZE、あなたが提案したように、同じトランザクションで子をロードするためにコレクションを呼び出します。クリーンで、子要素の構造の変更の影響を受けず、オーバーヘッドの少ないSQLを生成します。


0

Gsonライブラリを使用してオブジェクトをJsonに変換してみてください

サーブレットの例:

  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);

0

jpaリポジトリを使用している場合は、properties.put( "hibernate.enable_lazy_load_no_trans"、true);を設定します。jpaPropertymapへ


0

@NamedEntityGraphエンティティへの注釈を使用して、クエリにロードするコレクションを設定するロード可能なクエリを作成できます。

この選択の主な利点は、hibernateがエンティティとそのコレクションを取得するために単一のクエリを実行し、次のようにこのグラフを使用することを選択した場合にのみ実行されることです。

エンティティ構成

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

使用法

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);
    }

0

JPA-Hibernateのレイジーコレクションについては、ある種の誤解があります。まず最初に、レイジーコレクションを読み取ろうとすると例外がスローされ、変換やさらなるユースケースのために単にNULLを返すだけではないことを明確にしましょ う。

これは、データベース、特に結合された列のNullフィールドには意味があり、プログラミング言語のように単に提示されていない状態ではないためです。レイジーコレクションをNull値に解釈しようとすると、(データストア側で)これらのエンティティ間に関係がなく、正しくないことを意味します。したがって、例外をスローすることは、ある種のベストプラクティスであり、Hibernateではなくそれに対処する必要があります。

したがって、上記のように、私は次のことをお勧めします:

  1. 目的のオブジェクトを変更する前、またはステートレスセッションを使用してクエリを実行する前に、目的のオブジェクトをデタッチします
  2. レイジーフィールドを目的の値(ゼロ、ヌルなど)に操作します

また、他の回答で説明されているように、それを行うためのアプローチ(熱心なフェッチ、結合など)またはライブラリとメソッドはたくさんありますが、問題に対処して解決する前に、何が起こっているのかについてのビューを設定する必要があります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.