1
0

docs: 补充代码注释

This commit is contained in:
2025-08-16 15:39:29 +08:00
parent 895ce7ee82
commit c8501e7baf
10 changed files with 889 additions and 10 deletions

View File

@@ -2,8 +2,28 @@ package com.lanyuanxiaoyao.service.template.controller;
import com.lanyuanxiaoyao.service.template.controller.response.GlobalResponse;
/**
* 详情控制器接口,用于定义统一的获取实体详情的接口规范
*
* <p>
* 前端传入的JSON格式示例:
* <pre>
* GET /detail/1
* </pre>
* </p>
*
* @param <DETAIL_ITEM> 详情实体类型
* @author lanyuanxiaoyao
*/
public interface DetailController<DETAIL_ITEM> {
String DETAIL = "/detail/{id}";
/**
* 根据ID获取实体详情
*
* @param id 实体ID
* @return GlobalResponse<DETAIL_ITEM> 返回实体详情
* @throws Exception 查询过程中可能抛出的异常
*/
GlobalResponse<DETAIL_ITEM> detail(Long id) throws Exception;
}
}

View File

@@ -2,10 +2,77 @@ package com.lanyuanxiaoyao.service.template.controller;
import com.lanyuanxiaoyao.service.template.controller.response.GlobalCrudResponse;
/**
* 列表控制器接口,用于定义统一的获取实体列表的接口规范
*
* <p>
* 前端传入的JSON格式示例:
* <pre>
* {
* "query": {
* "equal": {
* "status": "ACTIVE"
* },
* "like": {
* "name": "关键字"
* }
* },
* "sort": [
* {
* "column": "createTime",
* "direction": "DESC"
* }
* ],
* "page": {
* "index": 0,
* "size": 10
* }
* }
* </pre>
* </p>
*
* <p>
* 支持的查询条件说明:
* <ul>
* <li>nullEqual: 指定字段值为null的条件列表</li>
* <li>notNullEqual: 指定字段值不为null的条件列表</li>
* <li>empty: 指定字段值为空的条件列表(如空字符串、空集合等)</li>
* <li>notEmpty: 指定字段值不为空的条件列表</li>
* <li>equal: 指定字段值相等的条件映射(字段名 -> 值)</li>
* <li>notEqual: 指定字段值不相等的条件映射(字段名 -> 值)</li>
* <li>like: 指定字段模糊匹配的条件映射(字段名 -> 匹配值)</li>
* <li>notLike: 指定字段不模糊匹配的条件映射(字段名 -> 匹配值)</li>
* <li>great: 指定字段大于条件的映射(字段名 -> 值)</li>
* <li>less: 指定字段小于条件的映射(字段名 -> 值)</li>
* <li>greatEqual: 指定字段大于等于条件的映射(字段名 -> 值)</li>
* <li>lessEqual: 指定字段小于等于条件的映射(字段名 -> 值)</li>
* <li>in: 指定字段值在指定范围内的条件映射(字段名 -> 值列表)</li>
* <li>notIn: 指定字段值不在指定范围内的条件映射(字段名 -> 值列表)</li>
* <li>between: 指定字段值在指定区间内的条件映射(字段名 -> 区间范围)</li>
* <li>notBetween: 指定字段值不在指定区间内的条件映射(字段名 -> 区间范围)</li>
* </ul>
* </p>
*
* @param <LIST_ITEM> 列表项的实体类型
* @author lanyuanxiaoyao
*/
public interface ListController<LIST_ITEM> {
String LIST = "/list";
/**
* 获取所有实体列表
*
* @return GlobalCrudResponse<LIST_ITEM> 返回实体列表
* @throws Exception 查询过程中可能抛出的异常
*/
GlobalCrudResponse<LIST_ITEM> list() throws Exception;
/**
* 根据查询条件获取实体列表
*
* @param query 查询条件对象
* @return GlobalCrudResponse<LIST_ITEM> 返回符合条件的实体列表
* @throws Exception 查询过程中可能抛出的异常
*/
GlobalCrudResponse<LIST_ITEM> list(Query query) throws Exception;
}
}

View File

