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 操作,避免手动处理 Connection、Statement、ResultSet 等样板代码(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
四、关键注意事项
- SQL 占位符使用 :必须用
?占位符传递参数,避免直接拼接 SQL(防止 SQL 注入攻击)。 - 空值处理 :年龄可能为空时,用
setObject赋值(setInt无法接收 null)。 - 自增 ID 获取 :新增时通过
KeyHolder获取自增 ID,需在prepareStatement中指定new String[]{"id"}(对应表的主键字段名)。 - 结果集映射 :
RowMapper是核心,负责将ResultSet中的字段值映射到实体类属性,字段名需与 SQL 查询字段一致。 - 资源管理 :
JdbcTemplate已自动处理Connection、Statement、ResultSet的关闭,无需手动关闭(避免资源泄露)。 - 异常处理 :
JdbcTemplate将SQLException封装为DataAccessException(Spring 统一数据访问异常),可根据需求捕获处理。
五、总结
本文通过 Spring Boot 3 + 纯 JDBC 实现了宠物管理系统的 CRUD 操作,核心是借助 JdbcTemplate 简化原生 JDBC 的样板代码,同时保留了 JDBC 的底层逻辑。这种方式的优势是轻量、灵活、无额外依赖,适合简单项目或需要深入理解数据库操作底层的场景。
如果你的项目需要更复杂的查询(如联表、分页)或批量操作,JdbcTemplate 也能支持(可通过 batchUpdate、自定义 RowMapper 实现)。通过本文的实践,相信你能更清晰地理解 ORM 框架的底层原理,为后续使用 MyBatis、JPA 等框架打下基础。
动手试试吧!如果遇到问题,可检查数据库连接配置、SQL 语句字段匹配、实体类 Getter/Setter 是否齐全~