天天看点

使用JDBC实现动态的多数据源

注意这是一个抽象类!

package copyright.hang.shu.daily.sql;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.apache.commons.lang.StringUtils;

import javax.sql.DataSource;
import java.beans.PropertyVetoException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

// 继承自javax.sql.DataSource
public abstract class DynamicDatasource implements DataSource {

    // 采用线程变量存放当前使用的数据库,如果不设置使用默认数据库
    private ThreadLocal<String> CURRENT_DATASOURCE =new ThreadLocal<>();

    // Map中存放的是会用到的数据源
    private Map<String,DataSource> dataSourceMap=new HashMap<>();

    private final ReadWriteLock lock=new ReentrantReadWriteLock();

    private String driverName;
    private String formatUrl;
    private String username;
    private String password;
    private String defaultDatabaseName;
    private List<String> dynamicDatasourceNames;

    public DynamicDatasource(String driverName, String formatUrl, String username, String password, String defaultDatabaseName, List<String> dynamicDatasourceNames) {
        this.driverName = driverName;
        this.formatUrl = formatUrl;
        this.username = username;
        this.password = password;
        this.defaultDatabaseName = defaultDatabaseName;
        this.dynamicDatasourceNames = dynamicDatasourceNames;
        init();
    }

    // 初始化数据源
    public void init() {
        try {
            lock.writeLock().lock();
            dynamicDatasourceNames.add(defaultDatabaseName);
            dynamicDatasourceNames.forEach(v->{
                createConnection(v);
            });
        }finally {
            lock.writeLock().unlock();
        }
    }

    // 创建数据源
    private void createConnection(String databaseName){
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        try {
            dataSource.setDriverClass(driverName);
        } catch (PropertyVetoException e) {
            e.printStackTrace();
        }

        // formatUrl中存在占位符,例如jdbc:mysql://127.0.0.1:3306/%s?useSSL=false&serverTimezone=UTC&&allowPublicKeyRetrieval=true
        dataSource.setJdbcUrl(String.format(formatUrl,databaseName));
        dataSource.setUser(username);
        dataSource.setPassword(password);
        dataSourceMap.put(databaseName,dataSource);
    }

    @Override
    public Connection getConnection() throws SQLException {
        try {
            lock.readLock().lock();
            if(CURRENT_DATASOURCE.get()==null){
                return dataSourceMap.get(defaultDatabaseName).getConnection();
            }
            return dataSourceMap.get(CURRENT_DATASOURCE.get()).getConnection();
        }finally {
            lock.readLock().unlock();
        }
    }

    // 添加数据源
    public final void addDataSource(String name,DataSource dataSource) throws Exception {
        try {
            lock.writeLock().lock();
            if(StringUtils.isBlank(name) || dataSource==null || dataSourceMap.containsKey(name)){
                throw new Exception();
            }
            dataSourceMap.put(name,dataSource);
        }finally {
            lock.writeLock().unlock();
        }
    }

    // 移除数据源
    public final void removeDataSource(String name) throws Exception {
        try {
            lock.writeLock().lock();
            dataSourceMap.remove(name);
        }finally {
            lock.writeLock().unlock();
        }
    }

    // 设置当前线程使用的数据源
    public void setCurrentDataSource(String name) throws Exception {
        try {
            lock.readLock().lock();
            if(!dataSourceMap.containsKey(name)){
                throw new Exception();
            }
            CURRENT_DATASOURCE.set(name);
        }finally {
            lock.readLock().unlock();
        }
        
    }
}