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