Spring Boot实战:从零搭建电商订单系统,告别配置地狱

一、开篇:一个真实的业务痛点

还记得我刚入行时,接到第一个电商项目的场景:

"小王,下周要上线一个订单系统,你负责把Spring框架搭起来。"

我满怀信心地打开IDE,准备大展身手。结果...

  • 配置Spring MVC:写了一堆xml,改了三天才发现<context:component-scan>路径写错了
  • 配置数据库连接池:HikariCP、Druid各种参数调来调去,启动报错看不懂
  • 配置日志框架:Log4j和Logback冲突,控制台一片红
  • 集成MyBatis:mapper文件放错位置,扫描不到接口

一周过去了,系统还在报错启动中。项目经理看着我欲言又止...

如果你也有过类似的经历,那么这篇文章就是为你准备的。

今天我们通过一个电商订单系统的实战案例,带你彻底搞懂Spring Boot,让你从"配置地狱"中解脱出来。


二、什么是Spring Boot?

2.1 通俗理解

想象一下,你要组装一台电脑:

传统Spring开发就像买散件组装:

  • 你需要自己选CPU、主板、内存、显卡
  • 自己买线缆、螺丝
  • 自己研究接线图、驱动兼容性
  • 组装完可能还点不亮,需要排查哪个地方没接好

Spring Boot就像买品牌整机:

  • 厂家已经帮你搭配好兼容的硬件
  • 开箱即用,插上电源就能启动
  • 如果你想升级某个部件,也有标准接口
  • 省去繁琐的组装过程,专注于使用电脑

2.2 官方定义

Spring Boot是基于Spring框架的"约定优于配置"(Convention over Configuration)的开发框架,它通过自动配置大大简化了Spring应用的初始搭建和开发过程。

核心思想:

  • 自动配置:根据你引入的依赖,自动配置好Spring应用
  • 起步依赖:把常用的依赖打包在一起,避免版本冲突
  • 内嵌服务器:直接运行main方法就能启动,无需部署到Tomcat
  • 生产就绪:提供健康检查、监控等开箱即用的功能

三、为什么要用Spring Boot?

3.1 传统Spring开发的痛点

让我们看看传统Spring开发需要做哪些配置:

<!-- web.xml 配置DispatcherServlet -->
<servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/spring-mvc.xml</param-value>
    </init-param>
</servlet>

<!-- spring-mvc.xml 配置 -->
<context:component-scan base-package="com.example"/>
<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 配置数据源 -->
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource">
    <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
    <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/order_db"/>
    <property name="username" value="root"/>
    <property name="password" value="123456"/>
</bean>

痛点总结:

  1. 配置繁琐:一个简单的web应用需要多个xml文件
  2. 依赖地狱:需要自己管理各个依赖的版本兼容性
  3. 部署麻烦:需要安装Tomcat,打包war包部署
  4. 学习曲线陡峭:新手不知道该配什么、怎么配

3.2 Spring Boot的优势

同样的功能,Spring Boot只需要:

@SpringBootApplication
public class OrderApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderApplication.class, args);
    }
}

优势总结:

  1. 零配置启动:一个注解搞定所有配置
  2. 依赖管理简单:starter自动管理版本兼容性
  3. 内嵌服务器:直接运行main方法
  4. 快速开发:专注于业务逻辑,而不是配置

四、怎么用Spring Boot?

4.1 项目初始化

方式一:使用Spring Initializr(推荐)

  1. 访问 https://start.spring.io/
  2. 选择:Maven Project、Java、Spring Boot 2.7.x
  3. 添加依赖:Spring Web、MyBatis Framework、MySQL Driver、Lombok
  4. 点击Generate下载项目

方式二:IDEA创建

New Project -> Spring Initializr -> 后续步骤同上

4.2 完整代码示例:电商订单系统

