外观
快速开始
本例创建独立的演示应用,用一张 H2 内存表验证完整 CRUD 链路。它只依赖 Component,不需要 Business 模块、登录用户 Provider 或外部数据库;重启后数据清空。演示接口不带身份校验,迁入正式应用时由应用设置访问规则。
准备 Java 25、Maven,以及可解析的 EFC 4.1.0-SNAPSHOT 构件。构建基线为 Spring Boot 4.1.0;Parent 与 BOM 的职责见统一 Dependencies。
1. 创建演示项目
在独立目录中创建 pom.xml:
xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.own</groupId>
<artifactId>springboot-dependencies-parent</artifactId>
<version>4.1.0-SNAPSHOT</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>base-business-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.own</groupId>
<artifactId>springboot-component-dependencies</artifactId>
<version>4.1.0-SNAPSHOT</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>com.own.component</groupId>
<artifactId>springboot-component-base-business</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
</plugin>
</plugins>
</build>
</project>已有应用只需合并相应依赖,已有统一 BOM 时不重复导入。此例使用标准单条 CRUD,不调用自定义批量 SQL,因此不额外引入 MyBatis 组件。
2. 配置数据源和表
创建 src/main/resources/application.yml:
yaml
server:
address: 127.0.0.1
port: 8080
spring:
datasource:
url: jdbc:h2:mem:base_business_demo;DB_CLOSE_DELAY=-1
username: sa
password: ""
driver-class-name: org.h2.Driver
sql:
init:
mode: always
schema-locations: classpath:base-business-demo.sql
mybatis-plus:
configuration:
map-underscore-to-camel-case: true
pagehelper:
helper-dialect: h2创建 src/main/resources/base-business-demo.sql:
sql
CREATE TABLE demo_label (
id BIGINT PRIMARY KEY,
create_time TIMESTAMP NOT NULL,
name VARCHAR(80) NOT NULL
);id 使用继承的 ASSIGN_ID 策略,不是数据库自增。此例继承最基础 PO,删除会物理移除记录;没有修改人、逻辑删除或版本字段。
3. 定义模型、服务和接口
创建 src/main/java/com/example/demo/BaseBusinessDemoApplication.java。为便于完整复制,示例将模型放在同一类中;实际应用可按职责拆文件,保持构造器为 public。
java
package com.example.demo;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.own.component.base.business.entity.BaseEntityDto;
import com.own.component.base.business.entity.BaseEntityPo;
import com.own.component.base.business.entity.BaseEntityQuery;
import com.own.component.base.business.entity.BaseEntitySimpleVo;
import com.own.component.base.business.entity.BaseEntityVo;
import com.own.component.base.business.mapper.BasePageMapper;
import com.own.component.base.business.model.PageModel;
import com.own.component.base.business.service.impl.AbstractBaseService;
import com.own.component.base.business.util.MapperUtil;
import com.own.component.base.model.R;
import com.own.component.base.model.ResultModel;
import com.own.constant.exception.BusinessSimpleException;
import org.apache.ibatis.annotations.Mapper;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Import;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
@MapperScan(basePackageClasses = BaseBusinessDemoApplication.class,
annotationClass = Mapper.class)
@Import(MapperUtil.class)
public class BaseBusinessDemoApplication {
public static void main(String[] args) {
SpringApplication.run(BaseBusinessDemoApplication.class, args);
}
@TableName("demo_label")
public static class LabelPo extends BaseEntityPo {
private String name;
public LabelPo() {}
public String getName() { return name; }
public void setName(String name) { this.name = name; }
}
public static class LabelDto extends BaseEntityDto<LabelPo> {
private String name;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
@Override
public void check() {
super.check();
if (name == null || name.isBlank() || name.length() > 80) {
throw new BusinessSimpleException("名称必须为 1 至 80 个字符且不能全为空白");
}
}
@Override
public LabelPo toPo(LabelPo old) {
LabelPo po = old == null ? new LabelPo() : old;
po.setName(name);
return po;
}
}
public static class LabelVo extends BaseEntityVo<LabelPo> {
private final String name;
public LabelVo(LabelPo po) {
super(po);
this.name = po.getName();
}
public String getName() { return name; }
}
public static class LabelMapVo extends BaseEntitySimpleVo<LabelPo> {
public LabelMapVo(LabelPo po) { super(po); }
}
public static class LabelQuery extends BaseEntityQuery {
public LabelQuery() {}
}
@Mapper
public interface LabelMapper extends
BasePageMapper<LabelPo, LabelVo, LabelMapVo, LabelQuery> {
}
@Service
public static class LabelService extends AbstractBaseService<
LabelPo, LabelDto, LabelVo, LabelMapVo, LabelQuery, LabelMapper> {
@Override
public LabelQuery defaultQuery() { return new LabelQuery(); }
@Override
public LabelPo beforeAdd(LabelPo po) {
po.init();
return po;
}
@Override
public QueryWrapper<LabelPo> queryWrapper(LabelQuery query) {
QueryWrapper<LabelPo> wrapper = new QueryWrapper<>();
wrapper.lambda().orderByDesc(LabelPo::getCreateTime, LabelPo::getId);
return wrapper;
}
}
@RestController
@RequestMapping("/demo/labels")
public static class LabelController {
private final LabelService service;
public LabelController(LabelService service) { this.service = service; }
@PostMapping
public ResultModel<LabelVo> add(@RequestBody LabelDto dto) {
dto.check();
return R.success(service.addByDto(dto));
}
@PutMapping("/{id}")
public ResultModel<LabelVo> update(@PathVariable("id") Long id,
@RequestBody LabelDto dto) {
dto.check();
return R.success(service.updateByDto(id, dto));
}
@GetMapping("/{id}")
public ResultModel<LabelVo> get(@PathVariable("id") Long id) {
return R.success(service.getVoById(id));
}
@PostMapping("/page")
public ResultModel<PageModel<LabelVo>> page(@RequestBody LabelQuery query) {
return R.success(service.page(query));
}
@DeleteMapping("/{id}")
public ResultModel<Boolean> delete(@PathVariable("id") Long id) {
service.deleteById(id);
return R.success(true);
}
}
}dto.check() 和 po.init() 都是显式调用。更新 DTO 直接复用旧对象,不调用带 Supplier 的转换辅助方法,避免非空分支回调自身。Mapper 继承的复杂查询方法尚未映射 SQL,此例仅调用 page(query),不调用 pageForComplex(query)。
4. 启动与验证
在演示项目目录启动:
sh
mvn spring-boot:run新增并查看分页:
sh
curl -sS -X POST 'http://127.0.0.1:8080/demo/labels' \
-H 'Content-Type: application/json' --data '{"name":"待处理"}'
curl -sS -X POST 'http://127.0.0.1:8080/demo/labels/page' \
-H 'Content-Type: application/json' --data '{"page":1,"rows":10}'新增响应的 code 为 00000,data 含字符串 ID、名称和创建时间。首次新增后,分页的 data.total=1、data.list 含该记录、data.isLastPage=true;分页不是内存切片,而是 PageHelper 拦截数据库查询。
将新增响应的 ID 填入下面变量,再验证修改、详情和删除:
sh
label_id='替换为新增响应的字符串ID'
curl -sS -X PUT "http://127.0.0.1:8080/demo/labels/$label_id" \
-H 'Content-Type: application/json' --data '{"name":"已处理"}'
curl -sS "http://127.0.0.1:8080/demo/labels/$label_id"
curl -sS -X DELETE "http://127.0.0.1:8080/demo/labels/$label_id"
curl -sS "http://127.0.0.1:8080/demo/labels/$label_id"修改和详情应返回新名称且保留创建时间;删除返回 data=true,之后详情返回 business_not_found。再次分页应无该记录。删除响应本身不证明目标原先存在,因为基类没有检查删除影响行数。
空白名称会被示例 DTO 拒绝;rows=1000 会在首次初始化时限制为 100。示例没有实现关键字条件,传 keywords 不会筛选标签。公共响应与错误标识见 Base 响应约定,后续扩展见使用与配置和查询与分页。