Работа с Spring Data JPA, Hibernate и менеджером нескольких транзакций: не определен bean-компонент с именем actionManager

РЕДАКТИРОВАТЬ: для тех, кто может быть заинтересован в этом вопросе, я предоставляю анализ проблемы с соответствующим решением в конце вопроса.

Я настраиваю модуль для веб-приложения, в котором я использую Spring 3.2, Hibernate 4.1, Spring Data JPA 1.3 и Apache CXF 2.5 (в частности, модуль JAX-RS). У меня есть следующая конфигурация (которая работает отлично, подробности опущены для краткости):

  @Bean(name = "entityManagerFactory")
  public LocalContainerEntityManagerFactoryBean getEntityManagerFactory() throws SQLException{
    LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
    //...    
    return factory;
  }

  @Bean(name = "transactionManager")
  public JpaTransactionManager getTransactionManager() throws SQLException{
    JpaTransactionManager manager = new JpaTransactionManager();
    //...    
    return manager;
  }

  @Bean(name = "persistenceExceptionTranslator")
  public PersistenceExceptionTranslator getPersistenceExceptionTranslator(){
    return new HibernateExceptionTranslator();
  }

Моя проблема в том, что мне приходится полагаться на некоторые внешние модули, которые определяют свои собственные PlatformTransactionManager, так что я работаю с большим количеством менеджеров транзакций одновременно. Эта проблема легко решается с помощью Transactional.html # value (), поэтому везде, где мне нужно использовать @Transactional Я уточнил аннотацию с именем менеджера транзакций, который я должен использовать для этой транзакции.
Я хотел бы изменить имя менеджера транзакций, который я определил в своем модуле, на что-то более значимое, чтобы оно соответствовало стандарту внешних модулей. Так, например, externalModule1 определяет своего менеджера как externalModule1TransactionManager и я хотел бы иметь

  @Bean(name = "myModuleransactionManager")
  public JpaTransactionManager getTransactionManager() throws SQLException{
    JpaTransactionManager manager = new JpaTransactionManager();
    //...    
    return manager;
  }

Это кажется довольно простым, к сожалению, когда я делаю это изменение (и я меняю использование @Transactional#value() соответственно я получаю исключение.

java.lang.RuntimeException: org.apache.cxf.interceptor.Fault: No bean named 'transactionManager' is defined
    at org.apache.cxf.interceptor.AbstractFaultChainInitiatorObserver.onMessage(AbstractFaultChainInitiatorObserver.java:110)
    at org.apache.cxf.phase.PhaseInterceptorChain.doIntercept(PhaseInterceptorChain.java:323)
    at org.apache.cxf.transport.ChainInitiationObserver.onMessage(ChainInitiationObserver.java:123)
    at org.apache.cxf.transport.http.AbstractHTTPDestination.invoke(AbstractHTTPDestination.java:207)
    at org.apache.cxf.transport.servlet.ServletController.invokeDestination(ServletController.java:213)
    at org.apache.cxf.transport.servlet.ServletController.invoke(ServletController.java:154)
    at org.apache.cxf.transport.servlet.CXFNonSpringServlet.invoke(CXFNonSpringServlet.java:126)
    at org.apache.cxf.transport.servlet.AbstractHTTPServlet.handleRequest(AbstractHTTPServlet.java:185)
    at org.apache.cxf.transport.servlet.AbstractHTTPServlet.doGet(AbstractHTTPServlet.java:113)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:621)
    at org.apache.cxf.transport.servlet.AbstractHTTPServlet.service(AbstractHTTPServlet.java:164)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:305)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:222)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:123)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:472)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:171)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:99)
    at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:936)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:407)
    at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1004)
    at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:589)
    at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:312)
    at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:895)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:918)
    at java.lang.Thread.run(Thread.java:662)
