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),提升数据库连接性能
相关推荐
黑客大白2 小时前
IDEA安装教程配置java环境(超详细)_idea配置java,零基础入门到精通,收藏这篇就够了
java·ide·intellij-idea
a9511416422 小时前
如何在Bootstrap中实现响应式的统计数据卡片
jvm·数据库·python
熬夜的咕噜猫2 小时前
Nosql Redis配置与优化
数据库·redis·nosql
ch.ju2 小时前
Java程序设计(第3版)第二章——三元运算符
java
txxzjmzlh2 小时前
Java 线程的几种状态
java·开发语言
lly2024062 小时前
JavaScript 对象
开发语言
椰猫子2 小时前
数据库(数据库相关概念、MySQL数据库、SQL(DDL、DML、DQL))
数据库·sql·mysql
Shorasul2 小时前
golang如何实现设备数据采集网关_golang设备数据采集网关实现要点
jvm·数据库·python
椰羊~王小美2 小时前
主流编程语言及工具的典型应用场景总结
java