@@ -6,62 +6,218 @@ import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
/**
* 查询条件封装类,用于构建复杂的查询条件
* 包含查询条件、排序条件和分页条件
*
* <p>前端传入的JSON格式示例:</p>
* <pre>
* {
* "query": {
* "equal": {
* "name": "张三"
* },
* "like": {
* "address": "%北京%"
* },
* "greatEqual": {
* "age": 18
* },
* "less": {
* "age": 60
* },
* "between": {
* "salary": {
* "start": 5000,
* "end": 10000
* }
* }
* },
* "sort": [
* {
* "column": "createTime",
* "direction": "DESC"
* }
* ],
* "page": {
* "index": 0,
* "size": 10
* }
* }
* </pre>
*
* <p>查询条件说明:</p>
* <ul>
* <li>nullEqual: 字段值为null的条件</li>
* <li>notNullEqual: 字段值不为null的条件</li>
* <li>empty: 字段值为空的条件</li>
* <li>notEmpty: 字段值不为空的条件</li>
* <li>equal: 字段值相等的条件</li>
* <li>notEqual: 字段值不相等的条件</li>
* <li>like: 字段值模糊匹配的条件</li>
* <li>notLike: 字段值不模糊匹配的条件</li>
* <li>great: 字段值大于的条件</li>
* <li>less: 字段值小于的条件</li>
* <li>greatEqual: 字段值大于等于的条件</li>
* <li>lessEqual: 字段值小于等于的条件</li>
* <li>in: 字段值在指定范围内的条件</li>
* <li>notIn: 字段值不在指定范围内的条件</li>
* <li>between: 字段值在指定区间内的条件</li>
* <li>notBetween: 字段值不在指定区间内的条件</li>
* </ul>
*/
@Setter
@Getter
@ToString
public class Query {
/**
* 查询条件
*/
private Queryable query;
/**
* 排序条件列表
*/
private List<Sortable> sort;
/**
* 分页条件
*/
private Pageable page;
/**
* 可查询条件类,封装各种查询条件
*/
@Setter
@Getter
@ToString
public static class Queryable {
/**
* 指定字段值为null的条件列表
*/
private List<String> nullEqual;
/**
* 指定字段值不为null的条件列表
*/
private List<String> notNullEqual;
/**
* 指定字段值为空的条件列表(如空字符串、空集合等)
*/
private List<String> empty;
/**
* 指定字段值不为空的条件列表
*/
private List<String> notEmpty;
/**
* 指定字段值相等的条件映射(字段名 -> 值)
*/
private Map<String, Object> equal;
/**
* 指定字段值不相等的条件映射(字段名 -> 值)
*/
private Map<String, Object> notEqual;
/**
* 指定字段模糊匹配的条件映射(字段名 -> 匹配值)
*/
private Map<String, String> like;
/**
* 指定字段不模糊匹配的条件映射(字段名 -> 匹配值)
*/
private Map<String, String> notLike;
/**
* 指定字段大于条件的映射(字段名 -> 值)
*/
private Map<String, Object> great;
/**
* 指定字段小于条件的映射(字段名 -> 值)
*/
private Map<String, Object> less;
/**
* 指定字段大于等于条件的映射(字段名 -> 值)
*/
private Map<String, Object> greatEqual;
/**
* 指定字段小于等于条件的映射(字段名 -> 值)
*/
private Map<String, Object> lessEqual;
/**
* 指定字段值在指定范围内的条件映射(字段名 -> 值列表)
*/
private Map<String, List<Object>> in;
/**
* 指定字段值不在指定范围内的条件映射(字段名 -> 值列表)
*/
private Map<String, List<Object>> notIn;
/**
* 指定字段值在指定区间内的条件映射(字段名 -> 区间范围)
*/
private Map<String, Between> between;
/**
* 指定字段值不在指定区间内的条件映射(字段名 -> 区间范围)
*/
private Map<String, Between> notBetween;
/**
* 区间范围类,用于表示起始值和结束值
*/
@Setter
@Getter
@ToString
public static class Between {
/**
* 起始值
*/
private Object start;
/**
* 结束值
*/
private Object end;
}
}
/**
* 可排序条件类,用于指定排序字段和排序方向
*/
@Setter
@Getter
@ToString
public static class Sortable {
/**
* 排序字段名
*/
private String column;
/**
* 排序方向
*/
private Direction direction;
/**
* 排序方向枚举
*/
public enum Direction {
/**
* 升序排列
*/
ASC,
/**
* 降序排列
*/
DESC,
}
}
/**
* 可分页条件类,用于指定分页参数
*/
@Setter
@Getter
@ToString
public static class Pageable {
/**
* 页码索引从0开始
*/
private Integer index;
/**
* 每页大小
*/
private Integer size;
}
}
}

View File

@@ -2,8 +2,27 @@ package com.lanyuanxiaoyao.service.template.controller;
import com.lanyuanxiaoyao.service.template.controller.response.GlobalResponse;
/**
* 删除控制器接口,用于定义统一的删除实体对象的接口规范
*
* <p>
* 前端传入的JSON格式示例:
* <pre>
* DELETE /remove/1
* </pre>
* </p>
*
* @author lanyuanxiaoyao
*/
public interface RemoveController {
String REMOVE = "/remove/{id}";
/**
* 根据ID删除实体对象
*
* @param id 需要删除的实体ID
* @return GlobalResponse<Object> 返回删除结果
* @throws Exception 删除过程中可能抛出的异常
*/
GlobalResponse<Object> remove(Long id) throws Exception;
}
}

