JDBC与SpringBoot后端开发 完整学习笔记

课次21-25:从原生JDBC到SpringBoot + MyBatis-Plus的企业级开发演进

第一部分:MySQL安装与数据库设计(课次21)

一、MySQL 8.0 安装

Windows安装步骤
下载MySQL Community Server:https://dev.mysql.com/downloads/mysql/

解压到指定目录,如 D:\mysql-8.0.35

在根目录创建 my.ini 配置文件:

ini
[mysqld]
port=3306
basedir=D:\mysql-8.0.35
datadir=D:\mysql-8.0.35\data
character-set-server=utf8mb4
default-storage-engine=INNODB
max_connections=200

[client]
default-character-set=utf8mb4
以管理员身份运行CMD,执行以下命令:

bash
cd D:\mysql-8.0.35\bin
mysqld --initialize --console # 初始化,记录临时密码
mysqld --install MySQL # 安装服务
net start MySQL # 启动服务
mysql -u root -p # 登录
ALTER USER 'root'@'localhost' IDENTIFIED BY '123456'; # 修改密码
FLUSH PRIVILEGES;
macOS/Linux安装
bash

macOS

brew install mysql && brew services start mysql

Ubuntu

sudo apt update && sudo apt install mysql-server
sudo systemctl start mysql

二、数据库与表结构

创建数据库
sql
CREATE DATABASE IF NOT EXISTS news_management
DEFAULT CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;

