我们在实现多租户系统的时候,为了数据安全和性能,往往会把数据库设计成一个租户一个数据库,如下图,主库记录了租户信息和对应的数据库地址,租户数据库则存储了租户相关的数据,租户数据库的表结构都是一致的,这种方式有个痛点就是变更表结构非常麻烦,需要一个个数据库去变更,幸运的是可以通过一段代码来实现SQL脚本的同步
使用工具类:Hutool
// 同步多租户脚本
@Slf4j
public class SQLSyncTest {
@Test
public void syncSQL() {
String mainDBUrl = "";
String mainDBUsername = "";
String mainDBPassword = "";
String tenantSQL = "sql脚本";
boolean existOnException = false; //遇到错误是否退出
List<String> excludeTenantCodes = Arrays.asList("xxx"); //不执行脚本的租户code
try {
// 建立与主库的连接,获取租户库账号
DataSource ds = new SimpleDataSource(mainDBUrl, mainDBUsername, mainDBPassword);
List<Entity> tenantInfos = Db.use(ds).query("select * from main_datasource");
for (Entity tenantInfo : tenantInfos) {
String tenantCode = tenantInfo.getStr("tenant_code");
if (excludeTenantCodes.contains(tenantCode)) continue;
try {
// 组装租户库账号
String tenantDBUrl = StrUtil.format("jdbc:mysql://{}:{}/{}"
, tenantInfo.getStr("ip")
, tenantInfo.getStr("port")
, tenantInfo.getStr("database_name"));
String tenantDBUsername = tenantInfo.getStr("user_name");
String tenantDBPassword = tenantInfo.getStr("password");
DataSource tenantDS = new SimpleDataSource(tenantDBUrl, tenantDBUsername, tenantDBPassword);
// 执行同步脚本
int result = Db.use(tenantDS).execute(tenantSQL);
log.info("租户: {}执行成功,成功数: {}", tenantCode, result);
} catch (Exception e) {
log.error("租户: {}出错, {}", tenantCode, e);
if (existOnException) return;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}