根據使用者注冊,系統自動建立私有資料庫,使用者登入,動态添加資料源到Spring資料路由,Session逾時删除資料源
好處:當資料量大的時候,類似水準切割效果,效率會高一些
壞處:資料源切換,Spring 事務處理比較繁瑣,資料連接配接處理不好會有很大消耗,如果涉及背景系統管理資料,也比較繁瑣.
使用Spring資料源路由,現在好像沒有直接添加資料源的方法,無奈之下隻能用反射.
使用者登入成功時,在Spring Security UserDetailService.loadUserByUsername 裡面添加使用者資料源
/**
* 加入使用者資料源
*/
routingDataSource.addDataSource(userid);
/**
* 根據使用者建立資料源
*/
public void addDataSource(String userid) {
if (StringUtils.isBlank(userid))
return;
DbInfo dbInfo = getDbInfoService().getDbInfoByUserId(userid);
try {
Field targetDataSources = AbstractRoutingDataSource.class.getDeclaredField("targetDataSources");
Field resolvedDataSources = AbstractRoutingDataSource.class.getDeclaredField("resolvedDataSources");
targetDataSources.setAccessible(true);
resolvedDataSources.setAccessible(true);
Map<Object, Object> dataSources = (Map<Object, Object>) targetDataSources.get(this);
if (dataSources.get(userInfo.getId().toString()) != null)
return;
Map<Object, DataSource> dataSources2 = (Map<Object, DataSource>) resolvedDataSources.get(this);
DruidDataSource dds = new DruidDataSource();
dds.setUrl("jdbc:mysql://" + dbInfo.getDbaddr() +
":" + dbInfo.getDbport() + "/" + dbInfo.getDbname() + "?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&useSSL=true");
dds.setUsername(dbInfo.getUsername());
dds.setPassword(dbInfo.getPwd());
dataSources.put(userid, dds);
dataSources2.put(userid, dds);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
加入了資料源,當然需要删除,可以在Session監聽器裡面,銷毀Session的時候删除
/**
* 根據使用者删除資料源
*/
public void removeDataSource(String userid) {
if (StringUtils.isBlank(userid))
return;
try {
Field targetDataSources = AbstractRoutingDataSource.class.getDeclaredField("targetDataSources");
Field resolvedDataSources = AbstractRoutingDataSource.class.getDeclaredField("resolvedDataSources");
targetDataSources.setAccessible(true);
resolvedDataSources.setAccessible(true);
Map<Object, Object> dataSources = (Map<Object, Object>) targetDataSources.get(this);
if (dataSources.get(userInfo.getUsrno()) != null) {
Map<Object, DataSource> dataSources2 = (Map<Object, DataSource>) resolvedDataSources.get(this);
dataSources.remove(userid);
dataSources2.remove(userid);
}
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
注解加Aop 切換資料源
注解
/**
* Created by 為 .
* 根據目前使用者切換資料源
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SwitchDataSource {
}
Spring AOP,新版本的SpringAOP 可以很好切入監聽器,因為監聽器可以被Spring容器管理了,變相加強了SpringAop,這樣就不需要使用原生Aspectj了
/**
* Created by 為 on 2017-4-27.
*/
@Component
@Aspect
@Order(0)//配置Spring注解事務時,在事務之前切換資料源
public class SwitchDataSourceAspectj {
//定義切點
@Pointcut("@annotation(com.lzw.common.annotation.SwitchDataSource)")
public void switchDataSource(){}
@Around("switchDataSource()")
public Object arounduserDataSource(ProceedingJoinPoint joinPoint){
DataSourceContextHolder.user();
try {
return joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}finally {
DataSourceContextHolder.write();
}
return null;
}
}
這樣可以在方法上添加注解切換資料源(注意事務與切換資料源的注解順序),不過如果在一個方法中需要多次切換到不同資料源查詢資料,會消耗很多連接配接數,為了更好控制資料庫連接配接數,需要使用Spring事務
程式設計式Spring事務
注入TransactionManager
@Resource
private PlatformTransactionManager platformTransactionManager;
開始事務處理,每個使用者單獨資料庫,通路量不大,是以沒有配置連接配接池,每次重新擷取連接配接性能比較低,開啟事務是為了資料庫連接配接重用
//為了節省連接配接數,盡可能在一次切換裡擷取需要的資料
DataSourceContextHolder.user();
//TransactionTemplate 必須每次new出來,不能使用Spring單例注入,設定的資料會一直存在.
TransactionTemplate transactionTemplate = new TransactionTemplate(platformTransactionManager);
transactionTemplate.setPropagationBehavior(Propagation.REQUIRES_NEW.value());
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
public void doInTransactionWithoutResult(TransactionStatus status) {
//資料庫操作代碼
}
});
DataSourceContextHolder.write();