webentwicklung-frage-antwort-db.com.de

Spring Boot + Hibernate + JPA Kein Transaktions-EntityManager verfügbar

Ich verwende Spring Boot 1.2.3.RELEASE-Version mit JPA über dem Ruhezustand. Ich erlebe folgende Ausnahme

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]

Nachfolgend ist meine Programmstruktur 
Konfigurationsklasse

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

}

Kann sich jemand in dem oben genannten Service-Klassencode anführen, warum 2 funktioniert und 1 eine Ausnahme auslöst.

Vielen Dank

12
amique

Zunächst zitiere ich die Spring-Data JPA Documentation , um zu begründen, warum die delete-Methode in Ihrem Fall funktioniert (ich meine die Option 2).

CRUD-Methoden für Repository-Instanzen sind standardmäßig transaktional. Zum Bei Leseoperationen wird das Flag readOnly der Transaktionskonfiguration gesetzt wahr, alle anderen sind mit einem einfachen @Transactional konfiguriert, so dass Die Standardtransaktionskonfiguration gilt. Für Details siehe JavaDoc von CrudRepository

Die delete-Methode ist eigentlich eine Methode der CrudRepository. Ihr Repository erweitert JpaRepository, wodurch CrudRespository erweitert wird. Daher gehört es zur CrudRepository-Schnittstelle und ist gemäß dem oben genannten Zitat transaktional.

Wenn Sie den Abschnitt Transaktionsabfragemethode lesen, werden Sie feststellen, dass die Option 4 derselbe ist und Sie wissen, wie Sie ein benutzerdefiniertes Transaktionsverhalten für alle Methoden Ihres Repositorys anwenden. Auch das Example 61 der Dokumentation zeigt das gleiche Szenario wie die Option 3.

Denken Sie jetzt daran, dass Sie nicht mit JDBC-Logik arbeiten. In diesem Fall kümmert sich die Datenbank um die Transaktionen, jedoch innerhalb eines ORM-basierten Rahmens. Für ORM-Frameworks ist eine Transaktion erforderlich, um die Synchronisation zwischen dem Objektcache und der Datenbank auszulösen. Sie müssen also einen Transaktionskontext für Methoden bereitstellen, die ORM-Logik ausführen, wie deleteByCustomerId.

Standardmäßig setzen @Transactional (ich meine ohne Parameter) den Ausbreitungsmodus auf REQUIREDund das Flag readOnly auf false. Wenn Sie eine mit Anmerkungen versehene Methode aufrufen, wird eine Transaktion initialisiert, wenn keine vorhanden ist. Dies ist der Grund, warum der Workaround von @LucasSaldanha (wie im Beispiel Verwenden einer Fassade zum Definieren von Transaktionen für mehrere Repository-Aufrufe) und die Option 4 funktioniert. Andernfalls fallen Sie ohne Transaktion in die geworfene Ausnahme der Option 1.

13
Guillermo

Ok, ich habe herausgefunden, wie es funktioniert.

Geben Sie einfach eine @Transactional-Annotation (org.springframework.transaction.annotation.Transactional) in Ihre deleteOrder -Methode unter OrderService ein.

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

Ich weiß wirklich nicht, warum der zweite funktioniert. Ich schätze, da es sich um eine direkte Methode der CrudRepository - Schnittstelle handelt, weiß man, wie es atomar ausgeführt wird.

Die vorherige ist ein Aufruf der deleteByCustomerId. Dieser Anruf wird verarbeitet, um den Kunden mit der angegebenen ID herauszufinden und diese zu löschen. Aus irgendeinem Grund verwendet es eine explizite Transaktion.

Wieder ist es nur eine Vermutung. Ich werde versuchen, einige Frühlingsentwickler zu kontaktieren und möglicherweise ein Problem zu eröffnen, um dieses Verhalten zu überprüfen.

Ich hoffe es hilft!

Referenz: http://spring.io/guides/gs/managing-transactions/

2
Lucas Saldanha

Ich erhielt immer noch die No transactional EntityManager available-Ausnahme, nachdem ich meine search()-Methode mit @Transactional versehen hatte.

Ich folgte diesem Tutorial , das beschreibt, wie die Hibernate-Suche in Spring Boot eingerichtet wird.

Das Problem für mich war, dass ich eine andere Abhängigkeit von hibernate-search-orm hatte. Die Abhängigkeit, die für mich ohne Probleme funktionierte, war

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

Nachdem dies der Gradle-Build-Datei hinzugefügt wurde, funktionierte alles wie erwartet. 

Hoffe, das hilft auch jemand anderem.

1
Patrick