web-dev-qa-db-fra.com

Spring Boot + Hibernate + JPA Aucun EntityManager transactionnel disponible

J'utilise la version de démarrage à ressort 1.2.3.LELEASE avec JPA sur veille prolongée. Je vis l'exception suivante

org.springframework.dao.InvalidDataAccessApiUsageException: No transactional EntityManager available; nested exception is javax.persistence.TransactionRequiredException: No transactional EntityManager available
at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.Java:410) ~[EntityManagerFactoryUtils.class:4.1.6.RELEASE]
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.Java:223) ~[HibernateJpaDialect.class:4.1.6.RELEASE]
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.Java:417) ~[AbstractEntityManagerFactoryBean.class:4.1.6.RELEASE]
at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.Java:59) ~[ChainedPersistenceExceptionTranslator.class:4.1.6.RELEASE]
at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.Java:213) ~[DataAccessUtils.class:4.1.6.RELEASE]
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.Java:147) ~[PersistenceExceptionTranslationInterceptor.class:4.1.6.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.Java:179) [ReflectiveMethodInvocation.class:4.1.6.RELEASE]
at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodIntercceptor.invoke(CrudMethodMetadataPostProcessor.Java:122) ~[CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodIntercceptor.class:na]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.Java:179) [ReflectiveMethodInvocation.class:4.1.6.RELEASE]
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.Java:92) [ExposeInvocationInterceptor.class:4.1.6.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.Java:179) [ReflectiveMethodInvocation.class:4.1.6.RELEASE]
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.Java:207) [JdkDynamicAopProxy.class:4.1.6.RELEASE]
at com.Sun.proxy.$Proxy110.deleteByCustomerId(Unknown Source) ~[na:na]

Caused by: javax.persistence.TransactionRequiredException: No transactional EntityManager available
at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.Java:275) ~[SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.class:4.1.6.RELEASE]
at com.Sun.proxy.$Proxy102.remove(Unknown Source) ~[na:na]
at org.springframework.data.jpa.repository.query.JpaQueryExecution$DeleteExecution.doExecute(JpaQueryExecution.Java:270) ~[JpaQueryExecution$DeleteExecution.class:na]
at org.springframework.data.jpa.repository.query.JpaQueryExecution.execute(JpaQueryExecution.Java:74) ~[JpaQueryExecution.class:na]
at org.springframework.data.jpa.repository.query.AbstractJpaQuery.doExecute(AbstractJpaQuery.Java:97) ~[AbstractJpaQuery.class:na]
at org.springframework.data.jpa.repository.query.AbstractJpaQuery.execute(AbstractJpaQuery.Java:88) ~[AbstractJpaQuery.class:na]
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.Java:395) ~[RepositoryFactorySupport$QueryExecutorMethodInterceptor.class:na]
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.Java:373) ~[RepositoryFactorySupport$QueryExecutorMethodInterceptor.class:na]

Voici la structure de mon programme 
Classe de configuration

@Configuration
@ComponentScan
@EnableAutoConfiguration
@EnableTransactionManagement
public class WSApplication {
    public static void main(final String[] args) {
        SpringApplication.run(WSApplication.class, args);
    }
}

@Entity
@Table(Orders)
public class Order {
    @id
    @GeneratedValue
    private long id;

    @Column(name = "customerId")
    private Long customerId;

    // getter & setter methods
    // equals & hashCode methods
}

public interface OrderRepository extends JpaRepository<Order, Long> {

    List<Order> findByCustomerId(Long customerId);

    // 4- @Transactional works fine
    void deleteByCustomerId(Long cusotmerId);

}

public class OrderService {

    @Autowired
    private OrderRepository repo;

    // 3- @Transactional works fine
    public void deleteOrder(long customerId){
        //1- throws exception
        repo.deleteByCustomerId(customerId); 

        //2- following works fine
        //repo.delete(repo.findByCustomerId(customerId).get(0));
    }

}

Dans le code de la classe de service ci-dessus, quelqu'un peut-il me guider, s'il vous plaît, pourquoi 2 œuvres et 1 exception au lancer.

Merci

12
amique

Tout d'abord, je cite la Spring-Data JPA Documentation pour expliquer pourquoi la méthode delete fonctionne dans votre cas (je parle de l'option 2).

Les méthodes CRUD sur les instances de référentiel sont transactionnelles par défaut. Pour opérations de lecture, l'indicateur de configuration de transaction readOnly est défini sur true, tous les autres sont configurés avec un @Transactional simple afin que La configuration de transaction par défaut s'applique. Pour plus de détails, voir JavaDoc de CrudRepository

La méthode delete est en réalité une méthode de CrudRepository. Votre référentiel s'étend JpaRepository qui étend CrudRespository, il appartient donc à l'interface CrudRepository et, selon la citation ci-dessus, est transactionnel.

Si vous lisez la section Méthode de requête transactionnelle , vous constaterez qu’elle est identique à l’option 4 et que vous saurez comment appliquer un comportement transactionnel personnalisé à toutes les méthodes de votre référentiel . le Exemple 61 de la documentation montre le même scénario que l'option 3.

N'oubliez pas que vous ne travaillez pas avec la logique JDBC. Dans ce cas, la base de données prend en charge les transactions, mais dans un cadre basé sur ORM. Les infrastructures ORM nécessitent une transaction afin de déclencher la synchronisation entre le cache d'objets et la base de données . Vous devez donc être conscient et fournir un contexte de transaction pour les méthodes utilisant une logique ORM telle que deleteByCustomerId.

Par défaut, @Transactional (je veux dire sans paramètre), définissez le mode de propagation sur REQUIREDet readOnly flag sur false. Lorsque vous appelez une méthode annotée à l'intérieur, une transaction est initialisée s'il n'en existe aucune. C’est la raison pour laquelle la solution de contournement de @LucasSaldanha (identique à l’exemple Utilisation d’une façade pour définir des transactions pour plusieurs appels au référentiel}) et l’option 4. Autrement, sans transaction, vous tombez dans l’exception levée de l’option 1.

13
Guillermo

Ok j'ai trouvé un moyen de le faire fonctionner.

Il suffit de mettre une annotation @Transactional (org.springframework.transaction.annotation.Transactional) dans votre méthode deleteOrder à OrderService.

@Transactional
public void deleteOrder(long customerId){
    repo.deleteByCustomerId(customerId);
}

Je ne sais vraiment pas pourquoi le second fonctionne. Je suppose que puisque c’est une méthode directe de l’interface CrudRepository, il sait comment l’exécuter de manière atomique.

Le premier est un appel à deleteByCustomerId. Cet appel sera traité pour trouver le client avec l'identifiant spécifié, puis le supprimer. Pour une raison quelconque, l'utilisation d'une transaction explicite est utilisée.

Encore une fois, ce n'est qu'une supposition. Je vais essayer de contacter des développeurs Spring et peut-être ouvrir un problème pour vérifier ce comportement.

J'espère que ça aide!

Référence: http://spring.io/guides/gs/managing-transactions/

2
Lucas Saldanha

J'ai toujours l'exception No transactional EntityManager available même après avoir annoté ma méthode search() avec @Transactional.

J'ai suivi ce tutoriel qui décrit comment configurer la recherche Hibernate dans Spring Boot.

Le problème pour moi était que j'avais une dépendance différente sur hibernate-search-orm. La dépendance qui a fonctionné pour moi sans aucun problème était

compile("org.hibernate:hibernate-search-orm:5.7.0.Final")

Après avoir ajouté ceci au fichier de construction de Gradle, tout a fonctionné comme prévu. 

J'espère que cela aide aussi quelqu'un d'autre.

1
Patrick