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