破局全栈:一个前端开发的Java入门实战记录(1)

背景

  • 公司后端是java,所以全栈选择java。期望一人开发一个模块,减少沟通成本。
  • 借助ai,我的学习成本降低
  • 我的学习方式:是通过案例查漏补缺,有阶段性学习成果。而不是全面学习,避免过于枯燥
  • 本文所有案列也是参考一位道友的项目,结合实际场景,进行调整。

java环境和编码工具

  • java环境:直接去官网下载 www.oracle.com/cn/java/tec...

  • 编码工具:trae cn

  • 导入java项目,提示我安装 Extension Pack for Java(微软官方包,一键装全)

子插件 作用
Language Support for Java(TM) by Red Hat Java 语法高亮、代码补全、跳转、重构(JDT.LS 语言服务器)
Debugger for Java 断点调试、变量查看、Call Stack
Maven for Java pom.xml 识别、依赖下载、生命周期命令
Gradle for Java build.gradle / settings.gradle 支持
Test Runner for Java JUnit 4/5、TestNG 单测运行
Project Manager for Java 左侧 JAVA PROJECTS 面板,管理多模块

另外提示我安装了

子插件 作用
Spring Boot Extension Pack 做 Spring Boot / Spring Cloud 项目,含 Bean 导航、Boot Dashboard、YAML 补全
Spring Initializr Java Support 新建 Spring Boot 项目向导

项目目录结构

perl 复制代码
java-road/
├── README.md              # 项目说明文档
└── study-h2              # 学习笔记 - H2 数据库
└── ... 后续补充
pom.xml                  # 项目全局依赖配置文件

案例代码地址:gitee.com/banmaxiaoba...

熟悉pom.xml文件

这里我让ai告诉如何了解,基于我是前端开发者的身份。它用了类比法告诉我。

  • maven项目类似前端里pnpm workspace,是一个多项目的结构。根目录下有个全局的pom.xml文件,用于管理所有子项目的依赖、插件等。每个子项目的pom.xml文件用于管理该子项目的依赖、插件等。
  • pom.xml具体结构如下:
json 复制代码
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.wy.study</groupId>
    <artifactId>java-road</artifactId>
    <version>1.0</version>
    <packaging>pom</packaging>

这块一个文件协议,项目名称是java-road,版本号是1.0,打包方式是pom

json 复制代码
<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>2.7.18</version>
</parent>

继承使用spring boot框架,版本号是2.7.18

json 复制代码
<modules>
  <module>study-h2</module>
</modules>

定义了它的所有子模块,其实每一个子模块都可以单独部署。因为继承父模块。往上也继承spring boot的配置。

json 复制代码
<properties>
  <maven.compiler.source>8</maven.compiler.source>
</properties

全局变量定义区,比如版本号,后面的依赖包可以直接引用变量

json 复制代码
<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
</dependencies>

定义项目中依赖包,类似前端里的 npm 包。这里有个<dependencyManagement>包裹的依赖包,不会实际下载。只是用于定义全局的依赖版本号。引入地方不写版本号,会自动引用全局的版本号。

json 复制代码
<build>
    <finalName>JPAApp</finalName>
    <plugins>
        <!--指定spring boot项目打包后启动的入口-->
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
                <mainClass>com.wy.study.h2.H2App</mainClass>
            </configuration>
            <executions>
                <execution>
                    <goals>
                        <goal>repackage</goal><!--创建一个自动可执行的jar或war文件 -->
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

java的打包配置,可以设置包名、插件配置、入口文件

案例一:Spring Boot + H2 + MyBatis-Plus:从 0 跑通一个嵌入式数据库 Demo

这篇文章记录我用 H2 嵌入式数据库 + MyBatis-Plus 搭第一个能跑通的 CRUD 的全过程。 没有理论堆砌,全是踩过的坑和跑通的代码。


0. 为什么选 H2?

