Перешел на JPA, теперь я всегда получаю TransactionRequiredException при любой вставке

1

У меня есть веб-приложение Spring4. Первоначально я использовал Hibernate SessionFactory и развивался с использованием API Spring Hibernate. Все работало нормально. Глупо, возможно, недавно я решил переключиться на использование JPA, а Hibernate остался моим провайдером. Я переконфигурировал настройки Spring и переписал большую часть моего кода. Изначально тестировалось так, что все мои базы данных читали, что они делают. Затем я попытался создать базу данных, и все они не работают так:

javax.persistence.TransactionRequiredException: no transaction is in progress
    at org.hibernate.ejb.AbstractEntityManagerImpl.flush(AbstractEntityManagerImpl.java:970)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:483)
    at org.springframework.orm.jpa.ExtendedEntityManagerCreator$ExtendedEntityManagerInvocationHandler.invoke(ExtendedEntityManagerCreator.java:342)
    at com.sun.proxy.$Proxy47.flush(Unknown Source)
    at com.taubler.oversite.dao.impl.EntityDaoImpl.insert(EntityDaoImpl.java:65)
    ...

Имейте в виду, что мой код работал нормально при использовании HibernateTemplate, SessionFactory, HibernateTransactionManager и т.д. Мои классы бизнес-логики, а также мои DAO, аннотируются с @Transactional же, как и раньше.

Похоже, что Hibernate пытается создать транзакцию, поскольку я вижу следующее в своих журналах непосредственно перед трассировкой стека:

