15

この質問は、ここやインターネットで何度も聞かれていることを知っています。それらの回答の多くを読みましたが、この問題を解決する適切な方法をまだ理解していません。Spring MVC と JPA を試していますが、遅延ロードされたプロパティにアクセスするたびに LazyInitializationException が発生します。

ここに私が実験しているコードのいくつかがあります:

@Repository
public class MyDAO {
    private static final Logger logger = LoggerFactory.getLogger(MyDAO.class);

    @PersistenceContext
    private EntityManager em;

    @Transactional
    public void logDOI() {
        DOI myDOI = em.find(DOI.class, Long.valueOf(1));

        // This line gives the expected output
        logger.info("Fetched DOI: " + myDOI.getDoiKey());

        // This line throws the LazyInitalizationException
        for(DOIMembership m : myDOI.getDoiMemberships()) {
            logger.info("Got DOI Membership id: " + m.getId());
        }
    }
}

私がアクセスしているエンティティ:

@Entity
@Table(name="DOI")
public class DOI implements Serializable {
    private static final long serialVersionUID = 1L;

    @Id
    @SequenceGenerator(name="DOI_ID_GENERATOR", sequenceName="DOI_SEQ")
    @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="DOI_ID_GENERATOR")
    private long id;

    // Other properties omitted

    //bi-directional many-to-one association to DOIMembership
    @OneToMany(mappedBy="doi", fetch=FetchType.LAZY)
    private Set<DOIMembership> doiMemberships;

    public DOI() {
    }

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

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

    // Other Accessors Omitted

}

DOI から参照されるエンティティ

@Entity
@Table(name="DOI_MEMBERSHIP")
public class DOIMembership implements Serializable {
    private static final long serialVersionUID = 1L;

    @Id
    @SequenceGenerator(name="DOI_MEMBERSHIP_ID_GENERATOR", sequenceName="DOI_MEMBERSHIP_SEQ")
    @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="DOI_MEMBERSHIP_ID_GENERATOR")
    private long id;

    //bi-directional many-to-one association to DOI
    @ManyToOne(fetch=FetchType.LAZY)
    @JoinColumn(name="DOI_ID")
    private DOI doi;

    @Column(name="GROUP_ID")
    private BigDecimal groupId;

    @Column(name="DATA_SET")
    private BigDecimal dataSetId;

    public DOIMembership() {
    }

    public BigDecimal getGroupId() {
        return groupId;
    }

    public BigDecimal getDataSetId() {
        return dataSetId;
    }

    public void setDataSetId(BigDecimal dataSetId) {
        this.dataSetId = dataSetId;
    }

    public void setGroupId(BigDecimal groupId) {
        this.groupId = groupId;
    }

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

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

    public DOI getDoi() {
        return this.doi;
    }

    public void setDoi(DOI doi) {
        this.doi = doi;
    }

}

Spring MVC コントローラー:

@Controller
@RequestMapping("/summary")
public class DOISummaryController {
    @Autowired
    MyDAO myDAO;

    @RequestMapping()
    public String DOISummary() {
        myDAO.logDOI();

        return "home";
    }
}

私の春の構成:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:task="http://www.springframework.org/schema/task"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx-3.1.xsd
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
        http://www.springframework.org/schema/task
        http://www.springframework.org/schema/task/spring-task-3.0.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-3.1.xsd">

    <!-- Root Context: defines shared resources visible to all other web components -->

    <context:property-placeholder location="WEB-INF/spring/root-context.properties, WEB-INF/spring/datasource-context.properties"  />

    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName">
            <value>oracle.jdbc.driver.OracleDriver</value>
        </property>
        <property name="url">
            <value>${Url}</value>
        </property>
        <property name="username">
            <value>${Username}</value>
        </property>
        <property name="password">
            <value>${Password}</value>
        </property>
    </bean>

    <bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="jpaVendorAdapter">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />
        </property>
        <property name="packagesToScan" value="org.myorg.doi.domain" />
        <property name="jpaProperties">
            <props>
                <prop key="hibernate.dialect">
                    org.hibernate.dialect.Oracle10gDialect
                </prop>
                <prop key="hibernate.max_fetch_depth">3</prop>
                <prop key="hibernate.jdbc.fetch_size">50</prop>
                <prop key="hibernate.jdbc.batch_size">10</prop>
                <prop key="hibernate.show_sql">true</prop>
            </props>
        </property>
    </bean>

    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="emf" />
    </bean>

    <tx:annotation-driven transaction-manager="transactionManager" />

    <context:annotation-config />

    <task:annotation-driven />

    <context:component-scan base-package="org.myorg.doi" />

