问题
I have 2 clients using same Spring based REST Application deployed in tomcat. Depending on client I need to choose between data sources and transaction manager. How do I choose at runtime, which transaction manager to use?
<bean id="First_dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="url" value="${First_jdbc.url}" />
<property name="driverClassName" value="${First_jdbc.driverClassName}" />
<property name="username" value="${First_jdbc.username}" />
<property name="password" value="${First_jdbc.password}" />
<property name="removeAbandoned" value="true" />
<property name="initialSize" value="20" />
<property name="maxActive" value="30" />
<!-- <property name="defaultAutoCommit" value="false" /> -->
</bean>
<bean id="Second_dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="url" value="${Second_jdbc.url}" />
<property name="driverClassName" value="${Second_jdbc.driverClassName}" />
<property name="username" value="${Second_jdbc.username}" />
<property name="password" value="${Second_jdbc.password}" />
<property name="removeAbandoned" value="true" />
<property name="initialSize" value="20" />
<property name="maxActive" value="30" />
<!-- <property name="defaultAutoCommit" value="false" /> -->
</bean>
<bean id="First_TransactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
scope="singleton">
<property name="dataSource" ref="First_dataSource" />
<qualifier value="SC_TM"></qualifier>
</bean>
<bean id="Second_TransactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
scope="singleton">
<property name="dataSource" ref="Second_dataSource" />
<qualifier value="Second_TM"></qualifier>
</bean>
In code how do choose @Transactional("????") at run time. If it is not possible with org.springframework.jdbc.datasource.DataSourceTransactionManager is there any other way of doing it?
回答1:
Using @Transactional
, you can specify the transaction manager like this:
@Transactional("First_TransactionManager")
or
@Transactional("Second_TransactionManager")
depending on which one you want to use. Make sure to use inside the transactional method the correct Entity Manager / session factory. Those you also have to specify which one you want to inject with @PersistenceContext("nameOfPersistenceUnit")
.
回答2:
I do not know why you want to change between the 2 transaction manager may be you need to check Transaction manager chain solution, but in case you need this you can add your @transactional on Repo methods and do 2 Repos and manage it from the service layer as switch, otherwise I'm believing that there is solution could be done using AOP but it will need more time to think about it.
回答3:
Problem is solved through AOP.
- Define multiple data sources and corresponding Transacation Manager (as I have shown in my base question)
- First_dataSource mapped with First_TransactionManager and Second_dataSource mapped with Second_TransactionManager
Select which data source to use programatically depending on your business rules. In my case it was orgId
public class DataSourceProvider { @Autowired DataSource First_dataSource; @Autowired DataSource Second_dataSource;
public DataSource getDataSource(int orgId) { if (orgId == Constants.BUSINESS_PARTY_1) return Second_dataSource; else return First_dataSource; } public DataSource getFirst_dataSource() { return First_dataSource; } public void setFirst_dataSource(DataSource First_dataSource) { First_dataSource = First_dataSource; } public DataSource getSecond_dataSource() { return Second_dataSource; } public void setSecond_dataSource(DataSource Second_dataSource) { Second_dataSource = Second_dataSource; } }
AOP Configuration:
回答4:
<tx:advice id="First_txAdvice" transaction-manager="First_TransactionManager">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED" rollback-for="Exception" />
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="First_daoimplOperation"
expression="execution(* in.company.common.service.CommonServiceImpl.*(..))" />
<aop:advisor advice-ref="First_txAdvice" pointcut-ref="First_daoimplOperation" />
</aop:config>
<tx:advice id="Second_txAdvice" transaction-manager="Second_TransactionManager">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED" rollback-for="Exception" />
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="Second_daoimplOperation"
expression="execution(* in.company.common.service.CommonServiceImpl.*(..))" />
<aop:advisor advice-ref="Second_txAdvice" pointcut-ref="Second_daoimplOperation" />
</aop:config>
all database related services should be in matching pointcut like in this case it is: in.company.common.service.CommonServiceImpl.*(..))
回答5:
Consider using the Spring provided AbstractRoutingDataSource
instead of going down the path of choosing between multiple transaction managers. It will be a much cleaner solution.
See my answer to a similar question here :
https://stackoverflow.com/a/44167079/2200690
来源:https://stackoverflow.com/questions/22893160/choose-between-muliple-transaction-managers-at-runtime