在正常项目中数据源是可以配置多个数据源使用切面(aop+注解)实现使用时的切换即当前线程中切换。还有就是在配置文件配置多个数据源。我这里想要实现的时同一个数据库 不同类型的切换比如 mysql 和sql server之间的切换、
<dependency>
<groupId>org.yamlgroupId>
<artifactId>snakeyamlartifactId>
<version>1.23version>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.1.2version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druid-spring-boot-starterartifactId>
<version>1.1.18version>
dependency>
其他springboot的依赖我就不贴了。。
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/库?characterEncoding=utf8&useSSL=false&serverTimezone=Hongkong&allowPublicKeyRetrieval=true
username: root
password: '123456'
driverClassName: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
mvc:
favicon:
enabled: false
view:
prefix: /WEB-INF/jsp/
suffix: .jsp
static-path-pattern: /*
devtools:
restart:
enabled: true
additional-paths: main/java
exclude: WEB-INF/*
mybatis-plus:
type-aliases-package: 实体类包路径 com/自己的/mapper/
mapper-locations: classpath*:com/自己的/mapper/*.xml
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
pagehelper:
helperDialect: mysql
reasonable: true
supportMethodsArguments: true
params: count=countSql
logging:
file: C:\\springboot\\info.log
server:
port: 18080
@Configuration
@EnableTransactionManagement
public class DruidDBConfig {
private final Logger log = LoggerFactory.getLogger(getClass());
// adi数据库连接信息
@Value("${spring.datasource.url}")
private String dbUrl;
@Value("${spring.datasource.username}")
private String username;
@Value("${spring.datasource.password}")
private String password;
@Value("${spring.datasource.driverClassName}")
private String driverClassName;
// 连接池连接信息
@Value("${spring.datasource.initialSize}")
private int initialSize;
@Value("${spring.datasource.minIdle}")
private int minIdle;
@Value("${spring.datasource.maxActive}")
private int maxActive;
@Value("${spring.datasource.maxWait}")
private int maxWait;
@Bean // 声明其为Bean实例
@Primary // 在同样的DataSource中,首先使用被标注的DataSource
@Qualifier("adiDataSource")
public DataSource dataSource() throws SQLException {
DruidDataSource datasource = new DruidDataSource();
// 基础连接信息
datasource.setUrl(this.dbUrl);
datasource.setUsername(username);
datasource.setPassword(password);
datasource.setDriverClassName(driverClassName);
// 连接池连接信息
datasource.setInitialSize(initialSize);
datasource.setMinIdle(minIdle);
datasource.setMaxActive(maxActive);
datasource.setMaxWait(maxWait);
datasource.setPoolPreparedStatements(true); //是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。
datasource.setMaxPoolPreparedStatementPerConnectionSize(50);
datasource.setConnectionProperties("oracle.net.CONNECT_TIMEOUT=6000;oracle.jdbc.ReadTimeout=60000");//对于耗时长的查询sql,会受限于ReadTimeout的控制,单位毫秒
datasource.setTestOnBorrow(true); //申请连接时执行validationQuery检测连接是否有效,这里建议配置为TRUE,防止取到的连接不可用
datasource.setTestWhileIdle(true);//建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
String validationQuery = "select 1 from dual";
datasource.setValidationQuery(validationQuery); //用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。
datasource.setFilters("stat,wall");//属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有:监控统计用的filter:stat日志用的filter:log4j防御sql注入的filter:wall
datasource.setTimeBetweenEvictionRunsMillis(60000); //配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
datasource.setMinEvictableIdleTimeMillis(180000); //配置一个连接在池中最小生存的时间,单位是毫秒,这里配置为3分钟180000
datasource.setKeepAlive(true); //打开druid.keepAlive之后,当连接池空闲时,池中的minIdle数量以内的连接,空闲时间超过minEvictableIdleTimeMillis,则会执行keepAlive操作,即执行druid.validationQuery指定的查询SQL,一般为select * from dual,只要minEvictableIdleTimeMillis设置的小于防火墙切断连接时间,就可以保证当连接空闲时自动做保活检测,不会被防火墙切断
datasource.setRemoveAbandoned(true); //是否移除泄露的连接/超过时间限制是否回收。
datasource.setRemoveAbandonedTimeout(3600); //泄露连接的定义时间(要超过最大事务的处理时间);单位为秒。这里配置为1小时
datasource.setLogAbandoned(true); 移除泄露连接发生是是否记录日志
return datasource;
}
@Bean(name = "dynamicDataSource")
@Qualifier("dynamicDataSource")
public DynamicDataSource dynamicDataSource() throws SQLException {
DynamicDataSource dynamicDataSource = new DynamicDataSource();
dynamicDataSource.setDebug(false);
//配置缺省的数据源
dynamicDataSource.setDefaultTargetDataSource(dataSource());
Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
targetDataSources.put("adiDataSource", dataSource());
dynamicDataSource.setTargetDataSources(targetDataSources);
return dynamicDataSource;
}
/**
* SqlSessionFactorybean 整合必须替换MybatisSqlSessionFactoryBean 重点
* @return
* @throws Exception
*/
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
MybatisSqlSessionFactoryBean sqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dynamicDataSource());
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:*.xml"));
sqlSessionFactoryBean.setTypeAliasesPackage("实体类包名路径");
//导入mybatis配置
MybatisConfiguration configuration = new MybatisConfiguration();
configuration.setJdbcTypeForNull(JdbcType.NULL);
configuration.setMapUnderscoreToCamelCase(true);
configuration.setCacheEnabled(false);
sqlSessionFactoryBean.setConfiguration(configuration);
return sqlSessionFactoryBean.getObject();
}
@Bean
public SqlSessionTemplate sqlSessionTemplate() throws Exception {
return new SqlSessionTemplate(sqlSessionFactory());
}
public class DynamicDataSource extends AbstractRoutingDataSource{
private boolean debug = true;
private final Logger log = LoggerFactory.getLogger(getClass());
private Map<Object, Object> dynamicTargetDataSources;
private Object dynamicDefaultTargetDataSource;
/**
* 这个方法会去检查数据源适用 aop的话这里是用不到的
* 因为使用的 是修改mybatis的数据源
*/
@Override
protected Object determineCurrentLookupKey() {
// String datasource = DBContextHolder.getDataSource();
// System.err.println(datasource);
// if (!StringUtils.isEmpty(datasource)) {
// Map
// if (dynamicTargetDataSources2.containsKey(datasource)) {
// log.info("---当前数据源:" + datasource + "---");
// } else {
// log.info("不存在的数据源:");
// return null;
throw new ADIException("不存在的数据源:"+datasource,500);
// }
// } else {
// log.info("---当前数据源:默认数据源---");
// }
return "";
}
@Override
public void setTargetDataSources(Map<Object, Object> targetDataSources) {
super.setTargetDataSources(targetDataSources);
this.dynamicTargetDataSources = targetDataSources;
}
// 创建数据源
public boolean createDataSource(String key, String driveClass, String url, String username, String password, String databasetype) {
try {
try { // 排除连接不上的错误
Class.forName(driveClass);
DriverManager.getConnection(url, username, password);// 相当于连接数据库
} catch (Exception e) {
return false;
}
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setName(key);
druidDataSource.setDriverClassName(driveClass);
druidDataSource.setUrl(url);
druidDataSource.setUsername(username);
druidDataSource.setPassword(password);
druidDataSource.setInitialSize(50); //初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时
druidDataSource.setMaxActive(200); //最大连接池数量
druidDataSource.setMaxWait(60000); //获取连接时最大等待时间,单位毫秒。当链接数已经达到了最大链接数的时候,应用如果还要获取链接就会出现等待的现象,等待链接释放并回到链接池,如果等待的时间过长就应该踢掉这个等待,不然应用很可能出现雪崩现象
druidDataSource.setMinIdle(40); //最小连接池数量
String validationQuery = "select 1 from dual";
druidDataSource.setTestOnBorrow(true); //申请连接时执行validationQuery检测连接是否有效,这里建议配置为TRUE,防止取到的连接不可用
druidDataSource.setTestWhileIdle(true);//建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
druidDataSource.setValidationQuery(validationQuery); //用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。
druidDataSource.setFilters("stat");//属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有:监控统计用的filter:stat日志用的filter:log4j防御sql注入的filter:wall
druidDataSource.setTimeBetweenEvictionRunsMillis(60000); //配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
druidDataSource.setMinEvictableIdleTimeMillis(180000); //配置一个连接在池中最小生存的时间,单位是毫秒,这里配置为3分钟180000
druidDataSource.setKeepAlive(true); //打开druid.keepAlive之后,当连接池空闲时,池中的minIdle数量以内的连接,空闲时间超过minEvictableIdleTimeMillis,则会执行keepAlive操作,即执行druid.validationQuery指定的查询SQL,一般为select * from dual,只要minEvictableIdleTimeMillis设置的小于防火墙切断连接时间,就可以保证当连接空闲时自动做保活检测,不会被防火墙切断
druidDataSource.setRemoveAbandoned(true); //是否移除泄露的连接/超过时间限制是否回收。
druidDataSource.setRemoveAbandonedTimeout(3600); //泄露连接的定义时间(要超过最大事务的处理时间);单位为秒。这里配置为1小时
druidDataSource.setLogAbandoned(true); 移除泄露连接发生是是否记录日志
druidDataSource.init();
// Map
// dynamicTargetDataSources2.put(key, createDataSource);// 加入map
this.dynamicTargetDataSources.put(key, druidDataSource);
setTargetDataSources(this.dynamicTargetDataSources);// 将map赋值给父类的TargetDataSources
// setTargetDataSources(dynamicTargetDataSources2);// 将map赋值给父类的TargetDataSources
super.afterPropertiesSet();// 将TargetDataSources中的连接信息放入resolvedDataSources管理
log.info(key+"数据源初始化成功");
//log.info(key+"数据源的概况:"+druidDataSource.dump());
/**
** 修改mybatis的数据源
*/
//修改MyBatis的数据源
SqlSessionFactory SqlSessionFactory = (SqlSessionFactory) SpringContextUtils.getBean(SqlSessionFactory.class);
Environment environment =SqlSessionFactory.getConfiguration().getEnvironment();
Field dataSourceField = environment.getClass().getDeclaredField("dataSource");
dataSourceField.setAccessible(true);//跳过检查
dataSourceField.set(environment,druidDataSource);//修改mybatis的数据源
//修改完成后所有线程使用此数据源
return true;
} catch (Exception e) {
log.error(e + "");
return false;
}
}
// 删除数据源
public boolean delDatasources(String datasourceid) {
Map<Object, Object> dynamicTargetDataSources2 = this.dynamicTargetDataSources;
if (dynamicTargetDataSources2.containsKey(datasourceid)) {
Set<DruidDataSource> druidDataSourceInstances = DruidDataSourceStatManager.getDruidDataSourceInstances();
for (DruidDataSource l : druidDataSourceInstances) {
if (datasourceid.equals(l.getName())) {
dynamicTargetDataSources2.remove(datasourceid);
DruidDataSourceStatManager.removeDataSource(l);
setTargetDataSources(dynamicTargetDataSources2);// 将map赋值给父类的TargetDataSources
super.afterPropertiesSet();// 将TargetDataSources中的连接信息放入resolvedDataSources管理
return true;
}
}
return false;
} else {
return false;
}
}
// 测试数据源连接是否有效
public boolean testDatasource(String key, String driveClass, String url, String username, String password) {
try {
Class.forName(driveClass);
DriverManager.getConnection(url, username, password);
return true;
} catch (Exception e) {
return false;
}
}
/**
* Specify the default target DataSource, if any.
*
* The mapped value can either be a corresponding
* {@link javax.sql.DataSource} instance or a data source name String (to be
* resolved via a {@link #setDataSourceLookup DataSourceLookup}).
*
* This DataSource will be used as target if none of the keyed
* {@link #setTargetDataSources targetDataSources} match the
* {@link #determineCurrentLookupKey()} current lookup key.
*/
@Override
public void setDefaultTargetDataSource(Object defaultTargetDataSource) {
super.setDefaultTargetDataSource(defaultTargetDataSource);
this.dynamicDefaultTargetDataSource = defaultTargetDataSource;
}
/**
* @param debug
* the debug to set
*/
public void setDebug(boolean debug) {
this.debug = debug;
}
/**
* @return the debug
*/
public boolean isDebug() {
return debug;
}
/**
* @return the dynamicTargetDataSources
*/
public Map<Object, Object> getDynamicTargetDataSources() {
return dynamicTargetDataSources;
}
/**
* @param dynamicTargetDataSources
* the dynamicTargetDataSources to set
*/
public void setDynamicTargetDataSources(Map<Object, Object> dynamicTargetDataSources) {
this.dynamicTargetDataSources = dynamicTargetDataSources;
}
/**
* @return the dynamicDefaultTargetDataSource
*/
public Object getDynamicDefaultTargetDataSource() {
return dynamicDefaultTargetDataSource;
}
/**
* @param dynamicDefaultTargetDataSource
* the dynamicDefaultTargetDataSource to set
*/
public void setDynamicDefaultTargetDataSource(Object dynamicDefaultTargetDataSource) {
this.dynamicDefaultTargetDataSource = dynamicDefaultTargetDataSource;
}
public void createDataSourceWithCheck(com.jksd.model.DataSource dataSource) throws Exception {
String datasourceId = dataSource.getDatasourceId();
log.info("准备创建数据源createDataSourceWithCheck"+datasourceId);
Map<Object, Object> dynamicTargetDataSources2 = this.dynamicTargetDataSources;
//判断数据源已经创建这里还是aop会用到的
if (dynamicTargetDataSources2.containsKey(datasourceId)) {
log.info("数据源"+datasourceId+"之前已经创建,准备测试数据源是否正常...");
//DataSource druidDataSource = (DataSource) dynamicTargetDataSources2.get(datasourceId);
DruidDataSource druidDataSource = (DruidDataSource) dynamicTargetDataSources2.get(datasourceId);
boolean rightFlag = true;
Connection connection = null;
try {
log.info(datasourceId+"数据源的概况->当前闲置连接数:"+druidDataSource.getPoolingCount());
long activeCount = druidDataSource.getActiveCount();
log.info(datasourceId+"数据源的概况->当前活动连接数:"+activeCount);
if(activeCount > 0) {
log.info(datasourceId+"数据源的概况->活跃连接堆栈信息:"+druidDataSource.getActiveConnectionStackTrace());
}
log.info("准备获取数据库连接...");
connection = druidDataSource.getConnection();
log.info("数据源"+datasourceId+"正常");
} catch (Exception e) {
log.error(e.getMessage(),e); //把异常信息打印到日志文件
rightFlag = false;
log.info("缓存数据源"+datasourceId+"已失效,准备删除...");
if(delDatasources(datasourceId)) {
log.info("缓存数据源删除成功");
} else {
log.info("缓存数据源删除失败");
}
} finally {
if(null != connection) {
connection.close();
}
}
if(rightFlag) {
log.info("不需要重新创建数据源");
return;
} else {
log.info("准备重新创建数据源...");
createDataSource(dataSource);
log.info("重新创建数据源完成");
}
} else {
//去创建数据源
createDataSource(dataSource);
}
}
private void createDataSource(com.jksd.model.DataSource dataSource) throws Exception {
String datasourceId = dataSource.getDatasourceId();
log.info("准备创建数据源createDataSource***"+datasourceId);
String databasetype = dataSource.getDatabasetype();
String username = dataSource.getUserName();
String password = dataSource.getPassWord();
String url = dataSource.getUrl();
String driveClass = dataSource.getDrverclass();
if(testDatasource(datasourceId,driveClass,url,username,password)) {
boolean result = this.createDataSource(datasourceId, driveClass, url, username, password, databasetype);
if(!result) {
log.error("数据源"+datasourceId+"配置正确,但是创建失败");
}
} else {
log.error("数据源配置有错误");
}
}
@Data
@TableName(value = "tbl_account")
public class DataSources {
@TableId(value = "datasourceId", type = IdType.AUTO)
private String datasourceId;
@TableField(value = "url")
private String url;
@TableField(value = "userName")
private String userName;
@TableField(value = "passWord")
private String passWord;
@TableField(value = "code")
private String code;
@TableField(value = "drverclass")
private String drverclass;
@TableField(value = "databasetype")
private String databasetype;
}
@RequestMapping("updatedatasours")
@ResponseBody
public Map<String,Object> updatedatasours(String type,String ip,String account ,String pwd) throws SQLException{
DataSources d = new DataSources();
if(type.equals("mysql")) {
d.setDatabasetype(type);
d.setDrverclass("com.mysql.cj.jdbc.Driver");
d.setUserName(account);
d.setPassWord(pwd);
d.setUrl("jdbc:mysql://"+ip+":3306/jksd?characterEncoding=utf8&useSSL=false&serverTimezone=Hongkong&allowPublicKeyRetrieval=true");
d.setDatasourceId("dasours1");
}else if(type.equals("MS SQL Server")){
d.setDatabasetype("sqlserver");
d.setDrverclass("com.microsoft.sqlserver.jdbc.SQLServerDriver");
d.setUserName(account);
d.setPassWord(pwd);
d.setUrl("jdbc:sqlserver://"+ip+":1433;DatabaseName=jksd");
d.setDatasourceId("dasours2");
}
try {
dynamicDataSource.createDataSourceWithCheck(d);
//同时去修改配置文件实现下次启动自动加载切换的数据源
YmlUtil.updateYamlFile(d);
//DBContextHolder.setDataSource(d.getDatasourceId());
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.err.println(e.toString());
}
}
public class YmlUpdateUtil {
public static void updateYamlFile(DataSources data) {
String src = "src/main/resources/application.yml";
Yaml yaml = new Yaml();
FileWriter fileWriter = null;
//层级map变量
Map<String, Object> springMap, dataSourceMap, resultMap,helperDialect;
try {
//读取yaml文件,默认返回根目录结构
resultMap = (Map<String, Object>) yaml.load(new FileInputStream(new File(src)));
//get出spring节点数据
springMap = (Map<String, Object>) resultMap.get("spring");
//get出数据库节点数据
dataSourceMap = (Map<String, Object>) springMap.get("datasource");
//修改数据库url,我这个是封装的参数,你们测试可以写死一个值尝试修改即可
dataSourceMap.put("url", data.getUrl());
//登录名
dataSourceMap.put("username",data.getUserName());
//驱动
dataSourceMap.put("driverClassName", data.getDrverclass());
//密码
dataSourceMap.put("password", data.getPassWord());
helperDialect = (Map<String, Object>) resultMap.get("pagehelper");
//分页插件配置也需要修改
helperDialect.put("helperDialect",data.getDatabasetype());
//字符输出
fileWriter = new FileWriter(new File(src));
//用yaml方法把map结构格式化为yaml文件结构
fileWriter.write(yaml.dumpAsMap(resultMap));
//刷新
fileWriter.flush();
//关闭流
fileWriter.close();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("对不起,yaml文件修改失败!");
}
}
实现页面配置连接信息进行数据库的切换
yml工具类是为了实现一次切换后面项目 启动时去加载切换过来的数据源。。。
页面就不贴了 记录下学习感兴趣可以去试试。。。