写后端学习 Demo,你不想每次都去装一套 MySQL 吧?H2 就是为这种场景量身定做的:

  • 纯 Java 写的,只有一个 jar。引入依赖就能用,零安装。
  • 三种模式任你挑
    • 内存模式(jdbc:h2:mem:)--- 重启就没,做单元测试绝配。
    • 文件模式(jdbc:h2:file:D:/workspace/h2/study-db)--- 持久化到磁盘,重启还在。
    • 服务模式(jdbc:h2:tcp://localhost/...)--- 像 MySQL 一样独立跑。
  • 自带 Web 控制台。启动浏览器就能看表看数据,不用装 Navicat。

一句话:H2 就是 Java 世界里的 SQLite。


1. 项目长什么样?

先把项目目录摆出来,让你一眼看明白:

bash 复制代码
study-h2/
├── pom.xml                                     # Maven 依赖
└── src/main/
    ├── java/com/wy/study/h2/
    │   ├── H2App.java                          # 启动类(main 方法在这里)
    │   ├── config/
    │   │   └── H2MybatisPlusConfig.java        # 分页插件配置
    │   ├── controller/
    │   │   └── H2UserController.java           # 对外的 HTTP 接口
    │   ├── entity/
    │   │   └── H2UserEntity.java               # 用户表对应的 Java 类
    │   └── service/
    │       ├── H2UserMapper.java               # MyBatis Mapper 接口(写 SQL 的地方)
    │       └── H2UserService.java              # 业务层(调用 Mapper)
    └── resources/
        ├── application.yml                     # 数据库连接 + H2 控制台配置
        └── mapper/
            └── H2UserMapper.xml                # 复杂 SQL 写这里(可选)

2. 第一步:加依赖(3 行搞定)

父 pom 已经统一管版本了,子模块只写坐标。pom.xml 里就加了这两个:

xml 复制代码
<dependencies>
    <!-- MyBatis-Plus:MyBatis 的增强版,自带 CRUD、分页 -->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
    </dependency>

    <!-- H2 数据库本体 -->
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
    </dependency>
</dependencies>

就这。不用加版本号,因为父 pom 的 dependencyManagement 已经统一管理好了。


3. 第二步:连数据库(yml 就够了)

打开 src/main/resources/application.yml

yaml 复制代码
server:
  port: 9203                      # 端口
  servlet:
    context-path: /h2             # 所有接口前面都要加 /h2

spring:
  datasource:
    url: jdbc:h2:file:D:/workspace/h2/study-db   # 文件模式,持久化到这个目录
    driver-class-name: org.h2.Driver             # H2 的驱动类
    username: root
    password: root
  h2:
    console:
      enabled: true               # 打开 Web 控制台
      path: /h2-console           # 浏览器访问 http://localhost:9203/h2/h2-console
      settings:
        web-allow-others: true    # 允许非本机访问

启动成功后,浏览器打开:

bash 复制代码
http://localhost:9203/h2/h2-console

JDBC URL 填上面的 jdbc:h2:file:D:/workspace/h2/study-db,点 Connect,就能看到 H2 自带的 SQL 编辑器了。


4. 重点来了:MyBatis 的三层结构怎么操作数据库?

这部分是我折腾最久的。先给你一张"点菜类比图",一秒看懂:

markdown 复制代码
┌────────────────────────────────────────────────────┐
│  Controller(服务员)                                │
│  → 接收客人请求,转给后厨                            │
│    对应:H2UserController.java                      │
└──────────────────────┬─────────────────────────────┘
                       │ 调用
┌──────────────────────▼─────────────────────────────┐
│  Service(后厨领班)                                │
│  → 决定业务流程,指挥 Mapper 干活                    │
│    对应:H2UserService.java                         │
└──────────────────────┬─────────────────────────────┘
                       │ 调用
┌──────────────────────▼─────────────────────────────┐
│  Mapper(厨师)                                     │
│  → 真正写 SQL,直接跟数据库打交道                    │
│    对应:H2UserMapper.java + H2UserMapper.xml       │
└────────────────────────────────────────────────────┘

加一个"服务员不想给错菜"------Controller 不直接写 SQL; 加一个"后厨不做脏活"------Service 不直接写 SQL; SQL 只在 Mapper 里写。这就是分层的意义。

下面三层从上到下逐个看代码。

第一层:Entity --- 表结构的 Java 镜像

H2 里有一张用户表 STUDY_H2_USER,对应的 Java 类就是 H2UserEntity

java 复制代码
@Data                                          // Lombok:自动生成 getter/setter/toString
@TableName("STUDY_H2_USER")                    // 对应数据库里的表名
public class H2UserEntity {

    @TableId(type = IdType.ASSIGN_UUID)        // 主键,用雪花算法/UUID 生成
    private String id;

    private String userName;
    private String password;
    private String email;
    private String phone;

    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime createTime;
}

这一层零逻辑,就负责把"数据库表字段"和"Java 类属性"一一对应起来。

第二层:Mapper --- 写 SQL 的地方

MyBatis-Plus 最爽的地方来了:你不用写单表的增删改查 SQL 。只要继承 BaseMapper

java 复制代码
@Mapper
public interface H2UserMapper extends BaseMapper<H2UserEntity> {

    // 这俩是我自己加的自定义方法
    @Select("SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES " +
            "WHERE TABLE_NAME = 'STUDY_H2_USER'")
    int checkTableExists();

    void createTable();   // SQL 写在 XML 里
}

继承了 BaseMapper,你自动就有了 17 个方法可以直接调:

scss 复制代码
insert()    → 插入
deleteById() → 按 id 删
updateById() → 按 id 更新
selectById() → 按 id 查
selectList() → 查列表
selectPage() → 分页查
...

createTable() 这种 DDL 写在哪?复杂 SQL 放 XML 文件:

src/main/resources/mapper/H2UserMapper.xml

xml 复制代码
<mapper namespace="com.wy.study.h2.service.H2UserMapper">

    <!-- id 必须跟接口里的方法名一模一样 -->
    <update id="createTable">
        CREATE TABLE STUDY_H2_USER
        (
            ID          VARCHAR(50) PRIMARY KEY,
            USER_NAME   VARCHAR(50)  NOT NULL,
            PASSWORD    VARCHAR(100) NOT NULL,
            EMAIL       VARCHAR(100),
            PHONE       VARCHAR(20),
            CREATE_TIME DATETIME
        )
    </update>

</mapper>

一个容易踩的坑:namespace 必须写 Mapper 接口的全限定名。写错了 MyBatis 找不到方法跟 XML 对应,直接 500。

第三层:Service --- 业务逻辑的集散地

java 复制代码
@Service
public class H2UserService
        extends ServiceImpl<H2UserMapper, H2UserEntity>   // 继承,自动拥有通用方法
        implements IService<H2UserEntity> {              // 实现,面向接口编程

    // 目前业务还简单,所以类体是空的也没关系
    // 但这里会是你未来写复杂逻辑的地方:
    //   - 下单要扣库存 + 加订单(事务)
    //   - 调用多个 Mapper 组合出一个复杂 DTO
    //   - 加缓存、发消息队列
}

继承 ServiceImpl 之后,Service 里就能直接调 userService.save(user) / userService.page(page) 这些方法,不用自己再去调 userMapper.insert()

为啥还要这一层?直接在 Controller 里调 Mapper 不行吗?

可以,但业务一复杂你会后悔。比如一个"下单"操作要:检查库存 → 扣库存 → 写订单 → 发通知 → 记日志,这一串逻辑不可能全堆在 Controller 里。Controller 应该越薄越好。

第四层:Controller --- 对外暴露 HTTP 接口

java 复制代码
@RestController
@RequestMapping("user")
public class H2UserController {

    @Resource
    private H2UserService userService;

    // http://localhost:9203/h2/user/add
    @GetMapping("add")
    public void add() {
        H2UserEntity user = new H2UserEntity();
        user.setUserName("wuyang");
        user.setEmail("wuyang@example.com");
        user.setPhone("13800000000");
        user.setPassword("simple");
        user.setCreateTime(LocalDateTime.now());
        userService.save(user);          // 一行调用,新增就完事了
    }

    // http://localhost:9203/h2/user/get?pageNum=1&pageSize=10
    @GetMapping("get")
    public Map<String, Object> get(
            @RequestParam(defaultValue = "1") Integer pageNum,
            @RequestParam(defaultValue = "10") Integer pageSize) {

        Page<H2UserEntity> page = new Page<>(pageNum, pageSize);
        IPage<H2UserEntity> result = userService.page(page);   // 一行调用,分页就完事了

        Map<String, Object> response = new HashMap<>();
        response.put("pageSize", pageSize);
        response.put("currentPage", pageNum);
        response.put("totalPages", result.getPages());
        response.put("totalCount", result.getTotal());
        response.put("data", result.getRecords());
        return response;
    }
}

跑起来看效果:

bash 复制代码
# 新增一条数据
curl http://localhost:9203/h2/user/add

# 分页查询
curl "http://localhost:9203/h2/user/get?pageNum=1&pageSize=10"
# 返回:
# {
#   "pageSize": 10,
#   "currentPage": 1,
#   "totalPages": 1,
#   "totalCount": 1,
#   "data": [{"id": "a1b2c3...", "userName": "wuyang", ...}]
# }

5. 自动建表:启动时偷偷做的一件事

我不想手动去 H2 控制台跑 CREATE TABLE,就在启动类里加了一段:

java 复制代码
@SpringBootApplication
public class H2App {

    @Resource
    private H2UserMapper userMapper;

    public static void main(String[] args) {
        SpringApplication.run(H2App.class, args);
        // 打几行日志,告诉你控制台地址、接口地址
    }

    // Spring Boot 启动完之后触发
    @EventListener(ApplicationReadyEvent.class)
    public void onApplicationReady() {
        // 先查表有没有
        if (userMapper.checkTableExists() == 0) {
            userMapper.createTable();    // 没有就建一张
            log.info("Table created.");
        } else {
            log.info("Table already exists.");
        }
    }
}

@EventListener(ApplicationReadyEvent.class) 监听启动完成事件,跑表检查 + 自动建表,这样你第一次启动就直接能用了。


6. 分页插件配置(不配置分页会变成查全量)

MyBatis-Plus 自带分页,但要注册一个拦截器才生效。H2MybatisPlusConfig.java

java 复制代码
@Configuration
public class H2MybatisPlusConfig {

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.setInterceptors(
            Collections.singletonList(paginationInnerInterceptor()));
        return interceptor;
    }

    @Bean
    public PaginationInnerInterceptor paginationInnerInterceptor() {
        PaginationInnerInterceptor p = new PaginationInnerInterceptor();
        p.setMaxLimit(-1L);           // 不限制每页最大条数
        p.setDbType(DbType.H2);       // 告诉它是 H2 方言
        p.setOptimizeJoin(true);       // join 查询优化
        return p;
    }
}

