多种方法上的Spring JPA事务 [英] Spring JPA transaction over multiple methods

查看:61
本文介绍了多种方法上的Spring JPA事务的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在 Tomcat 7 中运行的 Web 应用程序中使用带有 JPA 和 Hibernate 4 的 Spring 3.2.该应用程序分为控制器、服务一个 DAO 类.服务类在类和方法级别具有带注释的事务配置.DAO 是普通 JPA,实体管理器由 @PersistenceContext 注释注入.

@Service("galleryService")@Transactional(propagation=Propagation.SUPPORTS, readOnly=true)公共类 GalleryServiceImpl 实现 GalleryService {@覆盖公共图片getPicture(长图片ID){返回pictureDao.find(pictureId);}@覆盖公共列表getComments(图片){列表<图片评论>评论 = commentDao.findVisibleByPicture(图片);Collections.sort(comments, new Comment.ByCreatedOnComparator(Comment.ByCreatedOnComparator.SORT_DESCENDING));回复评论;}...}@控制器@RequestMapping("/gallery/displayPicture.html")公共类 DisplayPictureController 扩展 AbstractGalleryController {@RequestMapping(method = RequestMethod.GET)public String doGet(ModelMap 模型,@RequestParam(REQUEST_PARAM_PICTURE_ID) Long pictureId) {图片图片 = galleryService.getPicture(pictureId);如果(图片!= null){model.addAttribute("图片", 图片);//添加注释model.addAttribute("comments", galleryService.getComments(picture));} 别的 {LOGGER.warn(MessageFormat.format("Picture {0} not found.", pictureId));返回 ViewConstants.CONTENT_NOT_FOUND;}返回 ViewConstants.GALLERY_DISPLAY_PICTURE;}...}

我打开了 org.springframework.transaction 的调试日志记录,并注意到创建新事务"、打开新的 EntityManager"、获取..."、关闭..."和提交事务"已完成对于我的服务类中的每次方法调用.即使这些方法由我的控制器类中的一个方法调用.这是我的日志输出示例:

2014-12-03 10:53:00,448 org.springframework.transaction.support.AbstractPlatformTransactionManager getTransaction调试:创建名为 [de.domain.webapp.gallery.service.GalleryServiceImpl.getPicture] 的新事务:PROPAGATION_REQUIRED,ISOLATION_DEFAULT,readOnly2014-12-03 10:53:00,448 org.springframework.orm.jpa.JpaTransactionManager doBegin调试:为 JPA 事务打开了新的 EntityManager [org.hibernate.jpa.internal.EntityManagerImpl@6133a72f]2014-12-03 10:53:00,468 org.springframework.orm.jpa.JpaTransactionManager doBegin调试:将 JPA 事务公开为 JDBC 事务 [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@5182c1b7]2014-12-03 10:53:00,468 org.springframework.transaction.interceptor.TransactionAspectSupport prepareTransactionInfoTRACE:获取 [de.domain.webapp.gallery.service.GalleryServiceImpl.getPicture] 的交易2014-12-03 10:53:00,489 org.springframework.transaction.interceptor.TransactionAspectSupport commitTransactionAfterReturning跟踪:完成 [de.domain.webapp.gallery.service.GalleryServiceImpl.getPicture] 的交易2014-12-03 10:53:00,489 org.springframework.transaction.support.AbstractPlatformTransactionManager processCommit调试:启动事务提交2014-12-03 10:53:00,489 org.springframework.orm.jpa.JpaTransactionManager doCommit调试:在 EntityManager 上提交 JPA 事务 [org.hibernate.jpa.internal.EntityManagerImpl@6133a72f]2014-12-03 10:53:00,489 org.springframework.orm.jpa.JpaTransactionManager doCleanupAfterCompletion调试:交易后关闭 JPA EntityManager [org.hibernate.jpa.internal.EntityManagerImpl@6133a72f]2014-12-03 10:53:00,489 org.springframework.orm.jpa.EntityManagerFactoryUtils closeEntityManager调试:关闭 JPA EntityManager

我知道我可以使用 OpenSessionInView 来保持完整请求的休眠会话,但有些人说,OSIV 是一种反模式.相反,我使用的是 SpringOpenEntityManagerInViewFilter.但没有成功.我怎样才能实现,Spring 使用单个事务来调用我的控制器的多个服务层方法?还是我误会了什么?

这是我的 Spring 配置的一部分:

<属性名称="数据源" ref="数据源"/><属性名称="jpaVendorAdapter"><bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"><property name="database" value="MYSQL"/><property name="showSql" value="false"/></bean></属性></bean><bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean"><property name="jndiName" value="java:comp/env/jdbc/tikron"/></bean><bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor"/><bean id="jpaTemplate" class="org.springframework.orm.jpa.JpaTemplate"><property name="entityManagerFactory" ref="en​​tityManagerFactory"/></bean><bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"><property name="entityManagerFactory" ref="en​​tityManagerFactory"/></bean><tx:annotation-driven transaction-manager="transactionManager"/><context:component-scan base-package="de.domain.webapp"><context:include-filter type="regex" expression=".*Service"/></context:component-scan>

我的持久化单元:

<!-- 实体位于外部项目 tikron-data --><jar-file>/WEB-INF/lib/tikron-data-2.0.1-SNAPSHOT.jar</jar-file><!-- 启用JPA 2二级缓存--><shared-cache-mode>ALL</shared-cache-mode><属性><property name="hibernate.hbm2ddl.auto" value="validate"/><property name="hibernate.cache.use_second_level_cache" value="true"/><property name="hibernate.cache.use_query_cache" value="true"/><property name="hibernate.cache.region.factory_class" value="org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory"/><property name="hibernate.generate_statistics" value="false"/></属性></persistence-unit>

应用程序启动时的更多日志输出:

2014-12-03 10:46:48,428 org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean createNativeEntityManagerFactory信息:为持久性单元tikron-data"构建 JPA 容器 EntityManagerFactory2014-12-03 10:46:48,428 org.hibernate.ejb.HibernatePersistence logDeprecation警告:HHH015016:遇到不推荐使用的 javax.persistence.spi.PersistenceProvider [org.hibernate.ejb.HibernatePersistence];使用 [org.hibernate.jpa.HibernatePersistenceProvider] 代替.2014-12-03 10:46:48,448 org.hibernate.jpa.internal.util.LogHelper logPersistenceUnitInformation信息:HHH000204:处理 PersistenceUnitInfo [名称:tikron-data...]...2014-12-03 10:46:51,101 org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingleton信息:在 org.springframework.beans.factory.support.DefaultListableBeanFactory@6cccf90d 中预实例化单例:定义 bean [propertyConfigurer,messageSource,entityManagerFactory,dataSource];工厂层次结构的根2014-12-03 10:46:51,111 org.springframework.web.context.ContextLoader initWebApplicationContext信息:根 WebApplicationContext:初始化在 3374 毫秒内完成

提前致谢.

解决方案

您需要发挥您的 PROPAGATION_LEVEL 并正确构建您的服务 bean 调用.如果您在 Service 类上使用 @Transactional,那么您所描述的情况是正常的,因为事务划分发生在公共方法级别.因此,根据进入服务 bean 的公共方法时的传播级别,事务将启动、加入现有事务、抛出异常或以非事务方式执行.

要在一个事务中执行服务方法,只需将传播级别设置为支持,就像您在 GalleryService 中所做的那样(前提是您不在方法级别覆盖它),并从另一个服务的单个方法调用这些方法,该方法被注释为 @Transactional(propagation=Propagation.REQUIRED).让您的电话通过 bean 传递很重要,例如(galleryService.getPicture 而不是本地调用 getPicture),因为注入事务语义的方面针对包装 bean 的代理工作

@Service("exampleService")@Transactional(propagation=Propagation.REQUIRED)公共类 ExampleServiceImpl 实现 ExampleService {@自动连线私人画廊服务画廊服务;@覆盖公共无效单交易(){画廊服务.getPicture画廊服务.getComments}...}

简要的 PROPAGATION_LEVEL 词汇表

<块引用>

强制性支持当前事务,如果不存在则抛出异常.嵌套如果当前事务存在,则在嵌套事务中执行,其他行为类似于 PROPAGATION_REQUIRED.绝不以非事务方式执行,如果存在事务则抛出异常.不支持以非事务方式执行,如果存在则暂停当前事务.必需的支持当前事务,如果不存在则创建一个新事务.需要_新创建一个新事务,如果存在则暂停当前事务.支持支持当前事务,如果不存在则以非事务方式执行.

关于评论的更新

但是将服务方法调用合并到一个服务方法中是否是在单个事务中处理这些调用的唯一方法?

不,但在我看来,这是您最好的选择.考虑文章 http://www.ibm.com/developerworks/java/library/j-ts2/index.html.我所描述的是文章中所称的API 层策略.其定义为

<块引用>

API 层事务策略在你有作为后端主要入口点的粗粒度方法功能.(如果您愿意,可以称他们为服务.)在此场景、客户端(无论是基于 Web 的、基于 Web 服务的、基于消息,甚至桌面)向后端发出单个调用以执行特定请求.

现在在标准的三层架构中,您拥有表示层、业务层和持久层.简而言之,您可以注释您的控制器、服务或 DAO.服务是持有逻辑工作单元的服务.如果你注释你的控制器,它们是你的表示层的一部分,如果你的事务语义在那里,并且你决定切换或添加一个非 http 客户端(例如 Swing 客户端),你一定会迁移或复制你的事务逻辑.DAO 层也不应该是事务的所有者,'DAO 方法的粒度远小于业务逻辑单元.我在最佳实践等方面有所限制,但是,如果您不确定,请选择您的业务(服务)作为您的 API 事务层 :)

你有很多帖子在各个方向讨论这个话题

为什么将@transactional 与@service 与@ 结合使用控制器@Transactional"应该在哪里?放置服务层或DAO

非常好的和有趣的阅读,很多观点并且非常依赖上下文

I'm using Spring 3.2 with JPA and Hibernate 4 in a web application running in Tomcat 7. The application is divided into controller, service an DAO classes. The service classes have an annotated transaction configuration at class and method level. The DAOs are plain JPA with entity manager injected by @PersistenceContext annotation.

@Service("galleryService")
@Transactional(propagation=Propagation.SUPPORTS, readOnly=true)
public class GalleryServiceImpl implements GalleryService {

  @Override
  public Picture getPicture(Long pictureId) {
    return pictureDao.find(pictureId);
  }

  @Override
  public List<PictureComment> getComments(Picture picture) {
    List<PictureComment> comments = commentDao.findVisibleByPicture(picture);
    Collections.sort(comments, new Comment.ByCreatedOnComparator(Comment.ByCreatedOnComparator.SORT_DESCENDING));
    return comments;
  }
  ...
}

@Controller
@RequestMapping("/gallery/displayPicture.html")
public class DisplayPictureController extends AbstractGalleryController {

  @RequestMapping(method = RequestMethod.GET)
  public String doGet(ModelMap model, @RequestParam(REQUEST_PARAM_PICTURE_ID) Long pictureId) {
    Picture picture = galleryService.getPicture(pictureId);
    if (picture != null) {
      model.addAttribute("picture", picture);
      // Add comments
      model.addAttribute("comments", galleryService.getComments(picture));
    } else {
      LOGGER.warn(MessageFormat.format("Picture {0} not found.", pictureId));
      return ViewConstants.CONTENT_NOT_FOUND;
    }
    return ViewConstants.GALLERY_DISPLAY_PICTURE;
  }
  ...
}

I switched on debug logging for org.springframework.transaction and noticed, that "Creating new transaction", "Opened new EntityManager", "Getting...", "Closing..." and "Committing transaction" is done for every call of a method in my service class. Even if those methods where called by one single method in my controller class. Here is an example of my log output:

2014-12-03 10:53:00,448 org.springframework.transaction.support.AbstractPlatformTransactionManager getTransaction
DEBUG: Creating new transaction with name [de.domain.webapp.gallery.service.GalleryServiceImpl.getPicture]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT,readOnly
2014-12-03 10:53:00,448 org.springframework.orm.jpa.JpaTransactionManager doBegin
DEBUG: Opened new EntityManager [org.hibernate.jpa.internal.EntityManagerImpl@6133a72f] for JPA transaction
2014-12-03 10:53:00,468 org.springframework.orm.jpa.JpaTransactionManager doBegin
DEBUG: Exposing JPA transaction as JDBC transaction [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@5182c1b7]
2014-12-03 10:53:00,468 org.springframework.transaction.interceptor.TransactionAspectSupport prepareTransactionInfo
TRACE: Getting transaction for [de.domain.webapp.gallery.service.GalleryServiceImpl.getPicture]
2014-12-03 10:53:00,489 org.springframework.transaction.interceptor.TransactionAspectSupport commitTransactionAfterReturning
TRACE: Completing transaction for [de.domain.webapp.gallery.service.GalleryServiceImpl.getPicture]
2014-12-03 10:53:00,489 org.springframework.transaction.support.AbstractPlatformTransactionManager processCommit
DEBUG: Initiating transaction commit
2014-12-03 10:53:00,489 org.springframework.orm.jpa.JpaTransactionManager doCommit
DEBUG: Committing JPA transaction on EntityManager [org.hibernate.jpa.internal.EntityManagerImpl@6133a72f]
2014-12-03 10:53:00,489 org.springframework.orm.jpa.JpaTransactionManager doCleanupAfterCompletion
DEBUG: Closing JPA EntityManager [org.hibernate.jpa.internal.EntityManagerImpl@6133a72f] after transaction
2014-12-03 10:53:00,489 org.springframework.orm.jpa.EntityManagerFactoryUtils closeEntityManager
DEBUG: Closing JPA EntityManager

I know that I can use an OpenSessionInView to hold the hibernate session for a complete request but some people said, OSIV is an antipattern. Instead I'm using SpringOpenEntityManagerInViewFilter. But with no success. How can I achieve, Spring uses a single transaction for multiple service layer method calls of my controller? Or did I missunderstand anything?

Here a part of my Spring configuration:

<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="jpaVendorAdapter">
        <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
            <property name="database" value="MYSQL" />
            <property name="showSql" value="false" />
        </bean>
    </property>
</bean>

<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
    <property name="jndiName" value="java:comp/env/jdbc/tikron" />
</bean>

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

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

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

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

<context:component-scan base-package="de.domain.webapp">
    <context:include-filter type="regex" expression=".*Service"/>
</context:component-scan>

My persistence unit:

<persistence-unit name="tikron-data" transaction-type="RESOURCE_LOCAL">
  <!-- Entities located in external project tikron-data -->
  <jar-file>/WEB-INF/lib/tikron-data-2.0.1-SNAPSHOT.jar</jar-file>
  <!-- Enable JPA 2 second level cache -->
  <shared-cache-mode>ALL</shared-cache-mode>
  <properties>
    <property name="hibernate.hbm2ddl.auto" value="validate" />
    <property name="hibernate.cache.use_second_level_cache" value="true" />
    <property name="hibernate.cache.use_query_cache" value="true" />
    <property name="hibernate.cache.region.factory_class" value="org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory"/>
    <property name="hibernate.generate_statistics" value="false" /> 
  </properties>
</persistence-unit>

Some more log output from application startup:

2014-12-03 10:46:48,428 org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean createNativeEntityManagerFactory
INFO: Building JPA container EntityManagerFactory for persistence unit 'tikron-data'
2014-12-03 10:46:48,428 org.hibernate.ejb.HibernatePersistence logDeprecation
WARN: HHH015016: Encountered a deprecated javax.persistence.spi.PersistenceProvider [org.hibernate.ejb.HibernatePersistence]; use [org.hibernate.jpa.HibernatePersistenceProvider] instead.
2014-12-03 10:46:48,448 org.hibernate.jpa.internal.util.LogHelper logPersistenceUnitInformation
INFO: HHH000204: Processing PersistenceUnitInfo [
    name: tikron-data
    ...]

...

2014-12-03 10:46:51,101 org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@6cccf90d: defining beans [propertyConfigurer,messageSource,entityManagerFactory,dataSource]; root of factory hierarchy
2014-12-03 10:46:51,111 org.springframework.web.context.ContextLoader initWebApplicationContext
INFO: Root WebApplicationContext: initialization completed in 3374 ms

Thanks in advance.

解决方案

You need to play your PROPAGATION_LEVEL and structure your service bean calls properly. If you're using @Transactional on your Service classes, what you described is normal, since the transaction demarcation happens on public methods level. So depending on the propagation level when entering the public method of the service bean, the transaction will either start, join an existing transaction, throw an exception or execute non-transactionally.

To have service methods execute in one transaction, its enough to have the propagation level set to support as you do in your GalleryService (providing that you don't override it on a method level), and call these methods from a single method of another service which is annotated @Transactional(propagation=Propagation.REQUIRED). Its important to have you're calls pass through a bean e.g. (galleryService.getPicture instead of local call getPicture), 'cause aspects that inject the transaction semantics work against a proxy that wraps the bean

@Service("exampleService")
@Transactional(propagation=Propagation.REQUIRED)
public class ExampleServiceImpl implements ExampleService {

  @Autowired
  private GalleryService galleryService;

  @Override
  public void singleTransaction() {
    galleryService.getPicture
    galleryService.getComments
  }

  ...
}

a brief PROPAGATION_LEVEL glossary

MANDATORY
          Support a current transaction, throw an exception if none exists.
NESTED
          Execute within a nested transaction if a current transaction exists, behave like PROPAGATION_REQUIRED else.
NEVER
          Execute non-transactionally, throw an exception if a transaction exists.
NOT_SUPPORTED
          Execute non-transactionally, suspend the current transaction if one exists.
REQUIRED
          Support a current transaction, create a new one if none exists.
REQUIRES_NEW
          Create a new transaction, suspend the current transaction if one exists.
SUPPORTS
          Support a current transaction, execute non-transactionally if none exists.

UPDATE with respect to the comment

But is combining service method calls into one service method the only way to handle those calls in one single transaction?

No, but in my opinion its your best option. Consider the article http://www.ibm.com/developerworks/java/library/j-ts2/index.html. What I'm describing is whats referred to in the article as API layer strategy. Its defined as

The API Layer transaction strategy is used when you have coarse-grained methods that act as primary entry points to back-end functionality. (Call them services if you would like.) In this scenario, clients (be they Web-based, Web services based, message-based, or even desktop) make a single call to the back end to perform a particular request.

Now in the standard three-layer architecture you have the presentation, a business and a persistence layer. In simple words you can annotate your controllers, services or DAOs. Services are the ones holding the logical unit of work. If you annotate your controllers, they are part of your presentation layer, if your transactional semantics is there, and you decide to switch or add a non-http client (e.g. Swing client), you're bound to either migrate or duplicate your transaction logic. DAO layers should also not be the owners of transaction, 'the granularity of the DAO methods is much less than what is a business logical unit. I'm restraining from points like best-practice etc. but, if you're uncertain, choose your business (service) as your API transaction layer :)

You have numerous posts discussing this topic in all directions

why use @transactional with @service insted of with @controller Where should "@Transactional" be place Service Layer or DAO

very good and fun reading, many opinions and very context-dependant

这篇关于多种方法上的Spring JPA事务的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