跳转到正文

快速开始

本例创建独立的演示应用,用一张 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}'

新增响应的 code00000data 含字符串 ID、名称和创建时间。首次新增后,分页的 data.total=1data.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 响应约定,后续扩展见使用与配置查询与分页

示例涉及的源码类型

BaseEntityDtoBaseEntityPoBaseEntityQueryBaseEntitySimpleVoBaseEntityVoBasePageMapperPageModelAbstractBaseServiceMapperUtilRResultModelBusinessSimpleException