У меня есть веб-приложение 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
.
@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
поскольку это будет завершено завершением транзакции.
Вам нужно ввести EntityManager
непосредственно с @PersistenceContext
аннотации @PersistenceContext
, вместо PersistenceUnit