使用 Spring Data JPA、Hibernate 和多事务管理器:没有定义名为 'transactionManager' 的 bean



编辑:对于可能对这个问题感兴趣的人,我在问题的最后提供了对问题的分析和相关的解决方案。

我正在配置一个web应用程序的模块,其中我使用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,所以我发现自己同时使用更多的事务管理器。这个问题很容易通过transaction.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(){
   ...
}

那么,在我的理解这个配置应该工作,为什么它抛出那个异常?是否需要标准命名的事务管理器?还是因为什么?我发现了一些与同一应用程序中的多个事务管理器相关的问题(示例1、示例2),但这些问题的公认答案促使我提出了解决方案。我误解了什么,我做错了什么?
感谢大家谁愿意阅读这个长问题,直到这里!

EDIT根据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进行了注释。代码来自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)是强制性的。对我的目标来说很糟糕,但至少我现在知道了!

看起来像你的someOtherMethod调用任何其他@Transactional组件(一些类与save方法)。我认为它有@Transactinal()(空)注释(使用默认bean命名为transactionManager)。

您可以在stacktrace中看到2个TransactionInterceptor位置。请介绍一下具体情况。

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

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

我花了一段时间才弄清楚细节,所以我只是提供了一个关于如何配置Spring Data JPA存储库以使用多个数据源的更全面的解释:

多个jpa: xml配置中的存储库,如何使用Spring java配置与@ enablejparepository配置?

和一个完整的项目在这里演示:

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>

我觉得你的问题在概念上很有趣。因此,我能够修正一些我遗忘已久的概念。看起来这是java配置端的限制。所以你需要在两者之间使用一些xml然后给transactionmanager赋值,比如

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

,然后你可以使用你的transactionManager。默认的 SimpleJpaRepository 也将只使用新的。

Update:或者顺便说一下,你也可以通过配置使用它,现在似乎EnableTransactionManagement

我已经在我的服务层限定了@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)。但是我确实使用Lombok,我强烈推荐它。我只关注所问的问题,所以如果您以前从未这样做过,请阅读Spring文档,以配置有关JPA方言和Spring数据源驱动程序类的其他细节。

解释:当调用JPA嵌入的方法如findAll()save()时,TransactionInterceptor将寻找默认的"transactionManager"以下是通过Hibernate和JPA连接多个数据库所需的

    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
  • 创建数据库econfig
  • 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 !您只需要在databaseConfigs中的1上使用这个!对于第二个数据库,您可以使用相同的代码并进行明显的更改,包括名称更改(例如,firstEntityManagerFactory变为secondEntityManagerFactory等),更改适当的变量,更改ValidationQuery并定义正确的包。
    • 在第二个数据库econfig中,我从basePackages = {}声明中删除了"be.company.appname.config.firstdatabase"。只有指向存储库包的指针才可以。
  • 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;)
  • 在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。

    相关内容

    • 没有找到相关文章

    最新更新