Java JDBC 封装:从原生写法到工具类封装 + 增删改查

在 Java 操作数据库的过程中,原生 JDBC 代码存在大量重复逻辑:加载驱动、获取连接、释放资源...... 这些代码在每个业务中都要写一遍,不仅繁琐,还容易出错。

本文是个人的一些学习笔记,主要内容如下:

  1. 原生 JDBC 写法与封装后写法对比
  2. 配置文件 + 工具类封装
  3. 增删改查 4 个完整测试案例
  4. 项目结构与代码规范说明

一、项目结构说明

当前的项目结构如下,所有代码均放在 com.qcby 包下,配置文件放在 resources 目录:

复制代码
src
├── main
│   ├── java
│   │   └── com.qcby
│   │       ├── JDBCUtils.java       // 封装好的工具类
│   │       ├── TestJDBC.java        // 原生JDBC查询案例(封装前)
│   │       ├── TestJDBCUtils.java   // 封装后查询案例
│   │       ├── TestUpdate.java     // 封装后增删改案例
│   └── resources
│       └── db.properties            // 数据库配置文件

二、封装前:原生 JDBC 写法(以查询为例)

1. 原生代码痛点

  • 硬编码数据库连接信息(URL、账号、密码),修改时需要改代码
  • 每次都要手动写加载驱动、获取连接、释放资源的重复代码
  • 资源关闭操作分散,容易遗漏导致连接泄漏

2. 原生查询案例代码(TestJDBC.java)

java 复制代码
package com.qcby;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;

public class TestJDBC {
    public static void main(String[] args) throws Exception {
        // 1. 加载驱动(硬编码驱动类路径)
        Class.forName("com.mysql.cj.jdbc.Driver");

        // 2. 获取连接(硬编码数据库连接信息)
        String url = "jdbc:mysql:///jdbcdemo?serverTimezone=UTC&useSSL=false";
        String user = "root";
        String password = "Zhen@777";
        Connection conn = DriverManager.getConnection(url, user, password);

        // 3. 获取执行SQL的对象
        Statement stmt = conn.createStatement();

        // 4. 执行查询SQL
        String sql = "select * from t_user";
        ResultSet rs = stmt.executeQuery(sql);

        // 5. 遍历结果集
        while (rs.next()) {
            int id = rs.getInt("id");
            String username = rs.getString("username");
            String pwd = rs.getString("password");
            String email = rs.getString("email");

            System.out.println(id + "\t" + username + "\t" + pwd + "\t" + email);
        }

        // 6. 手动释放所有资源(顺序:结果集→Statement→连接)
        rs.close();
        stmt.close();
        conn.close();
    }
}

三、封装:配置文件 + JDBC 工具类

1. 数据库配置文件(db.properties)

放在 src/main/resources 目录下,统一管理数据库连接信息,避免硬编码:

properties:

复制代码
# 驱动类路径(MySQL8.0+用cj包)
driverClass=com.mysql.cj.jdbc.Driver
# 数据库连接URL(必须加时区配置)
url=jdbc:mysql:///jdbcdemo?serverTimezone=UTC&useSSL=false
# 数据库账号
username=root
# 数据库密码(修改为你自己的)
password=Zhen@777

2. JDBC 工具类(JDBCUtils.java)

封装加载驱动、获取连接、释放资源的通用逻辑,一次编写,到处使用:

java 复制代码
package com.qcby;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Properties;

/**
 * JDBC工具类:封装通用的数据库操作逻辑
 * 功能:1. 读取配置文件 2. 加载驱动 3. 获取连接 4. 释放资源
 */
public class JDBCUtils {

    // 配置文件对象,静态加载一次
    private static Properties props = new Properties();

    // 静态代码块:类加载时自动执行,只执行一次
    static {
        try {
            // 读取resources下的db.properties配置文件
            props.load(JDBCUtils.class.getClassLoader().getResourceAsStream("db.properties"));

            // 加载驱动(从配置文件读取驱动类路径)
            Class.forName(props.getProperty("driverClass"));
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("JDBC工具类初始化失败,请检查配置文件!", e);
        }
    }

