Springboot | Spring Boot 3 纯 JDBC 实现宠物管理系统增删改查(无 ORM 框架)

Spring Boot 3 纯 JDBC 实现宠物管理系统增删改查(无 ORM 框架)

在日常开发中,MyBatis、JPA 等 ORM 框架确实能大幅简化数据操作,但了解底层 JDBC 实现,能帮助我们更清晰地理解数据库连接、SQL 执行的核心逻辑。本文将基于 Spring Boot 3,不依赖任何 ORM 框架,仅通过原生 JDBC 完成宠物管理系统的增删改查(CRUD)操作,适合想夯实 JDBC 基础的同学参考。

一、环境准备

1. 技术栈说明

  • JDK:17+(Spring Boot 3 最低要求)
  • Spring Boot:3.2.x
  • 数据库:MySQL 8.0
  • 依赖:Spring Boot 核心依赖 + JDBC 启动器 + MySQL 驱动

2. 项目依赖配置(Maven)

创建 Spring Boot 项目后,在 pom.xml 中添加以下核心依赖:

xml 复制代码
<!-- Spring Boot 核心依赖 -->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.2.5</version>
    <relativePath/>
</parent>

<dependencies>
    <!-- Spring Boot JDBC 启动器(封装了原生 JDBC 操作,简化配置) -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
    
    <!-- MySQL 驱动(适配 MySQL 8.0) -->
    <dependency>
        <groupId>com.mysql</groupId>
        <artifactId>mysql-connector-j</artifactId>
        <scope>runtime</scope>
    </dependency>
    
    <!-- Spring Boot 测试依赖(用于后续功能测试) -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

3. 数据库连接配置

src/main/resources/application.properties 中配置 MySQL 连接信息,Spring Boot 会自动创建 DataSource(数据源)和 JdbcTemplate(JDBC 工具类,简化原生 JDBC 样板代码):

properties 复制代码
# 数据库 URL(替换为你的数据库名,这里用 pet_management)
spring.datasource.url=jdbc:mysql://localhost:3306/pet_management?serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true
# 数据库用户名
spring.datasource.username=root
# 数据库密码
spring.datasource.password=你的数据库密码
# MySQL 8.0 驱动类(Spring Boot 3 无需手动指定,会自动识别,但写出来更清晰)
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

4. 数据库表创建

手动在 MySQL 中创建 pet 表,字段与 Pet 实体类一一对应:

sql 复制代码
CREATE DATABASE IF NOT EXISTS pet_management;
USE pet_management;

