【问题标题】:Working with Spring Data JPA, Hibernate and multiple transaction manager: No bean named 'transactionManager' is defined使用 Spring Data JPA、Hibernate 和多事务管理器:未定义名为“transactionManager”的 bean
【发布时间】:2013-02-15 02:59:41
【问题描述】:

编辑:对于可能对此问题感兴趣的人,我会在问题的末尾提供问题的分析和相关的解决方案。

我正在为使用 Spring 3.2、Hibernate 4.1、Spring Data JPA 1.3 和 Apache CXF 2.5(特别是 JAX-RS 模块)的 Web 应用程序配置一个模块。我有以下配置(运行良好,为简洁起见省略了详细信息):

  @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?我在同一个应用程序中发现了一些与多个事务管理器相关的问题(example 1example2),但这些问题中公认的答案推动了我的解决方案。我误会了什么,我做错了什么?
感谢所有愿意阅读这个长问题的人!

编辑以根据 Michail 的回答提供完整的解释:使用 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注释。代码来自@ 987654343@

    @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)是强制性的。对我的目标不利,但至少我现在知道就是这样!

【问题讨论】:

    标签: spring hibernate cxf spring-data-jpa transactionmanager


    【解决方案1】:

    看起来您的someOtherMethod 调用了任何其他@Transactional 组件(带有save 方法的某个类)。而且我认为它有@Transactinal()(空)注释(它使用名为transactionManager的默认bean)。

    您可能会在堆栈跟踪中看到 2 个TransactionInterceptor 位置。请提供一些有关它的详细信息。

    【讨论】:

    • Michail,您的回答使我找到了问题的根本原因。我的配置非常好,问题出在Spring Data Jpa。我扩展了您的答案以提供对该声明的解释。
    【解决方案2】:

    我怀疑您只需要确保您的存储库在您的 @EnableJpaRepositories 注释中使用正确命名的事务管理器。

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

    我花了一段时间才弄清楚细节,所以我在这里提供了关于如何配置 Spring Data JPA 存储库以使用多个数据源的更完整说明:

    Multiple jpa:repositories in xml config, how to configure with @EnableJPARepositories using Spring java config?

    还有一个完整的项目在这里展示:

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

    【讨论】:

    • 我最终使用了具有多个数据源的单个事务管理器(命名为 transactionManager,因此 Spring JPA 很高兴)。在代码方面,我使用了许多抽象类来定义基本行为和扩展抽象行为的特定配置类。也就是说,如果我现在可以弄清楚我是如何做到的,我可以在 github 上创建一个示例项目来分享我的实现。可能有用。
    【解决方案3】:

    实际上,有一种方法可以将命名 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>
    

    【讨论】:

    • 我缺少的关键部分是&lt;jpa:repositories&gt; 标签上的transaction-manager-ref="myTransactionManager"。在&lt;tx:annotation-driven&gt; 标记处指定事务管理器也不够。
    【解决方案4】:

    我发现你的问题在概念上非常有趣。从而能够修改一些我早已遗忘的概念。看起来它是对 java config 方面的限制。因此,您将不得不在两者之间使用一些 xml,然后给 transactionmanager 类似

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

    那么你就可以使用你的事务管理器了。默认 SimpleJpaRepository 也将只使用新的。

    更新: 或者顺便说一下,你现在也可以通过 Config 使用它,看起来 EnableTransactionManagement

    【讨论】:

    • 我的 Spring 配置类已经用@EnableTransactionManagement 进行了注释,但这并没有帮助。而且SimpleJpaRepository的源代码明显使用@Transaction。 Spring 文档状态:You can omit the transaction-manager attribute in the &lt;tx:annotation-driven/&gt; tag if the bean name of the PlatformTransactionManager that you want to wire in has the name transactionManager. If the PlatformTransactionManager bean that you want to dependency-inject has any other name, then you have to use the transaction-manager attribute explicitly, as in the preceding example.
    • 因此,当@Transaction 不带值属性使用时,我知道除了名为transactionManager 的标准之外别无他法。我现在继续前进,有一些事情要做,但是一旦我提交这些事情,我就会有一个小想法,我想尝试一下!今天可能已经晚些时候了。然后,我最终会更新这个帖子。
    • 不,我相信那只是为了覆盖默认的 transactionManager bean 名称
    • 是的,&lt;tx:annotation-driven /&gt; 用于覆盖默认名称。但是另一方面,@Transaction 等价于@Transaction("transactionManager"),这就是为什么如果我更改默认名称然后SimpleJpaRepository 将停止工作。
    【解决方案5】:

    我在我的服务层有资格 @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

    【讨论】:

      【解决方案6】:

      我已经完成了几次,所以这里是如何完全用 Java 代码(没有 xml)来完成它。不过,我确实使用 Lombok,我强烈推荐。我只关注所提出的问题,因此,如果您以前从未这样做过,请阅读 Spring 文档以配置有关 JPA 方言和 spring 数据源驱动程序类的更多详细信息。

      解释:当调用像findAll()save()这样的JPA嵌入方法时,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. 创建您的数据库配置
      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!您只需要在 1 个 databaseConfigs 上使用它!对于您的第二个数据库,您可以使用相同的代码并进行明显的更改,包括名称更改(例如 firstEntityManagerFactory 变为 secondEntityManagerFactory 等)、更改适当的变量、更改 ValidationQuery 和定义正确的包。
      • 在第二个 databaseConfig 中,我从 basePackages = {} 声明中删除了 "be.company.appname.config.firstdatabase"。只有指向存储库包的指针才可以。
      1. 在您在firstEntityManagerFactory bean 中定义的包中创建您的databaseObject。
      @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 shorthand queries 的详细信息。 如果未在存储库中声明,您的应用程序将绕过您的存储库接口查找默认的transactionManager。但是通过声明该方法,您的应用程序将知道寻找您自己的自定义firstTransactionManager

      注意:BasicDataSource 的创建可能因您使用的数据库而异。我正在使用 DB2Dialect 连接到 AS400。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2015-05-05
        • 1970-01-01
        • 2011-04-18
        • 2018-01-05
        • 2017-12-23
        • 2012-09-03
        • 2012-09-07
        相关资源
        最近更新 更多