    /**
     * 获取数据库连接
     * @return Connection 数据库连接对象
     */
    public static Connection getConnection() {
        try {
            return DriverManager.getConnection(
                    props.getProperty("url"),
                    props.getProperty("username"),
                    props.getProperty("password")
            );
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 释放资源(查询场景:关闭ResultSet、Statement、Connection)
     * @param conn 数据库连接对象
     * @param stmt Statement对象
     * @param rs 结果集对象
     */
    public static void close(Connection conn, Statement stmt, ResultSet rs) {
        try {
            if (rs != null) rs.close();
            if (stmt != null) stmt.close();
            if (conn != null) conn.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 释放资源(增删改场景:关闭Statement、Connection,无需关闭ResultSet)
     * @param conn 数据库连接对象
     * @param stmt Statement对象
     */
    public static void close(Connection conn, Statement stmt) {
        close(conn, stmt, null);
    }
}

四、封装后:增删改查

1. 查询案例(TestJDBCUtils.java)

使用工具类完成查询,代码大幅简化,只关注业务 SQL:

java 复制代码
package com.qcby;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;

public class TestJDBCUtils {
    public static void main(String[] args) {
        Connection conn = null;
        Statement stmt = null;
        ResultSet rs = null;

        try {
            // 1. 从工具类获取连接(一行代码搞定)
            conn = JDBCUtils.getConnection();

            // 2. 获取执行SQL的对象
            stmt = conn.createStatement();

            // 3. 编写并执行查询SQL
            String sql = "select * from t_user";
            rs = stmt.executeQuery(sql);

            // 4. 遍历结果集并输出
            while (rs.next()) {
                System.out.println(
                        rs.getInt("id") + "\t" +
                        rs.getString("username") + "\t" +
                        rs.getString("password") + "\t" +
                        rs.getString("email")
                );
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 5. 释放所有资源(调用工具类方法)
            JDBCUtils.close(conn, stmt, rs);
        }
    }
}

2. 新增案例(TestUpdate.java 中 insert 部分)

java 复制代码
package com.qcby;

import java.sql.Connection;
import java.sql.Statement;

public class TestUpdate {
    public static void main(String[] args) {
        Connection conn = null;
        Statement stmt = null;

        try {
            // 1. 获取连接
            conn = JDBCUtils.getConnection();
            // 2. 获取执行对象
            stmt = conn.createStatement();

            // 3. 编写新增SQL
            String sql = "insert into t_user values(null,'封装测试','666','test@qq.com')";
            // 4. 执行增删改SQL(executeUpdate返回影响行数)
            int rows = stmt.executeUpdate(sql);
            System.out.println("新增数据成功,影响行数:" + rows);

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 5. 释放资源(增删改场景,无需ResultSet)
            JDBCUtils.close(conn, stmt);
        }
    }
}

3. 修改案例(TestUpdate.java 中 update 部分)

java 复制代码
package com.qcby;

import java.sql.Connection;
import java.sql.Statement;

public class TestUpdate {
    public static void main(String[] args) {
        Connection conn = null;
        Statement stmt = null;

        try {
            conn = JDBCUtils.getConnection();
            stmt = conn.createStatement();

            // 修改SQL:将用户名为"封装测试"的密码改为888888
            String sql = "update t_user set password='888888' where username='封装测试'";
            int rows = stmt.executeUpdate(sql);
            System.out.println("修改数据成功,影响行数:" + rows);

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.close(conn, stmt);
        }
    }
}

4. 删除案例(TestUpdate.java 中 delete 部分)

java 复制代码
package com.qcby;

import java.sql.Connection;
import java.sql.Statement;

public class TestUpdate {
    public static void main(String[] args) {
        Connection conn = null;
        Statement stmt = null;

        try {
            conn = JDBCUtils.getConnection();
            stmt = conn.createStatement();

            // 删除SQL:删除用户名为"封装测试"的记录
            String sql = "delete from t_user where username='封装测试'";
            int rows = stmt.executeUpdate(sql);
            System.out.println("删除数据成功,影响行数:" + rows);

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.close(conn, stmt);
        }
    }
}

五、封装前后对比总结

对比维度 原生 JDBC 写法 工具类封装写法
代码量 每个业务都要写连接、释放资源,代码冗余 仅关注业务 SQL,代码量减少 60% 以上
可维护性 数据库信息硬编码,修改需改动多处代码 配置文件统一管理,修改仅需改动配置文件
错误率 手动关闭资源易遗漏,导致连接泄漏 工具类统一处理,避免资源泄漏问题
复用性 无复用性,每个业务重复造轮子 工具类可在所有业务中直接调用

六、运行与验证步骤

  1. 检查配置文件 :确认 db.properties 中的账号、密码、URL 配置正确
  2. 运行查询案例 :执行 TestJDBCUtils.java,控制台输出 t_user 表所有数据
  3. 运行新增案例 :执行 TestUpdate.java 中的新增代码,控制台输出 影响行数:1,数据库新增一条数据
  4. 运行修改案例:执行修改代码,数据库中对应记录的密码被更新
  5. 运行删除案例:执行删除代码,数据库中新增的测试数据被删除

七、后续优化方向(其他博客有介绍)

  • 引入 PreparedStatement 防止 SQL 注入
  • 封装事务管理方法,实现业务操作的原子性
  • 增加连接池(如 Druid),提升数据库连接性能
相关推荐
你不是我我1 天前
【Java 开发日记】HTTP3 性能更好,为什么内网微服务依然多用 HTTP2?HTTP2 内网优势是什么?
java·开发语言·微服务
雪碧聊技术1 天前
大模型爆火!Java后端如何抓住Agent全栈开发的风口
java·大模型·agent·全栈开发
tjl521314_211 天前
04C++ 名称空间(Namespace)
开发语言·c++
赏金术士1 天前
Kotlin 数据流与单双向绑定
android·开发语言·kotlin
逻辑驱动的ken1 天前
Java高频面试场景题25
java·开发语言·深度学习·面试·职场和发展
杨云龙UP1 天前
SQL Server2022部署:Windows Server 2016下安装、SSMS配置、备份还原与1433端口放通全流程_20260508
运维·服务器·数据库·sql·sqlserver·2022
AI人工智能+电脑小能手1 天前
【大白话说Java面试题】【Java基础篇】第32题:Java的异常处理机制是什么
java·开发语言·后端·面试
爱吃土豆的马铃薯ㅤㅤㅤㅤㅤㅤㅤㅤㅤ1 天前
通过java后端代码来实现给word内容补充格式文本内容控件,以及 设置控件的标记和标题
java·c#·word
墨染天姬1 天前
【AI】cursor提示词小技巧
前端·数据库·人工智能
古月-一个C++方向的小白1 天前
MySQL数据库——数据类型
android·数据库·mysql