USE news_management;
user表(用户表)
sql
CREATE TABLE IF NOT EXISTS user (
id INT AUTO_INCREMENT PRIMARY KEY COMMENT '用户ID',
username VARCHAR(50) NOT NULL UNIQUE COMMENT '用户名',
password VARCHAR(255) NOT NULL COMMENT '密码',
email VARCHAR(100) COMMENT '邮箱',
phone VARCHAR(20) COMMENT '手机号',
news_count INT DEFAULT 0 COMMENT '发布新闻数量',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
deleted TINYINT DEFAULT 0 COMMENT '逻辑删除(0-未删除,1-已删除)'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
news表(新闻表)
sql
CREATE TABLE IF NOT EXISTS news (
id INT AUTO_INCREMENT PRIMARY KEY COMMENT '新闻ID',
title VARCHAR(200) NOT NULL COMMENT '新闻标题',
content TEXT NOT NULL COMMENT '新闻内容',
author_id INT NOT NULL COMMENT '作者ID(关联user.id)',
author_name VARCHAR(50) COMMENT '作者姓名(冗余字段)',
view_count INT DEFAULT 0 COMMENT '浏览次数',
status TINYINT DEFAULT 1 COMMENT '状态: 0-草稿,1-已发布,2-已下架',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '发布时间',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
deleted TINYINT DEFAULT 0 COMMENT '逻辑删除',
FOREIGN KEY (author_id) REFERENCES user(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='新闻表';

第二部分:JDBC原生开发(课次21-22)

三、JDBC核心接口

接口/类 作用 关键方法
DriverManager 管理JDBC驱动,获取连接 getConnection()
Connection 数据库连接对象 prepareStatement(), setAutoCommit(), commit(), rollback()
PreparedStatement 预编译SQL,防SQL注入 setXxx(), executeQuery(), executeUpdate()
ResultSet 查询结果集 next(), getXxx()

四、Maven依赖

xml


com.mysql
mysql-connector-j
8.0.33

五、JDBC工具类

java
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class DBUtil {
private static final String URL =
"jdbc:mysql://localhost:3306/news_management?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8mb4";
private static final String USER = "root";
private static final String PASSWORD = "123456";

static {
try {
Class.forName("com.mysql.cj.jdbc.Driver");
} catch (ClassNotFoundException e) {
throw new RuntimeException("MySQL驱动加载失败", e);
}
}

public static Connection getConnection() throws SQLException {
return DriverManager.getConnection(URL, USER, PASSWORD);
}
}

六、用户实体类

java
import java.time.LocalDateTime;

public class User {
private Integer id;
private String username;
private String password;
private String email;
private String phone;
private Integer newsCount;
private LocalDateTime createTime;
private LocalDateTime updateTime;
private Integer deleted;

// 无参构造、全参构造、Getter/Setter、toString()
}

七、注册功能(PreparedStatement防SQL注入)

java
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class UserService {

/**
* 用户注册
* @return true-注册成功, false-用户名已存在
*/
public boolean register(User user) {
String checkSql = "SELECT id FROM user WHERE username = ? AND deleted = 0";
String insertSql = "INSERT INTO user(username, password, email, phone) VALUES(?, ?, ?, ?)";

try (Connection conn = DBUtil.getConnection()) {
// 1. 检查用户名是否已存在
try (PreparedStatement checkStmt = conn.prepareStatement(checkSql)) {
checkStmt.setString(1, user.getUsername());
try (ResultSet rs = checkStmt.executeQuery()) {
if (rs.next()) return false;
}
}

// 2. 插入新用户
try (PreparedStatement insertStmt = conn.prepareStatement(insertSql)) {
insertStmt.setString(1, user.getUsername());
insertStmt.setString(2, user.getPassword());
insertStmt.setString(3, user.getEmail());
insertStmt.setString(4, user.getPhone());
return insertStmt.executeUpdate() > 0;
}
} catch (SQLException e) {
e.printStackTrace();
return false;
}
}
}

八、登录功能

java
public User login(String username, String password) {
String sql = "SELECT id, username, email, phone, news_count FROM user WHERE username = ? AND password = ? AND deleted = 0";

try (Connection conn = DBUtil.getConnection();
PreparedStatement stmt = conn.prepareStatement(sql)) {

stmt.setString(1, username);
stmt.setString(2, password);

try (ResultSet rs = stmt.executeQuery()) {
if (rs.next()) {
User user = new User();
user.setId(rs.getInt("id"));
user.setUsername(rs.getString("username"));
user.setEmail(rs.getString("email"));
user.setPhone(rs.getString("phone"));
user.setNewsCount(rs.getInt("news_count"));
return user;
}
}
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}

九、事务管理(发布新闻)

事务特性(ACID)
特性 说明
原子性 一组操作要么全部成功,要么全部失败
一致性 操作前后数据状态保持一致
隔离性 并发事务互不干扰
持久性 提交后数据永久保存
事务关键方法
java
Connection conn = DBUtil.getConnection();
conn.setAutoCommit(false); // 开启事务
conn.commit(); // 提交
conn.rollback(); // 回滚
发布新闻实现
java
public class NewsService {

public boolean publishNews(News news) {
String insertSql = "INSERT INTO news(title, content, author_id, author_name) VALUES(?, ?, ?, ?)";
String updateSql = "UPDATE user SET news_count = news_count + 1 WHERE id = ?";

Connection conn = null;
try {
conn = DBUtil.getConnection();
conn.setAutoCommit(false);

// 1. 插入新闻
try (PreparedStatement insertStmt = conn.prepareStatement(insertSql)) {
insertStmt.setString(1, news.getTitle());
insertStmt.setString(2, news.getContent());
insertStmt.setInt(3, news.getAuthorId());
insertStmt.setString(4, news.getAuthorName());
if (insertStmt.executeUpdate() == 0) {
throw new SQLException("新闻插入失败");
}
}

// 2. 更新用户news_count
try (PreparedStatement updateStmt = conn.prepareStatement(updateSql)) {
updateStmt.setInt(1, news.getAuthorId());
if (updateStmt.executeUpdate() == 0) {
throw new SQLException("用户更新失败");
}
}

conn.commit();
return true;

} catch (SQLException e) {
if (conn != null) {
try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); }
}
e.printStackTrace();
return false;
} finally {
if (conn != null) {
try { conn.setAutoCommit(true); conn.close(); } catch (SQLException e) { e.printStackTrace(); }
}
}
}
}

第三部分:SpringBoot + MyBatis-Plus(课次23-25)

十、项目创建

核心依赖(pom.xml)
xml



org.springframework.boot
spring-boot-starter-web



com.mysql
mysql-connector-j
runtime



com.baomidou
mybatis-plus-spring-boot3-starter
3.5.3.1



org.projectlombok
lombok
true

十一、配置文件(application.yml)

yaml
server:
port: 8080
servlet:
context-path: /api

spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/news_management?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8mb4
username: root
password: 123456

jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: Asia/Shanghai

mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
map-underscore-to-camel-case: true
global-config:
db-config:
logic-delete-field: deleted
logic-delete-value: 1
logic-not-delete-value: 0
id-type: auto

十二、Lombok常用注解

注解 作用
@Data 生成Getter/Setter、toString、equals、hashCode
@NoArgsConstructor 生成无参构造
@AllArgsConstructor 生成全参构造
@Builder 建造者模式
@Slf4j 日志对象

十三、MyBatis-Plus核心注解

注解 作用
@TableName 指定数据库表名
@TableId(type = IdType.AUTO) 指定主键,自增
@TableField 指定字段映射
@TableField(fill = FieldFill.INSERT) 插入时自动填充
@TableField(fill = FieldFill.INSERT_UPDATE) 插入和更新时自动填充
@TableLogic 逻辑删除字段
@Mapper 标记Mapper接口

十四、实体类

User实体
java
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;

@Data
@TableName("user")
public class User {
@TableId(type = IdType.AUTO)
private Integer id;
private String username;
private String password;
private String email;
private String phone;
private Integer newsCount;

@TableField(value = "create_time", fill = FieldFill.INSERT)
private LocalDateTime createTime;

@TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;

@TableLogic
private Integer deleted;
}
News实体
java
@Data
@TableName("news")
public class News {
@TableId(type = IdType.AUTO)
private Integer id;
private String title;
private String content;
private Integer authorId;
private String authorName;
private Integer viewCount;
private Integer status;

@TableField(value = "create_time", fill = FieldFill.INSERT)
private LocalDateTime createTime;

@TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;

@TableLogic
private Integer deleted;
}

十五、自动填充处理器

java
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;

@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}

@Override
public void updateFill(MetaObject metaObject) {
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
}

十六、Mapper接口

java
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface UserMapper extends BaseMapper {
// 继承BaseMapper,自动获得CRUD方法
}

@Mapper
public interface NewsMapper extends BaseMapper {
// 继承BaseMapper,自动获得CRUD方法
}
BaseMapper常用方法
方法 说明
insert(T entity) 插入记录
deleteById(Serializable id) 根据ID删除(逻辑删除)
updateById(T entity) 根据ID更新
selectById(Serializable id) 根据ID查询
selectList(Wrapper wrapper) 条件查询列表
selectPage(Page page, Wrapper wrapper) 分页查询
selectCount(Wrapper wrapper) 查询记录数
selectOne(Wrapper wrapper) 查询单条记录

十七、条件构造器 LambdaQueryWrapper

常用方法
方法 说明 示例
eq 等于 wrapper.eq(User::getUsername, "张三")
ne 不等于 wrapper.ne(User::getStatus, 0)
gt / ge 大于 / 大于等于 wrapper.ge(User::getNewsCount, 5)
lt / le 小于 / 小于等于 wrapper.le(User::getNewsCount, 10)
like 模糊匹配 wrapper.like(User::getUsername, "张")
likeLeft %value wrapper.likeLeft(User::getUsername, "张")
likeRight value% wrapper.likeRight(User::getUsername, "张")
in IN查询 wrapper.in(User::getId, Arrays.asList(1,2,3))
between BETWEEN wrapper.between(User::getCreateTime, start, end)
isNull / isNotNull 空判断 wrapper.isNull(User::getPhone)
orderByAsc / orderByDesc 排序 wrapper.orderByDesc(User::getCreateTime)
查询示例
java
@Test
public void testLambdaQuery() {
// 单条件查询
LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getUsername, "张三")
.eq(User::getDeleted, 0);
User user = userMapper.selectOne(wrapper);

// 多条件组合 + 排序
LambdaQueryWrapper wrapper2 = new LambdaQueryWrapper<>();
wrapper2.like(User::getUsername, "张")
.ge(User::getNewsCount, 3)
.orderByDesc(User::getCreateTime);
List users = userMapper.selectList(wrapper2);

// 条件判断(避免空值)
String keyword = "张";
LambdaQueryWrapper wrapper3 = new LambdaQueryWrapper<>();
wrapper3.like(keyword != null && !keyword.isEmpty(),
User::getUsername, keyword)
.eq(User::getDeleted, 0);
}

