Spring - Nenhum EntityManager com transação real disponível para o thread atual - não é possível processar de forma confiável a chamada 'persistir'

136

Recebo esse erro ao tentar chamar o método "persistir" para salvar o modelo de entidade no banco de dados no meu aplicativo Web Spring MVC. Não consigo encontrar na Internet qualquer post ou página que possa estar relacionada a esse erro específico. Parece que algo está errado com o bean EntityManagerFactory, mas eu sou bastante novo na programação do Spring, então para mim parece que tudo foi inicializado corretamente e de acordo com vários artigos de tutorial na 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
fonte
1
Como o erro diz, não há transação. Anote o método de registro com @Transaction.
Rohit

Respostas:

260

Eu tive o mesmo problema e anotei o método como @Transactional e ele funcionou.

UPDATE: verificando a documentação da primavera, por padrão, o PersistenceContext é do tipo Transaction, e é por isso que o método deve ser transacional ( http://docs.spring.io/spring/docs/current/spring-framework-reference/ html / orm.html ):

A anotação @PersistenceContext possui um tipo de atributo opcional, cujo padrão é PersistenceContextType.TRANSACTION. Esse padrão é o que você precisa para receber um proxy compartilhado do EntityManager. A alternativa, PersistenceContextType.EXTENDED, é um caso completamente diferente: isso resulta no chamado EntityManager estendido, que não é seguro para threads e, portanto, não deve ser usado em um componente acessado simultaneamente, como um bean singleton gerenciado pelo Spring. EntityManagers estendidos devem ser usados ​​apenas em componentes com estado que, por exemplo, residem em uma sessão, com o ciclo de vida do EntityManager não vinculado a uma transação atual, mas sendo completamente compatível com o aplicativo.

mlg
fonte
71
Se um método sem a @Transactionalanotação chamar um método com a @Transactionalanotação no mesmo arquivo de classe , você também ficará impressionado com esse erro (era isso que eu estava enfrentando).
27416 Jacob van Lingen
5
Anotei a classe de serviço com @Transactional, que também funciona. Não tenho certeza se é o caminho certo para ir, mas parece que ele está funcionando bem ...
milosmns
8
Outra coisa que vale a pena mencionar é que esta anotação deve ser usada para método público, pois não funciona de outra maneira.
Yuriy Kravets
7
Lembre-se, por favor, de que o @Transactional funciona apenas em métodos públicos.
Andrei_N 31/08/19
7
Para sua informação, precisa da anotação javax.transaction.Transactional (não da Spring).
Java-addict301 15/05/19
85

Eu recebi essa exceção ao tentar usar um método personalizado deleteBy no repositório de dados do spring. A operação foi tentada a partir de uma classe de teste JUnit.

A exceção não ocorre ao usar a @Transactionalanotação no nível da classe JUnit.

Kishore Guruswamy
fonte
8
Eu estava na mesma situação, em vez de anotar a classe de teste, anotei o método de serviço para que ele escorresse, mesmo que não fosse um teste.
Paul Nelson Baker
@Transactionalem nível de classe, pode mascarar possíveis problemas de teste que manipulam transações diferentes em seus serviços.
Zon
1
Eu usei @Trasactionalno método de repositório, já que é o local onde estou realmente interagindo com o banco de dados e funcionando bem.
Harish Kumar Saini
22

Esse erro me deixou tristemente por três dias, a situação que enfrentei produziu o mesmo erro. Seguindo todos os conselhos que pude encontrar, brinquei com a configuração, mas sem sucesso.

Eventualmente, achei que, a diferença, o Serviço que eu estava executando estava contido em um jar comum, o problema acabou sendo o AspectJ, que não tratava a instanciação do Serviço da mesma forma. De fato, o proxy estava simplesmente chamando o método subjacente sem que toda a mágica normal do Spring fosse executada antes da chamada do método.

No final, a anotação @Scope colocada no serviço conforme o exemplo resolveu o 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();
    }

}

O método que eu publiquei é um método de exclusão, mas as anotações afetam todos os métodos de persistência da mesma maneira.

Espero que esta postagem ajude alguém que tenha enfrentado problemas com o mesmo problema ao carregar um serviço de um jar

Chris March
fonte
Adicionar @Scope(proxyMode = ScopedProxyMode.INTERFACES)à classe DAO que implementa uma interface é realmente importante. Passei o dia inteiro para descobrir esse erro e sua solução é a única que funciona. Muito obrigado!
Thach Van
1
Uma anotação de sua resposta acabou de me salvar provavelmente em torno de 3 dias. Acabei de experimentar o verdadeiro poder do edenema. Дякс.
Oleksii Kyslytsyn
8

boardRepo.deleteByBoardId (id);

Enfrentou o mesmo problema. GOT javax.persistence.TransactionRequiredException: nenhum EntityManager com transação real disponível para o encadeamento atual

Eu o resolvi adicionando a anotação @Transactional acima do controlador / serviço.

Vikram S
fonte
7

Eu tive o mesmo problema e eu adicionei tx:annotation-drivenem applicationContext.xmle funcionou.

Stone Feng
fonte
4

Eu tive o mesmo erro ao acessar um método já transacional-anotado de um método não transacional dentro do mesmo 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();

Corrigi o erro chamando executeQuery () no 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
fonte
3

Adicionar a org.springframework.transaction.annotation.Transactionalanotação no nível da classe para a classe de teste corrigiu o problema para mim.

Leo
fonte
3

Apenas uma observação para outros usuários que procuram respostas para o erro. Outro problema comum é:

Você geralmente não pode chamar um @transactionalmétodo de dentro da mesma classe.

(Existem maneiras e meios de usar o AspectJ, mas a refatoração será muito mais fácil)

Então, você precisará de uma classe de chamada e uma classe que contenha os @transactionalmétodos.

sparkyspider
fonte
2

Para nós, o problema se resumia às mesmas configurações de contexto em vários arquivos de configuração. Verifique se você não duplicou o seguinte em vários arquivos de configuração.

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

Eu tive o mesmo código de erro quando usei @Transactionum método / nível de ação errado.

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

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

Eu tive que colocar o @Transactionallogo acima do método methodWithANumberOfDatabaseActions(), é claro.

Isso resolveu a mensagem de erro no meu caso.

outro nó
fonte
0

Eu removi o modo de

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

para fazer isso funcionar

ropo
fonte
0

Eu tive esse problema por dias e nada que encontrei em qualquer lugar on-line me ajudou. Estou postando minha resposta aqui, caso ajude mais alguém.

No meu caso, eu estava trabalhando em um microsserviço sendo chamado por meio de comunicação remota e minha anotação @Transactional no nível de serviço não estava sendo captada pelo proxy remoto.

Adicionar uma classe delegada entre as camadas de serviço e dao e marcar o método delegado como transacional corrigiu isso para mim.

fleeblewidget
fonte
0

Isso nos ajudou, talvez possa ajudar outras pessoas no futuro. @Transactionnão estava funcionando para nós, mas isso fez:

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

JLane
fonte
0

Se você tem

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

e super classe

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

e você liga

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

você não receberá um Spring TransactionInterceptor (que fornece uma transação).

Isso é o que você precisa fazer:

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

Inacreditável, mas verdadeiro.

user2006754
fonte