druid多數(shù)據(jù)源配置+Datasurce動態(tài)切換方式
druid多數(shù)據(jù)源配置+Datasurce動態(tài)切換
AbstractRoutingDataSource 數(shù)據(jù)源動態(tài)切換
spring 使用AbstractRoutingDataSource自定義動態(tài)數(shù)據(jù)源時的事務處理, 需要繼承spring的AbstractRoutingDataSource定義自己的動態(tài)數(shù)據(jù)源,可以根據(jù)需要動態(tài)的切換不同數(shù)據(jù)庫的數(shù)據(jù)源,使用起來非常方便。
public class ChooseDataSource extends AbstractRoutingDataSource {
/**
* 獲取與數(shù)據(jù)源相關的key
* 此key是Map<String,DataSource> resolvedDataSources 中與數(shù)據(jù)源綁定的key值
* 在通過determineTargetDataSource獲取目標數(shù)據(jù)源時使用
*/
@Override
protected Object determineCurrentLookupKey() {
return RouteHolder.getRouteKey();
}
}
通過容器RouteHolder存儲當前線程使用的數(shù)據(jù)源的key
/**
* 保存當前線程數(shù)據(jù)源的key
*/
public class RouteHolder {
private static ThreadLocal<String> routeKey = new ThreadLocal<String>();
/**
* 獲取當前線程的數(shù)據(jù)源路由的key
* @return
*/
public static String getRouteKey()
{
String key = routeKey.get();
return key;
}
/**
* 綁定當前線程數(shù)據(jù)源路由的key
* 在使用完成之后,必須調(diào)用removeRouteKey()方法刪除
* @param key
*/
public static void setRouteKey(String key)
{
routeKey.set(key);
}
/**
* 刪除與當前線程綁定的數(shù)據(jù)源路由的key
*/
public static void removeRouteKey()
{
routeKey.remove();
}
}
使用spring 的aop編程在業(yè)務邏輯方法運行前將當前方法使用數(shù)據(jù)源的key從業(yè)務邏輯方法上自定義注解@DataSource中解析數(shù)據(jù)源key并添加到RouteHolder中
/**
* 執(zhí)行dao方法之前的切面
* 獲取datasource對象之前往RouteHolder中指定當前線程數(shù)據(jù)源路由的key
*
*/
public class DataSourceAspect {
/**
* 在dao層方法之前獲取datasource對象之前在切面中指定當前線程數(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 這個切面類,應該針對的被代理類應該是service的實現(xiàn)類(serviceImpl),因為dao層用的mybatis只有一個dao層的接口,所以放在service上做處理比較好。

業(yè)務邏輯方法
@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
* 編譯器將把注釋記錄在類文件中,在運行時 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ù)包中定義的接口自動生成dao的實現(xiàn)類-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.westone.dao"></property>
</bean>
<!-- 為業(yè)務邏輯層的方法解析@DataSource注解 為當前線程的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>
<!-- 事務管理器配置 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 開啟事務注解驅(qū)動 在業(yè)務邏輯層上使用@Transactional 注解 為業(yè)務邏輯層管理事務-->
<tx:annotation-driven transaction-manager="transactionManager"/>
事務管理配置一定要配置在往RouteHolder中注入數(shù)據(jù)源key之前 否則會報
Could not open JDBC Connection for transaction; nested exception is java.lang.IllegalStateException: Cannot determine target DataSource for lookup key [null] 找不到數(shù)據(jù)源錯誤。
由此就可以根據(jù)方法上的@DataSource(“master”) 注解配置不同的數(shù)據(jù)源key 使用動態(tài)數(shù)據(jù)源。
解釋:
java.lang.reflect.Method.getAnnotation(Class annotationClass)
參數(shù):
annotationClass - Class對象對相應的注解類型,比如Datasource.class 。
返回值:
如果存在于此元素,則返回該元素注解指定的注解對象,否則返回為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();
}
編譯并運行上面的程序,這將產(chǎn)生以下結果
-name: getSampleMethod
value: Sample Method Annotation
getInterfaces()
能夠獲得這個對象所實現(xiàn)的接口
配置多數(shù)據(jù)源并實現(xiàn)Druid自動切換
Spring Boot配置多數(shù)據(jù)源
配置yml文件
這里并沒有對spring.datasource配置數(shù)據(jù)源,因為增加新數(shù)據(jù)源后,系統(tǒng)會覆蓋由spring.datasource自動配置的內(nèi)容。
這里自定義了兩個數(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來初始化,默認: 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ù)源配置
注意,配置類需要對DataSource、DataSourceTransactionManager、SqlSessionFactory 、SqlSessionTemplate四個數(shù)據(jù)項進行配置;DataSource類型需要引入javax.sql.DataSource;當系統(tǒng)中有多個數(shù)據(jù)源時,必須有一個數(shù)據(jù)源為主數(shù)據(jù)源,使用@Primary修飾。
@MapperScan對指定dao包建立映射,確保在多個數(shù)據(jù)源下,自動選擇合適的數(shù)據(jù)源,而在service層里不需要做特殊說明。
@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會根據(jù)數(shù)據(jù)源配置的映射自動選擇相應數(shù)據(jù)源,而不需要在service做特殊說明。
@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
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
idea中一鍵自動生成序列化serialVersionUID方式
這篇文章主要介紹了idea中一鍵自動生成序列化serialVersionUID方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-09-09
java實現(xiàn)cassandra高級操作之分頁實例(有項目具體需求)
這篇文章主要介紹了java實現(xiàn)cassandra高級操作之分頁實例(有項目具體需求),具有一定的參考價值,感興趣的小伙伴們可以參考一下。2017-04-04
聊聊Spring data jpa @query使用原生SQl,需要注意的坑
這篇文章主要介紹了Spring data jpa@query使用原生SQl,需要注意的坑,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-08-08
java.lang.UnsupportedOperationException的問題解決
本文主要介紹了java.lang.UnsupportedOperationException的問題解決,該錯誤表示調(diào)用的方法不被支持或不可用,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2024-07-07
Java使用CountDownLatch實現(xiàn)網(wǎng)絡同步請求的示例代碼
CountDownLatch 是一個同步工具類,用來協(xié)調(diào)多個線程之間的同步,它能夠使一個線程在等待另外一些線程完成各自工作之后,再繼續(xù)執(zhí)行。被將利用CountDownLatch實現(xiàn)網(wǎng)絡同步請求,異步同時獲取商品信息組裝,感興趣的可以了解一下2023-01-01
SpringCloud 服務負載均衡和調(diào)用 Ribbon、OpenFeign的方法
這篇文章主要介紹了SpringCloud 服務負載均衡和調(diào)用 Ribbon、OpenFeign的方法,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-09-09