Caused by: org.apache.cxf.interceptor.Fault: No bean named 'transactionManager' is defined
    at org.apache.cxf.service.invoker.AbstractInvoker.createFault(AbstractInvoker.java:155)
    at org.apache.cxf.service.invoker.AbstractInvoker.invoke(AbstractInvoker.java:121)
    at org.apache.cxf.jaxrs.JAXRSInvoker.invoke(JAXRSInvoker.java:167)
    at org.apache.cxf.jaxrs.JAXRSInvoker.invoke(JAXRSInvoker.java:94)
    at org.apache.cxf.interceptor.ServiceInvokerInterceptor$1.run(ServiceInvokerInterceptor.java:58)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:439)
    at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:303)
    at java.util.concurrent.FutureTask.run(FutureTask.java:138)
    at org.apache.cxf.workqueue.SynchronousExecutor.execute(SynchronousExecutor.java:37)
    at org.apache.cxf.interceptor.ServiceInvokerInterceptor.handleMessage(ServiceInvokerInterceptor.java:106)
    at org.apache.cxf.phase.PhaseInterceptorChain.doIntercept(PhaseInterceptorChain.java:263)
    ... 25 more
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'transactionManager' is defined
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanDefinition(DefaultListableBeanFactory.java:568)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getMergedLocalBeanDefinition(AbstractBeanFactory.java:1099)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:278)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:198)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.determineTransactionManager(TransactionAspectSupport.java:246)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:100)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:155)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    at org.springframework.data.jpa.repository.support.LockModeRepositoryPostProcessor$LockModePopulatingMethodIntercceptor.invoke(LockModeRepositoryPostProcessor.java:92)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:91)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:155)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:204)
    at sun.proxy.$Proxy98.save(Unknown Source)
    at myModule.package.SomeOtherClass.someOtherMethod(SomeOtherClass.java:114)
    at myModule.package.SomeOtherClass$$FastClassByCGLIB$$2bda5a73.invoke(<generated>)
    at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:698)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:150)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:110)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:631)
    at myModule.package.SomeClass$$EnhancerByCGLIB$$37044080.myMethod(<generated>)
    at myModule.package.SomeClass.someMethod(SomeClass.java:64)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at org.apache.cxf.service.invoker.AbstractInvoker.performInvocation(AbstractInvoker.java:173)
    at org.apache.cxf.service.invoker.AbstractInvoker.invoke(AbstractInvoker.java:89)
    ... 34 more

В частности, я хотел бы сосредоточить внимание на

myModule.package.SomeOtherClass.someOtherMethod(SomeClass.java:114)

а также

myModule.package.SomeClass.someMethod(SomeClass.java:64)

Их коды выглядят как

@Transactional("myModuleransactionManager")
public ... someOtherMethod(){
   ...
}

а также

public ... someMethod(){
   ...
}

Итак, в моем понимании эта конфигурация должна работать, почему она выдает это исключение? Требуется ли стандартный именованный менеджер транзакций? Или это что-то из-за cxf? Я нашел несколько вопросов, связанных с несколькими менеджерами транзакций в одном приложении ( пример 1, пример 2), но принятый ответ на эти вопросы приводит к моему решению. Что я неправильно понял и делаю неправильно?
Спасибо всем, кто готов прочитать этот длинный вопрос до здесь!

РЕДАКТИРОВАТЬ, чтобы предоставить полное объяснение, основанное на ответе Михаила: при использовании Spring Data JPA необходимо определить интерфейсы репозиториев для подключения к базе данных. someOtherMethod действительно вызывает один из моих репозиториев, который определяется как:

@Repository("myRepository")
@Transactional(propagation = Propagation.NESTED, value = "myModuleransactionManager")
public interface MyRepository extends JpaRepository<MyEntity, Integer>
{

}

Это снова выглядит хорошо, но, глядя на JpaRepository исходный код реализации (так, глядя на org.springframework.data.jpa.repository.support.SimpleJpaRepository Я обнаружил, что save (а также другие методы обновления) помечены @Transactional, Код от SimpleJpaRepository

    @Transactional
    public <S extends T> S save(S entity) {

        if (entityInformation.isNew(entity)) {
            em.persist(entity);
            return entity;
        } else {
            return em.merge(entity);
        }
    }

Следовательно, при использовании Spring Data JPA менеджер транзакций по умолчанию (тот, который называется transactionManager) является обязательным. Плохо для моей цели, но по крайней мере теперь я знаю, что это так!

6 ответов

Решение

Выглядит как твой someOtherMethod звонит любому другому @Transactional компонент (некоторый класс с save метод). И я думаю, что это @Transactinal() (пустая) аннотация (которая использует компонент по умолчанию с именем transactionManager).

Вы можете увидеть 2 TransactionInterceptor позиции в стековой трассе. Пожалуйста, предоставьте некоторые подробности об этом.

Я подозреваю, что вам просто нужно убедиться, что ваши репозитории используют правильно названный менеджер транзакций в вашей аннотации @EnableJpaRepositories.

т.е.

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
        entityManagerFactoryRef = "fooEntityManagerFactory", 
        transactionManagerRef = "fooTransactionManager",
        basePackages = {"com.sctrcd.multidsdemo.integration.repositories.foo"})
