Spring Boot 项目开发流程详解 (以员工管理为例)
这个案例开发流程是一个典型的基于 Spring Boot 的分层架构实现。下面我将详细阐述每个步骤及其核心思想,希望能让您对这个流程有一个清晰的理解。
核心目标: 实现模块化、高内聚、低耦合,使得代码易于理解、维护、测试和扩展。
流程总览:
请求 (Request)
→ Controller 层
→ Service 层 (接口+实现)
→ Mapper 层 (DAO)
→ 数据库 (Database)
→ Mapper 层
→ Service 层
→ Controller 层
→ 响应 (Response)
1. POJO 层 (Plain Old Java Object) / Entity 层
目的
定义数据模型,用于封装业务数据。这些类通常与数据库表结构相对应。
具体实现
创建 Emp.java
(员工信息实体类) 和 Empr.java
(员工工作经历实体类)。
Emp.java
示例:
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
package com.example.pojo;
import java.util.List; // 如果需要在Emp中直接持有Empr列表
// import lombok.Data; // 使用Lombok简化代码,可选
// @Data // Lombok注解,自动生成getter/setter/toString/equals/hashCode
public class Emp {
private Integer empId;
private String empName;
private String department;
private Double salary;
// private List<Empr> workExperiences; // 可选,用于一对多关系
// Standard getters and setters, constructor, toString, etc.
// 如果不用Lombok,需要手动添加
public Integer getEmpId() { return empId; }
public void setEmpId(Integer empId) { this.empId = empId; }
// ... 其他属性的getter/setter
}
|
Empr.java
示例:
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
package com.example.pojo;
import java.util.Date;
// import lombok.Data;
// @Data
public class Empr { // Employee Experience Record
private Integer emprId;
private Integer empId; // 外键,关联到Emp表的empId
private String companyName;
private String position;
private Date startDate;
private Date endDate;
private String description;
// Standard getters and setters, constructor, toString, etc.
}
|
关键点
- 属性:与数据库表字段一一对应,或根据业务需求定义。
- 注解(可选,但常用):
- Lombok:
@Data
, @Getter
, @Setter
, @ToString
, @EqualsAndHashCode
, @NoArgsConstructor
, @AllArgsConstructor
等注解可以极大简化代码,避免手动编写样板代码。
- JPA/MyBatis-Plus: 如果使用 JPA 或 MyBatis-Plus 等 ORM 框架,还会用到
@Entity
, @Table
, @Id
, @Column
, @TableId
, @TableName
等注解来映射数据库表和字段。
- 封装性:属性通常声明为
private
,通过 public
的 getter
和 setter
方法访问。
2. Mapper 层 (DAO - Data Access Object)
目的
定义数据访问接口,负责与数据库进行直接交互,执行 SQL 语句(增删改查)。
具体实现
创建 EmpMapper.java
和 EmpEmprMapper.java
接口,并在接口上添加 @Mapper
注解。
@Mapper
注解作用:
- 这是 MyBatis 的注解 (或者是 MyBatis-Plus 提供的,效果类似)。
- Spring Boot 在启动时会扫描带有
@Mapper
注解的接口,并为它们创建代理实现类,然后将这些代理实例注册到 Spring IOC 容器中。这样,在 Service 层就可以通过 @Autowired
自动注入这些 Mapper 接口的实例。
EmpMapper.java
示例:
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
package com.example.mapper;
import com.example.pojo.Emp;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param; // 多个参数时建议使用
import java.util.List;
@Mapper
public interface EmpMapper {
Emp findById(@Param("empId") Integer empId);
List<Emp> findAll();
int insertEmp(Emp emp);
int updateEmp(Emp emp);
int deleteEmp(@Param("empId") Integer empId);
}
|
EmpEmprMapper.java
示例:
java
1
2
3
4
5
6
7
8
9
10
11
12
13
|
package com.example.mapper;
import com.example.pojo.Empr;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface EmpEmprMapper {
List<Empr> findByEmpId(@Param("empId") Integer empId);
int insertEmpr(Empr empr);
// ... 其他CRUD方法
}
|
关键点
- 接口定义:只定义方法签名,不包含具体实现。
- SQL 实现:
- XML 文件:通常与 Mapper 接口同路径或在
resources
目录下创建对应的 XML 文件 (如 EmpMapper.xml
) 来编写 SQL 语句。
- 注解方式:对于简单 SQL,也可以直接使用
@Select
, @Insert
, @Update
, @Delete
等注解直接在接口方法上编写 SQL。
- 方法命名:通常遵循一定的规范,如
findByXXX
, insertXXX
, updateXXX
, deleteXXX
。
- 参数与返回:方法的参数对应 SQL 中的条件,返回值对应查询结果。
3. Service 层
目的
处理核心业务逻辑,对一个或多个 Mapper 操作进行编排和封装,形成一个完整的业务功能。它起到承上启下的作用,隔离 Controller 层和 Mapper 层。
分为两部分:接口和实现类
3.1 Service 接口 (EmpService.java
)
目的:定义业务契约,声明业务方法。这样做的好处是面向接口编程,提高代码的灵活性和可测试性 (方便 Mock)。
具体实现:
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
package com.example.service;
import com.example.pojo.Emp;
// import com.example.dto.EmpDetailDTO; // 可能需要DTO
import java.util.List;
public interface EmpService {
Emp getEmployeeById(Integer empId);
// EmpDetailDTO getEmployeeWithExperience(Integer empId); // 示例:返回包含经历的员工信息
List<Emp> getAllEmployees();
void addNewEmployee(Emp emp /*, List<Empr> experiences*/); // 可能同时添加员工和经历
void updateEmployeeInfo(Emp emp);
void removeEmployee(Integer empId);
}
|
3.2 Service 实现类 (EmpServiceImpl.java
)
目的:具体实现 Service 接口中定义的业务逻辑。
具体实现:
- 创建
impl
包 (约定俗成),在包内创建 EmpServiceImpl.java
类。
- 实现
EmpService
接口 (implements EmpService
)。
- 添加
@Service
注解。
@Service
注解作用:
- 这是 Spring 框架的 stereotype (构造型) 注解之一,用于标识这是一个业务逻辑层组件。
- Spring Boot 启动时会扫描带有
@Service
注解的类,并创建其实例注册到 Spring IOC 容器中,使其成为一个 Bean。这样,在 Controller 层就可以通过 @Autowired
自动注入 EmpService
接口的实例 (Spring 会自动找到其实现类 EmpServiceImpl
的实例)。
EmpServiceImpl.java
示例:
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
|
package com.example.service.impl;
import com.example.mapper.EmpEmprMapper;
import com.example.mapper.EmpMapper;
import com.example.pojo.Emp;
import com.example.pojo.Empr;
import com.example.service.EmpService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; // 引入事务注解
import java.util.List;
@Service // 声明为Spring IOC容器的Bean
public class EmpServiceImpl implements EmpService {
@Autowired // 自动注入EmpMapper实例
private EmpMapper empMapper;
@Autowired // 自动注入EmpEmprMapper实例
private EmpEmprMapper empEmprMapper;
@Override
public Emp getEmployeeById(Integer empId) {
// 简单示例:直接调用mapper
return empMapper.findById(empId);
}
/*
// 复杂业务示例:获取员工及其工作经历
@Override
public EmpDetailDTO getEmployeeWithExperience(Integer empId) {
Emp emp = empMapper.findById(empId);
if (emp == null) {
return null;
}
List<Empr> experiences = empEmprMapper.findByEmpId(empId);
// 假设EmpDetailDTO是专门用于封装员工和其经历的类
EmpDetailDTO dto = new EmpDetailDTO();
dto.setEmpId(emp.getEmpId());
dto.setEmpName(emp.getEmpName());
// ... 其他emp属性
dto.setWorkExperiences(experiences);
return dto;
}
*/
@Override
public List<Emp> getAllEmployees() {
return empMapper.findAll();
}
@Override
@Transactional // 声明式事务:保证方法内多个数据库操作的原子性
public void addNewEmployee(Emp emp /*, List<Empr> experiences*/) {
empMapper.insertEmp(emp); // 插入员工信息,emp对象通常会配置返回自增主键
// if (experiences != null && !experiences.isEmpty()) {
// for (Empr expr : experiences) {
// expr.setEmpId(emp.getEmpId()); // 设置外键
// empEmprMapper.insertEmpr(expr);
// }
// }
// 如果在插入经历时发生错误,整个操作会回滚
}
@Override
@Transactional
public void updateEmployeeInfo(Emp emp) {
empMapper.updateEmp(emp);
// 可能还需要更新相关的工作经历等
}
@Override
@Transactional
public void removeEmployee(Integer empId) {
// 实际业务中可能需要先删除关联的工作经历,或有其他逻辑检查
// empEmprMapper.deleteByEmpId(empId); // 假设有此方法
empMapper.deleteEmp(empId);
}
}
|
关键点
- 依赖注入 (
@Autowired
):通过 @Autowired
注解将 Mapper 层的实例注入到 Service 实现类中。
- 业务逻辑封装:包含条件判断、数据转换、多个 DAO 操作的组合等。
- 事务管理 (
@Transactional
):对于涉及多个写操作(增、删、改)的业务方法,通常会使用 @Transactional
注解来保证操作的原子性。如果方法中任意一个数据库操作失败,整个事务会回滚,保证数据一致性。
4. Controller 层
目的
接收前端 HTTP 请求,调用 Service 层处理业务逻辑,并将处理结果返回给前端。它是应用的入口点。
具体实现
创建 EmpController.java
类,并添加 @RestController
注解。
@RestController
注解作用:
- 这是一个组合注解,相当于
@Controller
+ @ResponseBody
。
@Controller
:将类标识为一个控制器组件,Spring IOC 容器会管理它。
@ResponseBody
:表示该控制器中所有方法的返回值都会直接作为 HTTP 响应体的内容(通常是 JSON 或 XML 格式),而不是视图名称。
@Slf4j
(可选):
- 这是 Lombok 提供的注解,用于快速集成 SLF4J 日志框架。它会在编译时自动为类添加一个
log
字段 ( private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(EmpController.class);
)。
- 作用:方便记录日志,用于调试、追踪问题和监控应用运行状态。
EmpController.java
示例:
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
|
package com.example.controller;
import com.example.pojo.Emp;
// import com.example.dto.EmpDetailDTO;
import com.example.service.EmpService;
import lombok.extern.slf4j.Slf4j; // 如果使用@Slf4j
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController // 声明为RESTful风格的控制器,方法返回JSON/XML
@RequestMapping("/api/employees") // 类级别的请求路径映射,所有方法都在此基础路径下
@Slf4j // 可选,用于日志记录
public class EmpController {
@Autowired // 自动注入EmpService实例
private EmpService empService;
// GET /api/employees/{id} - 获取指定ID的员工信息
@GetMapping("/{id}")
public ResponseEntity<?> getEmployeeById(@PathVariable Integer id) {
log.info("Request to get employee with ID: {}", id);
// EmpDetailDTO empDetail = empService.getEmployeeWithExperience(id);
Emp employee = empService.getEmployeeById(id);
if (employee != null) {
log.info("Found employee: {}", employee.getEmpName());
return ResponseEntity.ok(employee); // 返回200 OK 和员工数据
} else {
log.warn("Employee with ID: {} not found.", id);
return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Employee not found"); // 返回404 Not Found
}
}
// GET /api/employees - 获取所有员工信息
@GetMapping
public ResponseEntity<List<Emp>> getAllEmployees() {
log.info("Request to get all employees");
List<Emp> employees = empService.getAllEmployees();
return ResponseEntity.ok(employees);
}
// POST /api/employees - 新增员工
@PostMapping
public ResponseEntity<Emp> createEmployee(@RequestBody Emp emp /*, @RequestBody List<Empr> experiences (更复杂场景)*/) {
log.info("Request to create new employee: {}", emp.getEmpName());
empService.addNewEmployee(emp /*, experiences*/);
// 通常在emp对象中会包含数据库生成的ID,如果配置了MyBatis返回主键
return ResponseEntity.status(HttpStatus.CREATED).body(emp); // 返回201 Created 和创建的员工数据
}
// PUT /api/employees/{id} - 更新指定ID的员工信息
@PutMapping("/{id}")
public ResponseEntity<?> updateEmployee(@PathVariable Integer id, @RequestBody Emp emp) {
log.info("Request to update employee with ID: {}", id);
// 确保emp对象的id与路径变量id一致,或以路径变量为准
emp.setEmpId(id);
empService.updateEmployeeInfo(emp);
return ResponseEntity.ok("Employee updated successfully");
}
// DELETE /api/employees/{id} - 删除指定ID的员工
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteEmployee(@PathVariable Integer id) {
log.info("Request to delete employee with ID: {}", id);
empService.removeEmployee(id);
return ResponseEntity.noContent().build(); // 返回204 No Content
}
}
|
关键点
- 依赖注入 (
@Autowired
):注入 EmpService
实例。
- 请求映射注解:
@RequestMapping("/api/employees")
: 定义类级别的基础 URL 路径。
@GetMapping
, @PostMapping
, @PutMapping
, @DeleteMapping
: 分别对应 HTTP 的 GET, POST, PUT, DELETE 请求方法,并可指定具体的子路径 (如 /{id}
)。
- 参数绑定注解:
@PathVariable
: 从 URL 路径中提取参数 (如 /{id}
中的 id
)。
@RequestBody
: 将 HTTP 请求体中的 JSON/XML 数据自动转换为 Java 对象 (如 Emp
对象)。
@RequestParam
: 从 URL 查询参数中提取值 (如 ?name=John
)。
- 响应处理 (
ResponseEntity
):
- 使用
ResponseEntity
可以更精细地控制 HTTP 响应,包括状态码、头部信息和响应体。
- 可以直接返回 POJO 对象,Spring MVC 会自动通过
HttpMessageConverter
(如 JacksonHttpMessageConverter
) 将其序列化为 JSON (默认) 或 XML。
- RESTful API 设计:通常遵循 RESTful 风格来设计 API 接口。
总结与强调
- 分层解耦:每一层都有明确的职责,Controller 负责调度和 HTTP 交互,Service 负责业务逻辑,Mapper 负责数据持久化。这种分离使得各层可以独立开发、测试和修改,降低了模块间的耦合度。
- Spring IOC (Inversion of Control):通过
@Mapper
, @Service
, @RestController
等注解,我们将对象的创建和管理的权力交给了 Spring 容器。我们需要使用某个组件时,通过 @Autowired
进行依赖注入即可,无需手动 new
对象,这简化了对象管理和依赖关系。
- 面向接口编程:尤其是在 Service 层,通过定义接口和实现类,可以提高系统的灵活性和可扩展性。例如,未来如果需要更换
EmpServiceImpl
的实现,只要新的实现类也实现了 EmpService
接口,Controller 层的代码基本无需改动。
- 声明式事务:通过在 Service 层方法上使用
@Transactional
注解,可以优雅地实现事务管理,而无需编写冗余的事务控制代码。
- Lombok (可选但推荐):通过
@Data
, @Slf4j
等注解,可以减少大量的样板代码,使代码更简洁易读。
这个流程是 Spring Boot Web 应用开发中非常经典和常用的一种实践。它清晰地划分了不同组件的职责,使得项目结构更加规范,易于团队协作和长期维护。