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.row;
017
018import com.mybatisflex.core.FlexConsts;
019import com.mybatisflex.core.exception.FlexExceptions;
020import com.mybatisflex.core.paginate.Page;
021import com.mybatisflex.core.provider.RowSqlProvider;
022import com.mybatisflex.core.query.CPI;
023import com.mybatisflex.core.query.QueryColumn;
024import com.mybatisflex.core.query.QueryWrapper;
025import com.mybatisflex.core.util.CollectionUtil;
026import com.mybatisflex.core.util.MapperUtil;
027import com.mybatisflex.core.util.StringUtil;
028import org.apache.ibatis.annotations.*;
029import org.apache.ibatis.exceptions.TooManyResultsException;
030
031import java.util.Collection;
032import java.util.List;
033import java.util.Map;
034
035import static com.mybatisflex.core.query.QueryMethods.count;
036
037
038public interface RowMapper {
039
040    int DEFAULT_BATCH_SIZE = 1000;
041
042    //////insert //////
043
044    /**
045     * 插入 row 到数据表
046     *
047     * @param tableName 表名
048     * @param row       数据内容,当设置有主键时,主键会自动填充
049     * @return 执行影响的行数
050     * @see RowSqlProvider#insert(Map)
051     */
052    @InsertProvider(value = RowSqlProvider.class, method = "insert")
053    int insert(@Param(FlexConsts.SCHEMA_NAME) String schema, @Param(FlexConsts.TABLE_NAME) String tableName, @Param(FlexConsts.ROW) Row row);
054
055
056    /**
057     * 执行 insert sql 语句
058     *
059     * @param sql  insert sql 语句
060     * @param args 参数
061     * @return 执行影响的行数
062     * @see Db#insertBySql(String, Object...)
063     * @see RowSqlProvider#providerRawSql(Map)
064     */
065    @InsertProvider(value = RowSqlProvider.class, method = RowSqlProvider.METHOD_RAW_SQL)
066    int insertBySql(@Param(FlexConsts.SQL) String sql, @Param(FlexConsts.SQL_ARGS) Object... args);
067
068
069    /**
070     * 批量插入 rows 到数据表
071     * <p>
072     * 注意,批量插入中,只会根据第一条 row 数据来构建 Sql 插入字段,若每条数据字段不一致,可能造成个别字段无法插入的情况
073     *
074     * @param tableName 表名
075     * @param rows      数据内容,当设置有主键时,主键会自动填充
076     * @return 执行影响的行数
077     * @see RowSqlProvider#insertBatchWithFirstRowColumns(Map)
078     */
079    @InsertProvider(value = RowSqlProvider.class, method = "insertBatchWithFirstRowColumns")
080    int insertBatchWithFirstRowColumns(@Param(FlexConsts.SCHEMA_NAME) String schema, @Param(FlexConsts.TABLE_NAME) String tableName, @Param(FlexConsts.ROWS) List<Row> rows);
081
082
083    /////// delete /////
084
085    /**
086     * 执行 delete sql 语言
087     *
088     * @param sql  delete sql 语句
089     * @param args 参数
090     * @return 执行影响的行数
091     * @see RowSqlProvider#providerRawSql(Map)
092     */
093    @DeleteProvider(value = RowSqlProvider.class, method = RowSqlProvider.METHOD_RAW_SQL)
094    int deleteBySql(@Param(FlexConsts.SQL) String sql, @Param(FlexConsts.SQL_ARGS) Object... args);
095
096    /**
097     * 根据 id 删除数据
098     *
099     * @param schema    模式
100     * @param tableName 表名
101     * @param row       id 和 值的数据,可以通过 {@link Row#ofKey(String, Object)} 来创建
102     * @return 执行影响的行数
103     */
104    default int deleteById(String schema, String tableName, Row row) {
105        return deleteById(schema, tableName, StringUtil.join(",", row.obtainsPrimaryKeyStrings()), row.obtainsPrimaryValues());
106    }
107
108    /**
109     * 根据 id 删除数据
110     *
111     * @param schema     模式
112     * @param tableName  表名
113     * @param primaryKey 主键,多个主键用英文逗号隔开
114     * @param id         数据,多个主键时传入数组,例如 new Object[]{1,2}
115     * @return 执行影响的行数
116     * @see RowSqlProvider#deleteById(Map)
117     */
118    @DeleteProvider(value = RowSqlProvider.class, method = "deleteById")
119    int deleteById(@Param(FlexConsts.SCHEMA_NAME) String schema, @Param(FlexConsts.TABLE_NAME) String tableName, @Param(FlexConsts.PRIMARY_KEY) String primaryKey, @Param(FlexConsts.PRIMARY_VALUE) Object id);
120
121
122    /**
123     * 根据 多个 id 值删除多条数据
124     *
125     * @param schema     模式
126     * @param tableName  表名
127     * @param primaryKey 主键
128     * @param ids        id 的集合
129     * @return 执行影响的行数
130     * @see RowSqlProvider#deleteBatchByIds(Map)
131     */
132    @DeleteProvider(value = RowSqlProvider.class, method = "deleteBatchByIds")
133    int deleteBatchByIds(@Param(FlexConsts.SCHEMA_NAME) String schema, @Param(FlexConsts.TABLE_NAME) String tableName, @Param(FlexConsts.PRIMARY_KEY) String primaryKey, @Param(FlexConsts.PRIMARY_VALUE) Collection<?> ids);
134
135
136    /**
137     * 根据 queryWrapper 构建 where 条件来删除数据
138     *
139     * @param schema       模式
140     * @param tableName    表名
141     * @param queryWrapper queryWrapper
142     * @return 执行影响的行数
143     * @see RowSqlProvider#deleteByQuery(Map)
144     */
145    @DeleteProvider(value = RowSqlProvider.class, method = "deleteByQuery")
146    int deleteByQuery(@Param(FlexConsts.SCHEMA_NAME) String schema, @Param(FlexConsts.TABLE_NAME) String tableName, @Param(FlexConsts.QUERY) QueryWrapper queryWrapper);
147
148
149    ////////update ////
150
151    /**
152     * 执行 update sql 语句
153     *
154     * @param sql  sql 语句
155     * @param args 参数内容
156     * @return 执行影响的行数
157     * @see RowSqlProvider#providerRawSql(Map)
158     */
159    @UpdateProvider(value = RowSqlProvider.class, method = RowSqlProvider.METHOD_RAW_SQL)
160    int updateBySql(@Param(FlexConsts.SQL) String sql, @Param(FlexConsts.SQL_ARGS) Object... args);
161
162
163    /**
164     * 根据主键来更新数据
165     *
166     * @param schema    模式
167     * @param tableName 表名
168     * @param row       数据,其必须包含主键数据列名和值
169     * @return 执行影响的行数
170     * @see RowSqlProvider#updateById(Map)
171     */
172    @UpdateProvider(value = RowSqlProvider.class, method = "updateById")
173    int updateById(@Param(FlexConsts.SCHEMA_NAME) String schema, @Param(FlexConsts.TABLE_NAME) String tableName, @Param(FlexConsts.ROW) Row row);
174
175
176    /**
177     * 根据 queryWrapper 来构建 where 条件更新数据
178     *
179     * @param schema       模式
180     * @param tableName    表名
181     * @param data         更新数据
182     * @param queryWrapper queryWrapper
183     * @return 执行影响的行数
184     * @see RowSqlProvider#updateByQuery(Map)
185     */
186    @UpdateProvider(value = RowSqlProvider.class, method = "updateByQuery")
187    int updateByQuery(@Param(FlexConsts.SCHEMA_NAME) String schema, @Param(FlexConsts.TABLE_NAME) String tableName, @Param(FlexConsts.ROW) Row data, @Param(FlexConsts.QUERY) QueryWrapper queryWrapper);
188
189
190    /**
191     * 根据主键来批量更新数据
192     * 注意:
193     * 1、此方法需要在 mysql 等链接配置需要开启 allowMultiQueries=true
194     * 2、更新成功返回的结果也可能为 0
195     *
196     * @param schema    模式
197     * @param tableName 表名
198     * @param rows      数据,其必须包含主键数据列名和值
199     * @return 执行影响的行数
200     * @see RowSqlProvider#updateBatchById(Map)
201     */
202    @UpdateProvider(value = RowSqlProvider.class, method = "updateBatchById")
203    int updateBatchById(@Param(FlexConsts.SCHEMA_NAME) String schema, @Param(FlexConsts.TABLE_NAME) String tableName, @Param(FlexConsts.ROWS) List<Row> rows);
204
205
206    /**
207     * 更新 entity,主要用于进行批量更新的场景
208     *
209     * @param entity 实体类
210     * @see RowSqlProvider#updateEntity(Map)
211     * @see Db#updateEntitiesBatch(Collection, int)
212     */
213    @UpdateProvider(value = RowSqlProvider.class, method = "updateEntity")
214    int updateEntity(@Param(FlexConsts.ENTITY) Object entity);
215
216
217
218    ///////select /////
219
220    /**
221     * 通过原生 SQL 查询 1 条数据,要求数据必须返回 1 条内容,否则会报错
222     *
223     * @param sql  select sql 语句
224     * @param args 参数
225     * @return 返回一条数据
226     */
227    default Row selectOneBySql(String sql, Object... args) {
228        List<Row> rows = selectListBySql(sql, args);
229        if (rows == null || rows.isEmpty()) {
230            return null;
231        } else if (rows.size() == 1) {
232            return rows.get(0);
233        } else {
234            /** 当返回多条数据时,抛出异常, 保持和 Mybatis DefaultSqlSession 的统一逻辑,
235             * see: {@link org.apache.ibatis.session.defaults.DefaultSqlSession#selectOne(String, Object)} **/
236            throw new TooManyResultsException("Expected one result (or null) to be returned by selectOneBySql(), but found: " + rows.size());
237        }
238    }
239
240    /**
241     * 通过主键来查询数据
242     *
243     * @param schema    模式
244     * @param tableName 表名
245     * @param row       主键和ID的描述,通过 {@link Row#ofKey(String, Object)} 来进行构建
246     * @return 返回一条数据,或者 null
247     */
248    default Row selectOneById(String schema, String tableName, Row row) {
249        return selectOneById(schema, tableName, StringUtil.join(",", row.obtainsPrimaryKeyStrings()), row.obtainsPrimaryValues());
250    }
251
252    /**
253     * 根据主键来查询数据
254     *
255     * @param schema     模式
256     * @param tableName  表名
257     * @param primaryKey 主键
258     * @param id         id 值
259     * @return row or null
260     * @see RowSqlProvider#selectOneById(Map)
261     */
262    @SelectProvider(value = RowSqlProvider.class, method = "selectOneById")
263    Row selectOneById(@Param(FlexConsts.SCHEMA_NAME) String schema, @Param(FlexConsts.TABLE_NAME) String tableName, @Param(FlexConsts.PRIMARY_KEY) String primaryKey, @Param(FlexConsts.PRIMARY_VALUE) Object id);
264
265
266    /**
267     * 根据 queryWrapper 来查询 1 条数据
268     *
269     * @param schema       模式
270     * @param tableName    表名
271     * @param queryWrapper queryWrapper
272     * @return row or null
273     */
274    default Row selectOneByQuery(String schema, String tableName, QueryWrapper queryWrapper) {
275        List<Row> rows = selectListByQuery(schema, tableName, queryWrapper.limit(1L));
276        if (rows == null || rows.isEmpty()) {
277            return null;
278        } else {
279            return rows.get(0);
280        }
281    }
282
283    /**
284     * 通过自定义 sql 来查询一个 Row 列表
285     *
286     * @param sql  自定义的 sql
287     * @param args sql 参数
288     * @return row 列表
289     */
290    @SelectProvider(value = RowSqlProvider.class, method = RowSqlProvider.METHOD_RAW_SQL)
291    List<Row> selectListBySql(@Param(FlexConsts.SQL) String sql, @Param(FlexConsts.SQL_ARGS) Object... args);
292
293
294    /**
295     * 根据 queryWrapper 来查询一个 row 列表
296     *
297     * @param schema       模式
298     * @param tableName    表名
299     * @param queryWrapper queryWrapper
300     * @return row 列表
301     * @see RowSqlProvider#selectListByQuery(Map)
302     */
303    @SelectProvider(value = RowSqlProvider.class, method = "selectListByQuery")
304    List<Row> selectListByQuery(@Param(FlexConsts.SCHEMA_NAME) String schema, @Param(FlexConsts.TABLE_NAME) String tableName, @Param(FlexConsts.QUERY) QueryWrapper queryWrapper);
305
306
307    /**
308     * 查询某张表的全部数据
309     *
310     * @param schema    模式
311     * @param tableName 表名
312     * @return row 列表
313     */
314    default List<Row> selectAll(String schema, String tableName) {
315        return selectListByQuery(schema, tableName, QueryWrapper.create());
316    }
317
318    /**
319     * 通过 sql 查询某一个数据,sql 执行的结果应该只有 1 行 1 列
320     * 若返回有多列,则只取第一列的值,若有多行,则会出现 TooManyResultsException 错误
321     *
322     * @param sql  sql
323     * @param args sql 参数
324     * @return object
325     */
326    @SelectProvider(value = RowSqlProvider.class, method = RowSqlProvider.METHOD_RAW_SQL)
327    Object selectObject(@Param(FlexConsts.SQL) String sql, @Param(FlexConsts.SQL_ARGS) Object... args);
328
329
330    /**
331     * 通过 sql 查询多行数据,sql 执行的结果应该只有 1 列
332     *
333     * @param sql  sql 语句
334     * @param args sql 参数
335     * @return object list
336     */
337    @SelectProvider(value = RowSqlProvider.class, method = RowSqlProvider.METHOD_RAW_SQL)
338    List<Object> selectObjectList(@Param(FlexConsts.SQL) String sql, @Param(FlexConsts.SQL_ARGS) Object... args);
339
340
341    /**
342     * 查询数据,一般用于 select count(*)... 的语言,也可用于执行的结果只有一个数值的其他 sql
343     *
344     * @param sql  sql 语句
345     * @param args sql 参数
346     * @return 返回数据
347     */
348    default long selectCount(String sql, Object... args) {
349        Object object = selectObject(sql, args);
350        if (object == null) {
351            return 0;
352        } else if (object instanceof Number) {
353            return ((Number) object).longValue();
354        } else {
355            throw FlexExceptions.wrap("selectCount error, Can not get number value for sql: %s", sql);
356        }
357    }
358
359
360    /**
361     * 根据 queryWrapper 1 条数据
362     * queryWrapper 执行的结果应该只有 1 列,例如 QueryWrapper.create().select(ACCOUNT.id).where...
363     *
364     * @param schema       模式
365     * @param tableName    表名
366     * @param queryWrapper queryWrapper
367     * @return 数据
368     */
369    default Object selectObjectByQuery(String schema, String tableName, QueryWrapper queryWrapper) {
370        queryWrapper.limit(1L);
371        List<Object> objects = selectObjectListByQuery(schema, tableName, queryWrapper);
372        if (objects == null || objects.isEmpty()) {
373            return null;
374        }
375        return objects.get(0);
376    }
377
378
379    /**
380     * 根据 queryWrapper 来查询数据列表
381     * queryWrapper 执行的结果应该只有 1 列,例如 QueryWrapper.create().select(ACCOUNT.id).where...
382     *
383     * @param queryWrapper 查询包装器
384     * @return 数据列表
385     * @see RowSqlProvider#selectListByQuery(Map)
386     */
387    @SelectProvider(type = RowSqlProvider.class, method = "selectListByQuery")
388    List<Object> selectObjectListByQuery(@Param(FlexConsts.SCHEMA_NAME) String schema
389        , @Param(FlexConsts.TABLE_NAME) String tableName, @Param(FlexConsts.QUERY) QueryWrapper queryWrapper);
390
391
392    /**
393     * 查询数据量
394     *
395     * @param schema       模式
396     * @param tableName    表名
397     * @param queryWrapper 查询包装器
398     * @return 数据量
399     */
400    default long selectCountByQuery(String schema, String tableName, QueryWrapper queryWrapper) {
401        List<QueryColumn> selectColumns = CPI.getSelectColumns(queryWrapper);
402        if (CollectionUtil.isEmpty(selectColumns)) {
403            queryWrapper.select(count());
404        }
405
406        List<Object> objects = selectObjectListByQuery(schema, tableName, queryWrapper);
407        return MapperUtil.getLongNumber(objects);
408    }
409
410
411    /**
412     * 分页查询数据
413     *
414     * @param schema       模式
415     * @param tableName    表名
416     * @param page         page 封装类
417     * @param queryWrapper 条件
418     * @return
419     */
420    default Page<Row> paginate(String schema, String tableName, Page<Row> page, QueryWrapper queryWrapper) {
421        try {
422            CPI.setFromIfNecessary(queryWrapper, schema, tableName);
423
424            // 只有 totalRow 小于 0 的时候才会去查询总量
425            // 这样方便用户做总数缓存,而非每次都要去查询总量
426            // 一般的分页场景中,只有第一页的时候有必要去查询总量,第二页以后是不需要的
427            if (page.getTotalRow() < 0) {
428                QueryWrapper countQueryWrapper;
429                if (page.needOptimizeCountQuery()) {
430                    countQueryWrapper = MapperUtil.optimizeCountQueryWrapper(queryWrapper);
431                } else {
432                    countQueryWrapper = MapperUtil.rawCountQueryWrapper(queryWrapper);
433                }
434                page.setTotalRow(selectCountByQuery(schema, tableName, countQueryWrapper));
435            }
436
437            if (!page.hasRecords()) {
438                return page;
439            }
440
441            queryWrapper.limit(page.offset(), page.getPageSize());
442
443            page.setRecords(selectListByQuery(schema, tableName, queryWrapper));
444
445            return page;
446
447        } finally {
448            // 将之前设置的 limit 清除掉
449            // 保险起见把重置代码放到 finally 代码块中
450            CPI.setLimitRows(queryWrapper, null);
451            CPI.setLimitOffset(queryWrapper, null);
452        }
453
454    }
455
456}