3

私は自分のプロジェクトの始まりです。そのため、Hibernate LazyInitializationExceptions を回避するアーキテクチャを設計しようとしています。これまでのところ、私の applicationContext.xml には次のものがあります。

<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <property name="configLocation">
        <value>/WEB-INF/hibernate.cfg.xml</value>
    </property>
    <property name="configurationClass">
        <value>org.hibernate.cfg.AnnotationConfiguration</value>
    </property>        
    <property name="hibernateProperties">
        <props>
            <prop key="hibernate.dialect">${hibernate.dialect}</prop>        
            <prop key="hibernate.show_sql">${hibernate.show_sql}</prop>     
        </props>
    </property>
    <property name="eventListeners">
        <map>
            <entry key="merge">
                <bean class="org.springframework.orm.hibernate3.support.IdTransferringMergeEventListener"/>
            </entry>
        </map>
    </property>
</bean>

<bean id="dao" class="info.ems.hibernate.HibernateEMSDao" init-method="createSchema">
    <property name="hibernateTemplate">
        <bean class="org.springframework.orm.hibernate3.HibernateTemplate">
            <property name="sessionFactory" ref="sessionFactory"/>
            <property name="flushMode">
                <bean id="org.springframework.orm.hibernate3.HibernateAccessor.FLUSH_COMMIT" class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean"/>                    
            </property>
        </bean>
    </property>        
    <property name="schemaHelper">
        <bean class="info.ems.hibernate.SchemaHelper">                                
            <property name="driverClassName" value="${database.driver}"/>
            <property name="url" value="${database.url}"/>
            <property name="username" value="${database.username}"/>
            <property name="password" value="${database.password}"/>
            <property name="hibernateDialect" value="${hibernate.dialect}"/>   
            <property name="dataSourceJndiName" value="${database.datasource.jndiname}"/>
        </bean>                
    </property>
</bean>       

hibernate.cfg.xml:

<hibernate-configuration>
    <session-factory>       
        <mapping class="info.ems.models.User" />
        <mapping class="info.ems.models.Role" />
    </session-factory>
</hibernate-configuration>

Role.java:

@Entity
@Table(name="ROLE")
@Access(AccessType.FIELD)
public class Role implements Serializable {

    private static final long serialVersionUID = 3L;

    @Id
    @Column(name="ROLE_ID", updatable=false, nullable=false)
    @GeneratedValue(strategy = GenerationType.IDENTITY) 
    private long id;

    @Column(name="USERNAME")
    private String username;

    @Column(name="ROLE")
    private String role;

    public long getId() {
        return id;
    }

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

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getRole() {
        return role;
    }

    public void setRole(String role) {
        this.role = role;
    }
}

そして User.java:

@Entity
@Table(name = "USER")
@Access(AccessType.FIELD)
public class User implements UserDetails, Serializable {

    private static final long serialVersionUID = 2L;

    @Id
    @Column(name = "USER_ID", updatable=false, nullable=false)
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

    @Column(name = "USERNAME")
    private String username;

    @Column(name = "PASSWORD")
    private String password;

    @Column(name = "NAME")
    private String name;

    @Column(name = "EMAIL")
    private String email;

    @Column(name = "LOCKED")
    private boolean locked;

    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, targetEntity = Role.class)
    @JoinTable(name = "USER_ROLE", joinColumns = { @JoinColumn(name = "USER_ID") }, inverseJoinColumns = { @JoinColumn(name = "ROLE_ID") })
    private Set<Role> roles;

    @Override
    public GrantedAuthority[] getAuthorities() {
        List<GrantedAuthorityImpl> list = new ArrayList<GrantedAuthorityImpl>(0);
        for (Role role : roles) {
            list.add(new GrantedAuthorityImpl(role.getRole()));
        }
        return (GrantedAuthority[]) list.toArray(new GrantedAuthority[list.size()]);
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return !isLocked();
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }

    public long getId() {
        return id;
    }

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

    @Override
    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    @Override
    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getName() {
        return name;
    }

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

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public boolean isLocked() {
        return locked;
    }

    public void setLocked(boolean locked) {
        this.locked = locked;
    }

    public Set<Role> getRoles() {
        return roles;
    }

    public void setRoles(Set<Role> roles) {
        this.roles = roles;
    }
}

HibernateEMSDao には、データベースからユーザーを保存およびロードするための 2 つの方法があります。

public void saveUser(final User user) {     
    getHibernateTemplate().execute(new HibernateCallback() {

        @Override
        public Object doInHibernate(Session session) throws HibernateException, SQLException {
            session.flush();
            session.setCacheMode(CacheMode.IGNORE);
            session.save(user);
            session.flush();
            return null;
        }
    });     
}

public User getUser(final Long id) {
    return (User) getHibernateTemplate().execute(new HibernateCallback() {

        @Override
        public Object doInHibernate(Session session) throws HibernateException, SQLException {
            return session.get(User.class, id);
        }
    });
}

今、次のように実装した場合にテストしましたHibernateEMSDao#getUser

public User getUser(final Long id) {
    getHibernateTemplate().load(User.class, id);        
}

LazyInitializationExcaption を取得しています - セッションが閉じられています。しかし、最初の方法はうまく機能しています。したがって、近い将来、この例外を回避するための提案が必要です。どんな小さな情報でも価値があります。

