1、核心思想,spring提供了一个DataSource的子类,该类支持多个数据源
org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource
该类的源码如下:
org.springframework.jdbc.datasource.lookup; java.sql.Connection; java.sql.SQLException; java.util.HashMap; java.util.Iterator; java.util.Map; java.util.Map.Entry; javax.sql.DataSource; org.springframework.beans.factory.InitializingBean; org.springframework.jdbc.datasource.AbstractDataSource; org.springframework.jdbc.datasource.lookup.DataSourceLookup; org.springframework.jdbc.datasource.lookup.JndiDataSourceLookup; org.springframework.util.Assert; AbstractRoutingDataSource AbstractDataSource InitializingBean { // 注意这里,这里使用Map存放多个数据源 Map<Object, Object> targetDataSources; ... // 篇幅有限,省略其他代码,有兴趣的同学可以自行研究,还是支持蛮多功能的,比如JNDI等数据源方式 // 注意这个暴露的方法,spring在从数据源中获取数据库连接时,通过这个方法确定数据源 DataSource determineTargetDataSource() { Assert.notNull(.resolvedDataSources, ); Object lookupKey = .determineCurrentLookupKey(); DataSource dataSource = (DataSource).resolvedDataSources.get(lookupKey); (dataSource == && (.lenientFallback || lookupKey == )) { dataSource = .resolvedDefaultDataSource; } (dataSource == ) { IllegalStateException(+ lookupKey + ); } { dataSource; } } // 对外暴露的决定数据源的方法,通过外部设置数据源的key值, // spring自行从已存储的数据源中查找指定数据源 Object determineCurrentLookupKey(); }
通过分析源码,可以知道我们只需要实现如何动态设置切换数据源的方式即可,可以考虑使用注解的方式在指定的位置添加数据源注解,利用AOP动态指定数据源。
自定义的数据源如下:
com.yao.yz.yaowangdrug.dataSource; org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; DynamicDataSource AbstractRoutingDataSource{ Object determineCurrentLookupKey() { DataSourceHolder.(); } }
2、自定义注解,本章节不做过多解释,对于注解有兴趣的同学请自行研究,废话一句:程序员以实际应用为主,研究行的内容请自行学习
com.yao.yz.yaowangdrug.dataSource; java.lang.annotation.*; (RetentionPolicy.) (ElementType.) @{ String value(); }
3、定义一个数据源切换功能组件,其中数据源存放的方式采用ThreadLocal的形式,我是单独把存放做成一个额外的组件,这个是可以自行决定的,代码如下:
com.yao.yz.yaowangdrug.dataSource; DataSourceHolder { ThreadLocal<String> = InheritableThreadLocal<String>(); setDataSourceKey(String dataSource) { .set(dataSource); } Object getDataSourceKey() { .get(); } }
com.yao.yz.yaowangdrug.dataSource; org.apache.log4j.Logger; org.aspectj.lang.JoinPoint; org.aspectj.lang.reflect.MethodSignature; java.lang.reflect.Method; DynamicAspect { Logger = Logger.(DynamicAspect.); switchDataSource(JoinPoint point) NoSuchMethodException { Object target = point.getTarget(); String method = point.getSignature().getName(); Class<?>[] classz = target.getClass().getInterfaces(); Class<?>[] parameterTypes = ((MethodSignature) point.getSignature()) .getMethod().getParameterTypes(); Method m = classz[].getMethod(method, parameterTypes); (m != && m.isAnnotationPresent(.)) { data = m .getAnnotation(.); DataSourceHolder.(data.value()); } .info(+ DataSourceHolder.()); } }
4、在spring配置文件添加多个数据源,并将多个数据源统一纳入自定义数据源的管理,具体的配置信息如下:
<!--装载配置文件,将数据源的配置做成配置文件,方便管理,有兴趣的同学自行参考--><bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="location" value="Config.properties"/></bean> <!--数据源1--><bean id="dataSource1" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="com.mysql.jdbc.Driver" /> <property name="jdbcUrl" value="${dataSource1.jdbc.url}" /> <property name="user" value="${dataSource1.jdbc.username}" /> <property name="password" value="${dataSource1.jdbc.password}" /> <property name="minPoolSize" value="${dataSource1.jdbc.minPoolSize}" /> <property name="maxPoolSize" value="${dataSource1.jdbc.maxPoolSize}" /> </bean> <!--数据源2--><bean id="dataSource2" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="com.mysql.jdbc.Driver" /> <property name="jdbcUrl" value="${dataSource2.jdbc.url}" /> <property name="user" value="${dataSource2.jdbc.username}" /> <property name="password" value="${dataSource2.jdbc.password}" /> <property name="minPoolSize" value="${dataSource2.jdbc.minPoolSize}" /> <property name="maxPoolSize" value="${dataSource2.jdbc.maxPoolSize}" /></bean> <!--自定义数据源,将所有的数据源纳入自定义数据源管理--><bean id="dataSource" class="com.yao.yz.yaowangdrug.dataSource.DynamicDataSource"> <property name="targetDataSources"> <map> <!-- 对应spring提供的AbstractRoutingDataSource的Map --> <entry key="dataSource1" value-ref="dataSource1"/> <entry key="dataSource2" value-ref="dataSource2"/> </map> </property> <property name="defaultTargetDataSource" ref="dataSource1"/></bean>
5、配置数据源切换的AOP,代码如下:
<!--动态决定数据源--><bean id="dataSourceSwitchAspect" class="com.yao.yz.yaowangdrug.dataSource.DynamicAspect"/><aop:config> <aop:aspect id="dynamicAspect" ref="dataSourceSwitchAspect"> <!--数据源切换可以控制为Dao或者service层,请根据实际业务需要自行决定--> <aop:pointcut id="dynamicPointCut" expression="execution(* com.xx.xx.xx.dao.*.*(..))"/> <aop:before method="switchDataSource" pointcut-ref="dynamicPointCut"/> </aop:aspect></aop:config>
6、使用spring整合mybatis,和一般的整合却别在于使用的数据源为自定义数据源,代码如下:
classpath:ModelMapper.xml classpath:ModelMapper1.xml classpath:ModelMapper2.xml
注意:目前精力有限,此处为什么可以使用自定义多个数据源的底层原理还没来得及看,有兴趣的同学请自行研究,如果好心的话可以简单的告知我,谢谢你了~~~
7、将自定义数据源纳入spring的事务管理器管理,配置代码如下:
<!--事务管理器--><bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /></bean><!--申明事务回滚的方法和异常信息--><tx:advice id="txAdvic" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="do*" read-only="false" propagation="REQUIRED" rollback-for="java.lang.Exception"/> </tx:attributes></tx:advice><!--申明事务范围--><aop:config> <aop:pointcut id="txPointCut" expression="execution(* com.xx.xx.xx.service.*.*(..))"/> <aop:advisor advice-ref="txAdvic" pointcut-ref="txPointCut"/></aop:config>
Model类:
com.yao.yz.yaowangdrug.model; DBModel { String ; String ; String getValue() { ; } setValue(String value) { .= value; } String getKey() { ; } setKey(String key) { .= key; } }
Dao接口:
com.yao.yz.yaowangdrug.dao; ; com.yao.yz.yaowangdrug.model.DBModel; DBDao { () insert(DBModel dbModel) Exception; }
Service接口:
com.yao.yz.yaowangdrug.service; com.yao.yz.yaowangdrug.model.DBModel; ; DBService { doInsert() Exception; doUpdate() Exception; testSeperateDB() Exception; }
关于事务的控制,有一下几点说明:
1、采用申明或者注解实现事务控制时时,因为开启了事务控制,所以如果是两个不同的数据源Dao,根据spring的事务传播特性,第二个事务开启将使用已有的事务(即将采用第一个数据源的数据库连接)进行事务操作,所以此时事务控制是失效的(即使切面执行了数据源切换)。结论就是跨数据库的事务是无法通过spring的数据库控制实现的!!!请切记。
2、同一个数据源的事务控制和普通的数据源控制是一致的,没有什么区别。
以上代码都是经过测试通过,可以实现跨库的数据方式,主要的应用场景是mysql的数据库读写分离。如果不正确的地方请告知,谢谢!