2014-09-24 05:40:28 [http-bio-8080-exec-3] DEBUG org.hibernate.engine.transaction.internal.jdbc.JdbcTransaction - initial autocommit status: true
2014-09-24 05:40:28 [http-bio-8080-exec-3] DEBUG org.hibernate.engine.transaction.internal.jdbc.JdbcTransaction - disabling autocommit
2014-09-24 05:40:28 [http-bio-8080-exec-3] DEBUG org.hibernate.internal.SessionImpl - Opened session at timestamp: 14115372286
2014-09-24 05:40:28 [http-bio-8080-exec-3] TRACE org.hibernate.internal.SessionImpl - Setting flush mode to: AUTO
2014-09-24 05:40:28 [http-bio-8080-exec-3] TRACE org.hibernate.internal.SessionImpl - Setting cache mode to: NORMAL
2014-09-24 05:40:28 [http-bio-8080-exec-3] TRACE org.hibernate.event.internal.AbstractSaveEventListener - Transient instance of: com.taubler.oversite.entities.EmailAddress
2014-09-24 05:40:28 [http-bio-8080-exec-3] TRACE org.hibernate.event.internal.DefaultPersistEventListener - Saving transient instance
2014-09-24 05:40:28 [http-bio-8080-exec-3] TRACE org.hibernate.event.internal.AbstractSaveEventListener - Saving [com.taubler.oversite.entities.EmailAddress#<null>]
2014-09-24 05:40:28 [http-bio-8080-exec-3] TRACE org.hibernate.engine.spi.IdentifierValue - ID unsaved-value: null
2014-09-24 05:40:28 [http-bio-8080-exec-3] DEBUG org.hibernate.event.internal.AbstractSaveEventListener - Delaying identity-insert due to no transaction in progress
2014-09-24 05:40:28 [http-bio-8080-exec-3] DEBUG org.hibernate.internal.SessionImpl - Opened session at timestamp: 14115372287
2014-09-24 05:40:28 [http-bio-8080-exec-3] TRACE org.hibernate.internal.SessionImpl - Setting flush mode to: AUTO
2014-09-24 05:40:28 [http-bio-8080-exec-3] TRACE org.hibernate.internal.SessionImpl - Setting cache mode to: NORMAL
2014-09-24 05:40:28 [http-bio-8080-exec-3] DEBUG org.hibernate.engine.transaction.spi.AbstractTransactionImpl - rolling back
2014-09-24 05:40:28 [http-bio-8080-exec-3] DEBUG org.hibernate.engine.transaction.internal.jdbc.JdbcTransaction - rolled JDBC Connection
2014-09-24 05:40:28 [http-bio-8080-exec-3] DEBUG org.hibernate.engine.transaction.internal.jdbc.JdbcTransaction - re-enabling autocommit
2014-09-24 05:40:28 [http-bio-8080-exec-3] TRACE org.hibernate.engine.transaction.internal.TransactionCoordinatorImpl - after transaction completion
2014-09-24 05:40:28 [http-bio-8080-exec-3] TRACE org.hibernate.internal.SessionImpl - after transaction completion
2014-09-24 05:40:28 [http-bio-8080-exec-3] TRACE org.hibernate.internal.SessionImpl - Closing session
2014-09-24 05:40:28 [http-bio-8080-exec-3] TRACE org.hibernate.engine.jdbc.internal.LogicalConnectionImpl - Closing logical connection
2014-09-24 05:40:28 [http-bio-8080-exec-3] TRACE org.hibernate.engine.jdbc.internal.JdbcResourceRegistryImpl - Closing JDBC container [org.hibernate.engine.jdbc.internal.JdbcResourceRegistryImpl@2c4e3947]
2014-09-24 05:40:28 [http-bio-8080-exec-3] DEBUG org.hibernate.engine.jdbc.internal.LogicalConnectionImpl - Releasing JDBC connection
2014-09-24 05:40:28 [http-bio-8080-exec-3] DEBUG org.hibernate.engine.jdbc.internal.LogicalConnectionImpl - Released JDBC connection
2014-09-24 05:40:28 [http-bio-8080-exec-3] TRACE org.hibernate.engine.jdbc.internal.LogicalConnectionImpl - Logical connection closed

Вот какой код. Во-первых, фрагменты из моей конфигурации config:

<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
      <property name="dataSource" ref="dataSource" />
      <property name="packagesToScan" value="com.taubler.oversite.entities" />
      <property name="jpaVendorAdapter">
         <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />
      </property>
      <property name="jpaProperties">
         <props>
            <prop key="hibernate.hbm2ddl.auto">update</prop>
            <prop key="hibernate.dialect">org.hibernate.dialect.MySQLInnoDBDialect</prop>
            <prop key="hibernate.show_sql">true</prop>
            <prop key="hibernate.jdbc.batch_size">20</prop>
            <prop key="hibernate.ejb.naming_strategy">org.hibernate.cfg.ImprovedNamingStrategy</prop>               
         </props>
      </property>
</bean>

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

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

<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="${db.driver}" />
    <property name="url" value="${db.url}" />
    <property name="username" value="${db.username}" />
    <property name="password" value="${db.password}" />
</bean>

Пример бизнес-логики (менеджера). Этот класс @Autowired в SpringMVC-контроллер, поэтому контроллер вызывает прокси-сервер:

...

@Autowired
private EmailAddressDao emailAddressDao;
...

@Override
@Transactional
public EmailAddress addEmailAddress(User user, String email) {  
    EmailAddress emailAddress = new EmailAddress(user, email);
    emailAddress.setMain(false);
    emailAddress.setValidated(false);
    emailAddressDao.insert(emailAddress);
    this.initiateEmailValidation(emailAddress);
    return emailAddress;
}

И DAO, вызванный этим менеджером:

...

@PersistenceUnit
private EntityManagerFactory entityManagerFactory;

protected final EntityManager getEntityManager() {
    return entityManagerFactory.createEntityManager();
}

public boolean insert(Entity o) {
    o.setCreated(new Date());
    this.getEntityManager().persist(o);
    this.getEntityManager().flush();
    return true;
}

Я пробовал разные варианты этого. Первоначально оба метода DAO и Manager были аннотированы с помощью @Transactional(propagation=REQUIRED); так оно и было с чистым гибернатом. Я попытался удалить параметр распространения, аннотируя только метод Manager, аннотируя только метод DAO... ничего не работает.

Есть предположения? Кажется, что существует нечто принципиально иное между HibernateTransactionManager и JpaTransactionManager.

Теги:
spring
hibernate
jpa
transactions

2 ответа

4
Лучший ответ
@PersistenceUnit
private EntityManagerFactory entityManagerFactory;

protected final EntityManager getEntityManager() {
    return entityManagerFactory.createEntityManager();
}

Проблема в том, что вы сами создаете менеджер сущности, не делайте этого. Просто добавьте EntityManager вместо EntityManagerFactory и @PersistenceContext комментарий @PersistenceContext вместо @PersistenceUnit. Весна позаботится о том, чтобы сделать ее связанной с текущей транзакцией.

@PersistenceContext
private EntityManager entityManager;

protected final EntityManager getEntityManager() {
    return entityManger;
}

Если вы действительно хотите продолжать инъекцию EntityManagerFactory используйте метод getTransactionalEntityManager EntityManagerFactoryUtils чтобы получить экземпляр EntityManager управляемый Spring.

protected final EntityManager getEntityManager() {
     return EntityManagerFactoryUtils.getTransactionalEntityManager(entityManagerFactory);
}

Также тот факт, что он работал с "простым" спящим режимом, не означает, что ваша установка должна быть правильной. Вы упомянули, что вы использовали HibernateTemplate который в принципе мог работать без правильной настройки транзакции, поскольку он начнет новую трансацию только для действия. Поэтому вполне может быть, что приложение, похоже, работает правильно там, где оно на самом деле не было. Возможно, у вас было несколько транзакций, в которых вы ожидали одного (со служебного уровня).

Еще одно замечание: ваш код может быть опасным

public boolean insert(Entity o) {
    o.setCreated(new Date());
    this.getEntityManager().persist(o);
    this.getEntityManager().flush();
    return true;
}

Это, в вашем случае, может привести к EntityManager 2 разных EntityManager, поэтому вы могли бы сбрасывать еще один, когда вы упорствовали. Рядом с этим вы не должны называть flush поскольку это будет завершено завершением транзакции.

  • 0
    Это оно; Благодарю. Я не осознавал, что буквально каждый раз создавал новый EntityManager, но теперь это имеет смысл. Также справедливо отметить, что HibernateTemplate работает не как доказательство «правильной» настройки. Хотя, особенно при распространении = ТРЕБУЕМЫЕ, вложенные транзакционные методы должны быть в порядке и приводить только к одной транзакции. Я читал, однако, что распространение транзакций Spring ведет себя по-разному с диспетчером транзакций JPA.
2

Вам нужно ввести EntityManager непосредственно с @PersistenceContext аннотации @PersistenceContext, вместо PersistenceUnit

Ещё вопросы

Сообщество Overcoder
Наверх
Меню