-- 宠物表
CREATE TABLE IF NOT EXISTS pet (
    id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '宠物ID(自增)',
    name VARCHAR(50) NOT NULL COMMENT '宠物名称',
    breed VARCHAR(50) NOT NULL COMMENT '宠物品种',
    age INT COMMENT '宠物年龄(单位:岁)'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='宠物信息表';

二、核心代码实现

1. 实体类(Pet)

按照需求定义实体类,属性与数据库表字段对应:

java 复制代码
public class Pet {
    private Long id;         // 宠物ID(唯一标识,自增)
    private String name;     // 宠物名称
    private String breed;    // 宠物品种
    private Integer age;     // 宠物年龄

    // 无参构造(Spring 反射需要)
    public Pet() {}

    // 带参构造(不含 id,新增时无需指定)
    public Pet(String name, String breed, Integer age) {
        this.name = name;
        this.breed = breed;
        this.age = age;
    }

    // Getter + Setter(必须,否则无法通过结果集赋值)
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public String getBreed() { return breed; }
    public void setBreed(String breed) { this.breed = breed; }
    public Integer getAge() { return age; }
    public void setAge(Integer age) { this.age = age; }

    // toString 方法(方便打印测试结果)
    @Override
    public String toString() {
        return "Pet{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", breed='" + breed + '\'' +
                ", age=" + age +
                '}';
    }
}

2. 数据操作层(PetService)

核心逻辑集中在这里:通过 Spring 提供的 JdbcTemplate 封装 JDBC 操作,避免手动处理 ConnectionStatementResultSet 等样板代码(JdbcTemplate 已帮我们处理资源关闭、异常封装)。

创建 service 包,编写 PetService 类:

java 复制代码
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.stereotype.Service;
import java.sql.PreparedStatement;
import java.util.List;

@Service // 交给 Spring 管理,方便测试时注入
public class PetService {

    // 注入 Spring 自动配置的 JdbcTemplate
    private final JdbcTemplate jdbcTemplate;

    // 构造器注入(Spring Boot 3 推荐构造器注入,无需 @Autowired)
    public PetService(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    /**
     * 1. 新增宠物(返回自增 ID)
     */
    public Long addPet(Pet pet) {
        // SQL 语句:? 是占位符,避免 SQL 注入
        String sql = "INSERT INTO pet (name, breed, age) VALUES (?, ?, ?)";
        
        // KeyHolder:用于获取新增后的自增 ID
        KeyHolder keyHolder = new GeneratedKeyHolder();
        
        jdbcTemplate.update(connection -> {
            // 创建 PreparedStatement,指定返回自增主键
            PreparedStatement ps = connection.prepareStatement(sql, new String[]{"id"});
            // 给占位符赋值(顺序与 SQL 中 ? 一致)
            ps.setString(1, pet.getName());
            ps.setString(2, pet.getBreed());
            ps.setObject(3, pet.getAge()); // 用 setObject 兼容 null(年龄可能为空)
            return ps;
        }, keyHolder);
        
        // 返回自增的 ID
        return keyHolder.getKey().longValue();
    }

    /**
     * 2. 根据 ID 查询宠物
     */
    public Pet getPetById(Long id) {
        String sql = "SELECT id, name, breed, age FROM pet WHERE id = ?";
        
        // queryForObject:查询单个结果,RowMapper 用于将结果集映射为 Pet 对象
        try {
            return jdbcTemplate.queryForObject(sql, (rs, rowNum) -> {
                Pet pet = new Pet();
                pet.setId(rs.getLong("id"));
                pet.setName(rs.getString("name"));
                pet.setBreed(rs.getString("breed"));
                pet.setAge(rs.getInt("age"));
                return pet;
            }, id); // 最后一个参数是 SQL 中 ? 的值
        } catch (Exception e) {
            // 若查询不到数据,返回 null(避免抛出 NoSuchElementException)
            return null;
        }
    }

    /**
     * 3. 查询所有宠物
     */
    public List<Pet> getAllPets() {
        String sql = "SELECT id, name, breed, age FROM pet";
        
        // query:查询多个结果,返回 List
        return jdbcTemplate.query(sql, (rs, rowNum) -> {
            Pet pet = new Pet();
            pet.setId(rs.getLong("id"));
            pet.setName(rs.getString("name"));
            pet.setBreed(rs.getString("breed"));
            pet.setAge(rs.getInt("age"));
            return pet;
        });
    }

    /**
     * 4. 根据 ID 更新宠物信息
     */
    public int updatePet(Long id, Pet pet) {
        String sql = "UPDATE pet SET name = ?, breed = ?, age = ? WHERE id = ?";
        
        // update:执行增删改操作,返回受影响的行数
        return jdbcTemplate.update(
            sql,
            pet.getName(),
            pet.getBreed(),
            pet.getAge(),
            id // 最后一个参数是 WHERE 条件的 id
        );
    }

    /**
     * 5. 根据 ID 删除宠物
     */
    public int deletePet(Long id) {
        String sql = "DELETE FROM pet WHERE id = ?";
        return jdbcTemplate.update(sql, id);
    }
}

三、功能测试

通过 Spring Boot 测试类,验证所有 CRUD 操作是否正常。创建 src/test/java/xxx/xxx/PetServiceTest(包路径与主程序一致):

java 复制代码
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;

@SpringBootTest // 启动 Spring 上下文,自动注入依赖
public class PetServiceTest {

    @Autowired
    private PetService petService;

    @Test
    public void testCRUD() {
        // 1. 新增宠物
        Pet newPet = new Pet("旺财", "金毛", 3);
        Long petId = petService.addPet(newPet);
        System.out.println("新增宠物 ID:" + petId);
        assertNotNull(petId, "新增宠物失败,ID 为空");

        // 2. 根据 ID 查询
        Pet queriedPet = petService.getPetById(petId);
        System.out.println("查询到的宠物:" + queriedPet);
        assertNotNull(queriedPet, "查询宠物失败");
        assertEquals("旺财", queriedPet.getName(), "宠物名称不匹配");

        // 3. 更新宠物信息
        queriedPet.setName("来福");
        queriedPet.setAge(4);
        int updateRows = petService.updatePet(petId, queriedPet);
        System.out.println("更新受影响行数:" + updateRows);
        assertEquals(1, updateRows, "更新宠物失败");

        // 验证更新结果
        Pet updatedPet = petService.getPetById(petId);
        System.out.println("更新后的宠物:" + updatedPet);
        assertEquals("来福", updatedPet.getName(), "更新后名称不匹配");
        assertEquals(4, updatedPet.getAge(), "更新后年龄不匹配");

        // 4. 查询所有宠物
        List<Pet> allPets = petService.getAllPets();
        System.out.println("所有宠物:" + allPets);
        assertFalse(allPets.isEmpty(), "查询所有宠物失败");

        // 5. 删除宠物
        int deleteRows = petService.deletePet(petId);
        System.out.println("删除受影响行数:" + deleteRows);
        assertEquals(1, deleteRows, "删除宠物失败");

        // 验证删除结果
        Pet deletedPet = petService.getPetById(petId);
        assertNull(deletedPet, "删除宠物失败");
    }
}

测试结果

运行测试方法 testCRUD(),控制台输出如下(说明所有操作正常):

复制代码
新增宠物 ID:1
查询到的宠物:Pet{id=1, name='旺财', breed='金毛', age=3}
更新受影响行数:1
更新后的宠物:Pet{id=1, name='来福', breed='金毛', age=4}
所有宠物:[Pet{id=1, name='来福', breed='金毛', age=4}]
删除受影响行数:1

四、关键注意事项

  1. SQL 占位符使用 :必须用 ? 占位符传递参数,避免直接拼接 SQL(防止 SQL 注入攻击)。
  2. 空值处理 :年龄可能为空时,用 setObject 赋值(setInt 无法接收 null)。
  3. 自增 ID 获取 :新增时通过 KeyHolder 获取自增 ID,需在 prepareStatement 中指定 new String[]{"id"}(对应表的主键字段名)。
  4. 结果集映射RowMapper 是核心,负责将 ResultSet 中的字段值映射到实体类属性,字段名需与 SQL 查询字段一致。
  5. 资源管理JdbcTemplate 已自动处理 ConnectionStatementResultSet 的关闭,无需手动关闭(避免资源泄露)。
  6. 异常处理JdbcTemplateSQLException 封装为 DataAccessException(Spring 统一数据访问异常),可根据需求捕获处理。

五、总结

本文通过 Spring Boot 3 + 纯 JDBC 实现了宠物管理系统的 CRUD 操作,核心是借助 JdbcTemplate 简化原生 JDBC 的样板代码,同时保留了 JDBC 的底层逻辑。这种方式的优势是轻量、灵活、无额外依赖,适合简单项目或需要深入理解数据库操作底层的场景。

如果你的项目需要更复杂的查询(如联表、分页)或批量操作,JdbcTemplate 也能支持(可通过 batchUpdate、自定义 RowMapper 实现)。通过本文的实践,相信你能更清晰地理解 ORM 框架的底层原理,为后续使用 MyBatis、JPA 等框架打下基础。

动手试试吧!如果遇到问题,可检查数据库连接配置、SQL 语句字段匹配、实体类 Getter/Setter 是否齐全~

相关推荐
用户99045017780092 分钟前
ruoyi-vue2集成flowable6.7.2后端篇
后端
qq_12498707536 分钟前
基于springboot框架的小型饮料销售管理系统的设计与实现(源码+论文+部署+安装)
java·spring boot·后端·spring·毕业设计
我命由我1234513 分钟前
Python Flask 开发:在 Flask 中返回字符串时,浏览器将其作为 HTML 解析
服务器·开发语言·后端·python·flask·html·学习方法
IT_陈寒21 分钟前
JavaScript 性能优化:5个被低估的V8引擎技巧让你的代码提速50%
前端·人工智能·后端
YMatrix 官方技术社区23 分钟前
YMatrix 高可用详解:3 种镜像策略在节点宕机时表现有何不同?
运维·数据库·数据仓库·ai·数据库开发·数据库架构·ymatrix
想用offer打牌33 分钟前
数据库大事务有什么危害(面试版)
数据库·后端·架构
Jaising66634 分钟前
Spring 错误使用事务导致数据可见性问题分析
数据库·spring boot
踏浪无痕41 分钟前
别再只会用 Feign!手写一个 Mini RPC 框架搞懂 Spring Cloud 底层原理
后端·面试·架构
NMBG2243 分钟前
外卖综合项目
java·前端·spring boot
小徐Chao努力1 小时前
Spring AI Alibaba A2A 使用指南
java·人工智能·spring boot·spring·spring cloud·agent·a2a