View File

@@ -2,8 +2,31 @@ package com.lanyuanxiaoyao.service.template.controller;
import com.lanyuanxiaoyao.service.template.controller.response.GlobalResponse;
/**
* 保存控制器接口,用于定义统一的保存实体对象的接口规范
*
* <p>
* 前端传入的JSON格式示例:
* <pre>
* {
* // 实体对象的具体字段
* "name": "示例名称",
* "description": "示例描述"
* }
* </pre>
* </p>
*
* @author lanyuanxiaoyao
*/
public interface SaveController<SAVE_ITEM> {
String SAVE = "/save";
/**
* 保存实体对象
*
* @param item 需要保存的实体对象
* @return GlobalResponse<Long> 返回保存后的实体ID
* @throws Exception 保存过程中可能抛出的异常
*/
GlobalResponse<Long> save(SAVE_ITEM item) throws Exception;
}
}

View File

@@ -13,14 +13,106 @@ import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
/**
* 简单控制器支持类提供基础的CRUD操作实现
* <p>
* 该类实现了基本的增删改查功能,通过泛型支持不同类型的数据转换。
* 子类需要实现对应的Mapper函数来完成实体类与传输对象之间的转换。
* </p>
*
* <p>
* 前端传入的JSON格式示例:
* <pre>
* // 保存实体
* POST /save
* {
* // 保存项的具体字段
* "name": "示例名称",
* "description": "示例描述"
* }
*
* // 查询列表(无条件)
* GET /list
*
* // 查询列表(带条件)
* POST /list
* {
* "query": {
* "equal": {
* "status": "ACTIVE"
* },
* "like": {
* "name": "关键字"
* }
* },
* "sort": [
* {
* "column": "createTime",
* "direction": "DESC"
* }
* ],
* "page": {
* "index": 0,
* "size": 10
* }
* }
*
* // 获取详情
* GET /detail/1
*
* // 删除实体
* GET /remove/1
* </pre>
* </p>
*
* <p>
* 支持的查询条件说明:
* <ul>
* <li>nullEqual: 指定字段值为null的条件列表</li>
* <li>notNullEqual: 指定字段值不为null的条件列表</li>
* <li>empty: 指定字段值为空的条件列表(如空字符串、空集合等)</li>
* <li>notEmpty: 指定字段值不为空的条件列表</li>
* <li>equal: 指定字段值相等的条件映射(字段名 -> 值)</li>
* <li>notEqual: 指定字段值不相等的条件映射(字段名 -> 值)</li>
* <li>like: 指定字段模糊匹配的条件映射(字段名 -> 匹配值)</li>
* <li>notLike: 指定字段不模糊匹配的条件映射(字段名 -> 匹配值)</li>
* <li>great: 指定字段大于条件的映射(字段名 -> 值)</li>
* <li>less: 指定字段小于条件的映射(字段名 -> 值)</li>
* <li>greatEqual: 指定字段大于等于条件的映射(字段名 -> 值)</li>
* <li>lessEqual: 指定字段小于等于条件的映射(字段名 -> 值)</li>
* <li>in: 指定字段值在指定范围内的条件映射(字段名 -> 值列表)</li>
* <li>notIn: 指定字段值不在指定范围内的条件映射(字段名 -> 值列表)</li>
* <li>between: 指定字段值在指定区间内的条件映射(字段名 -> 区间范围)</li>
* <li>notBetween: 指定字段值不在指定区间内的条件映射(字段名 -> 区间范围)</li>
* </ul>
* </p>
*
* @param <ENTITY> 实体类型必须继承SimpleEntity
* @param <SAVE_ITEM> 保存项类型
* @param <LIST_ITEM> 列表项类型
* @param <DETAIL_ITEM> 详情项类型
* @author lanyuanxiaoyao
*/
@Slf4j
public abstract class SimpleControllerSupport<ENTITY extends SimpleEntity, SAVE_ITEM, LIST_ITEM, DETAIL_ITEM> implements SimpleController<SAVE_ITEM, LIST_ITEM, DETAIL_ITEM> {
protected final SimpleServiceSupport<ENTITY> service;
/**
* 构造函数
*
* @param service 简单服务支持类实例
*/
public SimpleControllerSupport(SimpleServiceSupport<ENTITY> service) {
this.service = service;
}
/**
* 保存实体对象
*
* @param item 需要保存的项
* @return GlobalResponse<Long> 返回保存后的实体ID
* @throws Exception 保存过程中可能抛出的异常
*/
@PostMapping(SAVE)
@Override
public GlobalResponse<Long> save(@RequestBody SAVE_ITEM item) throws Exception {
@@ -28,6 +120,12 @@ public abstract class SimpleControllerSupport<ENTITY extends SimpleEntity, SAVE_
return GlobalResponse.responseSuccess(service.save(mapper.apply(item)));
}
/**
* 获取所有实体列表
*
* @return GlobalCrudResponse<LIST_ITEM> 返回实体列表
* @throws Exception 查询过程中可能抛出的异常
*/
@GetMapping(LIST)
@Override
public GlobalCrudResponse<LIST_ITEM> list() throws Exception {
@@ -48,6 +146,13 @@ public abstract class SimpleControllerSupport<ENTITY extends SimpleEntity, SAVE_
);
}
/**
* 根据查询条件获取实体列表
*
* @param query 查询条件对象
* @return GlobalCrudResponse<LIST_ITEM> 返回符合条件的实体列表
* @throws Exception 查询过程中可能抛出的异常
*/
@PostMapping(LIST)
@Override
public GlobalCrudResponse<LIST_ITEM> list(@RequestBody Query query) throws Exception {
@@ -70,6 +175,13 @@ public abstract class SimpleControllerSupport<ENTITY extends SimpleEntity, SAVE_
);
}
/**
* 根据ID获取实体详情
*
* @param id 实体ID
* @return GlobalResponse<DETAIL_ITEM> 返回实体详情
* @throws Exception 查询过程中可能抛出的异常
*/
@GetMapping(DETAIL)
@Override
public GlobalResponse<DETAIL_ITEM> detail(@PathVariable("id") Long id) throws Exception {
@@ -77,6 +189,13 @@ public abstract class SimpleControllerSupport<ENTITY extends SimpleEntity, SAVE_
return GlobalResponse.responseSuccess(mapper.apply(service.detailOrThrow(id)));
}
/**
* 根据ID删除实体对象
*
* @param id 需要删除的实体ID
* @return GlobalResponse<Object> 返回删除结果
* @throws Exception 删除过程中可能抛出的异常
*/
@GetMapping(REMOVE)
@Override
public GlobalResponse<Object> remove(@PathVariable("id") Long id) throws Exception {
@@ -84,9 +203,24 @@ public abstract class SimpleControllerSupport<ENTITY extends SimpleEntity, SAVE_
return GlobalResponse.responseSuccess();
}
/**
* 保存项映射器,将保存项转换为实体对象
*
* @return Function<SAVE_ITEM, ENTITY> 保存项到实体的转换函数
*/
protected abstract Function<SAVE_ITEM, ENTITY> saveItemMapper();
/**
* 列表项映射器,将实体对象转换为列表项
*
* @return Function<ENTITY, LIST_ITEM> 实体到列表项的转换函数
*/
protected abstract Function<ENTITY, LIST_ITEM> listItemMapper();
/**
* 详情项映射器,将实体对象转换为详情项
*
* @return Function<ENTITY, DETAIL_ITEM> 实体到详情项的转换函数
*/
protected abstract Function<ENTITY, DETAIL_ITEM> detailItemMapper();
}
}

