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