4.2.1 项目结构
order-system/
├── src/main/java/com/example/order/
│   ├── OrderApplication.java          # 启动类
│   ├── controller/
│   │   └── OrderController.java       # 订单控制器
│   ├── service/
│   │   ├── OrderService.java          # 订单服务接口
│   │   └── impl/
│   │       └── OrderServiceImpl.java  # 订单服务实现
│   ├── mapper/
│   │   └── OrderMapper.java           # 订单Mapper
│   ├── entity/
│   │   └── Order.java                 # 订单实体
│   └── dto/
│       ├── OrderCreateRequest.java    # 创建订单请求
│       └── OrderResponse.java         # 订单响应
├── src/main/resources/
│   ├── application.yml                # 配置文件
│   └── mapper/
│       └── OrderMapper.xml            # MyBatis映射文件
└── pom.xml                             # Maven配置
4.2.2 pom.xml 依赖配置
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.14</version>
    </parent>
    
    <groupId>com.example</groupId>
    <artifactId>order-system</artifactId>
    <version>1.0.0</version>
    
    <properties>
        <java.version>1.8</java.version>
    </properties>
    
    <dependencies>
        <!-- Web开发核心依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        
        <!-- MyBatis持久层框架 -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.3.1</version>
        </dependency>
        
        <!-- MySQL驱动 -->
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>
        
        <!-- Lombok简化代码 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        
        <!-- 参数校验 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>
    </dependencies>
    
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
4.2.3 application.yml 配置文件
# 服务器端口配置
server:
  port: 8080
  servlet:
    context-path: /api

# 应用名称
spring:
  application:
    name: order-system
  
  # 数据源配置
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/order_db?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: 123456
    # HikariCP连接池配置
    hikari:
      minimum-idle: 5
      maximum-pool-size: 20
      connection-timeout: 30000
      idle-timeout: 600000
      max-lifetime: 1800000

# MyBatis配置
mybatis:
  # mapper文件位置
  mapper-locations: classpath:mapper/*.xml
  # 实体类别名包路径
  type-aliases-package: com.example.order.entity
  configuration:
    # 开启驼峰命名转换
    map-underscore-to-camel-case: true
    # 打印SQL日志
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
4.2.4 订单实体类
package com.example.order.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
import java.time.LocalDateTime;

/**
 * 订单实体类
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Order {
    /**
     * 订单ID
     */
    private Long id;
    
    /**
     * 订单编号
     */
    private String orderNo;
    
    /**
     * 用户ID
     */
    private Long userId;
    
    /**
     * 商品名称
     */
    private String productName;
    
    /**
     * 商品数量
     */
    private Integer quantity;
    
    /**
     * 订单金额
     */
    private BigDecimal amount;
    
    /**
     * 订单状态:0-待支付,1-已支付,2-已发货,3-已完成,4-已取消
     */
    private Integer status;
    
    /**
     * 创建时间
     */
    private LocalDateTime createTime;
    
    /**
     * 更新时间
     */
    private LocalDateTime updateTime;
}
4.2.5 DTO类
package com.example.order.dto;

import lombok.Data;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Positive;
import javax.validation.constraints.Size;
import java.math.BigDecimal;

/**
 * 创建订单请求
 */
@Data
public class OrderCreateRequest {
    /**
     * 用户ID
     */
    @NotNull(message = "用户ID不能为空")
    private Long userId;
    
    /**
     * 商品名称
     */
    @NotNull(message = "商品名称不能为空")
    @Size(min = 1, max = 100, message = "商品名称长度在1-100之间")
    private String productName;
    
    /**
     * 商品数量
     */
    @NotNull(message = "商品数量不能为空")
    @Positive(message = "商品数量必须大于0")
    private Integer quantity;
    
    /**
     * 订单金额
     */
    @NotNull(message = "订单金额不能为空")
    @Positive(message = "订单金额必须大于0")
    private BigDecimal amount;
}
package com.example.order.dto;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
import java.time.LocalDateTime;

