4、Spring Boot项目初始化:创建父工程、创建子模块、配置多环境profile、整合MyBatis-Plus

好,咱们正式开始动手了。

前面几章我们把秒杀系统的整体架构、数据流向、核心难点都捋了一遍。现在,该把袖子撸起来,真正开始写代码了。

我个人习惯,不管项目大小,先搭好工程骨架。骨架稳了,后面加功能才不慌。这一章我们就来做这件事——初始化一个Spring Boot项目,并且把秒杀系统需要的几个核心模块拆分好。

4.1 为什么用多模块?

你想想看,秒杀系统不是一个小玩具。它包含用户登录、商品管理、订单处理、秒杀核心逻辑、消息推送等等。如果全写在一个工程里,后期维护就是噩梦。

我在项目中遇到过,一个单体应用到了后期,光启动就要3分钟,改一行代码要重新编译整个项目。那感觉,嗯,谁用谁知道。

所以,我们采用Maven多模块结构。父工程只做依赖管理和版本控制,子模块各司其职。

核心原则:父工程不写业务代码,只做pom管理。子模块之间通过依赖引用,避免循环依赖。

4.2 创建父工程

我们先创建一个普通的Maven项目作为父工程。记住,父工程的packaging一定要是pom

<groupId>com.seckill</groupId>
<artifactId>seckill-parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>pom</packaging>

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.7.12</version>
    <relativePath/>
</parent>

这里我选了Spring Boot 2.7.12,为什么不是3.x?因为3.x要求JDK17,而很多生产环境还在用JDK8或11。咱们做秒杀系统,稳定第一。

小技巧:在父工程的<dependencyManagement>里统一管理所有子模块的版本号。这样升级依赖时,改一处就行。

4.3 创建子模块

接下来,我们创建四个核心子模块。每个模块对应一个独立的Maven子项目。

模块名 职责 关键依赖
seckill-common 公共工具类、常量、统一返回对象 lombok, hutool
seckill-dao 数据访问层,MyBatis-Plus映射 mybatis-plus-boot-starter, mysql-connector
seckill-service 业务逻辑层,事务、缓存 seckill-dao, redis, spring-tx
seckill-web 控制器层,接收请求、返回结果 seckill-service, spring-boot-starter-web

在父工程的pom.xml里,用<modules>标签把它们聚合起来:

<modules>
    <module>seckill-common</module>
    <module>seckill-dao</module>
    <module>seckill-service</module>
    <module>seckill-web</module>
</modules>

每个子模块的pom.xml里,<parent>指向父工程。举个例子,seckill-dao的配置:

<parent>
    <groupId>com.seckill</groupId>
    <artifactId>seckill-parent</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <relativePath>../pom.xml</relativePath>
</parent>

注意:子模块之间的依赖关系要理清楚。seckill-web依赖seckill-serviceseckill-service依赖seckill-daoseckill-dao依赖seckill-common。不要搞成循环依赖,否则Maven会报错。

4.4 配置多环境Profile

开发、测试、生产,这三个环境的配置肯定不一样。数据库地址、Redis密码、日志级别,都得分开。

Spring Boot的Profile机制就是干这个的。我们在seckill-web模块的src/main/resources下创建三个配置文件:

  • application-dev.yml —— 开发环境,数据库连本地,日志级别DEBUG
  • application-test.yml —— 测试环境,数据库连测试服务器,日志级别INFO
  • application-prod.yml —— 生产环境,数据库连线上,日志级别WARN,开启性能监控

举个例子,application-dev.yml的内容:

server:
  port: 8080

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/seckill?useUnicode=true&characterEncoding=utf-8
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver

logging:
  level:
    com.seckill: DEBUG

application-prod.yml则要谨慎得多:

server:
  port: 8080

spring:
  datasource:
    url: jdbc:mysql://prod-db:3306/seckill?useUnicode=true&characterEncoding=utf-8&useSSL=false
    username: seckill_prod
    password: ${DB_PASSWORD}  # 生产密码从环境变量读取
    hikari:
      maximum-pool-size: 20
      minimum-idle: 5

logging:
  level:
    com.seckill: WARN

启动时,通过--spring.profiles.active=dev来指定环境。我一般会在IDE里配好启动参数,部署时用脚本传参。

避坑指南:我曾经在生产环境忘记改Profile,结果连到了开发数据库,把测试数据写进去了。从那以后,我强制要求所有部署脚本必须显式指定Profile,并且加了一个启动检查——如果Profile是dev但IP不是本地,直接报错退出。

4.5 整合MyBatis-Plus

MyBatis-Plus,说白了就是MyBatis的增强版。它帮我们省去了写大量XML映射文件的痛苦。

seckill-dao模块里,引入依赖:

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.3.1</version>
</dependency>

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>

然后,在seckill-dao里创建一个配置类,开启MyBatis-Plus的扫描:

@Configuration
@MapperScan("com.seckill.dao.mapper")
public class MyBatisPlusConfig {

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 分页插件,秒杀系统的订单列表查询会用到
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}

接下来,我们创建一个实体类和Mapper接口。以秒杀商品为例:

@Data
@TableName("seckill_goods")
public class SeckillGoods {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String goodsName;
    private Integer stockCount;
    private BigDecimal seckillPrice;
    private LocalDateTime startTime;
    private LocalDateTime endTime;
}
public interface SeckillGoodsMapper extends BaseMapper<SeckillGoods> {
    // 这里什么都不用写,BaseMapper已经提供了CRUD方法
    // 如果需要复杂查询,可以在这里定义接口方法,配合XML或注解
}

你看,一个Mapper接口,继承BaseMapper,就有了insertselectByIdupdateByIddeleteById这些基础方法。是不是很省事?

核心要点:MyBatis-Plus的BaseMapper提供了单表CRUD,但秒杀系统的库存扣减不能用它自带的updateById。为什么?因为并发场景下,我们需要用update ... set stock = stock - 1 where stock > 0这样的原子操作。这个后面讲秒杀核心逻辑时会详细说。

4.6 验证项目结构

到这里,我们的项目骨架应该长这样:

seckill-parent
├── seckill-common
│   └── src/main/java/com/seckill/common
│       ├── constant
│       ├── exception
│       └── response
├── seckill-dao
│   └── src/main/java/com/seckill/dao
│       ├── config
│       ├── entity
│       └── mapper
├── seckill-service
│   └── src/main/java/com/seckill/service
│       ├── impl
│       └── SeckillGoodsService.java
└── seckill-web
    └── src/main/java/com/seckill/web
        ├── controller
        └── SeckillApplication.java  // 启动类

启动类放在seckill-web里,加上@SpringBootApplication注解:

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

启动后,访问http://localhost:8080/,能看到Spring Boot的默认错误页面,说明项目启动成功了。

注意:如果启动报错说找不到数据源,检查一下application.yml里是否配置了数据库连接。或者,你可以先注释掉MyBatisPlusConfig里的数据源相关配置,等数据库准备好了再打开。

4.7 本章小结

嗯,这一章我们做了几件基础但重要的事:

  • 创建了Maven多模块父工程,统一管理依赖
  • 拆分了四个子模块:common、dao、service、web
  • 配置了多环境Profile,开发、测试、生产各有一套配置
  • 整合了MyBatis-Plus,实现了基础的CRUD能力

项目骨架搭好了,下一章我们就可以开始写真正的业务代码了。秒杀系统的核心——库存扣减、限流、防超卖,这些硬骨头,我们一个一个啃。

公众号:蓝海资料掘金营,微信deep3321