LazyInitializationException в JPA и Hibernate
Я знаю, что этот вопрос задавался много раз здесь и через Интернет, и я прочитал многие из этих ответов, но я все еще не понимаю, как правильно решить эту проблему. Я экспериментирую с 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, но мне кажется, что мне не нужно его использовать, если я сохраняю весь доступ к своим сущностям в DAO с аннотацией @Transactional (или с помощью каких-либо других средств, о которых я не знаю).
Я думаю, что я могу подходить к этой проблеме неправильно, но я не знаю, каков правильный подход. Как эффективно использовать лениво загруженные свойства?
4 ответа
Благодаря Шайлендре я начал внимательно присматриваться к транзакции и заметил, что транзакция никогда не начиналась. С этой информацией я провел некоторое исследование и обнаружил, что Spring @Transaction не запускает транзакции. я кладу <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-компонентов: контекст корневого приложения, определенный с помощью параметра contextConfigLocation в файле web.xml, и контекст сервлета, определенный в DispatcherServlet в файле web.xml. Контекст сервлета мог видеть контекст приложения, но не наоборот. Теперь, в результате определения контекста: сканирования компонентов в контексте сервлета и сканирования всего пространства имен приложения, мой DAO был создан в контексте сервлета. Однако сканирование аннотации транзакции выполнялось в контексте приложения, и прокси-сервер AOP для него не мог быть выполнен оттуда. Простое изменение контекста: компонент-сканирование в контексте сервлета для сканирования только контроллеров MVC (<context:component-scan base-package="org.myapp.doi.web" />
все исправил; DAO создавался в контексте приложения и правильно настраивался на транзакции.
Лучший способ решить эту проблему - это сначала понять, почему и когда сессия закрывается, несмотря на то, что она находится под одной транзакцией. И лучшим способом для этого было бы включить уровень ведения журнала гибернации (поскольку вы используете hibernate в качестве поставщика JPA) для уровня DEBUG в конфигурации log4j и отслеживать, где сеансы закрыты. Это даст вам четкую картину. Хотя трассировка стека предполагает, что основной сеанс был закрыт, но, очевидно, нет причин, почему? Вы можете публиковать соответствующие отладочные / информационные сообщения в журнале.
Также вы можете настроить ведение журнала для Spring Framework для отслеживания инфраструктуры управления транзакциями.
Журналы дают довольно хорошие сообщения о том, когда основной сеанс был закрыт и транзакция зафиксирована.
Например,
opened session at timestamp: 13476520251
............
...........
after transaction begin
..............
select x,y,z from......
...............
...commit
..........
..flushing session
..after transaction completion
..closing session
Я тоже получил эту проблему, но вдохновленный вашим ответом, я ее решаю. Вот.
Мое приложение пытается использовать Jpa Repository для обработки данных в контроллере отдыха, который, как оказалось, получил ошибку без сеанса. Вот код:
@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 не может получить доступ к этому компоненту оставшегося контроллера, что приводит к этой ошибке.
Решение - создание контекста приложения bean-компонента среднего уровня между контроллером rest и репозиторием, инкапсулируя этот код. Я пытаюсь, это работает отлично. Обновите код позже.
Во-первых, для любой зависимости, установленной с помощью @Autowired, вы должны объявить Spring Bean для каждого DAO, который вы будете использовать, и он будет внедрен во время выполнения.
Может быть, эти 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" />