Spring - Нет EntityManager с фактической транзакцией, доступной для текущего потока - не может надежно обработать вызов 'persist'

137

Я получаю эту ошибку при попытке вызвать метод «persist» для сохранения модели сущности в базе данных в моем веб-приложении Spring MVC. Не могу найти ни одного сообщения или страницы в Интернете, которые могут иметь отношение к этой конкретной ошибке. Кажется, что-то не так с bean-компонентом EntityManagerFactory, но я новичок в программировании на Spring, поэтому мне кажется, что все инициализировано нормально и согласно различным учебным статьям в Интернете.

диспетчер-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";

        }




    }
Михал Бил
источник
1
Как говорится в ошибке, транзакции нет. Аннотируйте метод регистрации с помощью @Transaction.
Rohit

Ответы:

263

У меня была та же проблема, и я аннотировал метод, @Transactionalи он сработал.

ОБНОВЛЕНИЕ: проверка документации Spring, похоже, что по умолчанию PersistenceContext имеет тип Transaction, поэтому метод должен быть транзакционным ( http://docs.spring.io/spring/docs/current/spring-framework-reference/ html / orm.html ):

Аннотация @PersistenceContext имеет необязательный тип атрибута, который по умолчанию имеет значение PersistenceContextType.TRANSACTION. Это значение по умолчанию - это то, что вам нужно для получения общего прокси-сервера EntityManager. Альтернатива, PersistenceContextType.EXTENDED, представляет собой совершенно другое дело: это приводит к так называемому расширенному EntityManager, который не является потокобезопасным и, следовательно, не должен использоваться в компоненте с одновременным доступом, таком как управляемый Spring singleton bean. Расширенные EntityManager должны использоваться только в компонентах с отслеживанием состояния, которые, например, находятся в сеансе, при этом жизненный цикл EntityManager не привязан к текущей транзакции, а полностью зависит от приложения.

млг
источник
72
Если метод без @Transactionalаннотации вызывает метод с @Transactionalаннотацией в том же файле класса, вы также столкнетесь с этой ошибкой (с чем я столкнулся).
Jacob van Lingen
5
Я пометил класс обслуживания @Transactional, что тоже работает. Не уверен, что это правильный путь, но похоже, что он работает нормально ...
milosmns
8
Еще стоит упомянуть, что эту аннотацию следует использовать для общедоступного метода, иначе она не работает.
Юрий Кравец
7
Помните, пожалуйста, что @Transactional работает только с общедоступными методами.
Andrei_N
7
К вашему сведению, для этого нужна аннотация javax.transaction.Transactional (а не Spring).
java-addict301
85

Я получил это исключение при попытке использовать специальный метод deleteBy в репозитории данных Spring. Попытка выполнить операцию из тестового класса JUnit.

Исключение не возникает при использовании @Transactionalаннотации на уровне класса JUnit.

Кишор Гурусвами
источник
8
Я был в той же ситуации, вместо того, чтобы аннотировать тестовый класс, я аннотировал метод службы, чтобы он просочился вниз, даже если это не был тест.
Пол Нельсон Бейкер
@Transactionalна уровне класса может маскировать возможные проблемы тестирования, которые управляют различными транзакциями в ваших сервисах.
Зон
1
Я использовал @Trasactionalметод репозитория, так как это место, где я действительно взаимодействую с базой данных, и она отлично работает.
Хариш Кумар Шайни,
22

Эта ошибка меня обманывала в течение трех дней, ситуация, с которой я столкнулся, вызвала ту же ошибку. Следуя всем советам, которые я смог найти, я поигрался с конфигурацией, но безрезультатно.

В конце концов я нашел это, разница, Служба, которую я выполнял, содержалась в общей банке, проблема оказалась в том, что AspectJ не обрабатывает экземпляр Службы одинаково. По сути, прокси просто вызывал базовый метод без выполнения всей обычной магии Spring перед вызовом метода.

В конце концов, аннотация @Scope, размещенная на сервисе в соответствии с примером, решила проблему:

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

}

