1
0

Compare commits

...

10 Commits

30 changed files with 1643 additions and 1441 deletions

View File

@@ -96,6 +96,11 @@
<artifactId>lombok</artifactId> <artifactId>lombok</artifactId>
<version>1.18.36</version> <version>1.18.36</version>
</path> </path>
<path>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-jpamodelgen</artifactId>
<version>6.6.8.Final</version>
</path>
<path> <path>
<groupId>io.github.openfeign.querydsl</groupId> <groupId>io.github.openfeign.querydsl</groupId>
<artifactId>querydsl-apt</artifactId> <artifactId>querydsl-apt</artifactId>

View File

@@ -1,7 +1,5 @@
package com.lanyuanxiaoyao.service.template.controller; package com.lanyuanxiaoyao.service.template.controller;
import com.lanyuanxiaoyao.service.template.controller.response.GlobalResponse;
/** /**
* 详情控制器接口,用于定义统一的获取实体详情的接口规范 * 详情控制器接口,用于定义统一的获取实体详情的接口规范
* *

View File

@@ -0,0 +1,77 @@
package com.lanyuanxiaoyao.service.template.controller;
import java.util.Map;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Setter
@Getter
@ToString
public class GlobalResponse<T> {
private static final int SUCCESS_STATUS = 0;
private static final int ERROR_STATUS = 500;
private static final String SUCCESS_MESSAGE = "OK";
private static final String ERROR_MESSAGE = "ERROR";
private Integer status;
private String message;
private T data;
private GlobalResponse() {
this(SUCCESS_STATUS, SUCCESS_MESSAGE, null);
}
private GlobalResponse(Integer status, String message) {
this(status, message, null);
}
private GlobalResponse(Integer status, String message, T data) {
this.status = status;
this.message = message;
this.data = data;
}
public static GlobalResponse<Object> responseError() {
return responseError(ERROR_MESSAGE);
}
public static GlobalResponse<Object> responseError(String message) {
return new GlobalResponse<>(ERROR_STATUS, message);
}
public static GlobalResponse<Object> responseSuccess() {
return responseSuccess(SUCCESS_MESSAGE);
}
public static GlobalResponse<Object> responseSuccess(String message) {
return responseSuccess(message, null);
}
public static <E> GlobalResponse<E> responseSuccess(E data) {
return responseSuccess(SUCCESS_MESSAGE, data);
}
public static <E> GlobalResponse<E> responseSuccess(String message, E data) {
return new GlobalResponse<>(SUCCESS_STATUS, message, data);
}
public static GlobalResponse<Map<String, Object>> responseMapData(Map<String, Object> data) {
return responseSuccess(data);
}
public static GlobalResponse<Map<String, Object>> responseMapData(String key, Object value) {
return responseMapData(Map.of(key, value));
}
public static <T> GlobalResponse<Map<String, Object>> responseCrudData(Iterable<T> data, Integer total) {
return responseCrudData(data, total.longValue());
}
public static <T> GlobalResponse<Map<String, Object>> responseCrudData(Iterable<T> data, Long total) {
return responseMapData(Map.of("items", data, "total", total));
}
public static GlobalResponse<Map<String, Object>> responseDetailData(Object detail) {
return responseMapData("detail", detail);
}
}

View File

@@ -1,6 +1,6 @@
package com.lanyuanxiaoyao.service.template.controller; package com.lanyuanxiaoyao.service.template.controller;
import com.lanyuanxiaoyao.service.template.controller.response.GlobalCrudResponse; import java.util.Map;
/** /**
* 列表控制器接口,用于定义统一的获取实体列表的接口规范 * 列表控制器接口,用于定义统一的获取实体列表的接口规范
@@ -65,7 +65,7 @@ public interface ListController<LIST_ITEM> {
* @return GlobalCrudResponse<LIST_ITEM> 返回实体列表 * @return GlobalCrudResponse<LIST_ITEM> 返回实体列表
* @throws Exception 查询过程中可能抛出的异常 * @throws Exception 查询过程中可能抛出的异常
*/ */
GlobalCrudResponse<LIST_ITEM> list() throws Exception; GlobalResponse<Map<String, Object>> list() throws Exception;
/** /**
* 根据查询条件获取实体列表 * 根据查询条件获取实体列表
@@ -74,5 +74,5 @@ public interface ListController<LIST_ITEM> {
* @return GlobalCrudResponse<LIST_ITEM> 返回符合条件的实体列表 * @return GlobalCrudResponse<LIST_ITEM> 返回符合条件的实体列表
* @throws Exception 查询过程中可能抛出的异常 * @throws Exception 查询过程中可能抛出的异常
*/ */
GlobalCrudResponse<LIST_ITEM> list(Query query) throws Exception; GlobalResponse<Map<String, Object>> list(Query query) throws Exception;
} }

