11

JSF UI から JPA エンティティを編集するベスト プラクティスについて、専門家の意見を聞きたいです。

それで、問題について一言。

永続化されたオブジェクトがMyEntityあり、それをフェッチして編集するとします。私が使用するDAOレイヤーで

return em.find(MyEntity.class, id);

MyEntity「親」エンティティのプロキシを持つインスタンスを返します-そのうちの1つがMyParent. MyParentへのプロキシ グリーティングとして取得されます@Access(AccessType.PROPERTY):

@Entity
public class MyParent {

    @Id
    @Access(AccessType.PROPERTY)    
    private Long id;
    //...
}

そして MyEntity はそれへの参照を持っています:

@ManyToOne(fetch = FetchType.LAZY)
@LazyToOne(LazyToOneOption.PROXY)
private MyParent myParent;

ここまでは順調ですね。UI では、値オブジェクトを作成せずにフェッチしたオブジェクトを直接使用し、選択リストで親オブジェクトを使用します。

<h:selectOneMenu value="#{myEntity.myParent.id}" id="office">
    <f:selectItems value="#{parents}"/>
</h:selectOneMenu>

すべて正常にレンダリングされ、何もLazyInitializationException発生しません。しかし、オブジェクトを保存すると、

LazyInitializationException: could not initialize proxy - no Session

MyParentプロキシsetId()メソッドについて。

MyParent関係を変更すると、問題を簡単に修正できますEAGER

@ManyToOne(fetch = FetchType.EAGER)
private MyParent myParent;

またはを使用してオブジェクトをフェッチしますleft join fetch p.myParent(実際、それが私が今行っている方法です)。この場合、保存操作は正常に機能し、リレーションは新しいMyParentオブジェクトに透過的に変更されます。追加のアクション (手動コピー、手動参照設定) を実行する必要はありません。非常にシンプルで便利です。

しかし。オブジェクトem.find()が10 個の他のオブジェクトを参照している場合、10 個の追加の結合が発生します。これは、特に参照オブジェクトの状態をまったく使用しない場合、適切なデータベース操作ではありません。必要なのは、オブジェクトの状態ではなく、オブジェクトへのリンクだけです。

これは世界的な問題です。JSF スペシャリストがアプリケーションで JPA エンティティをどのように処理しているかを知りたいと思います。これは、余分な結合とLazyInitializationException.

拡張永続コンテキストは私には適していません。

ありがとう!

4

6 に答える 6

5

ビューが期待するモデルを正確に提供する必要があります。

JPA エンティティが必要なモデルと正確に一致する場合は、すぐに使用してください。

JPA エンティティのプロパティが少なすぎたり多すぎたりする場合は、必要に応じて明示的なFETCH JOIN. または、おそらく Hibernate 固有のフェッチ プロファイル、または EclipseLink 固有の属性グループを使用します。そうしないと、すべての場所で遅延初期化例外が発生するか、必要以上のメモリを消費する可能性があります。

「ビュー内のオープン セッション」パターンは、設計が不適切です。基本的に、 HTTP 要求応答処理全体で単一の DB トランザクションを開いたままにします。新しい DB トランザクションを開始するかどうかの制御は、完全にユーザーから奪われます。ビジネス ロジックで必要な場合、同じ HTTP 要求中に複数のトランザクションを生成することはできません。トランザクション中に 1 つのクエリが失敗すると、トランザクション全体がロールバックされることに注意してください。Spring や EJB3、またはそれらすべてを一緒に使用する必要がある、または便利な場合はいつですか?も参照してください。

JSF の観点では、「ビューでセッションを開く」パターンは、応答をレンダリングしながらビジネス ロジックを実行できることも意味します。これは、エンドユーザーにカスタム エラー ページを表示することを目的とした例外処理とはうまく調和しません。応答をレンダリングする途中でビジネス例外がスローされ、エンドユーザーが応答ヘッダーと HTML の一部をすでに受け取っている場合、サーバーは適切なエラー ページを表示するために応答を消去できません。また、getter メソッドでビジネス ロジックを実行することは、なぜ JSF が getter を複数回呼び出すのかに従って、JSF での慣例として嫌われています。

レンダリング応答フェーズが開始する前に、マネージド Bean アクション/リスナー メソッドの通常のサービス メソッド呼び出しを介して、ビューが必要とするモデルを正確に準備するだけです。たとえば、よくある状況は、遅延ロードされた 1 対多の子プロパティを持つ既存の (管理されていない) 親エンティティを手元に持っていて、それを ajax アクションを介して現在のビューにレンダリングしたい場合です。 ajaxリスナーメソッドがサービスレイヤーでフェッチして初期化できるようにします。

<f:ajax listener="#{bean.showLazyChildren(parent)}" render="children" />
public void showLazyChildren(Parent parent) {
    someParentService.fetchLazyChildren(parent);
}
public void fetchLazyChildren(Parent parent) {
    parent.setLazyChildren(em.merge(parent).getLazyChildren()); // Becomes managed.
    parent.getLazyChildren().size(); // Triggers lazy initialization.
}

