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表中,查询同理。

扫描二维码关注公众号,回复: 11327648 查看本文章


————————————————
版权声明:本文为CSDN博主「草帽epeee」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/c598976342/article/details/82893993

推荐:网络营销解决方案有哪些,网络营销的四个特点介绍

如何提升网站的排名,四个提升排名的简单技巧

如何抗ddos攻击,预防黑客ddos攻击的技巧

网络营销产品有哪些分类,网络营销起源及发展前景

猜你喜欢

转载自www.cnblogs.com/vwvwvwgwg/p/13171001.html