实现原理
com/lc/ibps/base/db/config/DatabaseConfigure.java
...
@Bean(/*initMethod = "initialize"*/)
@DependsOn({"j2CacheUtil","appUtil"})
//@ConditionalOnMissingBean(DatabaseInitializable.class)
public DatabaseInitializable databaseInitializable() {
return new DatabaseInitializable();
}
@Bean(/*initMethod = "initialize"*/)
@DependsOn({"j2CacheUtil","dataSourceDef"})
//@ConditionalOnMissingBean(DatabaseUpgradeInitializable.class)
public DatabaseUpgradeInitializable databaseUpgradeInitializable() {
return new DatabaseUpgradeInitializable();
}
...
com/lc/ibps/base/db/bootstrap/DatabaseInitializable.java
...
private void execute() {
try {
if (ITableMeta.isEmpty()) {
logger.warn("Initialize database cannot work under IDE environment!!!");
logger.debug("Starting to initialize database------------------>");
FileFilter filter = new FileFilter() {
@Override
public boolean accept(File file) {
return FileUtil.isFile(file);
}
};
// 建表sql文件
String dirCreate = DbConstant.getDirAutoDDLSQL(DbUtil.getCurDBtype());
URL url = URLUtil.getURL(dirCreate);
if(null == url) {
return;
}
logger.debug("Dir of create table sql {}.", url.getFile());
File dirCreateFile = new File(url.getFile());
// 判断目录是否多个,jar文件中也会存在
if(FileUtil.exist(dirCreateFile) && FileUtil.isDirectory(dirCreateFile)) {
File[] createFiles = dirCreateFile.listFiles(filter);
if(createFiles == null) {
// 没有文件直接返回
return;
}
for(File createFile : createFiles) {
execute(createFile);
logger.debug("Execute sql file {} success.", createFile.getAbsolutePath());
}
}
else {
return;
}
// 初始化数据sql文件
String dirInsert = DbConstant.getDirAutoDMLSQL(DbUtil.getCurDBtype());
logger.debug("Dir of insert data sql {}.", dirInsert);
url = URLUtil.getURL(dirInsert);
if(null == url) {
return;
}
File dirInsertFile = new File(url.getFile());
if(FileUtil.exist(dirInsertFile) && FileUtil.isDirectory(dirInsertFile)) {
File[] insertFiles = dirInsertFile.listFiles(filter);
if(null == insertFiles) {
// 没有文件直接返回
return;
}
for(File insertFile : insertFiles) {
execute(insertFile);
logger.debug("Execute sql file {} success.", insertFile.getAbsolutePath());
}
}
logger.debug("Ending of initialize database------------------>");
}
} catch (Exception e) {
throw new BaseException(e);
} finally {
}
}
...
com/lc/ibps/base/db/bootstrap/DatabaseUpgradeInitializable.java
...
private void record(String templateScript, String dsAlias, String schema, String cause) throws IOException {
File recordFile = getRecordFile();
if(BeanUtils.isNotEmpty(recordFile)) {
String sha256 = EncryptUtil.encryptSha256(templateScript);
InputStream is = new ClassPathResource(getRecordFilePath()).getStream();
String record = com.lc.ibps.base.core.util.FileUtil.readFile(is, StringPool.UTF_8, true);
if(StringUtil.isNotBlank(schema)) {
record = TenantSqlScriptUtil.formatSchemaForDML(record, schema);
}
getSepcJdbcTemplate(dsAlias).update(record, sha256, new Date(), cause);
}
}
...
通过注入DatabaseInitializable
和DatabaseUpgradeInitializable
这两个bean,实现租户创建空间后,自动执行相关DDL和DML语句的执行,以及执行对应的数据库更新脚本,并记录更新结果。
租户基础数据在哪?
com/lc/ibps/saas/base/db/tenant/operator/BaseTenantOperator.java
...
protected List<SchemaResultEntity> createTenantInternal(SchemaCreateEntity param) {
//1. 读取需要创建哪些数据库
Map<String, String> tenantDatabases = listTenantDatabases();
if(BeanUtils.isEmpty(tenantDatabases)) {
logger.warn("Database type {} hasn't tenant sql!", getDbType());
return new ArrayList<SchemaResultEntity>();
}
//2. 生成数据库信息
String tenantPrefix = AppUtil.getProperty(TenantConstant.DB_TENANT_PREFIX, TenantConstant.DB_TENANT_PREFIX_DEFAULT);
String active = AppUtil.getProfilesActive();
tenantPrefix += "_" + active;
tenantPrefix = tenantPrefix.replaceAll("-", "_");
logger.debug("tenantPrefix => {}", tenantPrefix);
List<SchemaResultEntity> result = createTenantSchema(param, tenantPrefix, tenantDatabases);
try {
//3. 创建数据库用户/模式
logger.info("Database type {} create tenant space starting!", getDbType());
createTenantInternal0(result.get(0));
logger.info("Database type {} create tenant space finished!", getDbType());
//4. 执行每个数据库用户/模式下DDL/DML SQL文件
logger.info("Database type {} execute tenant sql starting!", getDbType());
executeSqlScript(getDbType(), result.get(0));
logger.info("Database type {} execute tenant sql finished!", getDbType());
} catch (Exception e) {
try {
// 创建租户空间异常后是否自动删除已创建的库
boolean autoDelete = AppUtil.getProperty("db.tenant.schema.auto-delete", Boolean.class, true);
if(autoDelete) {
dropTenant(result);
}
} catch (Exception ignore) {}
throw e;
}
return result;
}
...
租户的基础数据在DML文件夹下的SQL脚本中,以及管理员创建空间时,tenant_type_
(租户资源类型)为init
(初始化资源)的数据也会同步到该租户下。
租户数据库信息如何记录?
com/lc/ibps/saas/process/callback/LocalTenantSchemaCreateProcessCallback.java
...
public void success(SchemaCreateEntity param, List<SchemaResultEntity> result) {
super.success(param, result);
for(SchemaResultEntity entity : result) {
String dsAlias = entity.getDsAlias();
String schema = entity.getSchema();
SaasTenantSchemaRepository saasTenantSchemaRepository = AppUtil.getBean(SaasTenantSchemaRepository.class);
SaasTenantSchemaPo tenantSchemaPo = saasTenantSchemaRepository.getByTenantProviderId(entity.getTenantId(), entity.getProviderId());
// 设置成已生成
tenantSchemaPo.setSchemaStatus(TenantSchemaStatus.CREATED.getValue());
tenantSchemaPo.setDsAlias(dsAlias);
tenantSchemaPo.setSchema(schema);
// 然后更新数据库
SaasTenantSchema saasTenantSchema = saasTenantSchemaRepository.newInstance(tenantSchemaPo);
saasTenantSchema.update();
}
for (SchemaResultEntity entity : result) {
// 同步初始化资源数据到该服务的空间内
// PC资源根据ibps_auth_res表的字段tenant_type_=init的全部读出来写到空间内;
if(TenantUtil.getProviderId().equalsIgnoreCase(entity.getProviderId())) {
ResourcesRepository resourcesRepository = AppUtil.getBean(ResourcesRepository.class);
List<ResourcesPo> resourcesPos = resourcesRepository.findByTenantType("init");
try {
TenantContext.forceTenantObject(TenantQueryUtil.get(entity.getTenantId()));
String dsAlias = TenantUtil.TenantSchemaUtil.getRealDsAlias(entity.getTenantId(), TenantUtil.getProviderId());
if (StringUtil.isNotBlank(dsAlias)) {//TODO 事务内不允许切换数据源,逻辑要调整
DbContextHolder.setDataSource(dsAlias, DbUtil.getCurDBtype());
}
Resources resources = resourcesRepository.newInstance();
resources.setDatas(resourcesPos);
resources.createBatch();
} catch (Exception e) {
throw new BaseException(e);
}
finally {
DbContextHolder.clearDataSource();
TenantContext.clearForceTenantObject();
}
}
}
String tenantName = OperatorParamter.Builder.create().get("tenantName", result.get(0).getOperatorParamters());
StringBuilder content = new StringBuilder();
send(tenantName, content.toString(), "成功");
}
...
租户的数据库信息通过对应的回调方法,同步更新到主平台的ibps_saas_tenant_schema
(SaaS核心-租户(企业)数据库信息表)
文档更新时间: 2020-10-08 18:18 作者:Eddy