001/*
002 *  Copyright (c) 2022-2024, 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.table;
017
018import com.mybatisflex.annotation.Column;
019import com.mybatisflex.annotation.InsertListener;
020import com.mybatisflex.annotation.KeyType;
021import com.mybatisflex.annotation.SetListener;
022import com.mybatisflex.annotation.UpdateListener;
023import com.mybatisflex.core.FlexConsts;
024import com.mybatisflex.core.FlexGlobalConfig;
025import com.mybatisflex.core.constant.SqlConsts;
026import com.mybatisflex.core.constant.SqlOperator;
027import com.mybatisflex.core.dialect.IDialect;
028import com.mybatisflex.core.dialect.OperateType;
029import com.mybatisflex.core.exception.FlexExceptions;
030import com.mybatisflex.core.exception.locale.LocalizedFormats;
031import com.mybatisflex.core.logicdelete.LogicDeleteManager;
032import com.mybatisflex.core.mybatis.TypeHandlerObject;
033import com.mybatisflex.core.optimisticlock.OptimisticLockManager;
034import com.mybatisflex.core.query.Brackets;
035import com.mybatisflex.core.query.CPI;
036import com.mybatisflex.core.query.Join;
037import com.mybatisflex.core.query.QueryColumn;
038import com.mybatisflex.core.query.QueryCondition;
039import com.mybatisflex.core.query.QueryMethods;
040import com.mybatisflex.core.query.QueryTable;
041import com.mybatisflex.core.query.QueryWrapper;
042import com.mybatisflex.core.query.SelectQueryColumn;
043import com.mybatisflex.core.query.SelectQueryTable;
044import com.mybatisflex.core.query.SqlOperators;
045import com.mybatisflex.core.query.UnionWrapper;
046import com.mybatisflex.core.row.Row;
047import com.mybatisflex.core.tenant.TenantManager;
048import com.mybatisflex.core.update.RawValue;
049import com.mybatisflex.core.update.UpdateWrapper;
050import com.mybatisflex.core.util.ArrayUtil;
051import com.mybatisflex.core.util.ClassUtil;
052import com.mybatisflex.core.util.CollectionUtil;
053import com.mybatisflex.core.util.ConvertUtil;
054import com.mybatisflex.core.util.EnumWrapper;
055import com.mybatisflex.core.util.FieldWrapper;
056import com.mybatisflex.core.util.MapUtil;
057import com.mybatisflex.core.util.ObjectUtil;
058import com.mybatisflex.core.util.SqlUtil;
059import com.mybatisflex.core.util.StringUtil;
060import org.apache.ibatis.mapping.ResultFlag;
061import org.apache.ibatis.mapping.ResultMap;
062import org.apache.ibatis.mapping.ResultMapping;
063import org.apache.ibatis.reflection.MetaObject;
064import org.apache.ibatis.reflection.Reflector;
065import org.apache.ibatis.reflection.ReflectorFactory;
066import org.apache.ibatis.session.Configuration;
067import org.apache.ibatis.type.TypeHandler;
068
069import java.lang.reflect.Field;
070import java.lang.reflect.Proxy;
071import java.sql.ResultSet;
072import java.sql.SQLException;
073import java.util.ArrayList;
074import java.util.Arrays;
075import java.util.Collections;
076import java.util.HashMap;
077import java.util.HashSet;
078import java.util.LinkedHashMap;
079import java.util.LinkedHashSet;
080import java.util.List;
081import java.util.Map;
082import java.util.Objects;
083import java.util.Set;
084import java.util.StringJoiner;
085import java.util.concurrent.ConcurrentHashMap;
086import java.util.function.Function;
087import java.util.stream.Collectors;
088
089import static com.mybatisflex.core.constant.SqlConsts.AND;
090import static com.mybatisflex.core.constant.SqlConsts.EQUALS_PLACEHOLDER;
091import static com.mybatisflex.core.constant.SqlConsts.IN;
092
093public class TableInfo {
094
095    // column 和 java 属性的称的关系映射
096    private final Map<String, ColumnInfo> columnInfoMapping = new HashMap<>();
097    // property:column
098    private final Map<String, String> propertyColumnMapping = new LinkedHashMap<>();
099    private String schema; // schema
100    private boolean camelToUnderline = true;
101    private String dataSource;
102
103    private String comment;
104    private String tableName; // 表名
105    private Class<?> entityClass; // 实体类
106    // 逻辑删除数据库列名
107    private String logicDeleteColumn;
108    // 乐观锁字段
109    private String versionColumn;
110    // 租户ID 字段
111    private String tenantIdColumn;
112    // 数据插入时,默认插入数据字段
113    private Map<String, String> onInsertColumns;
114
115    private String[] allColumns = new String[0];
116    // 数据更新时,默认更新内容的字段
117    private Map<String, String> onUpdateColumns;
118    // 大字段列
119    private String[] largeColumns = new String[0];
120
121    // 默认查询列,排除 large 等字段
122    private String[] defaultQueryColumns = new String[0];
123    // 所有的字段,但除了主键的列
124    private String[] columns = new String[0];
125
126    private List<ColumnInfo> columnInfoList;
127    private List<IdInfo> primaryKeyList;
128    // 主键字段
129    private String[] primaryColumns = new String[0];
130    private final Map<String, QueryColumn> columnQueryMapping = new HashMap<>();
131    // 在插入数据的时候,支持主动插入的主键字段,自增字段不需要主动插入
132    // 但通过自定义生成器生成 或者 Sequence 在 before 生成的时候,是需要主动插入数据的
133    private String[] insertPrimaryKeys;
134
135    private List<InsertListener> onInsertListeners;
136    private List<UpdateListener> onUpdateListeners;
137    private List<SetListener> onSetListeners;
138
139    /**
140     * 对应 MapperXML 配置文件中 {@code <resultMap>} 标签下的 {@code <association>} 标签。
141     */
142    private Map<String, Class<?>> associationType;
143
144    /**
145     * 对应 MapperXML 配置文件中 {@code <resultMap>} 标签下的 {@code <collection>} 标签。
146     */
147    private Map<Field, Class<?>> collectionType;
148
149
150    private final ReflectorFactory reflectorFactory = new BaseReflectorFactory() {
151        @Override
152        public Reflector findForClass(Class<?> type) {
153            return getReflector();
154        }
155    };
156    private Reflector reflector; // 反射工具
157
158    public String getSchema() {
159        return schema;
160    }
161
162    public void setSchema(String schema) {
163        this.schema = schema;
164    }
165
166    public Map<String, String> getPropertyColumnMapping() {
167        return this.propertyColumnMapping;
168    }
169
170    public String getTableName() {
171        return tableName;
172    }
173
174    public String getTableNameWithSchema() {
175        return StringUtil.buildSchemaWithTable(schema, tableName);
176    }
177
178    public String getWrapSchemaAndTableName(IDialect dialect, OperateType operateType) {
179        if (StringUtil.isNotBlank(schema)) {
180            String table = dialect.getRealTable(tableName, operateType);
181            return dialect.wrap(dialect.getRealSchema(schema, table, operateType)) + "." + dialect.wrap(table);
182        } else {
183            return dialect.wrap(dialect.getRealTable(tableName, operateType));
184        }
185    }
186
187    public void setTableName(String tableName) {
188        int indexOf = tableName.indexOf(".");
189        if (indexOf > 0) {
190            if (StringUtil.isBlank(schema)) {
191                this.schema = tableName.substring(0, indexOf);
192                this.tableName = tableName.substring(indexOf + 1);
193            } else {
194                this.tableName = tableName;
195            }
196        } else {
197            this.tableName = tableName;
198        }
199    }
200
201    public Class<?> getEntityClass() {
202        return entityClass;
203    }
204
205    public void setEntityClass(Class<?> entityClass) {
206        this.entityClass = entityClass;
207    }
208
209    public boolean isCamelToUnderline() {
210        return camelToUnderline;
211    }
212
213    public void setCamelToUnderline(boolean camelToUnderline) {
214        this.camelToUnderline = camelToUnderline;
215    }
216
217    public String getDataSource() {
218        return dataSource;
219    }
220
221    public void setDataSource(String dataSource) {
222        this.dataSource = dataSource;
223    }
224
225    public String getComment() {
226        return comment;
227    }
228
229    public void setComment(String comment) {
230        this.comment = comment;
231    }
232
233    public String getLogicDeleteColumnOrSkip() {
234        return LogicDeleteManager.getLogicDeleteColumn(logicDeleteColumn);
235    }
236
237    public String getLogicDeleteColumn() {
238        return logicDeleteColumn;
239    }
240
241    public void setLogicDeleteColumn(String logicDeleteColumn) {
242        this.logicDeleteColumn = logicDeleteColumn;
243    }
244
245    public String getOptimisticLockColumnOrSkip() {
246        return OptimisticLockManager.getOptimisticLockColumn(versionColumn);
247    }
248
249    public String getVersionColumn() {
250        return versionColumn;
251    }
252
253    public void setVersionColumn(String versionColumn) {
254        this.versionColumn = versionColumn;
255    }
256
257    public String getTenantIdColumn() {
258        return tenantIdColumn;
259    }
260
261    public void setTenantIdColumn(String tenantIdColumn) {
262        this.tenantIdColumn = tenantIdColumn;
263    }
264
265    public Map<String, String> getOnInsertColumns() {
266        return onInsertColumns;
267    }
268
269    public void setOnInsertColumns(Map<String, String> onInsertColumns) {
270        this.onInsertColumns = onInsertColumns;
271    }
272
273    public Map<String, String> getOnUpdateColumns() {
274        return onUpdateColumns;
275    }
276
277    public void setOnUpdateColumns(Map<String, String> onUpdateColumns) {
278        this.onUpdateColumns = onUpdateColumns;
279    }
280
281    public String[] getLargeColumns() {
282        return largeColumns;
283    }
284
285    public void setLargeColumns(String[] largeColumns) {
286        this.largeColumns = largeColumns;
287    }
288
289    public String[] getDefaultQueryColumns() {
290        return defaultQueryColumns;
291    }
292
293    public void setDefaultQueryColumns(String[] defaultQueryColumns) {
294        this.defaultQueryColumns = defaultQueryColumns;
295    }
296
297    public String[] getInsertPrimaryKeys() {
298        return insertPrimaryKeys;
299    }
300
301    public void setInsertPrimaryKeys(String[] insertPrimaryKeys) {
302        this.insertPrimaryKeys = insertPrimaryKeys;
303    }
304
305    public Reflector getReflector() {
306        return reflector;
307    }
308
309    public ReflectorFactory getReflectorFactory() {
310        return reflectorFactory;
311    }
312
313    public void setReflector(Reflector reflector) {
314        this.reflector = reflector;
315    }
316
317    public String[] getAllColumns() {
318        return allColumns;
319    }
320
321    public void setAllColumns(String[] allColumns) {
322        this.allColumns = allColumns;
323    }
324
325    public String[] getColumns() {
326        return columns;
327    }
328
329
330    public void setColumns(String[] columns) {
331        this.columns = columns;
332    }
333
334    public String[] getPrimaryColumns() {
335        return primaryColumns;
336    }
337
338    public void setPrimaryColumns(String[] primaryColumns) {
339        this.primaryColumns = primaryColumns;
340    }
341
342
343    public List<InsertListener> getOnInsertListeners() {
344        return onInsertListeners;
345    }
346
347    public void setOnInsertListeners(List<InsertListener> onInsertListeners) {
348        this.onInsertListeners = onInsertListeners;
349    }
350
351    public List<UpdateListener> getOnUpdateListeners() {
352        return onUpdateListeners;
353    }
354
355    public void setOnUpdateListeners(List<UpdateListener> onUpdateListeners) {
356        this.onUpdateListeners = onUpdateListeners;
357    }
358
359    public List<SetListener> getOnSetListeners() {
360        return onSetListeners;
361    }
362
363    public void setOnSetListeners(List<SetListener> onSetListeners) {
364        this.onSetListeners = onSetListeners;
365    }
366
367    public List<ColumnInfo> getColumnInfoList() {
368        return columnInfoList;
369    }
370
371    public String getColumnByProperty(String property) {
372        String column = propertyColumnMapping.get(property);
373        // 用于兼容字段MM不规范的情况
374        // fix https://gitee.com/mybatis-flex/mybatis-flex/issues/I9PDYO
375        if (column == null) {
376            for (Map.Entry<String, String> entry : propertyColumnMapping.entrySet()) {
377                if (property.equalsIgnoreCase(entry.getKey())) {
378                    column = entry.getValue();
379                    break;
380                }
381            }
382        }
383        return StringUtil.isNotBlank(column) ? column : property;
384    }
385
386    public Map<String, Class<?>> getAssociationType() {
387        return associationType;
388    }
389
390    public void setAssociationType(Map<String, Class<?>> associationType) {
391        this.associationType = associationType;
392    }
393
394    public void addAssociationType(String fieldName, Class<?> clazz) {
395        if (associationType == null) {
396            associationType = new HashMap<>();
397        }
398        associationType.put(fieldName, clazz);
399    }
400
401    public Map<Field, Class<?>> getCollectionType() {
402        return collectionType;
403    }
404
405    public void setCollectionType(Map<Field, Class<?>> collectionType) {
406        this.collectionType = collectionType;
407    }
408
409    public void addCollectionType(Field field, Class<?> genericClass) {
410        if (collectionType == null) {
411            collectionType = new HashMap<>();
412        }
413        collectionType.put(field, genericClass);
414    }
415
416    void setColumnInfoList(List<ColumnInfo> columnInfoList) {
417        this.columnInfoList = columnInfoList;
418        List<String> columnNames = new ArrayList<>();
419        for (int i = 0; i < columnInfoList.size(); i++) {
420            ColumnInfo columnInfo = columnInfoList.get(i);
421            // 真正的字段(没有做忽略标识)
422            if (!columnInfo.isIgnore()) {
423                columnNames.add(columnInfo.column);
424
425                columnInfoMapping.put(columnInfo.column, columnInfo);
426                propertyColumnMapping.put(columnInfo.property, columnInfo.column);
427
428                String[] alias = columnInfo.getAlias();
429                columnQueryMapping.put(columnInfo.column, new QueryColumn(schema, tableName, columnInfo.column, alias != null && alias.length > 0 ? alias[0] : null));
430            }
431        }
432
433        this.columns = columnNames.toArray(new String[]{});
434        this.allColumns = ArrayUtil.concat(allColumns, columns);
435    }
436
437
438    public List<IdInfo> getPrimaryKeyList() {
439        return primaryKeyList;
440    }
441
442    void setPrimaryKeyList(List<IdInfo> primaryKeyList) {
443        this.primaryKeyList = primaryKeyList;
444        this.primaryColumns = new String[primaryKeyList.size()];
445
446        List<String> insertIdFields = new ArrayList<>();
447
448        for (int i = 0; i < primaryKeyList.size(); i++) {
449            IdInfo idInfo = primaryKeyList.get(i);
450            primaryColumns[i] = idInfo.getColumn();
451
452            if (idInfo.getKeyType() != KeyType.Auto
453                && (idInfo.getBefore() != null && idInfo.getBefore())
454            ) {
455                insertIdFields.add(idInfo.getColumn());
456            }
457
458            columnInfoMapping.put(idInfo.column, idInfo);
459            propertyColumnMapping.put(idInfo.property, idInfo.column);
460
461            String[] alias = idInfo.getAlias();
462            columnQueryMapping.put(idInfo.column, new QueryColumn(schema, tableName, idInfo.column, alias != null && alias.length > 0 ? alias[0] : null));
463        }
464        this.allColumns = ArrayUtil.concat(allColumns, primaryColumns);
465        this.insertPrimaryKeys = insertIdFields.toArray(new String[0]);
466    }
467
468
469    /**
470     * 构建 insert 的 Sql 参数
471     *
472     * @param entity      从 entity 中获取
473     * @param ignoreNulls 是否忽略 null 值
474     * @return 数组
475     */
476    public Object[] buildInsertSqlArgs(Object entity, boolean ignoreNulls) {
477        MetaObject metaObject = EntityMetaObject.forObject(entity, reflectorFactory);
478        String[] insertColumns = obtainInsertColumns(entity, ignoreNulls);
479
480        Map<String, RawValue> rawValueMap = obtainUpdateRawValueMap(entity);
481
482        List<Object> values = new ArrayList<>();
483        for (String insertColumn : insertColumns) {
484            if (onInsertColumns == null || !onInsertColumns.containsKey(insertColumn)) {
485                if (rawValueMap.containsKey(insertColumn)) {
486                    values.addAll(Arrays.asList(rawValueMap.remove(insertColumn).getParams()));
487                    continue;
488                }
489                Object value = buildColumnSqlArg(metaObject, insertColumn);
490                if (ignoreNulls && value == null) {
491                    continue;
492                }
493                values.add(value);
494            }
495        }
496        values.addAll(rawValueMap.values()
497            .stream()
498            .flatMap(e -> Arrays.stream(e.getParams()))
499            .collect(Collectors.toList()));
500        return values.toArray();
501    }
502
503    /**
504     * 插入(新增)数据时,获取所有要插入的字段
505     *
506     * @param entity
507     * @param ignoreNulls
508     * @return 字段列表
509     */
510    public String[] obtainInsertColumns(Object entity, boolean ignoreNulls) {
511        if (!ignoreNulls) {
512            return ArrayUtil.concat(insertPrimaryKeys, columns);
513        }
514        // 忽略 null 字段,
515        else {
516            MetaObject metaObject = EntityMetaObject.forObject(entity, reflectorFactory);
517            List<String> retColumns = new ArrayList<>();
518            for (String insertColumn : allColumns) {
519                if (onInsertColumns != null && onInsertColumns.containsKey(insertColumn)) {
520                    retColumns.add(insertColumn);
521                } else {
522                    Object value = buildColumnSqlArg(metaObject, insertColumn);
523                    if (value == null) {
524                        continue;
525                    }
526                    retColumns.add(insertColumn);
527                }
528            }
529            return retColumns.toArray(new String[0]);
530        }
531    }
532
533
534    public Object[] buildInsertSqlArgsWithPk(Object entity, boolean ignoreNulls) {
535        MetaObject metaObject = EntityMetaObject.forObject(entity, reflectorFactory);
536        String[] insertColumns = obtainInsertColumnsWithPk(entity, ignoreNulls);
537
538        List<Object> values = new ArrayList<>(insertColumns.length);
539        for (String insertColumn : insertColumns) {
540            if (onInsertColumns == null || !onInsertColumns.containsKey(insertColumn)) {
541                Object value = buildColumnSqlArg(metaObject, insertColumn);
542                if (ignoreNulls && value == null) {
543                    continue;
544                }
545                values.add(value);
546            }
547        }
548        return values.toArray();
549    }
550
551
552    /**
553     * 插入(新增)数据时,获取所有要插入的字段
554     *
555     * @param entity
556     * @param ignoreNulls
557     * @return 字段列表
558     */
559    public String[] obtainInsertColumnsWithPk(Object entity, boolean ignoreNulls) {
560        if (!ignoreNulls) {
561            return allColumns;
562        } else {
563            MetaObject metaObject = EntityMetaObject.forObject(entity, reflectorFactory);
564            List<String> retColumns = new ArrayList<>();
565            for (String primaryKey : primaryColumns) {
566                Object value = buildColumnSqlArg(metaObject, primaryKey);
567                if (value == null) {
568                    throw new IllegalArgumentException("Entity Primary Key value must not be null.");
569                }
570                retColumns.add(primaryKey);
571            }
572            for (String insertColumn : columns) {
573                if (onInsertColumns != null && onInsertColumns.containsKey(insertColumn)) {
574                    retColumns.add(insertColumn);
575                } else {
576                    Object value = buildColumnSqlArg(metaObject, insertColumn);
577                    if (value == null) {
578                        continue;
579                    }
580                    retColumns.add(insertColumn);
581                }
582            }
583            return retColumns.toArray(new String[0]);
584        }
585    }
586
587
588    @SuppressWarnings({"unchecked", "rawtypes"})
589    public Map<String, RawValue> obtainUpdateRawValueMap(Object entity) {
590        if (!(entity instanceof UpdateWrapper)) {
591            return Collections.emptyMap();
592        }
593
594        Map<String, Object> updates = ((UpdateWrapper) entity).getUpdates();
595        if (updates.isEmpty()) {
596            return Collections.emptyMap();
597        }
598
599        Map<String, RawValue> map = new HashMap<>();
600        updates.forEach((key, value) -> {
601            if (value instanceof RawValue) {
602                String column = getColumnByProperty(key);
603                map.put(column, (RawValue) value);
604            }
605        });
606
607        return map;
608    }
609
610    /**
611     * 获取要修改的值
612     *
613     * @param entity
614     * @param ignoreNulls
615     */
616    public Set<String> obtainUpdateColumns(Object entity, boolean ignoreNulls, boolean includePrimary) {
617        MetaObject metaObject = EntityMetaObject.forObject(entity, reflectorFactory);
618        Set<String> columns = new LinkedHashSet<>(); // 需使用 LinkedHashSet 保证 columns 的顺序
619        if (entity instanceof UpdateWrapper) {
620            Map<String, Object> updates = ((UpdateWrapper) entity).getUpdates();
621            if (updates.isEmpty()) {
622                return Collections.emptySet();
623            }
624            for (String property : updates.keySet()) {
625
626                String column = getColumnByProperty(property);
627
628                if (onUpdateColumns != null && onUpdateColumns.containsKey(column)) {
629                    continue;
630                }
631
632                // 过滤乐观锁字段 和 租户字段
633                if (ObjectUtil.equalsAny(column, versionColumn, tenantIdColumn)) {
634                    continue;
635                }
636
637                if (!includePrimary && ArrayUtil.contains(primaryColumns, column)) {
638                    continue;
639                }
640
641//                Object value = updates.get(property);
642                // ModifyAttrsRecord 忽略 ignoreNulls 的设置
643                // Object value = getPropertyValue(metaObject, property);
644                // if (ignoreNulls && value == null) {
645                //     continue;
646                // }
647                columns.add(column);
648            }
649        }
650        // not ModifyAttrsRecord
651        else {
652            for (String column : this.columns) {
653                if (onUpdateColumns != null && onUpdateColumns.containsKey(column)) {
654                    continue;
655                }
656
657                // 过滤乐观锁字段 和 租户字段
658                if (ObjectUtil.equalsAny(column, versionColumn, tenantIdColumn)) {
659                    continue;
660                }
661
662                Object value = buildColumnSqlArg(metaObject, column);
663                if (ignoreNulls && value == null) {
664                    continue;
665                }
666
667                columns.add(column);
668            }
669        }
670        return columns;
671    }
672
673    /**
674     * 获取所有要修改的值,默认为全部除了主键以外的字段
675     *
676     * @param entity 实体对象
677     * @return 数组
678     */
679    public Object[] buildUpdateSqlArgs(Object entity, boolean ignoreNulls, boolean includePrimary) {
680
681        List<Object> values = new ArrayList<>();
682        if (entity instanceof UpdateWrapper) {
683            Map<String, Object> updates = ((UpdateWrapper) entity).getUpdates();
684            if (updates.isEmpty()) {
685                return FlexConsts.EMPTY_ARRAY;
686            }
687            for (String property : updates.keySet()) {
688
689                String column = getColumnByProperty(property);
690
691                if (onUpdateColumns != null && onUpdateColumns.containsKey(column)) {
692                    continue;
693                }
694                // 过滤乐观锁字段 和 租户字段
695                if (ObjectUtil.equalsAny(column, versionColumn, tenantIdColumn)) {
696                    continue;
697                }
698
699                if (!includePrimary && ArrayUtil.contains(primaryColumns, column)) {
700                    continue;
701                }
702
703                Object value = updates.get(property);
704                if (value instanceof RawValue) {
705                    values.addAll(Arrays.asList(((RawValue) value).getParams()));
706                    continue;
707                }
708
709                if (value != null) {
710                    ColumnInfo columnInfo = columnInfoMapping.get(column);
711                    if (columnInfo != null) {
712                        TypeHandler typeHandler = columnInfo.buildTypeHandler(null);
713                        if (typeHandler != null) {
714                            value = new TypeHandlerObject(typeHandler, value, columnInfo.getJdbcType());
715                        }
716                    }
717
718                    // fixed: https://gitee.com/mybatis-flex/mybatis-flex/issues/I7TFBK
719                    if (value.getClass().isEnum()) {
720                        EnumWrapper enumWrapper = EnumWrapper.of(value.getClass());
721                        value = enumWrapper.getEnumValue((Enum) value);
722                    }
723                }
724
725                // ModifyAttrsRecord 忽略 ignoreNulls 的设置,
726                // 当使用 ModifyAttrsRecord 时,可以理解为要对字段进行 null 值进行更新,否则没必要使用 ModifyAttrsRecord
727                // if (ignoreNulls && value == null) {
728                //    continue;
729                // }
730                values.add(value);
731            }
732        }
733        // normal entity. not ModifyAttrsRecord
734        else {
735            MetaObject metaObject = EntityMetaObject.forObject(entity, reflectorFactory);
736
737            for (String column : this.columns) {
738                if (onUpdateColumns != null && onUpdateColumns.containsKey(column)) {
739                    continue;
740                }
741
742                // 过滤乐观锁字段 和 租户字段
743                if (ObjectUtil.equalsAny(column, versionColumn, tenantIdColumn)) {
744                    continue;
745                }
746
747                // 普通 entity 忽略 includePrimary 的设置,
748                // 因为 for 循环中的 this.columns 本身就不包含有主键
749                // if (includePrimary) {
750                // }
751
752                Object value = buildColumnSqlArg(metaObject, column);
753                if (ignoreNulls && value == null) {
754                    continue;
755                }
756
757                values.add(value);
758            }
759        }
760
761        return values.toArray();
762    }
763
764
765    /**
766     * 构建主键的 sql 参数数据
767     *
768     * @param entity
769     */
770    public Object[] buildPkSqlArgs(Object entity) {
771        MetaObject metaObject = EntityMetaObject.forObject(entity, reflectorFactory);
772        Object[] values = new Object[primaryColumns.length];
773        for (int i = 0; i < primaryColumns.length; i++) {
774            values[i] = buildColumnSqlArg(metaObject, primaryColumns[i]);
775        }
776        return values;
777    }
778
779    public Object getValue(Object entity, String property) {
780        FieldWrapper fieldWrapper = FieldWrapper.of(entityClass, property);
781        return fieldWrapper.get(entity);
782    }
783
784    /**
785     * 获取主键值
786     *
787     * @param entity
788     * @return 主键值,有多个主键时返回数组
789     */
790    public Object getPkValue(Object entity) {
791        // 绝大多数情况为 1 个主键
792        if (primaryColumns.length == 1) {
793            MetaObject metaObject = EntityMetaObject.forObject(entity, reflectorFactory);
794            ColumnInfo columnInfo = columnInfoMapping.get(primaryColumns[0]);
795            return getPropertyValue(metaObject, columnInfo.property);
796        }
797        // 多个主键
798        else if (primaryColumns.length > 1) {
799            MetaObject metaObject = EntityMetaObject.forObject(entity, reflectorFactory);
800            Object[] values = new Object[primaryColumns.length];
801            for (int i = 0; i < primaryColumns.length; i++) {
802                ColumnInfo columnInfo = columnInfoMapping.get(primaryColumns[i]);
803                values[i] = getPropertyValue(metaObject, columnInfo.property);
804            }
805            return values;
806        }
807        // 无主键
808        else {
809            return null;
810        }
811    }
812
813
814    public Object[] buildTenantIdArgs() {
815        if (StringUtil.isBlank(tenantIdColumn)) {
816            return null;
817        }
818
819        return TenantManager.getTenantIds(tableName);
820    }
821
822
823    public String buildTenantCondition(String sql, Object[] tenantIdArgs, IDialect dialect) {
824        if (ArrayUtil.isNotEmpty(tenantIdArgs)) {
825            if (tenantIdArgs.length == 1) {
826                return sql + AND + dialect.wrap(tenantIdColumn) + EQUALS_PLACEHOLDER;
827            } else {
828                return sql + AND + dialect.wrap(tenantIdColumn) + IN + SqlUtil.buildSqlParamPlaceholder(tenantIdArgs.length);
829            }
830        } else {
831            return sql;
832        }
833    }
834
835    public void buildTenantCondition(StringBuilder sql, Object[] tenantIdArgs, IDialect dialect) {
836        if (ArrayUtil.isNotEmpty(tenantIdArgs)) {
837            if (tenantIdArgs.length == 1) {
838                sql.append(AND).append(dialect.wrap(tenantIdColumn)).append(EQUALS_PLACEHOLDER);
839            } else {
840                sql.append(AND).append(dialect.wrap(tenantIdColumn)).append(IN).append(SqlUtil.buildSqlParamPlaceholder(tenantIdArgs.length));
841            }
842        }
843    }
844
845
846    public void buildTenantCondition(QueryWrapper queryWrapper) {
847        Object[] tenantIdArgs = buildTenantIdArgs();
848        if (ArrayUtil.isNotEmpty(tenantIdArgs)) {
849            if (tenantIdArgs.length == 1) {
850                queryWrapper.where(QueryCondition.create(schema, tableName, tenantIdColumn, SqlConsts.EQUALS, tenantIdArgs[0]));
851            } else {
852                queryWrapper.where(QueryCondition.create(schema, tableName, tenantIdColumn, SqlConsts.IN, tenantIdArgs));
853            }
854        }
855    }
856
857
858    private static final String APPEND_CONDITIONS_FLAG = "appendConditions";
859
860    public void appendConditions(Object entity, QueryWrapper queryWrapper) {
861
862        Object appendConditions = CPI.getContext(queryWrapper, APPEND_CONDITIONS_FLAG);
863        if (Boolean.TRUE.equals(appendConditions)) {
864            return;
865        } else {
866            CPI.putContext(queryWrapper, APPEND_CONDITIONS_FLAG, Boolean.TRUE);
867        }
868
869        // select xxx.id,(select..) from xxx
870        List<QueryColumn> selectColumns = CPI.getSelectColumns(queryWrapper);
871        if (selectColumns != null && !selectColumns.isEmpty()) {
872            for (QueryColumn queryColumn : selectColumns) {
873                if (queryColumn instanceof SelectQueryColumn) {
874                    QueryWrapper selectColumnQueryWrapper = CPI.getQueryWrapper((SelectQueryColumn) queryColumn);
875                    doAppendConditions(entity, selectColumnQueryWrapper);
876                }
877            }
878        }
879
880        // select * from (select ... from ) 中的子查询处理
881        List<QueryTable> queryTables = CPI.getQueryTables(queryWrapper);
882        if (queryTables != null && !queryTables.isEmpty()) {
883            for (QueryTable queryTable : queryTables) {
884                if (queryTable instanceof SelectQueryTable) {
885                    QueryWrapper selectQueryWrapper = ((SelectQueryTable) queryTable).getQueryWrapper();
886                    doAppendConditions(entity, selectQueryWrapper);
887                }
888            }
889        }
890
891        // 添加乐观锁条件,只有在 update 的时候进行处理
892        if (StringUtil.isNotBlank(getOptimisticLockColumnOrSkip()) && entity != null) {
893            Object versionValue = buildColumnSqlArg(entity, versionColumn);
894            if (versionValue == null) {
895                throw FlexExceptions.wrap(LocalizedFormats.ENTITY_VERSION_NULL, entity);
896            }
897            queryWrapper.and(QueryCondition.create(schema, tableName, versionColumn, SqlConsts.EQUALS, versionValue));
898        }
899
900        // 逻辑删除
901        if (StringUtil.isNotBlank(getLogicDeleteColumnOrSkip())) {
902            // 逻辑删除时 保证前面的条件被括号包裹
903            // fix:https://gitee.com/mybatis-flex/mybatis-flex/issues/I9163G
904            QueryCondition whereCondition = CPI.getWhereQueryCondition(queryWrapper);
905            if (whereCondition != null && !(whereCondition instanceof Brackets)) {
906                QueryCondition wrappedCondition = new Brackets(whereCondition);
907                CPI.setWhereQueryCondition(queryWrapper, wrappedCondition);
908            }
909
910            String joinTableAlias = CPI.getContext(queryWrapper, "joinTableAlias");
911            LogicDeleteManager.getProcessor().buildQueryCondition(queryWrapper, this, joinTableAlias);
912        }
913
914        // 多租户
915        buildTenantCondition(queryWrapper);
916
917
918        // 子查询
919        List<QueryWrapper> childSelects = CPI.getChildSelect(queryWrapper);
920        if (CollectionUtil.isNotEmpty(childSelects)) {
921            for (QueryWrapper childQueryWrapper : childSelects) {
922                doAppendConditions(entity, childQueryWrapper);
923            }
924        }
925
926
927        // join
928        List<Join> joins = CPI.getJoins(queryWrapper);
929        if (CollectionUtil.isNotEmpty(joins)) {
930            for (Join join : joins) {
931                if (!join.checkEffective()) {
932                    continue;
933                }
934                QueryTable joinQueryTable = CPI.getJoinQueryTable(join);
935
936                // join select
937                if (joinQueryTable instanceof SelectQueryTable) {
938                    QueryWrapper childQuery = ((SelectQueryTable) joinQueryTable).getQueryWrapper();
939                    doAppendConditions(entity, childQuery);
940                }
941                // join table
942                else {
943                    String nameWithSchema = joinQueryTable.getNameWithSchema();
944                    if (StringUtil.isNotBlank(nameWithSchema)) {
945                        TableInfo tableInfo = TableInfoFactory.ofTableName(nameWithSchema);
946                        if (tableInfo != null) {
947                            QueryCondition joinQueryCondition = CPI.getJoinQueryCondition(join);
948                            QueryWrapper newWrapper = QueryWrapper.create()
949                                .where(joinQueryCondition);
950                            CPI.putContext(newWrapper, "joinTableAlias", joinQueryTable.getAlias());
951                            tableInfo.appendConditions(entity, newWrapper);
952                            QueryCondition whereQueryCondition = CPI.getWhereQueryCondition(newWrapper);
953                            CPI.setJoinQueryCondition(join, whereQueryCondition);
954                        }
955                    }
956                }
957            }
958        }
959
960        // union
961        List<UnionWrapper> unions = CPI.getUnions(queryWrapper);
962        if (CollectionUtil.isNotEmpty(unions)) {
963            for (UnionWrapper union : unions) {
964                QueryWrapper unionQueryWrapper = union.getQueryWrapper();
965                doAppendConditions(entity, unionQueryWrapper);
966            }
967        }
968    }
969
970
971    private void doAppendConditions(Object entity, QueryWrapper queryWrapper) {
972        List<QueryTable> queryTables = CPI.getQueryTables(queryWrapper);
973        if (queryTables != null && !queryTables.isEmpty()) {
974            for (QueryTable queryTable : queryTables) {
975                if (queryTable instanceof SelectQueryTable) {
976                    QueryWrapper childQuery = ((SelectQueryTable) queryTable).getQueryWrapper();
977                    doAppendConditions(entity, childQuery);
978                } else {
979                    String nameWithSchema = queryTable.getNameWithSchema();
980                    if (StringUtil.isNotBlank(nameWithSchema)) {
981                        TableInfo tableInfo = TableInfoFactory.ofTableName(nameWithSchema);
982                        if (tableInfo != null) {
983                            tableInfo.appendConditions(entity, queryWrapper);
984                        }
985                    }
986                }
987            }
988        }
989    }
990
991
992    public QueryWrapper buildQueryWrapper(Object entity, SqlOperators operators) {
993        QueryColumn[] queryColumns = new QueryColumn[defaultQueryColumns.length];
994        for (int i = 0; i < defaultQueryColumns.length; i++) {
995            queryColumns[i] = columnQueryMapping.get(defaultQueryColumns[i]);
996        }
997
998        QueryWrapper queryWrapper = QueryWrapper.create();
999
1000        String tableNameWithSchema = getTableNameWithSchema();
1001        queryWrapper.select(queryColumns).from(tableNameWithSchema);
1002
1003        MetaObject metaObject = EntityMetaObject.forObject(entity, reflectorFactory);
1004        propertyColumnMapping.forEach((property, column) -> {
1005            if (column.equals(logicDeleteColumn)) {
1006                return;
1007            }
1008            Object value = metaObject.getValue(property);
1009            if (value != null && !"".equals(value)) {
1010                QueryColumn queryColumn = Arrays.stream(queryColumns)
1011                    .filter(e -> e.getName().equals(column))
1012                    .findFirst()
1013                    .orElse(QueryMethods.column(getTableNameWithSchema(), column));
1014                if (operators != null) {
1015                    SqlOperator operator = operators.get(column);
1016                    if (operator == null) {
1017                        operator = SqlOperator.EQUALS;
1018                    } else if (operator == SqlOperator.IGNORE) {
1019                        return;
1020                    }
1021                    if (operator == SqlOperator.LIKE || operator == SqlOperator.NOT_LIKE) {
1022                        value = "%" + value + "%";
1023                    } else if (operator == SqlOperator.LIKE_LEFT || operator == SqlOperator.NOT_LIKE_LEFT) {
1024                        value = value + "%";
1025                    } else if (operator == SqlOperator.LIKE_RIGHT || operator == SqlOperator.NOT_LIKE_RIGHT) {
1026                        value = "%" + value;
1027                    }
1028                    queryWrapper.and(QueryCondition.create(queryColumn, operator, buildSqlArg(column, value)));
1029                } else {
1030                    queryWrapper.and(queryColumn.eq(buildSqlArg(column, value)));
1031                }
1032            }
1033        });
1034        return queryWrapper;
1035    }
1036
1037    private Object buildSqlArg(String column, Object value) {
1038        ColumnInfo columnInfo = columnInfoMapping.get(column);
1039        // 给定的列名在实体类中没有对应的字段
1040        if (columnInfo == null) {
1041            return value;
1042        }
1043        // 如果给定的列名在实体类中有对应的字段
1044        // 则使用实体类中属性标记的 @Column(typeHandler = ...) 类型处理器处理参数
1045        // 调用 TypeHandler#setParameter 为 PreparedStatement 设置值
1046        TypeHandler<?> typeHandler = columnInfo.buildTypeHandler(null);
1047        if (typeHandler != null) {
1048            return new TypeHandlerObject(typeHandler, value, columnInfo.getJdbcType());
1049        }
1050        return value;
1051    }
1052
1053    public String getKeyProperties() {
1054        StringJoiner joiner = new StringJoiner(",");
1055        for (IdInfo value : primaryKeyList) {
1056            joiner.add(FlexConsts.ENTITY + "." + value.getProperty());
1057        }
1058        return joiner.toString();
1059    }
1060
1061
1062    public String getKeyColumns() {
1063        StringJoiner joiner = new StringJoiner(",");
1064        for (IdInfo value : primaryKeyList) {
1065            joiner.add(value.getColumn());
1066        }
1067        return joiner.toString();
1068    }
1069
1070    public List<QueryColumn> getDefaultQueryColumn() {
1071        return Arrays.stream(defaultQueryColumns)
1072            .map(columnQueryMapping::get)
1073            .collect(Collectors.toList());
1074    }
1075
1076    private void getCombinedColumns(List<Class<?>> existedEntities, Class<?> entityClass, List<String> combinedColumns) {
1077        if (existedEntities.contains(entityClass)) {
1078            return;
1079        }
1080
1081        existedEntities.add(entityClass);
1082
1083        TableInfo tableInfo = TableInfoFactory.ofEntityClass(entityClass);
1084
1085        combinedColumns.addAll(Arrays.asList(tableInfo.allColumns));
1086
1087        if (tableInfo.collectionType != null) {
1088            tableInfo.collectionType.values()
1089                .forEach(e -> getCombinedColumns(existedEntities, e, combinedColumns));
1090        }
1091
1092        if (tableInfo.associationType != null) {
1093            tableInfo.associationType.values()
1094                .forEach(e -> getCombinedColumns(existedEntities, e, combinedColumns));
1095        }
1096    }
1097
1098    public ResultMap buildResultMap(Configuration configuration) {
1099        List<String> combinedColumns = new ArrayList<>();
1100
1101        getCombinedColumns(new ArrayList<>(), entityClass, combinedColumns);
1102
1103        // 预加载所有重复列,用于判断重名属性
1104        List<String> existedColumns = combinedColumns.stream()
1105            .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
1106            .entrySet()
1107            .stream()
1108            .filter(e -> e.getValue().intValue() > 1)
1109            .map(Map.Entry::getKey)
1110            .collect(Collectors.toList());
1111
1112        return doBuildResultMap(configuration, new HashSet<>(), existedColumns, false, getTableNameWithSchema());
1113    }
1114
1115    private ResultMap doBuildResultMap(Configuration configuration, Set<String> resultMapIds, List<String> existedColumns, boolean isNested, String nestedPrefix) {
1116
1117        String resultMapId = isNested ? "nested-" + nestedPrefix + ":" + entityClass.getName() : entityClass.getName();
1118
1119        // 是否有循环引用
1120        boolean withCircularReference = resultMapIds.contains(resultMapId) || resultMapIds.contains(entityClass.getName());
1121        if (withCircularReference) {
1122            return null;
1123        }
1124
1125        resultMapIds.add(resultMapId);
1126
1127        if (configuration.hasResultMap(resultMapId)) {
1128            return configuration.getResultMap(resultMapId);
1129        }
1130
1131        List<ResultMapping> resultMappings = new ArrayList<>();
1132
1133        // <resultMap> 标签下的 <id> 标签映射
1134        for (IdInfo idInfo : primaryKeyList) {
1135            doBuildColumnResultMapping(configuration, resultMappings, existedColumns, idInfo, CollectionUtil.newArrayList(ResultFlag.ID), isNested);
1136        }
1137
1138        // <resultMap> 标签下的 <result> 标签映射
1139        for (ColumnInfo columnInfo : columnInfoList) {
1140            doBuildColumnResultMapping(configuration, resultMappings, existedColumns, columnInfo, Collections.emptyList(), isNested);
1141        }
1142
1143        // <resultMap> 标签下的 <association> 标签映射
1144        if (associationType != null) {
1145            associationType.forEach((fieldName, fieldType) -> {
1146                // 获取嵌套类型的信息,也就是 javaType 属性
1147                TableInfo tableInfo = TableInfoFactory.ofEntityClass(fieldType);
1148                // 构建嵌套类型的 ResultMap 对象,也就是 <association> 标签下的内容
1149                ResultMap nestedResultMap = tableInfo.doBuildResultMap(configuration, resultMapIds, existedColumns, true, nestedPrefix);
1150                if (nestedResultMap != null) {
1151                    resultMappings.add(new ResultMapping.Builder(configuration, fieldName)
1152                        .javaType(fieldType)
1153                        .nestedResultMapId(nestedResultMap.getId())
1154                        .build());
1155                }
1156            });
1157        }
1158
1159        // <resultMap> 标签下的 <collection> 标签映射
1160        if (collectionType != null) {
1161            collectionType.forEach((field, genericClass) -> {
1162                if (TableInfoFactory.defaultSupportColumnTypes.contains(genericClass)) {
1163                    // List<String> List<Integer> 等
1164                    String columnName = TableInfoFactory.getColumnName(camelToUnderline, field, field.getAnnotation(Column.class));
1165                    // 映射 <result column="..."/>
1166                    ResultMapping resultMapping = new ResultMapping.Builder(configuration, null)
1167                        .column(columnName)
1168                        .typeHandler(configuration.getTypeHandlerRegistry().getTypeHandler(genericClass))
1169                        .build();
1170                    String nestedResultMapId = entityClass.getName() + "." + field.getName();
1171                    ResultMap nestedResultMap;
1172                    if (configuration.hasResultMap(nestedResultMapId)) {
1173                        nestedResultMap = configuration.getResultMap(nestedResultMapId);
1174                    } else {
1175                        nestedResultMap = new ResultMap.Builder(configuration, nestedResultMapId, genericClass, Collections.singletonList(resultMapping)).build();
1176                        configuration.addResultMap(nestedResultMap);
1177                    }
1178                    // 映射 <collection property="..." ofType="genericClass">
1179                    resultMappings.add(new ResultMapping.Builder(configuration, field.getName())
1180                        .javaType(field.getType())
1181                        .nestedResultMapId(nestedResultMap.getId())
1182                        .build());
1183                } else {
1184                    // 获取集合泛型类型的信息,也就是 ofType 属性
1185                    TableInfo tableInfo = TableInfoFactory.ofEntityClass(genericClass);
1186                    // 构建嵌套类型的 ResultMap 对象,也就是 <collection> 标签下的内容
1187                    ResultMap nestedResultMap = tableInfo.doBuildResultMap(configuration, resultMapIds, existedColumns, true, nestedPrefix);
1188                    if (nestedResultMap != null) {
1189                        resultMappings.add(new ResultMapping.Builder(configuration, field.getName())
1190                            .javaType(field.getType())
1191                            .nestedResultMapId(nestedResultMap.getId())
1192                            .build());
1193                    }
1194                }
1195            });
1196        }
1197
1198        ResultMap resultMap = new ResultMap.Builder(configuration, resultMapId, entityClass, resultMappings).build();
1199        configuration.addResultMap(resultMap);
1200        resultMapIds.add(resultMapId);
1201        return resultMap;
1202    }
1203
1204    private void doBuildColumnResultMapping(Configuration configuration, List<ResultMapping> resultMappings
1205        , List<String> existedColumns, ColumnInfo columnInfo, List<ResultFlag> flags, boolean isNested) {
1206
1207        if (!isNested) {
1208            if (existedColumns.contains(columnInfo.column)) {
1209                // userName -> tb_user$user_name
1210                resultMappings.add(new ResultMapping.Builder(configuration
1211                    , columnInfo.property
1212                    , tableName + "$" + columnInfo.column
1213                    , columnInfo.propertyType)
1214                    .jdbcType(columnInfo.getJdbcType())
1215                    .flags(flags)
1216                    .typeHandler(columnInfo.buildTypeHandler(configuration))
1217                    .build());
1218            }
1219            buildDefaultResultMapping(configuration, resultMappings, columnInfo, flags);
1220        } else {
1221            if (existedColumns.contains(columnInfo.column)) {
1222                // userName -> tb_user$user_name
1223                resultMappings.add(new ResultMapping.Builder(configuration
1224                    , columnInfo.property
1225                    , tableName + "$" + columnInfo.column
1226                    , columnInfo.propertyType)
1227                    .jdbcType(columnInfo.getJdbcType())
1228                    .flags(flags)
1229                    .typeHandler(columnInfo.buildTypeHandler(configuration))
1230                    .build());
1231            } else {
1232                buildDefaultResultMapping(configuration, resultMappings, columnInfo, flags);
1233            }
1234        }
1235
1236        if (ArrayUtil.isNotEmpty(columnInfo.alias)) {
1237            for (String alias : columnInfo.alias) {
1238                // userName -> alias
1239                resultMappings.add(new ResultMapping.Builder(configuration
1240                    , columnInfo.property
1241                    , alias
1242                    , columnInfo.propertyType)
1243                    .jdbcType(columnInfo.getJdbcType())
1244                    .flags(flags)
1245                    .typeHandler(columnInfo.buildTypeHandler(configuration))
1246                    .build());
1247            }
1248        }
1249
1250    }
1251
1252    private void buildDefaultResultMapping(Configuration configuration, List<ResultMapping> resultMappings, ColumnInfo columnInfo, List<ResultFlag> flags) {
1253        // userName -> user_name
1254        resultMappings.add(new ResultMapping.Builder(configuration
1255            , columnInfo.property
1256            , columnInfo.column
1257            , columnInfo.propertyType)
1258            .jdbcType(columnInfo.getJdbcType())
1259            .flags(flags)
1260            .typeHandler(columnInfo.buildTypeHandler(configuration))
1261            .build());
1262
1263        if (!Objects.equals(columnInfo.column, columnInfo.property)) {
1264            // userName -> userName
1265            resultMappings.add(new ResultMapping.Builder(configuration
1266                , columnInfo.property
1267                , columnInfo.property
1268                , columnInfo.propertyType)
1269                .jdbcType(columnInfo.getJdbcType())
1270                .flags(flags)
1271                .typeHandler(columnInfo.buildTypeHandler(configuration))
1272                .build());
1273        }
1274    }
1275
1276
1277    private Object buildColumnSqlArg(MetaObject metaObject, String column) {
1278        ColumnInfo columnInfo = columnInfoMapping.get(column);
1279        Object value = getPropertyValue(metaObject, columnInfo.property);
1280        if (value != null) {
1281            TypeHandler<?> typeHandler = columnInfo.buildTypeHandler(null);
1282            if (typeHandler != null) {
1283                return new TypeHandlerObject(typeHandler, value, columnInfo.getJdbcType());
1284            }
1285        }
1286        return value;
1287    }
1288
1289
1290    public Object buildColumnSqlArg(Object entityObject, String column) {
1291        MetaObject metaObject = EntityMetaObject.forObject(entityObject, reflectorFactory);
1292        return buildColumnSqlArg(metaObject, column);
1293    }
1294
1295
1296    public Object getPropertyValue(MetaObject metaObject, String property) {
1297        if (property != null && metaObject.hasGetter(property)) {
1298            return metaObject.getValue(property);
1299        }
1300        return null;
1301    }
1302
1303
1304    /**
1305     * 通过 row 实例类转换为一个 entity
1306     *
1307     * @return entity
1308     */
1309    public <T> T newInstanceByRow(Row row, int index) {
1310        Object instance = ClassUtil.newInstance(entityClass);
1311        MetaObject metaObject = EntityMetaObject.forObject(instance, reflectorFactory);
1312        Set<String> rowKeys = row.keySet();
1313        columnInfoMapping.forEach((column, columnInfo) -> {
1314            if (index <= 0) {
1315                String replace = column.replace("_", "");
1316                for (String rowKey : rowKeys) {
1317                    // 修复: 开启 mapUnderscoreToCamelCase = true 时, row 无法转换 entity 的问题
1318                    if (rowKey.equalsIgnoreCase(column) || rowKey.equalsIgnoreCase(replace)) {
1319                        setInstancePropertyValue(row, instance, metaObject, columnInfo, rowKey);
1320                    }
1321                }
1322            } else {
1323                for (int i = index; i >= 0; i--) {
1324                    String newColumn = i <= 0 ? column : column + "$" + i;
1325                    boolean fillValue = false;
1326                    String replace = column.replace("_", "");
1327                    for (String rowKey : rowKeys) {
1328                        // 修复: 开启 mapUnderscoreToCamelCase = true 时, row 无法转换 entity 的问题
1329                        if (rowKey.equalsIgnoreCase(newColumn) || rowKey.equalsIgnoreCase(replace)) {
1330                            setInstancePropertyValue(row, instance, metaObject, columnInfo, rowKey);
1331                            fillValue = true;
1332                            break;
1333                        }
1334                    }
1335                    if (fillValue) {
1336                        break;
1337                    }
1338                }
1339            }
1340        });
1341        // noinspection unchecked
1342        return (T) instance;
1343    }
1344
1345
1346    private void setInstancePropertyValue(Row row, Object instance, MetaObject metaObject, ColumnInfo columnInfo, String rowKey) {
1347        Object rowValue = row.get(rowKey);
1348        TypeHandler<?> typeHandler = columnInfo.buildTypeHandler(null);
1349        if (typeHandler != null) {
1350            try {
1351                // 通过 typeHandler 转换数据
1352                rowValue = typeHandler.getResult(getResultSet(rowValue), 0);
1353            } catch (SQLException e) {
1354                // ignore
1355            }
1356        }
1357        if (rowValue != null && !metaObject.getSetterType(columnInfo.property).isAssignableFrom(rowValue.getClass())) {
1358            rowValue = ConvertUtil.convert(rowValue, metaObject.getSetterType(columnInfo.property), true);
1359        }
1360        rowValue = invokeOnSetListener(instance, columnInfo.property, rowValue);
1361        metaObject.setValue(columnInfo.property, rowValue);
1362    }
1363
1364
1365    private ResultSet getResultSet(Object value) {
1366        return (ResultSet) Proxy.newProxyInstance(TableInfo.class.getClassLoader(),
1367            new Class[]{ResultSet.class}, (proxy, method, args) -> value);
1368    }
1369
1370
1371    /**
1372     * 初始化乐观锁版本号
1373     *
1374     * @param entityObject
1375     */
1376    public void initVersionValueIfNecessary(Object entityObject) {
1377        if (StringUtil.isBlank(versionColumn)) {
1378            return;
1379        }
1380
1381        MetaObject metaObject = EntityMetaObject.forObject(entityObject, reflectorFactory);
1382        Object columnValue = getPropertyValue(metaObject, columnInfoMapping.get(versionColumn).property);
1383        if (columnValue == null) {
1384            String name = columnInfoMapping.get(versionColumn).property;
1385            Class<?> clazz = metaObject.getSetterType(name);
1386            metaObject.setValue(name, ConvertUtil.convert(0L, clazz));
1387        }
1388    }
1389
1390    /**
1391     * 设置租户id
1392     *
1393     * @param entityObject
1394     */
1395    public void initTenantIdIfNecessary(Object entityObject) {
1396        if (StringUtil.isBlank(tenantIdColumn)) {
1397            return;
1398        }
1399
1400        MetaObject metaObject = EntityMetaObject.forObject(entityObject, reflectorFactory);
1401
1402        // 如果租户字段有值,则不覆盖。
1403        // https://gitee.com/mybatis-flex/mybatis-flex/issues/I7OWYD
1404        // https://gitee.com/mybatis-flex/mybatis-flex/issues/I920DK
1405        String property = columnInfoMapping.get(tenantIdColumn).property;
1406        if (metaObject.getValue(property) != null) {
1407            return;
1408        }
1409
1410        Object[] tenantIds = TenantManager.getTenantIds(tableName);
1411        if (tenantIds == null || tenantIds.length == 0) {
1412            return;
1413        }
1414
1415        // 默认使用第一个作为插入的租户ID
1416        Object tenantId = tenantIds[0];
1417        if (tenantId != null) {
1418            Class<?> setterType = metaObject.getSetterType(property);
1419            metaObject.setValue(property, ConvertUtil.convert(tenantId, setterType));
1420        }
1421    }
1422
1423    /**
1424     * 初始化逻辑删除的默认值
1425     *
1426     * @param entityObject
1427     */
1428    public void initLogicDeleteValueIfNecessary(Object entityObject) {
1429        if (StringUtil.isBlank(getLogicDeleteColumnOrSkip())) {
1430            return;
1431        }
1432
1433        MetaObject metaObject = EntityMetaObject.forObject(entityObject, reflectorFactory);
1434        ColumnInfo logicDeleteColumn = columnInfoMapping.get(this.logicDeleteColumn);
1435        Object columnValue = getPropertyValue(metaObject, logicDeleteColumn.property);
1436        if (columnValue == null) {
1437            Object normalValueOfLogicDelete = LogicDeleteManager.getProcessor().getLogicNormalValue();
1438            if (normalValueOfLogicDelete != null) {
1439                String property = logicDeleteColumn.property;
1440                Class<?> setterType = metaObject.getSetterType(property);
1441                metaObject.setValue(property, ConvertUtil.convert(normalValueOfLogicDelete, setterType));
1442            }
1443        }
1444    }
1445
1446
1447    private static final Map<Class<?>, List<InsertListener>> insertListenerCache = new ConcurrentHashMap<>();
1448
1449    public void invokeOnInsertListener(Object entity) {
1450        List<InsertListener> listeners = MapUtil.computeIfAbsent(insertListenerCache, entityClass, aClass -> {
1451            List<InsertListener> globalListeners = FlexGlobalConfig.getDefaultConfig()
1452                .getSupportedInsertListener(entityClass);
1453            List<InsertListener> allListeners = CollectionUtil.merge(onInsertListeners, globalListeners);
1454            Collections.sort(allListeners);
1455            return allListeners;
1456        });
1457        listeners.forEach(insertListener -> insertListener.onInsert(entity));
1458    }
1459
1460
1461    private static final Map<Class<?>, List<UpdateListener>> updateListenerCache = new ConcurrentHashMap<>();
1462
1463    public void invokeOnUpdateListener(Object entity) {
1464        List<UpdateListener> listeners = MapUtil.computeIfAbsent(updateListenerCache, entityClass, aClass -> {
1465            List<UpdateListener> globalListeners = FlexGlobalConfig.getDefaultConfig()
1466                .getSupportedUpdateListener(entityClass);
1467            List<UpdateListener> allListeners = CollectionUtil.merge(onUpdateListeners, globalListeners);
1468            Collections.sort(allListeners);
1469            return allListeners;
1470        });
1471        listeners.forEach(insertListener -> insertListener.onUpdate(entity));
1472    }
1473
1474
1475    private static final Map<Class<?>, List<SetListener>> setListenerCache = new ConcurrentHashMap<>();
1476
1477    public Object invokeOnSetListener(Object entity, String property, Object value) {
1478        List<SetListener> listeners = MapUtil.computeIfAbsent(setListenerCache, entityClass, aClass -> {
1479            List<SetListener> globalListeners = FlexGlobalConfig.getDefaultConfig()
1480                .getSupportedSetListener(entityClass);
1481            List<SetListener> allListeners = CollectionUtil.merge(onSetListeners, globalListeners);
1482            Collections.sort(allListeners);
1483            return allListeners;
1484        });
1485        for (SetListener setListener : listeners) {
1486            value = setListener.onSet(entity, property, value);
1487        }
1488        return value;
1489    }
1490
1491    public QueryColumn getQueryColumnByProperty(String property) {
1492        String column = getColumnByProperty(property);
1493        return columnQueryMapping.get(column);
1494    }
1495
1496}