前言
在实际的Java后端开发中,我们通常会遵循一个标准的开发流程,确保代码结构清晰、层次分明、易于维护。本文将以一个图书管理系统为例,详细介绍从数据库设计到接口实现的完整流程。
完整开发流程
第1步:创建数据表
↓
第2步:创建Entity实体类
↓
第3步:创建DTO对象
↓
第4步:创建Repository数据访问层
↓
第5步:创建Service业务逻辑层
↓
第6步:创建Controller控制器层
↓
第7步:创建SQL配置类(可选,复杂查询使用)
第1步:创建数据表
首先在数据库中创建表结构,这是整个系统的数据基础。
-- 图书表
CREATE TABLE book (
id VARCHAR(32) PRIMARY KEY,
book_name VARCHAR(100) NOT NULL,
author VARCHAR(50),
isbn VARCHAR(20) UNIQUE,
price DECIMAL(10, 2),
stock INT DEFAULT 0,
publish_date DATE,
deleted INT DEFAULT 0,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 图书借阅记录表
CREATE TABLE book_borrow (
id VARCHAR(32) PRIMARY KEY,
book_id VARCHAR(32),
reader_name VARCHAR(50),
borrow_date DATE,
return_date DATE,
status INT DEFAULT 0, -- 0-借阅中 1-已归还
deleted INT DEFAULT 0,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (book_id) REFERENCES book(id)
);
设计要点:
- 每个表都有主键
id - 使用
deleted字段实现逻辑删除(0-正常 1-删除) - 使用
create_time和update_time记录时间 - 外键关联保持数据完整性
第2步:创建Entity实体类
Entity类对应数据库表结构,用于ORM映射。
package com.example.library.entity;
import lombok.Data;
import javax.persistence.*;
import java.math.BigDecimal;
import java.util.Date;
/**
* 图书实体类
* 对应数据库表:book
*/
@Data
@Entity
@Table(name = "book")
public class Book {
@Id
@GeneratedValue(generator = "uuid")
@GenericGenerator(name = "uuid", strategy = "uuid")
@Column(name = "id", nullable = false)
private String id;
@Column(name = "book_name", nullable = false)
private String bookName;
@Column(name = "author")
private String author;
@Column(name = "isbn", unique = true)
private String isbn;
@Column(name = "price")
private BigDecimal price;
@Column(name = "stock")
private Integer stock;
@Column(name = "publish_date")
private Date publishDate;
@Column(name = "deleted")
private Integer deleted;
@Column(name = "create_time")
private Date createTime;
@Column(name = "update_time")
private Date updateTime;
}
Entity的作用:
- 与数据库表一一对应
- 负责数据的存储和读取
- 不包含业务逻辑
第3步:创建DTO对象
DTO(Data Transfer Object)用于前后端数据传输,与Entity分离。
3.1 请求DTO(接收前端参数)
package com.example.library.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* 新增图书请求DTO
*/
@Data
@ApiModel("新增图书请求")
public class AddBookDTO {
@ApiModelProperty("图书名称")
private String bookName;
@ApiModelProperty("作者")
private String author;
@ApiModelProperty("ISBN")
private String isbn;
@ApiModelProperty("价格")
private BigDecimal price;
@ApiModelProperty("库存数量")
private Integer stock;
@ApiModelProperty("出版日期")
private String publishDate;
}
3.2 响应DTO(返回数据给前端)
package com.example.library.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.math.BigDecimal;
/**
* 图书详情响应DTO
*/
@Data
@ApiModel("图书详情")
public class BookDetailDTO {
@ApiModelProperty("图书ID")
private String id;
@ApiModelProperty("图书名称")
private String bookName;
@ApiModelProperty("作者")
private String author;
@ApiModelProperty("价格")
private BigDecimal price;
@ApiModelProperty("库存状态")
private String stockStatus; // "充足"、"紧张"、"缺货"
@ApiModelProperty("是否可借")
private Boolean borrowable;
}
为什么要用DTO?
- Entity管数据:与数据库表结构对应
- DTO管传输:与前端接口对应,可以包含计算字段、格式化数据
- 解耦合:数据库表结构变化不影响接口
- 安全性:避免暴露敏感字段(如deleted、create_time)
第4步:创建Repository数据访问层
Repository负责与数据库交互,提供基本的CRUD操作。
package com.example.library.repository;
import com.example.library.entity.Book;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;
/**
* 图书数据访问层
*/
public interface BookRepository extends JpaRepository<Book, String> {
/**
* 根据ISBN查找图书
*/
Book findByIsbn(String isbn);
/**
* 根据作者模糊查询
*/
List<Book> findByAuthorContaining(String author);
/**
* 查询库存不足的图书
*/
@Query("SELECT b FROM Book b WHERE b.stock < :threshold AND b.deleted = 0")
List<Book> findLowStockBooks(@Param("threshold") Integer threshold);
/**
* 统计图书总数
*/
@Query("SELECT COUNT(b) FROM Book b WHERE b.deleted = 0")
Long countByDeleted();
}
Repository的特点:
- 继承
JpaRepository获得基础CRUD方法 - 使用方法命名规则自动生成SQL(如
findByAuthorContaining) - 使用
@Query注解自定义复杂查询 - 只负责数据访问,不包含业务逻辑
第5步:创建Service业务逻辑层
Service负责业务逻辑处理,连接Controller和Repository。
5.1 Service接口
package com.example.library.service;
import com.example.library.dto.AddBookDTO;
import com.example.library.dto.BookDetailDTO;
import com.example.library.entity.Book;
import java.util.List;
/**
* 图书业务逻辑接口
*/
public interface BookService {
/**
* 新增图书
*/
boolean addBook(AddBookDTO dto);
/**
* 获取图书详情
*/
BookDetailDTO getBookDetail(String bookId);
/**
* 借阅图书
*/
boolean borrowBook(String bookId, String readerName);
/**
* 获取库存不足的图书列表
*/
List<Book> getLowStockBooks(Integer threshold);
}
5.2 Service实现类
package com.example.library.service.impl;
import com.example.library.dto.AddBookDTO;
import com.example.library.dto.BookDetailDTO;
import com.example.library.entity.Book;
import com.example.library.repository.BookRepository;
import com.example.library.service.BookService;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Date;
import java.util.List;
import java.util.UUID;
/**
* 图书业务逻辑实现
*/
@Transactional
@Log4j2
@Service
public class BookServiceImpl implements BookService {
@Autowired
private BookRepository bookRepository;
@Override
public boolean addBook(AddBookDTO dto) {
try {
// 1. 检查ISBN是否已存在
Book existingBook = bookRepository.findByIsbn(dto.getIsbn());
if (existingBook != null) {
log.warn("ISBN已存在: {}", dto.getIsbn());
return false;
}
// 2. 创建图书实体
Book book = new Book();
book.setId(UUID.randomUUID().toString());
book.setBookName(dto.getBookName());
book.setAuthor(dto.getAuthor());
book.setIsbn(dto.getIsbn());
book.setPrice(dto.getPrice());
book.setStock(dto.getStock());
book.setDeleted(0);
book.setCreateTime(new Date());
book.setUpdateTime(new Date());
// 3. 保存到数据库
bookRepository.save(book);
return true;
} catch (Exception e) {
log.error("新增图书失败", e);
return false;
}
}
@Override
public BookDetailDTO getBookDetail(String bookId) {
// 1. 查询图书
Book book = bookRepository.findById(bookId).orElse(null);
if (book == null) {
return null;
}
// 2. Entity转DTO
BookDetailDTO dto = new BookDetailDTO();
dto.setId(book.getId());
dto.setBookName(book.getBookName());
dto.setAuthor(book.getAuthor());
dto.setPrice(book.getPrice());
// 3. 计算库存状态
if (book.getStock() == 0) {
dto.setStockStatus("缺货");
dto.setBorrowable(false);
} else if (book.getStock() < 5) {
dto.setStockStatus("紧张");
dto.setBorrowable(true);
} else {
dto.setStockStatus("充足");
dto.setBorrowable(true);
}
return dto;
}
@Override
public boolean borrowBook(String bookId, String readerName) {
try {
// 1. 查询图书
Book book = bookRepository.findById(bookId).orElse(null);
if (book == null) {
log.warn("图书不存在: {}", bookId);
return false;
}
// 2. 检查库存
if (book.getStock() <= 0) {
log.warn("库存不足: {}", book.getBookName());
return false;
}
// 3. 扣减库存
book.setStock(book.getStock() - 1);
book.setUpdateTime(new Date());
bookRepository.save(book);
// 4. 创建借阅记录(省略)
// ...
return true;
} catch (Exception e) {
log.error("借阅图书失败", e);
return false;
}
}
@Override
public List<Book> getLowStockBooks(Integer threshold) {
return bookRepository.findLowStockBooks(threshold);
}
}
Service的职责:
- 业务逻辑处理(如库存检查、状态计算)
- 事务管理(
@Transactional) - 异常处理和日志记录
- Entity与DTO之间的转换
第6步:创建Controller控制器层
Controller负责接收HTTP请求,调用Service处理,返回响应。
package com.example.library.controller;
import com.example.library.dto.AddBookDTO;
import com.example.library.dto.BookDetailDTO;
import com.example.library.entity.Book;
import com.example.library.service.BookService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 图书管理接口
*/
@Log4j2
@RestController
@RequestMapping("/api/book")
@Api(tags = "图书管理")
public class BookController {
@Autowired
private BookService bookService;
/**
* 新增图书
* POST /api/book/add
*/
@PostMapping("/add")
@ApiOperation("新增图书")
public boolean addBook(@RequestBody AddBookDTO dto) {
log.info("新增图书: {}", dto.getBookName());
return bookService.addBook(dto);
}
/**
* 获取图书详情
* GET /api/book/detail/{id}
*/
@GetMapping("/detail/{id}")
@ApiOperation("获取图书详情")
public BookDetailDTO getBookDetail(@PathVariable String id) {
log.info("查询图书详情: {}", id);
return bookService.getBookDetail(id);
}
/**
* 借阅图书
* POST /api/book/borrow
*/
@PostMapping("/borrow")
@ApiOperation("借阅图书")
public boolean borrowBook(@RequestParam String bookId,
@RequestParam String readerName) {
log.info("借阅图书: bookId={}, reader={}", bookId, readerName);
return bookService.borrowBook(bookId, readerName);
}
/**
* 获取库存不足的图书
* GET /api/book/low-stock
*/
@GetMapping("/low-stock")
@ApiOperation("获取库存不足的图书")
public List<Book> getLowStockBooks(@RequestParam(defaultValue = "5") Integer threshold) {
log.info("查询库存不足图书: threshold={}", threshold);
return bookService.getLowStockBooks(threshold);
}
}
Controller的职责:
- 定义REST接口(URL、请求方法、参数)
- 参数校验和绑定
- 调用Service处理业务
- 返回响应数据
- 记录接口访问日志
第7步:创建SQL配置类(可选)
对于复杂的统计查询、多表关联,可以使用YAML配置SQL,提高可维护性。
7.1 YAML配置文件
# src/main/resources/sql/book-sql.yml
book:
# 图书借阅统计
borrow-statistics:
columns: |
SELECT
b.book_name,
b.author,
COUNT(bb.id) AS borrow_count,
SUM(CASE WHEN bb.status = 0 THEN 1 ELSE 0 END) AS borrowing_count
tables: |
FROM book b
LEFT JOIN book_borrow bb ON b.id = bb.book_id AND bb.deleted = 0
condition: |
WHERE b.deleted = 0
AND b.publish_date >= :startDate
GROUP BY b.id, b.book_name, b.author
ORDER BY borrow_count DESC
# 热门图书排行
popular-books:
columns: |
SELECT
b.id,
b.book_name,
b.author,
COUNT(bb.id) AS borrow_times
tables: |
FROM book b
INNER JOIN book_borrow bb ON b.id = bb.book_id
condition: |
WHERE b.deleted = 0
AND bb.borrow_date >= :startDate
AND bb.borrow_date <= :endDate
GROUP BY b.id
ORDER BY borrow_times DESC
LIMIT 10
7.2 SQL配置类
package com.example.library.sql;
import com.gri.iot.common.sourceloader.YamlPropertySourceFacetory;
import com.gri.iot.common.sql.SqlBody;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
/**
* 图书SQL配置类
*/
@Data
@PropertySource(value = "classpath:/sql/book-sql.yml", factory = YamlPropertySourceFacetory.class)
@Configuration
@ConfigurationProperties(prefix = "book")
public class BookSqlConfig {
/**
* 图书借阅统计
*/
private SqlBody borrowStatistics;
/**
* 热门图书排行
*/
private SqlBody popularBooks;
}
7.3 使用SQL配置
@Service
public class BookReportServiceImpl implements BookReportService {
@Autowired
private BookSqlConfig bookSqlConfig;
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public List<Map<String, Object>> getBorrowStatistics(String startDate) {
// 1. 获取SQL配置
SqlBody sqlBody = bookSqlConfig.getBorrowStatistics();
// 2. 组装SQL
String sql = sqlBody.getColumns() +
sqlBody.getTables() +
sqlBody.getCondition();
// 3. 执行查询
return jdbcTemplate.queryForList(sql, startDate);
}
}
什么时候用YAML配置SQL?
- ✅ 复杂的多表关联查询
- ✅ 需要动态条件拼接
- ✅ 频繁调整的统计报表
- ✅ SQL语句很长(超过50行)
什么时候用JPA?
- ✅ 简单的CRUD操作
- ✅ 单表查询
- ✅ 方法命名规则可以满足的需求
各层之间的调用关系
┌─────────────────────────────────────────────────────────┐
│ 前端(Vue/React/小程序) │
└──────────────────────┬──────────────────────────────────┘
│ HTTP请求
▼
┌─────────────────────────────────────────────────────────┐
│ Controller层(控制器) │
│ - 接收请求参数 │
│ - 调用Service │
│ - 返回响应 │
└──────────────────────┬──────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ Service层(业务逻辑) │
│ - 业务逻辑处理 │
│ - 事务管理 │
│ - Entity ↔ DTO 转换 │
└─────────────┬─────────────────────────┬─────────────────┘
│ │
▼ ▼
┌─────────────────────────┐ ┌──────────────────────────┐
│ Repository层(JPA) │ │ SQL配置类(YAML) │
│ - 简单CRUD │ │ - 复杂查询 │
│ - 方法命名规则 │ │ - 统计报表 │
└────────────┬────────────┘ └────────────┬─────────────┘
│ │
└───────────┬───────────────┘
▼
┌─────────────────────────┐
│ 数据库(PostgreSQL) │
└─────────────────────────┘
总结
Java后端开发的标准流程可以总结为:
| 步骤 | 名称 | 作用 | 技术选型 |
|---|---|---|---|
| 1 | 数据库设计 | 数据存储基础 | PostgreSQL/MySQL |
| 2 | Entity实体类 | ORM映射,数据存储 | JPA @Entity |
| 3 | DTO对象 | 数据传输,前后端交互 | @Data, @ApiModel |
| 4 | Repository层 | 数据访问,简单CRUD | JpaRepository |
| 5 | Service层 | 业务逻辑,事务管理 | @Service, @Transactional |
| 6 | Controller层 | 接口定义,请求处理 | @RestController |
| 7 | SQL配置类 | 复杂查询,统计报表 | YAML + SqlBody |
核心原则
- 单一职责:每层只做自己该做的事
- 向下依赖:Controller → Service → Repository → Database
- Entity与DTO分离:数据存储与数据传输解耦
- 简洁至上:简单用JPA,复杂用YAML SQL
- 异常处理:Service层统一处理异常,记录日志
最佳实践
- ✅ Entity类只包含字段映射,不包含业务逻辑
- ✅ Service接口和实现类分离,便于扩展
- ✅ 使用Optional避免NPE
- ✅ 使用@Transactional管理事务
- ✅ Controller只做参数绑定和响应,不做业务逻辑
- ✅ 复杂SQL使用YAML配置,便于维护
- ✅ 所有接口添加Swagger文档注解
希望这篇文章能帮助你理解Java后端开发的完整流程!有问题欢迎讨论。