/**
 * 订单响应
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class OrderResponse {
    private Long id;
    private String orderNo;
    private Long userId;
    private String productName;
    private Integer quantity;
    private BigDecimal amount;
    private Integer status;
    private String statusDesc;
    private LocalDateTime createTime;
}
4.2.6 Mapper接口
package com.example.order.mapper;

import com.example.order.entity.Order;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

/**
 * 订单Mapper接口
 */
@Mapper
public interface OrderMapper {
    
    /**
     * 插入订单
     */
    int insert(Order order);
    
    /**
     * 根据ID查询订单
     */
    Order selectById(@Param("id") Long id);
    
    /**
     * 根据订单编号查询订单
     */
    Order selectByOrderNo(@Param("orderNo") String orderNo);
    
    /**
     * 更新订单状态
     */
    int updateStatus(@Param("id") Long id, @Param("status") Integer status);
}
4.2.7 Mapper XML文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.order.mapper.OrderMapper">

    <!-- 结果映射 -->
    <resultMap id="BaseResultMap" type="com.example.order.entity.Order">
        <id column="id" property="id"/>
        <result column="order_no" property="orderNo"/>
        <result column="user_id" property="userId"/>
        <result column="product_name" property="productName"/>
        <result column="quantity" property="quantity"/>
        <result column="amount" property="amount"/>
        <result column="status" property="status"/>
        <result column="create_time" property="createTime"/>
        <result column="update_time" property="updateTime"/>
    </resultMap>

    <!-- 插入订单 -->
    <insert id="insert" parameterType="com.example.order.entity.Order" 
            useGeneratedKeys="true" keyProperty="id">
        INSERT INTO t_order (
            order_no, user_id, product_name, quantity, amount, status, create_time, update_time
        ) VALUES (
            #{orderNo}, #{userId}, #{productName}, #{quantity}, #{amount}, 
            #{status}, #{createTime}, #{updateTime}
        )
    </insert>

    <!-- 根据ID查询 -->
    <select id="selectById" resultMap="BaseResultMap">
        SELECT * FROM t_order WHERE id = #{id}
    </select>

    <!-- 根据订单号查询 -->
    <select id="selectByOrderNo" resultMap="BaseResultMap">
        SELECT * FROM t_order WHERE order_no = #{orderNo}
    </select>

    <!-- 更新状态 -->
    <update id="updateStatus">
        UPDATE t_order SET status = #{status}, update_time = NOW() 
        WHERE id = #{id}
    </update>

</mapper>
4.2.8 Service层
package com.example.order.service;

import com.example.order.dto.OrderCreateRequest;
import com.example.order.dto.OrderResponse;

/**
 * 订单服务接口
 */
public interface OrderService {
    
    /**
     * 创建订单
     */
    OrderResponse createOrder(OrderCreateRequest request);
    
    /**
     * 根据ID查询订单
     */
    OrderResponse getOrderById(Long id);
    
    /**
     * 根据订单号查询订单
     */
    OrderResponse getOrderByOrderNo(String orderNo);
    
    /**
     * 支付订单
     */
    boolean payOrder(Long id);
    
    /**
     * 取消订单
     */
    boolean cancelOrder(Long id);
}
package com.example.order.service.impl;

import com.example.order.dto.OrderCreateRequest;
import com.example.order.dto.OrderResponse;
import com.example.order.entity.Order;
import com.example.order.mapper.OrderMapper;
import com.example.order.service.OrderService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
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.UUID;

/**
 * 订单服务实现类
 */
@Slf4j
@Service
public class OrderServiceImpl implements OrderService {

