druid多數(shù)據(jù)源配置+Datasurce動(dòng)態(tài)切換方式
druid多數(shù)據(jù)源配置+Datasurce動(dòng)態(tài)切換
AbstractRoutingDataSource 數(shù)據(jù)源動(dòng)態(tài)切換
spring 使用AbstractRoutingDataSource自定義動(dòng)態(tài)數(shù)據(jù)源時(shí)的事務(wù)處理, 需要繼承spring的AbstractRoutingDataSource定義自己的動(dòng)態(tài)數(shù)據(jù)源,可以根據(jù)需要?jiǎng)討B(tài)的切換不同數(shù)據(jù)庫(kù)的數(shù)據(jù)源,使用起來(lái)非常方便。
public class ChooseDataSource extends AbstractRoutingDataSource { /** * 獲取與數(shù)據(jù)源相關(guān)的key * 此key是Map<String,DataSource> resolvedDataSources 中與數(shù)據(jù)源綁定的key值 * 在通過(guò)determineTargetDataSource獲取目標(biāo)數(shù)據(jù)源時(shí)使用 */ @Override protected Object determineCurrentLookupKey() { return RouteHolder.getRouteKey(); } }
通過(guò)容器RouteHolder存儲(chǔ)當(dāng)前線程使用的數(shù)據(jù)源的key
/** * 保存當(dāng)前線程數(shù)據(jù)源的key */ public class RouteHolder { private static ThreadLocal<String> routeKey = new ThreadLocal<String>(); /** * 獲取當(dāng)前線程的數(shù)據(jù)源路由的key * @return */ public static String getRouteKey() { String key = routeKey.get(); return key; } /** * 綁定當(dāng)前線程數(shù)據(jù)源路由的key * 在使用完成之后,必須調(diào)用removeRouteKey()方法刪除 * @param key */ public static void setRouteKey(String key) { routeKey.set(key); } /** * 刪除與當(dāng)前線程綁定的數(shù)據(jù)源路由的key */ public static void removeRouteKey() { routeKey.remove(); } }
使用spring 的aop編程在業(yè)務(wù)邏輯方法運(yùn)行前將當(dāng)前方法使用數(shù)據(jù)源的key從業(yè)務(wù)邏輯方法上自定義注解@DataSource中解析數(shù)據(jù)源key并添加到RouteHolder中
/** * 執(zhí)行dao方法之前的切面 * 獲取datasource對(duì)象之前往RouteHolder中指定當(dāng)前線程數(shù)據(jù)源路由的key * */ public class DataSourceAspect { /** * 在dao層方法之前獲取datasource對(duì)象之前在切面中指定當(dāng)前線程數(shù)據(jù)源路由的key */ public void before(JoinPoint point) { Object target = point.getTarget(); String method = point.getSignature().getName(); Class<?>[] classz = target.getClass().getInterfaces(); Class<?>[] parameterTypes = ((MethodSignature) point.getSignature()) .getMethod().getParameterTypes(); try { if(classz != null && classz.length > 0) { Method m = classz[0].getMethod(method, parameterTypes); if (m != null && m.isAnnotationPresent(DataSource.class)) { DataSource data = m.getAnnotation(DataSource.class); RouteHolder.setRouteKey(data.value()); } } } catch (Exception e) { e.printStackTrace(); } } }
解釋:
DataSourceAspect 這個(gè)切面類,應(yīng)該針對(duì)的被代理類應(yīng)該是service的實(shí)現(xiàn)類(serviceImpl),因?yàn)閐ao層用的mybatis只有一個(gè)dao層的接口,所以放在service上做處理比較好。
業(yè)務(wù)邏輯方法
@Named("userService") public class UserService { @Inject private UserDao userDao; @DataSource("master") @Transactional(propagation=Propagation.REQUIRED) public void updatePasswd(int userid,String passwd) { User user = new User(); user.setUserid(userid); user.setPassword(passwd); userDao.updatePassword(user); } @DataSource("slave") @Transactional(propagation=Propagation.REQUIRED) public User getUser(int userid) { User user = userDao.getUserById(userid); System.out.println("username------:"+user.getUsername()); return user; } }
注解類
import java.lang.annotation.ElementType; import java.lang.annotation.Target; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** * RUNTIME * 編譯器將把注釋記錄在類文件中,在運(yùn)行時(shí) VM 將保留注釋,因此可以反射性地讀取。 * @author jiangxm * */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface DataSource { String value(); }
spring的配置文件
<bean id="dataSource" class="com.westone.datasource.DbRouteDataSource"> <property name="targetDataSources"> <map> <!-- write --> <entry key="master" value-ref="master"></entry> <!-- read --> <entry key="slave" value-ref="slave"></entry> </map> </property> </bean> <bean id="master" class="org.apache.commons.dbcp.BasicDataSource"> <property name="driverClassName" value="${jdbc.driverclass}" /> <property name="url" value="${jdbc.masterurl}" /> <property name="username" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> <property name="maxActive" value="${jdbc.maxActive}"></property> <property name="maxIdle" value="${jdbc.maxIdle}"></property> <property name="maxWait" value="${jdbc.maxWait}"></property> </bean> <bean id="slave" class="org.apache.commons.dbcp.BasicDataSource"> <property name="driverClassName" value="${jdbc.driverclass}" /> <property name="url" value="${jdbc.slaveurl}" /> <property name="username" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> <property name="maxActive" value="${jdbc.maxActive}"></property> <property name="maxIdle" value="${jdbc.maxIdle}"></property> <property name="maxWait" value="${jdbc.maxWait}"></property> </bean> <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate" > <constructor-arg index="0" ref="sqlSessionFactory" /> </bean> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="configLocation" value="classpath:config/mybatis/mybatis.cfg.xml"></property> <property name="dataSource" ref="dataSource" /> </bean> <!-- 配置mapper的映射掃描器 根據(jù)包中定義的接口自動(dòng)生成dao的實(shí)現(xiàn)類--> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.westone.dao"></property> </bean> <!-- 為業(yè)務(wù)邏輯層的方法解析@DataSource注解 為當(dāng)前線程的routeholder注入數(shù)據(jù)源key --> <bean id="aspectBean" class="com.westone.datasource.aspect.DataSourceAspect"></bean> <aop:config> <aop:aspect id="dataSourceAspect" ref="aspectBean"> <aop:pointcut id="dataSourcePoint" expression="execution(public * com.westone.service.*.*(..))" /> <aop:before method="beforeDaoMethod" pointcut-ref="dataSourcePoint"/> </aop:aspect> </aop:config> <!-- 事務(wù)管理器配置 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <!-- 開(kāi)啟事務(wù)注解驅(qū)動(dòng) 在業(yè)務(wù)邏輯層上使用@Transactional 注解 為業(yè)務(wù)邏輯層管理事務(wù)--> <tx:annotation-driven transaction-manager="transactionManager"/>
事務(wù)管理配置一定要配置在往RouteHolder中注入數(shù)據(jù)源key之前 否則會(huì)報(bào)
Could not open JDBC Connection for transaction; nested exception is java.lang.IllegalStateException: Cannot determine target DataSource for lookup key [null] 找不到數(shù)據(jù)源錯(cuò)誤。
由此就可以根據(jù)方法上的@DataSource(“master”) 注解配置不同的數(shù)據(jù)源key 使用動(dòng)態(tài)數(shù)據(jù)源。
解釋:
java.lang.reflect.Method.getAnnotation(Class annotationClass)
參數(shù):
annotationClass - Class對(duì)象對(duì)相應(yīng)的注解類型,比如Datasource.class 。
返回值:
如果存在于此元素,則返回該元素注解指定的注解對(duì)象,否則返回為null
例子
import java.lang.annotation.Annotation; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.reflect.Method; public class MethodDemo { public static void main(String[] args) { Method[] methods = SampleClass.class.getMethods(); Annotation annotation = methods[0].getAnnotation(CustomAnnotation.class); if(annotation instanceof CustomAnnotation){ CustomAnnotation customAnnotation = (CustomAnnotation) annotation; System.out.println("name: " + customAnnotation.name()); System.out.println("value: " + customAnnotation.value()); } } } @CustomAnnotation(name="SampleClass", value = "Sample Class Annotation") class SampleClass { private String sampleField; @CustomAnnotation(name="getSampleMethod", value = "Sample Method Annotation") public String getSampleField() { return sampleField; } public void setSampleField(String sampleField) { this.sampleField = sampleField; } } @Retention(RetentionPolicy.RUNTIME) @interface CustomAnnotation { public String name(); public String value(); }
編譯并運(yùn)行上面的程序,這將產(chǎn)生以下結(jié)果
-name: getSampleMethod
value: Sample Method Annotation
getInterfaces()
能夠獲得這個(gè)對(duì)象所實(shí)現(xiàn)的接口
配置多數(shù)據(jù)源并實(shí)現(xiàn)Druid自動(dòng)切換
Spring Boot配置多數(shù)據(jù)源
配置yml文件
這里并沒(méi)有對(duì)spring.datasource配置數(shù)據(jù)源,因?yàn)樵黾有聰?shù)據(jù)源后,系統(tǒng)會(huì)覆蓋由spring.datasource自動(dòng)配置的內(nèi)容。
這里自定義了兩個(gè)數(shù)據(jù)源spring.datasource.cmmi和spring.datasource.zentao
spring: datasource: type: com.alibaba.druid.pool.DruidDataSource base: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver initialize: true #指定初始化數(shù)據(jù)源,是否用data.sql來(lái)初始化,默認(rèn): true name: cmmi url: jdbc:mysql://127.0.0.1:3306/cmmi?useUnicode=true&characterEncoding=utf-8&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC&zeroDateTimeBehavior=convertToNull username: root password: root zentao: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver initialize: true name: zentaopro url: jdbc:mysql://127.0.0.1:3306/zentaopro?useUnicode=true&characterEncoding=utf-8&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC&zeroDateTimeBehavior=convertToNull username: root password: root
主數(shù)據(jù)源配置
注意,配置類需要對(duì)DataSource、DataSourceTransactionManager、SqlSessionFactory 、SqlSessionTemplate四個(gè)數(shù)據(jù)項(xiàng)進(jìn)行配置;DataSource類型需要引入javax.sql.DataSource;當(dāng)系統(tǒng)中有多個(gè)數(shù)據(jù)源時(shí),必須有一個(gè)數(shù)據(jù)源為主數(shù)據(jù)源,使用@Primary修飾。
@MapperScan對(duì)指定dao包建立映射,確保在多個(gè)數(shù)據(jù)源下,自動(dòng)選擇合適的數(shù)據(jù)源,而在service層里不需要做特殊說(shuō)明。
@Configuration @MapperScan(basePackages = "cmmi.dao.base", sqlSessionTemplateRef = "baseSqlSessionTemplate") public class BaseDataSourceConfig { @Bean(name = "baseDataSource") @ConfigurationProperties(prefix = "spring.datasource.base") @Primary public DataSource setDataSource() { return DataSourceBuilder.create().build(); } @Bean(name = "baseTransactionManager") @Primary public DataSourceTransactionManager setTransactionManager(@Qualifier("baseDataSource") DataSource dataSource) { return new DruidDataSource(); } @Bean(name = "baseSqlSessionFactory") @Primary public SqlSessionFactory setSqlSessionFactory(@Qualifier("baseDataSource") DataSource dataSource) throws Exception { SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setDataSource(dataSource); bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/base/*.xml")); return bean.getObject(); } @Bean(name = "baseSqlSessionTemplate") @Primary public SqlSessionTemplate setSqlSessionTemplate(@Qualifier("baseSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception { return new SqlSessionTemplate(sqlSessionFactory); } }
從數(shù)據(jù)源配置
@Configuration @MapperScan(basePackages = "cmmi.dao.zentao", sqlSessionTemplateRef = "zentaoSqlSessionTemplate") public class ZentaoDataSourceConfig { @Bean(name = "zentaoDataSource") @ConfigurationProperties(prefix = "spring.datasource.zentao") public DataSource setDataSource() { return new DruidDataSource(); } @Bean(name = "zentaoTransactionManager") public DataSourceTransactionManager setTransactionManager(@Qualifier("zentaoDataSource") DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } @Bean(name = "zentaoSqlSessionFactory") public SqlSessionFactory setSqlSessionFactory(@Qualifier("zentaoDataSource") DataSource dataSource) throws Exception { SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setDataSource(dataSource); bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/zentao/*.xml")); return bean.getObject(); } @Bean(name = "zentaoSqlSessionTemplate") public SqlSessionTemplate setSqlSessionTemplate(@Qualifier("zentaoSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception { return new SqlSessionTemplate(sqlSessionFactory); } }
使用dao
這里只需要正常使用dao就可以了,spring會(huì)根據(jù)數(shù)據(jù)源配置的映射自動(dòng)選擇相應(yīng)數(shù)據(jù)源,而不需要在service做特殊說(shuō)明。
@Service public class TestService { private final ZtUserMapper ztUserMapper; private final LevelDic levelDic; @Autowired public TestService(ZtUserMapper ztUserMapper, LevelDic levelDic) { this.ztUserMapper = ztUserMapper; this.levelDic = levelDic; } public void test() { ztUserMapper.selectByPrimaryKey(1); levelDic.setDicId(new Integer(1).byteValue()); } }
日志
o.a.c.c.C.[Tomcat].[localhost].[/cmmi] : Initializing Spring FrameworkServlet ‘dispatcherServlet'
o.s.web.servlet.DispatcherServlet : FrameworkServlet ‘dispatcherServlet': initialization started
o.s.web.servlet.DispatcherServlet : FrameworkServlet ‘dispatcherServlet': initialization completed in 23 ms
com.alibaba.druid.pool.DruidDataSource : {dataSource-1,cmmi} inited
com.alibaba.druid.pool.DruidDataSource : {dataSource-2,zentaopro} inited
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
idea中一鍵自動(dòng)生成序列化serialVersionUID方式
這篇文章主要介紹了idea中一鍵自動(dòng)生成序列化serialVersionUID方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-09-09java實(shí)現(xiàn)cassandra高級(jí)操作之分頁(yè)實(shí)例(有項(xiàng)目具體需求)
這篇文章主要介紹了java實(shí)現(xiàn)cassandra高級(jí)操作之分頁(yè)實(shí)例(有項(xiàng)目具體需求),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2017-04-04聊聊Spring data jpa @query使用原生SQl,需要注意的坑
這篇文章主要介紹了Spring data jpa@query使用原生SQl,需要注意的坑,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08java.lang.UnsupportedOperationException的問(wèn)題解決
本文主要介紹了java.lang.UnsupportedOperationException的問(wèn)題解決,該錯(cuò)誤表示調(diào)用的方法不被支持或不可用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2024-07-07關(guān)于Spring?Boot內(nèi)存泄露排查的記錄
這篇文章主要介紹了關(guān)于Spring?Boot內(nèi)存泄露排查的記錄,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-06-06Java使用CountDownLatch實(shí)現(xiàn)網(wǎng)絡(luò)同步請(qǐng)求的示例代碼
CountDownLatch 是一個(gè)同步工具類,用來(lái)協(xié)調(diào)多個(gè)線程之間的同步,它能夠使一個(gè)線程在等待另外一些線程完成各自工作之后,再繼續(xù)執(zhí)行。被將利用CountDownLatch實(shí)現(xiàn)網(wǎng)絡(luò)同步請(qǐng)求,異步同時(shí)獲取商品信息組裝,感興趣的可以了解一下2023-01-01SpringCloud 服務(wù)負(fù)載均衡和調(diào)用 Ribbon、OpenFeign的方法
這篇文章主要介紹了SpringCloud 服務(wù)負(fù)載均衡和調(diào)用 Ribbon、OpenFeign的方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-09-09Java String不可變性實(shí)現(xiàn)原理解析
這篇文章主要介紹了Java String不可變性實(shí)現(xiàn)原理解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-04-04