具体的には、JSFUISelectManyコンポーネントでは、完全に予期しない別の考えられる原因がありLazyInitializationExceptionます。選択したアイテムを保存する際、JSF は、選択したアイテムで満たす前に、基礎となるコレクションを再作成する必要があります。ただし、永続レイヤー固有の遅延ロード コレクションである場合実装すると、この例外もスローされます。collectionType解決策は、コンポーネントの属性をUISelectMany目的の「プレーン」タイプに明示的に設定することです。

<h:selectManyCheckbox ... collectionType="java.util.ArrayList">

これは、 com.sun.faces.renderkit.html_basic.MenuRenderer.convertSelectManyValuesForModel の org.hibernate.LazyInitializationException で詳細に尋ねられ、回答されています。

以下も参照してください。

于 2015-08-14T09:23:44.620 に答える
3

Hibernate >= 4.1.6 については、これを読んでください https://stackoverflow.com/a/11913404/3252285

OpenSessionInView フィルター (デザイン パターン) を使用すると非常に便利ですが、私の意見では、問題を完全に解決することはできません。理由は次のとおりです。

セッションに格納されたエンティティ、セッション Bean によって処理されたエンティティ、またはキャッシュから取得されたエンティティがあり、そのコレクションの 1 つが同じ読み込み要求中に初期化されていない場合、後で呼び出したときにいつでも例外を取得できます。 OSIV 設計パターンを使用する場合。

問題を詳しく説明しましょう。

  • 休止状態のプロキシは、正しく機能するために、開いているセッションにアタッチする必要があります。
  • Hibernate はListener or Handler、プロキシのセッションが閉じられた場合、またはプロキシが独自のセッションから切り離された場合にプロキシに接続するためのツール ( ) を提供していません。

休止状態がそれを提供しないのはなぜですか? : どのセッションに接続するかを特定するのは簡単ではないため、プロキシを再接続する必要がありますが、多くの場合は可能です。

では、LazyInitializationException が発生したときにプロキシを再接続する方法は?.

私のERPでは、これらのクラスを変更します:JavassistLazyInitializerそしてAbstractPersistentCollection、この例外はもう気にしません(バグなしで3年間使用されています):