    @Autowired
    private OrderMapper orderMapper;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public OrderResponse createOrder(OrderCreateRequest request) {
        log.info("开始创建订单,请求参数:{}", request);
        
        // 1. 构建订单实体
        Order order = new Order();
        order.setOrderNo(generateOrderNo());
        order.setUserId(request.getUserId());
        order.setProductName(request.getProductName());
        order.setQuantity(request.getQuantity());
        order.setAmount(request.getAmount());
        order.setStatus(0); // 待支付
        order.setCreateTime(LocalDateTime.now());
        order.setUpdateTime(LocalDateTime.now());
        
        // 2. 保存订单
        int rows = orderMapper.insert(order);
        if (rows <= 0) {
            throw new RuntimeException("订单创建失败");
        }
        
        log.info("订单创建成功,订单ID:{},订单号:{}", order.getId(), order.getOrderNo());
        
        // 3. 返回响应
        return buildOrderResponse(order);
    }

    @Override
    public OrderResponse getOrderById(Long id) {
        log.info("查询订单,订单ID:{}", id);
        Order order = orderMapper.selectById(id);
        if (order == null) {
            throw new RuntimeException("订单不存在");
        }
        return buildOrderResponse(order);
    }

    @Override
    public OrderResponse getOrderByOrderNo(String orderNo) {
        log.info("查询订单,订单号:{}", orderNo);
        Order order = orderMapper.selectByOrderNo(orderNo);
        if (order == null) {
            throw new RuntimeException("订单不存在");
        }
        return buildOrderResponse(order);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean 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("订单状态异常,当前状态:" + order.getStatus());
        }
        
        // 3. 更新订单状态
        int rows = orderMapper.updateStatus(id, 1); // 已支付
        if (rows <= 0) {
            throw new RuntimeException("支付失败");
        }
        
        log.info("订单支付成功,订单ID:{}", id);
        return true;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean cancelOrder(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("订单状态异常,当前状态:" + order.getStatus());
        }
        
        // 3. 更新订单状态
        int rows = orderMapper.updateStatus(id, 4); // 已取消
        if (rows <= 0) {
            throw new RuntimeException("取消订单失败");
        }
        
        log.info("订单取消成功,订单ID:{}", id);
        return true;
    }

    /**
     * 生成订单号
     */
    private String generateOrderNo() {
        // 格式:时间戳 + 随机数
        String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
        String random = String.valueOf((int)(Math.random() * 10000));
        return "ORD" + timestamp + String.format("%04d", Integer.parseInt(random));
    }

    /**
     * 构建订单响应
     */
    private OrderResponse buildOrderResponse(Order order) {
        OrderResponse response = new OrderResponse();
        BeanUtils.copyProperties(order, response);
        response.setStatusDesc(getStatusDesc(order.getStatus()));
        return response;
    }

    /**
     * 获取订单状态描述
     */
    private String getStatusDesc(Integer status) {
        switch (status) {
            case 0: return "待支付";
            case 1: return "已支付";
            case 2: return "已发货";
            case 3: return "已完成";
            case 4: return "已取消";
            default: return "未知";
        }
    }
}
4.2.9 Controller层
package com.example.order.controller;

import com.example.order.dto.OrderCreateRequest;
import com.example.order.dto.OrderResponse;
import com.example.order.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.*;

/**
 * 订单控制器
 */
@Slf4j
@RestController
@RequestMapping("/orders")
@Validated
public class OrderController {

    @Autowired
    private OrderService orderService;

    /**
     * 创建订单
     * POST /api/orders
     */
    @PostMapping
    public ResponseEntity<OrderResponse> createOrder(
            @Validated @RequestBody OrderCreateRequest 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 Long id) {
        log.info("收到查询订单请求,订单ID:{}", id);
        OrderResponse response = orderService.getOrderById(id);
        return ResponseEntity.ok(response);
    }

    /**
     * 根据订单号查询订单
     * GET /api/orders/orderNo/{orderNo}
     */
    @GetMapping("/orderNo/{orderNo}")
    public ResponseEntity<OrderResponse> getOrderByOrderNo(@PathVariable String orderNo) {
        log.info("收到查询订单请求,订单号:{}", orderNo);
        OrderResponse response = orderService.getOrderByOrderNo(orderNo);
        return ResponseEntity.ok(response);
    }

