Правильный способ настройки транзакций в Spring для разных источников данных?

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

В основном одна область приложения позволяет создавать гаджеты в базе данных A, а другая область приложения позволяет настраивать аналогичные гаджеты в базе данных B.

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

<aop:config>
    <aop:pointcut id="companyServicePoint" 
          expression="execution(* com.company.service.CompanyService.*(..))" />

    <aop:advisor advice-ref="companyServiceTxAdvice"
         pointcut-ref="companyServicePoint"/>
</aop:config>

<tx:advice id="companyServiceTxAdvice" transaction-manager="txManager">
    <tx:attributes>
        <!-- set propogation required on create methods, all others are read-only -->
        <tx:method name="create*" propagation="REQUIRED"/>
        <tx:method name="*" read-only="true" />
    </tx:attributes>
</tx:advice>

<bean id="txManager" 
   class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource" />
</bean>

Это устанавливает pointcut при любом выполнении любых методов в CompanyService и связывает рекомендации по сделкам с pointcut, которые требуют транзакций для любых методов, имя которых начинается с "create". Совет по транзакциям связан с TransactionManager, который привязан к источнику данных.

При добавлении второго (или более) источников данных, как я могу применить один и тот же совет по транзакциям к другим источникам данных? Поскольку совет АОП может быть связан только с одним транзакционным менеджером, который может быть связан только с одним источником данных, мне нужно настроить повторяющиеся рекомендации по сделке?

Если я настрою дубликат рекомендаций по транзакциям на один и тот же pointcut, не означает ли это, что любые вызовы методов в моем интерфейсе CompanyService потребуют распространения от всех моих данных?

Чтобы сделать мой последний вопрос немного яснее, у меня будет несколько объявленных beans, которые реализуют интерфейс CompanyService, и каждый из этих beans будет иметь отдельный CompanyDAO для доступа к их отдельному DataSource. Я боюсь, что этот подход будет означать, что при вызове companyService1 bean совет по транзакциям будет запущен на all companyService beans/dataSources.

Я иду об этом неправильно?

Обновление: Я действительно протестировал конфигурацию, о которой я говорил выше (прикрепление двух советников к тому же pointcut), и вызов любого метода для каждого отдельного экземпляра реализации CompanyService выполняется в факт создает новые транзакции на обоих источниках данных, как и ожидалось:

DEBUG company.serviceDataSourceTransactionManager - Creating new transaction with name [com.company.service.CompanyService.createCompany]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
DEBUG company.serviceDataSourceTransactionManager - Acquired Connection [connection1 string here...] for JDBC transaction
...
DEBUG company.serviceDataSourceTransactionManager - Creating new transaction with name [com.company.service.CompanyService.createCompany]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
DEBUG company.serviceDataSourceTransactionManager - Acquired Connection [connection2 string here...] for JDBC transaction
...
DEBUG company.serviceDataSourceTransactionManager - Rolling back JDBC transaction on Connection [connection1 string here...]
...
DEBUG company.serviceDataSourceTransactionManager - Rolling back JDBC transaction on Connection [connection2 string here...]

Похоже, что это вызовет проблемы в будущем, поскольку либо экземпляр CompanyService работает только с одним DataSource.

Есть ли лучший способ настроить то, что я пытаюсь выполнить?

Ответ 1

Да, вам нужен двойной совет по сделке. Обратите внимание, что в следующей конфигурации выражение pointcut выбирает конкретный CompanyService bean.

<bean id="companyService1" class="com.company.service.CompanyServiceImpl">
  <property name="companyDao">
    <bean class="com.company.service.CompanyDAO">
      <property name="dataSource" ref="dataSource1"/>
    </bean>
  </property>
</bean>

<aop:config>
  <aop:pointcut
      id="companyServicePoint1"
      expression="bean(companyService1)"/>
  <aop:advisor
      advice-ref="companyServiceTxAdvice1"
      pointcut-ref="companyServicePoint1"/>
</aop:config>

<tx:advice id="companyServiceTxAdvice1" transaction-manager="txManager1">
  <tx:attributes>
    <!-- set propogation required on create methods, all others are read-only -->
    <tx:method name="create*" propagation="REQUIRED"/>
    <tx:method name="*" read-only="true"/>
  </tx:attributes>
</tx:advice>

<bean
    id="txManager1" 
    class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  <property name="dataSource" ref="dataSource1"/>
</bean>

Чтобы настроить другой CompanyService bean, вам необходимо продублировать тот же подробный шаблон. Другой способ разграничения транзакций в Spring использует TransactionProxyFactoryBean. Он немного менее подробный, поскольку он использует родительское определение bean для настройки общих свойств, унаследованных дочерним элементом beans.

<bean
    id="baseTransactionProxy"
    class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"
    abstract="true">
  <property name="transactionAttributes">
    <props>
      <prop key="create*">PROPAGATION_REQUIRED</prop>
      <prop key="*">PROPAGATION_REQUIRED,readOnly</prop>
    </props>
  </property>
</bean>

<bean id="companyService1" parent="baseTransactionProxy">
  <property name="transactionManager" ref="txManager1"/>
  <property name="target">
    <bean class="com.company.service.CompanyServiceImpl">
      <property name="companyDao">
        <bean class="com.company.service.CompanyDAO">
          <property name="dataSource" ref="dataSource1"/>
        </bean>
      </property>
    </bean>
  </property>
</bean>

<bean
    id="txManager1" 
    class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  <property name="dataSource" ref="dataSource1"/>
</bean>