springboot项目中使用动态数据源
Posted vwvwvwgwg
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了springboot项目中使用动态数据源相关的知识,希望对你有一定的参考价值。
需求:
已有一个项目是针对某省的业务创建的,目前业务成熟,有其他省份的项目进来,功能和业务相同,需要对不同省份的业务数据分库管理,这样一来不同省份使用多个库,项目就需要使用动态数据源。已知解决方案都是在配置文件中配置多个数据源来切换数据源,考虑扩展和维护麻烦,需要更灵活的方案
实现:
使用AOP切面,根据接口传入的用户标识得到用户属于哪个省份,动态去切换到该省份的数据源。请求处理完毕,在方法结束后将数据源连接关闭,将属于此请求线程中的数据源清空。
代码:
说明:项目中使用的连接池是druid,使用tomcat自带的连接池也可以
1、数据源的配置
spring:
profiles:
active: dev2
datasource:
url: jdbc:mysql://127.0.0.1:3306/%s?useUnicode=true&characterEncoding=utf8
username: root
password: 123456
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
initialSize: 5
minIdle: 5
maxActive: 10
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
testConnectionOnCheckout: false
poolPreparedStatements: true
mybatis:
mapper-locations: classpath:mapper/*.xml
注意:此处数据库名称使用%s占位,不必写死
2、写动态数据源继承druid的DruidDataSource 类,重点是要重写getConnection()方法,因为每次操作数据库都要执行此方法区拿数据源,因此在这里去动态设置,将创建的数据源存到当前线程的threadlocal中,待方法结束记得去关闭连接,不然连接不释放,会耗尽数据库连接。
package com.mmednet.config;
import com.alibaba.druid.pool.DruidConnectionHolder;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import com.alibaba.druid.pool.DruidPooledConnection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import javax.sql.DataSource;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
/**
* Created by meridian on 2018/9/26.
*/
public class DynamicDataSourceOfDruid extends DruidDataSource {
private static HashMap<String, String> dataBaseMap = new HashMap<>();
public static ThreadLocal<DruidDataSource> connectionThreadLocal = new ThreadLocal<>();
/**
* 数据源中配置注入不进来,因此自己写了一个读取property的类去读取连接配置
*/
@Autowired
DruidDataSourceProperty druidDataSourceProperty;
static {
dataBaseMap.put("1","cxy");
dataBaseMap.put("2","cxy1");
}
@Override
public DruidPooledConnection getConnection(){
try {
String db = DataSourceContextHolder.getDB();
if (db==null){
db="1";
}
Properties properties = this.getConnectProperties();
Field[] declaredFields = druidDataSourceProperty.getClass().getDeclaredFields();
for (Field field:declaredFields) {
field.setAccessible(true);
properties.setProperty(field.getName(),(String) field.get(druidDataSourceProperty));
}
String urlFormat = properties.getProperty("url");
String url = String.format(urlFormat,dataBaseMap.get(db));
properties.setProperty("url",url);
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
DruidPooledConnection druidPooledConnection=(DruidPooledConnection)dataSource.getConnection();
DruidPooledConnection connection=(DruidPooledConnection)dataSource.getConnection();
connectionThreadLocal.set((DruidDataSource)dataSource);
return connection;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static void closeConnection(){
DruidDataSource druidDataSource = connectionThreadLocal.get();
if (druidDataSource!=null){
try {
druidDataSource.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
package com.mmednet.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* Created by meridian on 2018/9/26.
*/
@Component
@ConfigurationProperties(prefix = "spring.datasource")
public class DruidDataSourceProperty {
private String url;
private String username;
private String password;
private String type;
private String driverClassName;
private String initialSize;
private String minIdle;
private String maxActive;
private String timeBetweenEvictionRunsMillis;
private String minEvictableIdleTimeMillis;
private String validationQuery;
private String testWhileIdle;
private String testOnBorrow;
private String testOnReturn;
private String testConnectionOnCheckout;
private String poolPreparedStatements;
// private String filters;
// private String connectionProperties;
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getDriverClassName() {
return driverClassName;
}
public void setDriverClassName(String driverClassName) {
this.driverClassName = driverClassName;
}
public String getInitialSize() {
return initialSize;
}
public void setInitialSize(String initialSize) {
this.initialSize = initialSize;
}
public String getMinIdle() {
return minIdle;
}
public void setMinIdle(String minIdle) {
this.minIdle = minIdle;
}
public String getMaxActive() {
return maxActive;
}
public void setMaxActive(String maxActive) {
this.maxActive = maxActive;
}
public String getTimeBetweenEvictionRunsMillis() {
return timeBetweenEvictionRunsMillis;
}
public void setTimeBetweenEvictionRunsMillis(String timeBetweenEvictionRunsMillis) {
this.timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis;
}
public String getMinEvictableIdleTimeMillis() {
return minEvictableIdleTimeMillis;
}
public void setMinEvictableIdleTimeMillis(String minEvictableIdleTimeMillis) {
this.minEvictableIdleTimeMillis = minEvictableIdleTimeMillis;
}
public String getValidationQuery() {
return validationQuery;
}
public void setValidationQuery(String validationQuery) {
this.validationQuery = validationQuery;
}
public String getTestWhileIdle() {
return testWhileIdle;
}
public void setTestWhileIdle(String testWhileIdle) {
this.testWhileIdle = testWhileIdle;
}
public String getTestOnBorrow() {
return testOnBorrow;
}
public void setTestOnBorrow(String testOnBorrow) {
this.testOnBorrow = testOnBorrow;
}
public String getTestOnReturn() {
return testOnReturn;
}
public void setTestOnReturn(String testOnReturn) {
this.testOnReturn = testOnReturn;
}
public String getTestConnectionOnCheckout() {
return testConnectionOnCheckout;
}
public void setTestConnectionOnCheckout(String testConnectionOnCheckout) {
this.testConnectionOnCheckout = testConnectionOnCheckout;
}
public String getPoolPreparedStatements() {
return poolPreparedStatements;
}
public void setPoolPreparedStatements(String poolPreparedStatements) {
this.poolPreparedStatements = poolPreparedStatements;
}
// public String getFilters() {
// return filters;
// }
//
// public void setFilters(String filters) {
// this.filters = filters;
// }
//
// public String getConnectionProperties() {
// return connectionProperties;
// }
//
// public void setConnectionProperties(String connectionProperties) {
// this.connectionProperties = connectionProperties;
// }
}
3、编写springboot的配置类。注意将springboot的默认DataSource要排除掉。自己写了DataSource,还需要重写SqlSessionFactory ,这是Mybatis的配置类,他依赖DataSource,因此要将自己定义的DataSource注入进去
package com.mmednet.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Properties;
/**
* Created by meridian on 2018/9/26.
*/
@Configuration
@MapperScan(value = "sqlSessionFactory")
public class DataSourceConfig {
@Autowired
DataSourceProperty dataSourceProperty;
/**
* 构建自定义的动态数据源DataSource
* @return
*/
@Bean(name = "dynamicDataSource")
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource dataSource(){
return new DynamicDataSourceOfDruid();
}
/**
* 这是使用springboot的默认的tomcat连接池,也可以用
* @param dataSource
* @return
*/
// @Bean(name = "dynamicDataSource")
// @ConfigurationProperties(prefix = "spring.datasource")
// public DataSource dataSource(){
// DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create();
// dataSourceBuilder.type(DynamicDataSource.class);
// dataSourceBuilder.url(dataSourceProperty.getUrl());
// dataSourceBuilder.username(dataSourceProperty.getUsername());
// dataSourceBuilder.password(dataSourceProperty.getPassword());
// dataSourceBuilder.driverClassName(dataSourceProperty.getDriverClassName());
// dataSourceBuilder.type(DynamicDataSource.class);
// DataSource build = dataSourceBuilder.build();
// return dataSourceBuilder.build();
// }
@Bean(name = "sqlSessionFactory")
public SqlSessionFactory sqlSessionFactory(@Qualifier("dynamicDataSource") DataSource dataSource){
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
try {
Resource[] resources = new PathMatchingResourcePatternResolver()
.getResources("classpath:mapper/*.xml");
sqlSessionFactoryBean.setMapperLocations(resources);
return sqlSessionFactoryBean.getObject();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
4、编写aop切面
package com.mmednet.config;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
/**
* Created by meridian on 2018/9/26.
*/
@Aspect
@Component
public class DynamicDataSourceAspect {
@Pointcut("execution(public * com.mmednet.demo.controller.*.*(..))")
public void createDataSource(){}
@Before("createDataSource()")
public void doBefore(){
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
String uid = request.getParameter("uid");
DataSourceContextHolder.setDB(uid);
}
@After("createDataSource()")
public void doAfter(){
DataSourceContextHolder.clearDB();
//这是使用springboot自带的tomcat连接池时关闭连接
// DynamicDataSource.closeConnection();
DynamicDataSourceOfDruid.closeConnection();
}
}
package com.mmednet.config;
import com.sun.org.apache.bcel.internal.generic.RETURN;
import org.apache.tomcat.jdbc.pool.DataSource;
import org.apache.tomcat.jdbc.pool.PoolConfiguration;
import org.apache.tomcat.jdbc.pool.PoolProperties;
import org.springframework.beans.BeanUtils;
import java.util.HashMap;
/**
* Created by meridian on 2018/9/25.
*/
public class DataSourceContextHolder {
private static ThreadLocal<String> contextHolder = new ThreadLocal<String>();
// 设置数据源名
public static void setDB(String db) {
contextHolder.set(db);
}
//获取数据源名
public static String getDB(){
return contextHolder.get();
}
//清空数据源
public static void clearDB(){
contextHolder.remove();
}
}
5、开始编写业务,进行测试
package com.mmednet.demo.controller;
import com.mmednet.demo.User;
import com.mmednet.demo.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* Created by meridian on 2018/9/10.
*/
@Controller
@RequestMapping("api/cxy")
public class UserController {
@Autowired
UserService userService;
@RequestMapping("/get")
@ResponseBody
public User getUser(Integer id){
return userService.selectUserById(id);
}
@RequestMapping("/save")
@ResponseBody
public String save(User user){
userService.insertUser(user);
return "ok";
}
}
user实体类、DAO、service省略了
分别建了cxy和cxy1两个数据库,都有user表,uid=2时保存到了cxy2库中的user表中,uid=1时,保存到了cxy库中user表中,查询同理。
————————————————
版权声明:本文为CSDN博主「草帽epeee」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/c598976342/article/details/82893993
以上是关于springboot项目中使用动态数据源的主要内容,如果未能解决你的问题,请参考以下文章