【问题标题】:Spring fails to choose between multiple TransactionManager beansSpring 无法在多个 TransactionManager bean 之间进行选择
【发布时间】:2014-10-30 07:59:50
【问题描述】:

我将我的 Spring 批处理进程从命令行应用程序迁移到 Spring Boot Web 应用程序,包括 spring-batch-admin-manager(版本 1.3.0)。

我的旧命令行应用程序使用两个 JPA 数据库和两个 TransactionManager 实例。我记得要运行它是一个地狱般的配置。从 Spring Boot 开始时,我预计事情会变得更舒服,但现在情况似乎更糟:

Spring 无法为事务选择正确的 TransactionManager 实例。

在应用程序启动时,Spring 正在访问我的一个类以执行带有 @PostConstruct 注释的代码块,应该从中调用事务方法。

@PostConstruct
public void init() {
  eventType = eventTypeBo.findByLabel("Sport"); // <-- Calling transactional method
  //...
}

从这里抛出错误。看看堆栈跟踪:

Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: 
           No qualifying bean of type [org.springframework.transaction.PlatformTransactionManager] is defined: 
           expected single matching bean but found 2: transactionManager,osm.transactionManager
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:313)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.determineTransactionManager(TransactionAspectSupport.java:337)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:252)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:95)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:207)
    at com.sun.proxy.$Proxy89.findByLabel(Unknown Source)
    at com.company.model.service.impl.EventTypeBo.findByLabel(EventTypeBo.java:43)
    at com.company.batch.article.utils.converter.SomeConverter.init(SomeConverter.java:83)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    //...

从错误日志中可以看出,我的TransactionManagers 被命名为“transactionManager”和“osm.transactionManager”。相应地配置事务:

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

<!-- DATABASE 2 -->
<bean id="osm.transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="osm.entityManagerFactory" />
</bean>
<tx:advice id="txAdvice" transaction-manager="osm.transactionManager">
    <tx:attributes>
        <tx:method name="*" />
    </tx:attributes>
</tx:advice>
<aop:config>
    <aop:pointcut id="osmServiceOperation"
        expression="execution(* com.qompa.osm.service.spec..*Service.*(..))" />
    <aop:advisor advice-ref="txAdvice" pointcut-ref="osmServiceOperation" />
</aop:config>

我在访问数据时使用了两种不同的 GenericDao 实现来区分PersistenceContexts:

public class OsmGenericDao<T> implements IGenericDao<T> {

  @PersistenceContext(unitName = "osm.entityManagerFactory")
  protected EntityManager em;
  //...
}

public class GenericDao<T> implements IGenericDao<T> {

  @PersistenceContext(unitName = "entityManagerFactory")
  protected EntityManager em;
  //...
}

一切似乎都已正确配置:但仍然失败!

编辑: 据我所知,Spring 的TransactionAspectSupport 尝试通过限定符 (determineTransactionManager) 确定事务管理器。因为 findByLabel 的 @Transactional 注释没有限定符,它试图在 DefaultListableBeanFactory 的 getBean 方法的帮助下确定正确的 bean,其中发现了两个无法进一步区分的相同类型的 bean。

一定有办法告诉Spring根据持久化上下文选择transactionManager!

有什么想法吗?

【问题讨论】:

  • 放弃&lt;tx:adivce /&gt;&lt;aop:config /&gt; 并从&lt;tx:annotation-driven /&gt; 中删除transaction-manager="transactionManager"。而是在任何地方使用 @Transactional 并指定要在这些 @Transactional bean 上使用哪个事务管理器。
  • 我发现这是一个丑陋的解决方案,因为我将我的模块绑定到特定的 TransactionManager 名称。它以前一直在工作,所以现在应该......一定有另一种方法!
  • 所以您不介意将其绑定到特定的实体管理器,但 tx 管理器是个问题?使用@Transactional,您需要指定使用哪个,以防有多个管理器。如果你不使用多个&lt;tx:advice /&gt; 块。
  • 可能我有点挑剔……我试试看!给代码库中的每个 @Transactional 注释添加一个名称对我来说仍然看起来很难看(这意味着重构近一百个类)
  • 确实很丑。我认为您的配置一定有问题,因为我记得使用两个单独的持久性上下文没有问题。我会尝试找到它并返回答案。

标签: java spring jpa spring-boot spring-transactions


【解决方案1】:

这是我使用 2 个持久性上下文的工作配置:

<!-- METADATA -->

<!-- Metadata connection pool -->
<bean id="scprMetadataDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
    destroy-method="close">
    <property name="driverClass"
        value="..." />
    <property name="jdbcUrl"
        value="..." />
    <property name="user"
        value="..." />
    <property name="password"
        value="..." />
    <property name="maxPoolSize"
        value="..." />
    <property name="minPoolSize"
        value="..." />
</bean>

<!-- Metadata entity manager factory -->
<bean id="scprMetadataEntityManagerFactory"
    class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="jpaVendorAdapter">
        <bean
            class="org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter">
            <property name="database" value="H2" />
        </bean>
    </property>
</bean>

<!-- Metadata transaction manager -->
<bean id="scprMetadataTransactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="scprMetadataEntityManagerFactory" />
</bean>


<!-- DOMAIN -->

<!-- Domain connection pool -->
<bean id="scprDomainDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
    destroy-method="close">
    <property name="driverClass"
        value="..." />
    <property name="jdbcUrl"
        value="..." />
    <property name="user"
        value="..." />
    <property name="password"
        value="..." />
    <property name="maxPoolSize"
        value="..." />
    <property name="minPoolSize"
        value="..." />
</bean>

<!-- Domain entity manager factory -->
<bean id="scprDomainEntityManagerFactory"
    class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="jpaVendorAdapter">
        <bean
            class="org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter">
            <property name="database" value="SQL_SERVER" />
        </bean>
    </property>
</bean>

<!-- Domain transaction manager -->
<bean id="scprDomainTransactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="scprDomainEntityManagerFactory" />
</bean>

其他配置(您的问题中缺少):

<persistence-unit name="scpr_metadata" transaction-type="RESOURCE_LOCAL">
    <exclude-unlisted-classes>true</exclude-unlisted-classes>
    <class>...</class>
    <class>...</class>
    <class>...</class>
</persistence-unit>

<persistence-unit name="scpr_domain" transaction-type="RESOURCE_LOCAL">
    <exclude-unlisted-classes>true</exclude-unlisted-classes>
    <class>...</class>
    <class>...</class>
    <class>...</class>
</persistence-unit>

在我的 java 类中:

@Repository
public final class MetadataRepositoryImpl implements MetadataRepository {

@PersistenceContext(unitName = "scpr_metadata")
private EntityManager em;

还有:

@Repository
public final class DomainRepositoryImpl implements DomainRepository {

@PersistenceContext(unitName = "scpr_domain")
private EntityManager em;

希望对你有所帮助。

【讨论】:

  • 这正是我一直在做的。只是在我的问题中省略了 persistence.xml。我现在拥有的相同配置已在命令行应用程序中启动并运行。这就是为什么我认为这可能是一个 Spring Boot 配置问题......无论如何,谢谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-02-19
  • 1970-01-01
  • 2013-02-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多