View File

@@ -10,14 +10,40 @@ import lombok.ToString;
import org.hibernate.annotations.Comment;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
/**
* 仅包含ID的基础实体类
* <p>
* 该类作为所有实体类的基类仅提供ID字段使用Snowflake算法生成唯一标识。
* 通过继承此类实体类可以自动获得唯一ID字段以及相关的JPA注解配置。
* </p>
*
* <p>
* 该类使用了以下注解:
* <ul>
* <li>@MappedSuperclass: 标识该类为JPA映射的超类其属性会被映射到子类的数据库表字段中</li>
* <li>@EntityListeners(AuditingEntityListener.class): 启用Spring Data JPA的审计功能</li>
* <li>@Id: 标识id字段为主键</li>
* <li>@SnowflakeId: 使用Snowflake算法生成唯一ID</li>
* <li>@Comment: 为数据库字段添加注释</li>
* </ul>
* </p>
*
* @author lanyuanxiaoyao
*/
@Getter
@Setter
@ToString
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public class IdOnlyEntity {
/**
* 实体唯一标识符
* <p>
* 使用Snowflake算法生成的Long类型ID保证全局唯一性。
* </p>
*/
@Id
@SnowflakeId
@Comment("记录唯一标记")
private Long id;
}
}

View File

@@ -11,16 +11,49 @@ import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
/**
* 简单实体类,包含基础字段
* <p>
* 该类继承自IdOnlyEntity除了具备唯一ID外还提供了创建时间和修改时间字段。
* 通过Spring Data JPA的审计功能自动维护时间字段适用于大多数业务实体场景。
* </p>
*
* <p>
* 该类使用了以下注解:
* <ul>
* <li>@MappedSuperclass: 标识该类为JPA映射的超类其属性会被映射到子类的数据库表字段中</li>
* <li>@EntityListeners(AuditingEntityListener.class): 启用Spring Data JPA的审计功能</li>
* <li>@CreatedDate: 自动设置实体创建时间</li>
* <li>@LastModifiedDate: 自动更新实体最后修改时间</li>
* <li>@Comment: 为数据库字段添加注释</li>
* </ul>
* </p>
*
* @author lanyuanxiaoyao
*/
@Getter
@Setter
@ToString(callSuper = true)
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public class SimpleEntity extends IdOnlyEntity {
/**
* 记录创建时间
* <p>
* 由Spring Data JPA自动维护当实体首次持久化时设置该字段的值。
* </p>
*/
@CreatedDate
@Comment("记录创建时间")
private LocalDateTime createdTime;
/**
* 记录更新时间
* <p>
* 由Spring Data JPA自动维护当实体每次更新时刷新该字段的值。
* </p>
*/
@LastModifiedDate
@Comment("记录更新时间")
private LocalDateTime modifiedTime;
}
}

