根据用户注册,系统自动创建私有数据库,用户登录,动态添加数据源到Spring数据路由,Session超时删除数据源
好处:当数据量大的时候,类似水平切割效果,效率会高一些
坏处:数据源切换,Spring 事务处理比较繁琐,数据连接处理不好会有很大消耗,如果涉及后台系统管理数据,也比较繁琐.
使用Spring数据源路由,现在好像没有直接添加数据源的方法,无奈之下只能用反射.
用户登录成功时,在Spring Security UserDetailService.loadUserByUsername 里面添加用户数据源
/**
* 加入用户数据源*/routingDataSource.addDataSource(userid);
/**
* 根据用户创建数据源*/
public voidaddDataSource(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 dataSources = (Map) targetDataSources.get(this);if (dataSources.get(userInfo.getId().toString()) != null)return;
Map dataSources2 = (Map) resolvedDataSources.get(this);
DruidDataSource dds= newDruidDataSource();
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 voidremoveDataSource(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 dataSources = (Map) targetDataSources.get(this);if (dataSources.get(userInfo.getUsrno()) != null) {
Map dataSources2 = (Map) 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 @interfaceSwitchDataSource {
}
Spring AOP,新版本的SpringAOP 可以很好切入监听器,因为监听器可以被Spring容器管理了,变相加强了SpringAop,这样就不需要使用原生Aspectj了
/*** Created by 为 on 2017-4-27.*/@Component
@Aspect
@Order(0)//配置Spring注解事务时,在事务之前切换数据源public classSwitchDataSourceAspectj {//定义切点
@Pointcut("@annotation(com.lzw.common.annotation.SwitchDataSource)")public voidswitchDataSource(){}
@Around("switchDataSource()")publicObject arounduserDataSource(ProceedingJoinPoint joinPoint){
DataSourceContextHolder.user();try{returnjoinPoint.proceed();
}catch(Throwable throwable) {
throwable.printStackTrace();
}finally{
DataSourceContextHolder.write();
}return null;
}
}
这样可以在方法上添加注解切换数据源(注意事务与切换数据源的注解顺序),不过如果在一个方法中需要多次切换到不同数据源查询数据,会消耗很多连接数,为了更好控制数据库连接数,需要使用Spring事务
编程式Spring事务
注入TransactionManager
@Resourceprivate PlatformTransactionManager platformTransactionManager;
开始事务处理,每个用户单独数据库,访问量不大,所以没有配置连接池,每次重新获取连接性能比较低,开启事务是为了数据库连接重用
//为了节省连接数,尽可能在一次切换里获取需要的数据
DataSourceContextHolder.user();//TransactionTemplate 必须每次new出来,不能使用Spring单例注入,设置的数据会一直存在.
TransactionTemplate transactionTemplate = newTransactionTemplate(platformTransactionManager);
transactionTemplate.setPropagationBehavior(Propagation.REQUIRES_NEW.value());
transactionTemplate.execute(newTransactionCallbackWithoutResult() {
@Overridepublic voiddoInTransactionWithoutResult(TransactionStatus status) {//数据库操作代码
}
});
DataSourceContextHolder.write();