ありがとうございます。

注:サーバーを再起動した後、そのエラーが発生しました。

編集:コードが追加されました:

public void saveUser(final User user) {     
    Session session = getSession();
    Transaction transaction = session.beginTransaction();
    session.save(user);
    transaction.commit();
    session.close();
}
public User getUser(final Long id) {
    Session session = getSession();
    session.enableFetchProfile("USER-ROLE-PROFILE");
    User user = (User) session.get(User.class, id);
    session.disableFetchProfile("USER-ROLE-PROFILE");
    session.close();
    return user;
}
4

2 に答える 2

4

Hibernate、JPA、または ORM を一般的に使用する場合、遅延読み込みの処理は進行中の課題です。

LazyInitializationException の発生を防ぐだけでなく、クエリを効率的に実行することも重要です。一般的な DAO を使用する場合でも、戦略は、本当に必要なデータのみを可能な限り取得する必要があります。

Pro JPA 2Apress による Mike Keithの本では、これに関するセクション全体が提供されていますが、常に機能する普遍的な解決策はないようです。

FETCH 結合を実行すると役立つ場合があります。これは、エンティティ マネージャーの find メソッドを使用せずに、すべてに JPQL (または HQL が有害な場合は HQL) クエリを使用することを意味します。DAO には、この方法でエンティティ グラフをさまざまなレベルまで取得するいくつかの異なるメソッドを含めることができます。通常、データはこの方法でかなり効率的にフェッチされますが、多くの状況では、あまりにも多くのデータをフェッチする可能性があります。

同様に Mike Keith によって提案された別の解決策は、extended persistence context. この場合、コンテキスト (Hibernate セッション) はトランザクションにバインドされず、開いたままになります。したがって、エンティティは接続されたままになり、遅延読み込みは期待どおりに機能します。

ただし、拡張コンテキストを最終的に閉じるようにする必要があります。これを行う 1 つの方法は、リクエスト スコープや会話スコープなど、何らかのスコープにバインドされたステートフル セッション Bean によって管理することです。そうすれば、Bean はこのスコープの最後で自動的に破棄され、今度はこれがコンテキストを自動的に閉じます。

ただし、それ自体に問題がないわけではありません。開いているコンテキストは引き続きメモリを消費し、それを長期間 (通常はリクエスト スコープよりも長く) 開いたままにしておくと、メモリ不足の重大なリスクが発生する可能性があります。ほんの一握りのエンティティしか扱っていないことがわかっている場合は問題ありませんが、ここでは注意が必要です。

遅延読み込みに依存することに関するもう 1 つの問題は、よく知られている 1 + N クエリの問題です。中程度のサイズの結果リストを繰り返し処理すると、DB に数百または数千のクエリが送信される可能性があります。これがあなたのパフォーマンスを完全に破壊する可能性があることを説明する必要はないと思います.

この 1 + N クエリの問題は、第 2 レベルのキャッシュに大きく依存することで解決できる場合があります。エンティティの量がそれほど多くなく、頻繁に更新されない場合は、(Hibernate または JPA の第 2 レベルのエンティティ キャッシュを使用して) すべてがキャッシュされていることを確認すると、この問題を大幅に軽減できます。しかし... それは 2 つの大きな "if" です。また、プライマリ エンティティがキャッシュされていない 1 つのエンティティのみを参照する場合、何百ものクエリが再び取得されます。

さらに別のアプローチはfetch profile、Hibernate のサポートを利用することです。これは、他のアプローチと部分的に組み合わせることができます。リファレンスマニュアルには、これに関するセクションがあります: http://docs.jboss.org/hibernate/core/3.5/reference/en/html/performance.html#performance-fetching-profiles

したがって、あなたの質問に対する単一の明確な答えはないように思われますが、あなたの個々の状況に大きく依存する多くのアイデアや実践があるだけです.

于 2011-05-29T13:49:33.483 に答える
2

saveUserセッションをフラッシュしないでください。セッションをフラッシュすることは本当にまれです。これを Hibernate に任せれば、アプリケーションはより効率的になります。

そんなところにキャッシュモードを設定するのも実に奇妙です。どうしてそれをするの?

loadを使用するときではなく、使用するときに例外が発生する理由についての説明については、getload がエンティティが存在することを知っていると想定しているためです。選択クエリを実行してデータベースからユーザー データを取得するのではなく、オブジェクトでメソッドが初めて呼び出されたときにデータを取得するプロキシを返すだけです。初めてメソッドを呼び出したときにセッションが閉じられている場合、Hibernate はそれ以上データを取得できず、例外をスローします。loadデータを取得せずに既存のオブジェクトとの関係を開始する場合を除いて、めったに使用しないでください。getその他の場合にご利用ください。

LazyInitializationException を回避するための私の一般的な戦略は次のとおりです。

  • 可能な限り添付オブジェクトを使用してください。
  • 切り離されたオブジェクトを返すメソッドによってロードされるグラフを文書化し、このグラフが実際にロードされていることを単体テストします。
  • と を優先mergeします。これらのメソッドは、カスケードに応じて、いくつかのオブジェクトが接続され、他のオブジェクトが接続解除されたオブジェクトのグラフを残すことができます。この問題に悩まされることはありません。upadatesaveOrUpdatemerge
于 2011-05-29T14:18:22.323 に答える