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}