1
0

Compare commits

...

6 Commits

96 changed files with 1402 additions and 1374 deletions

93
pom.xml
View File

@@ -11,9 +11,11 @@
<modules> <modules>
<module>spring-boot-service-template-common</module> <module>spring-boot-service-template-common</module>
<module>spring-boot-service-template-eq</module> <module>spring-boot-service-template-database/spring-boot-service-template-database-common</module>
<module>spring-boot-service-template-jpa</module> <module>spring-boot-service-template-database/spring-boot-service-template-database-common-test</module>
<module>spring-boot-service-template-xbatis</module> <module>spring-boot-service-template-database/spring-boot-service-template-database-eq</module>
<module>spring-boot-service-template-database/spring-boot-service-template-database-jpa</module>
<module>spring-boot-service-template-database/spring-boot-service-template-database-xbatis</module>
</modules> </modules>
<properties> <properties>
@@ -29,6 +31,10 @@
<mapstruct.version>1.6.3</mapstruct.version> <mapstruct.version>1.6.3</mapstruct.version>
<mapstruct-plus.version>1.5.0</mapstruct-plus.version> <mapstruct-plus.version>1.5.0</mapstruct-plus.version>
<datasource-decorator.version>2.0.0</datasource-decorator.version> <datasource-decorator.version>2.0.0</datasource-decorator.version>
<easy-query.version>3.1.68</easy-query.version>
<xbatis.version>1.9.7-spring-boot4</xbatis.version>
<hutool.version>5.8.43</hutool.version>
</properties> </properties>
<dependencies> <dependencies>
@@ -36,6 +42,10 @@
<groupId>org.projectlombok</groupId> <groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId> <artifactId>lombok</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.jspecify</groupId>
<artifactId>jspecify</artifactId>
</dependency>
</dependencies> </dependencies>
<dependencyManagement> <dependencyManagement>
@@ -47,7 +57,12 @@
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.lanyuanxiaoyao</groupId> <groupId>com.lanyuanxiaoyao</groupId>
<artifactId>spring-boot-service-template-jpa</artifactId> <artifactId>spring-boot-service-template-database-common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.lanyuanxiaoyao</groupId>
<artifactId>spring-boot-service-template-database-common-test</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </dependency>
@@ -67,23 +82,6 @@
<scope>import</scope> <scope>import</scope>
</dependency> </dependency>
<dependency>
<groupId>com.blinkfox</groupId>
<artifactId>fenix-spring-boot-starter</artifactId>
<version>${fenix.version}</version>
</dependency>
<dependency>
<groupId>io.github.openfeign.querydsl</groupId>
<artifactId>querydsl-jpa</artifactId>
<version>${querydsl.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-ant</artifactId>
<version>${hibernate.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.mapstruct</groupId> <groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId> <artifactId>mapstruct</artifactId>
@@ -102,20 +100,65 @@
<version>${datasource-decorator.version}</version> <version>${datasource-decorator.version}</version>
</dependency> </dependency>
<!-- jpa -->
<dependency> <dependency>
<groupId>org.jspecify</groupId> <groupId>com.blinkfox</groupId>
<artifactId>jspecify</artifactId> <artifactId>fenix-spring-boot-starter</artifactId>
<version>1.0.0</version> <version>${fenix.version}</version>
</dependency>
<dependency>
<groupId>io.github.openfeign.querydsl</groupId>
<artifactId>querydsl-jpa</artifactId>
<version>${querydsl.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-ant</artifactId>
<version>${hibernate.version}</version>
</dependency> </dependency>
<!-- xbatis --> <!-- xbatis -->
<dependency> <dependency>
<groupId>cn.xbatis</groupId> <groupId>cn.xbatis</groupId>
<artifactId>xbatis-spring-boot-parent</artifactId> <artifactId>xbatis-spring-boot-parent</artifactId>
<version>1.9.6-spring-boot4</version> <version>${xbatis.version}</version>
<type>pom</type> <type>pom</type>
<scope>import</scope> <scope>import</scope>
</dependency> </dependency>
<!-- easy-query -->
<dependency>
<groupId>com.easy-query</groupId>
<artifactId>sql-springboot4-starter</artifactId>
<version>3.1.68</version>
</dependency>
<dependency>
<groupId>org.jspecify</groupId>
<artifactId>jspecify</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
<version>${hutool.version}</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-http</artifactId>
<version>${hutool.version}</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-json</artifactId>
<version>${hutool.version}</version>
</dependency>
</dependencies> </dependencies>
</dependencyManagement> </dependencyManagement>

View File

@@ -1,125 +0,0 @@
package com.lanyuanxiaoyao.service.template.common.service;
import com.lanyuanxiaoyao.service.template.common.entity.Query;
import com.lanyuanxiaoyao.service.template.common.helper.ObjectHelper;
public abstract class QueryParser<O> {
protected abstract void nullEqual(Query.Queryable queryable);
protected abstract void notNullEqual(Query.Queryable queryable);
protected abstract void empty(Query.Queryable queryable);
protected abstract void notEmpty(Query.Queryable queryable);
protected abstract void equal(Query.Queryable queryable);
protected abstract void notEqual(Query.Queryable queryable);
protected abstract void like(Query.Queryable queryable);
protected abstract void notLike(Query.Queryable queryable);
protected abstract void contain(Query.Queryable queryable);
protected abstract void notContain(Query.Queryable queryable);
protected abstract void startWith(Query.Queryable queryable);
protected abstract void notStartWith(Query.Queryable queryable);
protected abstract void endWith(Query.Queryable queryable);
protected abstract void notEndWith(Query.Queryable queryable);
protected abstract void great(Query.Queryable queryable);
protected abstract void less(Query.Queryable queryable);
protected abstract void greatEqual(Query.Queryable queryable);
protected abstract void lessEqual(Query.Queryable queryable);
protected abstract void inside(Query.Queryable queryable);
protected abstract void notInside(Query.Queryable queryable);
protected abstract void between(Query.Queryable queryable);
protected abstract void notBetween(Query.Queryable queryable);
protected abstract O build();
public O build(Query.Queryable queryable) {
if (ObjectHelper.isNull(queryable)) {
return null;
}
if (ObjectHelper.isNotEmpty(queryable.nullEqual())) {
nullEqual(queryable);
}
if (ObjectHelper.isNotEmpty(queryable.notNullEqual())) {
notNullEqual(queryable);
}
if (ObjectHelper.isNotEmpty(queryable.empty())) {
empty(queryable);
}
if (ObjectHelper.isNotEmpty(queryable.notEmpty())) {
notEmpty(queryable);
}
if (ObjectHelper.isNotEmpty(queryable.equal())) {
equal(queryable);
}
if (ObjectHelper.isNotEmpty(queryable.notEqual())) {
notEqual(queryable);
}
if (ObjectHelper.isNotEmpty(queryable.like())) {
like(queryable);
}
if (ObjectHelper.isNotEmpty(queryable.notLike())) {
notLike(queryable);
}
if (ObjectHelper.isNotEmpty(queryable.contain())) {
contain(queryable);
}
if (ObjectHelper.isNotEmpty(queryable.notContain())) {
notContain(queryable);
}
if (ObjectHelper.isNotEmpty(queryable.startWith())) {
startWith(queryable);
}
if (ObjectHelper.isNotEmpty(queryable.notStartWith())) {
notStartWith(queryable);
}
if (ObjectHelper.isNotEmpty(queryable.endWith())) {
endWith(queryable);
}
if (ObjectHelper.isNotEmpty(queryable.notEndWith())) {
notEndWith(queryable);
}
if (ObjectHelper.isNotEmpty(queryable.great())) {
great(queryable);
}
if (ObjectHelper.isNotEmpty(queryable.less())) {
less(queryable);
}
if (ObjectHelper.isNotEmpty(queryable.greatEqual())) {
greatEqual(queryable);
}
if (ObjectHelper.isNotEmpty(queryable.lessEqual())) {
lessEqual(queryable);
}
if (ObjectHelper.isNotEmpty(queryable.inside())) {
inside(queryable);
}
if (ObjectHelper.isNotEmpty(queryable.notInside())) {
notInside(queryable);
}
if (ObjectHelper.isNotEmpty(queryable.between())) {
between(queryable);
}
if (ObjectHelper.isNotEmpty(queryable.notBetween())) {
notBetween(queryable);
}
return build();
}
}

View File

@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.lanyuanxiaoyao</groupId>
<artifactId>spring-boot-service-template</artifactId>
<version>1.1.0-SNAPSHOT</version>
</parent>
<artifactId>spring-boot-service-template-database-common-test</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.github.gavlyukovskiy</groupId>
<artifactId>p6spy-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,234 @@
package com.lanyuanxiaoyao.service.template.database.common.test;
import java.util.Map;
import java.util.Random;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.util.Assert;
import org.springframework.web.client.RestTemplate;
import tools.jackson.databind.JsonNode;
import tools.jackson.databind.ObjectMapper;
@Slf4j
public class AbstractTestApplication {
private static final String BASE_URL = "http://localhost:2490";
private static final RestTemplate REST_CLIENT = new RestTemplate();
private static final ObjectMapper MAPPER = new ObjectMapper();
private static final Random random = new Random();
private static final String randomChars = "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM";
protected void testCrud() {
formatLog("Save");
var cid1 = saveItem("company", randomCompany("Apple")).get("data").asLong();
var cid2 = saveItem("company", randomCompany()).get("data").asLong();
var cid3 = saveItem("company", randomCompany()).get("data").asLong();
formatLog("List");
assertListItems(listItems("company"), 3, 3);
formatLog("Detail");
var company1 = detailItem("company", cid1);
Assert.isTrue(cid1 == company1.at("/data/id").asLong(), "id错误");
Assert.isTrue(company1.at("/data/name").asString("").startsWith("Apple"), "name错误");
var company2 = detailItem("company", cid2);
Assert.isTrue(cid2 == company2.at("/data/id").asLong(), "id错误");
var company3 = detailItem("company", cid3);
Assert.isTrue(cid3 == company3.at("/data/id").asLong(), "id错误");
formatLog("List Page");
// language=JSON
var pageRequest = """
{
"page": {
"index": 1,
"size": 2
}
}
""";
assertListItems(listItems("company", pageRequest), 2, 3);
formatLog("List Queryable");
// language=JSON
var queryRequest = """
{
"query": {
"notNullEqual": [
"name"
],
"equal": {
"name": "Apple"
},
"like": {
"name": "Appl%"
},
"contain": {
"name": "ple"
},
"startWith": {
"name": "Appl"
},
"endWith": {
"name": "le"
},
"less": {
"members": 100
},
"greatEqual": {
"members": 0,
"createdTime": "2025-01-01 00:00:00"
},
"inside": {
"name": [
"Apple",
"Banana"
]
},
"between": {
"members": {
"start": 0,
"end": 100
}
}
},
"page": {
"index": 1,
"size": 2
}
}
""";
assertListItems(listItems("company", queryRequest), 1, 1);
formatLog("Clean");
removeItem("company", cid1);
assertListItems(listItems("company"), 2, 2);
removeItem("company", cid2);
removeItem("company", cid3);
assertListItems(listItems("company"), 0, 0);
}
protected void assertListItems(JsonNode node, int itemSizeTarget, int itemTotalTarget) {
var itemSize = node.at("/data/items").size();
var itemTotal = node.at("/data/total").asLong();
Assert.isTrue(itemSize == itemSizeTarget, "数量错误 (%d)".formatted(itemSize));
Assert.isTrue(itemTotal == itemTotalTarget, "分页总数错误 (%d)".formatted(itemTotal));
}
protected void formatLog(String text) {
log.info("===== {} =====", text);
}
protected Map<String, Object> randomCompany() {
return randomCompany(randomString(10));
}
protected Map<String, Object> randomCompany(String name) {
return Map.of(
"name", name,
"members", randomInt(100)
);
}
protected Map<String, Object> randomEmployee(Long companyId) {
return randomEmployee(companyId, randomString(10));
}
protected Map<String, Object> randomEmployee(Long companyId, String name) {
return Map.of(
"name", name,
"age", randomInt(100),
"companyId", companyId
);
}
protected Map<String, Object> randomReport(Long employeeId) {
return Map.of(
"score", randomDouble(200),
"level", randomChar("ABCDE"),
"employeeId", employeeId
);
}
protected String randomString(String prefix, int length) {
return prefix + randomString(length);
}
protected String randomString(int length) {
var builder = new StringBuilder();
for (int i = 0; i < length; i++) {
builder.append(randomChars.charAt(random.nextInt(randomChars.length())));
}
return builder.toString();
}
protected String randomChar(String base) {
return base.charAt(randomInt(base.length())) + "";
}
protected int randomInt(int bound) {
return random.nextInt(1, bound);
}
protected double randomDouble(int bound) {
return random.nextDouble(1, bound);
}
protected HttpHeaders headers() {
var headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return headers;
}
protected JsonNode saveItem(String path, Object body) {
var response = REST_CLIENT.postForEntity(
"%s/%s/save".formatted(BASE_URL, path),
new HttpEntity<>(body, headers()),
String.class
);
Assert.isTrue(response.getStatusCode().is2xxSuccessful(), "请求失败");
Assert.notNull(response.getBody(), "请求失败");
return MAPPER.readTree(response.getBody());
}
protected JsonNode listItems(String path) {
var response = REST_CLIENT.getForEntity(
"%s/%s/list".formatted(BASE_URL, path),
String.class
);
Assert.isTrue(response.getStatusCode().is2xxSuccessful(), "请求失败");
Assert.notNull(response.getBody(), "请求失败");
return MAPPER.readTree(response.getBody());
}
protected JsonNode listItems(String path, String query) {
var response = REST_CLIENT.postForEntity(
"%s/%s/list".formatted(BASE_URL, path),
new HttpEntity<>(query, headers()),
String.class
);
Assert.isTrue(response.getStatusCode().is2xxSuccessful(), "请求失败");
Assert.notNull(response.getBody(), "请求失败");
return MAPPER.readTree(response.getBody());
}
protected JsonNode detailItem(String path, Long id) {
var response = REST_CLIENT.getForEntity(
"%s/%s/detail/%d".formatted(BASE_URL, path, id),
String.class
);
Assert.isTrue(response.getStatusCode().is2xxSuccessful(), "请求失败");
Assert.notNull(response.getBody(), "请求失败");
return MAPPER.readTree(response.getBody());
}
protected void removeItem(String path, Long id) {
var response = REST_CLIENT.getForEntity(
"%s/%s/remove/%d".formatted(BASE_URL, path, id),
Void.class
);
Assert.isTrue(response.getStatusCode().is2xxSuccessful(), "请求失败");
}
}

View File

@@ -0,0 +1,14 @@
package com.lanyuanxiaoyao.service.template.database.common.test.entity;
public enum Industry {
TECHNOLOGY,
FINANCE,
MEDIA,
SERVICE,
GOVERNMENT,
EDUCATION,
HEALTHCARE,
CONSTRUCTION,
RETAIL,
OTHER,
}

View File

@@ -0,0 +1,5 @@
package com.lanyuanxiaoyao.service.template.database.common.test.entity;
public enum Level {
A, B, C, D, E
}

View File

@@ -0,0 +1,12 @@
server:
port: 2490
decorator:
datasource:
p6spy:
multiline: false
exclude-categories:
- commit
- result
- resultset
- rollback
log-format: "%(category)|%(executionTime)|%(sqlSingleLine)"

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.lanyuanxiaoyao</groupId>
<artifactId>spring-boot-service-template</artifactId>
<version>1.1.0-SNAPSHOT</version>
</parent>
<artifactId>spring-boot-service-template-database-common</artifactId>
<dependencies>
<dependency>
<groupId>com.lanyuanxiaoyao</groupId>
<artifactId>spring-boot-service-template-common</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -1,7 +1,7 @@
package com.lanyuanxiaoyao.service.template.common.controller; package com.lanyuanxiaoyao.service.template.database.common.controller;
import com.lanyuanxiaoyao.service.template.common.entity.GlobalResponse; import com.lanyuanxiaoyao.service.template.database.common.entity.GlobalResponse;
import com.lanyuanxiaoyao.service.template.common.entity.Query; import com.lanyuanxiaoyao.service.template.database.common.entity.Query;
/** /**
* 查询控制器接口用于定义统一的查询实体详情和列表的接口规范 * 查询控制器接口用于定义统一的查询实体详情和列表的接口规范

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
package com.lanyuanxiaoyao.service.template.common.controller; package com.lanyuanxiaoyao.service.template.database.common.controller;
public interface SimpleController<SAVE_ITEM, LIST_ITEM, DETAIL_ITEM> extends SaveController<SAVE_ITEM>, QueryController<LIST_ITEM, DETAIL_ITEM>, RemoveController { public interface SimpleController<SAVE_ITEM, LIST_ITEM, DETAIL_ITEM> extends SaveController<SAVE_ITEM>, QueryController<LIST_ITEM, DETAIL_ITEM>, RemoveController {
} }

View File

@@ -1,4 +1,4 @@
package com.lanyuanxiaoyao.service.template.common.entity; package com.lanyuanxiaoyao.service.template.database.common.entity;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;

View File

@@ -1,4 +1,4 @@
package com.lanyuanxiaoyao.service.template.common.entity; package com.lanyuanxiaoyao.service.template.database.common.entity;
import java.util.List; import java.util.List;

View File

@@ -1,4 +1,4 @@
package com.lanyuanxiaoyao.service.template.common.entity; package com.lanyuanxiaoyao.service.template.database.common.entity;
import java.io.Serializable; import java.io.Serializable;
import java.util.List; import java.util.List;

View File

@@ -1,4 +1,4 @@
package com.lanyuanxiaoyao.service.template.common.exception; package com.lanyuanxiaoyao.service.template.database.common.exception;
public class IdNotFoundException extends RuntimeException { public class IdNotFoundException extends RuntimeException {
public IdNotFoundException(Long id) { public IdNotFoundException(Long id) {

View File

@@ -1,4 +1,4 @@
package com.lanyuanxiaoyao.service.template.common.exception; package com.lanyuanxiaoyao.service.template.database.common.exception;
public class NotCollectionException extends RuntimeException { public class NotCollectionException extends RuntimeException {
public NotCollectionException(String variable) { public NotCollectionException(String variable) {

View File

@@ -1,4 +1,4 @@
package com.lanyuanxiaoyao.service.template.common.exception; package com.lanyuanxiaoyao.service.template.database.common.exception;
public class NotComparableException extends RuntimeException { public class NotComparableException extends RuntimeException {
public NotComparableException(String variable) { public NotComparableException(String variable) {

View File

@@ -1,4 +1,4 @@
package com.lanyuanxiaoyao.service.template.common.exception; package com.lanyuanxiaoyao.service.template.database.common.exception;
public class NotStringException extends RuntimeException { public class NotStringException extends RuntimeException {
public NotStringException(String variable) { public NotStringException(String variable) {

View File

@@ -1,4 +1,4 @@
package com.lanyuanxiaoyao.service.template.common.helper; package com.lanyuanxiaoyao.service.template.database.common.helper;
import java.time.Instant; import java.time.Instant;

View File

@@ -0,0 +1,128 @@
package com.lanyuanxiaoyao.service.template.database.common.service;
import com.lanyuanxiaoyao.service.template.common.helper.ObjectHelper;
import com.lanyuanxiaoyao.service.template.database.common.entity.Query;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
public abstract class QueryParser<O> {
private final Query.Queryable queryable;
private final O container;
protected abstract void nullEqual(Query.Queryable queryable, O container);
protected abstract void notNullEqual(Query.Queryable queryable, O container);
protected abstract void empty(Query.Queryable queryable, O container);
protected abstract void notEmpty(Query.Queryable queryable, O container);
protected abstract void equal(Query.Queryable queryable, O container);
protected abstract void notEqual(Query.Queryable queryable, O container);
protected abstract void like(Query.Queryable queryable, O container);
protected abstract void notLike(Query.Queryable queryable, O container);
protected abstract void contain(Query.Queryable queryable, O container);
protected abstract void notContain(Query.Queryable queryable, O container);
protected abstract void startWith(Query.Queryable queryable, O container);
protected abstract void notStartWith(Query.Queryable queryable, O container);
protected abstract void endWith(Query.Queryable queryable, O container);
protected abstract void notEndWith(Query.Queryable queryable, O container);
protected abstract void great(Query.Queryable queryable, O container);
protected abstract void less(Query.Queryable queryable, O container);
protected abstract void greatEqual(Query.Queryable queryable, O container);
protected abstract void lessEqual(Query.Queryable queryable, O container);
protected abstract void inside(Query.Queryable queryable, O container);
protected abstract void notInside(Query.Queryable queryable, O container);
protected abstract void between(Query.Queryable queryable, O container);
protected abstract void notBetween(Query.Queryable queryable, O container);
public void build() {
if (ObjectHelper.isNull(queryable)) {
return;
}
if (ObjectHelper.isNotEmpty(queryable.nullEqual())) {
nullEqual(queryable, container);
}
if (ObjectHelper.isNotEmpty(queryable.notNullEqual())) {
notNullEqual(queryable, container);
}
if (ObjectHelper.isNotEmpty(queryable.empty())) {
empty(queryable, container);
}
if (ObjectHelper.isNotEmpty(queryable.notEmpty())) {
notEmpty(queryable, container);
}
if (ObjectHelper.isNotEmpty(queryable.equal())) {
equal(queryable, container);
}
if (ObjectHelper.isNotEmpty(queryable.notEqual())) {
notEqual(queryable, container);
}
if (ObjectHelper.isNotEmpty(queryable.like())) {
like(queryable, container);
}
if (ObjectHelper.isNotEmpty(queryable.notLike())) {
notLike(queryable, container);
}
if (ObjectHelper.isNotEmpty(queryable.contain())) {
contain(queryable, container);
}
if (ObjectHelper.isNotEmpty(queryable.notContain())) {
notContain(queryable, container);
}
if (ObjectHelper.isNotEmpty(queryable.startWith())) {
startWith(queryable, container);
}
if (ObjectHelper.isNotEmpty(queryable.notStartWith())) {
notStartWith(queryable, container);
}
if (ObjectHelper.isNotEmpty(queryable.endWith())) {
endWith(queryable, container);
}
if (ObjectHelper.isNotEmpty(queryable.notEndWith())) {
notEndWith(queryable, container);
}
if (ObjectHelper.isNotEmpty(queryable.great())) {
great(queryable, container);
}
if (ObjectHelper.isNotEmpty(queryable.less())) {
less(queryable, container);
}
if (ObjectHelper.isNotEmpty(queryable.greatEqual())) {
greatEqual(queryable, container);
}
if (ObjectHelper.isNotEmpty(queryable.lessEqual())) {
lessEqual(queryable, container);
}
if (ObjectHelper.isNotEmpty(queryable.inside())) {
inside(queryable, container);
}
if (ObjectHelper.isNotEmpty(queryable.notInside())) {
notInside(queryable, container);
}
if (ObjectHelper.isNotEmpty(queryable.between())) {
between(queryable, container);
}
if (ObjectHelper.isNotEmpty(queryable.notBetween())) {
notBetween(queryable, container);
}
}
}

View File

@@ -1,7 +1,7 @@
package com.lanyuanxiaoyao.service.template.common.service; package com.lanyuanxiaoyao.service.template.database.common.service;
import com.lanyuanxiaoyao.service.template.common.entity.Page; import com.lanyuanxiaoyao.service.template.database.common.entity.Page;
import com.lanyuanxiaoyao.service.template.common.entity.Query; import com.lanyuanxiaoyao.service.template.database.common.entity.Query;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;

View File

@@ -1,4 +1,4 @@
package com.lanyuanxiaoyao.service.template.common.service; package com.lanyuanxiaoyao.service.template.database.common.service;
import java.util.Set; import java.util.Set;

View File

@@ -1,4 +1,4 @@
package com.lanyuanxiaoyao.service.template.common.service; package com.lanyuanxiaoyao.service.template.database.common.service;
/** /**
* 保存服务接口用于定义统一的保存实体对象的服务规范 * 保存服务接口用于定义统一的保存实体对象的服务规范

View File

@@ -1,4 +1,4 @@
package com.lanyuanxiaoyao.service.template.common.service; package com.lanyuanxiaoyao.service.template.database.common.service;
public interface SimpleService<ENTITY> extends SaveService<ENTITY>, QueryService<ENTITY>, RemoveService<ENTITY> { public interface SimpleService<ENTITY> extends SaveService<ENTITY>, QueryService<ENTITY>, RemoveService<ENTITY> {
} }

View File

@@ -9,37 +9,28 @@
<version>1.1.0-SNAPSHOT</version> <version>1.1.0-SNAPSHOT</version>
</parent> </parent>
<artifactId>spring-boot-service-template-eq</artifactId> <artifactId>spring-boot-service-template-database-eq</artifactId>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>com.lanyuanxiaoyao</groupId> <groupId>com.lanyuanxiaoyao</groupId>
<artifactId>spring-boot-service-template-common</artifactId> <artifactId>spring-boot-service-template-database-common</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId> <artifactId>spring-boot-starter-web</artifactId>
<scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.easy-query</groupId> <groupId>com.easy-query</groupId>
<artifactId>sql-springboot4-starter</artifactId> <artifactId>sql-springboot4-starter</artifactId>
<version>3.1.68</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.github.gavlyukovskiy</groupId> <groupId>com.lanyuanxiaoyao</groupId>
<artifactId>p6spy-spring-boot-starter</artifactId> <artifactId>spring-boot-service-template-database-common-test</artifactId>
</dependency>
<dependency>
<groupId>org.jspecify</groupId>
<artifactId>jspecify</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
</dependencies> </dependencies>
@@ -70,7 +61,7 @@
<path> <path>
<groupId>com.easy-query</groupId> <groupId>com.easy-query</groupId>
<artifactId>sql-processor</artifactId> <artifactId>sql-processor</artifactId>
<version>3.1.68</version> <version>${easy-query.version}</version>
</path> </path>
</annotationProcessorPaths> </annotationProcessorPaths>
</configuration> </configuration>

View File

@@ -1,13 +1,13 @@
package com.lanyuanxiaoyao.service.template.eq.controller; package com.lanyuanxiaoyao.service.template.database.eq.controller;
import com.easy.query.core.proxy.AbstractProxyEntity; import com.easy.query.core.proxy.AbstractProxyEntity;
import com.easy.query.core.proxy.ProxyEntityAvailable; import com.easy.query.core.proxy.ProxyEntityAvailable;
import com.lanyuanxiaoyao.service.template.common.controller.SimpleController;
import com.lanyuanxiaoyao.service.template.common.entity.GlobalResponse;
import com.lanyuanxiaoyao.service.template.common.entity.Query;
import com.lanyuanxiaoyao.service.template.common.helper.ObjectHelper; import com.lanyuanxiaoyao.service.template.common.helper.ObjectHelper;
import com.lanyuanxiaoyao.service.template.eq.entity.SimpleEntity; import com.lanyuanxiaoyao.service.template.database.common.controller.SimpleController;
import com.lanyuanxiaoyao.service.template.eq.service.SimpleServiceSupport; import com.lanyuanxiaoyao.service.template.database.common.entity.GlobalResponse;
import com.lanyuanxiaoyao.service.template.database.common.entity.Query;
import com.lanyuanxiaoyao.service.template.database.eq.entity.SimpleEntity;
import com.lanyuanxiaoyao.service.template.database.eq.service.SimpleServiceSupport;
import java.util.function.Function; import java.util.function.Function;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;

View File

@@ -1,4 +1,4 @@
package com.lanyuanxiaoyao.service.template.eq.entity; package com.lanyuanxiaoyao.service.template.database.eq.entity;
import com.easy.query.core.annotation.Column; import com.easy.query.core.annotation.Column;
import lombok.Getter; import lombok.Getter;

View File

@@ -0,0 +1,16 @@
package com.lanyuanxiaoyao.service.template.database.eq.entity;
import com.easy.query.core.annotation.LogicDelete;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.FieldNameConstants;
@Getter
@Setter
@ToString
@FieldNameConstants
public class LogicDeleteEntity extends IdOnlyEntity {
@LogicDelete
private Boolean deleted = false;
}

View File

@@ -1,4 +1,4 @@
package com.lanyuanxiaoyao.service.template.eq.entity; package com.lanyuanxiaoyao.service.template.database.eq.entity;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import lombok.Getter; import lombok.Getter;
@@ -10,7 +10,7 @@ import lombok.experimental.FieldNameConstants;
@Setter @Setter
@ToString(callSuper = true) @ToString(callSuper = true)
@FieldNameConstants @FieldNameConstants
public class SimpleEntity extends IdOnlyEntity { public class SimpleEntity extends LogicDeleteEntity {
private LocalDateTime createdTime; private LocalDateTime createdTime;
private LocalDateTime modifiedTime; private LocalDateTime modifiedTime;
} }

View File

@@ -1,7 +1,7 @@
package com.lanyuanxiaoyao.service.template.eq.entity; package com.lanyuanxiaoyao.service.template.database.eq.entity;
import com.easy.query.core.basic.extension.generated.PrimaryKeyGenerator; import com.easy.query.core.basic.extension.generated.PrimaryKeyGenerator;
import com.lanyuanxiaoyao.service.template.common.helper.SnowflakeHelper; import com.lanyuanxiaoyao.service.template.database.common.helper.SnowflakeHelper;
import java.io.Serializable; import java.io.Serializable;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;

View File

@@ -1,22 +1,24 @@
package com.lanyuanxiaoyao.service.template.eq.service; package com.lanyuanxiaoyao.service.template.database.eq.service;
import com.easy.query.api.proxy.client.EasyEntityQuery; import com.easy.query.api.proxy.client.EasyEntityQuery;
import com.easy.query.core.enums.SQLExecuteStrategyEnum; import com.easy.query.core.enums.SQLExecuteStrategyEnum;
import com.easy.query.core.proxy.AbstractProxyEntity; import com.easy.query.core.proxy.AbstractProxyEntity;
import com.easy.query.core.proxy.ProxyEntityAvailable; import com.easy.query.core.proxy.ProxyEntityAvailable;
import com.lanyuanxiaoyao.service.template.common.entity.Page;
import com.lanyuanxiaoyao.service.template.common.entity.Query;
import com.lanyuanxiaoyao.service.template.common.exception.IdNotFoundException;
import com.lanyuanxiaoyao.service.template.common.helper.ObjectHelper; import com.lanyuanxiaoyao.service.template.common.helper.ObjectHelper;
import com.lanyuanxiaoyao.service.template.common.service.QueryParser; import com.lanyuanxiaoyao.service.template.database.common.entity.Page;
import com.lanyuanxiaoyao.service.template.common.service.SimpleService; import com.lanyuanxiaoyao.service.template.database.common.entity.Query;
import com.lanyuanxiaoyao.service.template.eq.entity.SimpleEntity; import com.lanyuanxiaoyao.service.template.database.common.exception.IdNotFoundException;
import com.lanyuanxiaoyao.service.template.database.common.service.QueryParser;
import com.lanyuanxiaoyao.service.template.database.common.service.SimpleService;
import com.lanyuanxiaoyao.service.template.database.eq.entity.SimpleEntity;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.mapstruct.Named;
import org.springframework.transaction.annotation.Transactional;
@Slf4j @Slf4j
@RequiredArgsConstructor @RequiredArgsConstructor
@@ -27,6 +29,7 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity & ProxyEn
protected final EasyEntityQuery entityQuery; protected final EasyEntityQuery entityQuery;
private final Class<ENTITY> target; private final Class<ENTITY> target;
@Transactional(rollbackFor = Throwable.class)
@Override @Override
public Long save(ENTITY entity) { public Long save(ENTITY entity) {
if (ObjectHelper.isNull(entity.getId())) { if (ObjectHelper.isNull(entity.getId())) {
@@ -39,6 +42,7 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity & ProxyEn
return entity.getId(); return entity.getId();
} }
@Transactional(rollbackFor = Throwable.class)
@Override @Override
public void save(Iterable<ENTITY> entities) { public void save(Iterable<ENTITY> entities) {
var insertList = new ArrayList<ENTITY>(); var insertList = new ArrayList<ENTITY>();
@@ -94,7 +98,7 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity & ProxyEn
var result = entityQuery.queryable(target) var result = entityQuery.queryable(target)
.where(this::commonPredicates) .where(this::commonPredicates)
.where(proxy -> new EqQueryParser<ENTITY, PROXY>(proxy).build(query.query())) .where(proxy -> new EqQueryParser<ENTITY, PROXY>(query.query(), proxy).build())
.orderBy(ObjectHelper.isNotEmpty(query.sort()), proxy -> query.sort().forEach(sort -> proxy.anyColumn(sort.column()).orderBy(Query.Sortable.Direction.ASC.equals(sort.direction())))) .orderBy(ObjectHelper.isNotEmpty(query.sort()), proxy -> query.sort().forEach(sort -> proxy.anyColumn(sort.column()).orderBy(Query.Sortable.Direction.ASC.equals(sort.direction()))))
.toPageResult(index, size); .toPageResult(index, size);
@@ -110,16 +114,19 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity & ProxyEn
.singleOptional(); .singleOptional();
} }
@Named("detail")
@Override @Override
public ENTITY detail(Long id) { public ENTITY detail(Long id) {
return detailOptional(id).orElse(null); return detailOptional(id).orElse(null);
} }
@Named("detailOrThrow")
@Override @Override
public ENTITY detailOrThrow(Long id) { public ENTITY detailOrThrow(Long id) {
return detailOptional(id).orElseThrow(() -> new IdNotFoundException(id)); return detailOptional(id).orElseThrow(() -> new IdNotFoundException(id));
} }
@Transactional(rollbackFor = Throwable.class)
@Override @Override
public void remove(Long id) { public void remove(Long id) {
if (ObjectHelper.isNotNull(id)) { if (ObjectHelper.isNotNull(id)) {
@@ -130,6 +137,7 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity & ProxyEn
} }
} }
@Transactional(rollbackFor = Throwable.class)
@Override @Override
public void remove(Set<Long> ids) { public void remove(Set<Long> ids) {
if (ObjectHelper.isNotEmpty(ids)) { if (ObjectHelper.isNotEmpty(ids)) {
@@ -140,102 +148,103 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity & ProxyEn
} }
} }
@RequiredArgsConstructor private static final class EqQueryParser<ENTITY extends SimpleEntity & ProxyEntityAvailable<ENTITY, PROXY>, PROXY extends AbstractProxyEntity<PROXY, ENTITY>> extends QueryParser<PROXY> {
private static final class EqQueryParser<ENTITY extends SimpleEntity & ProxyEntityAvailable<ENTITY, PROXY>, PROXY extends AbstractProxyEntity<PROXY, ENTITY>> extends QueryParser<Void> { public EqQueryParser(Query.Queryable queryable, PROXY container) {
private final PROXY proxy; super(queryable, container);
}
@Override @Override
protected void nullEqual(Query.Queryable queryable) { protected void nullEqual(Query.Queryable queryable, PROXY proxy) {
queryable.nullEqual().forEach(column -> proxy.anyColumn(column).isNull()); queryable.nullEqual().forEach(column -> proxy.anyColumn(column).isNull());
} }
@Override @Override
protected void notNullEqual(Query.Queryable queryable) { protected void notNullEqual(Query.Queryable queryable, PROXY proxy) {
queryable.notNullEqual().forEach(column -> proxy.anyColumn(column).isNotNull()); queryable.notNullEqual().forEach(column -> proxy.anyColumn(column).isNotNull());
} }
@Override @Override
protected void empty(Query.Queryable queryable) { protected void empty(Query.Queryable queryable, PROXY proxy) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@Override @Override
protected void notEmpty(Query.Queryable queryable) { protected void notEmpty(Query.Queryable queryable, PROXY proxy) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@Override @Override
protected void equal(Query.Queryable queryable) { protected void equal(Query.Queryable queryable, PROXY proxy) {
queryable.equal().forEach((column, value) -> proxy.anyColumn(column).eq(value)); queryable.equal().forEach((column, value) -> proxy.anyColumn(column).eq(value));
} }
@Override @Override
protected void notEqual(Query.Queryable queryable) { protected void notEqual(Query.Queryable queryable, PROXY proxy) {
queryable.notEqual().forEach((column, value) -> proxy.anyColumn(column).ne(value)); queryable.notEqual().forEach((column, value) -> proxy.anyColumn(column).ne(value));
} }
@Override @Override
protected void like(Query.Queryable queryable) { protected void like(Query.Queryable queryable, PROXY proxy) {
queryable.like().forEach((column, value) -> proxy.anyColumn(column).likeRaw(value)); queryable.like().forEach((column, value) -> proxy.anyColumn(column).likeRaw(value));
} }
@Override @Override
protected void notLike(Query.Queryable queryable) { protected void notLike(Query.Queryable queryable, PROXY proxy) {
queryable.notLike().forEach((column, value) -> proxy.anyColumn(column).notLikeRaw(value)); queryable.notLike().forEach((column, value) -> proxy.anyColumn(column).notLikeRaw(value));
} }
@Override @Override
protected void contain(Query.Queryable queryable) { protected void contain(Query.Queryable queryable, PROXY proxy) {
queryable.contain().forEach((column, value) -> proxy.anyColumn(column).like(value)); queryable.contain().forEach((column, value) -> proxy.anyColumn(column).like(value));
} }
@Override @Override
protected void notContain(Query.Queryable queryable) { protected void notContain(Query.Queryable queryable, PROXY proxy) {
queryable.notContain().forEach((column, value) -> proxy.anyColumn(column).notLike(value)); queryable.notContain().forEach((column, value) -> proxy.anyColumn(column).notLike(value));
} }
@Override @Override
protected void startWith(Query.Queryable queryable) { protected void startWith(Query.Queryable queryable, PROXY proxy) {
queryable.startWith().forEach((column, value) -> proxy.anyColumn(column).likeMatchLeft(value)); queryable.startWith().forEach((column, value) -> proxy.anyColumn(column).likeMatchLeft(value));
} }
@Override @Override
protected void notStartWith(Query.Queryable queryable) { protected void notStartWith(Query.Queryable queryable, PROXY proxy) {
queryable.notStartWith().forEach((column, value) -> proxy.anyColumn(column).notLikeMatchLeft(value)); queryable.notStartWith().forEach((column, value) -> proxy.anyColumn(column).notLikeMatchLeft(value));
} }
@Override @Override
protected void endWith(Query.Queryable queryable) { protected void endWith(Query.Queryable queryable, PROXY proxy) {
queryable.endWith().forEach((column, value) -> proxy.anyColumn(column).likeMatchRight(value)); queryable.endWith().forEach((column, value) -> proxy.anyColumn(column).likeMatchRight(value));
} }
@Override @Override
protected void notEndWith(Query.Queryable queryable) { protected void notEndWith(Query.Queryable queryable, PROXY proxy) {
queryable.notEndWith().forEach((column, value) -> proxy.anyColumn(column).notLikeMatchRight(value)); queryable.notEndWith().forEach((column, value) -> proxy.anyColumn(column).notLikeMatchRight(value));
} }
@Override @Override
protected void great(Query.Queryable queryable) { protected void great(Query.Queryable queryable, PROXY proxy) {
queryable.great().forEach((column, value) -> proxy.anyColumn(column).gt(value)); queryable.great().forEach((column, value) -> proxy.anyColumn(column).gt(value));
} }
@Override @Override
protected void less(Query.Queryable queryable) { protected void less(Query.Queryable queryable, PROXY proxy) {
queryable.less().forEach((column, value) -> proxy.anyColumn(column).lt(value)); queryable.less().forEach((column, value) -> proxy.anyColumn(column).lt(value));
} }
@Override @Override
protected void greatEqual(Query.Queryable queryable) { protected void greatEqual(Query.Queryable queryable, PROXY proxy) {
queryable.greatEqual().forEach((column, value) -> proxy.anyColumn(column).ge(value)); queryable.greatEqual().forEach((column, value) -> proxy.anyColumn(column).ge(value));
} }
@Override @Override
protected void lessEqual(Query.Queryable queryable) { protected void lessEqual(Query.Queryable queryable, PROXY proxy) {
queryable.lessEqual().forEach((column, value) -> proxy.anyColumn(column).le(value)); queryable.lessEqual().forEach((column, value) -> proxy.anyColumn(column).le(value));
} }
@Override @Override
protected void inside(Query.Queryable queryable) { protected void inside(Query.Queryable queryable, PROXY proxy) {
queryable.inside() queryable.inside()
.entrySet() .entrySet()
.stream() .stream()
@@ -244,7 +253,7 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity & ProxyEn
} }
@Override @Override
protected void notInside(Query.Queryable queryable) { protected void notInside(Query.Queryable queryable, PROXY proxy) {
queryable.notInside() queryable.notInside()
.entrySet() .entrySet()
.stream() .stream()
@@ -253,18 +262,19 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity & ProxyEn
} }
@Override @Override
protected void between(Query.Queryable queryable) { protected void between(Query.Queryable queryable, PROXY proxy) {
throw new UnsupportedOperationException(); queryable.between().forEach((column, value) -> {
proxy.anyColumn(column).gt(value.start());
proxy.anyColumn(column).le(value.end());
});
} }
@Override @Override
protected void notBetween(Query.Queryable queryable) { protected void notBetween(Query.Queryable queryable, PROXY proxy) {
throw new UnsupportedOperationException(); queryable.between().forEach((column, value) -> {
} proxy.anyColumn(column).le(value.start());
proxy.anyColumn(column).gt(value.end());
@Override });
protected Void build() {
return null;
} }
} }
} }

View File

@@ -4,7 +4,8 @@ create table if not exists Company
name varchar(255) not null, name varchar(255) not null,
members int not null, members int not null,
created_time timestamp not null default current_timestamp(), created_time timestamp not null default current_timestamp(),
modified_time timestamp not null default current_timestamp() on update current_timestamp() modified_time timestamp not null default current_timestamp() on update current_timestamp(),
deleted tinyint not null default false
); );
create table if not exists Employee create table if not exists Employee
@@ -12,6 +13,8 @@ create table if not exists Employee
id bigint primary key, id bigint primary key,
name varchar(255) not null, name varchar(255) not null,
age int not null, age int not null,
company_id bigint not null,
created_time timestamp not null default current_timestamp(), created_time timestamp not null default current_timestamp(),
modified_time timestamp not null default current_timestamp() on update current_timestamp() modified_time timestamp not null default current_timestamp() on update current_timestamp(),
deleted tinyint not null default false
); );

View File

@@ -0,0 +1,76 @@
package com.lanyuanxiaoyao.service.template.database.eq;
import com.easy.query.api.proxy.client.EasyEntityQuery;
import com.lanyuanxiaoyao.service.template.database.common.test.AbstractTestApplication;
import com.lanyuanxiaoyao.service.template.database.eq.entity.Company;
import com.lanyuanxiaoyao.service.template.database.eq.entity.Employee;
import com.lanyuanxiaoyao.service.template.database.eq.entity.proxy.EmployeeProxy;
import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.util.Assert;
@Slf4j
@RequiredArgsConstructor
@SpringBootApplication
public class TestApplication extends AbstractTestApplication {
private final EasyEntityQuery entityQuery;
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
@EventListener(ApplicationReadyEvent.class)
public void runTests() {
testCrud();
testDelete();
testQuery();
System.exit(0);
}
private void testDelete() {
formatLog("Delete");
saveItem("company", randomCompany());
saveItem("company", randomCompany());
entityQuery.deletable(Company.class)
.where(proxy -> proxy.id().isNotNull())
.allowDeleteStatement(true)
.executeRows();
}
private void testQuery() {
formatLog("Added");
var company1 = Company.builder().name(randomString(5)).members(randomInt(100)).build();
entityQuery.insertable(company1).executeRows();
var company2 = Company.builder().name(randomString(5)).members(randomInt(100)).build();
entityQuery.insertable(company2).executeRows();
var employee1 = Employee.builder().name("Tom").age(randomInt(100)).companyId(company1.getId()).build();
entityQuery.insertable(employee1).executeRows();
var employee2 = Employee.builder().name(randomString(10)).age(randomInt(100)).companyId(company2.getId()).build();
entityQuery.insertable(employee2).executeRows();
formatLog("Query");
var employees1 = entityQuery.queryable(Employee.class)
.include(EmployeeProxy::company)
.where(proxy -> {
proxy.name().isNotNull();
proxy.name().eq("Tom");
proxy.name().startsWith("To");
proxy.name().endsWith("om");
proxy.age().lt(200);
proxy.age().gt(0);
proxy.name().in(List.of("Tom", "Mike"));
})
.toList();
Assert.isTrue(employees1.size() == 1, "查询数量错误");
formatLog("Clean");
entityQuery.deletable(Company.class).where(proxy -> proxy.id().isNotNull()).executeRows();
entityQuery.deletable(Employee.class).where(proxy -> proxy.id().isNotNull()).executeRows();
}
}

View File

@@ -1,8 +1,8 @@
package com.lanyuanxiaoyao.service.template.eq.controller; package com.lanyuanxiaoyao.service.template.database.eq.controller;
import com.lanyuanxiaoyao.service.template.eq.entity.Company; import com.lanyuanxiaoyao.service.template.database.eq.entity.Company;
import com.lanyuanxiaoyao.service.template.eq.entity.proxy.CompanyProxy; import com.lanyuanxiaoyao.service.template.database.eq.entity.proxy.CompanyProxy;
import com.lanyuanxiaoyao.service.template.eq.service.CompanyService; import com.lanyuanxiaoyao.service.template.database.eq.service.CompanyService;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.function.Function; import java.util.function.Function;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;

View File

@@ -0,0 +1,33 @@
package com.lanyuanxiaoyao.service.template.database.eq.entity;
import com.easy.query.core.annotation.EntityProxy;
import com.easy.query.core.annotation.Navigate;
import com.easy.query.core.annotation.Table;
import com.easy.query.core.enums.RelationTypeEnum;
import com.easy.query.core.proxy.ProxyEntityAvailable;
import com.lanyuanxiaoyao.service.template.database.eq.entity.proxy.CompanyProxy;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.FieldNameConstants;
@Getter
@Setter
@ToString(callSuper = true)
@FieldNameConstants
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Table
@EntityProxy
public class Company extends SimpleEntity implements ProxyEntityAvailable<Company, CompanyProxy> {
private String name;
private Integer members;
@Navigate(value = RelationTypeEnum.OneToMany, selfProperty = {"id"}, targetProperty = {"companyId"})
private List<Employee> employees;
}

View File

@@ -0,0 +1,33 @@
package com.lanyuanxiaoyao.service.template.database.eq.entity;
import com.easy.query.core.annotation.EntityProxy;
import com.easy.query.core.annotation.Navigate;
import com.easy.query.core.annotation.Table;
import com.easy.query.core.enums.RelationTypeEnum;
import com.easy.query.core.proxy.ProxyEntityAvailable;
import com.lanyuanxiaoyao.service.template.database.eq.entity.proxy.EmployeeProxy;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.FieldNameConstants;
@Getter
@Setter
@ToString(callSuper = true)
@FieldNameConstants
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Table
@EntityProxy
public class Employee extends SimpleEntity implements ProxyEntityAvailable<Employee, EmployeeProxy> {
private String name;
private Integer age;
private Long companyId;
@Navigate(value = RelationTypeEnum.OneToOne, selfProperty = {"companyId"}, targetProperty = {"id"})
private Company company;
}

View File

@@ -1,8 +1,8 @@
package com.lanyuanxiaoyao.service.template.eq.service; package com.lanyuanxiaoyao.service.template.database.eq.service;
import com.easy.query.api.proxy.client.EasyEntityQuery; import com.easy.query.api.proxy.client.EasyEntityQuery;
import com.lanyuanxiaoyao.service.template.eq.entity.Company; import com.lanyuanxiaoyao.service.template.database.eq.entity.Company;
import com.lanyuanxiaoyao.service.template.eq.entity.proxy.CompanyProxy; import com.lanyuanxiaoyao.service.template.database.eq.entity.proxy.CompanyProxy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@Service @Service

View File

@@ -1,8 +1,8 @@
package com.lanyuanxiaoyao.service.template.eq.service; package com.lanyuanxiaoyao.service.template.database.eq.service;
import com.easy.query.api.proxy.client.EasyEntityQuery; import com.easy.query.api.proxy.client.EasyEntityQuery;
import com.lanyuanxiaoyao.service.template.eq.entity.Employee; import com.lanyuanxiaoyao.service.template.database.eq.entity.Employee;
import com.lanyuanxiaoyao.service.template.eq.entity.proxy.EmployeeProxy; import com.lanyuanxiaoyao.service.template.database.eq.entity.proxy.EmployeeProxy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@Service @Service

View File

@@ -1,10 +1,8 @@
server:
port: 2490
spring: spring:
application: profiles:
name: Test include: test
datasource: datasource:
url: "jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;MODE=MySQL;DATABASE_TO_LOWER=TRUE;INIT=runscript from '/Users/lanyuanxiaoyao/Project/IdeaProjects/spring-boot-service-template/spring-boot-service-template-eq/src/test/initial.sql'" url: "jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;MODE=MySQL;DATABASE_TO_LOWER=TRUE;INIT=runscript from '/Users/lanyuanxiaoyao/Project/IdeaProjects/spring-boot-service-template/spring-boot-service-template-database/spring-boot-service-template-database-eq/src/test/initial.sql'"
username: test username: test
password: test password: test
driver-class-name: org.h2.Driver driver-class-name: org.h2.Driver
@@ -12,13 +10,4 @@ easy-query:
database: mysql database: mysql
name-conversion: underlined name-conversion: underlined
print-sql: false print-sql: false
decorator: print-nav-sql: false
datasource:
p6spy:
multiline: false
exclude-categories:
- commit
- result
- resultset
- rollback
log-format: "%(category)|%(executionTime)|%(sqlSingleLine)"

View File

@@ -9,12 +9,12 @@
<version>1.1.0-SNAPSHOT</version> <version>1.1.0-SNAPSHOT</version>
</parent> </parent>
<artifactId>spring-boot-service-template-jpa</artifactId> <artifactId>spring-boot-service-template-database-jpa</artifactId>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>com.lanyuanxiaoyao</groupId> <groupId>com.lanyuanxiaoyao</groupId>
<artifactId>spring-boot-service-template-common</artifactId> <artifactId>spring-boot-service-template-database-common</artifactId>
</dependency> </dependency>
<dependency> <dependency>
@@ -22,6 +22,7 @@
<artifactId>spring-boot-starter-web</artifactId> <artifactId>spring-boot-starter-web</artifactId>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId> <artifactId>spring-boot-starter-data-jpa</artifactId>
@@ -41,24 +42,8 @@
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.mapstruct</groupId> <groupId>com.lanyuanxiaoyao</groupId>
<artifactId>mapstruct</artifactId> <artifactId>spring-boot-service-template-database-common-test</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.github.gavlyukovskiy</groupId>
<artifactId>p6spy-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.jspecify</groupId>
<artifactId>jspecify</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
</dependencies> </dependencies>

View File

@@ -1,11 +1,11 @@
package com.lanyuanxiaoyao.service.template.xbatis.controller; package com.lanyuanxiaoyao.service.template.database.jpa.controller;
import com.lanyuanxiaoyao.service.template.common.controller.SimpleController;
import com.lanyuanxiaoyao.service.template.common.entity.GlobalResponse;
import com.lanyuanxiaoyao.service.template.common.entity.Query;
import com.lanyuanxiaoyao.service.template.common.helper.ObjectHelper; import com.lanyuanxiaoyao.service.template.common.helper.ObjectHelper;
import com.lanyuanxiaoyao.service.template.xbatis.entity.SimpleEntity; import com.lanyuanxiaoyao.service.template.database.common.controller.SimpleController;
import com.lanyuanxiaoyao.service.template.xbatis.service.SimpleServiceSupport; import com.lanyuanxiaoyao.service.template.database.common.entity.GlobalResponse;
import com.lanyuanxiaoyao.service.template.database.common.entity.Query;
import com.lanyuanxiaoyao.service.template.database.jpa.entity.SimpleEntity;
import com.lanyuanxiaoyao.service.template.database.jpa.service.SimpleServiceSupport;
import java.util.function.Function; import java.util.function.Function;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;

View File

@@ -1,4 +1,4 @@
package com.lanyuanxiaoyao.service.template.jpa.entity; package com.lanyuanxiaoyao.service.template.database.jpa.entity;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.persistence.EntityListeners; import jakarta.persistence.EntityListeners;

View File

@@ -1,4 +1,4 @@
package com.lanyuanxiaoyao.service.template.jpa.entity; package com.lanyuanxiaoyao.service.template.database.jpa.entity;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.persistence.EntityListeners; import jakarta.persistence.EntityListeners;

View File

@@ -1,4 +1,4 @@
package com.lanyuanxiaoyao.service.template.jpa.entity; package com.lanyuanxiaoyao.service.template.database.jpa.entity;
import java.lang.annotation.ElementType; import java.lang.annotation.ElementType;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;

View File

@@ -1,6 +1,6 @@
package com.lanyuanxiaoyao.service.template.jpa.entity; package com.lanyuanxiaoyao.service.template.database.jpa.entity;
import com.lanyuanxiaoyao.service.template.common.helper.SnowflakeHelper; import com.lanyuanxiaoyao.service.template.database.common.helper.SnowflakeHelper;
import java.io.Serializable; import java.io.Serializable;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor;

View File

@@ -1,4 +1,4 @@
package com.lanyuanxiaoyao.service.template.jpa.helper; package com.lanyuanxiaoyao.service.template.database.jpa.helper;
import jakarta.persistence.Entity; import jakarta.persistence.Entity;
import java.io.IOException; import java.io.IOException;

View File

@@ -1,4 +1,4 @@
package com.lanyuanxiaoyao.service.template.jpa.repository; package com.lanyuanxiaoyao.service.template.database.jpa.repository;
import com.blinkfox.fenix.jpa.FenixJpaRepository; import com.blinkfox.fenix.jpa.FenixJpaRepository;
import com.blinkfox.fenix.specification.FenixJpaSpecificationExecutor; import com.blinkfox.fenix.specification.FenixJpaSpecificationExecutor;

View File

@@ -1,23 +1,22 @@
package com.lanyuanxiaoyao.service.template.jpa.service; package com.lanyuanxiaoyao.service.template.database.jpa.service;
import com.lanyuanxiaoyao.service.template.common.entity.Page;
import com.lanyuanxiaoyao.service.template.common.entity.Query;
import com.lanyuanxiaoyao.service.template.common.exception.IdNotFoundException;
import com.lanyuanxiaoyao.service.template.common.exception.NotCollectionException;
import com.lanyuanxiaoyao.service.template.common.exception.NotComparableException;
import com.lanyuanxiaoyao.service.template.common.exception.NotStringException;
import com.lanyuanxiaoyao.service.template.common.helper.ObjectHelper; import com.lanyuanxiaoyao.service.template.common.helper.ObjectHelper;
import com.lanyuanxiaoyao.service.template.common.service.QueryParser; import com.lanyuanxiaoyao.service.template.database.common.entity.Page;
import com.lanyuanxiaoyao.service.template.common.service.SimpleService; import com.lanyuanxiaoyao.service.template.database.common.entity.Query;
import com.lanyuanxiaoyao.service.template.jpa.entity.IdOnlyEntity; import com.lanyuanxiaoyao.service.template.database.common.exception.IdNotFoundException;
import com.lanyuanxiaoyao.service.template.jpa.entity.SimpleEntity; import com.lanyuanxiaoyao.service.template.database.common.exception.NotCollectionException;
import com.lanyuanxiaoyao.service.template.jpa.repository.SimpleRepository; import com.lanyuanxiaoyao.service.template.database.common.exception.NotComparableException;
import com.lanyuanxiaoyao.service.template.database.common.exception.NotStringException;
import com.lanyuanxiaoyao.service.template.database.common.service.QueryParser;
import com.lanyuanxiaoyao.service.template.database.common.service.SimpleService;
import com.lanyuanxiaoyao.service.template.database.jpa.entity.IdOnlyEntity;
import com.lanyuanxiaoyao.service.template.database.jpa.entity.SimpleEntity;
import com.lanyuanxiaoyao.service.template.database.jpa.repository.SimpleRepository;
import jakarta.persistence.criteria.CriteriaBuilder; import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery; import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Path; 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 java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.ArrayList; import java.util.ArrayList;
@@ -29,6 +28,7 @@ import lombok.extern.slf4j.Slf4j;
import org.mapstruct.Named; import org.mapstruct.Named;
import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort;
import org.springframework.transaction.annotation.Transactional;
/** /**
* 简单服务支持类提供基础的业务逻辑实现 * 简单服务支持类提供基础的业务逻辑实现
@@ -87,7 +87,7 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
* @param entity 需要保存的实体对象 * @param entity 需要保存的实体对象
* @return 返回保存后的实体ID * @return 返回保存后的实体ID
*/ */
@Transactional(rollbackOn = Throwable.class) @Transactional(rollbackFor = Throwable.class)
@Override @Override
public Long save(ENTITY entity) { public Long save(ENTITY entity) {
entity = repository.saveOrUpdateByNotNullProperties(entity); entity = repository.saveOrUpdateByNotNullProperties(entity);
@@ -103,7 +103,7 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
* *
* @param entities 需要保存的实体对象集合 * @param entities 需要保存的实体对象集合
*/ */
@Transactional(rollbackOn = Throwable.class) @Transactional(rollbackFor = Throwable.class)
@Override @Override
public void save(Iterable<ENTITY> entities) { public void save(Iterable<ENTITY> entities) {
repository.saveOrUpdateAllByNotNullProperties(entities); repository.saveOrUpdateAllByNotNullProperties(entities);
@@ -194,7 +194,11 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
var result = repository.findAll( var result = repository.findAll(
(root, query, builder) -> { (root, query, builder) -> {
var predicate = commonPredicates(root, query, builder); var predicate = commonPredicates(root, query, builder);
var queryPredicate = new JpaQueryParser<>(root, query, builder).build(listQuery.query()); var predicates = new ArrayList<Predicate>();
new JpaQueryParser<>(listQuery.query(), predicates, root, query, builder).build();
var queryPredicate = predicates.size() == 1
? predicates.get(0)
: builder.and(predicates.toArray(Predicate[]::new));
return ObjectHelper.isNull(predicate) return ObjectHelper.isNull(predicate)
? queryPredicate ? queryPredicate
: builder.and(predicate, queryPredicate); : builder.and(predicate, queryPredicate);
@@ -268,11 +272,11 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
* *
* @param id 实体主键ID * @param id 实体主键ID
*/ */
@Transactional(rollbackOn = Throwable.class) @Transactional(rollbackFor = Throwable.class)
@Override @Override
public void remove(Long id) { public void remove(Long id) {
if (ObjectHelper.isNotNull(id)) { if (ObjectHelper.isNotNull(id)) {
repository.deleteById(id); repository.deleteBatchByIds(List.of(id));
} }
} }
@@ -286,7 +290,7 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
* *
* @param ids 实体主键ID集合 * @param ids 实体主键ID集合
*/ */
@Transactional(rollbackOn = Throwable.class) @Transactional(rollbackFor = Throwable.class)
@Override @Override
public void remove(Set<Long> ids) { public void remove(Set<Long> ids) {
if (ObjectHelper.isNotEmpty(ids)) { if (ObjectHelper.isNotEmpty(ids)) {
@@ -295,14 +299,14 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private static final class JpaQueryParser<ENTITY> extends QueryParser<Predicate> { private static final class JpaQueryParser<ENTITY> extends QueryParser<List<Predicate>> {
private final Root<ENTITY> root; private final Root<ENTITY> root;
@SuppressWarnings({"unused", "FieldCanBeLocal"}) @SuppressWarnings({"unused", "FieldCanBeLocal"})
private final CriteriaQuery<?> query; private final CriteriaQuery<?> query;
private final CriteriaBuilder builder; private final CriteriaBuilder builder;
private final List<Predicate> predicates = new ArrayList<>();
private JpaQueryParser(Root<ENTITY> root, CriteriaQuery<?> query, CriteriaBuilder builder) { private JpaQueryParser(Query.Queryable queryable, List<Predicate> predicates, Root<ENTITY> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
super(queryable, predicates);
this.root = root; this.root = root;
this.query = query; this.query = query;
this.builder = builder; this.builder = builder;
@@ -434,17 +438,17 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
} }
@Override @Override
protected void nullEqual(Query.Queryable queryable) { protected void nullEqual(Query.Queryable queryable, List<Predicate> predicates) {
queryable.nullEqual().forEach(column -> predicates.add(builder.isNull(column(root, column)))); queryable.nullEqual().forEach(column -> predicates.add(builder.isNull(column(root, column))));
} }
@Override @Override
protected void notNullEqual(Query.Queryable queryable) { protected void notNullEqual(Query.Queryable queryable, List<Predicate> predicates) {
queryable.notNullEqual().forEach(column -> predicates.add(builder.isNotNull(column(root, column)))); queryable.notNullEqual().forEach(column -> predicates.add(builder.isNotNull(column(root, column))));
} }
@Override @Override
protected void empty(Query.Queryable queryable) { protected void empty(Query.Queryable queryable, List<Predicate> predicates) {
queryable.empty().forEach(column -> { queryable.empty().forEach(column -> {
var path = this.<Collection<Object>>column(root, column); var path = this.<Collection<Object>>column(root, column);
checkCollection(path, column); checkCollection(path, column);
@@ -453,7 +457,7 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
} }
@Override @Override
protected void notEmpty(Query.Queryable queryable) { protected void notEmpty(Query.Queryable queryable, List<Predicate> predicates) {
queryable.notEmpty().forEach(column -> { queryable.notEmpty().forEach(column -> {
var path = this.<Collection<Object>>column(root, column); var path = this.<Collection<Object>>column(root, column);
checkCollection(path, column); checkCollection(path, column);
@@ -462,7 +466,7 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
} }
@Override @Override
protected void equal(Query.Queryable queryable) { protected void equal(Query.Queryable queryable, List<Predicate> predicates) {
queryable.equal().forEach((column, value) -> { queryable.equal().forEach((column, value) -> {
var path = column(root, column); var path = column(root, column);
predicates.add(builder.equal(path, value(path, value))); predicates.add(builder.equal(path, value(path, value)));
@@ -470,7 +474,7 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
} }
@Override @Override
protected void notEqual(Query.Queryable queryable) { protected void notEqual(Query.Queryable queryable, List<Predicate> predicates) {
queryable.notEqual().forEach((column, value) -> { queryable.notEqual().forEach((column, value) -> {
var path = column(root, column); var path = column(root, column);
predicates.add(builder.notEqual(path, value(path, value))); predicates.add(builder.notEqual(path, value(path, value)));
@@ -478,7 +482,7 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
} }
@Override @Override
protected void like(Query.Queryable queryable) { protected void like(Query.Queryable queryable, List<Predicate> predicates) {
queryable.like().forEach((column, value) -> { queryable.like().forEach((column, value) -> {
var path = this.<String>column(root, column); var path = this.<String>column(root, column);
checkString(path, value, column); checkString(path, value, column);
@@ -487,7 +491,7 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
} }
@Override @Override
protected void notLike(Query.Queryable queryable) { protected void notLike(Query.Queryable queryable, List<Predicate> predicates) {
queryable.notLike().forEach((column, value) -> { queryable.notLike().forEach((column, value) -> {
var path = this.<String>column(root, column); var path = this.<String>column(root, column);
checkString(path, value, column); checkString(path, value, column);
@@ -496,7 +500,7 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
} }
@Override @Override
protected void contain(Query.Queryable queryable) { protected void contain(Query.Queryable queryable, List<Predicate> predicates) {
queryable.contain().forEach((column, value) -> { queryable.contain().forEach((column, value) -> {
var path = this.<String>column(root, column); var path = this.<String>column(root, column);
checkString(path, value, column); checkString(path, value, column);
@@ -505,7 +509,7 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
} }
@Override @Override
protected void notContain(Query.Queryable queryable) { protected void notContain(Query.Queryable queryable, List<Predicate> predicates) {
queryable.notContain().forEach((column, value) -> { queryable.notContain().forEach((column, value) -> {
var path = this.<String>column(root, column); var path = this.<String>column(root, column);
checkString(path, value, column); checkString(path, value, column);
@@ -514,7 +518,7 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
} }
@Override @Override
protected void startWith(Query.Queryable queryable) { protected void startWith(Query.Queryable queryable, List<Predicate> predicates) {
queryable.startWith().forEach((column, value) -> { queryable.startWith().forEach((column, value) -> {
var path = this.<String>column(root, column); var path = this.<String>column(root, column);
checkString(path, value, column); checkString(path, value, column);
@@ -523,7 +527,7 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
} }
@Override @Override
protected void notStartWith(Query.Queryable queryable) { protected void notStartWith(Query.Queryable queryable, List<Predicate> predicates) {
queryable.notStartWith().forEach((column, value) -> { queryable.notStartWith().forEach((column, value) -> {
var path = this.<String>column(root, column); var path = this.<String>column(root, column);
checkString(path, value, column); checkString(path, value, column);
@@ -532,7 +536,7 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
} }
@Override @Override
protected void endWith(Query.Queryable queryable) { protected void endWith(Query.Queryable queryable, List<Predicate> predicates) {
queryable.endWith().forEach((column, value) -> { queryable.endWith().forEach((column, value) -> {
var path = this.<String>column(root, column); var path = this.<String>column(root, column);
checkString(path, value, column); checkString(path, value, column);
@@ -541,7 +545,7 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
} }
@Override @Override
protected void notEndWith(Query.Queryable queryable) { protected void notEndWith(Query.Queryable queryable, List<Predicate> predicates) {
queryable.notEndWith().forEach((column, value) -> { queryable.notEndWith().forEach((column, value) -> {
var path = this.<String>column(root, column); var path = this.<String>column(root, column);
checkString(path, value, column); checkString(path, value, column);
@@ -550,7 +554,7 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
} }
@Override @Override
protected void great(Query.Queryable queryable) { protected void great(Query.Queryable queryable, List<Predicate> predicates) {
queryable.great().forEach((column, value) -> { queryable.great().forEach((column, value) -> {
var path = this.<Comparable<Object>>column(root, column); var path = this.<Comparable<Object>>column(root, column);
checkComparable(path, value, column); checkComparable(path, value, column);
@@ -559,7 +563,7 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
} }
@Override @Override
protected void less(Query.Queryable queryable) { protected void less(Query.Queryable queryable, List<Predicate> predicates) {
queryable.less().forEach((column, value) -> { queryable.less().forEach((column, value) -> {
var path = this.<Comparable<Object>>column(root, column); var path = this.<Comparable<Object>>column(root, column);
checkComparable(path, value, column); checkComparable(path, value, column);
@@ -568,7 +572,7 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
} }
@Override @Override
protected void greatEqual(Query.Queryable queryable) { protected void greatEqual(Query.Queryable queryable, List<Predicate> predicates) {
queryable.greatEqual().forEach((column, value) -> { queryable.greatEqual().forEach((column, value) -> {
var path = this.<Comparable<Object>>column(root, column); var path = this.<Comparable<Object>>column(root, column);
checkComparable(path, value, column); checkComparable(path, value, column);
@@ -577,7 +581,7 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
} }
@Override @Override
protected void lessEqual(Query.Queryable queryable) { protected void lessEqual(Query.Queryable queryable, List<Predicate> predicates) {
queryable.lessEqual().forEach((column, value) -> { queryable.lessEqual().forEach((column, value) -> {
var path = this.<Comparable<Object>>column(root, column); var path = this.<Comparable<Object>>column(root, column);
checkComparable(path, value, column); checkComparable(path, value, column);
@@ -586,7 +590,7 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
} }
@Override @Override
protected void inside(Query.Queryable queryable) { protected void inside(Query.Queryable queryable, List<Predicate> predicates) {
queryable.inside() queryable.inside()
.entrySet() .entrySet()
.stream() .stream()
@@ -595,7 +599,7 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
} }
@Override @Override
protected void notInside(Query.Queryable queryable) { protected void notInside(Query.Queryable queryable, List<Predicate> predicates) {
queryable.notInside() queryable.notInside()
.entrySet() .entrySet()
.stream() .stream()
@@ -604,7 +608,7 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
} }
@Override @Override
protected void between(Query.Queryable queryable) { protected void between(Query.Queryable queryable, List<Predicate> predicates) {
queryable.between().forEach((column, value) -> { queryable.between().forEach((column, value) -> {
var path = this.<Comparable<Object>>column(root, column); var path = this.<Comparable<Object>>column(root, column);
checkComparable(path, value, column); checkComparable(path, value, column);
@@ -613,19 +617,12 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
} }
@Override @Override
protected void notBetween(Query.Queryable queryable) { protected void notBetween(Query.Queryable queryable, List<Predicate> predicates) {
queryable.notBetween().forEach((column, value) -> { queryable.notBetween().forEach((column, value) -> {
var path = this.<Comparable<Object>>column(root, column); var path = this.<Comparable<Object>>column(root, column);
checkComparable(path, value, column); checkComparable(path, value, column);
predicates.add(builder.between(path, (Comparable<Object>) value(path, value.start()), (Comparable<Object>) value(path, value.end())).not()); predicates.add(builder.between(path, (Comparable<Object>) value(path, value.start()), (Comparable<Object>) value(path, value.end())).not());
}); });
} }
@Override
protected Predicate build() {
return predicates.size() == 1
? predicates.get(0)
: builder.and(predicates.toArray(Predicate[]::new));
}
} }
} }

View File

@@ -0,0 +1,185 @@
package com.lanyuanxiaoyao.service.template.database.jpa;
import com.blinkfox.fenix.EnableFenix;
import com.lanyuanxiaoyao.service.template.database.common.test.AbstractTestApplication;
import com.lanyuanxiaoyao.service.template.database.common.test.entity.Industry;
import com.lanyuanxiaoyao.service.template.database.common.test.entity.Level;
import com.lanyuanxiaoyao.service.template.database.jpa.entity.Company;
import com.lanyuanxiaoyao.service.template.database.jpa.entity.Company_;
import com.lanyuanxiaoyao.service.template.database.jpa.entity.Employee;
import com.lanyuanxiaoyao.service.template.database.jpa.entity.Employee_;
import com.lanyuanxiaoyao.service.template.database.jpa.entity.QEmployee;
import com.lanyuanxiaoyao.service.template.database.jpa.entity.Report;
import com.lanyuanxiaoyao.service.template.database.jpa.repository.CompanyRepository;
import com.lanyuanxiaoyao.service.template.database.jpa.repository.EmployeeRepository;
import com.lanyuanxiaoyao.service.template.database.jpa.repository.ReportRepository;
import java.util.List;
import java.util.Map;
import java.util.Set;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.util.Assert;
@Slf4j
@RequiredArgsConstructor
@SpringBootApplication
@EnableFenix
@EnableJpaAuditing
public class TestApplication extends AbstractTestApplication {
private final CompanyRepository companyRepository;
private final EmployeeRepository employeeRepository;
private final ReportRepository reportRepository;
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
@EventListener(ApplicationReadyEvent.class)
public void runTests() {
testCrud();
testDelete();
testSpecification();
testNative();
System.exit(0);
}
private void testDelete() {
formatLog("Delete JPA");
saveItem("company", randomCompany());
saveItem("company", randomCompany());
companyRepository.deleteAll();
formatLog("Delete JPA Batch");
saveItem("company", randomCompany());
saveItem("company", randomCompany());
companyRepository.deleteAllInBatch();
formatLog("Delete JPA by id");
var cid1 = saveItem("company", randomCompany()).get("data").asLong();
var cid2 = saveItem("company", randomCompany()).get("data").asLong();
companyRepository.deleteAllById(List.of(cid1, cid2));
formatLog("Delete Fenix by id");
cid1 = saveItem("company", randomCompany()).get("data").asLong();
cid2 = saveItem("company", randomCompany()).get("data").asLong();
companyRepository.deleteByIds(List.of(cid1, cid2));
formatLog("Delete Fenix Batch by id");
cid1 = saveItem("company", randomCompany()).get("data").asLong();
cid2 = saveItem("company", randomCompany()).get("data").asLong();
companyRepository.deleteBatchByIds(List.of(cid1, cid2));
}
private void testSpecification() {
formatLog("Added");
var company1 = companyRepository.save(Company.builder().name(randomString(5)).members(randomInt(100)).industries(Set.of(Industry.MEDIA, Industry.SERVICE)).build());
var company2 = companyRepository.save(Company.builder().name(randomString(5)).members(randomInt(100)).industries(Set.of(Industry.MEDIA, Industry.SERVICE)).build());
var employee1 = employeeRepository.save(
Employee.builder()
.name("Tom")
.age(randomInt(100))
.role(Employee.Role.USER)
.company(company1)
.connections(Map.of(
Employee.ConnectionType.ADDRESS, randomString(50),
Employee.ConnectionType.EMAIL, randomString(20)
))
.build()
);
var employee2 = employeeRepository.save(
Employee.builder()
.name(randomString(10))
.age(randomInt(100))
.role(Employee.Role.USER)
.company(company2)
.connections(Map.of(
Employee.ConnectionType.ADDRESS, randomString(50),
Employee.ConnectionType.EMAIL, randomString(20)
))
.build()
);
var report1 = reportRepository.save(Report.builder().score(randomDouble(50)).level(Level.B).employeeId(employee1.getId()).build());
var report2 = reportRepository.save(Report.builder().score(randomDouble(50)).level(Level.E).employeeId(employee2.getId()).build());
formatLog("Query");
var employees1 = 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, 200)
.andGreaterThanEqual(Employee.Fields.age, 0)
.andIn(Employee.Fields.name, List.of("Tom", "Mike"))
.andBetween(Employee.Fields.age, 0, 200)
.build()
);
Assert.isTrue(employees1.size() == 1, "查询数量错误");
var employees2 = 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), 200),
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, 200),
builder.isNotEmpty(root.get(Employee_.company).get(Company_.employees)),
builder.isMember(Industry.MEDIA, root.get(Employee_.company).get(Company_.industries))
)
);
Assert.isTrue(employees2.size() == 1, "查询数量错误");
var employees3 = 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(200))
.and(QEmployee.employee.age.goe(0))
.and(QEmployee.employee.name.in("Tom", "Mike"))
.and(QEmployee.employee.age.between(0, 200))
.and(QEmployee.employee.company().employees.isNotEmpty())
.and(QEmployee.employee.company().industries.contains(Industry.MEDIA))
.and(QEmployee.employee.connections.containsKey(Employee.ConnectionType.EMAIL))
);
Assert.isTrue(employees3.size() == 1, "查询数量错误");
formatLog("Clean");
reportRepository.deleteAllInBatch();
employeeRepository.deleteAllInBatch();
companyRepository.deleteAllInBatch();
}
private void testNative() {
formatLog("Added");
var company1 = companyRepository.save(Company.builder().name(randomString(5)).members(randomInt(100)).build());
var company2 = companyRepository.save(Company.builder().name(randomString(5)).members(randomInt(100)).build());
var company3 = companyRepository.save(Company.builder().name(randomString(5)).members(randomInt(100)).build());
var employee1 = employeeRepository.save(Employee.builder().name(randomString(10)).age(randomInt(100)).role(Employee.Role.USER).company(company1).build());
var employee2 = employeeRepository.save(Employee.builder().name(randomString(10)).age(randomInt(100)).role(Employee.Role.USER).company(company2).build());
var employee3 = employeeRepository.save(Employee.builder().name(randomString(10)).age(randomInt(100)).role(Employee.Role.USER).company(company3).build());
formatLog("HQL Query");
var list = employeeRepository.findAllEmployeeWithCompanyName();
Assert.isTrue(list.size() == 3, "数量错误");
formatLog("SQL Query");
var list_native = employeeRepository.findAllEmployeeWithCompanyNameNative();
Assert.isTrue(list_native.size() == 3, "数量错误");
formatLog("Clean");
employeeRepository.deleteAllInBatch();
companyRepository.deleteAllInBatch();
}
}

View File

@@ -1,7 +1,7 @@
package com.lanyuanxiaoyao.service.template.jpa.controller; package com.lanyuanxiaoyao.service.template.database.jpa.controller;
import com.lanyuanxiaoyao.service.template.jpa.entity.Company; import com.lanyuanxiaoyao.service.template.database.jpa.entity.Company;
import com.lanyuanxiaoyao.service.template.jpa.service.CompanyService; import com.lanyuanxiaoyao.service.template.database.jpa.service.CompanyService;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.function.Function; import java.util.function.Function;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;

View File

@@ -1,8 +1,8 @@
package com.lanyuanxiaoyao.service.template.jpa.controller; package com.lanyuanxiaoyao.service.template.database.jpa.controller;
import com.lanyuanxiaoyao.service.template.jpa.entity.Employee; import com.lanyuanxiaoyao.service.template.database.jpa.entity.Employee;
import com.lanyuanxiaoyao.service.template.jpa.service.CompanyService; import com.lanyuanxiaoyao.service.template.database.jpa.service.CompanyService;
import com.lanyuanxiaoyao.service.template.jpa.service.EmployeeService; import com.lanyuanxiaoyao.service.template.database.jpa.service.EmployeeService;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.function.Function; import java.util.function.Function;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;

View File

@@ -1,8 +1,9 @@
package com.lanyuanxiaoyao.service.template.jpa.controller; package com.lanyuanxiaoyao.service.template.database.jpa.controller;
import com.lanyuanxiaoyao.service.template.jpa.entity.Report; import com.lanyuanxiaoyao.service.template.database.common.test.entity.Level;
import com.lanyuanxiaoyao.service.template.jpa.service.EmployeeService; import com.lanyuanxiaoyao.service.template.database.jpa.entity.Report;
import com.lanyuanxiaoyao.service.template.jpa.service.ReportService; import com.lanyuanxiaoyao.service.template.database.jpa.service.EmployeeService;
import com.lanyuanxiaoyao.service.template.database.jpa.service.ReportService;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.function.Function; import java.util.function.Function;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
@@ -63,7 +64,7 @@ public class ReportController extends SimpleControllerSupport<Report, ReportCont
public record SaveItem( public record SaveItem(
Long id, Long id,
Double score, Double score,
Report.Level level, Level level,
Long employeeId Long employeeId
) { ) {
} }
@@ -73,7 +74,7 @@ public class ReportController extends SimpleControllerSupport<Report, ReportCont
Long employeeId, Long employeeId,
String employeeName, String employeeName,
Double score, Double score,
Report.Level level Level level
) { ) {
} }
@@ -82,7 +83,7 @@ public class ReportController extends SimpleControllerSupport<Report, ReportCont
Long employeeId, Long employeeId,
String employeeName, String employeeName,
Double score, Double score,
Report.Level level, Level level,
LocalDateTime createdTime, LocalDateTime createdTime,
LocalDateTime modifiedTime LocalDateTime modifiedTime
) { ) {

View File

@@ -1,5 +1,6 @@
package com.lanyuanxiaoyao.service.template.jpa.entity; package com.lanyuanxiaoyao.service.template.database.jpa.entity;
import com.lanyuanxiaoyao.service.template.database.common.test.entity.Industry;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.persistence.ConstraintMode; import jakarta.persistence.ConstraintMode;
import jakarta.persistence.ElementCollection; import jakarta.persistence.ElementCollection;
@@ -13,7 +14,10 @@ import jakarta.persistence.OneToMany;
import jakarta.persistence.Table; import jakarta.persistence.Table;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter; import lombok.Setter;
import lombok.ToString; import lombok.ToString;
import lombok.experimental.FieldNameConstants; import lombok.experimental.FieldNameConstants;
@@ -26,6 +30,9 @@ import org.springframework.data.jpa.domain.support.AuditingEntityListener;
@Getter @Getter
@ToString(callSuper = true) @ToString(callSuper = true)
@FieldNameConstants @FieldNameConstants
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Entity @Entity
@SoftDelete @SoftDelete
@DynamicUpdate @DynamicUpdate
@@ -38,26 +45,13 @@ public class Company extends SimpleEntity {
@Column(nullable = false, comment = "成员数") @Column(nullable = false, comment = "成员数")
private Integer members; private Integer members;
@OneToMany(mappedBy = "company")
@ToString.Exclude
private Set<Employee> employees;
@ElementCollection @ElementCollection
@JoinTable(foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT), inverseForeignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT)) @JoinTable(foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT), inverseForeignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@Column(nullable = false) @Column(nullable = false)
private Set<Industry> industries = new HashSet<>(); private Set<Industry> industries = new HashSet<>();
public enum Industry { @OneToMany(mappedBy = "company")
TECHNOLOGY, @ToString.Exclude
FINANCE, private Set<Employee> employees;
MEDIA,
SERVICE,
GOVERNMENT,
EDUCATION,
HEALTHCARE,
CONSTRUCTION,
RETAIL,
OTHER,
}
} }

View File

@@ -1,4 +1,4 @@
package com.lanyuanxiaoyao.service.template.jpa.entity; package com.lanyuanxiaoyao.service.template.database.jpa.entity;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.persistence.ConstraintMode; import jakarta.persistence.ConstraintMode;
@@ -15,7 +15,10 @@ import jakarta.persistence.MapKeyEnumerated;
import jakarta.persistence.Table; import jakarta.persistence.Table;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter; import lombok.Setter;
import lombok.ToString; import lombok.ToString;
import lombok.experimental.FieldNameConstants; import lombok.experimental.FieldNameConstants;
@@ -28,6 +31,9 @@ import org.springframework.data.jpa.domain.support.AuditingEntityListener;
@Getter @Getter
@ToString(callSuper = true) @ToString(callSuper = true)
@FieldNameConstants @FieldNameConstants
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Entity @Entity
@SoftDelete @SoftDelete
@DynamicUpdate @DynamicUpdate

View File

@@ -1,12 +1,16 @@
package com.lanyuanxiaoyao.service.template.jpa.entity; package com.lanyuanxiaoyao.service.template.database.jpa.entity;
import com.lanyuanxiaoyao.service.template.database.common.test.entity.Level;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.persistence.Entity; import jakarta.persistence.Entity;
import jakarta.persistence.EntityListeners; import jakarta.persistence.EntityListeners;
import jakarta.persistence.EnumType; import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated; import jakarta.persistence.Enumerated;
import jakarta.persistence.Table; import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter; import lombok.Setter;
import lombok.ToString; import lombok.ToString;
import lombok.experimental.FieldNameConstants; import lombok.experimental.FieldNameConstants;
@@ -19,6 +23,9 @@ import org.springframework.data.jpa.domain.support.AuditingEntityListener;
@Getter @Getter
@ToString(callSuper = true) @ToString(callSuper = true)
@FieldNameConstants @FieldNameConstants
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Entity @Entity
@SoftDelete @SoftDelete
@DynamicUpdate @DynamicUpdate
@@ -34,8 +41,4 @@ public class Report extends SimpleEntity {
@Column(nullable = false, comment = "员工 ID") @Column(nullable = false, comment = "员工 ID")
private Long employeeId; private Long employeeId;
public enum Level {
A, B, C, D, E
}
} }

View File

@@ -1,4 +1,4 @@
package com.lanyuanxiaoyao.service.template.jpa.entity.vo; package com.lanyuanxiaoyao.service.template.database.jpa.entity.vo;
public record EmployeeWithCompanyName( public record EmployeeWithCompanyName(
String name, String name,

View File

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

View File

@@ -1,7 +1,7 @@
package com.lanyuanxiaoyao.service.template.jpa.repository; package com.lanyuanxiaoyao.service.template.database.jpa.repository;
import com.lanyuanxiaoyao.service.template.jpa.entity.Employee; import com.lanyuanxiaoyao.service.template.database.jpa.entity.Employee;
import com.lanyuanxiaoyao.service.template.jpa.entity.vo.EmployeeWithCompanyName; import com.lanyuanxiaoyao.service.template.database.jpa.entity.vo.EmployeeWithCompanyName;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import org.springframework.data.jpa.domain.Specification; import org.springframework.data.jpa.domain.Specification;
@@ -16,9 +16,9 @@ public interface EmployeeRepository extends SimpleRepository<Employee> {
@Override @Override
Optional<Employee> findOne(Specification<Employee> specification); Optional<Employee> findOne(Specification<Employee> specification);
@Query(value = "select e.name, c.name, e.age, e.role from employee e, company c where e.company_id = c.id", nativeQuery = true) @Query(value = "select e.name, c.name, e.age, e.role from employee e, company c where e.company_id = c.id and c.deleted = false and e.deleted = false", nativeQuery = true)
List<EmployeeWithCompanyName> findAllEmployeeWithCompanyNameNative(); List<EmployeeWithCompanyName> findAllEmployeeWithCompanyNameNative();
@Query("select new com.lanyuanxiaoyao.service.template.jpa.entity.vo.EmployeeWithCompanyName(employee.name, employee.company.name, employee.age, cast(employee.role as string)) from Employee employee") @Query("select new com.lanyuanxiaoyao.service.template.database.jpa.entity.vo.EmployeeWithCompanyName(employee.name, employee.company.name, employee.age, cast(employee.role as string)) from Employee employee")
List<EmployeeWithCompanyName> findAllEmployeeWithCompanyName(); List<EmployeeWithCompanyName> findAllEmployeeWithCompanyName();
} }

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,12 @@
spring:
profiles:
include: test
datasource:
url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1
username: test
password: test
driver-class-name: org.h2.Driver
jpa:
generate-ddl: true
fenix:
print-banner: false

View File

@@ -9,12 +9,12 @@
<version>1.1.0-SNAPSHOT</version> <version>1.1.0-SNAPSHOT</version>
</parent> </parent>
<artifactId>spring-boot-service-template-xbatis</artifactId> <artifactId>spring-boot-service-template-database-xbatis</artifactId>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>com.lanyuanxiaoyao</groupId> <groupId>com.lanyuanxiaoyao</groupId>
<artifactId>spring-boot-service-template-common</artifactId> <artifactId>spring-boot-service-template-database-common</artifactId>
</dependency> </dependency>
<dependency> <dependency>
@@ -22,30 +22,15 @@
<artifactId>spring-boot-starter-web</artifactId> <artifactId>spring-boot-starter-web</artifactId>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>cn.xbatis</groupId> <groupId>cn.xbatis</groupId>
<artifactId>xbatis-spring-boot-starter</artifactId> <artifactId>xbatis-spring-boot-starter</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.github.gavlyukovskiy</groupId> <groupId>com.lanyuanxiaoyao</groupId>
<artifactId>p6spy-spring-boot-starter</artifactId> <artifactId>spring-boot-service-template-database-common-test</artifactId>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.jspecify</groupId>
<artifactId>jspecify</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
</dependencies> </dependencies>

View File

@@ -1,9 +1,9 @@
package com.lanyuanxiaoyao.service.template.xbatis.configuration; package com.lanyuanxiaoyao.service.template.database.xbatis.configuration;
import cn.xbatis.core.incrementer.GeneratorFactory; import cn.xbatis.core.incrementer.GeneratorFactory;
import cn.xbatis.core.mybatis.mapper.BasicMapper; import cn.xbatis.core.mybatis.mapper.BasicMapper;
import com.lanyuanxiaoyao.service.template.xbatis.entity.SnowflakeIdGenerator; import com.lanyuanxiaoyao.service.template.database.xbatis.entity.SnowflakeIdGenerator;
import com.lanyuanxiaoyao.service.template.xbatis.mapper.MybatisBasicMapper; import com.lanyuanxiaoyao.service.template.database.xbatis.mapper.MybatisBasicMapper;
import org.mybatis.spring.annotation.MapperScan; import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;

View File

@@ -1,11 +1,11 @@
package com.lanyuanxiaoyao.service.template.jpa.controller; package com.lanyuanxiaoyao.service.template.database.xbatis.controller;
import com.lanyuanxiaoyao.service.template.common.controller.SimpleController;
import com.lanyuanxiaoyao.service.template.common.entity.GlobalResponse;
import com.lanyuanxiaoyao.service.template.common.entity.Query;
import com.lanyuanxiaoyao.service.template.common.helper.ObjectHelper; import com.lanyuanxiaoyao.service.template.common.helper.ObjectHelper;
import com.lanyuanxiaoyao.service.template.jpa.entity.SimpleEntity; import com.lanyuanxiaoyao.service.template.database.common.controller.SimpleController;
import com.lanyuanxiaoyao.service.template.jpa.service.SimpleServiceSupport; import com.lanyuanxiaoyao.service.template.database.common.entity.GlobalResponse;
import com.lanyuanxiaoyao.service.template.database.common.entity.Query;
import com.lanyuanxiaoyao.service.template.database.xbatis.entity.SimpleEntity;
import com.lanyuanxiaoyao.service.template.database.xbatis.service.SimpleServiceSupport;
import java.util.function.Function; import java.util.function.Function;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;

View File

@@ -1,4 +1,4 @@
package com.lanyuanxiaoyao.service.template.xbatis.entity; package com.lanyuanxiaoyao.service.template.database.xbatis.entity;
import cn.xbatis.db.IdAutoType; import cn.xbatis.db.IdAutoType;
import cn.xbatis.db.annotations.TableId; import cn.xbatis.db.annotations.TableId;

View File

@@ -0,0 +1,20 @@
package com.lanyuanxiaoyao.service.template.database.xbatis.entity;
import cn.xbatis.db.annotations.LogicDelete;
import cn.xbatis.db.annotations.LogicDeleteTime;
import java.time.LocalDateTime;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.FieldNameConstants;
@Getter
@Setter
@ToString
@FieldNameConstants
public class LogicDeleteEntity extends IdOnlyEntity {
@LogicDelete
private Boolean deleted = false;
@LogicDeleteTime
private LocalDateTime deletedTime;
}

View File

@@ -1,4 +1,4 @@
package com.lanyuanxiaoyao.service.template.xbatis.entity; package com.lanyuanxiaoyao.service.template.database.xbatis.entity;
import cn.xbatis.db.annotations.TableField; import cn.xbatis.db.annotations.TableField;
import java.time.LocalDateTime; import java.time.LocalDateTime;
@@ -11,7 +11,7 @@ import lombok.experimental.FieldNameConstants;
@Setter @Setter
@ToString(callSuper = true) @ToString(callSuper = true)
@FieldNameConstants @FieldNameConstants
public class SimpleEntity extends IdOnlyEntity { public class SimpleEntity extends LogicDeleteEntity {
@TableField(defaultValue = "{NOW}", defaultValueFillAlways = true) @TableField(defaultValue = "{NOW}", defaultValueFillAlways = true)
private LocalDateTime createdTime; private LocalDateTime createdTime;
@TableField(defaultValue = "{NOW}", defaultValueFillAlways = true, updateDefaultValue = "{NOW}", updateDefaultValueFillAlways = true) @TableField(defaultValue = "{NOW}", defaultValueFillAlways = true, updateDefaultValue = "{NOW}", updateDefaultValueFillAlways = true)

View File

@@ -1,7 +1,7 @@
package com.lanyuanxiaoyao.service.template.xbatis.entity; package com.lanyuanxiaoyao.service.template.database.xbatis.entity;
import cn.xbatis.core.incrementer.Generator; import cn.xbatis.core.incrementer.Generator;
import com.lanyuanxiaoyao.service.template.common.helper.SnowflakeHelper; import com.lanyuanxiaoyao.service.template.database.common.helper.SnowflakeHelper;
public class SnowflakeIdGenerator implements Generator<Long> { public class SnowflakeIdGenerator implements Generator<Long> {
@Override @Override

View File

@@ -1,4 +1,4 @@
package com.lanyuanxiaoyao.service.template.xbatis.mapper; package com.lanyuanxiaoyao.service.template.database.xbatis.mapper;
import cn.xbatis.core.mybatis.mapper.BasicMapper; import cn.xbatis.core.mybatis.mapper.BasicMapper;

View File

@@ -1,16 +1,16 @@
package com.lanyuanxiaoyao.service.template.xbatis.service; package com.lanyuanxiaoyao.service.template.database.xbatis.service;
import cn.xbatis.core.mybatis.mapper.context.Pager; import cn.xbatis.core.mybatis.mapper.context.Pager;
import cn.xbatis.core.sql.MybatisCmdFactory; import cn.xbatis.core.sql.MybatisCmdFactory;
import cn.xbatis.core.sql.executor.chain.QueryChain; import cn.xbatis.core.sql.executor.chain.QueryChain;
import com.lanyuanxiaoyao.service.template.common.entity.Page;
import com.lanyuanxiaoyao.service.template.common.entity.Query;
import com.lanyuanxiaoyao.service.template.common.exception.IdNotFoundException;
import com.lanyuanxiaoyao.service.template.common.helper.ObjectHelper; import com.lanyuanxiaoyao.service.template.common.helper.ObjectHelper;
import com.lanyuanxiaoyao.service.template.common.service.QueryParser; import com.lanyuanxiaoyao.service.template.database.common.entity.Page;
import com.lanyuanxiaoyao.service.template.common.service.SimpleService; import com.lanyuanxiaoyao.service.template.database.common.entity.Query;
import com.lanyuanxiaoyao.service.template.xbatis.entity.SimpleEntity; import com.lanyuanxiaoyao.service.template.database.common.exception.IdNotFoundException;
import com.lanyuanxiaoyao.service.template.xbatis.mapper.MybatisBasicMapper; import com.lanyuanxiaoyao.service.template.database.common.service.QueryParser;
import com.lanyuanxiaoyao.service.template.database.common.service.SimpleService;
import com.lanyuanxiaoyao.service.template.database.xbatis.entity.SimpleEntity;
import com.lanyuanxiaoyao.service.template.database.xbatis.mapper.MybatisBasicMapper;
import db.sql.api.cmd.LikeMode; import db.sql.api.cmd.LikeMode;
import db.sql.api.impl.cmd.basic.OrderByDirection; import db.sql.api.impl.cmd.basic.OrderByDirection;
import db.sql.api.impl.cmd.struct.Where; import db.sql.api.impl.cmd.struct.Where;
@@ -18,6 +18,8 @@ import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.mapstruct.Named;
import org.springframework.transaction.annotation.Transactional;
@Slf4j @Slf4j
public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implements SimpleService<ENTITY> { public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implements SimpleService<ENTITY> {
@@ -32,14 +34,17 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
this.mapper = mapper; this.mapper = mapper;
} }
@Transactional(rollbackFor = Throwable.class)
@Override @Override
public Long save(ENTITY entity) { public Long save(ENTITY entity) {
return (long) mapper.save(entity); mapper.saveOrUpdate(entity);
return entity.getId();
} }
@Transactional(rollbackFor = Throwable.class)
@Override @Override
public void save(Iterable<ENTITY> entities) { public void save(Iterable<ENTITY> entities) {
mapper.save(entities); mapper.saveOrUpdate(entities);
} }
@Override @Override
@@ -71,7 +76,6 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
var size = Math.max(ObjectHelper.defaultIfNull(query.page().size(), DEFAULT_PAGE_SIZE), 1); var size = Math.max(ObjectHelper.defaultIfNull(query.page().size(), DEFAULT_PAGE_SIZE), 1);
paging = Pager.of(index, size); paging = Pager.of(index, size);
} }
chain.paging(paging);
if (ObjectHelper.isNotEmpty(query.sort())) { if (ObjectHelper.isNotEmpty(query.sort())) {
query.sort().forEach(sort -> chain.orderBy(OrderByDirection.valueOf(sort.direction().name()), sort.column())); query.sort().forEach(sort -> chain.orderBy(OrderByDirection.valueOf(sort.direction().name()), sort.column()));
@@ -79,141 +83,146 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
var where = chain.where(); var where = chain.where();
commonPredicates(where); commonPredicates(where);
new XBatisQueryParser<>(target, factory, where).build(query.query()); new XBatisQueryParser<>(query.query(), where, target, factory).build();
return new Page<>(chain.list(), chain.count()); var pager = chain.paging(paging);
return new Page<>(pager.getResults(), pager.getTotal());
} }
private Optional<ENTITY> detailOptional(Long id) { private Optional<ENTITY> detailOptional(Long id) {
if (ObjectHelper.isNull(id)) { if (ObjectHelper.isNull(id)) {
return Optional.empty(); return Optional.empty();
} }
return Optional.ofNullable(mapper.getById(target, id)); return mapper.getOptionalById(target, id);
} }
@Named("detail")
@Override @Override
public ENTITY detail(Long id) { public ENTITY detail(Long id) {
return detailOptional(id).orElse(null); return detailOptional(id).orElse(null);
} }
@Named("detailOrThrow")
@Override @Override
public ENTITY detailOrThrow(Long id) { public ENTITY detailOrThrow(Long id) {
return detailOptional(id).orElseThrow(() -> new IdNotFoundException(id)); return detailOptional(id).orElseThrow(() -> new IdNotFoundException(id));
} }
@Transactional(rollbackFor = Throwable.class)
@Override @Override
public void remove(Long id) { public void remove(Long id) {
mapper.deleteById(target, id); mapper.deleteById(target, id);
} }
@Transactional(rollbackFor = Throwable.class)
@Override @Override
public void remove(Set<Long> ids) { public void remove(Set<Long> ids) {
mapper.deleteByIds(target, ids); mapper.deleteByIds(target, ids);
} }
private static final class XBatisQueryParser<ENTITY> extends QueryParser<Void> { private static final class XBatisQueryParser<ENTITY> extends QueryParser<Where> {
private final Class<ENTITY> target; private final Class<ENTITY> target;
private final MybatisCmdFactory factory; private final MybatisCmdFactory factory;
private final Where where;
private XBatisQueryParser(Class<ENTITY> target, MybatisCmdFactory factory, Where where) { private XBatisQueryParser(Query.Queryable queryable, Where where, Class<ENTITY> target, MybatisCmdFactory factory) {
super(queryable, where);
this.target = target; this.target = target;
this.factory = factory; this.factory = factory;
this.where = where;
} }
@Override @Override
protected void nullEqual(Query.Queryable queryable) { protected void nullEqual(Query.Queryable queryable, Where where) {
queryable.nullEqual().forEach(column -> where.isNull(factory.field(target, column))); queryable.nullEqual().forEach(column -> where.isNull(factory.field(target, column)));
} }
@Override @Override
protected void notNullEqual(Query.Queryable queryable) { protected void notNullEqual(Query.Queryable queryable, Where where) {
queryable.notNullEqual().forEach(column -> where.isNotNull(factory.field(target, column))); queryable.notNullEqual().forEach(column -> where.isNotNull(factory.field(target, column)));
} }
@Override @Override
protected void empty(Query.Queryable queryable) { protected void empty(Query.Queryable queryable, Where where) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@Override @Override
protected void notEmpty(Query.Queryable queryable) { protected void notEmpty(Query.Queryable queryable, Where where) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@Override @Override
protected void equal(Query.Queryable queryable) { protected void equal(Query.Queryable queryable, Where where) {
queryable.equal().forEach((column, value) -> where.eq(factory.field(target, column), value)); queryable.equal().forEach((column, value) -> where.eq(factory.field(target, column), value));
} }
@Override @Override
protected void notEqual(Query.Queryable queryable) { protected void notEqual(Query.Queryable queryable, Where where) {
queryable.notEqual().forEach((column, value) -> where.ne(factory.field(target, column), value)); queryable.notEqual().forEach((column, value) -> where.ne(factory.field(target, column), value));
} }
@Override @Override
protected void like(Query.Queryable queryable) { protected void like(Query.Queryable queryable, Where where) {
queryable.like().forEach((column, value) -> where.like(LikeMode.NONE, factory.field(target, column), value)); queryable.like().forEach((column, value) -> where.like(LikeMode.NONE, factory.field(target, column), value));
} }
@Override @Override
protected void notLike(Query.Queryable queryable) { protected void notLike(Query.Queryable queryable, Where where) {
queryable.notLike().forEach((column, value) -> where.notLike(LikeMode.NONE, factory.field(target, column), value)); queryable.notLike().forEach((column, value) -> where.notLike(LikeMode.NONE, factory.field(target, column), value));
} }
@Override @Override
protected void contain(Query.Queryable queryable) { protected void contain(Query.Queryable queryable, Where where) {
queryable.contain().forEach((column, value) -> where.like(factory.field(target, column), value)); queryable.contain().forEach((column, value) -> where.like(factory.field(target, column), value));
} }
@Override @Override
protected void notContain(Query.Queryable queryable) { protected void notContain(Query.Queryable queryable, Where where) {
queryable.notContain().forEach((column, value) -> where.notLike(factory.field(target, column), value)); queryable.notContain().forEach((column, value) -> where.notLike(factory.field(target, column), value));
} }
@Override @Override
protected void startWith(Query.Queryable queryable) { protected void startWith(Query.Queryable queryable, Where where) {
queryable.startWith().forEach((column, value) -> where.like(LikeMode.LEFT, factory.field(target, column), value)); queryable.startWith().forEach((column, value) -> where.like(LikeMode.RIGHT, factory.field(target, column), value));
} }
@Override @Override
protected void notStartWith(Query.Queryable queryable) { protected void notStartWith(Query.Queryable queryable, Where where) {
queryable.notStartWith().forEach((column, value) -> where.notLike(LikeMode.LEFT, factory.field(target, column), value)); queryable.notStartWith().forEach((column, value) -> where.notLike(LikeMode.RIGHT, factory.field(target, column), value));
} }
@Override @Override
protected void endWith(Query.Queryable queryable) { protected void endWith(Query.Queryable queryable, Where where) {
queryable.endWith().forEach((column, value) -> where.like(LikeMode.RIGHT, factory.field(target, column), value)); queryable.endWith().forEach((column, value) -> where.like(LikeMode.LEFT, factory.field(target, column), value));
} }
@Override @Override
protected void notEndWith(Query.Queryable queryable) { protected void notEndWith(Query.Queryable queryable, Where where) {
queryable.notEndWith().forEach((column, value) -> where.notLike(LikeMode.RIGHT, factory.field(target, column), value)); queryable.notEndWith().forEach((column, value) -> where.notLike(LikeMode.LEFT, factory.field(target, column), value));
} }
@Override @Override
protected void great(Query.Queryable queryable) { protected void great(Query.Queryable queryable, Where where) {
queryable.great().forEach((column, value) -> where.gt(factory.field(target, column), value)); queryable.great().forEach((column, value) -> where.gt(factory.field(target, column), value));
} }
@Override @Override
protected void less(Query.Queryable queryable) { protected void less(Query.Queryable queryable, Where where) {
queryable.less().forEach((column, value) -> where.lt(factory.field(target, column), value)); queryable.less().forEach((column, value) -> where.lt(factory.field(target, column), value));
} }
@Override @Override
protected void greatEqual(Query.Queryable queryable) { protected void greatEqual(Query.Queryable queryable, Where where) {
queryable.greatEqual().forEach((column, value) -> where.gte(factory.field(target, column), value)); queryable.greatEqual().forEach((column, value) -> where.gte(factory.field(target, column), value));
} }
@Override @Override
protected void lessEqual(Query.Queryable queryable) { protected void lessEqual(Query.Queryable queryable, Where where) {
queryable.lessEqual().forEach((column, value) -> where.lte(factory.field(target, column), value)); queryable.lessEqual().forEach((column, value) -> where.lte(factory.field(target, column), value));
} }
@Override @Override
protected void inside(Query.Queryable queryable) { protected void inside(Query.Queryable queryable, Where where) {
queryable.inside() queryable.inside()
.entrySet() .entrySet()
.stream() .stream()
@@ -222,7 +231,7 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
} }
@Override @Override
protected void notInside(Query.Queryable queryable) { protected void notInside(Query.Queryable queryable, Where where) {
queryable.notInside() queryable.notInside()
.entrySet() .entrySet()
.stream() .stream()
@@ -231,18 +240,13 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
} }
@Override @Override
protected void between(Query.Queryable queryable) { protected void between(Query.Queryable queryable, Where where) {
queryable.between().forEach((column, value) -> where.between(factory.field(target, column), value.start(), value.end())); queryable.between().forEach((column, value) -> where.between(factory.field(target, column), value.start(), value.end()));
} }
@Override @Override
protected void notBetween(Query.Queryable queryable) { protected void notBetween(Query.Queryable queryable, Where where) {
queryable.notBetween().forEach((column, value) -> where.notBetween(factory.field(target, column), value.start(), value.end())); queryable.notBetween().forEach((column, value) -> where.notBetween(factory.field(target, column), value.start(), value.end()));
} }
@Override
protected Void build() {
return null;
}
} }
} }

View File

@@ -4,7 +4,9 @@ create table if not exists Company
name varchar(255) not null, name varchar(255) not null,
members int not null, members int not null,
created_time timestamp not null, created_time timestamp not null,
modified_time timestamp not null modified_time timestamp not null,
deleted tinyint not null default 0,
deleted_time timestamp
); );
create table if not exists Employee create table if not exists Employee
@@ -12,6 +14,9 @@ create table if not exists Employee
id bigint primary key, id bigint primary key,
name varchar(255) not null, name varchar(255) not null,
age int not null, age int not null,
company_id bigint not null,
created_time timestamp not null, created_time timestamp not null,
modified_time timestamp not null modified_time timestamp not null,
deleted tinyint not null default 0,
deleted_time timestamp
); );

View File

@@ -0,0 +1,82 @@
package com.lanyuanxiaoyao.service.template.database.xbatis;
import cn.xbatis.core.sql.executor.chain.QueryChain;
import com.lanyuanxiaoyao.service.template.database.common.test.AbstractTestApplication;
import com.lanyuanxiaoyao.service.template.database.xbatis.entity.Company;
import com.lanyuanxiaoyao.service.template.database.xbatis.entity.Employee;
import com.lanyuanxiaoyao.service.template.database.xbatis.entity.vo.EmployeeWithCompanyName;
import com.lanyuanxiaoyao.service.template.database.xbatis.mapper.MybatisBasicMapper;
import db.sql.api.cmd.LikeMode;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.util.Assert;
@Slf4j
@RequiredArgsConstructor
@MapperScan("com.lanyuanxiaoyao.service.template.database.xbatis.mapper")
@SpringBootApplication
public class TestApplication extends AbstractTestApplication {
private final MybatisBasicMapper mapper;
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
@EventListener(ApplicationReadyEvent.class)
public void runTests() {
testCrud();
testDelete();
testQuery();
System.exit(0);
}
private void testDelete() {
formatLog("Delete");
saveItem("company", randomCompany());
saveItem("company", randomCompany());
mapper.deleteAll(Company.class);
}
private void testQuery() {
formatLog("Added");
var company1 = Company.builder().name(randomString(5)).members(randomInt(100)).build();
mapper.saveOrUpdate(company1);
var company2 = Company.builder().name(randomString(5)).members(randomInt(100)).build();
mapper.saveOrUpdate(company2);
var employee1 = Employee.builder().name("Tom").age(randomInt(100)).companyId(company1.getId()).build();
mapper.saveOrUpdate(employee1);
var employee2 = Employee.builder().name(randomString(10)).age(randomInt(100)).companyId(company2.getId()).build();
mapper.saveOrUpdate(employee2);
formatLog("Query");
var employees1 = QueryChain.of(mapper, Employee.class)
.isNotNull(Employee::getName)
.eq(Employee::getName, "Tom")
.like(Employee::getName, "To")
.like(LikeMode.RIGHT, Employee::getName, "To")
.like(LikeMode.LEFT, Employee::getName, "om")
.lt(Employee::getAge, 200)
.gt(Employee::getAge, 0)
.in(Employee::getName, "Tom", "Mike")
.between(Employee::getAge, 0, 200)
.list();
Assert.isTrue(employees1.size() == 1, "查询数量错误");
formatLog("Query Join");
var employees2 = QueryChain.of(mapper, Employee.class)
.select(Employee::getName, Employee::getAge)
.select(Company::getId, c -> c.as(EmployeeWithCompanyName::getCompanyName))
.leftJoin(Employee.class, Company.class, on -> on.eq(Employee::getCompanyId, Company::getId).gt(Company::getMembers, 0))
.eq(Employee::getName, "Tom")
.lt(Company::getMembers, 200)
.returnType(EmployeeWithCompanyName.class)
.list();
Assert.isTrue(employees2.size() == 1, "查询数量错误");
}
}

View File

@@ -1,7 +1,7 @@
package com.lanyuanxiaoyao.service.template.xbatis.controller; package com.lanyuanxiaoyao.service.template.database.xbatis.controller;
import com.lanyuanxiaoyao.service.template.xbatis.entity.Company; import com.lanyuanxiaoyao.service.template.database.xbatis.entity.Company;
import com.lanyuanxiaoyao.service.template.xbatis.service.CompanyService; import com.lanyuanxiaoyao.service.template.database.xbatis.service.CompanyService;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.function.Function; import java.util.function.Function;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;

View File

@@ -1,7 +1,10 @@
package com.lanyuanxiaoyao.service.template.xbatis.entity; package com.lanyuanxiaoyao.service.template.database.xbatis.entity;
import cn.xbatis.db.annotations.Table; import cn.xbatis.db.annotations.Table;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter; import lombok.Setter;
import lombok.ToString; import lombok.ToString;
import lombok.experimental.FieldNameConstants; import lombok.experimental.FieldNameConstants;
@@ -10,6 +13,9 @@ import lombok.experimental.FieldNameConstants;
@Setter @Setter
@ToString(callSuper = true) @ToString(callSuper = true)
@FieldNameConstants @FieldNameConstants
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Table @Table
public class Company extends SimpleEntity { public class Company extends SimpleEntity {
private String name; private String name;

View File

@@ -0,0 +1,25 @@
package com.lanyuanxiaoyao.service.template.database.xbatis.entity;
import cn.xbatis.db.annotations.Table;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.FieldNameConstants;
@Getter
@Setter
@ToString(callSuper = true)
@FieldNameConstants
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Table
public class Employee extends SimpleEntity {
private String name;
private Integer age;
private Long companyId;
}

View File

@@ -1,6 +1,5 @@
package com.lanyuanxiaoyao.service.template.xbatis.entity; package com.lanyuanxiaoyao.service.template.database.xbatis.entity.vo;
import cn.xbatis.db.annotations.Table;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import lombok.ToString; import lombok.ToString;
@@ -10,8 +9,8 @@ import lombok.experimental.FieldNameConstants;
@Setter @Setter
@ToString(callSuper = true) @ToString(callSuper = true)
@FieldNameConstants @FieldNameConstants
@Table public class EmployeeWithCompanyName {
public class Employee extends SimpleEntity {
private String name; private String name;
private Integer age; private Integer age;
private String companyName;
} }

View File

@@ -0,0 +1,12 @@
package com.lanyuanxiaoyao.service.template.database.xbatis.service;
import com.lanyuanxiaoyao.service.template.database.xbatis.entity.Company;
import com.lanyuanxiaoyao.service.template.database.xbatis.mapper.MybatisBasicMapper;
import org.springframework.stereotype.Service;
@Service
public class CompanyService extends SimpleServiceSupport<Company> {
public CompanyService(MybatisBasicMapper mapper) {
super(Company.class, mapper);
}
}

View File

@@ -0,0 +1,12 @@
package com.lanyuanxiaoyao.service.template.database.xbatis.service;
import com.lanyuanxiaoyao.service.template.database.xbatis.entity.Employee;
import com.lanyuanxiaoyao.service.template.database.xbatis.mapper.MybatisBasicMapper;
import org.springframework.stereotype.Service;
@Service
public class EmployeeService extends SimpleServiceSupport<Employee> {
public EmployeeService(MybatisBasicMapper mapper) {
super(Employee.class, mapper);
}
}

View File

@@ -1,22 +1,11 @@
server:
port: 2490
spring: spring:
application: profiles:
name: Test include: test
datasource: datasource:
url: "jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;MODE=MySQL;DATABASE_TO_LOWER=TRUE;INIT=runscript from '/Users/lanyuanxiaoyao/Project/IdeaProjects/spring-boot-service-template/spring-boot-service-template-xbatis/src/test/initial.sql'" url: "jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;MODE=MySQL;DATABASE_TO_LOWER=TRUE;INIT=runscript from '/Users/lanyuanxiaoyao/Project/IdeaProjects/spring-boot-service-template/spring-boot-service-template-database/spring-boot-service-template-database-xbatis/src/test/initial.sql'"
username: test username: test
password: test password: test
driver-class-name: org.h2.Driver driver-class-name: org.h2.Driver
mybatis: mybatis:
configuration: configuration:
banner: false banner: false
decorator:
datasource:
p6spy:
multiline: false
exclude-categories:
- commit
- result
- resultset
log-format: "%(category)|%(executionTime)|%(sqlSingleLine)"

View File

@@ -1,167 +0,0 @@
package com.lanyuanxiaoyao.service.template.eq;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.util.Assert;
import org.springframework.web.client.RestTemplate;
import tools.jackson.databind.JsonNode;
import tools.jackson.databind.ObjectMapper;
@Slf4j
@RequiredArgsConstructor
@SpringBootApplication
public class TestApplication {
private static final String BASE_URL = "http://localhost:2490";
private static final RestTemplate REST_CLIENT = new RestTemplate();
private static final ObjectMapper MAPPER = new ObjectMapper();
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
@EventListener(ApplicationReadyEvent.class)
public void runTests() {
// 增
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 companies = listItems("company");
Assert.isTrue(companies.at("/data/items").size() == 3, "数量错误");
Assert.isTrue(companies.at("/data/total").asLong() == 3, "返回数量错误");
// language=JSON
var companies2 = listItems("company", "{\n" +
" \"page\": {\n" +
" \"index\": 1,\n" +
" \"size\": 2\n" +
" }\n" +
"}");
Assert.isTrue(companies2.at("/data/items").size() == 2, "数量错误");
Assert.isTrue(companies2.at("/data/total").asLong() == 3, "返回数量错误");
// language=JSON
var companies3 = listItems("company", "{\n" +
" \"query\": {\n" +
" \"notNullEqual\": [\n" +
" \"name\"\n" +
" ],\n" +
" \"equal\": {\n" +
" \"name\": \"Apple\"\n" +
" },\n" +
" \"like\": {\n" +
" \"name\": \"Appl%\"\n" +
" },\n" +
" \"contain\": {\n" +
" \"name\": \"ple\"\n" +
" },\n" +
" \"startWith\": {\n" +
" \"name\": \"Appl\"\n" +
" },\n" +
" \"endWith\": {\n" +
" \"name\": \"le\"\n" +
" },\n" +
" \"less\": {\n" +
" \"members\": 50\n" +
" },\n" +
" \"greatEqual\": {\n" +
" \"members\": 0,\n" +
" \"createdTime\": \"2025-01-01 00:00:00\"\n" +
" },\n" +
" \"inside\": {\n" +
" \"name\": [\n" +
" \"Apple\",\n" +
" \"Banana\"\n" +
" ]\n" +
" }\n" +
" },\n" +
" \"page\": {\n" +
" \"index\": 1,\n" +
" \"size\": 2\n" +
" }\n" +
"}");
Assert.isTrue(companies3.at("/data/items").size() == 1, "数量错误");
Assert.isTrue(companies3.at("/data/total").asLong() == 1, "返回数量错误");
var company1 = detailItem("company", cid1);
Assert.isTrue(cid1 == company1.at("/data/id").asLong(), "id错误");
Assert.isTrue("Apple".equals(company1.at("/data/name").asString()), "name错误");
// 改
var cid4 = saveItem("company", "{\"id\": %d, \"name\": \"Dog\"}".formatted(cid2)).get("data").asLong();
Assert.isTrue(cid2 == cid4, "id错误");
var company2 = detailItem("company", cid2);
Assert.isTrue("Dog".equals(company2.at("/data/name").asString()), "name错误");
// 删
removeItem("company", cid3);
Assert.isTrue(listItems("company").at("/data/items").size() == 2, "数量错误");
Assert.isTrue(listItems("company").at("/data/total").asLong() == 2, "返回数量错误");
log.info(listItems("company").toPrettyString());
System.exit(0);
}
private HttpHeaders headers() {
var headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return headers;
}
private JsonNode saveItem(String path, String body) {
var response = REST_CLIENT.postForEntity(
"%s/%s/save".formatted(BASE_URL, path),
new HttpEntity<>(body, headers()),
String.class
);
Assert.isTrue(response.getStatusCode().is2xxSuccessful(), "请求失败");
Assert.notNull(response.getBody(), "请求失败");
return MAPPER.readTree(response.getBody());
}
private JsonNode listItems(String path) {
var response = REST_CLIENT.getForEntity(
"%s/%s/list".formatted(BASE_URL, path),
String.class
);
Assert.isTrue(response.getStatusCode().is2xxSuccessful(), "请求失败");
Assert.notNull(response.getBody(), "请求失败");
return MAPPER.readTree(response.getBody());
}
private JsonNode listItems(String path, String query) {
var response = REST_CLIENT.postForEntity(
"%s/%s/list".formatted(BASE_URL, path),
new HttpEntity<>(query, headers()),
String.class
);
Assert.isTrue(response.getStatusCode().is2xxSuccessful(), "请求失败");
Assert.notNull(response.getBody(), "请求失败");
return MAPPER.readTree(response.getBody());
}
private JsonNode detailItem(String path, Long id) {
var response = REST_CLIENT.getForEntity(
"%s/%s/detail/%d".formatted(BASE_URL, path, id),
String.class
);
Assert.isTrue(response.getStatusCode().is2xxSuccessful(), "请求失败");
Assert.notNull(response.getBody(), "请求失败");
return MAPPER.readTree(response.getBody());
}
private void removeItem(String path, Long id) {
var response = REST_CLIENT.getForEntity(
"%s/%s/remove/%d".formatted(BASE_URL, path, id),
Void.class
);
Assert.isTrue(response.getStatusCode().is2xxSuccessful(), "请求失败");
}
}

View File

@@ -1,21 +0,0 @@
package com.lanyuanxiaoyao.service.template.eq.entity;
import com.easy.query.core.annotation.EntityProxy;
import com.easy.query.core.annotation.Table;
import com.easy.query.core.proxy.ProxyEntityAvailable;
import com.lanyuanxiaoyao.service.template.eq.entity.proxy.CompanyProxy;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.FieldNameConstants;
@Getter
@Setter
@ToString(callSuper = true)
@FieldNameConstants
@Table
@EntityProxy
public class Company extends SimpleEntity implements ProxyEntityAvailable<Company, CompanyProxy> {
private String name;
private Integer members;
}

View File

@@ -1,21 +0,0 @@
package com.lanyuanxiaoyao.service.template.eq.entity;
import com.easy.query.core.annotation.EntityProxy;
import com.easy.query.core.annotation.Table;
import com.easy.query.core.proxy.ProxyEntityAvailable;
import com.lanyuanxiaoyao.service.template.eq.entity.proxy.EmployeeProxy;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.FieldNameConstants;
@Getter
@Setter
@ToString(callSuper = true)
@FieldNameConstants
@Table
@EntityProxy
public class Employee extends SimpleEntity implements ProxyEntityAvailable<Employee, EmployeeProxy> {
private String name;
private Integer age;
}

View File

@@ -1,89 +0,0 @@
package com.lanyuanxiaoyao.service.template.jpa;
import com.lanyuanxiaoyao.service.template.jpa.helper.DatabaseHelper;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.Assert;
/**
* Helper测试类
* 用于测试驼峰命名法转下划线命名法的功能
*/
@Slf4j
public class HelperTest {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
// 通过反射调用Helper类中的private静态方法camelConvert
var camelConvert = DatabaseHelper.class.getDeclaredMethod("camelConvert", String.class);
camelConvert.setAccessible(true);
// 测试用例集合
Map<String, String> testCases = new HashMap<>();
// 基本转换测试
testCases.put("helloWorld", "hello_world");
testCases.put("firstName", "first_name");
testCases.put("lastName", "last_name");
testCases.put("URL", "url");
testCases.put("HTTPResponse", "http_response");
testCases.put("XMLParser", "xml_parser");
// 边界情况测试
testCases.put(null, null); // null输入
testCases.put("", ""); // 空字符串
testCases.put("a", "a"); // 单个小写字母
testCases.put("A", "a"); // 单个大写字母
testCases.put("aB", "a_b"); // 两个字符
testCases.put("Ab", "ab"); // 首字母大写
// 数字相关测试
testCases.put("field1Name", "field1_name");
testCases.put("field12Name", "field12_name");
testCases.put("2FARequired", "2_fa_required");
testCases.put("ID", "id");
testCases.put("userID", "user_id");
testCases.put("HTML5Parser", "html5_parser");
// 连续大写字母测试
testCases.put("HTTPSConnection", "https_connection");
testCases.put("XMLHttpRequest", "xml_http_request");
testCases.put("URLPath", "url_path");
testCases.put("APIKey", "api_key");
testCases.put("JWTToken", "jwt_token");
// 特殊场景测试
testCases.put("iPhone", "i_phone"); // 以小写字母开头,后面有大写
testCases.put("iOSVersion", "i_os_version"); // 连续小写字母后跟大写
testCases.put("CAPTCHA", "captcha"); // 全大写字母缩写
log.info("开始执行驼峰命名转下划线命名测试...");
int passedTests = 0;
int totalTests = testCases.size();
for (Map.Entry<String, String> testCase : testCases.entrySet()) {
String input = testCase.getKey();
String expected = testCase.getValue();
String actual = (String) camelConvert.invoke(null, input);
try {
Assert.isTrue(Objects.equals(expected, actual), "测试失败: 输入='%s', 期望='%s', 实际='%s'".formatted(input, expected, actual));
passedTests++;
log.info("✓ 测试通过: '{}' -> '{}'", input, actual);
} catch (Exception e) {
log.error("✗ {}", e.getMessage());
}
}
log.info("测试结果: {}/{} 通过", passedTests, totalTests);
if (passedTests == totalTests) {
log.info("所有测试通过!✓");
} else {
log.error("有测试失败!✗");
System.exit(1);
}
}
}

View File

@@ -1,302 +0,0 @@
package com.lanyuanxiaoyao.service.template.jpa;
import com.blinkfox.fenix.EnableFenix;
import com.lanyuanxiaoyao.service.template.jpa.entity.Company;
import com.lanyuanxiaoyao.service.template.jpa.entity.Company_;
import com.lanyuanxiaoyao.service.template.jpa.entity.Employee;
import com.lanyuanxiaoyao.service.template.jpa.entity.Employee_;
import com.lanyuanxiaoyao.service.template.jpa.entity.QEmployee;
import com.lanyuanxiaoyao.service.template.jpa.entity.Report;
import com.lanyuanxiaoyao.service.template.jpa.entity.Report_;
import com.lanyuanxiaoyao.service.template.jpa.repository.EmployeeRepository;
import jakarta.annotation.Resource;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.util.Assert;
import org.springframework.web.client.RestTemplate;
import tools.jackson.databind.JsonNode;
import tools.jackson.databind.ObjectMapper;
@SpringBootApplication
@EnableFenix
@EnableJpaAuditing
public class TestApplication {
private static final Logger log = LoggerFactory.getLogger(TestApplication.class);
private static final String BASE_URL = "http://localhost:2490";
private static final RestTemplate REST_CLIENT = new RestTemplate();
private static final ObjectMapper MAPPER = new ObjectMapper();
@Resource
private EmployeeRepository employeeRepository;
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
@EventListener(ApplicationReadyEvent.class)
public void runTests() {
// 增
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 companies = listItems("company");
Assert.isTrue(companies.at("/data/items").size() == 3, "数量错误");
Assert.isTrue(companies.at("/data/total").asLong() == 3, "返回数量错误");
// language=JSON
var companies2 = listItems("company", "{\n" +
" \"page\": {\n" +
" \"index\": 1,\n" +
" \"size\": 2\n" +
" }\n" +
"}");
Assert.isTrue(companies2.at("/data/items").size() == 2, "数量错误");
Assert.isTrue(companies2.at("/data/total").asLong() == 3, "返回数量错误");
// language=JSON
var companies3 = listItems("company", "{\n" +
" \"query\": {\n" +
" \"notNullEqual\": [\n" +
" \"name\"\n" +
" ],\n" +
" \"equal\": {\n" +
" \"name\": \"Apple\"\n" +
" },\n" +
" \"like\": {\n" +
" \"name\": \"Appl%\"\n" +
" },\n" +
" \"contain\": {\n" +
" \"name\": \"ple\"\n" +
" },\n" +
" \"startWith\": {\n" +
" \"name\": \"Appl\"\n" +
" },\n" +
" \"endWith\": {\n" +
" \"name\": \"le\"\n" +
" },\n" +
" \"less\": {\n" +
" \"members\": 50\n" +
" },\n" +
" \"greatEqual\": {\n" +
" \"members\": 0,\n" +
" \"createdTime\": \"2025-01-01 00:00:00\"\n" +
" },\n" +
" \"inside\": {\n" +
" \"name\": [\n" +
" \"Apple\",\n" +
" \"Banana\"\n" +
" ]\n" +
" },\n" +
" \"between\": {\n" +
" \"members\": {\n" +
" \"start\": 0,\n" +
" \"end\": 50\n" +
" }\n" +
" }\n" +
" },\n" +
" \"page\": {\n" +
" \"index\": 1,\n" +
" \"size\": 2\n" +
" }\n" +
"}");
Assert.isTrue(companies3.at("/data/items").size() == 1, "数量错误");
Assert.isTrue(companies3.at("/data/total").asLong() == 1, "返回数量错误");
var company1 = detailItem("company", cid1);
Assert.isTrue(cid1 == company1.at("/data/id").asLong(), "id错误");
Assert.isTrue("Apple".equals(company1.at("/data/name").asText()), "name错误");
// 改
var cid4 = saveItem("company", "{\"id\": %d, \"name\": \"Dog\"}".formatted(cid2)).get("data").asLong();
Assert.isTrue(cid2 == cid4, "id错误");
var company2 = detailItem("company", cid2);
Assert.isTrue("Dog".equals(company2.at("/data/name").asText()), "name错误");
// 删
removeItem("company", cid3);
Assert.isTrue(listItems("company").at("/data/items").size() == 2, "数量错误");
Assert.isTrue(listItems("company").at("/data/total").asLong() == 2, "返回数量错误");
log.info(listItems("company").toPrettyString());
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 employees = listItems("employee");
Assert.isTrue(employees.at("/data/items").size() == 3, "数量错误");
Assert.isTrue(employees.at("/data/total").asLong() == 3, "返回数量错误");
var employee1 = detailItem("employee", eid1);
Assert.isTrue(eid1 == employee1.at("/data/id").asLong(), "id错误");
Assert.isTrue("Tom".equals(employee1.at("/data/name").asText()), "name错误");
Assert.isTrue(18 == employee1.at("/data/age").asInt(), "age错误");
System.exit(0);
}
@EventListener(ApplicationReadyEvent.class)
public void runSpecificationTests() {
// 增
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.isMember(Company.Industry.MEDIA, root.get(Employee_.company).get(Company_.industries))
)
)
);
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"))
.and(QEmployee.employee.company().industries.contains(Company.Industry.MEDIA))
.and(QEmployee.employee.connections.containsKey(Employee.ConnectionType.EMAIL))
)
);
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);
}
@EventListener(ApplicationReadyEvent.class)
public void runNativeQueryTests() {
// 增
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 list = employeeRepository.findAllEmployeeWithCompanyName();
Assert.isTrue(list.size() == 3, "数量错误");
log.debug("Results: {}", list);
var list_native = employeeRepository.findAllEmployeeWithCompanyNameNative();
Assert.isTrue(list_native.size() == 3, "数量错误");
log.debug("Results: {}", list_native);
}
private HttpHeaders headers() {
var headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return headers;
}
private JsonNode saveItem(String path, String body) {
var response = REST_CLIENT.postForEntity(
"%s/%s/save".formatted(BASE_URL, path),
new HttpEntity<>(body, headers()),
String.class
);
Assert.isTrue(response.getStatusCode().is2xxSuccessful(), "请求失败");
Assert.notNull(response.getBody(), "请求失败");
return MAPPER.readTree(response.getBody());
}
private JsonNode listItems(String path) {
var response = REST_CLIENT.getForEntity(
"%s/%s/list".formatted(BASE_URL, path),
String.class
);
Assert.isTrue(response.getStatusCode().is2xxSuccessful(), "请求失败");
Assert.notNull(response.getBody(), "请求失败");
return MAPPER.readTree(response.getBody());
}
private JsonNode listItems(String path, String query) {
var response = REST_CLIENT.postForEntity(
"%s/%s/list".formatted(BASE_URL, path),
new HttpEntity<>(query, headers()),
String.class
);
Assert.isTrue(response.getStatusCode().is2xxSuccessful(), "请求失败");
Assert.notNull(response.getBody(), "请求失败");
return MAPPER.readTree(response.getBody());
}
private JsonNode detailItem(String path, Long id) {
var response = REST_CLIENT.getForEntity(
"%s/%s/detail/%d".formatted(BASE_URL, path, id),
String.class
);
Assert.isTrue(response.getStatusCode().is2xxSuccessful(), "请求失败");
Assert.notNull(response.getBody(), "请求失败");
return MAPPER.readTree(response.getBody());
}
private void removeItem(String path, Long id) {
var response = REST_CLIENT.getForEntity(
"%s/%s/remove/%d".formatted(BASE_URL, path, id),
Void.class
);
Assert.isTrue(response.getStatusCode().is2xxSuccessful(), "请求失败");
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,23 +0,0 @@
server:
port: 2490
spring:
application:
name: Test
datasource:
url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1
username: test
password: test
driver-class-name: org.h2.Driver
jpa:
generate-ddl: true
fenix:
print-banner: false
decorator:
datasource:
p6spy:
multiline: false
exclude-categories:
- commit
- result
- resultset
log-format: "%(category)|%(executionTime)|%(sqlSingleLine)"

View File

@@ -1,173 +0,0 @@
package com.lanyuanxiaoyao.service.template.xbatis;
import lombok.extern.slf4j.Slf4j;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.util.Assert;
import org.springframework.web.client.RestTemplate;
import tools.jackson.databind.JsonNode;
import tools.jackson.databind.ObjectMapper;
@Slf4j
@MapperScan("com.lanyuanxiaoyao.service.template.xbatis.mapper")
@SpringBootApplication
public class TestApplication {
private static final String BASE_URL = "http://localhost:2490";
private static final RestTemplate REST_CLIENT = new RestTemplate();
private static final ObjectMapper MAPPER = new ObjectMapper();
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
@EventListener(ApplicationReadyEvent.class)
public void runTests() {
// 增
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 companies = listItems("company");
Assert.isTrue(companies.at("/data/items").size() == 3, "数量错误");
Assert.isTrue(companies.at("/data/total").asLong() == 3, "返回数量错误");
// language=JSON
var companies2 = listItems("company", "{\n" +
" \"page\": {\n" +
" \"index\": 1,\n" +
" \"size\": 2\n" +
" }\n" +
"}");
Assert.isTrue(companies2.at("/data/items").size() == 2, "数量错误");
Assert.isTrue(companies2.at("/data/total").asLong() == 3, "返回数量错误");
// language=JSON
var companies3 = listItems("company", "{\n" +
" \"query\": {\n" +
" \"notNullEqual\": [\n" +
" \"name\"\n" +
" ],\n" +
" \"equal\": {\n" +
" \"name\": \"Apple\"\n" +
" },\n" +
" \"like\": {\n" +
" \"name\": \"Appl%\"\n" +
" },\n" +
" \"contain\": {\n" +
" \"name\": \"ple\"\n" +
" },\n" +
" \"startWith\": {\n" +
" \"name\": \"Appl\"\n" +
" },\n" +
" \"endWith\": {\n" +
" \"name\": \"le\"\n" +
" },\n" +
" \"less\": {\n" +
" \"members\": 50\n" +
" },\n" +
" \"greatEqual\": {\n" +
" \"members\": 0,\n" +
" \"createdTime\": \"2025-01-01 00:00:00\"\n" +
" },\n" +
" \"inside\": {\n" +
" \"name\": [\n" +
" \"Apple\",\n" +
" \"Banana\"\n" +
" ]\n" +
" },\n" +
" \"between\": {\n" +
" \"members\": {\n" +
" \"start\": 0,\n" +
" \"end\": 50\n" +
" }\n" +
" }\n" +
" },\n" +
" \"page\": {\n" +
" \"index\": 1,\n" +
" \"size\": 2\n" +
" }\n" +
"}");
Assert.isTrue(companies3.at("/data/items").size() == 1, "数量错误");
Assert.isTrue(companies3.at("/data/total").asLong() == 1, "返回数量错误");
var company1 = detailItem("company", cid1);
Assert.isTrue(cid1 == company1.at("/data/id").asLong(), "id错误");
Assert.isTrue("Apple".equals(company1.at("/data/name").asText()), "name错误");
// 改
var cid4 = saveItem("company", "{\"id\": %d, \"name\": \"Dog\"}".formatted(cid2)).get("data").asLong();
Assert.isTrue(cid2 == cid4, "id错误");
var company2 = detailItem("company", cid2);
Assert.isTrue("Dog".equals(company2.at("/data/name").asText()), "name错误");
// 删
removeItem("company", cid3);
Assert.isTrue(listItems("company").at("/data/items").size() == 2, "数量错误");
Assert.isTrue(listItems("company").at("/data/total").asLong() == 2, "返回数量错误");
log.info(listItems("company").toPrettyString());
System.exit(0);
}
private HttpHeaders headers() {
var headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return headers;
}
private JsonNode saveItem(String path, String body) {
var response = REST_CLIENT.postForEntity(
"%s/%s/save".formatted(BASE_URL, path),
new HttpEntity<>(body, headers()),
String.class
);
Assert.isTrue(response.getStatusCode().is2xxSuccessful(), "请求失败");
Assert.notNull(response.getBody(), "请求失败");
return MAPPER.readTree(response.getBody());
}
private JsonNode listItems(String path) {
var response = REST_CLIENT.getForEntity(
"%s/%s/list".formatted(BASE_URL, path),
String.class
);
Assert.isTrue(response.getStatusCode().is2xxSuccessful(), "请求失败");
Assert.notNull(response.getBody(), "请求失败");
return MAPPER.readTree(response.getBody());
}
private JsonNode listItems(String path, String query) {
var response = REST_CLIENT.postForEntity(
"%s/%s/list".formatted(BASE_URL, path),
new HttpEntity<>(query, headers()),
String.class
);
Assert.isTrue(response.getStatusCode().is2xxSuccessful(), "请求失败");
Assert.notNull(response.getBody(), "请求失败");
return MAPPER.readTree(response.getBody());
}
private JsonNode detailItem(String path, Long id) {
var response = REST_CLIENT.getForEntity(
"%s/%s/detail/%d".formatted(BASE_URL, path, id),
String.class
);
Assert.isTrue(response.getStatusCode().is2xxSuccessful(), "请求失败");
Assert.notNull(response.getBody(), "请求失败");
return MAPPER.readTree(response.getBody());
}
private void removeItem(String path, Long id) {
var response = REST_CLIENT.getForEntity(
"%s/%s/remove/%d".formatted(BASE_URL, path, id),
Void.class
);
Assert.isTrue(response.getStatusCode().is2xxSuccessful(), "请求失败");
}
}

View File

@@ -1,12 +0,0 @@
package com.lanyuanxiaoyao.service.template.xbatis.service;
import com.lanyuanxiaoyao.service.template.xbatis.entity.Company;
import com.lanyuanxiaoyao.service.template.xbatis.mapper.MybatisBasicMapper;
import org.springframework.stereotype.Service;
@Service
public class CompanyService extends SimpleServiceSupport<Company> {
public CompanyService(MybatisBasicMapper mapper) {
super(Company.class, mapper);
}
}

View File

@@ -1,12 +0,0 @@
package com.lanyuanxiaoyao.service.template.xbatis.service;
import com.lanyuanxiaoyao.service.template.xbatis.entity.Employee;
import com.lanyuanxiaoyao.service.template.xbatis.mapper.MybatisBasicMapper;
import org.springframework.stereotype.Service;
@Service
public class EmployeeService extends SimpleServiceSupport<Employee> {
public EmployeeService(MybatisBasicMapper mapper) {
super(Employee.class, mapper);
}
}