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(推荐新手)
  1. 访问 https://start.spring.io/
  2. 选择项目配置:
    • Project: Maven
    • Language: Java
    • Spring Boot: 2.7.18(稳定版)
    • Packaging: Jar
    • Java: 8或11
  3. 添加依赖(电商订单系统必备):
    • Spring Web(Web开发)
    • MyBatis Framework(数据库操作)
    • MySQL Driver(数据库驱动)
    • Lombok(简化代码)
    • Validation(参数校验)
  4. 点击"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应用。回顾一下核心要点:

  1. Spring Boot是什么:简化Spring开发的脚手架框架,约定优于配置
  2. 为什么要用:开发效率高、配置简单、部署方便、开箱即用
  3. 怎么用:通过注解驱动、自动配置、起步依赖快速搭建项目
  4. 踩坑经验:端口占用、数据库连接、日期格式、事务失效、循环依赖

记住,Spring Boot只是工具,真正的功夫在于对业务的理解和对架构的思考。多写代码,多踩坑,多总结,你一定能成为一名优秀的Java开发者!

如果这篇文章对你有帮助,欢迎点赞、收藏、转发!有问题欢迎在评论区留言,我们一起交流学习~ 🚀


作者简介:10年Java开发经验,曾在阿里、京东从事电商系统架构设计,热爱分享技术心得,希望帮助更多开发者少走弯路。

相关文章

  • MyBatis实战:电商系统中的复杂查询处理
  • Redis缓存:让你的电商系统快10倍的秘密
  • Spring Cloud微服务:从单体到分布式的蜕变

本文标签: 也能 上手 保姆 实战 订单