踩坑提醒:不加这个配置,userService.page(page) 表面能跑,实际发出的 SQL 没有 LIMIT,会把整张表数据都查出来。开发环境感觉不到,生产会炸。


7. 跑起来的全链路

从浏览器点进去到数据存进 H2,一共走了这么多路:

sql 复制代码
浏览器
  ↓ GET /h2/user/add
Tomcat(9203 端口)
  ↓ 路由到
H2UserController.add()
  ↓ 调用
H2UserService.save(user)        ← ServiceImpl 里其实就是调了 Mapper
  ↓ 调用
H2UserMapper.insert(user)       ← BaseMapper 自带的方法
  ↓ MyBatis 拼 SQL
JDBC
  ↓
H2 文件数据库(D:/workspace/h2/study-db.mv.db)

分层的好处就在这:每一层只干自己那点事,改 SQL 只动 Mapper,加新业务只动 Service,换数据库(比如换成 MySQL)只改 yml 就行。


8. 踩过的坑清单(希望你跳过)

症状 解决
Mapper XML 的 namespace 写错 启动 OK,调接口报 Invalid bound statement 写成接口的全限定名,复制粘贴
没加分页插件 page() 不报错但没分页 SQL 必须注册 MybatisPlusInterceptor
依赖不加版本号但父 pom 没管版本 父 pom 没配 dependencyManagement 在父 pom 里统一版本
H2 URL 写成 mem: 重启数据就没了 想要持久化就用 file:
Windows 路径反斜杠 JDBC URL 写 D:\xxx 被当转义字符 用正斜杠 D:/xxx
Mapper 没加 @Mapper Spring 找不到这个 Bean,注入报错 @Mapper 或者启动类加 @MapperScan

