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