public class FooConfig {
    //...
}

Мне потребовалось некоторое время, чтобы выяснить детали, поэтому я только что представил более полное объяснение того, как настроить репозитории Spring Data JPA для работы с несколькими источниками данных здесь:

Несколько jpa: репозиториев в xml config, как настроить @EnableJPARepositories с помощью Spring java config?

И полный проект, демонстрирующий это здесь:

https://github.com/gratiartis/multids-demo

На самом деле, есть способ использовать именованный TransactionManager с Spring Data JPA. Это работает для меня:

<bean id="myTransactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="myEntityManagerFactory" />
</bean>
<tx:annotation-driven transaction-manager="myTransactionManager"/>

<jpa:repositories base-package="com.xxx.yyy" entity-manager-factory-ref="myEntityManagerFactory" transaction-manager-ref="myTransactionManager">
</jpa:repositories>

Я получил квалификацию @Transactional на своем сервисном уровне. И мне кажется, что мы можем отключить управление транзакциями в репозитории Spring Data следующим образом:

<jpa:repositories base-package="ru.xdsoft.conn.thanksapp.thanks.dao.repository"
                  entity-manager-factory-ref="thanksEntityManagerFactory"
                  enable-default-transactions="false"
/>
<jpa:repositories base-package="ru.xdsoft.conn.thanksapp.orgstruct.dao"
                  entity-manager-factory-ref="orgStructEntityManagerFactory"
                  enable-default-transactions="false"
/>

Не уверен на 100%, но ошибка исчезла. Нашел его здесь: https://github.com/spring-projects/spring-data-jpa/blob/master/src/test/resources/org/springframework/data/jpa/repository/support/disable-default-transactions.xml

Я нашел ваш вопрос очень интересным концептуально. И, таким образом, смог пересмотреть некоторые из моих давно забытых концепций. Похоже, это ограничение на стороне конфигурации Java. Таким образом, вам придется прибегнуть к небольшому количеству XML между ними, а затем дать транзакции менеджер что-то вроде

<tx:annotation-driven transaction-manager="myModuletransactionManager"/>

тогда вы можете дать использовать ваш TransactionManager. По умолчанию SimpleJpaRepository также будет использовать только новый.

Обновление: или, кстати, вы можете использовать это через Config также теперь, кажется, EnableTransactionManagement

Я делал это пару раз, поэтому вот как это сделать полностью в Java-коде (без xml). Однако я использую Ломбок, который очень рекомендую. Я сосредотачиваюсь исключительно на заданной проблеме, поэтому, если вы никогда не делали этого раньше, прочтите документацию Spring для настройки дополнительных деталей, касающихся диалектов JPA и классов драйверов источников данных Spring.

Объяснение: при вызове встроенных методов JPA, таких как findAll() или же save() TransactionInterceptor будет искать значение по умолчанию "transactionManager"Вот что вам нужно для подключения нескольких баз данных через Hibernate и JPA.

  1. Определите свои переменные в application.properties
      first.datasource.url=my/database/url/example
first.datasource.username=username-example
first.datasource.password=password-example

second.datasource.url=my/database/url/example
second.datasource.username=username-example
second.datasource.password=password-example
  1. Создайте свою базу данныхConfig
      import org.apache.commons.dbcp2.BasicDataSource;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
        entityManagerFactoryRef = "firstEntityManagerFactory", basePackages = {
        "be.company.appname.repository.firstdatabase", "be.company.appname.config.firstdatabase"
})
public class FirstDatabaseConfig {

