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