【问题标题】:Spring Data JPA: Pageable query rollbacks transactionSpring Data JPA:可分页查询回滚事务
【发布时间】:2014-12-11 16:32:06
【问题描述】:

我在尝试使用 Spring Data 存储库中的分页运行 @NamedQuery 时考虑了一个问题。 实体类如下所示:

@NamedQueries({
@NamedQuery(
        name = "Customer.findByNamePattern",
        query = "select c from Customer c where c.name like :pattern"
    )    
}) 
@Entity
public class Customer {
    @Id
    @GeneratedValue(strategy = GenerationType.TABLE)
    private Long id;    
    private String name; 

仓库界面为:

public interface CustomerRepository  extends JpaRepository<Customer, Long> {    
    //@Query("select c from Customer c where c.name like :pattern")
    Page<Customer> findByNamePattern(@Param("pattern") String pattern,Pageable pageable);
}

当我尝试从非事务上下文 (junit) 调用分页存储库方法时,它工作正常。

当我从事务服务方法调用它时,例如:

@Service("customerService")
@Transactional
public class CustomerServiceImpl implements CustomerService {
    private static Logger log = Logger.getLogger( CustomerServiceImpl.class.getName());
    @Autowired
    private CustomerRepository customerRepository;

    @Transactional(readOnly = true)
    public Page<Customer> findAllPaged(int pageNum, int pageSize) {     
        PageRequest pr = new PageRequest(pageNum,pageSize);
        return customerRepository.findAll(pr);      
    }

    @Transactional(readOnly = true)
    public Page<Customer> findByNamePatternPaged(String keyword, int pageNum, int pageSize) {       
        PageRequest pr = new PageRequest(pageNum,pageSize);
        String pattern = "%"+keyword+"%";
        return customerRepository.findByNamePattern(pattern, pr);       
    }

...再次调用findAllPaged()可以正常工作。

但是,当我尝试调用应该使用命名查询的方法时,我总是会遇到异常:

javax.persistence.RollbackException
org.springframework.transaction.TransactionSystemException: Could not commit JPA transaction;      nested exception is javax.persistence.RollbackException: Transaction marked as rollbackOnly
at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:524)
at     org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:757)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:726)
at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:478)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:272)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:95)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:207)
at com.sun.proxy.$Proxy35.findByNamePatternPaged(Unknown Source)
at datapagedquery.service.TestCustomerService.testFindByPatternPaged(TestCustomerService.java:36)
...
Caused by: javax.persistence.RollbackException: Transaction marked as rollbackOnly
at org.hibernate.jpa.internal.TransactionImpl.commit(TransactionImpl.java:74)
at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:515)
... 33 more

在存储库方法上使用org.springframework.data.jpa.repository.Query 注释在事务上下文中再次正常工作。

经过一段时间的调试,似乎是在 org.springframework.data.jpa.repository.query.NamedQuerydoCreateCountQuery()hasNamedQuery()

@Override
protected TypedQuery<Long> doCreateCountQuery(Object[] values) {

    EntityManager em = getEntityManager();
    TypedQuery<Long> countQuery = null;

    if (hasNamedQuery(em, countQueryName)) {
        countQuery = em.createNamedQuery(countQueryName, Long.class);
    } else {
        Query query = createQuery(values);
        String queryString = extractor.extractQueryString(query);
        countQuery = em.createQuery(QueryUtils.createCountQueryFor(queryString, countProjection), Long.class);
    }

    return createBinder(values).bind(countQuery);
}   
private static boolean hasNamedQuery(EntityManager em, String queryName) {

    try {
        em.createNamedQuery(queryName);
        return true;
    } catch (IllegalArgumentException e) {
        LOG.debug("Did not find named query {}", queryName);
        return false;
    }
}   

它尝试从生成的名称Customer.findByNamePattern.count 创建一个TypedQuery,该名称在 EntityManager 的命名查询存储库中不存在。 hasNamedQuery() 检查它,捕获抛出的 IllegalArgumentException, 并以另一种方式创建它。问题是虽然IllegalArgumentException被捕获,但事务被回滚(有时!)

我找到了以下解决方法:

  1. 在存储库方法上使用org.springframework.data.jpa.repository.Query注解

  2. 或者-创建另一个命名查询

    @NamedQuery(
        name = "Customer.findByNamePattern.count",
        query = "select count(c.id) from Customer c where c.name like :pattern"
    ),
    

这对我来说不是很清楚:

  • 调用findAll() 应该会导致同样的问题,但不会。为什么?
  • 使用org.springframework.data.jpa.repository.Query 而不是@NamedQuery 也不会导致问题,为什么?
  • 如何在事务上下文中使用带有可分页选项的 @NamedQuery 来避免该问题(而不是显式创建计数查询)?

任何帮助将不胜感激!

更新

使用的版本是: 春天:4.0.5.RELEASE 弹簧数据:1.6.0.RELEASE,1.7.0.RELEASE 休眠:4.3.5.Final

在 [https://jira.spring.io/browse/DATAJPA-442] 阅读了类似的错误后,我将休眠版本降级为 4.2.15.Final,从而解决了该问题。 但是一个问题仍然存在,是否可以在不更改 Hibernate 版本的情况下解决问题?

【问题讨论】:

    标签: spring jpa spring-data spring-data-jpa spring-transactions


    【解决方案1】:

    我添加了一个可能修复的 PR:https://github.com/spring-projects/spring-data-jpa/pull/110 我们使用一个新的(一次性的)EntityManager 来执行命名查询查找,这样原始的 EntityManager 就不会受到查找失败的影响。 事实证明,您的问题很难重现。你介意试一试吗? 也许您甚至可以为此提供一个小测试用例?

    【讨论】:

    • Thomas,谢谢你的帮助,我已经在github.com/sztgeza/springdata-pageable-query上传了一个小演示
    • 非常感谢您的测试用例。我们对 DATAJPA-617 的更改似乎解决了您的问题。我使用 Spring Boot 和带有 FIX 的 JPA 版本创建了一个小示例:github.com/thomasdarimont/spring-data-bugs/tree/master/…
    • Thomas,非常感谢您的修复,现在看来它可以工作了。请问,我们可以在哪个正式版本(以及何时)使用该修复程序?德克萨斯州。又来了!
    【解决方案2】:

    您遇到的问题是由多个工件驱动的:

    根据定义,JPA EntityManager 在引发异常后必须关闭(并可能重新创建)。这通常在实体操作失败并且您可以确定EntityManagers 状态时使用。对于简单的命名查询查找,这是非常严格的,因为它肯定不需要新的EntityManagercreated。但是,我们需要处理这个问题。

    也就是说,我们已经针对手动定义的查询解决了这个问题(这就是为什么您看到它适用于@Query)。但是,我们为DATAJPA-350 引入的防御机制并未应用于命名查询部分。我已经为你创建了DATAJPA-617

    【讨论】:

    • 感谢您的回答!
    猜你喜欢
    • 2018-08-07
    • 1970-01-01
    • 1970-01-01
    • 2020-07-05
    • 1970-01-01
    • 1970-01-01
    • 2013-05-08
    • 1970-01-01
    • 2013-09-15
    相关资源
    最近更新 更多