    @Value("${first.datasource.url}")
    private String url;
    @Value("${first.datasource.username}")
    private String username;
    @Value("${first.datasource.password}")
    private String password;
    @Value("${spring.datasource.driver-class-name}")
    private String driver;

    @Primary
    @Bean(name = "firstDataSourceProperties")
    @ConfigurationProperties("first.datasource")
    public DataSourceProperties dataSourceProperties() {
        return new DataSourceProperties();
    }

    @Primary
    @Bean(name = "firstDataSource")
    @ConfigurationProperties("first.datasource.configuration")
    public DataSource dataSource(@Qualifier("firstDataSourceProperties") DataSourceProperties dataSourceProperties) {
        BasicDataSource dataSource = new BasicDataSource();
        dataSource.setDriverClassName(driver);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        dataSource.setRemoveAbandonedOnBorrow(true);
        dataSource.setRemoveAbandonedOnMaintenance(true);
        dataSource.setInitialSize(10);
        dataSource.setMaxTotal(20);
        dataSource.setValidationQuery("select 1 from MY_SCHEMA.TABLE");
        dataSource.setValidationQueryTimeout(900_000);
        dataSource.setTestWhileIdle(true);
        dataSource.setLogAbandoned(true);
        dataSource.setTestOnReturn(true);
        dataSource.setTestOnBorrow(true);
        dataSource.setDefaultAutoCommit(false);
        return dataSource;
    }

    @Primary
    @Bean(name = "firstEntityManagerFactory")
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(
            EntityManagerFactoryBuilder builder,
            @Qualifier("firstDataSource") DataSource firstDataSource
    ) {
        return builder
                .dataSource(firstDataSource)
                .packages("be.company.appname.model.firstdatabase")
                .persistenceUnit("first")
                .build();
    }

    @Primary
    @Bean(name = "firstTransactionManager")
    public PlatformTransactionManager transactionManager(
            @Qualifier("firstEntityManagerFactory") EntityManagerFactory firstEntityManagerFactory
    ) {
        return new JpaTransactionManager(firstEntityManagerFactory);
    }
}

Несколько замечаний по этому поводу:

  • обратите внимание на @Primary! Вам это понадобится только на одной из конфигураций базы данных ! Для своей второй базы данных вы можете использовать тот же код и внести очевидные изменения, включая изменение имен (например, firstEntityManagerFactory становится secondEntityManagerFactory и т. Д.), Изменение соответствующих переменных, изменение ValidationQuery и определение правильных пакетов.
  • во втором databaseConfig я удалил "be.company.appname.config.firstdatabase" от basePackages = {}декларация. Подойдет только указатель на пакет репозитория.
  1. Создайте свой объект базы данных в пакете, который вы определили в firstEntityManagerFactory фасоль.
      @Entity
@Table(name = "USER")
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MyUser {

    @Id
    @Column(name = "ID")
    private Long id;

    @Column(name = "USERNAME")
    private String userName;

    @Column(name = "UID")
    private String uid;

    @Column(name = "FIRST_NAME")
    private String firstName;

    @Column(name = "LAST_NAME")
    private String lastName;
  • @Table это точное имя вашей таблицы базы данных
  • @Column- точное имя вашей таблицы-столбца базы данных. Ваши собственные переменные поля не обязательно должны совпадать, но я делаю это по привычке (например, объявляя @Column(name = "USERNAME") private String name; также будет работать
  1. Создайте свой репозиторий в пакете, который вы объявили в классе databaseConfig.
      @Repository
@Transactional(value = "firstTransactionManager")
public interface MyUserRepository extends JpaRepository<MyUser, Long> {

    List<MyUser> findAll();
}

Что вызывает появление исключения спрашивающего? Например:

Я звоню MyUserRepository.findById(1L)без объявления метода в моем репозитории. Это известное сокращение встроенного JPA по умолчанию. Взгляните на подробности ваших собственных сокращенных запросов JPA . Если не объявлено в репозитории, ваше приложение будет искать по умолчанию transactionManagerминуя ваш репозиторий-интерфейс. Но, объявив метод, ваше приложение будет знать, что нужно искать свой собственный firstTransactionManager

примечание: создание BasicDataSourceможет зависеть от того, какую базу данных вы используете. Я использую DB2Dialectдля подключения к AS400.

Другие вопросы по тегам