View File

@@ -6,6 +6,100 @@ import org.springframework.data.querydsl.QuerydslPredicateExecutor;
import org.springframework.data.repository.NoRepositoryBean;
import org.springframework.data.repository.query.QueryByExampleExecutor;
/**
* 简单仓库接口,整合多种数据访问功能
* <p>
* 该接口继承了多个Spring Data JPA和扩展功能接口为数据访问层提供丰富的查询和操作能力。
* 通过继承此接口可以快速实现常见的数据访问功能包括基本CRUD操作、复杂条件查询、
* 示例查询和QueryDSL查询等。
* </p>
*
* <p>
* 继承的接口功能说明:
* <ul>
* <li>
* FenixJpaRepository: 扩展的JPA仓库接口提供基本的CRUD操作、分页和排序功能。
* <p><b>常用方法示例:</b></p>
* <pre>
* // 保存实体
* User user = new User("张三", "zhangsan@example.com");
* User savedUser = repository.save(user);
*
* // 根据ID查找
* Optional<User> user = repository.findById(1L);
*
* // 查找所有
* List<User> users = repository.findAll();
*
* // 分页查询
* Pageable pageable = PageRequest.of(0, 10, Sort.by("createdTime").descending());
* Page<User> userPages = repository.findAll(pageable);
*
* // 根据ID删除
* repository.deleteById(1L);
* </pre>
* </li>
* <li>
* FenixJpaSpecificationExecutor: Fenix扩展的JPA规范执行器支持通过Specification构建复杂查询条件。
* <p><b>常用方法示例:</b></p>
* <pre>
* // 使用Specification构建复杂查询
* Specification<User> spec = (root, query, criteriaBuilder) -> {
* List<Predicate> predicates = new ArrayList<>();
* predicates.add(criteriaBuilder.equal(root.get("status"), "ACTIVE"));
* predicates.add(criteriaBuilder.like(root.get("name"), "%张三%"));
* return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
* };
* List<User> users = repository.findAll(spec);
*
* // 分页查询
* Page<User> userPages = repository.findAll(spec, pageable);
* </pre>
* </li>
* <li>
* QueryByExampleExecutor: 示例查询执行器,通过示例对象进行查询。
* <p><b>常用方法示例:</b></p>
* <pre>
* // 使用示例对象查询
* User exampleUser = new User();
* exampleUser.setName("张三");
* exampleUser.setStatus("ACTIVE");
* Example<User> example = Example.of(exampleUser);
* List<User> users = repository.findAll(example);
*
* // 使用匹配器增强查询
* ExampleMatcher matcher = ExampleMatcher.matching()
* .withMatcher("name", ExampleMatcher.GenericPropertyMatchers.startsWith())
* .withIgnorePaths("createdTime");
* Example<User> exampleWithMatcher = Example.of(exampleUser, matcher);
* List<User> matchedUsers = repository.findAll(exampleWithMatcher);
* </pre>
* </li>
* <li>
* QuerydslPredicateExecutor: QueryDSL查询执行器支持类型安全的查询构建。
* <p><b>常用方法示例:</b></p>
* <pre>
* // 使用QueryDSL构建类型安全查询
* QUser qUser = QUser.user;
* Iterable<User> users = repository.findAll(
* qUser.status.eq("ACTIVE")
* .and(qUser.name.like("%张三%"))
* );
*
* // 排序和分页
* Iterable<User> sortedUsers = repository.findAll(
* qUser.status.eq("ACTIVE"),
* qUser.createdTime.desc()
* );
* </pre>
* </li>
* </ul>
* </p>
*
* @param <E> 实体类型
* @param <ID> 实体ID类型
* @author lanyuanxiaoyao
*/
@NoRepositoryBean
public interface SimpleRepository<E, ID> extends FenixJpaRepository<E, ID>, FenixJpaSpecificationExecutor<E>, QueryByExampleExecutor<E>, QuerydslPredicateExecutor<E> {
}
}

