删除旧租户的数据库
DROP DATABASE ?;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| ### 租户的多数据源切换的aop ```java /** * 租户的多数据源切换的aop */ @Aspect public class TenantSourceExAop implements Ordered {
private final Logger log = LoggerFactory.getLogger(this.getClass());
/** * 拦截控制器层 */ @Pointcut("execution(* *..controller.*.*(..))") public void cutService() { }
@Around("cutService()") public Object around(ProceedingJoinPoint point) throws Throwable { try { // 根据系统总开关来进行aop if (ConstantContextHolder.getTenantOpenFlag()) {
// 当前用户已经登陆并且租户信息不为空 if (LoginContextHolder.me().hasLogin()) { Dict tenantInfo = LoginContextHolder.me().getSysLoginUser().getTenants(); if (tenantInfo != null) {
// 获取当前用户登录的租户标识,切换数据源 String tenantDbName = tenantInfo.getStr(TenantConstants.TENANT_DB_NAME); if (StrUtil.isNotBlank(tenantDbName)) { CurrentDataSourceContext.setDataSourceType(tenantDbName); log.debug(">>> 多租户AOP--TenantSourceExAop--设置数据源为:" + tenantDbName); } } } } return point.proceed(); } finally { log.debug(">>> 多租户AOP--TenantSourceExAop--清空数据源信息!"); CurrentDataSourceContext.clearDataSourceType(); TenantCodeHolder.remove(); TenantDbNameHolder.remove(); } }
/** * aop的顺序要早于多数据源切换的 */ @Override public int getOrder() { return AopSortConstant.TENANT_EXCHANGE_AOP; }
}
|
详细参考:
https://www.cnblogs.com/zdd-java/p/zdd_datasource_aop.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| public abstract class AbstractRoutingDataSource extends AbstractDataSource {
protected abstract DataSource determineDataSource();
@Override public Connection getConnection() throws SQLException { return determineDataSource().getConnection(); }
@Override public Connection getConnection(String username, String password) throws SQLException { return determineDataSource().getConnection(username, password); }
@Override @SuppressWarnings("unchecked") public <T> T unwrap(Class<T> iface) throws SQLException { if (iface.isInstance(this)) { return (T) this; } return determineDataSource().unwrap(iface); }
@Override public boolean isWrapperFor(Class<?> iface) throws SQLException { return (iface.isInstance(this) || determineDataSource().isWrapperFor(iface)); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| class DynamicDataSource implements AbstractDataSource { @Override protected DataSource determineDataSource() {
String dataSourceType = CurrentDataSourceContext.getDataSourceType();
if (StrUtil.isEmpty(dataSourceType)) { dataSourceType = DatabaseConstant.MASTER_DATASOURCE_NAME; }
Map<String, DataSource> dataSources = DataSourceContext.getDataSources(); return dataSources.get(dataSourceType); } }
|
1 2 3 4 5 6 7 8 9 10 11 12
| @Configuration @Import(DataSourcePropertiesConfig.class) public class MultiDataSourceConfig {
@Bean public DynamicDataSource dataSource() { return new DynamicDataSource(); } }
|
1 2 3 4 5 6 7 8 9
| public class RemoveTenantListener implements ApplicationListener<ServletRequestHandledEvent> {
@Override public void onApplicationEvent(ServletRequestHandledEvent event) { TenantCodeHolder.remove(); TenantDbNameHolder.remove(); }
}
|
动态添加租户

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| @Override @Transactional(rollbackFor = Exception.class) public void add(TenantInfoParam param) {
DruidProperties druidProperties = SpringUtil.getBean(DruidProperties.class); String databaseName = TENANT_DB_PREFIX + param.getCode(); DatabaseUtil.createDatabase(druidProperties, databaseName);
DatabaseInfoService databaseInfoService; try { databaseInfoService = SpringUtil.getBean(DatabaseInfoService.class); } catch (Exception e) { throw new TenantException(TenantExceptionEnum.DBS_MODULAR_NOT_ENABLE_ERROR); } DatabaseInfoParam dataBaseInfo = DataBaseInfoFactory.createDataBaseInfo(druidProperties, databaseName); databaseInfoService.add(dataBaseInfo);
SqlRunUtil.runClassPathSql(INIT_SQL_FILE_NAME, databaseName);
TenantInfo tenantInfo = new TenantInfo(); BeanUtil.copyProperties(param, tenantInfo); tenantInfo.setDbName(databaseName); this.save(tenantInfo);
String hashPw = BCrypt.hashpw(param.getAdminPassword(), BCrypt.gensalt()); DataSource dataSource = DataSourceContext.getDataSources().get(databaseName); Connection connection = null; try { connection = dataSource.getConnection(); SqlExecutor.execute(connection, UPDATE_SQL, hashPw); } catch (SQLException e) { log.error(">>> 更新多租户的用户密码错误!", e); throw new TenantException(TenantExceptionEnum.UPDATE_TENANT_PASSWORD_ERROR); } finally { DbUtil.close(connection); } }
|
一个事务相关的问题
每次调用的时候会走chooseDataSource再走AOP,经查询发现是事务问题导致,因为它先走了事务切面,然后事务还没结束的时候如果再去切换数据源的话是不成立的。方法解决很简单,设置一下切换数据源的AOP的优先级,确保在事务执行之前就已经切换数据源
有事务时不管注解是什么都会取master,spring事务切面优先执行,创建事务时dataSource已经被绑定了,去执行方法时通过切面虽然切换了当前数据源但是,执行sql时是从事务的threadLocal resources 中取,取的是创建事务时的数据库连接。
声明式事务与上文切换数据源的aop都是基于动态代理,或者说后置处理器实现的,标记一个@Order是不是可以解决这个问题?

https://blog.csdn.net/qq_31156277/article/details/85227415
有那么多dataSource,但都不是Bean,只有一个是Bean,
和Bean的名字么有关系,使用的时候使用的是接口,调用的时候看的是子类,所以这里有多个子类是Bean的时候就会使用@Primary来决定,都没有的话就会抛出异常。
但是本项目里面因为只有一个DataSource,所以不存在这个问题