Spring data JPA: transaction deferred requests

I looked at the issue when trying to run @NamedQuery with paging from the Spring data repository. The entity class looks like this:

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

      

Repository interface:

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);
}

      

When I try to call the methods of the checked out repository from a non-transactional context (junit), it works fine.

When I call it from a transactional service method like:

@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);       
    }

      

... the call findAllPaged()

works fine again.

However, when I try to call a method that is supposed to use a named query, I always get an exception:

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

      

Using annotation org.springframework.data.jpa.repository.Query

in a repository method works fine again from a transactional context.

After a while of debugging, it seems that the problem is caused org.springframework.data.jpa.repository.query.NamedQuery

in doCreateCountQuery()

and 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;
    }
}   

      

It is trying to create TypedQuery

from a generated name Customer.findByNamePattern.count

that does not exist in the repository with the request name EntityManager. hasNamedQuery()

checks it, catches it thrown IllegalArgumentException

, and creates it differently. The problem is that, although captured IllegalArgumentException

, the transaction is rolled back (sometimes!)

I found the following workarounds:

  • using annotation org.springframework.data.jpa.repository.Query

    for repository method

  • OR - create another named query

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

What is not clear to me:

  • the call findAll()

    should cause the same problem, but it doesn't. What for?
  • using org.springframework.data.jpa.repository.Query

    instead @NamedQuery

    also doesn't cause a problem, why?
  • How can I use @NamedQuery with a paged parameter from the transaction context to avoid the problem (and not explicitly create a count query)?

Any help would be appreciated!

UPDATE

versions used: Spring: 4.0.5.RELEASE spring-data: 1.6.0.RELEASE, 1.7.0.RELEASE Hibernate: 4.3.5. The final

After reading a similar bug in [ https://jira.spring.io/browse/DATAJPA-442] , I downgraded the hibernate version to 4.2.15.Final which fixed the problem. However, the question is still alive, is it possible to solve the problem without changing the Hibernate version?

+3


source to share


2 answers


I added a PR with a potential fix: https://github.com/spring-projects/spring-data-jpa/pull/110 We are using a new (throwaway) EntityManager to execute a named query request so the original EntityManager is not affected by unsuccessful search. As it turns out, your problem is quite difficult to reproduce. Could you give it a spin? Perhaps you could even provide a small test case for this?



+1


source


The problem you are facing is driven by several artifacts:

By definition, JPA EntityManager

should be closed (and potentially re-created) after it throws an exception. This usually happens when the object's operations fail, and you can be sure of the status EntityManager

. For a simple named query search, this is pretty strict as it doesn't need to create a new one EntityManager

. However, we need to deal with this.



However, we are already working on this issue for manual queries (which is why you see it working for @Query

). However, the protection mechanism we introduced for DATAJPA-350 is not applied to the named part of the request. I have created DATAJPA-617 for you.

+1


source







All Articles