赞
踩
最近因为项目可能要用到多数据源,所以最近研究了下动态切换数据源,在spring2.0以后增加了AbstractRoutingDataSource 来实现对数据源的操作。
对数据源进行切换 继承扩展AbstractRoutingDataSource这个抽象类 ,根据提供的键值切换对应的数据源,下面我们来看下这个类
public abstract class AbstractRoutingDataSourceextends AbstractDataSourceimplements InitializingBean {
@Nullable
private Map targetDataSources; //存放所有的数据源的map 根据key 值可获取对应的数据源
@Nullable
private Object defaultTargetDataSource; //默认的数据源
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的时候执行,可以针对某个具体的bean进行配置。afterPropertiesSet 必须实现 InitializingBean接口。实现 InitializingBean接口必须实现afterPropertiesSet方法。方法体是将数据源分别进行复制到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);
}
}
// 先调用determineTargetDataSource()方法返回DataSource在进行getConnection()。
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();//这里就是根据 determineCurrentLookupKey获取相应的key 下面再根据key值进行获取相应的数据源.**
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方法
数据源配置:
spring:
#数据源配置
datasource:
common:
dbconfig:
minIdle: 5
maxActive: 20
# 配置获取连接等待超时的时间
maxWait: 60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
timeBetweenEvictionRunsMillis: 60000
# ? 配置一个连接在池中最小生存的时间,单位是毫秒
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
# 打开PSCache,并且指定每个连接上PSCache的大小
poolPreparedStatements: true
maxPoolPreparedStatementPerConnectionSize: 20
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'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#指定初始化数据源,是否用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
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
# 打开PSCache,并且指定每个连接上PSCache的大小
poolPreparedStatements: true
maxPoolPreparedStatementPerConnectionSize: 20
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
spring.datasource.filters: stat,wall,log4j
# 通过connectProperties属性来打开mergeSql功能;慢SQL记录
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
下面创建注册类,实现数据源的注册和初始化
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;
/**
* 动态数据源注册
* @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";
// 数据源配置信息
private PropertyValues dataSourcePropertyValues;
// 默认数据源
private DataSource defaultDataSource;
// 动态数据源
private Map<String, DataSource> dynamicDataSources = new HashMap<>();
/**
* 加载多数据源配置
* @param env
*/
@Override
public void setEnvironment(Environment env) {
//获取数据源配置 的节点
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");
//数据源存放的属性
String dsPrefixs = parentMap.get("names").toString();
//遍历生成相应的数据源并存储
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);
}
// 设置默认数据源
if ("base".equals(dsPrefix)) {
defaultDataSource = ds;
} else {
dynamicDataSources.put(dsPrefix, ds);
}
//初始化数据源
dataBinder(ds, env,dsPrefix);
}
}
/**
* 初始化数据源
* @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的监控统计功能 stat表示sql合并,wall表示防御SQL注入攻击
datasource.setFilters("stat,wall");
} catch (SQLException e) {
e.printStackTrace();
}
return datasource;
}
/**
* 加载数据源配置信息
* @param dataSource
* @param env
*/
private void dataBinder(DataSource dataSource, Environment env,String defix) {
Binder binder = Binder.get(env);
binder.bind( defix,Bindable.ofInstance(dataSource));
}
/**
* 注册数据源been
* @param importingClassMetadata
* @param registry
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
// 将主数据源添加到更多数据源中
targetDataSources.put("dataSource", defaultDataSource);
// 添加更多数据源
targetDataSources.putAll(dynamicDataSources);
// 创建动态数据源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 容器中
为了方便使用我们运用注解的形式
/**
* 指定使用哪个数据源的注解
* Created by liqun on 2018/5/16.
*/
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface TargetDataSource{
Stringname();
}
拦截注解,设置相应的数据源,使用完成后清楚设置的数据源
/**
* 利用aop原理实现切换数据源
* Created by liqun on 2018/5/16.
*/
@Aspect
@Component
public class TargetDataSourceAspect {
/**
* 根据@TargetDataSource的name值设置不同的DataSource
* @param joinPoint
* @param targetDataSource
*/
@Before("@annotation(targetDataSource)")
public void changeDataSource(JoinPoint joinPoint,TargetDataSource targetDataSource){
DynamicDataSource.setDataSourceKey(targetDataSource.name());
}
/**
* 方法执行完之后清楚当前数据源,让其使用默认数据源
* @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一样调用封装好的方法了
“`
@Mapper
public interface UserDaoextends BaseDao {
}
”’
下面service的调用
”’
@Service
public class UserService {
@Autowired
private UserDaouserDao;
@TargetDataSource(name ="test")
public UsergetUser(int id) {
return userDao.selectByPrimaryKey(id);
}
public ListgetUsers() {
return userDao.selectAll();
}
}
”’
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。