</beans>

要求に応じて、スタック トレース:

SEVERE: Servlet.service() for servlet [appServlet] in context with path [/DOI] threw exception [Request processing failed; nested exception is org.hibernate.LazyInitializationException: could not initialize proxy - no Session] with root cause
org.hibernate.LazyInitializationException: could not initialize proxy - no Session
    at org.hibernate.collection.internal.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:430)
    at org.hibernate.collection.internal.AbstractPersistentCollection.read(AbstractPersistentCollection.java:121)
    at org.hibernate.collection.internal.PersistentSet.iterator(PersistentSet.java:180)
    at org.myorg.doi.dao.MyDAO.logDOI(MyDAO.java:27)
    at org.myorg.doi.web.DOISummaryController.DOISummary(DOISummaryController.java:29)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at org.springframework.web.method.support.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:213)
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:126)
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:96)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:617)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:578)
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:80)
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:923)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:852)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:882)
    at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:778)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:621)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:722)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:305)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:225)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:123)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:472)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:168)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:98)
    at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:927)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118)
    at com.springsource.insight.collection.tcserver.request.HttpRequestOperationCollectionValve.traceNextValve(HttpRequestOperationCollectionValve.java:116)
    at com.springsource.insight.collection.tcserver.request.HttpRequestOperationCollectionValve.invoke(HttpRequestOperationCollectionValve.java:98)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:407)
    at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1001)
    at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:585)
    at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:310)
    at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
    at java.lang.Thread.run(Thread.java:680)

ご覧のとおり、私は純粋な JPA を使用しようとしており、Hibernate のみを JPA プロバイダーとして使用しています。

セッションがエンティティから切り離されているために例外が発生していることを理解しています。しかし、logDOI メソッドに @Transactional のアノテーションが付けられているため、現在トランザクション中である場合はそうはならないと思いました。

もちろん、FetchType を EAGER に変更するとすべてが完全に機能しますが、そうする必要はないようです。

OpenEntityManagerInViewFilter も認識していますが、@Transactional で注釈が付けられた DAO 内のエンティティへのすべてのアクセスを維持する場合 (または、私が認識していない他の手段を介して)、それを使用する必要はないようです。

この問題に間違ってアプローチしている可能性があると思いますが、正しいアプローチが何であるかはわかりません。遅延ロードされたプロパティを効果的に使用するにはどうすればよいですか?

4

4 に答える 4

18

Shailendra のおかげで、私はトランザクションを詳しく調べ始め、トランザクションがまったく開始されていないことに気付きました。その情報を使用して調査を行ったところ、Spring @Transaction not starting transactionsが見つかりました。<tx:annotation-driven/>servlet-context.xml ファイルを挿入すると、突然 logDOI のトランザクションが開始され、すべてが正しく機能しました。LazyInitializationException が発生しなくなりました。なぜそれが機能したのかはまったくわかりません。それに関する情報をいただければ幸いです。

アップデート:

私はそれを考え出した。私の問題の重要な部分は、servlet-context.xml ファイルにありました。これはそれがどのように見えたかです

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:beans="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd">

    <!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->

    <!-- Enables the Spring MVC @Controller programming model -->
    <annotation-driven />

    <!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory -->
    <resources mapping="/resources/**" location="/resources/" />

    <!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory -->
    <beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <beans:property name="prefix" value="/WEB-INF/views/" />
        <beans:property name="suffix" value=".jsp" />
    </beans:bean>

    <context:component-scan base-package="org.myapp.doi" />

</beans:beans>