十八、分页插件配置与使用

分页插件配置
java
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
PaginationInnerInterceptor paginationInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
paginationInterceptor.setMaxLimit(100L);
interceptor.addInnerInterceptor(paginationInterceptor);
return interceptor;
}
}
分页查询示例
java
@Test
public void testPageQuery() {
// 1. 创建分页对象(当前页,每页大小)
Page page = new Page<>(1, 10);

// 2. 构建查询条件
LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>();
wrapper.eq(News::getStatus, 1)
.orderByDesc(News::getCreateTime);

// 3. 执行分页查询
Page result = newsMapper.selectPage(page, wrapper);

// 4. 获取分页数据
List records = result.getRecords(); // 当前页数据
long total = result.getTotal(); // 总记录数
long pages = result.getPages(); // 总页数
long current = result.getCurrent(); // 当前页码
long size = result.getSize(); // 每页大小
}
分页结果封装
java
@Data
public class PageResult {
private Long total;
private Long pages;
private Long current;
private Long size;
private List records;

public static PageResult fromPage(Page page) {
PageResult result = new PageResult<>();
result.setTotal(page.getTotal());
result.setPages(page.getPages());
result.setCurrent(page.getCurrent());
result.setSize(page.getSize());
result.setRecords(page.getRecords());
return result;
}
}
带条件的分页查询
java
@Test
public void testPageWithCondition() {
String keyword = "Spring";
Integer status = 1;

Page page = new Page<>(1, 5);
LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>();
wrapper.like(keyword != null && !keyword.isEmpty(), News::getTitle, keyword)
.eq(status != null, News::getStatus, status)
.orderByDesc(News::getCreateTime);

Page result = newsMapper.selectPage(page, wrapper);
PageResult pageResult = PageResult.fromPage(result);
}