View File

@@ -122,6 +122,30 @@ public class Query {
* 指定字段不模糊匹配的条件映射(字段名 -> 匹配值) * 指定字段不模糊匹配的条件映射(字段名 -> 匹配值)
*/ */
private Map<String, String> notLike; private Map<String, String> notLike;
/**
* 指定字段包含指定字符串的条件映射(字段名 -> 包含值)
*/
private Map<String, String> contain;
/**
* 指定字段不包含指定字符串的条件映射(字段名 -> 不包含值)
*/
private Map<String, String> notContain;
/**
* 指定字段以指定字符串开头的条件映射(字段名 -> 开头值)
*/
private Map<String, String> startWith;
/**
* 指定字段不以指定字符串开头的条件映射(字段名 -> 不开头值)
*/
private Map<String, String> notStartWith;
/**
* 指定字段以指定字符串结尾的条件映射(字段名 -> 结尾值)
*/
private Map<String, String> endWith;
/**
* 指定字段不以指定字符串结尾的条件映射(字段名 -> 不结尾值)
*/
private Map<String, String> notEndWith;
/** /**
* 指定字段大于条件的映射(字段名 -> 值) * 指定字段大于条件的映射(字段名 -> 值)
*/ */
@@ -141,11 +165,11 @@ public class Query {
/** /**
* 指定字段值在指定范围内的条件映射(字段名 -> 值列表) * 指定字段值在指定范围内的条件映射(字段名 -> 值列表)
*/ */
private Map<String, List<Object>> in; private Map<String, List<Object>> inside;
/** /**
* 指定字段值不在指定范围内的条件映射(字段名 -> 值列表) * 指定字段值不在指定范围内的条件映射(字段名 -> 值列表)
*/ */
private Map<String, List<Object>> notIn; private Map<String, List<Object>> notInside;
/** /**
* 指定字段值在指定区间内的条件映射(字段名 -> 区间范围) * 指定字段值在指定区间内的条件映射(字段名 -> 区间范围)
*/ */

View File

@@ -1,7 +1,5 @@
package com.lanyuanxiaoyao.service.template.controller; package com.lanyuanxiaoyao.service.template.controller;
import com.lanyuanxiaoyao.service.template.controller.response.GlobalResponse;
/** /**
* 删除控制器接口,用于定义统一的删除实体对象的接口规范 * 删除控制器接口,用于定义统一的删除实体对象的接口规范
* *

View File

@@ -1,7 +1,5 @@
package com.lanyuanxiaoyao.service.template.controller; package com.lanyuanxiaoyao.service.template.controller;
import com.lanyuanxiaoyao.service.template.controller.response.GlobalResponse;
/** /**
* 保存控制器接口,用于定义统一的保存实体对象的接口规范 * 保存控制器接口,用于定义统一的保存实体对象的接口规范
* *

View File

@@ -1,11 +1,10 @@
package com.lanyuanxiaoyao.service.template.controller; package com.lanyuanxiaoyao.service.template.controller;
import com.lanyuanxiaoyao.service.template.controller.response.GlobalCrudResponse;
import com.lanyuanxiaoyao.service.template.controller.response.GlobalResponse;
import com.lanyuanxiaoyao.service.template.entity.SimpleEntity; import com.lanyuanxiaoyao.service.template.entity.SimpleEntity;
import com.lanyuanxiaoyao.service.template.helper.ObjectHelper; import com.lanyuanxiaoyao.service.template.helper.ObjectHelper;
import com.lanyuanxiaoyao.service.template.service.SimpleServiceSupport; import com.lanyuanxiaoyao.service.template.service.SimpleServiceSupport;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.function.Function; import java.util.function.Function;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
@@ -128,10 +127,10 @@ public abstract class SimpleControllerSupport<ENTITY extends SimpleEntity, SAVE_
*/ */
@GetMapping(LIST) @GetMapping(LIST)
@Override @Override
public GlobalCrudResponse<LIST_ITEM> list() throws Exception { public GlobalResponse<Map<String, Object>> list() throws Exception {
var mapper = listItemMapper(); var mapper = listItemMapper();
var result = service.list(); var result = service.list();
return GlobalCrudResponse.responseCrudData( return GlobalResponse.responseCrudData(
result result
.stream() .stream()
.map(entity -> { .map(entity -> {
@@ -155,13 +154,13 @@ public abstract class SimpleControllerSupport<ENTITY extends SimpleEntity, SAVE_
*/ */
@PostMapping(LIST) @PostMapping(LIST)
@Override @Override
public GlobalCrudResponse<LIST_ITEM> list(@RequestBody Query query) throws Exception { public GlobalResponse<Map<String, Object>> list(@RequestBody Query query) throws Exception {
if (ObjectHelper.isNull(query)) { if (ObjectHelper.isNull(query)) {
return GlobalCrudResponse.responseCrudData(List.of(), 0); return GlobalResponse.responseCrudData(List.of(), 0);
} }
var mapper = listItemMapper(); var mapper = listItemMapper();
var result = service.list(query); var result = service.list(query);
return GlobalCrudResponse.responseCrudData( return GlobalResponse.responseCrudData(
result.get() result.get()
.map(entity -> { .map(entity -> {
try { try {

View File

@@ -1,24 +0,0 @@
package com.lanyuanxiaoyao.service.template.controller.response;
public class GlobalCrudResponse<T> extends GlobalMapResponse {
public void setData(Iterable<T> list) {
setData("items", list);
}
public void setTotal(Long total) {
setData("total", total);
}
public void setTotal(Integer total) {
setTotal(total.longValue());
}
public void setData(Iterable<T> list, Long total) {
setData(list);
setTotal(total);
}
public void setData(Iterable<T> list, Integer total) {
setData(list, total.longValue());
}
}

View File

@@ -1,7 +0,0 @@
package com.lanyuanxiaoyao.service.template.controller.response;
public class GlobalDetailResponse extends GlobalMapResponse {
public void setDetail(Object detail) {
setData("detail", detail);
}
}

View File

@@ -1,15 +0,0 @@
package com.lanyuanxiaoyao.service.template.controller.response;
import java.util.HashMap;
import java.util.Map;
public class GlobalMapResponse extends GlobalResponse<Map<String, Object>> {
public GlobalMapResponse() {
setData(new HashMap<>());
}
public GlobalMapResponse setData(String key, Object value) {
getData().put(key, value);
return this;
}
}

View File

@@ -1,105 +0,0 @@
package com.lanyuanxiaoyao.service.template.controller.response;
import java.util.Map;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Setter
@Getter
@ToString
public class GlobalResponse<T> {
private static final int SUCCESS_STATUS = 0;
private static final int ERROR_STATUS = 500;
private static final String SUCCESS_MESSAGE = "OK";
private static final String ERROR_MESSAGE = "ERROR";
private Integer status;
private String message;
private T data;
public static GlobalResponse<Object> responseError() {
return responseError(ERROR_MESSAGE);
}
public static GlobalResponse<Object> responseError(String message) {
GlobalResponse<Object> response = new GlobalResponse<>();
response.setStatus(ERROR_STATUS);
response.setMessage(message);
return response;
}
public static GlobalResponse<Object> responseSuccess() {
GlobalResponse<Object> response = new GlobalResponse<>();
response.setStatus(SUCCESS_STATUS);
response.setMessage(SUCCESS_MESSAGE);
return response;
}
public static GlobalResponse<Object> responseSuccess(String message) {
GlobalResponse<Object> response = new GlobalResponse<>();
response.setStatus(SUCCESS_STATUS);
response.setMessage(message);
return response;
}
public static <E> GlobalResponse<E> responseSuccess(String message, E data) {
GlobalResponse<E> response = new GlobalResponse<>();
response.setStatus(SUCCESS_STATUS);
response.setMessage(message);
response.setData(data);
return response;
}
public static <E> GlobalResponse<E> responseSuccess(E data) {
GlobalResponse<E> response = new GlobalResponse<>();
response.setStatus(SUCCESS_STATUS);
response.setMessage(SUCCESS_MESSAGE);
response.setData(data);
return response;
}
public static GlobalMapResponse responseMapData() {
GlobalMapResponse response = new GlobalMapResponse();
response.setStatus(SUCCESS_STATUS);
response.setMessage(SUCCESS_MESSAGE);
return response;
}
public static GlobalMapResponse responseMapData(Map<String, Object> data) {
GlobalMapResponse response = responseMapData();
response.setData(data);
return response;
}
public static GlobalMapResponse responseMapData(String key, Object value) {
GlobalMapResponse response = responseMapData();
response.setData(key, value);
return response;
}
public static <T> GlobalCrudResponse<T> responseCrudData(Iterable<T> data) {
GlobalCrudResponse<T> response = new GlobalCrudResponse<>();
response.setStatus(SUCCESS_STATUS);
response.setMessage(SUCCESS_MESSAGE);
response.setData(data);
return response;
}
public static <T> GlobalCrudResponse<T> responseCrudData(Iterable<T> data, Integer total) {
GlobalCrudResponse<T> response = responseCrudData(data);
response.setTotal(total);
return response;
}
public static <T> GlobalCrudResponse<T> responseCrudData(Iterable<T> data, Long total) {
GlobalCrudResponse<T> response = responseCrudData(data);
response.setTotal(total);
return response;
}
public static GlobalDetailResponse responseDetailData(Object detail) {
GlobalDetailResponse response = new GlobalDetailResponse();
response.setDetail(detail);
return response;
}
}

View File

@@ -3,7 +3,6 @@ package com.lanyuanxiaoyao.service.template.service;
import com.lanyuanxiaoyao.service.template.controller.Query; import com.lanyuanxiaoyao.service.template.controller.Query;
import com.lanyuanxiaoyao.service.template.entity.SimpleEntity; import com.lanyuanxiaoyao.service.template.entity.SimpleEntity;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
@@ -18,13 +17,9 @@ public interface SimpleService<ENTITY extends SimpleEntity> {
Page<ENTITY> list(Query query) throws Exception; Page<ENTITY> list(Query query) throws Exception;
Optional<ENTITY> detailOptional(Long id) throws Exception;
ENTITY detail(Long id) throws Exception; ENTITY detail(Long id) throws Exception;
ENTITY detailOrThrow(Long id) throws Exception; ENTITY detailOrThrow(Long id) throws Exception;
ENTITY detailOrNull(Long id) throws Exception;
void remove(Long id) throws Exception; void remove(Long id) throws Exception;
} }

View File

@@ -11,6 +11,8 @@ import jakarta.persistence.criteria.Path;
import jakarta.persistence.criteria.Predicate; import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Root; import jakarta.persistence.criteria.Root;
import jakarta.transaction.Transactional; import jakarta.transaction.Transactional;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
@@ -87,6 +89,7 @@ import org.springframework.data.domain.Sort;
*/ */
@Slf4j @Slf4j
public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implements SimpleService<ENTITY> { public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implements SimpleService<ENTITY> {
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private static final Integer DEFAULT_PAGE_INDEX = 1; private static final Integer DEFAULT_PAGE_INDEX = 1;
private static final Integer DEFAULT_PAGE_SIZE = 10; private static final Integer DEFAULT_PAGE_SIZE = 10;
protected final SimpleRepository<ENTITY, Long> repository; protected final SimpleRepository<ENTITY, Long> repository;
@@ -182,7 +185,7 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
if (ObjectHelper.isEmpty(column)) { if (ObjectHelper.isEmpty(column)) {
throw new IllegalArgumentException("Column cannot be blank"); throw new IllegalArgumentException("Column cannot be blank");
} }
var columns = column.split("/"); var columns = column.split("\\.");
Path<Y> path = root.get(columns[0]); Path<Y> path = root.get(columns[0]);
for (int i = 1; i < columns.length; i++) { for (int i = 1; i < columns.length; i++) {
path = path.get(columns[i]); path = path.get(columns[i]);
@@ -213,6 +216,8 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
} else { } else {
throw new IllegalArgumentException("枚举类型字段需要 String 类型的值"); throw new IllegalArgumentException("枚举类型字段需要 String 类型的值");
} }
} else if (javaType.isAssignableFrom(LocalDateTime.class)) {
return LocalDateTime.parse(String.valueOf(value), DATE_TIME_FORMATTER);
} }
return value; return value;
} }
@@ -271,71 +276,105 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
if (ObjectHelper.isNotEmpty(queryable.getLike())) { if (ObjectHelper.isNotEmpty(queryable.getLike())) {
queryable.getLike().forEach((column, value) -> { queryable.getLike().forEach((column, value) -> {
var path = column(root, column); var path = column(root, column);
checkString(path, column); checkString(path, value, column);
checkString(value, column);
predicates.add(builder.like(column(root, column), value)); predicates.add(builder.like(column(root, column), value));
}); });
} }
if (ObjectHelper.isNotEmpty(queryable.getNotLike())) { if (ObjectHelper.isNotEmpty(queryable.getNotLike())) {
queryable.getNotLike().forEach((column, value) -> { queryable.getNotLike().forEach((column, value) -> {
var path = column(root, column); var path = column(root, column);
checkString(path, column); checkString(path, value, column);
checkString(value, column);
predicates.add(builder.notLike(column(root, column), value)); predicates.add(builder.notLike(column(root, column), value));
}); });
} }
if (ObjectHelper.isNotEmpty(queryable.getContain())) {
queryable.getContain().forEach((column, value) -> {
var path = this.<String>column(root, column);
checkString(path, value, column);
predicates.add(builder.like(column(root, column), "%" + value + "%"));
});
}
if (ObjectHelper.isNotEmpty(queryable.getNotContain())) {
queryable.getNotContain().forEach((column, value) -> {
var path = this.<String>column(root, column);
checkString(path, value, column);
predicates.add(builder.notLike(column(root, column), "%" + value + "%"));
});
}
if (ObjectHelper.isNotEmpty(queryable.getStartWith())) {
queryable.getStartWith().forEach((column, value) -> {
var path = this.<String>column(root, column);
checkString(path, value, column);
predicates.add(builder.like(column(root, column), value + "%"));
});
}
if (ObjectHelper.isNotEmpty(queryable.getNotStartWith())) {
queryable.getNotStartWith().forEach((column, value) -> {
var path = this.<String>column(root, column);
checkString(path, value, column);
predicates.add(builder.notLike(column(root, column), value + "%"));
});
}
if (ObjectHelper.isNotEmpty(queryable.getEndWith())) {
queryable.getEndWith().forEach((column, value) -> {
var path = this.<String>column(root, column);
checkString(path, value, column);
predicates.add(builder.like(column(root, column), "%" + value));
});
}
if (ObjectHelper.isNotEmpty(queryable.getNotEndWith())) {
queryable.getNotEndWith().forEach((column, value) -> {
var path = this.<String>column(root, column);
checkString(path, value, column);
predicates.add(builder.notLike(column(root, column), "%" + value));
});
}
if (ObjectHelper.isNotEmpty(queryable.getGreat())) { if (ObjectHelper.isNotEmpty(queryable.getGreat())) {
queryable.getGreat().forEach((column, value) -> { queryable.getGreat().forEach((column, value) -> {
var path = this.<Comparable<Object>>column(root, column); var path = this.<Comparable<Object>>column(root, column);
checkComparable(path, column); checkComparable(path, value, column);
checkComparable(value, column); predicates.add(builder.greaterThan(path, (Comparable<Object>) value(path, value)));
predicates.add(builder.greaterThan(path, (Comparable<Object>) value));
}); });
} }
if (ObjectHelper.isNotEmpty(queryable.getLess())) { if (ObjectHelper.isNotEmpty(queryable.getLess())) {
queryable.getLess().forEach((column, value) -> { queryable.getLess().forEach((column, value) -> {
var path = this.<Comparable<Object>>column(root, column); var path = this.<Comparable<Object>>column(root, column);
checkComparable(path, column); checkComparable(path, value, column);
checkComparable(value, column); predicates.add(builder.lessThan(path, (Comparable<Object>) value(path, value)));
predicates.add(builder.lessThan(path, (Comparable<Object>) value));
}); });
} }
if (ObjectHelper.isNotEmpty(queryable.getGreatEqual())) { if (ObjectHelper.isNotEmpty(queryable.getGreatEqual())) {
queryable.getGreatEqual().forEach((column, value) -> { queryable.getGreatEqual().forEach((column, value) -> {
var path = this.<Comparable<Object>>column(root, column); var path = this.<Comparable<Object>>column(root, column);
checkComparable(path, column); checkComparable(path, value, column);
checkComparable(value, column); predicates.add(builder.greaterThanOrEqualTo(path, (Comparable<Object>) value(path, value)));
predicates.add(builder.greaterThanOrEqualTo(path, (Comparable<Object>) value));
}); });
} }
if (ObjectHelper.isNotEmpty(queryable.getLessEqual())) { if (ObjectHelper.isNotEmpty(queryable.getLessEqual())) {
queryable.getLessEqual().forEach((column, value) -> { queryable.getLessEqual().forEach((column, value) -> {
var path = this.<Comparable<Object>>column(root, column); var path = this.<Comparable<Object>>column(root, column);
checkComparable(path, column); checkComparable(path, value, column);
checkComparable(value, column); predicates.add(builder.lessThanOrEqualTo(path, (Comparable<Object>) value(path, value)));
predicates.add(builder.lessThanOrEqualTo(path, (Comparable<Object>) value));
}); });
} }
if (ObjectHelper.isNotEmpty(queryable.getIn())) { if (ObjectHelper.isNotEmpty(queryable.getInside())) {
queryable.getIn().forEach((column, value) -> predicates.add(builder.in(column(root, column)).value(value))); queryable.getInside().forEach((column, value) -> predicates.add(builder.in(column(root, column)).value(value)));
} }
if (ObjectHelper.isNotEmpty(queryable.getNotIn())) { if (ObjectHelper.isNotEmpty(queryable.getNotInside())) {
queryable.getNotIn().forEach((column, value) -> predicates.add(builder.in(column(root, column)).value(value).not())); queryable.getNotInside().forEach((column, value) -> predicates.add(builder.in(column(root, column)).value(value).not()));
} }
if (ObjectHelper.isNotEmpty(queryable.getBetween())) { if (ObjectHelper.isNotEmpty(queryable.getBetween())) {
queryable.getBetween().forEach((column, value) -> { queryable.getBetween().forEach((column, value) -> {
var path = this.<Comparable<Object>>column(root, column); var path = this.<Comparable<Object>>column(root, column);
checkComparable(path, column); checkComparable(path, value, column);
checkComparable(value, column); predicates.add(builder.between(column(root, column), (Comparable<Object>) value(path, value.getStart()), (Comparable<Object>) value(path, value.getEnd())));
predicates.add(builder.between(column(root, column), (Comparable<Object>) value.getStart(), (Comparable<Object>) value.getEnd()));
}); });
} }
if (ObjectHelper.isNotEmpty(queryable.getNotBetween())) { if (ObjectHelper.isNotEmpty(queryable.getNotBetween())) {
queryable.getNotBetween().forEach((column, value) -> { queryable.getNotBetween().forEach((column, value) -> {
var path = this.<Comparable<Object>>column(root, column); var path = this.<Comparable<Object>>column(root, column);
checkComparable(path, column); checkComparable(path, value, column);
checkComparable(value, column); predicates.add(builder.between(column(root, column), (Comparable<Object>) value(path, value.getStart()), (Comparable<Object>) value(path, value.getEnd())).not());
predicates.add(builder.between(column(root, column), (Comparable<Object>) value.getStart(), (Comparable<Object>) value.getEnd()).not());
}); });
} }
@@ -351,21 +390,8 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
* @param column 字段名称 * @param column 字段名称
* @throws NotComparableException 当字段类型不可比较时抛出 * @throws NotComparableException 当字段类型不可比较时抛出
*/ */
private void checkComparable(Path<?> path, String column) { private void checkComparable(Path<?> path, Object value, String column) {
if (!ObjectHelper.isComparable(path.getJavaType())) { if (!ObjectHelper.isComparable(path.getJavaType()) || !ObjectHelper.isComparable(value)) {
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); throw new NotComparableException(column);
} }
} }
@@ -377,9 +403,9 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
* @param column 字段名称 * @param column 字段名称
* @throws NotComparableException 当区间值不可比较时抛出 * @throws NotComparableException 当区间值不可比较时抛出
*/ */
private void checkComparable(Query.Queryable.Between value, String column) { private void checkComparable(Path<?> path, Query.Queryable.Between value, String column) {
checkComparable(value.getStart(), column); checkComparable(path, value.getStart(), column);
checkComparable(value.getEnd(), column); checkComparable(path, value.getEnd(), column);
} }
/** /**
@@ -415,21 +441,8 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
* @param column 字段名称 * @param column 字段名称
* @throws NotStringException 当字段类型不是字符串时抛出 * @throws NotStringException 当字段类型不是字符串时抛出
*/ */
private void checkString(Path<?> path, String column) { private void checkString(Path<?> path, Object value, String column) {
if (!ObjectHelper.isString(path.getJavaType())) { if (!ObjectHelper.isString(path.getJavaType()) || !ObjectHelper.isString(value)) {
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); throw new NotStringException(column);
} }
} }
@@ -491,8 +504,7 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
* @param id 实体ID * @param id 实体ID
* @return Optional<ENTITY> 返回实体详情的Optional包装 * @return Optional<ENTITY> 返回实体详情的Optional包装
*/ */
@Override private Optional<ENTITY> detailOptional(Long id) {
public Optional<ENTITY> detailOptional(Long id) {
if (ObjectHelper.isNull(id)) { if (ObjectHelper.isNull(id)) {
return Optional.empty(); return Optional.empty();
} }
@@ -518,7 +530,7 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
*/ */
@Override @Override
public ENTITY detail(Long id) { public ENTITY detail(Long id) {
return detailOrNull(id); return detailOptional(id).orElse(null);
} }
/** /**
@@ -536,20 +548,6 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
return detailOptional(id).orElseThrow(() -> new IdNotFoundException(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删除实体 * 根据ID删除实体
* <p> * <p>
@@ -574,13 +572,6 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
* </p> * </p>
*/ */
public static final class IdNotFoundException extends RuntimeException { public static final class IdNotFoundException extends RuntimeException {
/**
* 构造函数(无参)
*/
public IdNotFoundException() {
super("资源不存在");
}
/** /**
* 构造函数带ID参数 * 构造函数带ID参数
* *

View File

@@ -4,6 +4,15 @@ import com.blinkfox.fenix.EnableFenix;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.lanyuanxiaoyao.service.template.entity.Company_;
import com.lanyuanxiaoyao.service.template.entity.Employee;
import com.lanyuanxiaoyao.service.template.entity.Employee_;
import com.lanyuanxiaoyao.service.template.entity.QEmployee;
import com.lanyuanxiaoyao.service.template.entity.Report;
import com.lanyuanxiaoyao.service.template.entity.Report_;
import com.lanyuanxiaoyao.service.template.repository.EmployeeRepository;
import jakarta.annotation.Resource;
import java.util.List;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
@@ -29,6 +38,8 @@ public class TestApplication {
private static final String BASE_URL = "http://localhost:2490"; private static final String BASE_URL = "http://localhost:2490";
private static final RestTemplate REST_CLIENT = new RestTemplate(); private static final RestTemplate REST_CLIENT = new RestTemplate();
private static final ObjectMapper MAPPER = new ObjectMapper(); private static final ObjectMapper MAPPER = new ObjectMapper();
@Resource
private EmployeeRepository employeeRepository;
public static void main(String[] args) { public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args); SpringApplication.run(TestApplication.class, args);
@@ -67,11 +78,21 @@ public class TestApplication {
" \"like\": {\n" + " \"like\": {\n" +
" \"name\": \"Appl%\"\n" + " \"name\": \"Appl%\"\n" +
" },\n" + " },\n" +
" \"contain\": {\n" +
" \"name\": \"ple\"\n" +
" },\n" +
" \"startWith\": {\n" +
" \"name\": \"Appl\"\n" +
" },\n" +
" \"endWith\": {\n" +
" \"name\": \"le\"\n" +
" },\n" +
" \"less\": {\n" + " \"less\": {\n" +
" \"members\": 50\n" + " \"members\": 50\n" +
" },\n" + " },\n" +
" \"greatEqual\": {\n" + " \"greatEqual\": {\n" +
" \"members\": 0\n" + " \"members\": 0,\n" +
" \"createdTime\": \"2025-01-01 00:00:00\"\n" +
" },\n" + " },\n" +
" \"in\": {\n" + " \"in\": {\n" +
" \"name\": [\n" + " \"name\": [\n" +
@@ -127,6 +148,86 @@ public class TestApplication {
System.exit(0); System.exit(0);
} }
@EventListener(ApplicationReadyEvent.class)
public void runSpecificationTests() throws JsonProcessingException {
// 增
var cid1 = saveItem("company", "{\"name\": \"Apple\",\"members\": 10}").get("data").asLong();
var cid2 = saveItem("company", "{\"name\": \"Banana\",\"members\": 20}").get("data").asLong();
var cid3 = saveItem("company", "{\"name\": \"Cheery\",\"members\": 20}").get("data").asLong();
var eid1 = saveItem("employee", "{\"name\": \"Tom\",\"age\": 18, \"companyId\": %d}".formatted(cid1)).get("data").asLong();
var eid2 = saveItem("employee", "{\"name\": \"Jerry\",\"age\": 18, \"companyId\": %d}".formatted(cid1)).get("data").asLong();
var eid3 = saveItem("employee", "{\"name\": \"Mike\",\"age\": 18, \"companyId\": %d}".formatted(cid2)).get("data").asLong();
var rid = saveItem("report", "{\"employeeId\": %d, \"score\": 56.38, \"level\": \"A\"}".formatted(eid1)).get("data").asLong();
var rid2 = saveItem("report", "{\"employeeId\": %d, \"score\": 78.98, \"level\": \"B\"}".formatted(eid2)).get("data").asLong();
log.debug(
"Results: {}",
employeeRepository.findAll(
builder -> builder
.andIsNotNull(Employee.Fields.name)
.andEquals(Employee.Fields.name, "Tom")
.andLike(Employee.Fields.name, "To%")
.andStartsWith(Employee.Fields.name, "To")
.andEndsWith(Employee.Fields.name, "om")
.andLessThan(Employee.Fields.age, 50)
.andGreaterThanEqual(Employee.Fields.age, 0)
.andIn(Employee.Fields.name, List.of("Tom", "Mike"))
.andBetween(Employee.Fields.age, 0, 50)
.build()
)
);
log.debug(
"Results: {}",
employeeRepository.findAll(
(root, query, builder) ->
builder.and(
builder.isNotNull(root.get(Employee_.name)),
builder.equal(root.get(Employee_.name), "Tom"),
builder.like(root.get(Employee_.name), "To%"),
builder.lessThan(root.get(Employee_.age), 50),
builder.greaterThanOrEqualTo(root.get(Employee_.age), 0),
builder.in(root.get(Employee_.NAME)).value(List.of("Tom", "Mike")),
builder.between(root.get(Employee_.age), 0, 50),
builder.isNotEmpty(root.get(Employee_.company).get(Company_.employees)),
builder.notEqual(root.get(Employee_.company).get(Company_.name), "Apple")
)
)
);
log.debug(
"Results: {}",
employeeRepository.findAll(
QEmployee.employee.name.isNotNull()
.and(QEmployee.employee.name.eq("Tom"))
.and(QEmployee.employee.name.like("To%"))
.and(QEmployee.employee.name.startsWith("To"))
.and(QEmployee.employee.name.endsWith("om"))
.and(QEmployee.employee.age.lt(50))
.and(QEmployee.employee.age.goe(0))
.and(QEmployee.employee.name.in("Tom", "Mike"))
.and(QEmployee.employee.age.between(0, 50))
.and(QEmployee.employee.company().employees.isNotEmpty())
.and(QEmployee.employee.company().employees.any().name.ne("Tom"))
)
);
log.debug(
"Results: {}",
employeeRepository.findAll(
(root, query, builder) -> {
var reportRoot = query.from(Report.class);
return builder.and(
builder.equal(root.get(Employee_.id), reportRoot.get(Report_.employeeId)),
builder.equal(reportRoot.get(Report_.level), Report.Level.A)
);
}
)
);
System.exit(0);
}
private HttpHeaders headers() { private HttpHeaders headers() {
var headers = new HttpHeaders(); var headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON); headers.setContentType(MediaType.APPLICATION_JSON);

View File

@@ -0,0 +1,107 @@
package com.lanyuanxiaoyao.service.template.controller;
import com.lanyuanxiaoyao.service.template.entity.Report;
import com.lanyuanxiaoyao.service.template.service.EmployeeService;
import com.lanyuanxiaoyao.service.template.service.ReportService;
import java.time.LocalDateTime;
import java.util.function.Function;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("report")
public class ReportController extends SimpleControllerSupport<Report, ReportController.SaveItem, ReportController.ListItem, ReportController.DetailItem> {
private final EmployeeService employeeService;
public ReportController(ReportService service, EmployeeService employeeService) {
super(service);
this.employeeService = employeeService;
}
@Override
protected Function<SaveItem, Report> saveItemMapper() {
return item -> {
var report = new Report();
report.setId(item.getId());
report.setScore(item.getScore());
report.setLevel(item.getLevel());
report.setEmployeeId(item.getEmployeeId());
return report;
};
}
@Override
protected Function<Report, ListItem> listItemMapper() {
return report -> {
var employee = employeeService.detailOrThrow(report.getEmployeeId());
return new ListItem(
report.getId(),
employee.getId(),
employee.getName(),
report.getScore(),
report.getLevel()
);
};
}
@Override
protected Function<Report, DetailItem> detailItemMapper() {
return report -> {
var employee = employeeService.detailOrThrow(report.getEmployeeId());
return new DetailItem(
report.getId(),
employee.getId(),
employee.getName(),
report.getScore(),
report.getLevel(),
report.getCreatedTime(),
report.getModifiedTime()
);
};
}
@Setter
@Getter
@ToString
@AllArgsConstructor
@NoArgsConstructor
public static class SaveItem {
private Long id;
private Double score;
private Report.Level level;
private Long employeeId;
}
@Setter
@Getter
@ToString
@AllArgsConstructor
@NoArgsConstructor
public static class ListItem {
private Long id;
private Long employeeId;
private String employeeName;
private Double score;
private Report.Level level;
}
@Setter
@Getter
@ToString
@AllArgsConstructor
@NoArgsConstructor
public static class DetailItem {
private Long id;
private Long employeeId;
private String employeeName;
private Double score;
private Report.Level level;
private LocalDateTime createdTime;
private LocalDateTime modifiedTime;
}
}

View File

@@ -0,0 +1,40 @@
package com.lanyuanxiaoyao.service.template.entity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EntityListeners;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.FieldNameConstants;
import org.hibernate.annotations.Comment;
import org.hibernate.annotations.DynamicUpdate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
@Setter
@Getter
@ToString(callSuper = true)
@FieldNameConstants
@Entity
@DynamicUpdate
@EntityListeners(AuditingEntityListener.class)
@Comment("报告")
public class Report extends SimpleEntity {
@Column(nullable = false)
@Comment("分数")
private Double score = 0.0;
@Column(nullable = false)
@Enumerated(EnumType.STRING)
@Comment("等级")
private Level level;
@Column(nullable = false)
@Comment("员工id")
private Long employeeId;
public enum Level {
A, B, C, D, E
}
}

View File

@@ -0,0 +1,8 @@
package com.lanyuanxiaoyao.service.template.repository;
import com.lanyuanxiaoyao.service.template.entity.Report;
import org.springframework.stereotype.Repository;
@Repository
public interface ReportRepository extends SimpleRepository<Report, Long> {
}

View File

@@ -0,0 +1,12 @@
package com.lanyuanxiaoyao.service.template.service;
import com.lanyuanxiaoyao.service.template.entity.Report;
import com.lanyuanxiaoyao.service.template.repository.ReportRepository;
import org.springframework.stereotype.Service;
@Service
public class ReportService extends SimpleServiceSupport<Report> {
public ReportService(ReportRepository repository) {
super(repository);
}
}