001/*
002 *  Copyright (c) 2022-2025, 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.core.exception.FlexExceptions;
019import com.mybatisflex.core.query.QueryWrapper;
020import com.mybatisflex.core.row.Row;
021import com.mybatisflex.core.util.*;
022
023import java.lang.reflect.Field;
024import java.util.*;
025
026public class ToManyRelation<SelfEntity> extends AbstractRelation<SelfEntity> {
027
028    protected String mapKeyField;
029
030    protected FieldWrapper mapKeyFieldWrapper;
031
032    protected String orderBy;
033
034    protected long limit = 0;
035
036    protected String selfValueSplitBy;
037
038
039    public ToManyRelation(String selfField, String targetSchema, String targetTable, String targetField, String valueField,
040                          String joinTable, String joinSelfColumn, String joinTargetColumn,
041                          String dataSource, Class<SelfEntity> selfEntityClass, Field relationField,
042                          String extraCondition, String[] selectColumns) {
043        super(selfField, targetSchema, targetTable, targetField, valueField,
044            joinTable, joinSelfColumn, joinTargetColumn,
045            dataSource, selfEntityClass, relationField,
046            extraCondition, selectColumns
047        );
048    }
049
050    public static Class<? extends Map> getMapWrapType(Class<?> type) {
051        if (ClassUtil.canInstance(type.getModifiers())) {
052            return (Class<? extends Map>) type;
053        }
054
055        return HashMap.class;
056    }
057
058    /**
059     * 构建查询目标对象的 QueryWrapper
060     *
061     * @param targetValues 条件的值
062     * @return QueryWrapper
063     */
064    @Override
065    public QueryWrapper buildQueryWrapper(Set<Object> targetValues) {
066        if (StringUtil.hasText(selfValueSplitBy) && CollectionUtil.isNotEmpty(targetValues)) {
067            Set<Object> newTargetValues = new HashSet<>();
068            for (Object targetValue : targetValues) {
069                if (targetValue == null) {
070                    continue;
071                }
072                if (!(targetValue instanceof String)) {
073                    throw FlexExceptions.wrap("split field only support String type, but current type is: \"" + targetValue.getClass().getName() + "\"");
074                }
075                String[] splitValues = ((String) targetValue).split(selfValueSplitBy);
076                for (String splitValue : splitValues) {
077                    // 排除空值
078                    if (splitValue == null || splitValue.isEmpty()) {
079                        continue;
080                    }
081                    //优化分割后的数据类型(防止在数据库查询时候出现隐式转换)
082                    newTargetValues.add(ConvertUtil.convert(splitValue, targetFieldWrapper.getFieldType()));
083                }
084            }
085            targetValues = newTargetValues;
086        }
087        return super.buildQueryWrapper(targetValues);
088    }
089
090    @Override
091    public void customizeQueryWrapper(QueryWrapper queryWrapper) {
092        if (StringUtil.hasText(orderBy)) {
093            queryWrapper.orderBy(orderBy);
094        }
095
096        if (limit > 0) {
097            queryWrapper.limit(limit);
098        }
099    }
100
101    @SuppressWarnings("rawtypes")
102    @Override
103    public void join(List<SelfEntity> selfEntities, List<?> targetObjectList, List<Row> mappingRows) {
104
105        //目标表关联字段->目标表对象
106        Map<String, List<Object>> leftFieldToRightTableMap = new HashMap<>(targetObjectList.size());
107        for (Object targetObject : targetObjectList) {
108            Object targetJoinFieldValue = targetFieldWrapper.get(targetObject);
109            if (targetJoinFieldValue != null) {
110                leftFieldToRightTableMap.computeIfAbsent(targetJoinFieldValue.toString(), k -> new ArrayList<>(1)).add(targetObject);
111            }
112        }
113
114        //通过中间表
115        if (mappingRows != null) {
116            //当使用中间表时,需要重新映射关联关系
117            Map<String, List<Object>> temp = new HashMap<>(selfEntities.size());
118            for (Row mappingRow : mappingRows) {
119                Object midTableJoinSelfValue = mappingRow.getIgnoreCase(joinSelfColumn);
120                if (midTableJoinSelfValue == null) {
121                    continue;
122                }
123                Object midTableJoinTargetValue = mappingRow.getIgnoreCase(joinTargetColumn);
124                if (midTableJoinTargetValue == null) {
125                    continue;
126                }
127                List<Object> targetObjects = leftFieldToRightTableMap.get(midTableJoinTargetValue.toString());
128                if (targetObjects == null) {
129                    continue;
130                }
131                temp.computeIfAbsent(midTableJoinSelfValue.toString(), k -> new ArrayList<>(targetObjects.size())).addAll(targetObjects);
132            }
133            leftFieldToRightTableMap = temp;
134        }
135
136
137        //关联集合的类型
138        Class<?> fieldType = relationFieldWrapper.getFieldType();
139        boolean isMapType = Map.class.isAssignableFrom(fieldType);
140        Class<?> wrapType = isMapType ? getMapWrapType(fieldType) : MapperUtil.getCollectionWrapType(fieldType);
141        boolean splitMode = StringUtil.hasText(selfValueSplitBy);
142
143        for (SelfEntity selfEntity : selfEntities) {
144            if (selfEntity == null) {
145                continue;
146            }
147            Object selfValue = selfFieldWrapper.get(selfEntity);
148            if (selfValue == null) {
149                continue;
150            }
151            selfValue = selfValue.toString();
152
153            //只有当splitBy不为空时才会有多个值
154            Set<String> targetMappingValues;
155
156            if (splitMode) {
157                String[] splitValues = ((String) selfValue).split(selfValueSplitBy);
158                targetMappingValues = new LinkedHashSet<>(Arrays.asList(splitValues));
159            } else {
160                targetMappingValues = new HashSet<>(1);
161                targetMappingValues.add((String) selfValue);
162            }
163
164            if (targetMappingValues.isEmpty()) {
165                return;
166            }
167
168            // map
169            if (isMapType) {
170                Map map = (Map) ClassUtil.newInstance(wrapType);
171                Set<Object> validateCountSet = new HashSet<>(targetMappingValues.size());
172                for (String targetMappingValue : targetMappingValues) {
173                    List<Object> targetObjects = leftFieldToRightTableMap.get(targetMappingValue);
174                    //如果非真实外键约束 可能没有对应的对象
175                    if (targetObjects == null) {
176                        continue;
177                    }
178                    for (Object targetObject : targetObjects) {
179                        Object keyValue = mapKeyFieldWrapper.get(targetObject);
180                        Object needKeyValue = ConvertUtil.convert(keyValue, relationFieldWrapper.getKeyType());
181                        if (validateCountSet.contains(needKeyValue)) {
182                            //当字段类型为Map时,一个key对应的value只能有一个
183                            throw FlexExceptions.wrap("When fieldType is Map, the target entity can only be one,\n" +
184                                " current entity type is : " + selfEntity + "\n" +
185                                " relation field name is : " + relationField.getName() + "\n" +
186                                " target entity is : " + targetObjects);
187                        }
188                        validateCountSet.add(needKeyValue);
189
190                        //noinspection unchecked
191                        map.put(needKeyValue, targetObject);
192                    }
193                }
194                if (!map.isEmpty()) {
195                    relationFieldWrapper.set(map, selfEntity);
196                }
197
198            }
199            //集合
200            else {
201                Collection collection = (Collection) ClassUtil.newInstance(wrapType);
202                if (onlyQueryValueField) {
203                    Object first = targetObjectList.iterator().next();
204                    //将getter方法用单独的变量存储 FieldWrapper.of虽然有缓存 但每次调用至少有一个HashMap的get开销
205                    FieldWrapper fieldValueFieldWrapper = FieldWrapper.of(first.getClass(), valueField);
206                    for (String targetMappingValue : targetMappingValues) {
207                        List<Object> targetObjects = leftFieldToRightTableMap.get(targetMappingValue);
208                        if (targetObjects == null) {
209                            continue;
210                        }
211                        for (Object targetObject : targetObjects) {
212                            //仅绑定某个字段
213                            //noinspection unchecked
214                            collection.add(fieldValueFieldWrapper.get(targetObject));
215                        }
216                    }
217                } else {
218                    for (String targetMappingValue : targetMappingValues) {
219                        List<Object> targetObjects = leftFieldToRightTableMap.get(targetMappingValue);
220                        if (targetObjects == null) {
221                            continue;
222                        }
223                        //noinspection unchecked
224                        collection.addAll(targetObjects);
225                    }
226                }
227
228                relationFieldWrapper.set(collection, selfEntity);
229            }
230        }
231
232    }
233
234    public void setMapKeyField(String mapKeyField) {
235        this.mapKeyField = mapKeyField;
236        if (StringUtil.hasText(mapKeyField)) {
237            this.mapKeyFieldWrapper = FieldWrapper.of(targetEntityClass, mapKeyField);
238        } else {
239            if (Map.class.isAssignableFrom(relationFieldWrapper.getFieldType())) {
240                throw FlexExceptions.wrap("Please config mapKeyField for map field: " + relationFieldWrapper.getField());
241            }
242        }
243    }
244
245}