十九、启动类与测试Controller

启动类
java
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class NewsApplication {
public static void main(String[] args) {
SpringApplication.run(NewsApplication.class, args);
}
}
测试Controller
java
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;

@RestController
public class HelloController {
@GetMapping("/hello")
public Map<String, String> hello() {
Map<String, String> result = new HashMap<>();
result.put("message", "SpringBoot 3.1.5 启动成功!");
result.put("time", java.time.LocalDateTime.now().toString());
return result;
}
}
访问:http://localhost:8080/api/hello

附录:快速参考

JDBC vs MyBatis-Plus 对比
特性 JDBC原生 MyBatis-Plus
SQL编写 手动编写完整SQL 自动生成CRUD,条件构造器
结果映射 手动rs.getXxx()赋值 自动映射到实体类
SQL注入防护 PreparedStatement 自动使用预编译
分页 手动拼接LIMIT 分页插件自动处理
事务管理 setAutoCommit(false) @Transactional注解
开发效率 低 高
事务管理对比
方式 代码示例 适用场景
JDBC原生 conn.setAutoCommit(false) + commit()/rollback() 学习原理
Spring注解 @Transactional 生产环境推荐

项目结构参考

text
src/main/java/com/example/news/
├── NewsApplication.java # 启动类
├── config/
│ ├── MybatisPlusConfig.java # MyBatis-Plus配置
│ └── MyMetaObjectHandler.java # 自动填充处理器
├── controller/
│ └── HelloController.java # 控制器
├── entity/
│ ├── User.java # 用户实体
│ └── News.java # 新闻实体
├── mapper/
│ ├── UserMapper.java # 用户Mapper
│ └── NewsMapper.java # 新闻Mapper
└── service/
├── UserService.java # 用户服务
└── NewsService.java # 新闻服务