admin 管理员组文章数量: 1184232
Spring Boot实战:从零搭建电商订单系统,新手也能轻松上手的保姆级教程
开篇:一个真实的电商痛点
"小王,这个订单接口怎么又超时了?用户投诉说支付成功后订单状态没更新!" "老张,新来的实习生配置环境配了一天还没跑起来,Spring的配置文件太复杂了..." "这次双十一大促,我们的服务能扛得住吗?上次系统崩溃的教训还历历在目..."
如果你在电商公司待过,这些对话一定不陌生。订单系统作为电商的核心,承载着用户下单、支付、发货等关键业务流程。但传统的Java开发方式,配置繁琐、部署复杂、启动缓慢,让很多开发者头疼不已。
今天,我要带大家认识一个能解决这些痛点的神器——Spring Boot。它就像给你的Java项目装上了"自动驾驶"系统,让你专注于业务逻辑,而不是和配置文件较劲。
什么是Spring Boot?用大白话讲给你听
通俗解释
想象一下,你要做一道菜:
-
传统Spring开发:就像从零开始做菜,你需要自己买锅、准备调料、切菜、掌握火候...每一步都要亲力亲为,稍有不对就做不好。
-
Spring Boot:就像点了一份"预制菜套餐",锅碗瓢盆、调料配比都给你准备好了,你只需要按照简单步骤加热摆盘,就能做出一道美味佳肴。
技术定义
Spring Boot是Spring开源组织下的子项目,它是Spring组件的一站式解决方案,主要简化了Spring应用的初始搭建和开发过程。它通过"约定优于配置"的理念,提供了大量的默认配置,让开发者能够快速创建独立运行的、生产级别的Spring应用程序。
核心特性
| 特性 | 说明 | 类比 | |------|------|------| | 自动配置 | 根据类路径下的jar包和类,自动配置Bean | 智能家居自动调节灯光和温度 | | 起步依赖 | 打包常用依赖,简化依赖管理 | 套餐包含主食+汤+饮料 | | 内嵌服务器 | 内置Tomcat/Jetty,无需部署WAR包 | 自带发动机的房车,随处可停 | | 生产就绪 | 提供监控、健康检查等开箱即用功能 | 新车出厂自带安全气囊和ABS |
为什么要用Spring Boot?对比才知道差距
传统Spring开发的痛点
<!-- 传统Spring的applicationContext.xml配置(吓人不?) -->
<beans xmlns="http://www.springframework/schema/beans"
xmlns:xsi="http://www.w3/2001/XMLSchema-instance"
xmlns:context="http://www.springframework/schema/context"
xmlns:mvc="http://www.springframework/schema/mvc"
xsi:schemaLocation="...">
<!-- 扫描组件 -->
<context:component-scan base-package="com.ecommerce"/>
<!-- 数据源配置 -->
<bean id="dataSource" class="org.apachemons.dbcp2.BasicDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/ecommerce"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean>
<!-- MyBatis配置 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="mapperLocations" value="classpath:mapper/*.xml"/>
</bean>
<!-- 事务管理 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 还有几十行配置... -->
</beans>
问题清单:
- ❌ 配置文件冗长,新手看着就头晕
- ❌ 依赖版本冲突,jar包地狱
- ❌ 部署需要Tomcat,打包成WAR繁琐
- ❌ 启动慢,开发效率低
- ❌ 缺乏监控,出问题难排查
Spring Boot的优势
# Spring Boot的application.yml配置(清爽多了!)
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/ecommerce
username: root
password: 123456
mybatis:
mapper-locations: classpath:mapper/*.xml
优势清单:
- ✅ 零XML配置,约定优于配置
- ✅ 起步依赖管理版本,告别冲突
- ✅ 内嵌Tomcat,java -jar直接运行
- ✅ 快速启动,开发体验极佳
- ✅ Actuator监控,健康状态一目了然
电商场景对比
| 场景 | 传统Spring | Spring Boot | 效果对比 | |------|-----------|-------------|----------| | 新人入职 | 配环境2天 | 配环境2小时 | 效率提升8倍 | | 接口开发 | 写配置1小时 | 写配置5分钟 | 效率提升12倍 | | 服务部署 | 打包+部署30分钟 | java -jar秒启动 | 速度提升180倍 | | 问题排查 | 看日志半天 | Actuator看监控 | 效率提升4倍 |
怎么用Spring Boot?实战电商订单系统
第一步:创建项目(3分钟搞定)
方式一:Spring Initializr(推荐新手)
- 访问 https://start.spring.io/
- 选择项目配置:
- Project: Maven
- Language: Java
- Spring Boot: 2.7.18(稳定版)
- Packaging: Jar
- Java: 8或11
- 添加依赖(电商订单系统必备):
- Spring Web(Web开发)
- MyBatis Framework(数据库操作)
- MySQL Driver(数据库驱动)
- Lombok(简化代码)
- Validation(参数校验)
- 点击"GENERATE"下载项目压缩包
方式二:IDEA快速创建
File → New → Project → Spring Initializr
第二步:项目结构一览
ecommerce-order-system/
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/
│ │ │ └── ecommerce/
│ │ │ ├── EcommerceOrderApplication.java # 启动类
│ │ │ ├── controller/ # 控制器层
│ │ │ ├── service/ # 服务层
│ │ │ ├── mapper/ # 数据访问层
│ │ │ ├── entity/ # 实体类
│ │ │ ├── dto/ # 数据传输对象
│ │ │ └── config/ # 配置类
│ │ └── resources/
│ │ ├── application.yml # 配置文件
│ │ ├── mapper/ # MyBatis映射文件
│ │ └── static/ # 静态资源
│ └── test/
└── pom.xml # Maven配置
第三步:核心代码实现
1. 实体类(订单表)
package com.ecommerce.entity;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Positive;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 订单实体类
* 使用Lombok简化getter/setter/toString等方法
*/
@Data
public class Order {
/**
* 订单ID(主键)
*/
private Long id;
/**
* 订单编号(业务主键)
*/
@NotBlank(message = "订单编号不能为空")
private String orderNo;
/**
* 用户ID
*/
@NotNull(message = "用户ID不能为空")
private Long userId;
/**
* 商品名称
*/
@NotBlank(message = "商品名称不能为空")
private String productName;
/**
* 商品数量
*/
@NotNull(message = "商品数量不能为空")
@Positive(message = "商品数量必须大于0")
private Integer quantity;
/**
* 订单金额
*/
@NotNull(message = "订单金额不能为空")
@Positive(message = "订单金额必须大于0")
private BigDecimal amount;
/**
* 订单状态:0-待支付,1-已支付,2-已发货,3-已完成,4-已取消
*/
private Integer status;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新时间
*/
private LocalDateTime updateTime;
}
2. 数据传输对象(DTO)
package com.ecommerce.dto;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Positive;
import java.io.Serializable;
import java.math.BigDecimal;
/**
* 创建订单请求DTO
* 用于接收前端传入的订单数据
*/
@Data
public class CreateOrderRequest implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 用户ID
*/
@NotNull(message = "用户ID不能为空")
private Long userId;
/**
* 商品名称
*/
@NotBlank(message = "商品名称不能为空")
private String productName;
/**
* 商品数量
*/
@NotNull(message = "商品数量不能为空")
@Positive(message = "商品数量必须大于0")
private Integer quantity;
/**
* 商品单价
*/
@NotNull(message = "商品单价不能为空")
@Positive(message = "商品单价必须大于0")
private BigDecimal unitPrice;
}
package com.ecommerce.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 订单响应DTO
* 用于返回给前端的订单数据
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class OrderResponse implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 订单ID
*/
private Long id;
/**
* 订单编号
*/
private String orderNo;
/**
* 商品名称
*/
private String productName;
/**
* 商品数量
*/
private Integer quantity;
/**
* 订单金额
*/
private BigDecimal amount;
/**
* 订单状态
*/
private Integer status;
/**
* 订单状态描述
*/
private String statusDesc;
/**
* 创建时间
*/
private LocalDateTime createTime;
}
3. 数据访问层(Mapper)
package com.ecommerce.mapper;
import com.ecommerce.entity.Order;
import org.apache.ibatis.annotations.*;
import java.time.LocalDateTime;
import java.util.List;
/**
* 订单数据访问接口
* 使用注解方式编写SQL,简单直观
*/
@Mapper
public interface OrderMapper {
/**
* 插入订单
*/
@Insert("INSERT INTO orders (order_no, user_id, product_name, quantity, amount, status, create_time, update_time) " +
"VALUES (#{orderNo}, #{userId}, #{productName}, #{quantity}, #{amount}, #{status}, #{createTime}, #{updateTime})")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insert(Order order);
/**
* 根据ID查询订单
*/
@Select("SELECT * FROM orders WHERE id = #{id}")
Order selectById(Long id);
/**
* 根据订单编号查询订单
*/
@Select("SELECT * FROM orders WHERE order_no = #{orderNo}")
Order selectByOrderNo(String orderNo);
/**
* 查询用户的所有订单
*/
@Select("SELECT * FROM orders WHERE user_id = #{userId} ORDER BY create_time DESC")
List<Order> selectByUserId(Long userId);
/**
* 更新订单状态
*/
@Update("UPDATE orders SET status = #{status}, update_time = #{updateTime} WHERE id = #{id}")
int updateStatus(@Param("id") Long id, @Param("status") Integer status, @Param("updateTime") LocalDateTime updateTime);
/**
* 取消订单
*/
@Update("UPDATE orders SET status = 4, update_time = #{updateTime} WHERE id = #{id}")
int cancelOrder(@Param("id") Long id, @Param("updateTime") LocalDateTime updateTime);
}
4. 服务层(Service)
package com.ecommerce.service;
import com.ecommerce.dto.CreateOrderRequest;
import com.ecommerce.dto.OrderResponse;
import com.ecommerce.entity.Order;
import com.ecommerce.mapper.OrderMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
/**
* 订单服务类
* 包含订单的核心业务逻辑
*/
@Slf4j
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
/**
* 创建订单
* @Transactional注解确保事务一致性,失败自动回滚
*/
@Transactional(rollbackFor = Exception.class)
public OrderResponse createOrder(CreateOrderRequest request) {
log.info("开始创建订单,用户ID:{},商品:{}", request.getUserId(), request.getProductName());
// 1. 生成订单编号(使用UUID保证唯一性)
String orderNo = generateOrderNo();
// 2. 计算订单金额
BigDecimal amount = request.getUnitPrice().multiply(new BigDecimal(request.getQuantity()));
// 3. 构建订单对象
Order order = new Order();
order.setOrderNo(orderNo);
order.setUserId(request.getUserId());
order.setProductName(request.getProductName());
order.setQuantity(request.getQuantity());
order.setAmount(amount);
order.setStatus(0); // 待支付
order.setCreateTime(LocalDateTime.now());
order.setUpdateTime(LocalDateTime.now());
// 4. 保存订单到数据库
int rows = orderMapper.insert(order);
if (rows == 0) {
log.error("订单创建失败,orderNo:{}", orderNo);
throw new RuntimeException("订单创建失败");
}
log.info("订单创建成功,订单ID:{},订单编号:{}", order.getId(), orderNo);
// 5. 返回订单响应
return convertToResponse(order);
}
/**
* 根据订单ID查询订单
*/
public OrderResponse getOrderById(Long id) {
log.info("查询订单,订单ID:{}", id);
Order order = orderMapper.selectById(id);
if (order == null) {
log.warn("订单不存在,订单ID:{}", id);
throw new RuntimeException("订单不存在");
}
return convertToResponse(order);
}
/**
* 根据订单编号查询订单
*/
public OrderResponse getOrderByOrderNo(String orderNo) {
log.info("查询订单,订单编号:{}", orderNo);
Order order = orderMapper.selectByOrderNo(orderNo);
if (order == null) {
log.warn("订单不存在,订单编号:{}", orderNo);
throw new RuntimeException("订单不存在");
}
return convertToResponse(order);
}
/**
* 查询用户的所有订单
*/
public List<OrderResponse> getOrdersByUserId(Long userId) {
log.info("查询用户订单,用户ID:{}", userId);
List<Order> orders = orderMapper.selectByUserId(userId);
return orders.stream()
.map(this::convertToResponse)
.collect(Collectors.toList());
}
/**
* 支付订单
*/
@Transactional(rollbackFor = Exception.class)
public OrderResponse payOrder(Long id) {
log.info("开始支付订单,订单ID:{}", id);
// 1. 查询订单
Order order = orderMapper.selectById(id);
if (order == null) {
throw new RuntimeException("订单不存在");
}
// 2. 校验订单状态
if (order.getStatus() != 0) {
throw new RuntimeException("订单状态异常,当前状态:" + getStatusDesc(order.getStatus()));
}
// 3. 更新订单状态为已支付
int rows = orderMapper.updateStatus(id, 1, LocalDateTime.now());
if (rows == 0) {
throw new RuntimeException("支付失败");
}
log.info("订单支付成功,订单ID:{}", id);
// 4. 返回更新后的订单
order.setStatus(1);
return convertToResponse(order);
}
/**
* 发货
*/
@Transactional(rollbackFor = Exception.class)
public OrderResponse shipOrder(Long id) {
log.info("开始发货,订单ID:{}", id);
Order order = orderMapper.selectById(id);
if (order == null) {
throw new RuntimeException("订单不存在");
}
if (order.getStatus() != 1) {
throw new RuntimeException("订单状态异常,当前状态:" + getStatusDesc(order.getStatus()));
}
int rows = orderMapper.updateStatus(id, 2, LocalDateTime.now());
if (rows == 0) {
throw new RuntimeException("发货失败");
}
log.info("订单发货成功,订单ID:{}", id);
order.setStatus(2);
return convertToResponse(order);
}
/**
* 取消订单
*/
@Transactional(rollbackFor = Exception.class)
public OrderResponse cancelOrder(Long id) {
log.info("开始取消订单,订单ID:{}", id);
Order order = orderMapper.selectById(id);
if (order == null) {
throw new RuntimeException("订单不存在");
}
// 只有待支付订单可以取消
if (order.getStatus() != 0) {
throw new RuntimeException("订单状态异常,当前状态:" + getStatusDesc(order.getStatus()));
}
int rows = orderMapper.cancelOrder(id, LocalDateTime.now());
if (rows == 0) {
throw new RuntimeException("取消订单失败");
}
log.info("订单取消成功,订单ID:{}", id);
order.setStatus(4);
return convertToResponse(order);
}
/**
* 生成订单编号
* 格式:ORD + 时间戳 + 随机数
*/
private String generateOrderNo() {
String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
String random = String.valueOf((int)((Math.random() * 9 + 1) * 1000));
return "ORD" + timestamp + random;
}
/**
* 获取订单状态描述
*/
private String getStatusDesc(Integer status) {
switch (status) {
case 0: return "待支付";
case 1: return "已支付";
case 2: return "已发货";
case 3: return "已完成";
case 4: return "已取消";
default: return "未知状态";
}
}
/**
* 将Order实体转换为OrderResponse DTO
*/
private OrderResponse convertToResponse(Order order) {
OrderResponse response = new OrderResponse();
response.setId(order.getId());
response.setOrderNo(order.getOrderNo());
response.setProductName(order.getProductName());
response.setQuantity(order.getQuantity());
response.setAmount(order.getAmount());
response.setStatus(order.getStatus());
response.setStatusDesc(getStatusDesc(order.getStatus()));
response.setCreateTime(order.getCreateTime());
return response;
}
}
5. 控制器层(Controller)
package com.ecommerce.controller;
import com.ecommerce.dto.CreateOrderRequest;
import com.ecommerce.dto.OrderResponse;
import com.ecommerce.service.OrderService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import java.util.List;
/**
* 订单控制器
* 处理HTTP请求,调用Service层处理业务
*/
@Slf4j
@RestController
@RequestMapping("/api/orders")
@Validated
public class OrderController {
@Autowired
private OrderService orderService;
/**
* 创建订单
* POST /api/orders
*/
@PostMapping
public ResponseEntity<OrderResponse> createOrder(@Valid @RequestBody CreateOrderRequest request) {
log.info("收到创建订单请求:{}", request);
OrderResponse response = orderService.createOrder(request);
return ResponseEntity.ok(response);
}
/**
* 根据订单ID查询订单
* GET /api/orders/{id}
*/
@GetMapping("/{id}")
public ResponseEntity<OrderResponse> getOrderById(@PathVariable @NotNull Long id) {
log.info("收到查询订单请求,订单ID:{}", id);
OrderResponse response = orderService.getOrderById(id);
return ResponseEntity.ok(response);
}
/**
* 根据订单编号查询订单
* GET /api/orders/no/{orderNo}
*/
@GetMapping("/no/{orderNo}")
public ResponseEntity<OrderResponse> getOrderByOrderNo(@PathVariable String orderNo) {
log.info("收到查询订单请求,订单编号:{}", orderNo);
OrderResponse response = orderService.getOrderByOrderNo(orderNo);
return ResponseEntity.ok(response);
}
/**
* 查询用户的所有订单
* GET /api/orders/user/{userId}
*/
@GetMapping("/user/{userId}")
public ResponseEntity<List<OrderResponse>> getOrdersByUserId(@PathVariable @NotNull Long userId) {
log.info("收到查询用户订单请求,用户ID:{}", userId);
List<OrderResponse> responses = orderService.getOrdersByUserId(userId);
return ResponseEntity.ok(responses);
}
/**
* 支付订单
* PUT /api/orders/{id}/pay
*/
@PutMapping("/{id}/pay")
public ResponseEntity<OrderResponse> payOrder(@PathVariable @NotNull Long id) {
log.info("收到支付订单请求,订单ID:{}", id);
OrderResponse response = orderService.payOrder(id);
return ResponseEntity.ok(response);
}
/**
* 发货
* PUT /api/orders/{id}/ship
*/
@PutMapping("/{id}/ship")
public ResponseEntity<OrderResponse> shipOrder(@PathVariable @NotNull Long id) {
log.info("收到发货请求,订单ID:{}", id);
OrderResponse response = orderService.shipOrder(id);
return ResponseEntity.ok(response);
}
/**
* 取消订单
* PUT /api/orders/{id}/cancel
*/
@PutMapping("/{id}/cancel")
public ResponseEntity<OrderResponse> cancelOrder(@PathVariable @NotNull Long id) {
log.info("收到取消订单请求,订单ID:{}", id);
OrderResponse response = orderService.cancelOrder(id);
return ResponseEntity.ok(response);
}
}
6. 全局异常处理
package com.ecommerce.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.HashMap;
import java.util.Map;
/**
* 全局异常处理器
* 统一处理各种异常,返回友好的错误信息
*/
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 处理参数校验异常
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, Object>> handleValidationException(MethodArgumentNotValidException ex) {
Map<String, Object> result = new HashMap<>();
result.put("code", 400);
result.put("message", "参数校验失败");
// 提取具体的校验错误信息
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getAllErrors().forEach(error -> {
String fieldName = ((FieldError) error).getField();
String errorMessage = error.getDefaultMessage();
errors.put(fieldName, errorMessage);
});
result.put("errors", errors);
log.warn("参数校验失败:{}", errors);
return ResponseEntity.badRequest().body(result);
}
/**
* 处理业务异常
*/
@ExceptionHandler(RuntimeException.class)
public ResponseEntity<Map<String, Object>> handleRuntimeException(RuntimeException ex) {
Map<String, Object> result = new HashMap<>();
result.put("code", 500);
result.put("message", ex.getMessage());
log.error("业务异常:{}", ex.getMessage(), ex);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result);
}
/**
* 处理其他异常
*/
@ExceptionHandler(Exception.class)
public ResponseEntity<Map<String, Object>> handleException(Exception ex) {
Map<String, Object> result = new HashMap<>();
result.put("code", 500);
result.put("message", "系统繁忙,请稍后重试");
log.error("系统异常:{}", ex.getMessage(), ex);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result);
}
}
7. 配置文件(application.yml)
# 服务端口
server:
port: 8080
# 应用名称
spring:
application:
name: ecommerce-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
idle-timeout: 30000
max-lifetime: 1800000
connection-timeout: 30000
connection-test-query: SELECT 1
# Jackson配置(JSON序列化)
jackson:
time-zone: GMT+8
date-format: yyyy-MM-dd HH:mm:ss
default-property-inclusion: non_null
# MyBatis配置
mybatis:
configuration:
map-underscore-to-camel-case: true # 下划线转驼峰
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 打印SQL日志
# 日志配置
logging:
level:
root: INFO
com.ecommerce: DEBUG
com.ecommerce.mapper: DEBUG # 打印SQL执行日志
pattern:
console: '%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n'
# Actuator监控配置
management:
endpoints:
web:
exposure:
include: health,info,metrics # 暴露的端点
endpoint:
health:
show-details: always
8. 启动类
package com.ecommerce;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* Spring Boot启动类
* @SpringBootApplication:包含三个注解的组合
* - @Configuration:表示这是一个配置类
* - @EnableAutoConfiguration:启用自动配置
* - @ComponentScan:自动扫描组件
*/
@SpringBootApplication
@MapperScan("com.ecommerce.mapper") // 扫描Mapper接口
public class EcommerceOrderApplication {
public static void main(String[] args) {
SpringApplication.run(EcommerceOrderApplication.class, args);
System.out.println("========================================");
System.out.println("电商订单系统启动成功!");
System.out.println("访问地址:http://localhost:8080");
System.out.println("健康检查:http://localhost:8080/actuator/health");
System.out.println("========================================");
}
}
第四步:数据库建表SQL
-- 创建数据库
CREATE DATABASE IF NOT EXISTS ecommerce DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE ecommerce;
-- 创建订单表
CREATE TABLE IF NOT EXISTS orders (
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '订单ID',
order_no VARCHAR(64) NOT NULL UNIQUE COMMENT '订单编号',
user_id BIGINT NOT NULL COMMENT '用户ID',
product_name VARCHAR(255) NOT NULL COMMENT '商品名称',
quantity INT NOT NULL COMMENT '商品数量',
amount DECIMAL(10, 2) NOT NULL COMMENT '订单金额',
status TINYINT NOT NULL DEFAULT 0 COMMENT '订单状态:0-待支付,1-已支付,2-已发货,3-已完成,4-已取消',
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
INDEX idx_order_no (order_no),
INDEX idx_user_id (user_id),
INDEX idx_create_time (create_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='订单表';
第五步:接口测试
使用Postman或curl测试
# 1. 创建订单
curl -X POST http://localhost:8080/api/orders \
-H "Content-Type: application/json" \
-d '{
"userId": 1,
"productName": "iPhone 15 Pro",
"quantity": 1,
"unitPrice": 7999.00
}'
# 响应示例
# {
# "id": 1,
# "orderNo": "ORD202401151430251234",
# "productName": "iPhone 15 Pro",
# "quantity": 1,
# "amount": 7999.00,
# "status": 0,
# "statusDesc": "待支付",
# "createTime": "2024-01-15T14:30:25"
# }
# 2. 查询订单
curl http://localhost:8080/api/orders/1
# 3. 支付订单
curl -X PUT http://localhost:8080/api/orders/1/pay
# 4. 发货
curl -X PUT http://localhost:8080/api/orders/1/ship
# 5. 查询用户订单
curl http://localhost:8080/api/orders/user/1
# 6. 取消订单
curl -X PUT http://localhost:8080/api/orders/1/cancel
实战踩坑:新手最容易遇到的5个坑
坑1:启动失败 - 端口被占用
现象:
Port 8080 was already in use.
原因:8080端口已被其他程序占用
解决方案:
# 方式一:修改配置文件
server:
port: 8081 # 改成其他端口
# 方式二:启动时指定端口
java -jar app.jar --server.port=8081
# 方式三:查找并关闭占用端口的进程
# Windows
netstat -ano | findstr 8080
taskkill /F /PID <进程ID>
# Linux/Mac
lsof -i :8080
kill -9 <进程ID>
坑2:数据库连接失败
现象:
Communications link failure
原因:
- MySQL服务未启动
- 数据库地址/端口/账号密码错误
- 防火墙阻止连接
解决方案:
spring:
datasource:
# 检查MySQL是否启动
url: jdbc:mysql://localhost:3306/ecommerce?useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: 你的正确密码
# 添加连接测试
hikari:
connection-test-query: SELECT 1
坑3:日期时间格式问题
现象:
{
"createTime": "2024-01-15T14:30:25"
}
前端想要的是 2024-01-15 14:30:25 格式
解决方案:
spring:
jackson:
time-zone: GMT+8
date-format: yyyy-MM-dd HH:mm:ss
或者在实体类上添加注解:
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime createTime;
坑4:事务失效
现象:方法抛出异常后数据没有回滚
原因:
- 异常类型不是RuntimeException
- 方法被同类其他方法调用(AOP代理失效)
解决方案:
// 正确写法
@Transactional(rollbackFor = Exception.class) // 明确指定回滚异常类型
public void createOrder() {
// 业务逻辑
}
// 错误写法:同类调用导致事务失效
@Service
public class OrderService {
@Transactional
public void methodA() {
methodB(); // ❌ 事务失效
}
public void methodB() {
// 业务逻辑
}
}
// 正确写法:注入自己调用
@Service
public class OrderService {
@Autowired
private OrderService self; // 注入自己
public void methodA() {
self.methodB(); // ✅ 事务生效
}
@Transactional
public void methodB() {
// 业务逻辑
}
}
坑5:循环依赖
现象:
The dependencies of some of the beans in the application context form a cycle
原因:A依赖B,B又依赖A,形成循环
解决方案:
// 错误写法
@Service
public class OrderService {
@Autowired
private UserService userService;
}
@Service
public class UserService {
@Autowired
private OrderService orderService; // 循环依赖
}
// 方案一:使用@Lazy延迟加载
@Service
public class UserService {
@Autowired
@Lazy // 延迟加载
private OrderService orderService;
}
// 方案二:重构代码,消除循环依赖
// 把公共逻辑提取到第三个Service中
延伸阅读:进阶之路怎么走?
掌握了Spring Boot的基础使用后,你可以按以下路径继续深入学习:
📚 初级阶段(1-3个月)
- [ ] Spring Boot常用注解详解
- [ ] RESTful API设计规范
- [ ] 参数校验与异常处理
- [ ] 日志框架使用(Logback/Log4j2)
- [ ] 单元测试(JUnit 5 + Mockito)
📚 中级阶段(3-6个月)
- [ ] Spring Boot自动配置原理
- [ ] Spring Boot启动流程分析
- [ ] 自定义Starter开发
- [ ] 缓存集成(Redis + Spring Cache)
- [ ] 消息队列集成(RabbitMQ/Kafka)
- [ ] 定时任务(Spring Task + Quartz)
📚 高级阶段(6-12个月)
- [ ] Spring Boot性能优化
- [ ] 微服务架构(Spring Cloud)
- [ ] 容器化部署(Docker + Kubernetes)
- [ ] 分布式事务(Seata)
- [ ] 服务治理(Nacos/Consul)
- [ ] 链路追踪(SkyWalking/Zipkin)
🎯 推荐资源
| 类型 | 名称 | 说明 | |------|------|------| | 官方文档 | Spring Boot Reference | 最权威的资料,必读! | | 书籍 | 《Spring Boot实战》 | 入门经典,适合新手 | | 书籍 | 《Spring Boot微服务实战》 | 进阶必读 | | 视频 | B站搜"Spring Boot" | 免费优质教程很多 | | 博客 | Spring官方博客 | 第一手资讯 |
总结
通过这篇文章,我们以电商订单系统为例,从零开始搭建了一个完整的Spring Boot应用。回顾一下核心要点:
- Spring Boot是什么:简化Spring开发的脚手架框架,约定优于配置
- 为什么要用:开发效率高、配置简单、部署方便、开箱即用
- 怎么用:通过注解驱动、自动配置、起步依赖快速搭建项目
- 踩坑经验:端口占用、数据库连接、日期格式、事务失效、循环依赖
记住,Spring Boot只是工具,真正的功夫在于对业务的理解和对架构的思考。多写代码,多踩坑,多总结,你一定能成为一名优秀的Java开发者!
如果这篇文章对你有帮助,欢迎点赞、收藏、转发!有问题欢迎在评论区留言,我们一起交流学习~ 🚀
作者简介:10年Java开发经验,曾在阿里、京东从事电商系统架构设计,热爱分享技术心得,希望帮助更多开发者少走弯路。
相关文章:
- MyBatis实战:电商系统中的复杂查询处理
- Redis缓存:让你的电商系统快10倍的秘密
- Spring Cloud微服务:从单体到分布式的蜕变
版权声明:本文标题:Spring Boot实战:从零搭建电商订单系统,新手也能轻松上手的保姆级教程 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.roclinux.cn/b/1768024816a3527209.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论