spring动态切换数据库支持事务

来源:互联网 发布:知乎周刊哪里下 编辑:程序博客网 时间:2024/05/22 17:17
在项目中有mysql的多个库,在代码中同一个方法可能会操作不同的表。在网上学习了各种方法。大概总结了一下。
1.mycat、cobar等分布式数据库中间件。
可以很好的支持,但是太重量级了,对我们项目有点大材小用。
2.spring的AbstractRoutingDataSource实现数据库连接切换。
可以动态的切换数据源,但是对事务有影响,可以用JTA实现事务一致,但是效率较低。而且我们项目事务可以单库一致就满足需求。所以采用了这种方式。
下面是具体的实现过程:
1)spring的配置文件中配置多个数据源。
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"destroy-method="close"><property name="driverClassName" value="com.mysql.jdbc.Driver" /><property name="url" value="${mysql.url}" /><property name="username" value="${mysql.username}" /><property name="password" value="${mysql.password}" /><!-- 连接池启动时的初始值 --><property name="initialSize" value="1" /><!-- 最大空闲值.当经过一个高峰时间后,连接池可以慢慢将已经用不到的连接慢慢释放一部分,一直减少到maxIdle为止 --><property name="maxIdle" value="2" /><!-- 最小空闲值.当空闲的连接数少于阀值时,连接池就会预申请一些连接,以避免洪峰来时再申请而造成的性能开销 --><property name="minIdle" value="1" /></bean><bean id="xiaomi" class="org.apache.commons.dbcp.BasicDataSource"destroy-method="close"><property name="driverClassName" value="com.mysql.jdbc.Driver" /><property name="url" value="${mysql.url.xiaomi}" /><property name="username" value="${mysql.username}" /><property name="password" value="${mysql.password}" /><!-- 连接池启动时的初始值 --><property name="initialSize" value="1" /><!-- 最大空闲值.当经过一个高峰时间后,连接池可以慢慢将已经用不到的连接慢慢释放一部分,一直减少到maxIdle为止 --><property name="maxIdle" value="2" /><!-- 最小空闲值.当空闲的连接数少于阀值时,连接池就会预申请一些连接,以避免洪峰来时再申请而造成的性能开销 --><property name="minIdle" value="1" /></bean>


2)定义动态的数据源
<bean class="com.futuren.wzk.common.datasource.DynamicDataSource"id="dynamicDataSource"><property name="targetDataSources"><map key-type="java.lang.String"><entry value-ref="dataSource" key="wzk"></entry><entry value-ref="xiaomi" key="xiaomi"></entry></map></property><property name="defaultTargetDataSource" ref="dataSource" /></bean>


3)定义动态数据源和辅助类
public class DynamicDataSource extends AbstractRoutingDataSource {@Overrideprotected Object determineCurrentLookupKey() {String type = DataSourceContextHolder.getDataSourceType();return type;}}public class DataSourceContextHolder {private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();/*** @Description: 设置数据源类型* @param dataSourceType* 数据库类型* @return void* @throws*/public static void setDataSourceType(String dataSourceType) {contextHolder.set(dataSourceType);}/*** @Description: 获取数据源类型* @param* @return String* @throws*/public static String getDataSourceType() {return contextHolder.get();}/*** @Description: 清除数据源类型* @param* @return void* @throws*/public static void clearDataSourceType() {contextHolder.remove();}}


4)修改事务管理器的数据源为动态数据源,指定事务注解的排序为2,我们会指定切换数据源的注解为1,这样在事务之前切换数据源,否则在事务之后切换的的话,无效。
<!-- 注解事务处理 --><bean id="transactionManager"class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="dynamicDataSource" /><!-- 启用注解 --><tx:annotation-driven transaction-manager="transactionManager" order="2"/>


5)定义切换数据库的注解和aop切面,指定排序为1,这里有个疑问,通过切点获取代理方法的注解数据,我用的是反射,但是网上有说可以直接作为参数传入的,我一直没有试验成功,不知道哪里有错,后续哪位大神指导的,可以分享一下。
@Target({ElementType.METHOD, ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Inherited@Documentedpublic @interface DataSource {String name();}@Component@Aspect@Order(1)public class DataSourceProxy {@Before(value="@annotation(com.futuren.wzk.common.datasource.DataSource)")public void before(JoinPoint jp) {String methodName = jp.getSignature().getName();Method[] methods = jp.getTarget().getClass().getMethods();for(Method method : methods) {if(method.getName().equals(methodName)) {DataSource ds = method.getAnnotation(DataSource.class);DataSourceContextHolder.setDataSourceType(ds.name());}}}}


6)在项目中使用
@Override@Transactional@DataSource(name="ucenter")public int addUser(User user) {userMapper.insert(user);return user.getUid();}


这种方法,只支持单库事务,如果要多库事务,可能要引入JTA,或者是其他自定义实现。或者其他我不知道的技术。欢迎讨论!

0 0