    /**
     * 支付订单
     * PUT /api/orders/{id}/pay
     */
    @PutMapping("/{id}/pay")
    public ResponseEntity<Boolean> payOrder(@PathVariable Long id) {
        log.info("收到支付订单请求,订单ID:{}", id);
        boolean result = orderService.payOrder(id);
        return ResponseEntity.ok(result);
    }

    /**
     * 取消订单
     * PUT /api/orders/{id}/cancel
     */
    @PutMapping("/{id}/cancel")
    public ResponseEntity<Boolean> cancelOrder(@PathVariable Long id) {
        log.info("收到取消订单请求,订单ID:{}", id);
        boolean result = orderService.cancelOrder(id);
        return ResponseEntity.ok(result);
    }
}
4.2.10 启动类
package com.example.order;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * 订单系统启动类
 */
@SpringBootApplication
@MapperScan("com.example.order.mapper")
public class OrderApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderApplication.class, args);
        System.out.println("========================================");
        System.out.println("订单系统启动成功!");
        System.out.println("访问地址:http://localhost:8080/api");
        System.out.println("========================================");
    }
}
4.2.11 数据库建表SQL
-- 创建数据库
CREATE DATABASE IF NOT EXISTS order_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

USE order_db;

-- 创建订单表
CREATE TABLE IF NOT EXISTS t_order (
    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '订单ID',
    order_no VARCHAR(32) NOT NULL UNIQUE COMMENT '订单编号',
    user_id BIGINT NOT NULL COMMENT '用户ID',
    product_name VARCHAR(100) 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)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单表';

4.3 测试接口

使用Postman或curl测试接口:

# 1. 创建订单
curl -X POST http://localhost:8080/api/orders \
  -H "Content-Type: application/json" \
  -d '{
    "userId": 1001,
    "productName": "iPhone 15 Pro",
    "quantity": 1,
    "amount": 7999.00
  }'

# 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/cancel

五、实战踩坑总结

坑1:启动报错 Field xxx could not be found

现象:

***************************
APPLICATION FAILED TO START
***************************

Description:

Field orderMapper in com.example.order.service.impl.OrderServiceImpl required a bean of type 'com.example.order.mapper.OrderMapper' that could not be found.

原因:

  • 忘记在Mapper接口上添加@Mapper注解
  • 或者忘记在启动类上添加@MapperScan注解

解决方案:

// 方式一:在每个Mapper接口上添加@Mapper
@Mapper
public interface OrderMapper {
    // ...
}

// 方式二:在启动类上添加@MapperScan(推荐)
@SpringBootApplication
@MapperScan("com.example.order.mapper")
public class OrderApplication {
    // ...
}

坑2:数据库连接失败 Communications link failure

现象:

java.sql.SQLNonTransientConnectionException: Could not create connection to database server

原因:

  • MySQL服务未启动
  • 数据库地址、端口、用户名、密码配置错误
  • 数据库不存在

解决方案:

  1. 检查MySQL服务是否启动:systemctl status mysql
  2. 检查配置文件中的数据库连接信息
  3. 确认数据库已创建:CREATE DATABASE order_db;
  4. 测试连接:mysql -h localhost -u root -p

坑3:MyBatis找不到Mapper XML文件

现象:

org.apache.ibatis.binding.BindingException: Invalid bound statement (not found)

原因:

  • Mapper XML文件路径配置错误
  • Maven编译时没有将XML文件复制到target目录

解决方案:

