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