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 是否齐全~

相关推荐
h***67374 小时前
SpringBoot整合easy-es
spring boot·后端·elasticsearch
S***267510 小时前
基于SpringBoot和Leaflet的行政区划地图掩膜效果实战
java·spring boot·后端
JIngJaneIL10 小时前
社区互助|社区交易|基于springboot+vue的社区互助交易系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·论文·毕设·社区互助
这是程序猿11 小时前
基于java的ssm框架旅游在线平台
java·开发语言·spring boot·spring·旅游·旅游在线平台
i***t91911 小时前
基于SpringBoot和PostGIS的云南与缅甸的千里边境线实战
java·spring boot·spring
k***082911 小时前
【监控】spring actuator源码速读
java·spring boot·spring
一 乐11 小时前
应急知识学习|基于springboot+vue的应急知识学习系统(源码+数据库+文档)
数据库·vue.js·spring boot