1
0

fix(jpa): 完善测试用例

This commit is contained in:
2026-01-21 14:51:14 +08:00
parent 02b9636fec
commit a8e8ec0ef2
7 changed files with 124 additions and 44 deletions

View File

@@ -2,17 +2,16 @@ package com.lanyuanxiaoyao.service.template.database.common.test;
import java.util.Map; import java.util.Map;
import java.util.Random; import java.util.Random;
import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpEntity; import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import tools.jackson.databind.JsonNode; import tools.jackson.databind.JsonNode;
import tools.jackson.databind.ObjectMapper; import tools.jackson.databind.ObjectMapper;
@RequiredArgsConstructor @Slf4j
public class AbstractTestApplication { public class AbstractTestApplication {
private static final String BASE_URL = "http://localhost:2490"; private static final String BASE_URL = "http://localhost:2490";
private static final RestTemplate REST_CLIENT = new RestTemplate(); private static final RestTemplate REST_CLIENT = new RestTemplate();
@@ -20,17 +19,18 @@ public class AbstractTestApplication {
private static final Random random = new Random(); private static final Random random = new Random();
private static final String randomChars = "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM"; private static final String randomChars = "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM";
private final JdbcTemplate template;
protected void testCrud() { protected void testCrud() {
formatLog("Save");
var cid1 = saveItem("company", randomCompany("Apple")).get("data").asLong(); var cid1 = saveItem("company", randomCompany("Apple")).get("data").asLong();
var cid2 = saveItem("company", randomCompany()).get("data").asLong(); var cid2 = saveItem("company", randomCompany()).get("data").asLong();
var cid3 = saveItem("company", randomCompany()).get("data").asLong(); var cid3 = saveItem("company", randomCompany()).get("data").asLong();
formatLog("List");
var companies1 = listItems("company"); var companies1 = listItems("company");
Assert.isTrue(companies1.at("/data/items").size() == 3, "数量错误"); Assert.isTrue(companies1.at("/data/items").size() == 3, "数量错误");
Assert.isTrue(companies1.at("/data/total").asLong() == 3, "返回数量错误"); Assert.isTrue(companies1.at("/data/total").asLong() == 3, "返回数量错误");
formatLog("Detail");
var company1 = detailItem("company", cid1); var company1 = detailItem("company", cid1);
Assert.isTrue(cid1 == company1.at("/data/id").asLong(), "id错误"); Assert.isTrue(cid1 == company1.at("/data/id").asLong(), "id错误");
Assert.isTrue(company1.at("/data/name").asString("").startsWith("Apple"), "name错误"); Assert.isTrue(company1.at("/data/name").asString("").startsWith("Apple"), "name错误");
@@ -41,6 +41,7 @@ public class AbstractTestApplication {
var company3 = detailItem("company", cid3); var company3 = detailItem("company", cid3);
Assert.isTrue(cid3 == company3.at("/data/id").asLong(), "id错误"); Assert.isTrue(cid3 == company3.at("/data/id").asLong(), "id错误");
formatLog("List Page");
// language=JSON // language=JSON
var pageRequest = """ var pageRequest = """
{ {
@@ -54,6 +55,7 @@ public class AbstractTestApplication {
Assert.isTrue(companies2.at("/data/items").size() == 2, "数量错误"); Assert.isTrue(companies2.at("/data/items").size() == 2, "数量错误");
Assert.isTrue(companies2.at("/data/total").asLong() == 3, "返回数量错误"); Assert.isTrue(companies2.at("/data/total").asLong() == 3, "返回数量错误");
formatLog("List Queryable");
// language=JSON // language=JSON
var queryRequest = """ var queryRequest = """
{ {
@@ -106,6 +108,7 @@ public class AbstractTestApplication {
Assert.isTrue(companies3.at("/data/items").size() == 1, "数量错误"); Assert.isTrue(companies3.at("/data/items").size() == 1, "数量错误");
Assert.isTrue(companies3.at("/data/total").asLong() == 1, "返回数量错误"); Assert.isTrue(companies3.at("/data/total").asLong() == 1, "返回数量错误");
formatLog("Clean");
removeItem("company", cid1); removeItem("company", cid1);
Assert.isTrue(listItems("company").at("/data/items").size() == 2, "数量错误"); Assert.isTrue(listItems("company").at("/data/items").size() == 2, "数量错误");
Assert.isTrue(listItems("company").at("/data/total").asLong() == 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, "返回数量错误"); Assert.isTrue(listItems("company").at("/data/total").asLong() == 0, "返回数量错误");
} }
protected void formatLog(String text) {
log.info("===== {} =====", text);
}
protected Map<String, Object> randomCompany() { protected Map<String, Object> randomCompany() {
return randomCompany(randomString(10)); 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); return prefix + randomString(length);
} }
private String randomString(int length) { protected String randomString(int length) {
var builder = new StringBuilder(); var builder = new StringBuilder();
for (int i = 0; i < length; i++) { for (int i = 0; i < length; i++) {
builder.append(randomChars.charAt(random.nextInt(randomChars.length()))); builder.append(randomChars.charAt(random.nextInt(randomChars.length())));
@@ -158,15 +165,15 @@ public class AbstractTestApplication {
return builder.toString(); return builder.toString();
} }
private String randomChar(String base) { protected String randomChar(String base) {
return base.charAt(randomInt(base.length())) + ""; return base.charAt(randomInt(base.length())) + "";
} }
private int randomInt(int bound) { protected int randomInt(int bound) {
return random.nextInt(1, bound); return random.nextInt(1, bound);
} }
private double randomDouble(int bound) { protected double randomDouble(int bound) {
return random.nextDouble(1, bound); return random.nextDouble(1, bound);
} }

View File

@@ -276,7 +276,7 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
@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));
} }
} }

View File

@@ -3,24 +3,30 @@ package com.lanyuanxiaoyao.service.template.database.jpa;
import com.blinkfox.fenix.EnableFenix; import com.blinkfox.fenix.EnableFenix;
import com.lanyuanxiaoyao.service.template.database.common.test.AbstractTestApplication; 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.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.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.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.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.CompanyRepository;
import com.lanyuanxiaoyao.service.template.database.jpa.repository.EmployeeRepository; import com.lanyuanxiaoyao.service.template.database.jpa.repository.EmployeeRepository;
import com.lanyuanxiaoyao.service.template.database.jpa.repository.ReportRepository; import com.lanyuanxiaoyao.service.template.database.jpa.repository.ReportRepository;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener; import org.springframework.context.event.EventListener;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing; import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@Slf4j @Slf4j
@RequiredArgsConstructor
@SpringBootApplication @SpringBootApplication
@EnableFenix @EnableFenix
@EnableJpaAuditing @EnableJpaAuditing
@@ -29,13 +35,6 @@ public class TestApplication extends AbstractTestApplication {
private final EmployeeRepository employeeRepository; private final EmployeeRepository employeeRepository;
private final ReportRepository reportRepository; 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) { public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args); SpringApplication.run(TestApplication.class, args);
} }
@@ -43,21 +42,72 @@ public class TestApplication extends AbstractTestApplication {
@EventListener(ApplicationReadyEvent.class) @EventListener(ApplicationReadyEvent.class)
public void runTests() { public void runTests() {
testCrud(); testCrud();
testDelete();
testSpecification(); testSpecification();
testNative(); testNative();
System.exit(0); 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 cid1 = saveItem("company", randomCompany()).get("data").asLong();
var cid2 = saveItem("company", randomCompany()).get("data").asLong(); var cid2 = saveItem("company", randomCompany()).get("data").asLong();
var cid3 = saveItem("company", randomCompany()).get("data").asLong(); companyRepository.deleteAllById(List.of(cid1, cid2));
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();
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( var employees1 = employeeRepository.findAll(
builder -> builder builder -> builder
.andIsNotNull(Employee.Fields.name) .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.in(root.get(Employee_.NAME)).value(List.of("Tom", "Mike")),
builder.between(root.get(Employee_.age), 0, 200), builder.between(root.get(Employee_.age), 0, 200),
builder.isNotEmpty(root.get(Employee_.company).get(Company_.employees)), 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, "查询数量错误"); Assert.isTrue(employees2.size() == 1, "查询数量错误");
@@ -100,31 +150,36 @@ public class TestApplication extends AbstractTestApplication {
.and(QEmployee.employee.name.in("Tom", "Mike")) .and(QEmployee.employee.name.in("Tom", "Mike"))
.and(QEmployee.employee.age.between(0, 200)) .and(QEmployee.employee.age.between(0, 200))
.and(QEmployee.employee.company().employees.isNotEmpty()) .and(QEmployee.employee.company().employees.isNotEmpty())
.and(QEmployee.employee.company().industries.contains(Industry.MEDIA).not()) .and(QEmployee.employee.company().industries.contains(Industry.MEDIA))
.and(QEmployee.employee.connections.containsKey(Employee.ConnectionType.EMAIL).not()) .and(QEmployee.employee.connections.containsKey(Employee.ConnectionType.EMAIL))
); );
Assert.isTrue(employees3.size() == 1, "查询数量错误"); Assert.isTrue(employees3.size() == 1, "查询数量错误");
companyRepository.deleteAllById(List.of(cid1, cid2, cid3)); formatLog("Clean");
employeeRepository.deleteAllById(List.of(eid1, eid2)); reportRepository.deleteAllInBatch();
reportRepository.deleteAllById(List.of(rid1, rid2)); employeeRepository.deleteAllInBatch();
companyRepository.deleteAllInBatch();
} }
private void testNative() { private void testNative() {
var cid1 = saveItem("company", randomCompany()).get("data").asLong(); formatLog("Added");
var cid2 = saveItem("company", randomCompany()).get("data").asLong(); var company1 = companyRepository.save(Company.builder().name(randomString(5)).members(randomInt(100)).build());
var cid3 = saveItem("company", randomCompany()).get("data").asLong(); var company2 = companyRepository.save(Company.builder().name(randomString(5)).members(randomInt(100)).build());
var eid1 = saveItem("employee", randomEmployee(cid1)).get("data").asLong(); var company3 = companyRepository.save(Company.builder().name(randomString(5)).members(randomInt(100)).build());
var eid2 = saveItem("employee", randomEmployee(cid2)).get("data").asLong(); var employee1 = employeeRepository.save(Employee.builder().name(randomString(10)).age(randomInt(100)).role(Employee.Role.USER).company(company1).build());
var eid3 = saveItem("employee", randomEmployee(cid3)).get("data").asLong(); 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(); var list = employeeRepository.findAllEmployeeWithCompanyName();
Assert.isTrue(list.size() == 3, "数量错误"); Assert.isTrue(list.size() == 3, "数量错误");
formatLog("SQL Query");
var list_native = employeeRepository.findAllEmployeeWithCompanyNameNative(); var list_native = employeeRepository.findAllEmployeeWithCompanyNameNative();
Assert.isTrue(list_native.size() == 3, "数量错误"); Assert.isTrue(list_native.size() == 3, "数量错误");
companyRepository.deleteAll(); formatLog("Clean");
employeeRepository.deleteAll(); employeeRepository.deleteAllInBatch();
companyRepository.deleteAllInBatch();
} }
} }

View File

@@ -14,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;
@@ -27,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
@@ -39,13 +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<>();
@OneToMany(mappedBy = "company")
@ToString.Exclude
private Set<Employee> employees;
} }

View File

@@ -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

@@ -7,7 +7,10 @@ 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;
@@ -20,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

View File

@@ -16,7 +16,7 @@ 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.database.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")