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.QueryColumn;
020import com.mybatisflex.core.query.QueryWrapper;
021import com.mybatisflex.core.row.Row;
022import com.mybatisflex.core.table.IdInfo;
023import com.mybatisflex.core.table.TableInfo;
024import com.mybatisflex.core.table.TableInfoFactory;
025import com.mybatisflex.core.util.ArrayUtil;
026import com.mybatisflex.core.util.ClassUtil;
027import com.mybatisflex.core.util.FieldWrapper;
028import com.mybatisflex.core.util.StringUtil;
029
030import java.lang.reflect.Field;
031import java.util.*;
032
033import static com.mybatisflex.core.query.QueryMethods.column;
034
035public abstract class AbstractRelation<SelfEntity> {
036
037    protected String name;
038    protected String simpleName;
039    protected Class<SelfEntity> selfEntityClass;
040    protected Field relationField;
041    protected FieldWrapper relationFieldWrapper;
042
043    protected Field selfField;
044    protected FieldWrapper selfFieldWrapper;
045
046    protected String targetSchema;
047    protected String targetTable;
048    protected Field targetField;
049
050    protected String valueField;
051    protected boolean onlyQueryValueField;
052
053    protected Class<?> targetEntityClass;
054    protected TableInfo targetTableInfo;
055    protected FieldWrapper targetFieldWrapper;
056
057    protected String joinTable;
058    protected String joinSelfColumn;
059    protected String joinTargetColumn;
060
061    protected String dataSource;
062
063    protected String extraConditionSql;
064    protected List<String> extraConditionParamKeys;
065
066    protected QueryColumn conditionColumn;
067    protected String[] selectColumns;
068
069    public AbstractRelation(String selfField, String targetSchema, String targetTable, String targetField, String valueField,
070                            String joinTable, String joinSelfColumn, String joinTargetColumn,
071                            String dataSource, Class<SelfEntity> entityClass, Field relationField,
072                            String extraCondition, String[] selectColumns
073    ) {
074        this.name = entityClass.getSimpleName() + "." + relationField.getName();
075        this.simpleName = relationField.getName();
076        this.selfEntityClass = entityClass;
077        this.relationField = relationField;
078        this.relationFieldWrapper = FieldWrapper.of(entityClass, relationField.getName());
079
080        this.joinTable = joinTable;
081        this.joinSelfColumn = joinSelfColumn;
082        this.joinTargetColumn = joinTargetColumn;
083
084        this.dataSource = dataSource;
085
086        this.selfField = ClassUtil.getFirstField(entityClass, field -> field.getName().equalsIgnoreCase(selfField));
087        this.selfFieldWrapper = FieldWrapper.of(entityClass, selfField);
088
089        String tableNameWithSchema = StringUtil.buildSchemaWithTable(targetSchema, targetTable);
090        //以使用者注解配置为主
091        this.targetTableInfo = StringUtil.noText(targetTable) ? TableInfoFactory.ofEntityClass(relationFieldWrapper.getMappingType()) : TableInfoFactory.ofTableName(tableNameWithSchema);
092        this.targetSchema = targetTableInfo != null ? targetTableInfo.getSchema() : targetSchema;
093        this.targetTable = targetTableInfo != null ? targetTableInfo.getTableName() : targetTable;
094
095        //当指定了 valueField 的时候,一般是 String Integer 等基本数据类型
096        this.targetEntityClass = (StringUtil.hasText(valueField) && targetTableInfo != null) ? targetTableInfo.getEntityClass() : relationFieldWrapper.getMappingType();
097
098        this.targetField = ClassUtil.getFirstField(targetEntityClass, field -> field.getName().equalsIgnoreCase(targetField));
099        if (this.targetField == null) {
100            throw new IllegalStateException("Can not find field by name \"" + targetField + "\" from class: " + targetEntityClass.getName());
101        }
102
103        this.targetFieldWrapper = FieldWrapper.of(targetEntityClass, targetField);
104
105        this.valueField = valueField;
106        this.onlyQueryValueField = StringUtil.hasText(valueField);
107
108        this.conditionColumn = targetTableInfo == null ? column(targetTable, StringUtil.camelToUnderline(this.targetField.getName()))
109            : column(targetTable, targetTableInfo.getColumnByProperty(this.targetField.getName()));
110
111        if (onlyQueryValueField) {
112            //仅绑定字段时只需要查询关联列和该字段列即可
113            this.selectColumns = new String[]{conditionColumn.getName(), targetTableInfo != null ? targetTableInfo.getColumnByProperty(this.valueField) : StringUtil.camelToUnderline(this.valueField)};
114        } else {
115            if (ArrayUtil.isNotEmpty(selectColumns)) {
116                if (ArrayUtil.contains(selectColumns, conditionColumn.getName())) {
117                    this.selectColumns = selectColumns;
118                } else {
119                    //需要追加 conditionColumn,因为进行内存 join 的时候,需要用到这个内容进行对比
120                    this.selectColumns = ArrayUtil.concat(selectColumns, new String[]{conditionColumn.getName()});
121                }
122            }
123
124        }
125
126        initExtraCondition(extraCondition);
127    }
128
129    protected void initExtraCondition(String extraCondition) {
130        if (StringUtil.noText(extraCondition)) {
131            return;
132        }
133
134
135        List<String> sqlParamKeys = null;
136        char[] chars = extraCondition.toCharArray();
137        StringBuilder sqlBuilder = new StringBuilder();
138        sqlBuilder.append(chars[0]);
139        char prev, current;
140        boolean keyStart = false;
141        StringBuilder currentKey = null;
142        for (int i = 1; i < chars.length; i++) {
143            prev = chars[i - 1];
144            current = chars[i];
145            if (prev == ' ' && current == ':') {
146                keyStart = true;
147                currentKey = new StringBuilder();
148            } else if (keyStart) {
149                if (current != ' ' && current != ')') {
150                    currentKey.append(current);
151                } else {
152                    if (sqlParamKeys == null) {
153                        sqlParamKeys = new ArrayList<>();
154                    }
155                    sqlParamKeys.add(currentKey.toString());
156                    sqlBuilder.append("?").append(current);
157                    keyStart = false;
158                    currentKey = null;
159                }
160            } else {
161                sqlBuilder.append(current);
162            }
163        }
164        if (keyStart && currentKey != null && currentKey.length() > 0) {
165            if (sqlParamKeys == null) {
166                sqlParamKeys = new ArrayList<>();
167            }
168            sqlParamKeys.add(currentKey.toString());
169            sqlBuilder.append(" ?");
170        }
171
172        this.extraConditionSql = sqlBuilder.toString();
173        this.extraConditionParamKeys = sqlParamKeys != null ? sqlParamKeys : Collections.emptyList();
174    }
175
176    public String getName() {
177        return name;
178    }
179
180    public String getSimpleName() {
181        return simpleName;
182    }
183
184    public Class<SelfEntity> getSelfEntityClass() {
185        return selfEntityClass;
186    }
187
188    public void setSelfEntityClass(Class<SelfEntity> selfEntityClass) {
189        this.selfEntityClass = selfEntityClass;
190    }
191
192    public Field getRelationField() {
193        return relationField;
194    }
195
196    public void setRelationField(Field relationField) {
197        this.relationField = relationField;
198    }
199
200    public FieldWrapper getRelationFieldWrapper() {
201        return relationFieldWrapper;
202    }
203
204    public void setRelationFieldWrapper(FieldWrapper relationFieldWrapper) {
205        this.relationFieldWrapper = relationFieldWrapper;
206    }
207
208    public Field getSelfField() {
209        return selfField;
210    }
211
212    public void setSelfField(Field selfField) {
213        this.selfField = selfField;
214    }
215
216    public FieldWrapper getSelfFieldWrapper() {
217        return selfFieldWrapper;
218    }
219
220    public void setSelfFieldWrapper(FieldWrapper selfFieldWrapper) {
221        this.selfFieldWrapper = selfFieldWrapper;
222    }
223
224    public Field getTargetField() {
225        return targetField;
226    }
227
228    public void setTargetField(Field targetField) {
229        this.targetField = targetField;
230    }
231
232    public Class<?> getTargetEntityClass() {
233        return targetEntityClass;
234    }
235
236    public void setTargetEntityClass(Class<?> targetEntityClass) {
237        this.targetEntityClass = targetEntityClass;
238    }
239
240    public TableInfo getTargetTableInfo() {
241        return targetTableInfo;
242    }
243
244    public void setTargetTableInfo(TableInfo targetTableInfo) {
245        this.targetTableInfo = targetTableInfo;
246    }
247
248    public FieldWrapper getTargetFieldWrapper() {
249        return targetFieldWrapper;
250    }
251
252    public void setTargetFieldWrapper(FieldWrapper targetFieldWrapper) {
253        this.targetFieldWrapper = targetFieldWrapper;
254    }
255
256    public String getTargetSchema() {
257        return targetSchema;
258    }
259
260    public void setTargetSchema(String targetSchema) {
261        this.targetSchema = targetSchema;
262    }
263
264    public String getTargetTable() {
265        return targetTable;
266    }
267
268    public void setTargetTable(String targetTable) {
269        this.targetTable = targetTable;
270    }
271
272    public String getValueField() {
273        return valueField;
274    }
275
276    public void setValueField(String valueField) {
277        this.valueField = valueField;
278    }
279
280    public boolean isOnlyQueryValueField() {
281        return onlyQueryValueField;
282    }
283
284    public void setOnlyQueryValueField(boolean onlyQueryValueField) {
285        this.onlyQueryValueField = onlyQueryValueField;
286    }
287
288    public String getJoinTable() {
289        return joinTable;
290    }
291
292    public void setJoinTable(String joinTable) {
293        this.joinTable = joinTable;
294    }
295
296    public String getJoinSelfColumn() {
297        return joinSelfColumn;
298    }
299
300    public void setJoinSelfColumn(String joinSelfColumn) {
301        this.joinSelfColumn = joinSelfColumn;
302    }
303
304    public String getJoinTargetColumn() {
305        return joinTargetColumn;
306    }
307
308    public void setJoinTargetColumn(String joinTargetColumn) {
309        this.joinTargetColumn = joinTargetColumn;
310    }
311
312    public Set<Object> getSelfFieldValues(List<SelfEntity> selfEntities) {
313        if (selfEntities == null || selfEntities.isEmpty()) {
314            return Collections.emptySet();
315        }
316        Set<Object> values = new LinkedHashSet<>();
317        selfEntities.forEach(self -> {
318            Object value = selfFieldWrapper.get(self);
319            if (value != null && !"".equals(value)) {
320                values.add(value);
321            }
322        });
323        return values;
324    }
325
326
327    public Class<?> getMappingType() {
328        return relationFieldWrapper.getMappingType();
329    }
330
331    public String getDataSource() {
332        return dataSource;
333    }
334
335    public void setDataSource(String dataSource) {
336        this.dataSource = dataSource;
337    }
338
339    public String getTargetTableWithSchema() {
340        if (StringUtil.hasText(targetTable)) {
341            return StringUtil.hasText(targetSchema) ? targetSchema + "." + targetTable : targetTable;
342        } else {
343            return targetTableInfo.getTableNameWithSchema();
344        }
345    }
346
347    protected boolean isRelationByMiddleTable() {
348        return StringUtil.hasText(joinTable);
349    }
350
351
352    protected static Class<?> getTargetEntityClass(Class<?> entityClass, Field relationField) {
353        return FieldWrapper.of(entityClass, relationField.getName()).getMappingType();
354    }
355
356    protected static String getDefaultPrimaryProperty(String key, Class<?> entityClass, String message) {
357        if (StringUtil.hasText(key)) {
358            return key;
359        }
360
361        TableInfo tableInfo = TableInfoFactory.ofEntityClass(entityClass);
362        List<IdInfo> primaryKeyList = tableInfo.getPrimaryKeyList();
363        if (primaryKeyList == null || primaryKeyList.size() != 1) {
364            throw FlexExceptions.wrap(message);
365        }
366
367        return primaryKeyList.get(0).getProperty();
368    }
369
370
371    /**
372     * 构建查询目标对象的 QueryWrapper
373     *
374     * @param targetValues 条件的值
375     * @return QueryWrapper
376     */
377    public QueryWrapper buildQueryWrapper(Set<Object> targetValues) {
378        QueryWrapper queryWrapper = QueryWrapper.create();
379
380        if (ArrayUtil.isNotEmpty(selectColumns)) {
381            queryWrapper.select(selectColumns);
382        }
383
384        queryWrapper.from(getTargetTableWithSchema());
385
386        if (targetValues.size() > 1) {
387            queryWrapper.where(conditionColumn.in(targetValues));
388        } else {
389            queryWrapper.where(conditionColumn.eq(targetValues.iterator().next()));
390        }
391
392        if (StringUtil.hasText(extraConditionSql)) {
393            queryWrapper.and(extraConditionSql, RelationManager.getExtraConditionParams(extraConditionParamKeys));
394        }
395
396        customizeQueryWrapper(queryWrapper);
397
398        return queryWrapper;
399    }
400
401
402    /**
403     * 方便子类追加自定义的条件
404     *
405     * @param queryWrapper 查询条件
406     */
407    public void customizeQueryWrapper(QueryWrapper queryWrapper) {
408        //do thing
409    }
410
411
412    /**
413     * @param selfEntities     当前的实体类列表
414     * @param targetObjectList 查询到的结果
415     * @param mappingRows      中间表的映射数据,非中间表查询的场景下,mappingRows 永远为 null
416     */
417    public abstract void join(List<SelfEntity> selfEntities, List<?> targetObjectList, List<Row> mappingRows);
418
419}