Весна, JPA, стеклянная рыба, EAR, TransactionRequiredException - PullRequest
2 голосов
/ 15 декабря 2011

После почти 10 часов поиска и проб разных вещей я не мог понять, что является главной причиной моей проблемы.Так что, может быть, кто-то может помочь мне здесь.Я учу Spring + JPA.У меня есть проект EAR (в Eclipse), состоящий из двух проектов: JPA и Servlets.Этот EAR развертывается на сервере приложений Glassfish.

Проект JPA: Бины, службы и DAO.

Проект сервлета: Только один простой HttpRequestHandlerServlet, вызов службы из JPA.

Проблема: На стороне сервлета получение данных из dao (через службу) работает нормально.Хранение данных вызывает TransactionRequiredException.


Проект JPA

Dao:

@PersistenceContext
EntityManager em;

@Transactional
public void persist(Employee entity) {
        em.persist(entity);
        em.flush();
    }
}

Служба:

@Autowired
private EmployeeDao dao;

public Employee store(Employee in) {
    dao.persist(in);
    return in;
}

services.xml:

<tx:annotation-driven transaction-manager="transactionManager" />
<context:annotation-config />
<context:load-time-weaver />
<jee:jndi-lookup id="dataSource" jndi-name="jdbc/test" />

<bean id="entityManagerFactory"
    class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="persistenceUnitName" value="testJPA" />
</bean>

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

<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />

<bean id="employeeDao" class="... EmployeeDaoImpl" />
<bean id="employeeService" class="... EmployeeServiceImpl">
    <property name="employeeDao" ref="employeeDao" />
</bean>

beanRefContext.xml:

<bean id="serviceContext"
    class="org.springframework.context.support.ClassPathXmlApplicationContext">
    <constructor-arg>
        <list>
            <value>classpath:services.xml</value>
        </list>
    </constructor-arg>
</bean>

persistence.xml:

<persistence-unit name="testJPA">
    <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
    <jta-data-source>jdbc/test</jta-data-source> 
    <class>... entity.Employee</class>
    <properties>
        <property name="eclipselink.logging.level" value="FINE"/>
    </properties> 
</persistence-unit>

Проект сервлета

web.xml:

<context-param>
    <param-name>parentContextKey</param-name>
    <param-value>serviceContext</param-value>
</context-param>

<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<servlet>
    <servlet-name>test</servlet-name>
    <servlet-class>org.springframework.web.context.support.HttpRequestHandlerServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>test</servlet-name>
    <url-pattern>/Test</url-pattern>
</servlet-mapping>

applicationContext.xml:

<bean id="test" class="... TestServlet">
    <property name="employeeService" ref="employeeService" />
</bean>

Сводка

В тестовом сервлете вызов employeeService.store( ... ) вызывает исключение:

    WARNING: StandardWrapperValve[test]: PWC1406: Servlet.service() for servlet testt threw exception
javax.persistence.TransactionRequiredException: 
Exception Description: No externally managed transaction is currently active for this thread
    at org.eclipse.persistence.internal.jpa.transaction.JTATransactionWrapper.throwCheckTransactionFailedException(JTATransactionWrapper.java:86)
    at org.eclipse.persistence.internal.jpa.transaction.JTATransactionWrapper.checkForTransaction(JTATransactionWrapper.java:46)
    at org.eclipse.persistence.internal.jpa.EntityManagerImpl.checkForTransaction(EntityManagerImpl.java:1776)
    at org.eclipse.persistence.internal.jpa.EntityManagerImpl.flush(EntityManagerImpl.java:780)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:601)
    at org.springframework.orm.jpa.ExtendedEntityManagerCreator$ExtendedEntityManagerInvocationHandler.invoke(ExtendedEntityManagerCreator.java:365)

Основные примечания:

  1. Проблема вызвана em.flush().
  2. Получение данных из JPA / db работает хорошо из сервлетов.
  3. Из контейнераМодульные тесты в проекте JPA работают хорошо (тоже em.flush).
  4. Инициализация контекстов пружин кажется хорошей в соответствии с журналами пружин.ServiceContext создан и WebApplicationContext впоследствии создается с его parrent.

Я попытался найти проблему где-то в моей иерархической инициализации контекста, поместив ее в проект Servlet, я также попробовал только одно определение bean-компонента вApplicationContext.xml проекта сервлета.Ничего из этого не работает, так что проблема, вероятно, будет где-то еще.

Действительно спасибо за любые комментарии, С уважением Z.

Ответы [ 2 ]

1 голос
/ 09 января 2012

Я переключился:

  • glassfish -> jboss,
  • eclipselink -> hibernate.

Моя конфигурация работает без каких-либо изменений.

1 голос
/ 15 декабря 2011

Прежде всего, учтите, что маркировка @Transactional непосредственно на уровне DAO с точки зрения дизайна считается несколько опрометчивой - обычно транзакции охватывают уровень обслуживания, поэтому рекомендуется устанавливать разграничение транзакций только на уровне обслуживания.

Во-вторых, хотя я непосредственно не работал с реализацией EclipseLink JPA, судя по этому исключению, вы пытались запустить глобальную транзакцию, но вы не настроили глобальную (JTA) реализацию диспетчера транзакций.

Ваша конфигурация также намекает на это:

  • в services.xml вы настроили локально управляемый менеджер транзакций (org.springframework.orm.jpa.JpaTransactionManager),
  • в файле persistence.xml вы настроили поставщика JPA на ожидание управления глобальными транзакциями для данного источника данных (через <jta-data-source>jdbc/test</jta-data-source>)

Если вам нужно работать с локальными транзакциями (что имеет место в 95% случаев), вы должны сконфигурировать вашего провайдера JPA для ЛОКАЛЬНОЙ транзакции примерно так:

    <persistence-unit name="test" transaction-type="RESOURCE_LOCAL">
          <non-jta-data-source>jdbc/test</non-jta-data-source>
    </persistence-unit>

Если глобальные транзакции - это то, что вам действительно нужно, тогда вам нужно будет внедрить другую реализацию диспетчера транзакций в контекст Spring. Более подробные инструкции о том, как настроить этот сценарий, зависят от самой реализации - на ум приходят две свободно доступные Atomikos и Bitronix . Они оба хорошо интегрируются с Spring и хорошо документированы.

...