Spring: no hay EntityManager con una transacción real disponible para el subproceso actual: no se puede procesar de manera confiable la llamada 'persistente'

137

Recibo este error cuando intento invocar el método "persistente" para guardar el modelo de entidad en la base de datos en mi aplicación web Spring MVC. Realmente no puedo encontrar ninguna publicación o página en Internet que pueda relacionarse con este error en particular. Parece que algo está mal con el bean EntityManagerFactory, pero soy bastante nuevo en la programación de Spring, así que para mí parece que todo está bien inicializado y de acuerdo con varios artículos de tutoriales en la 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
fuente
1
Como dice el error, no hay transacción. Anote el método de registro con @Transaction.
Rohit

Respuestas:

260

Tuve el mismo problema y anoté el método como @Transactionaly funcionó.

ACTUALIZACIÓN: comprobando la documentación de primavera, parece que, de forma predeterminada, el PersistenceContext es de tipo Transacción, por lo que el método debe ser transaccional ( http://docs.spring.io/spring/docs/current/spring-framework-reference/ html / orm.html ):

La anotación @PersistenceContext tiene un tipo de atributo opcional, que por defecto es PersistenceContextType.TRANSACTION. Este valor predeterminado es lo que necesita para recibir un proxy EntityManager compartido. La alternativa, PersistenceContextType.EXTENDED, es un asunto completamente diferente: esto da como resultado un EntityManager extendido, que no es seguro para subprocesos y, por lo tanto, no debe usarse en un componente al que se accede simultáneamente, como un bean singleton administrado por Spring. Se supone que los EntityManagers extendidos solo se usan en componentes con estado que, por ejemplo, residen en una sesión, con el ciclo de vida del EntityManager no vinculado a una transacción actual, sino que depende completamente de la aplicación.

mlg
fuente
71
Si un método sin la @Transactionalanotación llama a un método con la @Transactionalanotación en el mismo archivo de clase, también se verá afectado por este error (eso es lo que estaba enfrentando).
Jacob van Lingen
55
Anoté la clase de servicio con @Transactional, eso también funciona. No estoy seguro de si es el camino correcto, pero parece que funciona bien ...
milosmns 05 de
8
Otra cosa que vale la pena mencionar es que esta anotación debe usarse para el método público, ya que no funciona de otra manera.
Yuriy Kravets
77
Recuerde, por favor, que @Transactional funciona solo con métodos públicos.
Andrei_N
77
Para su información, esto necesita la anotación javax.transaction.Transactional (no la Spring).
java-addict301
85

Obtuve esta excepción al intentar usar un método personalizado deleteBy en el repositorio de datos de Spring. La operación se intentó desde una clase de prueba JUnit.

La excepción no ocurre al usar la @Transactionalanotación en el nivel de clase JUnit.

Kishore Guruswamy
fuente
8
Estaba en la misma situación, en lugar de anotar la clase de prueba, anoté el método de servicio para que funcionara incluso si no era una prueba.
Paul Nelson Baker,
@Transactionala nivel de clase puede enmascarar posibles problemas de prueba que manipulan diferentes transacciones en sus servicios.
Zon
1
Utilicé @Trasactionalel método de repositorio ya que es el lugar donde realmente estoy interactuando con la base de datos y funciona bien.
Harish Kumar Saini
22

Este error me hizo perder el tiempo durante tres días, la situación que enfrenté produjo el mismo error. Siguiendo todos los consejos que pude encontrar, jugué con la configuración pero fue en vano.

Finalmente lo encontré, la diferencia, el Servicio que estaba ejecutando estaba contenido en un contenedor común, el problema resultó ser que AspectJ no trataba la instanciación del Servicio de la misma manera. En efecto, el proxy simplemente estaba llamando al método subyacente sin que se ejecutara toda la magia Spring normal antes de la llamada al método.

Al final, la anotación @Scope colocada en el servicio según el ejemplo resolvió el problema:

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

}

El método que he publicado es un método de eliminación, pero las anotaciones afectan a todos los métodos de persistencia de la misma manera.

Espero que esta publicación ayude a alguien que ha tenido problemas con el mismo problema al cargar un servicio desde un jar

Chris March
fuente
Agregar @Scope(proxyMode = ScopedProxyMode.INTERFACES)a la clase DAO que implementa una interfaz es realmente importante. Pasé todo el día para descubrir este error y su solución es la única que funciona. ¡Muchas gracias!
Thach Van
1
Una anotación de tu respuesta me ha salvado probablemente alrededor de 3 días. Acabo de experimentar el verdadero poder de edenema. Дякс.
Oleksii Kyslytsyn
8

boardRepo.deleteByBoardId (id);

Enfrenté el mismo problema. GOT javax.persistence.TransactionRequiredException: no hay EntityManager con transacción real disponible para el hilo actual

Lo resolví agregando la anotación @Transactional sobre el controlador / servicio.

Vikram S
fuente
7

Yo tenía el mismo problema y añadí tx:annotation-drivenen applicationContext.xmly funcionó.

Feng de piedra
fuente
4

Tuve el mismo error al acceder a un método anotado ya transaccionalmente desde un método no transaccional dentro del mismo componente:

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

Solucioné el error llamando a executeQuery () en el componente auto-referenciado:

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
fuente
3

Agregar la org.springframework.transaction.annotation.Transactionalanotación a nivel de clase para la clase de prueba me solucionó el problema.

León
fuente
3

Solo una nota para otros usuarios que buscan respuestas para el error. Otro problema común es:

Por lo general, no puede llamar a un @transactionalmétodo desde la misma clase.

(Hay formas y medios de usar AspectJ pero la refactorización será mucho más fácil)

Por lo tanto, necesitará una clase de llamada y una clase que contenga los @transactionalmétodos.

sparkyspider
fuente
2

Para nosotros, el problema se redujo a la misma configuración de contexto en varios archivos de configuración. Compruebe que no ha duplicado lo siguiente en varios archivos de configuración.

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

Tuve el mismo código de error cuando utilicé @Transactionun método / nivel de acción incorrecto.

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

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

Tuve que colocar el @Transactionaljusto encima del método methodWithANumberOfDatabaseActions(), por supuesto.

Eso resolvió el mensaje de error en mi caso.

otro nodo
fuente
0

Quité el modo de

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

para hacer que esto funcione

ropo
fuente
0

Tuve este problema durante días y nada de lo que encontré en línea me ayudó, estoy publicando mi respuesta aquí en caso de que ayude a alguien más.

En mi caso, estaba trabajando en un microservicio que se llamaba a través de la comunicación remota, y el proxy remoto no estaba recogiendo mi anotación @Transactional a nivel de servicio.

Agregar una clase de delegado entre las capas de servicio y dao y marcar el método de delegado como transaccional me arregló esto.

Fleeblewidget
fuente
0

Esto nos ayudó, tal vez pueda ayudar a otros en el futuro. @Transactionno funcionaba para nosotros, pero esto sí:

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

JLane
fuente
0

Si usted tiene

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

y superclase

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

y tu llamas

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

no obtendrá un Spring TransactionInterceptor (que le da una transacción).

Esto es lo que necesitas hacer:

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

Increíble pero cierto.

usuario2006754
fuente