因為項目必須要用到多數(shù)據(jù)源耳鸯,所以最近研究了下動態(tài)切換數(shù)據(jù)源,在spring2.0以后增加了AbstractRoutingDataSource 來實現(xiàn)對數(shù)據(jù)源的操作湿蛔。
對數(shù)據(jù)源進行切換 繼承擴展AbstractRoutingDataSource這個抽象類 ,根據(jù)提供的鍵值切換對應(yīng)的數(shù)據(jù)源,下面我們來看下這個類
public abstract class AbstractRoutingDataSourceextends AbstractDataSourceimplements InitializingBean {
@Nullable
private Map targetDataSources; //存放所有的數(shù)據(jù)源的map 根據(jù)key 值可獲取對應(yīng)的數(shù)據(jù)源
@Nullable
private Object defaultTargetDataSource; //默認的數(shù)據(jù)源
private boolean lenientFallback =true;
private DataSourceLookup dataSourceLookup =new JndiDataSourceLookup();
@Nullable
private Map resolvedDataSources;
@Nullable
private DataSource resolvedDefaultDataSource;
public AbstractRoutingDataSource() {
}
public void setTargetDataSources(Map targetDataSources) {
this.targetDataSources = targetDataSources;
}
public void setDefaultTargetDataSource(Object defaultTargetDataSource) {
this.defaultTargetDataSource = defaultTargetDataSource;
}
public void setLenientFallback(boolean lenientFallback) {
this.lenientFallback = lenientFallback;
}
public void setDataSourceLookup(@Nullable DataSourceLookup dataSourceLookup) {
this.dataSourceLookup = (DataSourceLookup)(dataSourceLookup !=null?dataSourceLookup:new JndiDataSourceLookup());
}
// 初始化bean的時候執(zhí)行,可以針對某個具體的bean進行配置县爬。afterPropertiesSet 必須實現(xiàn) InitializingBean接口阳啥。實現(xiàn) InitializingBean接口必須實現(xiàn)afterPropertiesSet方法。方法體是將數(shù)據(jù)源分別進行復制到resolvedDataSources和resolvedDefaultDataSource中
public void afterPropertiesSet() {
if(this.targetDataSources ==null) {
throw new IllegalArgumentException("Property 'targetDataSources' is required");
}else {
this.resolvedDataSources =new HashMap(this.targetDataSources.size());
this.targetDataSources.forEach((key, value) -> {
Object lookupKey =this.resolveSpecifiedLookupKey(key);
DataSource dataSource =this.resolveSpecifiedDataSource(value);
this.resolvedDataSources.put(lookupKey, dataSource);
});
if(this.defaultTargetDataSource !=null) {
this.resolvedDefaultDataSource =this.resolveSpecifiedDataSource(this.defaultTargetDataSource);
}
}
}
protected Object resolveSpecifiedLookupKey(Object lookupKey) {
return lookupKey;
}
protected DataSource resolveSpecifiedDataSource(Object dataSource)throws IllegalArgumentException {
if(dataSourceinstanceof DataSource) {
return (DataSource)dataSource;
}else if(dataSourceinstanceof String) {
return this.dataSourceLookup.getDataSource((String)dataSource);
}else {
throw new IllegalArgumentException("Illegal data source value - only [javax.sql.DataSource] and String supported: " + dataSource);
}
}
// 先調(diào)用determineTargetDataSource()方法返回DataSource在進行g(shù)etConnection()捌省。
public Connection getConnection()throws SQLException {
return this.determineTargetDataSource().getConnection();
}
public Connection getConnection(String username, String password)throws SQLException {
return this.determineTargetDataSource().getConnection(username, password);
}
public T unwrap(Class iface)throws SQLException {
return iface.isInstance(this)?this:this.determineTargetDataSource().unwrap(iface);
}
public boolean isWrapperFor(Class iface)throws SQLException {
return iface.isInstance(this) ||this.determineTargetDataSource().isWrapperFor(iface);
}
protected DataSource determineTargetDataSource() {
Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
** Object lookupKey =this.determineCurrentLookupKey();//這里就是根據(jù) determineCurrentLookupKey獲取相應(yīng)的key 下面再根據(jù)key值進行獲取相應(yīng)的數(shù)據(jù)源.**
DataSource dataSource = (DataSource)this.resolvedDataSources.get(lookupKey);
if(dataSource ==null && (this.lenientFallback || lookupKey ==null)) {
dataSource =this.resolvedDefaultDataSource;
}
if(dataSource ==null) {
throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey +"]");
}else {
return dataSource;
}
}
//重寫此方法就可以進行
@Nullable
protected abstract Object determineCurrentLookupKey();
}
定義DynamicDataSource 重寫determineCurrentLookupKey方法
image
數(shù)據(jù)源配置:
spring:
#數(shù)據(jù)源配置
datasource:
common:
dbconfig:
minIdle: 5
maxActive: 20
# 配置獲取連接等待超時的時間
maxWait: 60000
# 配置間隔多久才進行一次檢測苫纤,檢測需要關(guān)閉的空閑連接,單位是毫秒
timeBetweenEvictionRunsMillis: 60000
# ? 配置一個連接在池中最小生存的時間纲缓,單位是毫秒
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
# 打開PSCache卷拘,并且指定每個連接上PSCache的大小
poolPreparedStatements: true
maxPoolPreparedStatementPerConnectionSize: 20
# 配置監(jiān)控統(tǒng)計攔截的filters,去掉后監(jiān)控界面sql無法統(tǒng)計祝高,'wall'用于防火墻
spring.datasource.filters: stat,wall,log4j
# 通過connectProperties屬性來打開mergeSql功能栗弟;慢SQL記錄
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
names: base,bloodsugar
base:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
initialize: true#指定初始化數(shù)據(jù)源,是否用data.sql來初始化工闺,默認: true
name: cmmi
url: jdbc:mysql://192.168.2.13:3306/api?useUnicode=true&characterEncoding=utf-8&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC&zeroDateTimeBehavior=convertToNull
username: root
password: eeesys
minIdle: 5
maxActive: 20
# 配置獲取連接等待超時的時間
maxWait: 60000
# 配置間隔多久才進行一次檢測乍赫,檢測需要關(guān)閉的空閑連接,單位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一個連接在池中最小生存的時間陆蟆,單位是毫秒
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
# 打開PSCache雷厂,并且指定每個連接上PSCache的大小
poolPreparedStatements: true
maxPoolPreparedStatementPerConnectionSize: 20
# 配置監(jiān)控統(tǒng)計攔截的filters,去掉后監(jiān)控界面sql無法統(tǒng)計叠殷,'wall'用于防火墻
spring.datasource.filters: stat,wall,log4j
# 通過connectProperties屬性來打開mergeSql功能改鲫;慢SQL記錄
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
下面創(chuàng)建注冊類,實現(xiàn)數(shù)據(jù)源的注冊和初始化
package com.txby.common.mybatis.DataSource;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
import com.alibaba.druid.pool.DruidDataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.boot.bind.RelaxedDataBinder;
import org.springframework.boot.bind.RelaxedPropertyResolver;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotationMetadata;
/**
* 動態(tài)數(shù)據(jù)源注冊
* @author liqun
* @data 2018-05-16
*/
public class DynamicDataSourceRegister implements ImportBeanDefinitionRegistrar, EnvironmentAware {
private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceRegister.class);
private static final String prefix = "spring.datasource";
// 數(shù)據(jù)源配置信息
private PropertyValues dataSourcePropertyValues;
// 默認數(shù)據(jù)源
private DataSource defaultDataSource;
// 動態(tài)數(shù)據(jù)源
private Map<String, DataSource> dynamicDataSources = new HashMap<>();
/**
* 加載多數(shù)據(jù)源配置
* @param env
*/
@Override
public void setEnvironment(Environment env) {
//獲取數(shù)據(jù)源配置 的節(jié)點
Binder binder = Binder.get(env);
Map parentMap = binder.bind(prefix, Map.class).get();
Map common = (Map<String, Object>)((Map<String, Object>) parentMap.get("common")).get("dbconfig");
//數(shù)據(jù)源存放的屬性
String dsPrefixs = parentMap.get("names").toString();
//遍歷生成相應(yīng)的數(shù)據(jù)源并存儲
for (String dsPrefix : dsPrefixs.split(",")) {
Map<String,Object> map = (Map<String, Object>) parentMap.get(dsPrefix);
DataSource ds = null;
if(map.get("minIdle") == null ){
ds = initDataSource(map,common );
}else {
ds = initDataSource(map,null);
}
// 設(shè)置默認數(shù)據(jù)源
if ("base".equals(dsPrefix)) {
defaultDataSource = ds;
} else {
dynamicDataSources.put(dsPrefix, ds);
}
//初始化數(shù)據(jù)源
dataBinder(ds, env,dsPrefix);
}
}
/**
* 初始化數(shù)據(jù)源
* @param map
* @return
*/
public DataSource initDataSource(Map<String, Object> map ,Map<String, Object> commonMap ) {
String driverClassName = map.get("driver-class-name").toString();
String url = map.get("url").toString();
String username = map.get("username").toString();
String password = map.get("password").toString();
Integer minIdle = 0 ;
Integer maxActive =0;
Long maxWait;
Long minEvictableIdleTimeMillis;
if(map.get("minIdle") == null ){
minIdle = Integer.parseInt(commonMap.get("minIdle").toString());
maxActive = Integer.parseInt(commonMap.get("maxActive").toString());
maxWait = Long.parseLong(commonMap.get("maxWait").toString());
minEvictableIdleTimeMillis = Long.parseLong(commonMap.get("minEvictableIdleTimeMillis").toString());
}else{
minIdle = Integer.parseInt(map.get("minIdle").toString());
maxActive = Integer.parseInt(map.get("maxActive").toString());
maxWait = Long.parseLong(map.get("maxWait").toString());
minEvictableIdleTimeMillis = Long.parseLong(map.get("minEvictableIdleTimeMillis").toString());
}
DruidDataSource datasource = new DruidDataSource();
datasource.setUrl(url);
datasource.setDriverClassName(driverClassName);
datasource.setUsername(username);
datasource.setPassword(password);
datasource.setMinIdle(minIdle);
datasource.setMaxWait(maxWait);
datasource.setMaxActive(maxActive);
datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
try {
//開啟Druid的監(jiān)控統(tǒng)計功能 stat表示sql合并,wall表示防御SQL注入攻擊
datasource.setFilters("stat,wall");
} catch (SQLException e) {
e.printStackTrace();
}
return datasource;
}
/**
* 加載數(shù)據(jù)源配置信息
* @param dataSource
* @param env
*/
private void dataBinder(DataSource dataSource, Environment env,String defix) {
Binder binder = Binder.get(env);
binder.bind( defix,Bindable.ofInstance(dataSource));
}
/**
* 注冊數(shù)據(jù)源been
* @param importingClassMetadata
* @param registry
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
// 將主數(shù)據(jù)源添加到更多數(shù)據(jù)源中
targetDataSources.put("dataSource", defaultDataSource);
// 添加更多數(shù)據(jù)源
targetDataSources.putAll(dynamicDataSources);
// 創(chuàng)建動態(tài)數(shù)據(jù)源DynamicDataSource
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(DynamicDataSource.class);
beanDefinition.setSynthetic(true);
MutablePropertyValues mpv = beanDefinition.getPropertyValues();
mpv.addPropertyValue("defaultTargetDataSource", defaultDataSource);
mpv.addPropertyValue("targetDataSources", targetDataSources);
registry.registerBeanDefinition("dataSource", beanDefinition);
}
}
啟動類中加 @Import({DynamicDataSourceRegister.class}) 把注冊類注入spring 容器中
為了方便使用我們運用注解的形式
/**
* 指定使用哪個數(shù)據(jù)源的注解
* Created by liqun on 2018/5/16.
*/
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface TargetDataSource{
Stringname();
}
攔截注解,設(shè)置相應(yīng)的數(shù)據(jù)源林束,使用完成后清楚設(shè)置的數(shù)據(jù)源
/**
* 利用aop原理實現(xiàn)切換數(shù)據(jù)源
* Created by liqun on 2018/5/16.
*/
@Aspect
@Component
public class TargetDataSourceAspect {
/**
* 根據(jù)@TargetDataSource的name值設(shè)置不同的DataSource
* @param joinPoint
* @param targetDataSource
*/
@Before("@annotation(targetDataSource)")
public void changeDataSource(JoinPoint joinPoint,TargetDataSource targetDataSource){
DynamicDataSource.setDataSourceKey(targetDataSource.name());
}
/**
* 方法執(zhí)行完之后清楚當前數(shù)據(jù)源像棘,讓其使用默認數(shù)據(jù)源
* @param joinPoint
* @param targetDataSource
*/
@After("@annotation(targetDataSource)")
public void restoreDataSource(JoinPoint joinPoint,TargetDataSource targetDataSource){
DynamicDataSource.clearDataSourceKey();
}
}
到此就可以在Service中引用了
@TargetDataSource(name ="test")
public UsergetUser(int id) {
return userDao.selectByPrimaryKey(id);
}
下面我們配置下通用mapper
pom.xml
tk.mybatis
mapper
3.3.7
配置通用的mapper
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import tk.mybatis.spring.mapper.MapperScannerConfigurer;
import java.util.Properties;
@Configuration
public class MyBatisMapperScannerConfig {
@Bean
public MapperScannerConfigurermapperScannerConfigurer() {
MapperScannerConfigurer mapperScannerConfigurer =new MapperScannerConfigurer();
mapperScannerConfigurer.setSqlSessionFactoryBeanName("sqlSessionFactory");
mapperScannerConfigurer.setBasePackage("com.txby.web.dao.base");//掃描該路徑下的dao
Properties properties =new Properties();
properties.setProperty("mappers", "com.txby.common.mybatis.BaseDao");//通用dao
properties.setProperty("notEmpty", "false");
properties.setProperty("IDENTITY", "MYSQL");
mapperScannerConfigurer.setProperties(properties);
return mapperScannerConfigurer;
}
}
通用mapper 的dao
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import tk.mybatis.spring.mapper.MapperScannerConfigurer;
import java.util.Properties;
@Configuration
public class MyBatisMapperScannerConfig {
@Bean
public MapperScannerConfigurermapperScannerConfigurer() {
MapperScannerConfigurer mapperScannerConfigurer =new MapperScannerConfigurer();
mapperScannerConfigurer.setSqlSessionFactoryBeanName("sqlSessionFactory");
mapperScannerConfigurer.setBasePackage("com.txby.web.dao.base");//掃描該路徑下的dao
Properties properties =new Properties();
properties.setProperty("mappers", "com.txby.common.mybatis.BaseDao");//通用dao
properties.setProperty("notEmpty", "false");
properties.setProperty("IDENTITY", "MYSQL");
mapperScannerConfigurer.setProperties(properties);
return mapperScannerConfigurer;
}
}
UserDao繼承通用dao 就可以像JPA一樣調(diào)用封裝好的方法了
@Mapper
public interface UserDaoextends BaseDao {
}
'''
下面service的調(diào)用
'''
@Service
public class UserService {
@Autowired
private UserDaouserDao;
@TargetDataSource(name ="test")
public UsergetUser(int id) {
return userDao.selectByPrimaryKey(id);
}
public ListgetUsers() {
return userDao.selectAll();
}
}
'''