From a8e8ec0ef2f9879f06a4452cddb25618fca83b90 Mon Sep 17 00:00:00 2001 From: lanyuanxiaoyao Date: Wed, 21 Jan 2026 14:51:14 +0800 Subject: [PATCH] =?UTF-8?q?fix(jpa):=20=E5=AE=8C=E5=96=84=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E7=94=A8=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/test/AbstractTestApplication.java | 27 +++-- .../jpa/service/SimpleServiceSupport.java | 2 +- .../database/jpa/TestApplication.java | 111 +++++++++++++----- .../template/database/jpa/entity/Company.java | 14 ++- .../database/jpa/entity/Employee.java | 6 + .../template/database/jpa/entity/Report.java | 6 + .../jpa/repository/EmployeeRepository.java | 2 +- 7 files changed, 124 insertions(+), 44 deletions(-) diff --git a/spring-boot-service-template-database/spring-boot-service-template-database-common-test/src/main/java/com/lanyuanxiaoyao/service/template/database/common/test/AbstractTestApplication.java b/spring-boot-service-template-database/spring-boot-service-template-database-common-test/src/main/java/com/lanyuanxiaoyao/service/template/database/common/test/AbstractTestApplication.java index 3c4b93b..42645a9 100644 --- a/spring-boot-service-template-database/spring-boot-service-template-database-common-test/src/main/java/com/lanyuanxiaoyao/service/template/database/common/test/AbstractTestApplication.java +++ b/spring-boot-service-template-database/spring-boot-service-template-database-common-test/src/main/java/com/lanyuanxiaoyao/service/template/database/common/test/AbstractTestApplication.java @@ -2,17 +2,16 @@ package com.lanyuanxiaoyao.service.template.database.common.test; import java.util.Map; import java.util.Random; -import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; -import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.util.Assert; import org.springframework.web.client.RestTemplate; import tools.jackson.databind.JsonNode; import tools.jackson.databind.ObjectMapper; -@RequiredArgsConstructor +@Slf4j public class AbstractTestApplication { private static final String BASE_URL = "http://localhost:2490"; private static final RestTemplate REST_CLIENT = new RestTemplate(); @@ -20,17 +19,18 @@ public class AbstractTestApplication { private static final Random random = new Random(); private static final String randomChars = "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM"; - private final JdbcTemplate template; - 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"); var companies1 = listItems("company"); Assert.isTrue(companies1.at("/data/items").size() == 3, "数量错误"); Assert.isTrue(companies1.at("/data/total").asLong() == 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错误"); @@ -41,6 +41,7 @@ public class AbstractTestApplication { var company3 = detailItem("company", cid3); Assert.isTrue(cid3 == company3.at("/data/id").asLong(), "id错误"); + formatLog("List Page"); // language=JSON var pageRequest = """ { @@ -54,6 +55,7 @@ public class AbstractTestApplication { Assert.isTrue(companies2.at("/data/items").size() == 2, "数量错误"); Assert.isTrue(companies2.at("/data/total").asLong() == 3, "返回数量错误"); + formatLog("List Queryable"); // language=JSON var queryRequest = """ { @@ -106,6 +108,7 @@ public class AbstractTestApplication { Assert.isTrue(companies3.at("/data/items").size() == 1, "数量错误"); Assert.isTrue(companies3.at("/data/total").asLong() == 1, "返回数量错误"); + formatLog("Clean"); removeItem("company", cid1); Assert.isTrue(listItems("company").at("/data/items").size() == 2, "数量错误"); Assert.isTrue(listItems("company").at("/data/total").asLong() == 2, "返回数量错误"); @@ -115,6 +118,10 @@ public class AbstractTestApplication { Assert.isTrue(listItems("company").at("/data/total").asLong() == 0, "返回数量错误"); } + protected void formatLog(String text) { + log.info("===== {} =====", text); + } + protected Map randomCompany() { return randomCompany(randomString(10)); } @@ -146,11 +153,11 @@ public class AbstractTestApplication { ); } - private String randomString(String prefix, int length) { + protected String randomString(String prefix, int length) { return prefix + randomString(length); } - private String randomString(int 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()))); @@ -158,15 +165,15 @@ public class AbstractTestApplication { return builder.toString(); } - private String randomChar(String base) { + protected String randomChar(String base) { return base.charAt(randomInt(base.length())) + ""; } - private int randomInt(int bound) { + protected int randomInt(int bound) { return random.nextInt(1, bound); } - private double randomDouble(int bound) { + protected double randomDouble(int bound) { return random.nextDouble(1, bound); } diff --git a/spring-boot-service-template-database/spring-boot-service-template-database-jpa/src/main/java/com/lanyuanxiaoyao/service/template/database/jpa/service/SimpleServiceSupport.java b/spring-boot-service-template-database/spring-boot-service-template-database-jpa/src/main/java/com/lanyuanxiaoyao/service/template/database/jpa/service/SimpleServiceSupport.java index 515eb9f..d1623f1 100644 --- a/spring-boot-service-template-database/spring-boot-service-template-database-jpa/src/main/java/com/lanyuanxiaoyao/service/template/database/jpa/service/SimpleServiceSupport.java +++ b/spring-boot-service-template-database/spring-boot-service-template-database-jpa/src/main/java/com/lanyuanxiaoyao/service/template/database/jpa/service/SimpleServiceSupport.java @@ -276,7 +276,7 @@ public abstract class SimpleServiceSupport implemen @Override public void remove(Long id) { if (ObjectHelper.isNotNull(id)) { - repository.deleteById(id); + repository.deleteBatchByIds(List.of(id)); } } diff --git a/spring-boot-service-template-database/spring-boot-service-template-database-jpa/src/test/java/com/lanyuanxiaoyao/service/template/database/jpa/TestApplication.java b/spring-boot-service-template-database/spring-boot-service-template-database-jpa/src/test/java/com/lanyuanxiaoyao/service/template/database/jpa/TestApplication.java index 818ec19..023cad4 100644 --- a/spring-boot-service-template-database/spring-boot-service-template-database-jpa/src/test/java/com/lanyuanxiaoyao/service/template/database/jpa/TestApplication.java +++ b/spring-boot-service-template-database/spring-boot-service-template-database-jpa/src/test/java/com/lanyuanxiaoyao/service/template/database/jpa/TestApplication.java @@ -3,24 +3,30 @@ 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.jdbc.core.JdbcTemplate; import org.springframework.util.Assert; @Slf4j +@RequiredArgsConstructor @SpringBootApplication @EnableFenix @EnableJpaAuditing @@ -29,13 +35,6 @@ public class TestApplication extends AbstractTestApplication { private final EmployeeRepository employeeRepository; private final ReportRepository reportRepository; - public TestApplication(JdbcTemplate jdbcTemplate, CompanyRepository companyRepository, EmployeeRepository employeeRepository, ReportRepository reportRepository) { - super(jdbcTemplate); - this.companyRepository = companyRepository; - this.employeeRepository = employeeRepository; - this.reportRepository = reportRepository; - } - public static void main(String[] args) { SpringApplication.run(TestApplication.class, args); } @@ -43,21 +42,72 @@ public class TestApplication extends AbstractTestApplication { @EventListener(ApplicationReadyEvent.class) public void runTests() { testCrud(); + testDelete(); testSpecification(); testNative(); System.exit(0); } - private void testSpecification() { + 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(); - var cid3 = saveItem("company", randomCompany()).get("data").asLong(); - var eid1 = saveItem("employee", randomEmployee(cid1, "Tom")).get("data").asLong(); - var eid2 = saveItem("employee", randomEmployee(cid2)).get("data").asLong(); - var rid1 = saveItem("report", randomReport(eid1)).get("data").asLong(); - var rid2 = saveItem("report", randomReport(eid2)).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) @@ -84,7 +134,7 @@ public class TestApplication extends AbstractTestApplication { 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.isNotMember(Industry.MEDIA, root.get(Employee_.company).get(Company_.industries)) + builder.isMember(Industry.MEDIA, root.get(Employee_.company).get(Company_.industries)) ) ); Assert.isTrue(employees2.size() == 1, "查询数量错误"); @@ -100,31 +150,36 @@ public class TestApplication extends AbstractTestApplication { .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).not()) - .and(QEmployee.employee.connections.containsKey(Employee.ConnectionType.EMAIL).not()) + .and(QEmployee.employee.company().industries.contains(Industry.MEDIA)) + .and(QEmployee.employee.connections.containsKey(Employee.ConnectionType.EMAIL)) ); Assert.isTrue(employees3.size() == 1, "查询数量错误"); - companyRepository.deleteAllById(List.of(cid1, cid2, cid3)); - employeeRepository.deleteAllById(List.of(eid1, eid2)); - reportRepository.deleteAllById(List.of(rid1, rid2)); + formatLog("Clean"); + reportRepository.deleteAllInBatch(); + employeeRepository.deleteAllInBatch(); + companyRepository.deleteAllInBatch(); } private void testNative() { - var cid1 = saveItem("company", randomCompany()).get("data").asLong(); - var cid2 = saveItem("company", randomCompany()).get("data").asLong(); - var cid3 = saveItem("company", randomCompany()).get("data").asLong(); - var eid1 = saveItem("employee", randomEmployee(cid1)).get("data").asLong(); - var eid2 = saveItem("employee", randomEmployee(cid2)).get("data").asLong(); - var eid3 = saveItem("employee", randomEmployee(cid3)).get("data").asLong(); + 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, "数量错误"); - companyRepository.deleteAll(); - employeeRepository.deleteAll(); + formatLog("Clean"); + employeeRepository.deleteAllInBatch(); + companyRepository.deleteAllInBatch(); } } diff --git a/spring-boot-service-template-database/spring-boot-service-template-database-jpa/src/test/java/com/lanyuanxiaoyao/service/template/database/jpa/entity/Company.java b/spring-boot-service-template-database/spring-boot-service-template-database-jpa/src/test/java/com/lanyuanxiaoyao/service/template/database/jpa/entity/Company.java index 098b241..fae5962 100644 --- a/spring-boot-service-template-database/spring-boot-service-template-database-jpa/src/test/java/com/lanyuanxiaoyao/service/template/database/jpa/entity/Company.java +++ b/spring-boot-service-template-database/spring-boot-service-template-database-jpa/src/test/java/com/lanyuanxiaoyao/service/template/database/jpa/entity/Company.java @@ -14,7 +14,10 @@ import jakarta.persistence.OneToMany; import jakarta.persistence.Table; import java.util.HashSet; import java.util.Set; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; +import lombok.NoArgsConstructor; import lombok.Setter; import lombok.ToString; import lombok.experimental.FieldNameConstants; @@ -27,6 +30,9 @@ import org.springframework.data.jpa.domain.support.AuditingEntityListener; @Getter @ToString(callSuper = true) @FieldNameConstants +@AllArgsConstructor +@NoArgsConstructor +@Builder @Entity @SoftDelete @DynamicUpdate @@ -39,13 +45,13 @@ public class Company extends SimpleEntity { @Column(nullable = false, comment = "成员数") private Integer members; - @OneToMany(mappedBy = "company") - @ToString.Exclude - private Set employees; - @ElementCollection @JoinTable(foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT), inverseForeignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT)) @Enumerated(EnumType.STRING) @Column(nullable = false) private Set industries = new HashSet<>(); + + @OneToMany(mappedBy = "company") + @ToString.Exclude + private Set employees; } diff --git a/spring-boot-service-template-database/spring-boot-service-template-database-jpa/src/test/java/com/lanyuanxiaoyao/service/template/database/jpa/entity/Employee.java b/spring-boot-service-template-database/spring-boot-service-template-database-jpa/src/test/java/com/lanyuanxiaoyao/service/template/database/jpa/entity/Employee.java index d6c1104..8d85203 100644 --- a/spring-boot-service-template-database/spring-boot-service-template-database-jpa/src/test/java/com/lanyuanxiaoyao/service/template/database/jpa/entity/Employee.java +++ b/spring-boot-service-template-database/spring-boot-service-template-database-jpa/src/test/java/com/lanyuanxiaoyao/service/template/database/jpa/entity/Employee.java @@ -15,7 +15,10 @@ import jakarta.persistence.MapKeyEnumerated; import jakarta.persistence.Table; import java.util.HashMap; import java.util.Map; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; +import lombok.NoArgsConstructor; import lombok.Setter; import lombok.ToString; import lombok.experimental.FieldNameConstants; @@ -28,6 +31,9 @@ import org.springframework.data.jpa.domain.support.AuditingEntityListener; @Getter @ToString(callSuper = true) @FieldNameConstants +@AllArgsConstructor +@NoArgsConstructor +@Builder @Entity @SoftDelete @DynamicUpdate diff --git a/spring-boot-service-template-database/spring-boot-service-template-database-jpa/src/test/java/com/lanyuanxiaoyao/service/template/database/jpa/entity/Report.java b/spring-boot-service-template-database/spring-boot-service-template-database-jpa/src/test/java/com/lanyuanxiaoyao/service/template/database/jpa/entity/Report.java index 569629e..7441e0a 100644 --- a/spring-boot-service-template-database/spring-boot-service-template-database-jpa/src/test/java/com/lanyuanxiaoyao/service/template/database/jpa/entity/Report.java +++ b/spring-boot-service-template-database/spring-boot-service-template-database-jpa/src/test/java/com/lanyuanxiaoyao/service/template/database/jpa/entity/Report.java @@ -7,7 +7,10 @@ import jakarta.persistence.EntityListeners; import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; import jakarta.persistence.Table; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; +import lombok.NoArgsConstructor; import lombok.Setter; import lombok.ToString; import lombok.experimental.FieldNameConstants; @@ -20,6 +23,9 @@ import org.springframework.data.jpa.domain.support.AuditingEntityListener; @Getter @ToString(callSuper = true) @FieldNameConstants +@AllArgsConstructor +@NoArgsConstructor +@Builder @Entity @SoftDelete @DynamicUpdate diff --git a/spring-boot-service-template-database/spring-boot-service-template-database-jpa/src/test/java/com/lanyuanxiaoyao/service/template/database/jpa/repository/EmployeeRepository.java b/spring-boot-service-template-database/spring-boot-service-template-database-jpa/src/test/java/com/lanyuanxiaoyao/service/template/database/jpa/repository/EmployeeRepository.java index a35db08..ac052f8 100644 --- a/spring-boot-service-template-database/spring-boot-service-template-database-jpa/src/test/java/com/lanyuanxiaoyao/service/template/database/jpa/repository/EmployeeRepository.java +++ b/spring-boot-service-template-database/spring-boot-service-template-database-jpa/src/test/java/com/lanyuanxiaoyao/service/template/database/jpa/repository/EmployeeRepository.java @@ -16,7 +16,7 @@ public interface EmployeeRepository extends SimpleRepository { @Override Optional findOne(Specification 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 findAllEmployeeWithCompanyNameNative(); @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")