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