主な問題は、そのコンテキストにありました: コンポーネント スキャン ライン。Spring MVC は、Bean がインスタンス化される 2 つのコンテキストを作成します。web.xml ファイルの contextConfigLocation パラメーターで定義されたルート アプリケーション コンテキストと、web.xml ファイルの DispatcherServlet で定義されたサーブレット コンテキストです。サーブレット コンテキストはアプリケーション コンテキストを参照できますが、その逆はできません。context:component-scan がサーブレット コンテキストで定義され、アプリケーションの名前空間全体がスキャンされた結果、私の DAO はサーブレット コンテキストでインスタンス化されていました。ただし、トランザクション アノテーションのスキャンはアプリケーション コンテキストで実行されており、そのための AOP プロキシはそこから実行できませんでした。MVC コントローラーのみをスキャンするように、サーブレット コンテキストの context:component-scan を変更するだけです (<context:component-scan base-package="org.myapp.doi.web" />すべてを修正しました。DAO はアプリケーション コンテキストで作成され、トランザクション用に適切に設定されていました。

于 2012-09-17T16:30:54.790 に答える
3

これを解決する最善の方法は、単一のトランザクションの下にあるにもかかわらず、セッションが閉じられる理由とタイミングを最初に理解することです。そして、そのための最良の方法は、log4j構成でhibernate(hibernateをJPAプロバイダーとして使用しているため)ログレベルをDEBUGに有効にし、セッションが閉じられている場所を追跡することです。これにより、明確なイメージが得られます。スタック トレースは、基礎となるセッションが閉じられたことを示唆していますが、明らかに理由はありません。ログに記録された関連するデバッグ/情報メッセージを投稿できます。

また、Spring フレームワークのロギングを設定して、トランザクション管理インフラストラクチャを追跡することもできます

ログは、基になるセッションがいつ閉じられ、トランザクションがコミットされたかについて、かなり適切なメッセージを提供します。

たとえば、

opened session at timestamp: 13476520251
............
...........
after transaction begin
..............

select x,y,z from......
...............
...commit
..........
..flushing session
..after transaction completion
..closing session
于 2012-09-16T10:36:02.503 に答える
1

私もこの問題を抱えていましたが、あなたの答えに触発されて解決しました。はい、これ。

私のアプリケーションは、Jpa リポジトリを使用して REST コントローラーでデータを処理しようとしましたが、セッションなしエラーが発生しました。コードは次のとおりです。

@RequestMapping(value = "/create", method = POST, consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Void> create(@RequestBody Map<String, Object> params) {
    Project project = Factory.project().build(params);
    project = repository.save(project);
    return ResponseEntity.ok().build();
}

この投稿 と この投稿によると、サーブレット コンテキストの Bean はアプリケーション コンテキストの Bean を参照できることがわかっています。したがって、TransactionManager はこの REST コントローラー Bean にアクセスできず、このエラーが発生します。

ソリューション、残りのコントローラーとリポジトリの間に中間層 Bean アプリケーション コンテキストを作成し、それらのコードをカプセル化します。試してみましたが、うまくいきます。後でコードを更新します。

于 2015-09-29T01:26:47.500 に答える
0

まず、@Autowired で設定された依存関係の場合、使用する DAO ごとに Spring Bean を宣言する必要があり、実行時に注入されます。

おそらく、これらの DAO には、次のような sessionFactory 参照が必要です。

<!-- a dao in which you inject your Hibernate SessionFactory bean by its own id -->
<bean id="userDAO" class="com.enterprise.model.dao.UserDAO">
    <property name="sessionFactory">
        <ref bean="sessionFactoryId" />
    </property>
</bean>

<!-- activate the @Repository annotation -->
<bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />

次に、Spring XML 構成内にこれを追加するのを忘れている可能性があります。

<!-- Add JPA support -->
<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="loadTimeWeaver">
        <bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver"/>
    </property>
</bean>

<!-- Add Transaction support -->
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="emf"/>
</bean>

第三に、インターセプターのもの:

<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
    <property name="interceptors">
        <list>
            <ref bean="openEntityManagerInViewInterceptor"/>
        </list>
    </property>
</bean>

<bean id="openEntityManagerInViewInterceptor"
      class="org.springframework.orm.jpa.support.OpenEntityManagerInViewInterceptor">
    <property name="entityManagerFactory" ref="entityManagerFactory" />
于 2012-09-14T17:00:43.773 に答える