一朋友应聘到新公司,结果不会写数据源切换,只好帮他写写。
先说业务需求,和一般的数据源切换不同,这里的需求的数据源配置信息不是先配置好的,而是存在数据库之中,不同的用户登录之后,先查询到的自己的数据源配置信息,然后生成数据源,再进行数据源切换。不忍吐槽,这个架构就出了很大的纰漏,但是毕竟是需求,也就不多说什么了。
那么根据需求来分析,最主要的就是用户数据源获取和获取完之后的再生成一个数据源可供加载。
所以先设计一个关于用户数据源信息的类
public class UserDataConfig implements Serializable {
private String key;
private String driven;
private String username;
private String password;
private String url;
public UserDataConfig() {}
public UserDataConfig(String key, String driven, String username, String password, String url) {
this.key = key;
this.driven = driven;
this.username = username;
this.password = password;
this.url = url;
}
get/set....
}
然后抽象实现:
public abstract class ToggleUserDataSource extends AbstractDataSource implements InitializingBean {
private static Logger logger = LoggerFactory.getLogger(ToggleUserDataSource.class);
private DataSource defaultDataSource; //默认的数据源注入 ref
private Map<String,DataSource> userDataSource = new ConcurrentHashMap<String, DataSource>();
private ThreadLocal<DataSource> exportDataSource = new ThreadLocal<DataSource>(); //将要输出的DataSource 架构有问题 当用量上来的时候可能发生OutOfMemory
private Class<? extends DataSource> poolType; //将要生成的数据库连接池
private Lock lock = new ReentrantLock();
/**
* 对外切换数据源接口
* @param userKey 用户标识
* @return 是否切换成功
*/
public boolean toggleDataSource(String userKey){
DataSource dataSource = userDataSource.get(userKey);
if (dataSource == null){
lock.lock();
try {
if(dataSource == null){
dataSource = buildDataSource(userKey);
}
}finally {
lock.unlock();
}
if(dataSource == null){
return false;
}
userDataSource.put(userKey,dataSource);
}
exportDataSource.set(dataSource);
return true;
}
private DataSource buildDataSource(String userKey){
UserDataConfig userDataConfig = getUserDataConfig(userKey,defaultDataSource);
DataSource source = exportDataSourcePoolBuild(userDataConfig);
/*DataSource result = DataSourceBuilder.create(CustomUserDataSource.class.getClassLoader())
.driverClassName(userDataConfig.getDriven())
.url(userDataConfig.getUrl()).username(userDataConfig.getUsername())
.password(userDataConfig.getPassword()).build();*/
return source;
}
@Override
public Connection getConnection() throws SQLException {
checkExportDtaSource();
return exportDataSource.get().getConnection();
}
@Override
public void afterPropertiesSet() throws Exception {
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
checkExportDtaSource();
return exportDataSource.get().getConnection(username,password);
}
protected DataSource getDefaultDataSource() {
return defaultDataSource;
}
/**
* 暴露的默认数据源注入
* @param defaultDataSource
*/
public void setDefaultDataSource(DataSource defaultDataSource) {
this.defaultDataSource = defaultDataSource;
}
/**
* 当用户查询的config配置返回null 则使用默认的数据库连接池
*/
private void checkExportDtaSource(){
if(this.exportDataSource.get() == null){
this.exportDataSource.set(defaultDataSource);
}
}
public void updateCache(String userKey){
DataSource dataSource = null;
this.lock.lock();
try {
dataSource = buildDataSource(userKey);
}finally {
this.lock.unlock();
}
userDataSource.put(userKey,dataSource);
}
/**
* 用户实现
* @param userKey
* @param defaultDataSource
* @return
*/
protected abstract UserDataConfig getUserDataConfig(String userKey,DataSource defaultDataSource);
protected abstract DataSource exportDataSourcePoolBuild(UserDataConfig userDataConfig);
}
提供一个默认数据源是用来替换DataSource的注入,近似装饰者模式。另外使用模板设计,这里预留了两个抽象方法UserDataConfig和exportDataSourcePoolBuild,顾名思义,UserDataConfig就是通过默认的数据源和用户key来得到用户的数据源信息,而exportDataSourcePoolBuild则是通过生成好的UserDataConfig对象来生成配置生成新的DataSource,这里我是使用了druid。顺带一说,这个子类应该要保持应用中唯一,所以用了单例模式。
public class UserDataSource extends ToggleUserDataSource {
private static UserDataSource userDataSource;
@Override
protected UserDataConfig getUserDataConfig(String userKey, DataSource defaultDataSource) {
UserDataConfig config = new UserDataConfig();
config.setDriven("com.mysql.jdbc.Driver");
config.setUrl("jdbc:mysql://localhost:3306/vue?useUnicode=true&characterEncoding=utf8");
config.setUsername("root");
config.setPassword("123456");
return config;
}
@Override
protected DataSource exportDataSourcePoolBuild(UserDataConfig userDataConfig) {
//使用DruidDataSource生成数据库连接池
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setUrl(userDataConfig.getUrl());
druidDataSource.setUsername(userDataConfig.getUsername());
druidDataSource.setPassword(userDataConfig.getPassword());
druidDataSource.setDriverClassName(userDataConfig.getDriven());
druidDataSource.setMaxActive(3);
return druidDataSource;
}
private UserDataSource(){}
public static UserDataSource getSingleton() {
if (null == userDataSource) {
synchronized (UserDataSource.class) {
if (null == userDataSource) {
userDataSource = new UserDataSource();
}
}
}
return userDataSource;
}
}
然后再配置DataSource的bean
@Configuration
@EnableConfigurationProperties(DataSourceProperties.class)
public class DataSourceConfig {
@Autowired
private DataSourceProperties dataSourceProperties;
@Bean
@Primary
public DataSource dataSource(){
DruidDataSource defaultDataSource = new DruidDataSource();
defaultDataSource.setDriverClassName(dataSourceProperties.getDriverClassName());
defaultDataSource.setUsername(dataSourceProperties.getUsername());
defaultDataSource.setPassword(dataSourceProperties.getPassword());
defaultDataSource.setUrl(dataSourceProperties.getUrl());
UserDataSource userDataSource = UserDataSource.getSingleton();
userDataSource.setDefaultDataSource(defaultDataSource);
//source.toggleDataSource("s");
return userDataSource;
}
}
当需要切换的时候,调用toggleDataSource()。这里其实可以配合上aop去给需要切换数据源的方法加注解,比较容易,这里就不实现了。
完整资源就扔这了http://download.csdn.net/download/qq_15125845/10048641