Spring - Aucun EntityManager avec une transaction réelle disponible pour le thread actuel - ne peut pas traiter de manière fiable l'appel 'persist'

137

J'obtiens cette erreur en essayant d'appeler la méthode «persist» pour enregistrer le modèle d'entité dans la base de données de mon application Web Spring MVC. Je ne trouve pas vraiment de publication ou de page sur Internet pouvant être liée à cette erreur particulière. Il semble que quelque chose ne va pas avec le bean EntityManagerFactory, mais je suis assez nouveau dans la programmation Spring, donc pour moi, il semble que tout est bien initialisé et selon divers articles de tutoriel sur le Web.

dispatcher-servlet.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
 xmlns:context="http://www.springframework.org/schema/context"
 xmlns:jpa="http://www.springframework.org/schema/data/jpa"
xsi:schemaLocation="
 http://www.springframework.org/schema/mvc 
 http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
 http://www.springframework.org/schema/beans 
 http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
 http://www.springframework.org/schema/context 
  http://www.springframework.org/schema/context/spring-context-4.0.xsd
  http://www.springframework.org/schema/jdbc
  http://www.springframework.org/schema/jdbc/spring-jdbc-3.2.xsd
  http://www.springframework.org/schema/data/jpa
  http://www.springframework.org/schema/data/jpa/spring-jpa-1.3.xsd
  http://www.springframework.org/schema/data/repository
  http://www.springframework.org/schema/data/repository/spring-repository-1.5.xsd
  http://www.springframework.org/schema/jee
  http://www.springframework.org/schema/jee/spring-jee-3.2.xsd">

    <context:component-scan base-package="wymysl.Controllers" />
    <jpa:repositories base-package="wymysl.repositories"/> 
    <context:component-scan base-package="wymysl.beans" /> 
    <context:component-scan base-package="wymysl.Validators" /> 
    <bean
     class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />
     <bean class="org.springframework.orm.hibernate4.HibernateExceptionTranslator"/>

     <bean id="passwordValidator" class="wymysl.Validators.PasswordValidator"></bean>

     <bean id="dataSource"
        class="org.springframework.jdbc.datasource.DriverManagerDataSource">

        <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver" />
        <property name="url" value="jdbc:oracle:thin:@localhost:1521:xe" />
        <property name="username" value="system" />
        <property name="password" value="polskabieda1" />
    </bean>

 <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="persistenceXmlLocation" value="classpath:./META-INF/persistence.xml" />
    <property name="dataSource" ref="dataSource" />

    <property name="jpaVendorAdapter">
        <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
            <property name="databasePlatform" value="org.hibernate.dialect.H2Dialect" />
            <property name="showSql" value="true" />
            <property name="generateDdl" value="false" />
        </bean>
    </property>
    <property name="jpaProperties">
        <props>
            <prop key="hibernate.max_fetch_depth">3</prop>
            <prop key="hibernate.jdbc.fetch_size">50</prop>
            <prop key="hibernate.jdbc.batch_size">10</prop>
        </props>
    </property>
</bean>

    <mvc:annotation-driven />

    <bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
    <property name="basename" value="classpath:messages" />
</bean>

    <bean name="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
             <property name="entityManagerFactory" ref="entityManagerFactory"/>
    </bean>


    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix">
        <value>/WEB-INF/jsp/</value>
    </property>
    <property name="suffix">
        <value>.jsp</value>
    </property>
</bean>

    <mvc:resources mapping="/resources/**" location="/resources/" />
    <mvc:resources mapping="/resources/*" location="/resources/css/"  
    cache-period="31556926"/>



</beans>

RegisterController.java

@Controller
public class RegisterController {

    @PersistenceContext
    EntityManager entityManager;

    @Autowired
    PasswordValidator passwordValidator;

    @InitBinder
    private void initBinder(WebDataBinder binder) {
        binder.setValidator(passwordValidator);
    }

    @RequestMapping(value = "/addUser", method = RequestMethod.GET)
    public String register(Person person) {


        return "register";

    }

