3

Я рассмотрел проблему при попытке запустить @NamedQuery с пейджингом из репозитория Spring Data. класса Лица выглядит следующим образом:Spring Data JPA: транзакция отложенных запросов на страницы данных

@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.NamedQuery, в doCreateCountQuery() и 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; 
    } 
} 

Он пытается создать TypedQuery из сгенерированного имени Customer.findByNamePattern.count, который не существует в репозиторий запросов 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 с возможностью страницы, из контекста транзакции, чтобы избежать проблемы (и не создавать явно запрос на счет)?

Любая помощь будет оценена!

UPDATE

используемые варианты были: Spring: 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?

ответ

1

Я добавил PR с потенциальным исправлением: https://github.com/spring-projects/spring-data-jpa/pull/110 Мы используем новое (одноразовый) EntityManager для выполнения запроса с именем поиска так, что исходный EntityManager не будет зависеть от неудачного поиска. Как оказалось, ваша проблема довольно сложно воспроизвести. Не могли бы вы дать ему вращение? Возможно, вы могли бы даже предоставить небольшой тестовый пример?

+0

Thomas, спасибо за вашу помощь, я загрузил небольшую демонстрационную версию на https://github.com/sztgeza/springdata-pageable-query – demura

+0

Большое спасибо за тестовый файл. Наши изменения для DATAJPA-617, похоже, устраняют вашу проблему. Я создал небольшой пример с Spring Boot и JPA с FIX: https://github.com/thomasdarimont/spring-data-bugs/tree/master/DATAJPA-617 –

+0

Томас, большое спасибо за исправление, похоже что он работает сейчас. Могу ли я спросить, в каком официальном выпуске (и когда) мы можем использовать исправление? Tx. еще раз! – demura

1

Проблема запуска в управляются несколько артефактов:

По определению JPA EntityManager должен быть закрыт (и, возможно, воссозданным) после того, как он бросает исключение. Обычно это происходит в случае сбоя операций объекта, и вы можете быть уверены в состоянии EntityManager. Для простого поиска по именованным запросам это довольно строго, поскольку для него совершенно не нужен новый EntityManager. Однако нам нужно иметь дело с этим.

Тем не менее, мы уже работаем над этой проблемой для заданных вручную запросов (поэтому вы видите, что она работает для @Query). Однако защитный механизм, введенный нами для DATAJPA-350, мы не применяем к указанной части запроса. Я создал DATAJPA-617 для вас.

+0

Спасибо за ваш ответ! – demura

Смежные вопросы