001/*
002 *  Copyright (c) 2022-2023, 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.mybatis;
017
018import com.mybatisflex.annotation.Table;
019import com.mybatisflex.core.FlexConsts;
020import com.mybatisflex.core.handler.CompositeEnumTypeHandler;
021import com.mybatisflex.core.keygen.MultiEntityKeyGenerator;
022import com.mybatisflex.core.keygen.MultiRowKeyGenerator;
023import com.mybatisflex.core.keygen.MybatisKeyGeneratorUtil;
024import com.mybatisflex.core.keygen.RowKeyGenerator;
025import com.mybatisflex.core.mybatis.executor.FlexBatchExecutor;
026import com.mybatisflex.core.mybatis.executor.FlexReuseExecutor;
027import com.mybatisflex.core.mybatis.executor.FlexSimpleExecutor;
028import com.mybatisflex.core.table.TableInfo;
029import com.mybatisflex.core.table.TableInfoFactory;
030import com.mybatisflex.core.util.StringUtil;
031import org.apache.ibatis.executor.CachingExecutor;
032import org.apache.ibatis.executor.Executor;
033import org.apache.ibatis.executor.keygen.KeyGenerator;
034import org.apache.ibatis.executor.keygen.NoKeyGenerator;
035import org.apache.ibatis.executor.keygen.SelectKeyGenerator;
036import org.apache.ibatis.executor.parameter.ParameterHandler;
037import org.apache.ibatis.executor.resultset.ResultSetHandler;
038import org.apache.ibatis.executor.statement.StatementHandler;
039import org.apache.ibatis.mapping.BoundSql;
040import org.apache.ibatis.mapping.Environment;
041import org.apache.ibatis.mapping.MappedStatement;
042import org.apache.ibatis.mapping.ResultMap;
043import org.apache.ibatis.session.*;
044import org.apache.ibatis.transaction.Transaction;
045import org.apache.ibatis.util.MapUtil;
046
047import java.lang.reflect.ParameterizedType;
048import java.lang.reflect.Proxy;
049import java.lang.reflect.Type;
050import java.util.Collections;
051import java.util.List;
052import java.util.Map;
053import java.util.concurrent.ConcurrentHashMap;
054
055/**
056 * @author michael
057 * @author life
058 */
059public class FlexConfiguration extends Configuration {
060
061    private static final Map<String, MappedStatement> dynamicMappedStatementCache = new ConcurrentHashMap<>();
062
063    public FlexConfiguration() {
064        setObjectWrapperFactory(new FlexWrapperFactory());
065        setDefaultEnumTypeHandler(CompositeEnumTypeHandler.class);
066    }
067
068
069    public FlexConfiguration(Environment environment) {
070        super(environment);
071        setObjectWrapperFactory(new FlexWrapperFactory());
072        setDefaultEnumTypeHandler(CompositeEnumTypeHandler.class);
073    }
074
075    /**
076     * 为原生 sql 设置参数
077     */
078    @Override
079    public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
080        String mappedStatementId = mappedStatement.getId();
081        /**
082         *  以 "!selectKey" 结尾的 mappedStatementId,是用于 Sequence 生成主键的,无需为其设置参数
083         *  {@link SelectKeyGenerator#SELECT_KEY_SUFFIX}
084         */
085        if (!mappedStatementId.endsWith(SelectKeyGenerator.SELECT_KEY_SUFFIX)
086            && parameterObject instanceof Map
087            && ((Map<?, ?>) parameterObject).containsKey(FlexConsts.SQL_ARGS)) {
088            SqlArgsParameterHandler sqlArgsParameterHandler = new SqlArgsParameterHandler(mappedStatement, (Map) parameterObject, boundSql);
089            return (ParameterHandler) interceptorChain.pluginAll(sqlArgsParameterHandler);
090        } else {
091            return super.newParameterHandler(mappedStatement, parameterObject, boundSql);
092        }
093    }
094
095
096    @Override
097    public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement
098        , RowBounds rowBounds, ParameterHandler parameterHandler, ResultHandler resultHandler, BoundSql boundSql) {
099        ResultSetHandler resultSetHandler = new FlexResultSetHandler(executor, mappedStatement, parameterHandler,
100            resultHandler, boundSql, rowBounds);
101        return (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
102    }
103
104    /**
105     * 替换为 FlexStatementHandler,主要用来为实体类的多主键做支持、和数据审计
106     * FlexStatementHandler 和 原生的 RoutingStatementHandler 对比,没有任何性能影响
107     */
108    @Override
109    public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
110        StatementHandler statementHandler = new FlexStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
111        statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
112        return statementHandler;
113    }
114
115
116    /**
117     * 替换为 Flex 的 Executor,主要用于重建 CacheKey
118     * 默认情况下,Mybatis 的 CacheKey 构建是必须有 ParameterMapping,而 Flex 的 select 是不带有 ParameterMapping 的
119     */
120    @Override
121    public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
122        executorType = executorType == null ? defaultExecutorType : executorType;
123        Executor executor;
124        if (ExecutorType.BATCH == executorType) {
125            executor = new FlexBatchExecutor(this, transaction);
126        } else if (ExecutorType.REUSE == executorType) {
127            executor = new FlexReuseExecutor(this, transaction);
128        } else {
129            executor = new FlexSimpleExecutor(this, transaction);
130        }
131        if (cacheEnabled) {
132            executor = new CachingExecutor(executor);
133        }
134        executor = (Executor) interceptorChain.pluginAll(executor);
135        return executor;
136    }
137
138
139    @Override
140    public MappedStatement getMappedStatement(String id) {
141        MappedStatement ms = super.getMappedStatement(id);
142        //动态 resultsMap,方法名称为:selectListByQuery
143        Class<?> asType = MappedStatementTypes.getCurrentType();
144        if (asType != null) {
145            return MapUtil.computeIfAbsent(dynamicMappedStatementCache, id + ":" + asType.getName(),
146                clazz -> replaceResultMap(ms, TableInfoFactory.ofEntityClass(asType))
147            );
148        }
149
150        return ms;
151    }
152
153
154    @Override
155    public void addMappedStatement(MappedStatement ms) {
156        //替换 RowMapper.insert 的主键生成器
157        //替换 RowMapper.insertBatchWithFirstRowColumns 的主键生成器
158        if (ms.getId().startsWith("com.mybatisflex.core.row.RowMapper.insert")) {
159            ms = replaceRowKeyGenerator(ms);
160        }
161        //entity insert methods
162        else if (StringUtil.endsWithAny(ms.getId(), "insert", FlexConsts.METHOD_INSERT_BATCH)
163            && ms.getKeyGenerator() == NoKeyGenerator.INSTANCE) {
164            ms = replaceEntityKeyGenerator(ms);
165        }
166        //entity select
167        else if (StringUtil.endsWithAny(ms.getId(), "selectOneById", "selectListByIds"
168            , "selectListByQuery", "selectCursorByQuery")) {
169            ms = replaceResultMap(ms, getTableInfo(ms));
170        } else {
171            List<ResultMap> resultMaps = ms.getResultMaps();
172            //根据 resultMap 里面的 class 进行判断
173            for (ResultMap resultMap : resultMaps) {
174                //获取结果的类型
175                Class<?> clazz = resultMap.getType();
176                //判断是否为表实体类
177                if (clazz.getDeclaredAnnotation(Table.class) != null && isDefaultResultMap(ms.getId(), resultMap.getId())) {
178                    TableInfo tableInfo = TableInfoFactory.ofEntityClass(clazz);
179                    ms = replaceResultMap(ms, tableInfo);
180                }
181            }
182        }
183        super.addMappedStatement(ms);
184    }
185
186    /**
187     * 是否为默认的 resultMap,也就是未配置 resultMap
188     *
189     * @return
190     */
191    private boolean isDefaultResultMap(String statementId, String resultMapId) {
192        // 参考 {@code  MapperBuilderAssistant.getStatementResultMaps}
193        return resultMapId.equals(statementId + "-Inline");
194    }
195
196
197    /**
198     * 替换 entity 查询的 ResultMap
199     */
200    private MappedStatement replaceResultMap(MappedStatement ms, TableInfo tableInfo) {
201
202        if (tableInfo == null) {
203            return ms;
204        }
205
206        String resultMapId = tableInfo.getEntityClass().getName();
207
208        ResultMap resultMap;
209        if (hasResultMap(resultMapId)) {
210            resultMap = getResultMap(resultMapId);
211        } else {
212            resultMap = tableInfo.buildResultMap(this);
213        }
214
215        return new MappedStatement.Builder(ms.getConfiguration(), ms.getId(), ms.getSqlSource(), ms.getSqlCommandType())
216            .resource(ms.getResource())
217            .fetchSize(ms.getFetchSize())
218            .timeout(ms.getTimeout())
219            .statementType(ms.getStatementType())
220            .keyGenerator(NoKeyGenerator.INSTANCE)
221            .keyProperty(ms.getKeyProperties() == null ? null : String.join(",", ms.getKeyProperties()))
222            .keyColumn(ms.getKeyColumns() == null ? null : String.join(",", ms.getKeyColumns()))
223            .databaseId(databaseId)
224            .lang(ms.getLang())
225            .resultOrdered(ms.isResultOrdered())
226            .resultSets(ms.getResultSets() == null ? null : String.join(",", ms.getResultSets()))
227            .resultMaps(Collections.singletonList(resultMap))
228            .resultSetType(ms.getResultSetType())
229            .flushCacheRequired(ms.isFlushCacheRequired())
230            .useCache(ms.isUseCache())
231            .cache(ms.getCache())
232            .build();
233    }
234
235    /**
236     * 生成新的、已替换主键生成器的 MappedStatement
237     *
238     * @param ms MappedStatement
239     * @return replaced MappedStatement
240     */
241    private MappedStatement replaceRowKeyGenerator(MappedStatement ms) {
242
243        //执行原生 SQL,不需要为其设置主键生成器
244        if (ms.getId().endsWith("BySql")) {
245            return ms;
246        }
247
248        KeyGenerator keyGenerator = new RowKeyGenerator(ms);
249        if (ms.getId().endsWith("insertBatchWithFirstRowColumns")) {
250            keyGenerator = new MultiRowKeyGenerator(keyGenerator);
251        }
252
253        return new MappedStatement.Builder(ms.getConfiguration(), ms.getId(), ms.getSqlSource(), ms.getSqlCommandType())
254            .resource(ms.getResource())
255            .fetchSize(ms.getFetchSize())
256            .timeout(ms.getTimeout())
257            .statementType(ms.getStatementType())
258            // 替换主键生成器
259            .keyGenerator(keyGenerator)
260            .keyProperty(ms.getKeyProperties() == null ? null : String.join(",", ms.getKeyProperties()))
261            .keyColumn(ms.getKeyColumns() == null ? null : String.join(",", ms.getKeyColumns()))
262            .databaseId(databaseId)
263            .lang(ms.getLang())
264            .resultOrdered(ms.isResultOrdered())
265            .resultSets(ms.getResultSets() == null ? null : String.join(",", ms.getResultSets()))
266            .resultMaps(ms.getResultMaps())
267            .resultSetType(ms.getResultSetType())
268            .flushCacheRequired(ms.isFlushCacheRequired())
269            .useCache(ms.isUseCache())
270            .cache(ms.getCache())
271            .build();
272    }
273
274    /**
275     * 生成新的、已替换主键生成器的 MappedStatement
276     *
277     * @param ms MappedStatement
278     * @return replaced MappedStatement
279     */
280    private MappedStatement replaceEntityKeyGenerator(MappedStatement ms) {
281
282        TableInfo tableInfo = getTableInfo(ms);
283        if (tableInfo == null) {
284            return ms;
285        }
286
287        KeyGenerator keyGenerator = MybatisKeyGeneratorUtil.createTableKeyGenerator(tableInfo, ms);
288        if (keyGenerator == NoKeyGenerator.INSTANCE) {
289            return ms;
290        }
291
292        //批量插入
293        if (ms.getId().endsWith(FlexConsts.METHOD_INSERT_BATCH)) {
294            keyGenerator = new MultiEntityKeyGenerator(keyGenerator);
295        }
296
297        return new MappedStatement.Builder(ms.getConfiguration(), ms.getId(), ms.getSqlSource(), ms.getSqlCommandType())
298            .resource(ms.getResource())
299            .fetchSize(ms.getFetchSize())
300            .timeout(ms.getTimeout())
301            .statementType(ms.getStatementType())
302            // 替换主键生成器
303            .keyGenerator(keyGenerator)
304            .keyProperty(tableInfo.getKeyProperties())
305            .keyColumn(tableInfo.getKeyColumns())
306            .databaseId(databaseId)
307            .lang(ms.getLang())
308            .resultOrdered(ms.isResultOrdered())
309            .resultSets(ms.getResultSets() == null ? null : String.join(",", ms.getResultSets()))
310            .resultMaps(ms.getResultMaps())
311            .resultSetType(ms.getResultSetType())
312            .flushCacheRequired(ms.isFlushCacheRequired())
313            .useCache(ms.isUseCache())
314            .cache(ms.getCache())
315            .build();
316    }
317
318
319    private TableInfo getTableInfo(MappedStatement ms) {
320        String mapperClassName = ms.getId().substring(0, ms.getId().lastIndexOf("."));
321        try {
322            Class<?> mapperClass = Class.forName(mapperClassName);
323            return TableInfoFactory.ofMapperClass(mapperClass);
324        } catch (ClassNotFoundException e) {
325            return null;
326        }
327    }
328
329
330    @Override
331    public <T> void addMapper(Class<T> type) {
332        Type[] genericInterfaces = type.getGenericInterfaces();
333        boolean isGenericInterface = false;
334        for (Type genericInterface : genericInterfaces) {
335            if (genericInterface instanceof ParameterizedType) {
336                Type actualTypeArgument = ((ParameterizedType) genericInterface).getActualTypeArguments()[0];
337                if (actualTypeArgument instanceof Class) {
338                    Mappers.addMapping((Class<?>) actualTypeArgument, type);
339                } else {
340                    isGenericInterface = true;
341                    break;
342                }
343            }
344        }
345
346        //不支持泛型类添加
347        if (!isGenericInterface) {
348            super.addMapper(type);
349        }
350    }
351
352
353    @SuppressWarnings("unchecked")
354    @Override
355    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
356        T mapper = super.getMapper(type, sqlSession);
357        return (T) Proxy.newProxyInstance(type.getClassLoader(), new Class[]{type}
358            , new MapperInvocationHandler(mapper, environment.getDataSource()));
359    }
360
361}