Опубликованный мной метод - это метод удаления, но аннотации одинаково влияют на все методы сохранения.

Надеюсь, этот пост поможет кому-то еще, кто боролся с той же проблемой при загрузке службы из банки.

Крис Марч
источник
Добавление @Scope(proxyMode = ScopedProxyMode.INTERFACES)к классу DAO, реализующему интерфейс, действительно важно. Я потратил целый день, чтобы выяснить эту ошибку, и ваше решение - единственное, что работает. Большое спасибо!
Thach Van
1
Один L'annotation из вашего ответа только что спас меня, вероятно, около 3 дней. Я только что испытал на себе настоящую силу отеков. Дякс.
Алексей Кислицын
11

У меня была такая же ошибка, потому что я переключился с XML- на java-конфигурацию.

Дело в том, что я не переносил <tx:annotation-driven/>тег, как предложил Стоун Фэн.

Поэтому я просто добавил, @EnableTransactionManagementкак было предложено здесь, Настройка транзакций, управляемых аннотациями, в Spring в классе @Configuration , и теперь он работает

Сергей Верфель
источник
8

boardRepo.deleteByBoardId (ID);

Столкнулся с той же проблемой. GOT javax.persistence.TransactionRequiredException: нет EntityManager с фактической транзакцией, доступной для текущего потока

Я решил это, добавив аннотацию @Transactional над контроллером / службой.

Викрам С
источник
7

У меня была такая же проблема , и я добавил tx:annotation-drivenв applicationContext.xmlи это сработало.

Камень Фэн
источник
4

У меня была такая же ошибка при доступе к уже аннотированному транзакционному методу из нетранзакционного метода в том же компоненте:

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

Я исправил ошибку, вызвав executeQuery () в компоненте с собственной ссылкой:

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
источник
3

Добавление org.springframework.transaction.annotation.Transactionalаннотации на уровне класса для тестового класса устранило проблему для меня.

Лео
источник
3

Просто примечание для других пользователей, ищущих ответы на воровскую ошибку. Еще одна распространенная проблема:

Обычно вы не можете вызывать @transactionalметод из одного и того же класса.

(Есть способы и средства использования AspectJ, но рефакторинг будет намного проще)

Итак, вам понадобятся вызывающий класс и класс, содержащий @transactionalметоды.

sparkyspider
источник
2

Для нас проблема заключалась в одинаковых настройках контекста в нескольких файлах конфигурации. Убедитесь, что вы не дублировали следующее в нескольких файлах конфигурации.

<context:property-placeholder location="classpath*:/module.properties"/>
<context:component-scan base-package="...." />
Ник Уэст
источник
2

У меня был тот же код ошибки, когда я использовал @Transactionнеправильный метод / уровень действия.

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

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

Конечно, мне пришлось разместить его @Transactionalчуть выше метода methodWithANumberOfDatabaseActions().

Это решило сообщение об ошибке в моем случае.

anothernode
источник
0

Я удалил режим из

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

заставить эту работу

Ropo
источник
0

У меня была эта проблема в течение нескольких дней, и ничего, что я нашел в Интернете, мне не помогло, я отправляю свой ответ здесь на случай, если это поможет кому-то еще.

В моем случае я работал над микросервисом, который вызывается через удаленное взаимодействие, и моя аннотация @Transactional на уровне сервиса не принималась удаленным прокси.

Добавление класса делегата между слоями сервиса и dao и маркировка метода делегата как транзакционного исправило это для меня.

fleeblewidget
источник
0

Это помогло нам, может быть, это поможет другим в будущем. @Transactionу нас не работал, но это сработало:

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

JLane
источник
0

Если у вас есть

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

и супер-класс

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

и ты звонишь

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

вы не получите Spring TransactionInterceptor (который дает вам транзакцию).

Вот что вам нужно сделать:

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

Невероятно, но факт.

user2006754
источник