diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index ac662e4d..397957ee 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -58,6 +58,7 @@ export default defineConfig({ items: [ {text: '增删改', link: '/zh/base/add-delete-update'}, {text: '查询和分页', link: '/zh/base/query'}, + {text: 'IService', link: '/zh/base/service'}, {text: 'QueryWrapper', link: '/zh/base/querywrapper'}, ] }, diff --git a/docs/zh/base/service.md b/docs/zh/base/service.md new file mode 100644 index 00000000..17b0f7a2 --- /dev/null +++ b/docs/zh/base/service.md @@ -0,0 +1,73 @@ +# 顶级 Service 接口 + +MyBatis-Flex 提供了一个名为 `IService` 的接口,及其默认实现类 `ServiceImpl` ,用于简化在 「Service」 层重复定义 「Mapper」 层的方法。 + +> `IService` 接口只是提供了简单且常用的 “增删改查” 方法,更多细节以及复杂的业务,还是需要使用 `BaseMapper` 进行处理。 + +## 保存数据 + +`IService` 的接口提供了 save、saveOrUpdate、saveBatch 方法,用于保存数据: + +- **save(entity)**:保存一条数据,忽略 null 值的字段。 +- **saveOrUpdate(entity)**:保存一条数据,如果数据存在则更新数据。 +- **saveBatch(entities)**:批量保存多条数据。 +- **saveBatch(entities, size)**:批量保存多条数据,按指定数量切分。 + + +## 删除数据 + +`IService` 的接口提供了 remove、removeById、removeByIds、removeByMap 方法,用于删除数据: + +- **remove(query)**:根据 `QueryCondition` 或 `QueryWrapper` 构建的条件来删除数据。 +- **removeById(id)**:根据主键删除数据,复合主键需要传入一个数组。 +- **removeByIds(ids)**:根据主键的集合,批量删除多条数据。 +- **removeByMap(map)**:根据 `Map<字段名,值>` 组成的条件删除数据,字段名和值的关系为相等的关系;同时,防止 "不小心" 全表删除数据,Map 的值不允许为 null 或者空数据。 + + +## 更新数据 + +`IService` 的接口提供了 update、updateById、updateByMap 方法,用于更新数据: + +- **update(entity, query)**:根据 `QueryCondition` 或 `QueryWrapper` 构建的条件更新数据,实体类可以没有主键(如果有也会被忽略),实体类的 null 属性,会自动被忽略。 +- **updateById(entity)**:根据主键更新数据,要求主键值不能为空,否则会抛出异常;同时,数据为 null 的字段不会更新到数据库。 +- **updateByMap(entity, map)**:根据 `Map<字段名,值>` 组成的条件更新数据,实体类可以没有主键(如果有也会被忽略),实体类的 null 属性,会自动被忽略。 + + +## 查询数据 + +### 查询一条数据 + +`IService` 的接口提供了 getById、getByIdOpt、getOne、getOneOpt 方法,用于查询一条数据: + +- **getById(id)**:根据主键查询数据。 +- **getByIdOpt(id)**:根据主键查询数据,并封装为 `Optional` 返回。 +- **getOne(query)**: 根据 `QueryCondition` 或 `QueryWrapper` 构建的条件查询一条数据。 +- **getOneOpt(query)**: 根据 `QueryCondition` 或 `QueryWrapper` 构建的条件查询一条数据,并封装为 `Optional` 返回。 + +### 查询多条数据 + +`IService` 的接口提供了 list、listByIds、listByMap 方法,用于查询多条数据: + +- **list()**:查询所有数据。 +- **list(query)**:根据 `QueryCondition` 或 `QueryWrapper` 构建的条件查询多条数据。 +- **listByIds(ids)**:根据主键的集合查询多条数据。 +- **listByMap(map)**:根据 `Map<字段名,值>` 组成的条件查询多条数据。 + +### 查询数据数量 + +`IService` 的接口提供了 exists、count 方法,用于查询数据数量; + +- **count()**:查询所有数据数量。 +- **count(query)**:根据 `QueryCondition` 或 `QueryWrapper` 构建的条件查询数据数量。 +- **exist(query)**:根据 `QueryCondition` 或 `QueryWrapper` 构建的条件判断数据是否存在。 + +### 分页查询数据 + +`IService` 的接口提供了 page 方法,用于分页查询数据: + +- **page(page)**:分页查询所有数据。 +- **page(page, query)**:根据 `QueryCondition` 或 `QueryWrapper` 构建的条件分页查询数据。 + +## 其他方法 + +- **getBaseMapper()**:获取对应的 `BaseMapper` 接口。 \ No newline at end of file diff --git a/mybatis-flex-core/src/main/java/com/mybatisflex/core/util/SqlUtil.java b/mybatis-flex-core/src/main/java/com/mybatisflex/core/util/SqlUtil.java index 0ebabaaf..60fc7870 100644 --- a/mybatis-flex-core/src/main/java/com/mybatisflex/core/util/SqlUtil.java +++ b/mybatis-flex-core/src/main/java/com/mybatisflex/core/util/SqlUtil.java @@ -62,4 +62,25 @@ public class SqlUtil { } + /** + * 根据数据库响应结果判断数据库操作是否成功。 + * + * @param result 数据库操作返回影响条数 + * @return {@code true} 操作成功,{@code false} 操作失败。 + */ + public static boolean retBool(int result) { + return result >= 1; + } + + /** + * 根据数据库响应结果判断数据库操作是否成功。 + * + * @param result 数据库操作返回影响条数 + * @return {@code true} 操作成功,{@code false} 操作失败。 + */ + public static boolean retBool(long result) { + return result >= 1L; + } + + } diff --git a/mybatis-flex-spring/src/main/java/com/mybatisflex/spring/service/IService.java b/mybatis-flex-spring/src/main/java/com/mybatisflex/spring/service/IService.java new file mode 100644 index 00000000..55e4c14a --- /dev/null +++ b/mybatis-flex-spring/src/main/java/com/mybatisflex/spring/service/IService.java @@ -0,0 +1,395 @@ +/** + * Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com). + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.mybatisflex.spring.service; + +import com.mybatisflex.core.BaseMapper; +import com.mybatisflex.core.paginate.Page; +import com.mybatisflex.core.query.QueryCondition; +import com.mybatisflex.core.query.QueryWrapper; +import com.mybatisflex.core.util.CollectionUtil; +import org.springframework.transaction.annotation.Transactional; + +import java.io.Serializable; +import java.util.*; + +import static com.mybatisflex.core.util.SqlUtil.retBool; + +/** + * 由 Mybatis-Flex 提供的顶级增强 Service 接口。 + * + * @author 王帅 + * @since 2023-05-01 + * @param 实体类(Entity)类型 + */ +@SuppressWarnings("unused") +public interface IService { + + /** + * 获取对应实体类(Entity)的基础映射类(BaseMapper)。 + * + * @return 基础映射类(BaseMapper) + */ + BaseMapper getBaseMapper(); + + // ===== 保存(增)操作 ===== + + /** + * 保存实体类对象数据。 + * + * @param entity 实体类对象 + * @return {@code true} 保存成功,{@code false} 保存失败。 + * @apiNote 默认调用的是 {@link BaseMapper#insertSelective(Object)} 方法,忽略 + * {@code null} 字段的数据,使数据库配置的默认值生效。 + */ + default boolean save(T entity) { + return retBool(getBaseMapper().insertSelective(entity)); + } + + /** + * 保存或者更新实体类对象数据。 + * + * @param entity 实体类对象 + * @return {@code true} 保存或更新成功,{@code false} 保存或更新失败。 + * @apiNote 如果实体类对象主键有值,则更新数据,若没有值,则保存数据。 + */ + default boolean saveOrUpdate(T entity) { + return retBool(getBaseMapper().insertOrUpdate(entity)); + } + + /** + * 批量保存实体类对象数据。 + * + * @param entities 实体类对象 + * @return {@code true} 保存成功,{@code false} 保存失败。 + */ + @Transactional(rollbackFor = Exception.class) + default boolean saveBatch(Collection entities) { + return retBool(getBaseMapper().insertBatch(new ArrayList<>(entities))); + } + + /** + * 批量保存实体类对象数据。 + * + * @param entities 实体类对象 + * @param size 每次保存切分的数量 + * @return {@code true} 保存成功,{@code false} 保存失败。 + */ + @Transactional(rollbackFor = Exception.class) + default boolean saveBatch(Collection entities, int size) { + return retBool(getBaseMapper().insertBatch(new ArrayList<>(entities), size)); + } + + // ===== 删除(删)操作 ===== + + /** + * 根据查询条件删除数据。 + * + * @param query 查询条件 + * @return {@code true} 删除成功,{@code false} 删除失败。 + */ + default boolean remove(QueryWrapper query) { + return retBool(getBaseMapper().deleteByQuery(query)); + } + + /** + * 根据查询条件删除数据。 + * + * @param query 查询条件 + * @return {@code true} 删除成功,{@code false} 删除失败。 + */ + default boolean remove(QueryCondition query) { + return retBool(getBaseMapper().deleteByCondition(query)); + } + + /** + * 根据数据主键删除数据。 + * + * @param id 数据主键 + * @return {@code true} 删除成功,{@code false} 删除失败。 + */ + default boolean removeById(Serializable id) { + return retBool(getBaseMapper().deleteById(id)); + } + + /** + * 根据数据主键批量删除数据。 + * + * @param ids 数据主键 + * @return {@code true} 删除成功,{@code false} 删除失败。 + */ + @Transactional(rollbackFor = Exception.class) + default boolean removeByIds(Collection ids) { + if (CollectionUtil.isEmpty(ids)) { + return false; + } + return retBool(getBaseMapper().deleteBatchByIds(ids)); + } + + /** + * 根据 {@link Map} 构建查询条件删除数据。 + * + * @param query 查询条件 + * @return {@code true} 删除成功,{@code false} 删除失败。 + */ + default boolean removeByMap(Map query) { + return retBool(getBaseMapper().deleteByMap(query)); + } + + // ===== 更新(改)操作 ===== + + /** + * 根据查询条件更新数据。 + * + * @param entity 实体类对象 + * @param query 查询条件 + * @return {@code true} 更新成功,{@code false} 更新失败。 + */ + default boolean update(T entity, QueryWrapper query) { + return retBool(getBaseMapper().updateByQuery(entity, query)); + } + + /** + * 根据查询条件更新数据。 + * + * @param entity 实体类对象 + * @param query 查询条件 + * @return {@code true} 更新成功,{@code false} 更新失败。 + */ + default boolean update(T entity, QueryCondition query) { + return retBool(getBaseMapper().updateByCondition(entity, query)); + } + + /** + * 根据数据主键更新数据。 + * + * @param entity 实体类对象 + * @return {@code true} 更新成功,{@code false} 更新失败。 + */ + default boolean updateById(T entity) { + return retBool(getBaseMapper().update(entity)); + } + + /** + * 根据 {@link Map} 构建查询条件更新数据。 + * + * @param entity 实体类对象 + * @param query 查询条件 + * @return {@code true} 更新成功,{@code false} 更新失败。 + */ + default boolean updateByMap(T entity, Map query) { + return retBool(getBaseMapper().updateByMap(entity, query)); + } + + // ===== 查询(查)操作 ===== + + /** + * 根据数据主键查询一条数据。 + * + * @param id 数据主键 + * @return 查询结果数据 + */ + default T getById(Serializable id) { + return getBaseMapper().selectOneById(id); + } + + /** + * 根据数据主键查询一条数据。 + * + * @param id 数据主键 + * @return 查询结果数据 + * @apiNote 该方法会将查询结果封装为 {@link Optional} 类进行返回,方便链式操作。 + */ + default Optional getByIdOpt(Serializable id) { + return Optional.ofNullable(getById(id)); + } + + /** + * 根据查询条件查询一条数据。 + * + * @param query 查询条件 + * @return 查询结果数据 + */ + default T getOne(QueryWrapper query) { + return getBaseMapper().selectOneByQuery(query); + } + + /** + * 根据查询条件查询一条数据。 + * + * @param query 查询条件 + * @return 查询结果数据 + * @apiNote 该方法会将查询结果封装为 {@link Optional} 类进行返回,方便链式操作。 + */ + default Optional getOneOpt(QueryWrapper query) { + return Optional.ofNullable(getOne(query)); + } + + /** + * 根据查询条件查询一条数据。 + * + * @param query 查询条件 + * @return 查询结果数据 + */ + default T getOne(QueryCondition query) { + return getBaseMapper().selectOneByCondition(query); + } + + /** + * 根据查询条件查询一条数据。 + * + * @param query 查询条件 + * @return 查询结果数据 + * @apiNote 该方法会将查询结果封装为 {@link Optional} 类进行返回,方便链式操作。 + */ + default Optional getOneOpt(QueryCondition query) { + return Optional.ofNullable(getOne(query)); + } + + /** + * 查询所有数据。 + * + * @return 所有数据 + */ + default List list() { + return getBaseMapper().selectAll(); + } + + /** + * 根据查询条件查询数据集合。 + * + * @param query 查询条件 + * @return 数据集合 + */ + default List list(QueryWrapper query) { + return getBaseMapper().selectListByQuery(query); + } + + /** + * 根据查询条件查询数据集合。 + * + * @param query 查询条件 + * @return 数据集合 + */ + default List list(QueryCondition query) { + return getBaseMapper().selectListByCondition(query); + } + + /** + * 根据数据主键查询数据集合。 + * + * @param ids 数据主键 + * @return 数据集合 + */ + default List listByIds(Collection ids) { + return getBaseMapper().selectListByIds(ids); + } + + /** + * 根据 {@link Map} 构建查询条件查询数据集合。 + * + * @param query 查询条件 + * @return 数据集合 + */ + default List listByMap(Map query) { + return getBaseMapper().selectListByMap(query); + } + + // ===== 数量查询操作 ===== + + /** + * 根据查询条件判断数据是否存在。 + * + * @param query 查询条件 + * @return {@code true} 数据存在,{@code false} 数据不存在。 + */ + default boolean exists(QueryWrapper query) { + return retBool(count(query)); + } + + /** + * 根据查询条件判断数据是否存在。 + * + * @param query 查询条件 + * @return {@code true} 数据存在,{@code false} 数据不存在。 + */ + default boolean exists(QueryCondition query) { + return retBool(count(query)); + } + + /** + * 查询所有数据数量。 + * + * @return 所有数据数量 + */ + default long count() { + return getBaseMapper().selectCountByQuery(QueryWrapper.create()); + } + + /** + * 根据查询条件查询数据数量。 + * + * @param query 查询条件 + * @return 数据数量 + */ + default long count(QueryWrapper query) { + return getBaseMapper().selectCountByQuery(query); + } + + /** + * 根据查询条件查询数据数量。 + * + * @param query 查询条件 + * @return 数据数量 + */ + default long count(QueryCondition query) { + return getBaseMapper().selectCountByCondition(query); + } + + // ===== 分页查询操作 ===== + + /** + * 分页查询所有数据。 + * + * @param page 分页对象 + * @return 分页对象 + */ + default Page page(Page page) { + return getBaseMapper().paginate(page, QueryWrapper.create()); + } + + /** + * 根据查询条件分页查询数据。 + * + * @param page 分页对象 + * @param query 查询条件 + * @return 分页对象 + */ + default Page page(Page page, QueryWrapper query) { + return getBaseMapper().paginate(page, query); + } + + /** + * 根据查询条件分页查询数据。 + * + * @param page 分页对象 + * @param query 查询条件 + * @return 分页对象 + */ + default Page page(Page page, QueryCondition query) { + return getBaseMapper().paginate(page, QueryWrapper.create().where(query)); + } + +} \ No newline at end of file diff --git a/mybatis-flex-spring/src/main/java/com/mybatisflex/spring/service/impl/ServiceImpl.java b/mybatis-flex-spring/src/main/java/com/mybatisflex/spring/service/impl/ServiceImpl.java new file mode 100644 index 00000000..e1c537a9 --- /dev/null +++ b/mybatis-flex-spring/src/main/java/com/mybatisflex/spring/service/impl/ServiceImpl.java @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com). + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.mybatisflex.spring.service.impl; + +import com.mybatisflex.core.BaseMapper; +import com.mybatisflex.spring.service.IService; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * 由 Mybatis-Flex 提供的顶级增强 Service 接口的默认实现类。 + * + * @author 王帅 + * @since 2023-05-01 + * @param 映射类(Mapper)类型 + * @param 实体类(Entity)类型 + */ +@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") +public class ServiceImpl, T> implements IService { + + @Autowired + protected M baseMapper; + + @Override + public BaseMapper getBaseMapper() { + return baseMapper; + } + +} \ No newline at end of file