Spring Data JPA:可分页查询导致事务回滚

3

当我尝试从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. using org.springframework.data.jpa.repository.Query annotation on the repository method

  2. OR- creating another named query

    @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 也不会导致这个问题,为什么? - 我如何在事务上下文中使用带有 pageable 选项的 @NamedQuery,以避免这个问题(而且不需要显式创建计数查询)?
任何帮助都将不胜感激!
更新
所使用的版本为: Spring:4.0.5.RELEASE spring-data:1.6.0.RELEASE、1.7.0.RELEASE Hibernate:4.3.5.Final
阅读了类似错误的报告后[https://jira.spring.io/browse/DATAJPA-442],我将 Hibernate 版本降级至 4.2.15.Final,问题得到了解决。 然而,一个问题仍然存在,是否可以在不更改 Hibernate 版本的情况下解决这个问题?
2个回答

1
您遇到的问题是由多个因素驱动的:
根据定义,JPA EntityManager 在抛出异常后必须关闭(并可能重新创建)。这通常是为了在实体操作失败时确保 EntityManager 的状态。对于简单的命名查询查找,这是相当严格的,因为它肯定不需要创建新的 EntityManager 。但是,我们需要处理这个问题。
也就是说,对于手动定义的查询,我们已经解决了这个问题(这就是为什么您可以在@Query中看到它工作)。然而,我们引入的防御机制DATAJPA-350没有应用于命名查询部分。我已经为您创建了DATAJPA-617

谢谢你的回答! - demura

1

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


Thomas,感谢您的帮助,我已经将一个小的演示上传到了https://github.com/sztgeza/springdata-pageable-query。 - demura
非常感谢你提供的测试用例。我们针对DATAJPA-617所做的更改似乎解决了你的问题。我创建了一个使用Spring Boot和JPA版本FIX的小示例:https://github.com/thomasdarimont/spring-data-bugs/tree/master/DATAJPA-617 - Thomas Darimont
Thomas,非常感谢你的修复,看起来现在它可以工作了。我可以问一下,在哪个正式版本(以及何时)我们可以使用这个修复吗?再次感谢! - demura

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接