001/*
002 *  Copyright (c) 2022-2025, Mybatis-Flex (fuhai999@gmail.com).
003 *  <p>
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *  <p>
008 *  http://www.apache.org/licenses/LICENSE-2.0
009 *  <p>
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package com.mybatisflex.core.keygen;
017
018import com.mybatisflex.annotation.KeyType;
019import com.mybatisflex.core.FlexConsts;
020import com.mybatisflex.core.FlexGlobalConfig;
021import com.mybatisflex.core.exception.FlexExceptions;
022import com.mybatisflex.core.table.IdInfo;
023import com.mybatisflex.core.table.TableInfo;
024import com.mybatisflex.core.util.StringUtil;
025import org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator;
026import org.apache.ibatis.executor.keygen.KeyGenerator;
027import org.apache.ibatis.executor.keygen.NoKeyGenerator;
028import org.apache.ibatis.executor.keygen.SelectKeyGenerator;
029import org.apache.ibatis.mapping.*;
030import org.apache.ibatis.session.Configuration;
031
032import java.util.ArrayList;
033import java.util.Arrays;
034import java.util.List;
035
036public class MybatisKeyGeneratorUtil {
037
038    private MybatisKeyGeneratorUtil() {
039    }
040
041    public static KeyGenerator createTableKeyGenerator(TableInfo tableInfo, MappedStatement ms) {
042        List<IdInfo> primaryKeyList = tableInfo.getPrimaryKeyList();
043
044        //无主键
045        if (primaryKeyList == null || primaryKeyList.isEmpty()) {
046            return NoKeyGenerator.INSTANCE;
047        }
048
049        //多主键的
050        if (primaryKeyList.size() > 1) {
051            return new MultiPrimaryKeyGenerator(ms, tableInfo, primaryKeyList);
052        }
053
054        return createIdKeyGenerator(tableInfo, ms, primaryKeyList.get(0));
055    }
056
057
058    public static KeyGenerator createIdKeyGenerator(TableInfo tableInfo, MappedStatement ms, IdInfo idInfo) {
059        FlexGlobalConfig flexGlobalConfig = FlexGlobalConfig.getConfig(ms.getConfiguration());
060
061        if(flexGlobalConfig == null){
062            return NoKeyGenerator.INSTANCE;
063        }
064
065        FlexGlobalConfig.KeyConfig globalKeyConfig = flexGlobalConfig.getKeyConfig();
066        KeyType keyType = getKeyType(idInfo, globalKeyConfig);
067
068        if (keyType == null || keyType == KeyType.None) {
069            return NoKeyGenerator.INSTANCE;
070        }
071
072        //自增主键
073        if (keyType == KeyType.Auto) {
074            return Jdbc3KeyGenerator.INSTANCE;
075        }
076
077        //通过 java 生成的主键
078        if (keyType == KeyType.Generator) {
079            return new CustomKeyGenerator(ms.getConfiguration(), tableInfo, idInfo);
080        }
081
082        //通过序列生成的注解
083        String sequence = getKeyValue(idInfo, globalKeyConfig);
084        if (StringUtil.noText(sequence)) {
085            throw FlexExceptions.wrap("Please config sequence by @Id(value=\"...\") for field: %s in class: %s"
086                , idInfo.getProperty()
087                , tableInfo.getEntityClass().getName());
088        }
089
090
091        String selectId = ms.getId() + SelectKeyGenerator.SELECT_KEY_SUFFIX;
092        SqlSource sqlSource = ms.getLang().createSqlSource(ms.getConfiguration(), sequence.trim(), idInfo.getPropertyType());
093        MappedStatement.Builder msBuilder = new MappedStatement.Builder(ms.getConfiguration(), selectId, sqlSource, SqlCommandType.SELECT)
094            .resource(ms.getResource())
095            .fetchSize(null)
096            .timeout(null)
097            .statementType(StatementType.PREPARED)
098            .keyGenerator(NoKeyGenerator.INSTANCE)
099            .keyProperty(FlexConsts.ENTITY + "." + idInfo.getProperty())
100            .keyColumn(idInfo.getColumn())
101            .databaseId(ms.getDatabaseId())
102            .lang(ms.getLang())
103            .resultOrdered(false)
104            .resultSets(null)
105            .resultMaps(createIdResultMaps(ms.getConfiguration(), selectId + "-Inline", idInfo.getPropertyType(), new ArrayList<>()))
106            .resultSetType(null)
107            .flushCacheRequired(false)
108            .useCache(false)
109            .cache(ms.getCache());
110
111        MappedStatement keyMappedStatement = msBuilder.build();
112        ms.getConfiguration().addMappedStatement(keyMappedStatement);
113
114        //看到有的框架把 keyGenerator 添加到 mybatis 的当前配置里去,其实是完全没必要的
115        //因为只有在 xml 解析的时候,才可能存在多一个 MappedStatement 拥有同一个 keyGenerator 的情况
116        //当前每个方法都拥有一个自己的 keyGenerator 了,没必要添加
117        //addKeyGenerator(selectId, keyGenerator)
118        return new SelectKeyGenerator(keyMappedStatement, isKeyBefore(idInfo, globalKeyConfig));
119    }
120
121
122    private static List<ResultMap> createIdResultMaps(Configuration configuration,
123                                                      String statementId, Class<?> resultType, List<ResultMapping> resultMappings) {
124        ResultMap resultMap = new ResultMap.Builder(configuration, statementId, resultType, resultMappings, null)
125            .build();
126        return Arrays.asList(resultMap);
127    }
128
129
130    /**
131     * 获取主键的 keyType,优先通过 @id 获取,获取不到通过全局配置获取
132     */
133    public static KeyType getKeyType(IdInfo idInfo, FlexGlobalConfig.KeyConfig globalKeyConfig) {
134        KeyType keyType = idInfo.getKeyType();
135        if (keyType != KeyType.None) {
136            return keyType;
137        }
138
139        if (globalKeyConfig != null) {
140            return globalKeyConfig.getKeyType();
141        }
142
143        return keyType;
144    }
145
146
147    public static String getKeyValue(IdInfo idInfo, FlexGlobalConfig.KeyConfig globalKeyConfig) {
148        String value = idInfo.getValue();
149        if (StringUtil.noText(value) && globalKeyConfig != null) {
150            value = globalKeyConfig.getValue();
151        }
152        return value;
153    }
154
155
156    public static boolean isKeyBefore(IdInfo idInfo, FlexGlobalConfig.KeyConfig globalKeyConfig) {
157        Boolean before = idInfo.getBefore();
158        if (before == null && globalKeyConfig != null) {
159            return globalKeyConfig.isBefore();
160        } else {
161            return before == null || before;
162        }
163    }
164
165}