# application.yml中正确配置mapper路径
mybatis:
  mapper-locations: classpath:mapper/*.xml
<!-- pom.xml中添加资源文件配置 -->
<build>
    <resources>
        <resource>
            <directory>src/main/java</directory>
            <includes>
                <include>**/*.xml</include>
            </includes>
        </resource>
        <resource>
            <directory>src/main/resources</directory>
            <includes>
                <include>**/*.*</include>
            </includes>
        </resource>
    </resources>
</build>

坑4:事务不回滚

现象:

  • 抛出异常后,数据库数据仍然被修改

原因:

  • 默认只回滚RuntimeExceptionError
  • 方法修饰符不是public
  • 同类内部方法调用

解决方案:

// 1. 明确指定回滚异常类型
@Transactional(rollbackFor = Exception.class)
public void someMethod() {
    // ...
}

// 2. 确保方法是public
@Transactional(rollbackFor = Exception.class)
public void someMethod() {
    // ...
}

// 3. 避免同类内部调用(通过注入自己调用)
@Service
public class OrderServiceImpl implements OrderService {
    @Autowired
    private OrderService self; // 注入自己
    
    public void methodA() {
        self.methodB(); // 通过代理调用
    }
    
    @Transactional
    public void methodB() {
        // ...
    }
}

坑5:端口被占用 Web server failed to start

现象:

Web server failed to start. Port 8080 was already in use.

原因:

  • 8080端口已被其他程序占用

解决方案:

# 方式一:修改配置文件端口
server:
  port: 8081

# 方式二:启动时指定端口
java -jar order-system.jar --server.port=8081

# 方式三:查找并关闭占用端口的程序
# Windows
netstat -ano | findstr :8080
taskkill /PID <进程ID> /F

# Linux/Mac
lsof -i :8080
kill -9 <进程ID>

六、延伸阅读

恭喜你!到这里你已经掌握了Spring Boot的基础用法。接下来你可以继续学习:

6.1 进阶方向

| 方向 | 推荐技术 | 学习路径 | |------|---------|---------| | 持久层进阶 | Spring Data JPA、多数据源、读写分离 | JPA基础 -> 复杂查询 -> 性能优化 | | 缓存技术 | Redis、Spring Cache、Caffeine | 缓存原理 -> Spring Cache集成 -> 分布式缓存 | | 消息队列 | RabbitMQ、Kafka、RocketMQ | 消息队列基础 -> Spring Boot集成 -> 异步处理 | | 微服务 | Spring Cloud、Nacos、Gateway | 微服务架构 -> 服务注册发现 -> 网关路由 | | 安全框架 | Spring Security、JWT、OAuth2 | 认证授权 -> JWT集成 -> 单点登录 | | 监控运维 | Actuator、Prometheus、Grafana | 健康检查 -> 指标收集 -> 可视化监控 |

6.2 推荐资源

  • 官方文档:https://spring.io/projects/spring-boot
  • Spring Boot实战(书籍)
  • Baeldung:https://www.baeldung.com/spring-boot
  • Spring中文社区:https://springcloud.cn/

6.3 练手项目建议

  1. 用户管理系统:CRUD + 权限控制
  2. 博客系统:文章管理 + 评论 + 标签
  3. 秒杀系统:高并发 + 缓存 + 消息队列
  4. 在线商城:商品 + 订单 + 支付

七、总结

通过本文的学习,你应该已经掌握:

Spring Boot是什么:约定优于配置的开发框架 ✅ 为什么要用:简化配置、快速开发、提高效率 ✅ 怎么使用:通过电商订单系统实战,掌握完整开发流程 ✅ 常见坑点:提前避坑,少走弯路

记住: Spring Boot不是魔法,它只是帮你做了很多繁琐的配置工作。作为开发者,你仍然需要理解Spring的核心原理(IoC、AOP),这样才能在遇到问题时快速定位和解决。

最后的话:

学习技术就像爬山,一开始觉得陡峭难行,但每爬一步,看到的风景就更美。Spring Boot只是Java生态的冰山一角,保持好奇心,持续学习,你会成为一名优秀的Java开发者!

如果这篇文章对你有帮助,欢迎点赞、收藏、转发!有问题欢迎在评论区交流~


作者:技术大牛带徒弟 发布时间:2024年 适用人群:Java初学者、1-3年经验开发者

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