Spring Boot入门实战:从零搭建电商订单系统,3天上手微服务开发
Spring Boot入门实战:从零搭建电商订单系统,3天上手微服务开发
开篇:那个加班到凌晨的周末
还记得我刚入职那会儿,接到一个任务:开发一个电商订单系统。那时候用的是传统的SSM框架(Spring + SpringMVC + MyBatis),光是搭建项目环境就花了我整整一天。
配置XML文件、配置数据源、配置事务管理器、配置视图解析器…各种配置文件看得我头昏眼花。好不容易跑起来,结果一个Bean注入失败,排查了半天才发现是包扫描路径写错了。
更崩溃的是:每次加一个新功能,都要改一堆配置文件,生怕改错一个地方整个项目就起不来。那时候我就在想:为什么不能有一个"开箱即用"的框架,让我专注于写业务代码,而不是折腾配置?
后来我遇到了Spring Boot,那一刻我仿佛打开了新世界的大门。今天,我就以电商订单系统为例,带你一步步掌握Spring Boot的核心用法。
什么是Spring Boot?
通俗解释
想象一下,你要组装一台电脑:
-
传统Spring框架:就像买散件组装,你需要自己选CPU、主板、内存、显卡,还要考虑它们之间的兼容性,接线、驱动安装…对新手来说简直是噩梦。
-
Spring Boot:就像买品牌整机,出厂前已经帮你把所有硬件都组装好了,系统也预装好了,你只需要开机就能用。当然,如果你有特殊需求,也可以自己更换配件。
Spring Boot的本质:它不是对Spring功能的增强,而是对Spring的"封装"和"简化"。它通过约定优于配置的理念,把繁琐的配置工作自动化了。
核心特性
| 特性 | 说明 | 类比 |
|---|---|---|
| 自动装配 | 根据classpath下的jar包自动配置Bean | 智能手机自动识别SIM卡并配置网络 |
| 起步依赖 | 打包常用依赖,简化依赖管理 | 肯德基套餐,不用一个个点菜 |
| 内嵌服务器 | 内置Tomcat,无需部署WAR包 | 笔记本自带摄像头,不用外接 |
| 生产就绪 | 提供健康检查、监控等开箱即用功能 | 新车出厂自带保养提醒 |
为什么要用Spring Boot?
传统方案 vs Spring Boot
场景:开发一个订单查询接口
传统SSM方式:
<!-- web.xml 配置 -->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
</servlet>
<!-- spring-mvc.xml 配置 -->
<context:component-scan base-package="com.example.controller"/>
<mvc:annotation-driven/>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"/>
<property name="suffix" value=".jsp"/>
</bean>
<!-- applicationContext.xml 配置 -->
<context:component-scan base-package="com.example.service"/>
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/ecommerce"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean>
Spring Boot方式:
# application.yml(只需这一个配置文件)
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/ecommerce
username: root
password: 123456
对比总结
| 对比项 | 传统SSM | Spring Boot |
|---|---|---|
| 配置文件数量 | 5-10个XML | 1个yml/properties |
| 项目启动时间 | 30-60秒 | 5-15秒 |
| 部署方式 | 需要Tomcat容器 | java -jar直接运行 |
| 开发效率 | 低(大量配置) | 高(专注业务) |
| 学习曲线 | 陡峭(需理解大量配置) | 平缓(约定优于配置) |
怎么用Spring Boot?
实战:搭建电商订单系统
我们通过一个完整的订单系统来演示Spring Boot的核心用法。
1. 项目初始化
方式一:使用Spring Initializr(推荐)
- 访问 https://start.spring.io/
- 选择:Maven Project + Java + Spring Boot 2.7.x
- 添加依赖:Spring Web, Spring Data JPA, MySQL Driver, Lombok
- 点击Generate下载项目压缩包
方式二:IDEA创建
File -> New -> Project -> Spring Initializr
2. 项目结构
ecommerce-order-system/
├── src/main/java/com/example/order/
│ ├── OrderApplication.java # 启动类
│ ├── controller/
│ │ └── OrderController.java # 控制层
│ ├── service/
│ │ ├── OrderService.java # 服务接口
│ │ └── impl/
│ │ └── OrderServiceImpl.java # 服务实现
│ ├── repository/
│ │ └── OrderRepository.java # 数据访问层
│ ├── entity/
│ │ └── Order.java # 实体类
│ └── dto/
│ └── OrderResponse.java # 响应DTO
├── src/main/resources/
│ ├── application.yml # 配置文件
│ └── data.sql # 初始化数据
└── pom.xml # Maven配置
3. 核心代码实现
① 启动类(OrderApplication.java)
package com.example.order;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* 订单系统启动类
* @SpringBootApplication:组合注解,包含以下三个注解的功能:
* - @Configuration:标记为配置类
* - @EnableAutoConfiguration:开启自动装配
* - @ComponentScan:自动扫描组件
*/
@SpringBootApplication
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
System.out.println("========================================");
System.out.println("订单系统启动成功!访问地址:http://localhost:8080");
System.out.println("========================================");
}
}
② 实体类(Order.java)
package com.example.order.entity;
import lombok.Data;
import javax.persistence.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 订单实体类
* @Data:Lombok注解,自动生成getter/setter/toString等方法
*/
@Data
@Entity
@Table(name = "t_order")
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/** 订单编号 */
@Column(nullable = false, unique = true, length = 32)
private String orderNo;
/** 用户ID */
@Column(nullable = false)
private Long userId;
/** 商品名称 */
@Column(nullable = false, length = 100)
private String productName;
/** 订单金额 */
@Column(nullable = false, precision = 10, scale = 2)
private BigDecimal amount;
/** 订单状态:0-待支付,1-已支付,2-已发货,3-已完成,4-已取消 */
@Column(nullable = false)
private Integer status;
/** 创建时间 */
@Column(nullable = false, updatable = false)
private LocalDateTime createTime;
/** 更新时间 */
@Column(nullable = false)
private LocalDateTime updateTime;
@PrePersist
protected void onCreate() {
createTime = LocalDateTime.now();
updateTime = LocalDateTime.now();
}
@PreUpdate
protected void onUpdate() {
updateTime = LocalDateTime.now();
}
}
③ 数据访问层(OrderRepository.java)
package com.example.order.repository;
import com.example.order.entity.Order;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* 订单数据访问层
* 继承JpaRepository后,自动获得CRUD方法:
* - save():保存
* - findById():根据ID查询
* - findAll():查询所有
* - deleteById():根据ID删除
*/
@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
/**
* 根据用户ID查询订单列表
* Spring Data JPA会自动根据方法名生成SQL
*/
List<Order> findByUserId(Long userId);
/**
* 根据订单状态查询
*/
List<Order> findByStatus(Integer status);
/**
* 自定义查询:根据用户ID和状态查询
*/
List<Order> findByUserIdAndStatus(Long userId, Integer status);
/**
* 使用@Query自定义JPQL查询
*/
@Query("SELECT o FROM Order o WHERE o.userId = :userId AND o.amount > :minAmount")
List<Order> findByUserIdAndAmountGreaterThan(@Param("userId") Long userId,
@Param("minAmount") java.math.BigDecimal minAmount);
}
④ 服务层(OrderService.java 和 OrderServiceImpl.java)
// OrderService.java
package com.example.order.service;
import com.example.order.dto.OrderResponse;
import com.example.order.entity.Order;
import java.util.List;
public interface OrderService {
/**
* 创建订单
*/
Order createOrder(Order order);
/**
* 根据ID查询订单
*/
Order getOrderById(Long id);
/**
* 查询用户的所有订单
*/
List<Order> getOrdersByUserId(Long userId);
/**
* 更新订单状态
*/
Order updateOrderStatus(Long id, Integer status);
/**
* 取消订单
*/
void cancelOrder(Long id);
}
// OrderServiceImpl.java
package com.example.order.service.impl;
import com.example.order.entity.Order;
import com.example.order.repository.OrderRepository;
import com.example.order.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.UUID;
/**
* 订单服务实现类
* @Service:标记为服务层组件,Spring会自动扫描并注册为Bean
* @Transactional:开启事务管理
*/
@Service
@Transactional
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderRepository orderRepository;
@Override
public Order createOrder(Order order) {
// 生成订单编号
String orderNo = generateOrderNo();
order.setOrderNo(orderNo);
// 设置初始状态为待支付
order.setStatus(0);
// 保存订单
Order savedOrder = orderRepository.save(order);
System.out.println("订单创建成功:" + orderNo);
return savedOrder;
}
@Override
public Order getOrderById(Long id) {
// findById返回Optional,需要处理可能为空的情况
return orderRepository.findById(id)
.orElseThrow(() -> new RuntimeException("订单不存在,ID:" + id));
}
@Override
public List<Order> getOrdersByUserId(Long userId) {
return orderRepository.findByUserId(userId);
}
@Override
public Order updateOrderStatus(Long id, Integer status) {
Order order = getOrderById(id);
order.setStatus(status);
return orderRepository.save(order);
}
@Override
public void cancelOrder(Long id) {
Order order = getOrderById(id);
// 只有待支付状态的订单才能取消
if (order.getStatus() != 0) {
throw new RuntimeException("只有待支付状态的订单才能取消");
}
order.setStatus(4); // 设置为已取消
orderRepository.save(order);
}
/**
* 生成订单编号
* 格式:时间戳 + 随机数
*/
private String generateOrderNo() {
return System.currentTimeMillis() + "" + (int)(Math.random() * 10000);
}
}
⑤ 控制层(OrderController.java)
package com.example.order.controller;
import com.example.order.entity.Order;
import com.example.order.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 订单控制器
* @RestController:组合注解 = @Controller + @ResponseBody
* - @Controller:标记为控制器
* - @ResponseBody:返回值自动转为JSON
*/
@RestController
@RequestMapping("/api/orders")
public class OrderController {
@Autowired
private OrderService orderService;
/**
* 创建订单
* POST /api/orders
*/
@PostMapping
public ResponseEntity<Map<String, Object>> createOrder(@RequestBody Order order) {
Order createdOrder = orderService.createOrder(order);
Map<String, Object> result = new HashMap<>();
result.put("code", 200);
result.put("message", "订单创建成功");
result.put("data", createdOrder);
return ResponseEntity.ok(result);
}
/**
* 根据ID查询订单
* GET /api/orders/{id}
*/
@GetMapping("/{id}")
public ResponseEntity<Map<String, Object>> getOrderById(@PathVariable Long id) {
Order order = orderService.getOrderById(id);
Map<String, Object> result = new HashMap<>();
result.put("code", 200);
result.put("message", "查询成功");
result.put("data", order);
return ResponseEntity.ok(result);
}
/**
* 查询用户的所有订单
* GET /api/orders/user/{userId}
*/
@GetMapping("/user/{userId}")
public ResponseEntity<Map<String, Object>> getOrdersByUserId(@PathVariable Long userId) {
List<Order> orders = orderService.getOrdersByUserId(userId);
Map<String, Object> result = new HashMap<>();
result.put("code", 200);
result.put("message", "查询成功");
result.put("data", orders);
result.put("total", orders.size());
return ResponseEntity.ok(result);
}
/**
* 更新订单状态
* PUT /api/orders/{id}/status
*/
@PutMapping("/{id}/status")
public ResponseEntity<Map<String, Object>> updateOrderStatus(
@PathVariable Long id,
@RequestParam Integer status) {
Order order = orderService.updateOrderStatus(id, status);
Map<String, Object> result = new HashMap<>();
result.put("code", 200);
result.put("message", "订单状态更新成功");
result.put("data", order);
return ResponseEntity.ok(result);
}
/**
* 取消订单
* DELETE /api/orders/{id}
*/
@DeleteMapping("/{id}")
public ResponseEntity<Map<String, Object>> cancelOrder(@PathVariable Long id) {
orderService.cancelOrder(id);
Map<String, Object> result = new HashMap<>();
result.put("code", 200);
result.put("message", "订单取消成功");
return ResponseEntity.ok(result);
}
}
⑥ 配置文件(application.yml)
# 服务器配置
server:
port: 8080
servlet:
context-path: /
# 应用名称
spring:
application:
name: order-system
# 数据源配置
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/ecommerce?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: 123456
# HikariCP连接池配置(Spring Boot默认连接池)
hikari:
minimum-idle: 5
maximum-pool-size: 20
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
# JPA配置
jpa:
hibernate:
ddl-auto: update # 自动更新表结构
show-sql: true # 显示SQL语句
properties:
hibernate:
format_sql: true # 格式化SQL
dialect: org.hibernate.dialect.MySQL8Dialect
# 日志配置
logging:
level:
root: INFO
com.example.order: DEBUG
org.hibernate.SQL: DEBUG
org.hibernate.type.descriptor.sql.BasicBinder: TRACE
⑦ pom.xml依赖配置
<?xml version="1.0" encoding="UTF-8"?>
<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
n http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.14</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>order-system</artifactId>
<version>1.0.0</version>
<name>order-system</name>
<description>电商订单系统</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- Spring Web:提供Web开发支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Data JPA:提供数据访问支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Lombok:简化Java代码 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Spring Boot Test:测试支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
4. 测试接口
启动项目后,使用Postman或curl测试接口:
① 创建订单
curl -X POST http://localhost:8080/api/orders \
-H "Content-Type: application/json" \
-d '{
"userId": 1,
"productName": "iPhone 15 Pro",
"amount": 7999.00
}'
响应:
{
"code": 200,
"message": "订单创建成功",
"data": {
"id": 1,
"orderNo": "17031234567891234",
"userId": 1,
"productName": "iPhone 15 Pro",
"amount": 7999.00,
"status": 0,
"createTime": "2024-01-01T10:30:00",
"updateTime": "2024-01-01T10:30:00"
}
}
② 查询订单
curl http://localhost:8080/api/orders/1
③ 查询用户订单
curl http://localhost:8080/api/orders/user/1
④ 更新订单状态
curl -X PUT "http://localhost:8080/api/orders/1/status?status=1"
⑤ 取消订单
curl -X DELETE http://localhost:8080/api/orders/1
实战踩坑:那些年我踩过的坑
坑1:@Autowired注入失败
现象:
@Service
public class OrderServiceImpl {
@Autowired
private OrderRepository orderRepository;
// 启动时报错:Field orderRepository required a bean of type 'OrderRepository' that could not be found
}
原因: OrderRepository没有被Spring扫描到。
解决方案:
// 方式1:在Repository接口上添加@Repository注解
@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
}
// 方式2:确保Repository在启动类的包或子包下
@SpringBootApplication
// 启动类在com.example.order,Repository应该在com.example.order.repository下
public class OrderApplication {
}
// 方式3:如果不在子包,手动指定扫描路径
@SpringBootApplication(scanBasePackages = "com.example")
public class OrderApplication {
}
坑2:事务不生效
现象:
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderRepository orderRepository;
@Transactional
public void createOrder(Order order) {
orderRepository.save(order);
// 这里抛出异常,但订单仍然被保存了,事务没有回滚!
int i = 1 / 0;
}
}
原因: Spring事务基于AOP代理,同类内部方法调用不会经过代理,事务不生效。
解决方案:
// 方式1:将事务方法提取到另一个Service中
@Service
public class OrderTransactionService {
@Autowired
private OrderRepository orderRepository;
@Transactional
public void saveOrder(Order order) {
orderRepository.save(order);
int i = 1 / 0; // 这里会回滚
}
}
@Service
public class OrderServiceImpl {
@Autowired
private OrderTransactionService transactionService;
public void createOrder(Order order) {
transactionService.saveOrder(order);
}
}
// 方式2:注入自身,通过代理调用
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private OrderService self; // 注入代理对象
public void createOrder(Order order) {
self.saveOrderWithTransaction(order);
}
@Transactional
@Override
public void saveOrderWithTransaction(Order order) {
orderRepository.save(order);
int i = 1 / 0;
}
}
坑3:日期时间格式化问题
现象:
{
"createTime": "2024-01-01T10:30:00", // 前端不想要这种格式
"updateTime": "2024-01-01T10:30:00"
}
解决方案:
// 方式1:在实体类字段上使用@JsonFormat
@Data
@Entity
public class Order {
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
@Column(nullable = false, updatable = false)
private LocalDateTime createTime;
}
// 方式2:全局配置(推荐)
@Configuration
public class JacksonConfig {
@Bean
public Jackson2ObjectMapperBuilderCustomizer customizer() {
return builder -> {
builder.serializerByType(LocalDateTime.class,
new JsonSerializer<LocalDateTime>() {
@Override
public void serialize(LocalDateTime value, JsonGenerator gen, SerializerProvider serializers)
throws IOException {
gen.writeString(value.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
}
});
};
}
}
// 方式3:配置文件方式(最简单)
spring:
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
坑4:循环依赖问题
现象:
@Service
public class OrderService {
@Autowired
private UserService userService;
}
@Service
public class UserService {
@Autowired
private OrderService orderService; // 循环依赖,启动报错!
}
解决方案:
// 方式1:使用@Lazy注解(延迟加载)
@Service
public class UserService {
@Autowired
@Lazy
private OrderService orderService;
}
// 方式2:重构代码,消除循环依赖(推荐)
// 将公共逻辑抽取到第三个Service中
@Service
public class CommonService {
// 公共逻辑
}
@Service
public class OrderService {
@Autowired
private CommonService commonService;
}
@Service
public class UserService {
@Autowired
private CommonService commonService;
}
坑5:接口返回null问题
现象:
@GetMapping("/{id}")
public Order getOrderById(@PathVariable Long id) {
return orderRepository.findById(id).orElse(null);
// 当订单不存在时返回null,前端收到"null"字符串
}
解决方案:
// 方式1:统一返回格式(推荐)
@Data
@AllArgsConstructor
public class ApiResponse<T> {
private Integer code;
private String message;
private T data;
public static <T> ApiResponse<T> success(T data) {
return new ApiResponse<>(200, "success", data);
}
public static <T> ApiResponse<T> error(String message) {
return new ApiResponse<>(500, message, null);
}
}
@GetMapping("/{id}")
public ApiResponse<Order> getOrderById(@PathVariable Long id) {
Order order = orderRepository.findById(id)
.orElseThrow(() -> new RuntimeException("订单不存在"));
return ApiResponse.success(order);
}
// 方式2:全局异常处理
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(RuntimeException.class)
public ApiResponse<String> handleRuntimeException(RuntimeException e) {
return ApiResponse.error(e.getMessage());
}
}
延伸阅读:下一步学什么?
掌握了Spring Boot基础后,你可以继续学习以下方向:
1. Spring Boot进阶
- Spring Boot Actuator:应用监控和健康检查
- Spring Boot Admin:可视化的监控管理界面
- Profile多环境配置:开发/测试/生产环境切换
- 自定义Starter:封装自己的启动器
2. 微服务架构
- Spring Cloud:服务注册发现、配置中心、熔断降级
- Nacos:阿里巴巴开源的服务治理框架
- Docker:容器化部署
- Kubernetes:容器编排
3. 数据库进阶
- MyBatis-Plus:增强版MyBatis,开发效率更高
- Redis:缓存中间件
- 分库分表:ShardingSphere
- 读写分离:主从复制配置
4. 消息队列
- RabbitMQ:可靠的消息队列
- Kafka:高吞吐量的消息队列
- RocketMQ:阿里巴巴开源的消息中间件
5. 安全认证
- Spring Security:强大的安全框架
- JWT:无状态的token认证
- OAuth2:第三方登录授权
推荐学习资源
| 资源类型 | 推荐内容 |
|---|---|
| 官方文档 | https://spring.io/projects/spring-boot |
| 视频教程 | B站搜索"Spring Boot尚硅谷" |
| 书籍 | 《Spring Boot实战》、《Spring Boot微服务实战》 |
| 练手项目 | GitHub搜索"spring-boot-demo" |
写在最后
Spring Boot之所以流行,是因为它真正做到了简化开发,让开发者专注于业务逻辑。对于1-3年经验的Java开发者来说,掌握Spring Boot是进阶的必经之路。
记住:
- 不要死记硬背注解,理解其背后的原理
- 多动手实践,光看不练假把式
- 遇到问题先看官方文档,再查Stack Overflow
- 保持好奇心,探索更多Spring生态
如果你在实践过程中遇到问题,欢迎在评论区留言,我们一起交流进步!
加油,未来的架构师! 🚀
更多推荐
所有评论(0)