View File

@@ -20,16 +20,95 @@ import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
/**
* 简单服务支持类,提供基础的业务逻辑实现
* <p>
* 该类实现了SimpleService接口提供实体的增删改查等基本操作。
* 通过继承此类,可以快速实现常见的业务逻辑功能,包括:
* <ul>
* <li>实体的保存和更新</li>
* <li>实体的条件查询和分页查询</li>
* <li>实体的详情查询(多种方式)</li>
* <li>实体的删除操作</li>
* </ul>
* </p>
*
* <p>
* 查询条件说明:
* <ul>
* <li>nullEqual: 指定字段值为null的条件列表</li>
* <li>notNullEqual: 指定字段值不为null的条件列表</li>
* <li>empty: 指定字段值为空的条件列表(如空字符串、空集合等)</li>
* <li>notEmpty: 指定字段值不为空的条件列表</li>
* <li>equal: 指定字段值相等的条件映射(字段名 -> 值)</li>
* <li>notEqual: 指定字段值不相等的条件映射(字段名 -> 值)</li>
* <li>like: 指定字段模糊匹配的条件映射(字段名 -> 匹配值)</li>
* <li>notLike: 指定字段不模糊匹配的条件映射(字段名 -> 匹配值)</li>
* <li>great: 指定字段大于条件的映射(字段名 -> 值)</li>
* <li>less: 指定字段小于条件的映射(字段名 -> 值)</li>
* <li>greatEqual: 指定字段大于等于条件的映射(字段名 -> 值)</li>
* <li>lessEqual: 指定字段小于等于条件的映射(字段名 -> 值)</li>
* <li>in: 指定字段值在指定范围内的条件映射(字段名 -> 值列表)</li>
* <li>notIn: 指定字段值不在指定范围内的条件映射(字段名 -> 值列表)</li>
* <li>between: 指定字段值在指定区间内的条件映射(字段名 -> 区间范围)</li>
* <li>notBetween: 指定字段值不在指定区间内的条件映射(字段名 -> 区间范围)</li>
* </ul>
* </p>
*
* <p>
* 前端传入的JSON格式示例:
* <pre>
* {
* "query": {
* "equal": {
* "status": "ACTIVE"
* },
* "like": {
* "name": "关键字"
* }
* },
* "sort": [
* {
* "column": "createdTime",
* "direction": "DESC"
* }
* ],
* "page": {
* "index": 1,
* "size": 10
* }
* }
* </pre>
* </p>
*
* @param <ENTITY> 实体类型必须继承SimpleEntity
* @author lanyuanxiaoyao
*/
@Slf4j
public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implements SimpleService<ENTITY> {
private static final Integer DEFAULT_PAGE_INDEX = 1;
private static final Integer DEFAULT_PAGE_SIZE = 10;
protected final SimpleRepository<ENTITY, Long> repository;
/**
* 构造函数
*
* @param repository 简单仓库实例
*/
public SimpleServiceSupport(SimpleRepository<ENTITY, Long> repository) {
this.repository = repository;
}
/**
* 保存实体对象
* <p>
* 使用saveOrUpdateByNotNullProperties方法保存实体只更新非空字段。
* 该方法具有事务性,遇到任何异常都会回滚。
* </p>
*
* @param entity 需要保存的实体对象
* @return Long 返回保存后的实体ID
*/
@Transactional(rollbackOn = Throwable.class)
@Override
public Long save(ENTITY entity) {
@@ -37,6 +116,14 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
return entity.getId();
}
/**
* 统计符合条件的实体数量
* <p>
* 根据[listPredicate](file:///Users/lanyuanxiaoyao/Project/IdeaProjects/spring-boot-service-template/src/main/java/com/lanyuanxiaoyao/service/template/service/SimpleServiceSupport.java#L261-L263)方法构建的条件统计实体数量。
* </p>
*
* @return Long 返回符合条件的实体数量
*/
@Override
public Long count() {
return repository.count(
@@ -50,6 +137,14 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
);
}
/**
* 获取所有符合条件的实体列表
* <p>
* 根据[listPredicate](file:///Users/lanyuanxiaoyao/Project/IdeaProjects/spring-boot-service-template/src/main/java/com/lanyuanxiaoyao/service/template/service/SimpleServiceSupport.java#L261-L263)方法构建的条件查询所有实体。
* </p>
*
* @return List<ENTITY> 返回符合条件的实体列表
*/
@Override
public List<ENTITY> list() {
return repository.findAll(
@@ -63,6 +158,15 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
);
}
/**
* 根据ID集合获取实体列表
* <p>
* 根据提供的ID集合查询对应的实体列表并结合[listPredicate](file:///Users/lanyuanxiaoyao/Project/IdeaProjects/spring-boot-service-template/src/main/java/com/lanyuanxiaoyao/service/template/service/SimpleServiceSupport.java#L261-L263)方法构建的条件。
* </p>
*
* @param ids ID集合
* @return List<ENTITY> 返回ID集合对应的实体列表
*/
@Override
public List<ENTITY> list(Set<Long> ids) {
return repository.findAll(
@@ -74,6 +178,19 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
);
}
/**
* 解析字段路径
* <p>
* 支持多级字段路径解析,使用"/"分隔多级字段。
* 例如: "user/name" 表示实体的user属性的name字段。
* </p>
*
* @param root JPA Criteria查询根节点
* @param column 字段路径字符串
* @param <Y> 字段类型
* @return Path<Y> 返回字段路径对象
* @throws IllegalArgumentException 当字段路径为空时抛出
*/
private <Y> Path<Y> column(Root<ENTITY> root, String column) {
if (ObjectHelper.isEmpty(column)) {
throw new IllegalArgumentException("Column cannot be blank");
@@ -86,6 +203,19 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
return path;
}
/**
* 处理字段值
* <p>
* 对于枚举类型字段,将字符串值转换为对应的枚举值。
* 其他类型直接返回原值。
* </p>
*
* @param column 字段路径
* @param value 字段值
* @param <Y> 字段类型
* @return Object 处理后的字段值
* @throws IllegalArgumentException 当枚举类型字段的值不是字符串时抛出
*/
@SuppressWarnings({"unchecked", "rawtypes"})
private <Y> Object value(Path<Y> column, Object value) {
var javaType = column.getJavaType();
@@ -100,6 +230,19 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
return value;
}
/**
* 构建查询条件谓词列表
* <p>
* 根据Query.Queryable对象构建JPA Criteria查询的谓词列表。
* 支持多种查询条件类型,包括相等、不等、模糊匹配、范围查询等。
* </p>
*
* @param queryable 查询条件对象
* @param root JPA Criteria查询根节点
* @param query JPA Criteria查询对象
* @param builder JPA Criteria构建器
* @return List<Predicate> 返回构建的谓词列表
*/
@SuppressWarnings("unchecked")
protected List<Predicate> queryPredicates(Query.Queryable queryable, Root<ENTITY> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
var predicates = new ArrayList<Predicate>();
@@ -211,51 +354,122 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
return predicates;
}
/**
* 检查字段类型是否可比较
*
* @param path 字段路径
* @param column 字段名称
* @throws NotComparableException 当字段类型不可比较时抛出
*/
private void checkComparable(Path<?> path, String column) {
if (!ObjectHelper.isComparable(path.getJavaType())) {
throw new NotComparableException(column);
}
}
/**
* 检查值是否可比较
*
* @param value 值对象
* @param column 字段名称
* @throws NotComparableException 当值不可比较时抛出
*/
private void checkComparable(Object value, String column) {
if (!ObjectHelper.isComparable(value)) {
throw new NotComparableException(column);
}
}
/**
* 检查区间值是否可比较
*
* @param value 区间对象
* @param column 字段名称
* @throws NotComparableException 当区间值不可比较时抛出
*/
private void checkComparable(Query.Queryable.Between value, String column) {
checkComparable(value.getStart(), column);
checkComparable(value.getEnd(), column);
}
/**
* 检查字段类型是否为集合
*
* @param path 字段路径
* @param column 字段名称
* @throws NotCollectionException 当字段类型不是集合时抛出
*/
private void checkCollection(Path<?> path, String column) {
if (!ObjectHelper.isCollection(path.getJavaType())) {
throw new NotCollectionException(column);
}
}
/**
* 检查值是否为集合
*
* @param value 值对象
* @param column 字段名称
* @throws NotCollectionException 当值不是集合时抛出
*/
private void checkCollection(Object value, String column) {
if (!ObjectHelper.isCollection(value)) {
throw new NotCollectionException(column);
}
}
/**
* 检查字段类型是否为字符串
*
* @param path 字段路径
* @param column 字段名称
* @throws NotStringException 当字段类型不是字符串时抛出
*/
private void checkString(Path<?> path, String column) {
if (!ObjectHelper.isString(path.getJavaType())) {
throw new NotStringException(column);
}
}
/**
* 检查值是否为字符串
*
* @param value 值对象
* @param column 字段名称
* @throws NotStringException 当值不是字符串时抛出
*/
private void checkString(Object value, String column) {
if (!ObjectHelper.isString(value)) {
throw new NotStringException(column);
}
}
/**
* 构建列表查询条件
* <p>
* 子类可以重写此方法以添加特定的查询条件。
* 默认返回空列表,表示不添加额外条件。
* </p>
*
* @param root JPA Criteria查询根节点
* @param query JPA Criteria查询对象
* @param builder JPA Criteria构建器
* @return List<Predicate> 返回查询条件谓词列表
*/
protected List<Predicate> listPredicate(Root<ENTITY> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
return new ArrayList<>(0);
}
/**
* 根据查询条件分页获取实体列表
* <p>
* 支持复杂的查询条件和分页功能。
* 默认分页参数第1页每页10条记录按创建时间降序排列。
* </p>
*
* @param listQuery 查询条件对象
* @return Page<ENTITY> 返回分页查询结果
*/
@Override
public Page<ENTITY> list(Query listQuery) {
var pageRequest = PageRequest.of(DEFAULT_PAGE_INDEX - 1, DEFAULT_PAGE_SIZE, Sort.by("createdTime").descending());
@@ -276,6 +490,15 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
);
}
/**
* 根据ID获取实体详情Optional包装
* <p>
* 如果ID为空则返回空Optional否则根据ID查询实体。
* </p>
*
* @param id 实体ID
* @return Optional<ENTITY> 返回实体详情的Optional包装
*/
@Override
public Optional<ENTITY> detailOptional(Long id) {
if (ObjectHelper.isNull(id)) {
@@ -290,21 +513,58 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
);
}
/**
* 根据ID获取实体详情
* <p>
* 如果实体不存在则返回null。
* </p>
*
* @param id 实体ID
* @return ENTITY 返回实体详情不存在时返回null
*/
@Override
public ENTITY detail(Long id) {
return detailOrNull(id);
}
/**
* 根据ID获取实体详情不存在时抛出异常
* <p>
* 如果实体不存在则抛出IdNotFoundException异常。
* </p>
*
* @param id 实体ID
* @return ENTITY 返回实体详情
* @throws IdNotFoundException 当实体不存在时抛出
*/
@Override
public ENTITY detailOrThrow(Long id) {
return detailOptional(id).orElseThrow(() -> new IdNotFoundException(id));
}
/**
* 根据ID获取实体详情不存在时返回null
* <p>
* 与[detail](file:///Users/lanyuanxiaoyao/Project/IdeaProjects/spring-boot-service-template/src/main/java/com/lanyuanxiaoyao/service/template/service/SimpleService.java#L21-L21)方法功能相同。
* </p>
*
* @param id 实体ID
* @return ENTITY 返回实体详情不存在时返回null
*/
@Override
public ENTITY detailOrNull(Long id) {
return detailOptional(id).orElse(null);
}
/**
* 根据ID删除实体
* <p>
* 具有事务性,遇到任何异常都会回滚。
* 如果ID为空则不执行任何操作。
* </p>
*
* @param id 实体ID
*/
@Transactional(rollbackOn = Throwable.class)
@Override
public void remove(Long id) {
@@ -313,31 +573,78 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
}
}
/**
* ID未找到异常
* <p>
* 当根据ID查询实体但实体不存在时抛出此异常。
* </p>
*/
public static final class IdNotFoundException extends RuntimeException {
/**
* 构造函数(无参)
*/
public IdNotFoundException() {
super("资源不存在");
}
/**
* 构造函数带ID参数
*
* @param id 实体ID
*/
public IdNotFoundException(Long id) {
super("ID为 %d 的资源不存在".formatted(id));
}
}
/**
* 不可比较异常
* <p>
* 当尝试对不可比较的字段或值执行比较操作时抛出此异常。
* </p>
*/
public static final class NotComparableException extends RuntimeException {
/**
* 构造函数
*
* @param variable 变量名称
*/
public NotComparableException(String variable) {
super("变量 %s 不能比较".formatted(variable));
}
}
/**
* 非集合异常
* <p>
* 当尝试对非集合类型的字段或值执行集合操作时抛出此异常。
* </p>
*/
public static final class NotCollectionException extends RuntimeException {
/**
* 构造函数
*
* @param variable 变量名称
*/
public NotCollectionException(String variable) {
super("变量 %s 不是集合".formatted(variable));
}
}
/**
* 非字符串异常
* <p>
* 当尝试对非字符串类型的字段或值执行字符串操作时抛出此异常。
* </p>
*/
public static final class NotStringException extends RuntimeException {
/**
* 构造函数
*
* @param variable 变量名称
*/
public NotStringException(String variable) {
super("变量 %s 不是字符串".formatted(variable));
}
}
}
}