001/** 002 * Copyright (c) 2015-2022, Michael Yang 杨福海 (fuhai999@gmail.com). 003 * <p> 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * <p> 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * <p> 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package io.jboot.db; 017 018import com.jfinal.plugin.activerecord.ActiveRecordPlugin; 019import com.jfinal.plugin.activerecord.CaseInsensitiveContainerFactory; 020import com.jfinal.plugin.activerecord.IDbProFactory; 021import com.jfinal.plugin.activerecord.Model; 022import com.jfinal.plugin.activerecord.dialect.Dialect; 023import io.jboot.Jboot; 024import io.jboot.components.cache.JbootCache; 025import io.jboot.db.datasource.DataSourceBuilder; 026import io.jboot.db.datasource.DataSourceConfig; 027import io.jboot.db.datasource.DataSourceConfigManager; 028import io.jboot.db.dbpro.JbootDbProFactory; 029import io.jboot.db.dialect.*; 030import io.jboot.exception.JbootException; 031import io.jboot.exception.JbootIllegalConfigException; 032import io.jboot.utils.ClassUtil; 033import io.jboot.utils.StrUtil; 034 035import javax.sql.DataSource; 036import java.lang.reflect.Constructor; 037import java.util.*; 038 039 040/** 041 * 数据库 管理 042 */ 043public class ArpManager { 044 045 private static ArpManager instance; 046 047 048 private List<ActiveRecordPlugin> activeRecordPlugins = new ArrayList<>(); 049 050 051 public static ArpManager me() { 052 if (instance == null) { 053 instance = new ArpManager(); 054 } 055 return instance; 056 } 057 058 private ArpManager() { 059 Map<String, DataSourceConfig> datasourceConfigs = DataSourceConfigManager.me().getDatasourceConfigs(); 060 createdRecordPlugins(datasourceConfigs); 061 } 062 063 private void createdRecordPlugins(Map<String, DataSourceConfig> allConfigs) { 064 065 Map<Integer, DataSourceConfig> dsCache = new HashMap<>(); 066 067 // 优先初始化 默认数据源 068 DataSourceConfig mainDataSourceConfig = allConfigs.remove(DataSourceConfig.NAME_DEFAULT); 069 initRecordPlugin(dsCache, mainDataSourceConfig); 070 071 072 // 初始化默认数据源后,再开始初始化其他数据库 073 for (Map.Entry<String, DataSourceConfig> entry : allConfigs.entrySet()) { 074 initRecordPlugin(dsCache, entry.getValue()); 075 } 076 077 078 // 为所有的 activeRecordPlugin 添加 jfinal 的表映射 079 for (ActiveRecordPlugin activeRecordPlugin : activeRecordPlugins) { 080 DataSourceConfig dataSourceConfig = dsCache.get(System.identityHashCode(activeRecordPlugin)); 081 082 List<TableInfo> tableInfos = dataSourceConfig.getTableInfos(); 083 if (tableInfos != null && !tableInfos.isEmpty()) { 084 for (TableInfo table : tableInfos) { 085 String tableName = StrUtil.isNotBlank(dataSourceConfig.getTablePrefix()) ? dataSourceConfig.getTablePrefix() + table.getTableName() : table.getTableName(); 086 if (StrUtil.isNotBlank(table.getPrimaryKey())) { 087 activeRecordPlugin.addMapping(tableName, table.getPrimaryKey(), (Class<? extends Model<?>>) table.getModelClass()); 088 } else { 089 activeRecordPlugin.addMapping(tableName, (Class<? extends Model<?>>) table.getModelClass()); 090 } 091 } 092 } 093 } 094 095 } 096 097 private void initRecordPlugin(Map<Integer, DataSourceConfig> arpDatasourceConfigs, DataSourceConfig datasourceConfig) { 098 if (datasourceConfig != null && datasourceConfig.isConfigOk()) { 099 100 // 执行 createRecordPlugin(...) 的时候,会同时完善 DataSourceConfig 里绑定的表数据 101 // createRecordPlugin完毕后,就可以通过 dataSourceConfig.getTableInfos() 去获取该数据源有哪些表 102 ActiveRecordPlugin activeRecordPlugin = createRecordPlugin(datasourceConfig); 103 104 arpDatasourceConfigs.put(System.identityHashCode(activeRecordPlugin), datasourceConfig); 105 activeRecordPlugins.add(activeRecordPlugin); 106 } 107 } 108 109 110 /** 111 * 创建 ActiveRecordPlugin 插件,用于数据库读写 112 * 113 * @param config 114 * @return 115 */ 116 public ActiveRecordPlugin createRecordPlugin(DataSourceConfig config) { 117 118 ActiveRecordPlugin activeRecordPlugin = newRecordPlugin(config); 119 120 if (StrUtil.isNotBlank(config.getDbProFactory())) { 121 IDbProFactory dbProFactory = Objects.requireNonNull(ClassUtil.newInstance(config.getDbProFactory()), 122 "Can not create dbProfactory by class: " + config.getDbProFactory()); 123 activeRecordPlugin.setDbProFactory(dbProFactory); 124 } else { 125 activeRecordPlugin.setDbProFactory(new JbootDbProFactory()); 126 } 127 128 if (StrUtil.isNotBlank(config.getContainerFactory())) { 129 activeRecordPlugin.setContainerFactory(ClassUtil.newInstance(config.getContainerFactory())); 130 } 131 132 if (config.getTransactionLevel() != null) { 133 activeRecordPlugin.setTransactionLevel(config.getTransactionLevel()); 134 } 135 136 // 使用 Jboot 的 SqlDebugger 代替了 137 activeRecordPlugin.setShowSql(false); 138 139 JbootCache jbootCache = Jboot.getCache(); 140 if (jbootCache != null) { 141 activeRecordPlugin.setCache(jbootCache); 142 } 143 144 configSqlTemplate(activeRecordPlugin, config); 145 configDialect(activeRecordPlugin, config); 146 147 /** 148 * 在一个表有多个数据源的情况下,应该只需要添加一个映射就可以了 149 * 添加映射:默认为该 model 的数据源 150 * 不添加映射:通过 model.use("xxx").save() 这种方式去调用该数据源 151 * 不添加映射使用从场景一般是:读写分离时,用于读取只读数据库的数据 152 */ 153 if (config.isNeedAddMapping()) { 154 TableInfoManager.me().initConfigMappingTables(config); 155 } 156 157 return activeRecordPlugin; 158 } 159 160 private ActiveRecordPlugin newRecordPlugin(DataSourceConfig config) { 161 162 String configName = config.getName(); 163 DataSource dataSource = new DataSourceBuilder(config).build(); 164 165 String clazzName = config.getActiveRecordPluginClass(); 166 if (StrUtil.isBlank(clazzName)) { 167 return StrUtil.isNotBlank(configName) 168 ? new ActiveRecordPlugin(configName, dataSource) 169 : new ActiveRecordPlugin(dataSource); 170 } 171 172 try { 173 Class<ActiveRecordPlugin> arpc = (Class<ActiveRecordPlugin>) Class.forName(clazzName, false, Thread.currentThread().getContextClassLoader()); 174 if (StrUtil.isNotBlank(configName)) { 175 Constructor constructor = arpc.getConstructor(String.class, DataSource.class); 176 return (ActiveRecordPlugin) constructor.newInstance(configName, dataSource); 177 } else { 178 Constructor constructor = arpc.getConstructor(DataSource.class); 179 return (ActiveRecordPlugin) constructor.newInstance(dataSource); 180 } 181 } catch (Exception e) { 182 throw new JbootException(e.toString(), e); 183 } 184 } 185 186 187 /** 188 * 配置 本地 sql 189 * 190 * @param datasourceConfig 191 * @param activeRecordPlugin 192 */ 193 private void configSqlTemplate(ActiveRecordPlugin activeRecordPlugin, DataSourceConfig datasourceConfig) { 194 String sqlTemplatePath = datasourceConfig.getSqlTemplatePath(); 195 if (StrUtil.isNotBlank(sqlTemplatePath)) { 196 activeRecordPlugin.setBaseSqlTemplatePath(sqlTemplatePath); 197 } else { 198 activeRecordPlugin.setBaseSqlTemplatePath(null); 199 } 200 201 202 String sqlTemplateString = datasourceConfig.getSqlTemplate(); 203 if (sqlTemplateString != null) { 204 String[] sqlTemplateFiles = sqlTemplateString.split(","); 205 for (String sql : sqlTemplateFiles) { 206 activeRecordPlugin.addSqlTemplate(sql); 207 } 208 } 209 } 210 211 /** 212 * 配置 数据源的 方言 213 * 214 * @param activeRecordPlugin 215 * @param datasourceConfig 216 */ 217 private void configDialect(ActiveRecordPlugin activeRecordPlugin, DataSourceConfig datasourceConfig) { 218 219 if (datasourceConfig.getDialectClass() != null) { 220 Dialect dialect = ClassUtil.newInstance(datasourceConfig.getDialectClass(), false); 221 if (dialect == null) { 222 throw new JbootIllegalConfigException("Can not new instance by class: " + datasourceConfig.getDialectClass()); 223 } 224 activeRecordPlugin.setDialect(dialect); 225 return; 226 } 227 228 switch (datasourceConfig.getType()) { 229 case DataSourceConfig.TYPE_MYSQL: 230 activeRecordPlugin.setDialect(new JbootMysqlDialect()); 231 break; 232 case DataSourceConfig.TYPE_ORACLE: 233 if (StrUtil.isBlank(datasourceConfig.getContainerFactory())) { 234 activeRecordPlugin.setContainerFactory(new CaseInsensitiveContainerFactory()); 235 } 236 activeRecordPlugin.setDialect(new JbootOracleDialect()); 237 break; 238 case DataSourceConfig.TYPE_SQLSERVER: 239 activeRecordPlugin.setDialect(new JbootSqlServerDialect()); 240 break; 241 case DataSourceConfig.TYPE_SQLITE: 242 activeRecordPlugin.setDialect(new JbootSqlite3Dialect()); 243 break; 244 case DataSourceConfig.TYPE_ANSISQL: 245 activeRecordPlugin.setDialect(new JbootAnsiSqlDialect()); 246 break; 247 case DataSourceConfig.TYPE_POSTGRESQL: 248 activeRecordPlugin.setDialect(new JbootPostgreSqlDialect()); 249 break; 250 case DataSourceConfig.TYPE_DM: 251 activeRecordPlugin.setDialect(new JbootDmDialect()); 252 break; 253 case DataSourceConfig.TYPE_CLICKHOUSE: 254 activeRecordPlugin.setDialect(new JbootClickHouseDialect()); 255 break; 256 case DataSourceConfig.TYPE_INFORMIX: 257 activeRecordPlugin.setDialect(new JbootInformixDialect()); 258 break; 259 default: 260 throw new JbootIllegalConfigException("only support datasource type: mysql、oracle、sqlserver、sqlite、ansisql、postgresql and clickhouse, please check your jboot.properties. "); 261 } 262 } 263 264 265 public List<ActiveRecordPlugin> getActiveRecordPlugins() { 266 return activeRecordPlugins; 267 } 268 269 public ActiveRecordPlugin getActiveRecordPlugin(String configName) { 270 for (ActiveRecordPlugin arp : activeRecordPlugins) { 271 if (configName.equals(arp.getConfig().getName())) { 272 return arp; 273 } 274 } 275 return null; 276 } 277 278}