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