001/*
002 *  Copyright (c) 2022-2023, Mybatis-Flex (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 com.mybatisflex.core.relation;
017
018import com.mybatisflex.annotation.RelationManyToMany;
019import com.mybatisflex.annotation.RelationManyToOne;
020import com.mybatisflex.annotation.RelationOneToMany;
021import com.mybatisflex.annotation.RelationOneToOne;
022import com.mybatisflex.core.BaseMapper;
023import com.mybatisflex.core.FlexConsts;
024import com.mybatisflex.core.FlexGlobalConfig;
025import com.mybatisflex.core.datasource.DataSourceKey;
026import com.mybatisflex.core.query.QueryWrapper;
027import com.mybatisflex.core.row.Row;
028import com.mybatisflex.core.util.*;
029import org.apache.ibatis.util.MapUtil;
030
031import java.lang.reflect.Field;
032import java.util.*;
033import java.util.concurrent.ConcurrentHashMap;
034
035import static com.mybatisflex.core.query.QueryMethods.column;
036
037/**
038 * @author michael
039 */
040@SuppressWarnings("rawtypes")
041public class RelationManager {
042
043    private RelationManager() {
044    }
045
046    private static Map<Class<?>, List<AbstractRelation>> classRelations = new ConcurrentHashMap<>();
047
048    /**
049     * 默认查询深度
050     */
051    private static int defaultQueryDepth = FlexGlobalConfig.getDefaultConfig().getDefaultRelationQueryDepth();
052
053    /**
054     * 递归查询深度,默认为 2,在一些特殊场景下可以修改这个值
055     */
056    private static ThreadLocal<Integer> depthThreadLocal = ThreadLocal.withInitial(() -> defaultQueryDepth);
057
058    /**
059     * 附加条件的查询参数
060     */
061    private static ThreadLocal<Map<String, Object>> extraConditionParams = new ThreadLocal<>();
062
063
064    /**
065     * 查询时,可忽略某些已经添加 Relation 注解的属性
066     */
067    private static ThreadLocal<Set<String>> ignoreRelations = new ThreadLocal<>();
068
069
070    /**
071     * 查询时,仅查询这个配置的 Relations
072     */
073    private static ThreadLocal<Set<String>> onlyQueryRelations = new ThreadLocal<>();
074
075
076    /**
077     * 每次查询是否自动清除 depth  extraConditionParams ignoreRelations 的配置
078     */
079    private static ThreadLocal<Boolean> autoClearConfig = ThreadLocal.withInitial(() -> true);
080
081
082    public static int getDefaultQueryDepth() {
083        return defaultQueryDepth;
084    }
085
086    public static void setDefaultQueryDepth(int defaultQueryDepth) {
087        RelationManager.defaultQueryDepth = defaultQueryDepth;
088    }
089
090    public static void setMaxDepth(int maxDepth) {
091        depthThreadLocal.set(maxDepth);
092    }
093
094    public static int getMaxDepth() {
095        return depthThreadLocal.get();
096    }
097
098    public static void clearMaxDepth() {
099        extraConditionParams.remove();
100    }
101
102
103    public static void setExtraConditionParams(Map<String, Object> params) {
104        extraConditionParams.set(params);
105    }
106
107    public static void addExtraConditionParam(String key, Object value) {
108        Map<String, Object> params = extraConditionParams.get();
109        if (params == null) {
110            params = new HashMap<>();
111            extraConditionParams.set(params);
112        }
113        params.put(key, value);
114    }
115
116    public static Map<String, Object> getExtraConditionParams() {
117        return extraConditionParams.get();
118    }
119
120    public static void clearExtraConditionParams() {
121        extraConditionParams.remove();
122    }
123
124
125    //////ignore relations //////
126    public static Set<String> getIgnoreRelations() {
127        return ignoreRelations.get();
128    }
129
130    public static void setIgnoreRelations(Set<String> ignoreRelations) {
131        RelationManager.ignoreRelations.set(ignoreRelations);
132    }
133
134
135    public static <T> void addIgnoreRelations(LambdaGetter<T>... ignoreRelations) {
136        Set<String> relations = RelationManager.ignoreRelations.get();
137        if (relations == null) {
138            relations = new HashSet<>();
139            setIgnoreRelations(relations);
140        }
141        for (LambdaGetter<T> lambdaGetter : ignoreRelations) {
142            String fieldName = LambdaUtil.getFieldName(lambdaGetter);
143            relations.add(fieldName);
144        }
145    }
146
147    public static void addIgnoreRelations(String... ignoreRelations) {
148        Set<String> relations = RelationManager.ignoreRelations.get();
149        if (relations == null) {
150            relations = new HashSet<>();
151            setIgnoreRelations(relations);
152        }
153        relations.addAll(Arrays.asList(ignoreRelations));
154    }
155
156
157    public static void clearIgnoreRelations() {
158        ignoreRelations.remove();
159    }
160
161
162    //////query relations //////
163    public static Set<String> getQueryRelations() {
164        return onlyQueryRelations.get();
165    }
166
167    public static void setQueryRelations(Set<String> queryRelations) {
168        RelationManager.onlyQueryRelations.set(queryRelations);
169    }
170
171
172    public static <T> void addQueryRelations(LambdaGetter<T>... queryRelations) {
173        Set<String> relations = RelationManager.onlyQueryRelations.get();
174        if (relations == null) {
175            relations = new HashSet<>();
176            setQueryRelations(relations);
177        }
178        for (LambdaGetter<T> lambdaGetter : queryRelations) {
179            String fieldName = LambdaUtil.getFieldName(lambdaGetter);
180            relations.add(fieldName);
181        }
182    }
183
184    public static void addQueryRelations(String... queryRelations) {
185        Set<String> relations = RelationManager.onlyQueryRelations.get();
186        if (relations == null) {
187            relations = new HashSet<>();
188            setQueryRelations(relations);
189        }
190        relations.addAll(Arrays.asList(queryRelations));
191    }
192
193
194    public static void clearQueryRelations() {
195        onlyQueryRelations.remove();
196    }
197
198
199    public static void setAutoClearConfig(boolean enable) {
200        autoClearConfig.set(enable);
201    }
202
203    public static boolean getAutoClearConfig() {
204        return autoClearConfig.get();
205    }
206
207    public static void clearAutoClearConfig() {
208        autoClearConfig.remove();
209    }
210
211
212    static Object[] getExtraConditionParams(List<String> keys) {
213        if (keys == null || keys.isEmpty()) {
214            return FlexConsts.EMPTY_ARRAY;
215        }
216        Map<String, Object> paramMap = extraConditionParams.get();
217        if (paramMap == null || paramMap.isEmpty()) {
218            return new Object[keys.size()];
219        }
220
221        Object[] params = new Object[keys.size()];
222        for (int i = 0; i < keys.size(); i++) {
223            params[i] = paramMap.get(keys.get(i));
224        }
225
226        return params;
227    }
228
229
230    private static List<AbstractRelation> getRelations(Class<?> clazz) {
231        return MapUtil.computeIfAbsent(classRelations, clazz, RelationManager::doGetRelations);
232    }
233
234    private static List<AbstractRelation> doGetRelations(Class<?> entityClass) {
235        List<Field> allFields = ClassUtil.getAllFields(entityClass);
236        List<AbstractRelation> relations = new ArrayList<>();
237        for (Field field : allFields) {
238            RelationManyToMany manyToManyAnnotation = field.getAnnotation(RelationManyToMany.class);
239            if (manyToManyAnnotation != null) {
240                relations.add(new ManyToMany<>(manyToManyAnnotation, entityClass, field));
241            }
242
243            RelationManyToOne manyToOneAnnotation = field.getAnnotation(RelationManyToOne.class);
244            if (manyToOneAnnotation != null) {
245                relations.add(new ManyToOne<>(manyToOneAnnotation, entityClass, field));
246            }
247
248            RelationOneToMany oneToManyAnnotation = field.getAnnotation(RelationOneToMany.class);
249            if (oneToManyAnnotation != null) {
250                relations.add(new OneToMany<>(oneToManyAnnotation, entityClass, field));
251            }
252
253            RelationOneToOne oneToOneAnnotation = field.getAnnotation(RelationOneToOne.class);
254            if (oneToOneAnnotation != null) {
255                relations.add(new OneToOne<>(oneToOneAnnotation, entityClass, field));
256            }
257        }
258        return relations;
259    }
260
261
262    public static <Entity> void queryRelations(BaseMapper<?> mapper, List<Entity> entities) {
263        doQueryRelations(mapper, entities, 0, depthThreadLocal.get(), ignoreRelations.get(), onlyQueryRelations.get());
264        clearConfigIfNecessary();
265    }
266
267    /**
268     * 清除查询配置
269     */
270    private static void clearConfigIfNecessary() {
271        Boolean autoClearEnable = autoClearConfig.get();
272        if (autoClearEnable != null && autoClearEnable) {
273            depthThreadLocal.remove();
274            extraConditionParams.remove();
275            onlyQueryRelations.remove();
276            ignoreRelations.remove();
277        }
278    }
279
280
281    @SuppressWarnings({"rawtypes", "unchecked"})
282    static <Entity> void doQueryRelations(BaseMapper<?> mapper, List<Entity> entities, int currentDepth, int maxDepth, Set<String> ignoreRelations, Set<String> queryRelations) {
283        if (CollectionUtil.isEmpty(entities)) {
284            return;
285        }
286
287        if (currentDepth >= maxDepth) {
288            return;
289        }
290
291        Class<Entity> entityClass = (Class<Entity>) ClassUtil.getUsefulClass(entities.get(0).getClass());
292        List<AbstractRelation> relations = getRelations(entityClass);
293        if (relations.isEmpty()) {
294            return;
295        }
296
297        String currentDsKey = DataSourceKey.get();
298        try {
299            relations.forEach(relation -> {
300
301                //ignore
302                if (ignoreRelations != null && (ignoreRelations.contains(relation.getSimpleName())
303                    || ignoreRelations.contains(relation.getName()))) {
304                    return;
305                }
306
307                //only query
308                if (queryRelations != null && !queryRelations.isEmpty()
309                    && !queryRelations.contains(relation.getSimpleName())
310                    && !queryRelations.contains(relation.getName())) {
311                    return;
312                }
313
314                //注解配置的数据源
315                String configDsKey = relation.getDataSource();
316                if (StringUtil.isBlank(configDsKey) && currentDsKey != null) {
317                    configDsKey = currentDsKey;
318                }
319
320                try {
321                    if (StringUtil.isNotBlank(configDsKey)) {
322                        DataSourceKey.use(configDsKey);
323                    }
324
325                    Set<Object> targetValues;
326                    List<Row> mappingRows = null;
327
328                    //通过中间表关联查询
329                    if (relation.isRelationByMiddleTable()) {
330                        targetValues = new HashSet<>();
331                        Set selfFieldValues = relation.getSelfFieldValues(entities);
332                        QueryWrapper queryWrapper = QueryWrapper.create().select()
333                            .from(relation.getJoinTable());
334                        if (selfFieldValues.size() > 1) {
335                            queryWrapper.where(column(relation.getJoinSelfColumn()).in(selfFieldValues));
336                        } else {
337                            queryWrapper.where(column(relation.getJoinSelfColumn()).eq(selfFieldValues.iterator().next()));
338                        }
339
340                        mappingRows = mapper.selectListByQueryAs(queryWrapper, Row.class);
341                        if (CollectionUtil.isEmpty(mappingRows)) {
342                            return;
343                        }
344
345                        for (Row mappingData : mappingRows) {
346                            Object targetValue = mappingData.getIgnoreCase(relation.getJoinTargetColumn());
347                            if (targetValue != null) {
348                                targetValues.add(targetValue);
349                            }
350                        }
351                    }
352                    //通过外键字段关联查询
353                    else {
354                        targetValues = relation.getSelfFieldValues(entities);
355                    }
356
357                    if (CollectionUtil.isEmpty(targetValues)) {
358                        return;
359                    }
360
361                    //仅绑定字段:As目标实体类 不进行字段绑定:As映射类型
362                    QueryWrapper queryWrapper = relation.buildQueryWrapper(targetValues);
363                    List<?> targetObjectList = mapper.selectListByQueryAs(queryWrapper, relation.isOnlyQueryValueField() ? relation.getTargetEntityClass() : relation.getMappingType());
364                    if (CollectionUtil.isNotEmpty(targetObjectList)) {
365
366                        //递归查询
367                        doQueryRelations(mapper, targetObjectList, currentDepth + 1, maxDepth, ignoreRelations, queryRelations);
368
369                        //进行内存 join
370                        relation.join(entities, targetObjectList, mappingRows);
371                    }
372                } finally {
373                    if (StringUtil.isNotBlank(configDsKey)) {
374                        DataSourceKey.clear();
375                    }
376                }
377            });
378        } finally {
379            if (currentDsKey != null) {
380                DataSourceKey.use(currentDsKey);
381            }
382        }
383    }
384
385}