class JavassistLazyInitializer{
     @Override
     public Object invoke(
                        final Object proxy,
                        final Method thisMethod,
                        final Method proceed,
                        final Object[] args) throws Throwable {
            if ( this.constructed ) {
                Object result;
                try {
                    result = this.invoke( thisMethod, args, proxy );
                }
                catch ( Throwable t ) {
                    throw new Exception( t.getCause() );
                }           
                if ( result == INVOKE_IMPLEMENTATION ) {
                    Object target = null;
                    try{
                        target = getImplementation();
                    }catch ( LazyInitializationException lze ) {
              /* Catching the LazyInitException and reatach the proxy to the right Session */
                    EntityManager em = ContextConfig.getCurrent().getDAO(
                                        BaseBean.getWcx(), 
                                        HibernateProxyHelper.getClassWithoutInitializingProxy(proxy)).
                                        getEm();
                                ((Session)em.getDelegate()).refresh(proxy);// attaching the proxy                   
                    }   
                    try{                
                        if (target==null)
                            target = getImplementation();
                            .....
                    }
        ....
     }

そしてその

class AbstractPersistentCollection{
private <T> T withTemporarySessionIfNeeded(LazyInitializationWork<T> lazyInitializationWork) {
        SessionImplementor originalSession = null;
        boolean isTempSession = false;
        boolean isJTA = false;      
        if ( session == null ) {
            if ( allowLoadOutsideTransaction ) {
                session = openTemporarySessionForLoading();
                isTempSession = true;
            }
            else {
    /* Let try to reatach the proxy to the right Session */
                try{
                session = ((SessionImplementor)ContextConfig.getCurrent().getDAO(
                        BaseBean.getWcx(), HibernateProxyHelper.getClassWithoutInitializingProxy(
                        owner)).getEm().getDelegate());             
                SessionFactoryImplementor impl = (SessionFactoryImplementor) ((SessionImpl)session).getSessionFactory();            
                ((SessionImpl)session).getPersistenceContext().addUninitializedDetachedCollection(
                        impl.getCollectionPersister(role), this);
                }catch(Exception e){
                        e.printStackTrace();        
                }
                if (session==null)
                    throwLazyInitializationException( "could not initialize proxy - no Session" );
            }
        }
        if (session==null)
            throwLazyInitializationException( "could not initialize proxy - no Session" );
        ....
    }
...
}

注:

  • JTAやその他のケースのように、すべての可能性を修正したわけではありません。
  • このソリューションは、キャッシュを有効にするとさらに効果的です
于 2015-08-17T08:57:36.287 に答える
2

非常に一般的なアプローチは、ビュー フィルターでオープン エンティティ マネージャーを作成することです。Spring は 1 つを提供します (ここを確認してください)。

Spring を使用しているようには見えませんが、それは実際には問題ではありません。必要に応じてそのクラスのコードを調整できます。Open Session in Viewフィルターもチェックできますが、これは同じことを行いますが、エンティティ マネージャーではなく休止状態のセッションを開いたままにします。

このアプローチは、アプリケーションには適していない可能性があります。このパターンまたはアンチパターンについて SO でいくつかの議論があります。リンク 1。ほとんどのアプリケーション (小規模で同時ユーザーが 20 人未満) では、このソリューションは問題なく機能すると思います。

編集

ここにFSFとより良い関係を持つSpringクラスがあります

于 2011-06-15T07:39:40.637 に答える
2

遅延読み込みは、パフォーマンスを大幅に向上させる重要な機能です。ただし、これのユーザビリティは本来あるべきよりもはるかに悪いです。

特に、初期化されていないコレクションに遭遇して AJAX-Requests の処理を​​開始する場合、注釈は、Hibernate にこれをすぐにロードしないように指示するだけで役に立ちません。Hibernate は他に何も処理しませんが、LazyInitializationExceptionあなたが経験したように、あなたに投げかけます。

これに対する私の解決策は、完全ではないか、全体的に悪夢である可能性がありますが、次のルールを適用することで、どのようなシナリオでも機能します(これは最初に書かれたものですが、それ以来機能していることを認めなければなりません)。

fetch = FetchType.LAZY 使用しているすべてのエンティティは、を拡張し、返される前に問題のゲッターをLazyEntity呼び出す必要があります。(カスタムバリデーターがこの制約を処理し、不足している拡張機能や への呼び出しを報告します)initializeCollection()collectioninitializeCollection

Example-Class (グループが遅延ロードされているユーザー):

public class User extends LazyEntity{
     @OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
     @BatchSize(size = 5)
     List<Group> groups; 

     public List<Group> getGroups(){
       initializeCollection(this.groups);
       return this.groups;
     }
}

の実装はinitializeCollection(Collection collection)次のようになります。インライン コメントにより、どのシナリオに何が必要かがわかります。別のセッションが現在データをフェッチしている間に、2 つのアクティブなセッションがエンティティの所有権を譲渡することを避けるために、メソッドは同期されます。(同時の Ajax リクエストが同じインスタンスで進行している場合にのみ表示されます。)

public abstract class LazyEntity {

    @SuppressWarnings("rawtypes")
    protected synchronized void initializeCollection(Collection collection) {
        if (collection instanceof AbstractPersistentCollection) {
             //Already loaded?
             if (!Hibernate.isInitialized(collection)) {
                AbstractPersistentCollection ps = (AbstractPersistentCollection) collection;

                //Is current Session closed? Then this is an ajax call, need new session!
                //Else, Hibernate will know what to do.
                if (ps.getSession() == null) {
                    //get an OPEN em. This needs to be handled according to your application.
                    EntityManager em = ContextHelper.getBean(ServiceProvider.class).getEntityManager();

                    //get any Session to obtain SessionFactory
                    Session anySession = em.unwrap(Session.class);
                    SessionFactory sf = anySession.getSessionFactory();

                    //get a new session    
                    Session newSession = sf.openSession();

                    //move "this" to the new session.
                    newSession.update(this);

                    //let hibernate do its work on the current session.
                    Hibernate.initialize(collection);

                    //done, we can abandon the "new Session".
                    newSession.close();
                }
            }
        }
    }
}

ただし、このアプローチでは、エンティティを保存するたびに現在のセッションにエンティティが関連付けられているかどうかを検証する必要があることに注意してくださいmerge()

于 2015-08-16T20:28:08.933 に答える
1

Open Session in View 設計パターンは、Java EE 環境で簡単に実装できます (休止状態、春、または Java EE 以外の何かに依存しません)。OpenSessionInViewとほとんど同じですが、Hibernate セッションの代わりに JTA トランザクションを使用する必要があります

@WebFilter(urlPatterns = {"*"})
public class JTAFilter implements Filter{

    @Resource
    private UserTransaction ut;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        try{
           ut.begin();
           chain.doFilter(request, response);
        }catch(NotSupportedException | SystemException e){
            throw new ServletException("", e);
        } finally {
            try {
               if(ut.getStatus()!= Status.STATUS_MARKED_ROLLBACK){
                   ut.commit();
               }
            } catch (Exception e) {
                throw new ServletException("", e);
            }
       }
  }

  @Override
  public void destroy() {

  }
}
于 2015-08-17T12:57:12.307 に答える
1

EJB3 でのビューでのオープン セッションの標準サポートはありません。この回答を参照してください。

マッピングのフェッチ タイプは単なるデフォルト オプションであり、クエリ時にオーバーライドできます。これは例です:

select g from Group g fetch join g.students

したがって、プレーンな EJB3 での代替手段は、必要なデータを明示的に照会することにより、ビューのレンダリングに必要なすべてのデータがレンダリングの開始前にロードされるようにすることです。

于 2013-12-12T13:43:54.333 に答える