9. 总结一下

  • H2:嵌入式数据库,一个 jar 搞定,自带 Web 控制台,学习/Demo 首选。
  • 三层结构:Controller(收请求)→ Service(业务逻辑)→ Mapper(写 SQL)。各司其职。
  • MyBatis-Plus :继承 BaseMapper 就有全套单表 CRUD,继承 ServiceImpl 业务层也不用自己写。
  • XML 写复杂 SQL :简单注解 @Select/@Insert 够用,复杂 DDL/多表联查丢 XML 里。

下一篇打算写另外一种持久化方法 JPA

相关推荐
唐青枫2 小时前
Java Tomcat 实战指南:从 Servlet 容器到 Spring Boot 部署
java
wsaaaqqq2 小时前
roudan:自由选择实体、灵活操作数据、快速写入数据库的 Java 框架
java
plainGeekDev6 小时前
null 判断 → Kotlin 可空类型
android·java·kotlin
糖拌西瓜皮6 小时前
Java开发者视角:深入理解Node.js异步编程模型
java·后端·node.js
plainGeekDev6 小时前
getter/setter → Kotlin 属性
android·java·kotlin
一线大码6 小时前
Smart-Doc 的简单使用
java·后端·restful
MacroZheng7 小时前
Claude Code官方桌面端正式发布,夯爆了!
java·人工智能·后端
虚无境7 小时前
如何编写一个SpringBoot项目告警推送的Starter
java·prometheus·webhook