Spring JPA事务通过多种方法

Posted

技术标签:

【中文标题】Spring JPA事务通过多种方法【英文标题】:Spring JPA transaction over multiple methods 【发布时间】:2015-01-31 20:58:09 【问题描述】:

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

@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;
  
  ...

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

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

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

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

<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>

我的持久化单元:

<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>

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

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

提前致谢。

【问题讨论】:

【参考方案1】:

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

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

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

  @Autowired
  private GalleryService galleryService;

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

  ...

一个简短的 PROPAGATION_LEVEL 词汇表

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.

关于评论的更新

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

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

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

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

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

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

非常好的和有趣的阅读,很多意见和非常依赖上下文

【讨论】:

感谢您的解释!但是,将服务方法调用组合成一个服务方法是在一个事务中处理这些调用的唯一方法吗?这将导致为每个控制器提供一种服务方法。我想,我的问题是由于我的 webapp 中的错误 Spring 配置引起的。 编辑了一个答案,但它只是一般性的评论,而不是一个决定性的答案

以上是关于Spring JPA事务通过多种方法的主要内容,如果未能解决你的问题,请参考以下文章

spring spring data jpa save操作事务

Spring data JPA:未提交每条记录的事务

两小时上手Spring Boot (开发红包程序———通过JPA 连接Mysql ,事务等内容) 遇到的问题

Spring JPA无法提交jdbc事务的解决办法

JPA事务不生效原因

Spring Data JPA事务支持