    @RequestMapping(value = "/addUser", method = RequestMethod.POST)
    public String register(@ModelAttribute("person") @Valid @Validated Person person, BindingResult result) {
        if(result.hasErrors()) {
            return "register";
        } else {
            entityManager.persist(person);
            return "index";

        }




    }
Michał Bil
la source
1
Comme le dit l'erreur, il n'y a pas de transaction. Annotez la méthode de registre avec @Transaction.
Rohit

Réponses:

262

J'ai eu le même problème et j'ai annoté la méthode au fur @Transactionalet à mesure et cela a fonctionné.

UPDATE: en vérifiant la documentation du printemps, il ressemble par défaut au PersistenceContext est de type Transaction, c'est pourquoi la méthode doit être transactionnelle ( http://docs.spring.io/spring/docs/current/spring-framework-reference/ html / orm.html ):

L'annotation @PersistenceContext a un type d'attribut facultatif, qui par défaut est PersistenceContextType.TRANSACTION. Cette valeur par défaut est ce dont vous avez besoin pour recevoir un proxy EntityManager partagé. L'alternative, PersistenceContextType.EXTENDED, est une affaire complètement différente: cela se traduit par un EntityManager étendu, qui n'est pas thread-safe et ne doit donc pas être utilisé dans un composant à accès simultané tel qu'un bean singleton géré par Spring. Les EntityManagers étendus ne sont censés être utilisés que dans les composants avec état qui, par exemple, résident dans une session, le cycle de vie de l'EntityManager n'étant pas lié à une transaction en cours, mais étant complètement à la hauteur de l'application.

mlg
la source
71
Si une méthode sans l' @Transactionalannotion appelle une méthode avec l' @Transactionalannotation dans le même fichier de classe, vous serez également frappé par cette erreur (c'est ce à quoi je faisais face).
Jacob van Lingen
5
J'ai annoté la classe de service avec @Transactional, cela fonctionne également. Je ne sais pas si c'est la bonne voie à suivre, mais il semble que cela fonctionne bien ...
milosmns
8
Une autre chose à mentionner est que cette annotation doit être utilisée pour la méthode publique car elle ne fonctionne pas autrement.
Yuriy Kravets
7
N'oubliez pas, s'il vous plaît, que @Transactional ne fonctionne que sur les méthodes publiques.
Andrei_N
7
Pour info, cela nécessite l'annotation javax.transaction.Transactional (pas celle de Spring).
java-addict301
85

J'ai eu cette exception en essayant d'utiliser une méthode personnalisée deleteBy dans le référentiel de données de printemps. L'opération a été tentée à partir d'une classe de test JUnit.

L'exception ne se produit pas lors de l'utilisation de l' @Transactionalannotation au niveau de la classe JUnit.

Kishore Guruswamy
la source
8
J'étais dans la même situation, au lieu d'annoter la classe de test, j'ai annoté la méthode de service pour qu'elle coule même si ce n'était pas un test.
Paul Nelson Baker
@Transactionalau niveau de la classe peut masquer d'éventuels problèmes de test qui manipulent différentes transactions dans vos services.
Zon
1
J'ai utilisé @Trasactionalla méthode du référentiel car c'est l'endroit où j'interagis réellement avec la base de données et qu'elle fonctionne bien.
Harish Kumar Saini
22

Cette erreur m'a foxed pendant trois jours, la situation à laquelle j'ai été confrontée a produit la même erreur. Suite à tous les conseils que j'ai pu trouver, j'ai joué avec la configuration mais en vain.

Finalement, je l'ai trouvé, la différence, le service que j'exécutais était contenu dans un fichier jar commun, le problème s'est avéré être qu'AspectJ ne traitait pas l'instanciation du service de la même manière. En effet, le proxy appelait simplement la méthode sous-jacente sans que toute la magie Spring normale ne soit exécutée avant l'appel de la méthode.

En fin de compte, l'annotation @Scope placée sur le service selon l'exemple a résolu le problème:

@Service
@Scope(proxyMode = ScopedProxyMode.INTERFACES)
@Transactional
public class CoreServiceImpl implements CoreService {
    @PersistenceContext
    protected EntityManager entityManager;

    @Override
    public final <T extends AbstractEntity> int deleteAll(Class<T> clazz) {
        CriteriaDelete<T> criteriaDelete = entityManager.getCriteriaBuilder().createCriteriaDelete(clazz);
        criteriaDelete.from(clazz);
        return entityManager.createQuery(criteriaDelete).executeUpdate();
    }

}

La méthode que j'ai publiée est une méthode de suppression mais les annotations affectent toutes les méthodes de persistance de la même manière.

J'espère que cet article aidera quelqu'un d'autre qui a rencontré le même problème lors du chargement d'un service à partir d'un pot

Chris March
la source
L'ajout @Scope(proxyMode = ScopedProxyMode.INTERFACES)à la classe DAO qui implémente une interface est vraiment important. J'ai passé toute la journée à découvrir cette erreur et votre solution est la seule qui fonctionne. Merci beaucoup!
Thach Van
1
Une annotation de votre réponse vient de me sauver probablement environ 3 jours. Je viens de découvrir le vrai pouvoir de l'œdénème. Дякс.
Oleksii Kyslytsyn
8

boardRepo.deleteByBoardId (id);

Face au même problème. GOT javax.persistence.TransactionRequiredException: Aucun EntityManager avec transaction réelle disponible pour le thread actuel

Je l'ai résolu en ajoutant l' annotation @Transactional au-dessus du contrôleur / service.

Vikram S
la source
7

J'ai eu le même problème et j'ai ajouté tx:annotation-drivendans applicationContext.xmlet cela a fonctionné.

Pierre Feng
la source
4

J'ai eu la même erreur lors de l'accès à une méthode déjà annotée transactionnelle à partir d'une méthode non transactionnelle au sein du même composant:

Before:
    @Component
    public class MarketObserver {
        @PersistenceContext(unitName = "maindb")
        private EntityManager em;

        @Transactional(value = "txMain", propagation = Propagation.REQUIRES_NEW)
        public void executeQuery() {
          em.persist(....);
        }


        @Async
        public void startObserving() {
          executeQuery(); //<-- Wrong
        }
    }

    //In another bean:
     marketObserver.startObserving();

J'ai corrigé l'erreur en appelant executeQuery () sur le composant auto-référencé:

Fixed version:
    @Component
    public class MarketObserver {
        @PersistenceContext(unitName = "maindb")
        private EntityManager em;

        @Autowired
        private GenericApplicationContext context;

        @Transactional(value = "txMain", propagation = Propagation.REQUIRES_NEW)
        public void executeQuery() {
          em.persist(....);
        }


        @Async
        public void startObserving() {
          context.getBean(MarketObserver.class).executeQuery(); //<-- Works
        }
    }
YDZOGODOQ
la source
3

L'ajout de l' org.springframework.transaction.annotation.Transactionalannotation au niveau de la classe pour la classe de test a résolu le problème pour moi.

Leo
la source
3

Juste une note pour les autres utilisateurs qui recherchent des réponses à cette erreur. Un autre problème courant est:

Vous ne pouvez généralement pas appeler une @transactionalméthode à partir de la même classe.

(Il existe des moyens d'utiliser AspectJ mais la refactorisation sera beaucoup plus facile)

Vous aurez donc besoin d'une classe d'appel et d'une classe contenant les @transactionalméthodes.

sparkyspider
la source
2

Pour nous, le problème se résumait aux mêmes paramètres de contexte dans plusieurs fichiers de configuration. Vérifiez que vous n'avez pas dupliqué les éléments suivants dans plusieurs fichiers de configuration.

<context:property-placeholder location="classpath*:/module.properties"/>
<context:component-scan base-package="...." />
Nick West
la source
2

J'ai eu le même code d'erreur lorsque j'ai utilisé @Transactionune méthode / un niveau d'action incorrect.

methodWithANumberOfDatabaseActions() { 
   methodA( ...)
   methodA( ...)
}

@Transactional
void methodA( ...) {
  ... ERROR message
}

J'ai dû placer le @Transactionaljuste au-dessus de la méthode methodWithANumberOfDatabaseActions(), bien sûr.

Cela a résolu le message d'erreur dans mon cas.

un autre nœud
la source
0

J'ai supprimé le mode de

<tx:annotation-driven mode="aspectj"
transaction-manager="transactionManager" />

faire ce travail

ropo
la source
0

J'ai eu ce problème pendant des jours et rien de ce que j'ai trouvé nulle part en ligne ne m'a aidé, je publie ma réponse ici au cas où cela aiderait quelqu'un d'autre.

Dans mon cas, je travaillais sur un microservice appelé via la communication à distance, et mon annotation @Transactional au niveau du service n'était pas captée par le proxy distant.

L'ajout d'une classe de délégué entre les couches service et dao et le marquage de la méthode de délégué comme transactionnel a résolu ce problème.

fleeblewidget
la source
0

Cela nous a aidés, peut-être que cela peut aider les autres à l'avenir. @Transactionne fonctionnait pas pour nous, mais cela a fonctionné:

@ConditionalOnMissingClass("org.springframework.orm.jpa.JpaTransactionManager")

JLane
la source
0

Si tu as

@Transactional // Spring Transactional
class MyDao extends Dao {
}

et super classe

class Dao {
    public void save(Entity entity) { getEntityManager().merge(entity); }
}

et vous appelez

@Autowired MyDao myDao;
myDao.save(entity);

vous n'obtiendrez pas un Spring TransactionInterceptor (qui vous donne une transaction).

Voici ce que vous devez faire:

@Transactional 
class MyDao extends Dao {
    public void save(Entity entity) { super.save(entity); }
}

Incroyable mais vrai.

user2006754
la source