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