JDBC笔记

Java 2026-07-13 69
目录
预计阅读时间:10 分钟

一、依赖

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.47</version>
    <scope>runtime</scope>
</dependency>

二、获取链接

// JDBC连接的URL, 不同数据库有不同的格式:
String JDBC_URL = "jdbc:mysql://localhost:3306/test";
String JDBC_USER = "root";
String JDBC_PASSWORD = "password";
// 获取连接:
Connection conn = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD);
// TODO: 访问数据库...
// 关闭连接:
conn.close();

// 更多使用try()
try (Connection conn = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD)) {
    ...
}
  • URL

    说明 示例
    jdbc 协议固定前缀 jdbc
    mysql 数据库类型 mysqlpostgresql
    localhost 数据库服务器地址 localhost(本机)或 IP
    3306 端口号 MySQL 默认 3306
    test 数据库名 你要连接的库
    - USER

    每个库有若干USER,认证身份,管理权限,审计追踪

    • 设计原则

      原则 说明 示例
      最小权限 只给必要的权限 只读用户不给写权限
      分离用户 不同用途不同用户 查询用 readonly,维护用 admin
      限制来源 指定允许的主机 'user'@'192.168.1.%' 而不是 'user'@'%'
      定期改密 密码定期更换 使用密码管理工具
      禁止硬编码 代码里不写明文密码 使用环境变量或配置中心
      - USER的属性
      属性 说明
      user 用户名
      host 允许从哪个主机连接(localhost 只能本机,% 任意主机)
      password 加密存储的密码
      privileges 拥有的权限
      - 命名规范

      java // 常见命名方式 "root" // 超级管理员 "admin" // 管理员 "app_java" // Java应用专用 "readonly_user" // 只读用户 "backend_service" // 后端服务用户 "deploy" // 部署用户

    • 创建不同场景的用户

      语法

      sql CREATE USER '用户名'@'主机' IDENTIFIED BY '密码'; GRANT 权限列表 ON 数据库名.表名 TO '用户名'@'主机'; // 权限列表即SQL语法关键字 // 库名字.*表示所有表

      例如

      ```sql -- 1. 只读权限 GRANT SELECT ON test.* TO 'readonly'@'%';

      -- 2. 读写权限(增删改查) GRANT SELECT, INSERT, UPDATE, DELETE ON test.* TO 'app'@'%';

      -- 3. 表结构管理权限 GRANT CREATE, ALTER, DROP, INDEX ON test.* TO 'developer'@'%';

      -- 4. 完整数据库管理权限 GRANT ALL PRIVILEGES ON test.* TO 'admin'@'localhost';

      -- 5. 全局管理权限(谨慎使用) GRANT ALL PRIVILEGES ON . TO 'super_admin'@'localhost' WITH GRANT OPTION; ```

三、操作

1、查询

  1. 获取 Connection 连接实例
  2. 编写带占位符 ? 的 SQL 语句
  3. 使用 Connection 的 prepareStatement(sql) 方法创建 PreparedStatement 对象
  4. 使用 setXxx() 方法为占位符设置参数(索引从1开始)
  5. 执行 PreparedStatement 对象提供的 executeQuery() 方法。使用 ResultSet 接收结果集
  6. 根据 SELECT 列的对应位置或列名来调用 getLong()getString() 等方法

  • 基础示例

    ```java // 带占位符的 SQL String sql = "SELECT id, grade, name, gender FROM students WHERE gender = ? AND grade > ?";

    try (Connection conn = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD); PreparedStatement pstmt = conn.prepareStatement(sql)) {

    // 设置参数(索引从1开始)
    pstmt.setInt(1, 1);      // gender = 1
    pstmt.setInt(2, 2);      // grade > 2
    
    // 执行查询
    try (ResultSet rs = pstmt.executeQuery()) {
        while (rs.next()) {
            // 方式1:按索引获取(索引从1开始)
            long id = rs.getLong(1);
            long grade = rs.getLong(2);
    
            // 方式2:按列名获取(推荐,更清晰)
            String name = rs.getString("name");
            int gender = rs.getInt("gender");
        }
    }
    

    } catch (SQLException e) { e.printStackTrace(); } ```

  • 对于空值null

    ```java // 场景1:数据库真的是 0 int age = rs.getInt("age"); // 返回 0 // 场景2:数据库是 NULL int age = rs.getInt("age"); // 因为基本类型没有null,所以也返回 0(无法区分!)

    // 为此,使用以下写法 int age = rs.getInt("age"); // 先读取 if (rs.wasNull()) { // 再判断刚才读到的是不是 NULL // 说明数据库里是 NULL } else { // 说明数据库里有值(可能是0,也可能是其他数) } ```

  • 类型映射

    SQL 数据类型 Java 数据类型 ResultSet 获取方法 PreparedStatement 设置方法
    BITBOOLBOOLEAN boolean getBoolean() setBoolean()
    TINYINTSMALLINTINTEGER int getInt() setInt()
    BIGINT long getLong() setLong()
    REAL float getFloat() setFloat()
    FLOATDOUBLE double getDouble() setDouble()
    CHARVARCHARTEXT String getString() setString()
    以下不太常用 比较特殊 比较难记 hhh😄❌
    DECIMALNUMERIC java.math.BigDecimal getBigDecimal() setBigDecimal()
    DATE java.sql.Date / LocalDate getDate() / getObject() setDate() / setObject()
    TIME java.sql.Time / LocalTime getTime() / getObject() setTime() / setObject()
    TIMESTAMPDATETIME java.sql.Timestamp / LocalDateTime getTimestamp() / getObject() setTimestamp() / setObject()
    BINARYBLOB byte[] getBytes() setBytes()
    CLOB java.sql.Clob / String getClob() / getString() setClob() / setString()

    日期时间

    ```java // 查询 - 直接获取 LocalDate birthDate = rs.getObject("birth_date", LocalDate.class); LocalDateTime createTime = rs.getObject("create_time", LocalDateTime.class); LocalTime alarmTime = rs.getObject("alarm_time", LocalTime.class);

    // 设置参数 pstmt.setObject(1, LocalDate.now()); pstmt.setObject(2, LocalDateTime.now()); pstmt.setObject(3, LocalTime.now()); ```

    DECIMAL 

    ```java // 查询 BigDecimal price = rs.getBigDecimal("price"); // 或 BigDecimal price = rs.getObject("price", BigDecimal.class);

    // 设置 pstmt.setBigDecimal(1, new BigDecimal("99.99")); // 精度 BigDecimal amount = rs.getBigDecimal("amount", 2); // 保留2位小数 ```

2、插入,更新,删除executeUpdate()

使用executeUpdate() ,返回该次操作的记录数

  • 插入

    使用pstmt实例的getGeneratedKeys()方法返回ResultSet之后插入数据的获取主键

    ```java try (Connection conn = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD)) { try (PreparedStatement ps = conn.prepareStatement( "INSERT INTO students (id, grade, name, gender) VALUES (?,?,?,?)")) { ps.setObject(1, 999); // 注意:索引从1开始 ps.setObject(2, 1); // grade ps.setObject(3, "Bob"); // name ps.setObject(4, "M"); // gender int n = ps.executeUpdate(); // 1

        // 获取主键
        try (ResultSet rs = ps.getGeneratedKeys()) {
            if (rs.next()) {
                long id = rs.getLong(1); // 注意:索引从1开始
            }
        }
    }
    

    } ```

  • 更新

    java try (Connection conn = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD)) { try (PreparedStatement ps = conn.prepareStatement("UPDATE students SET name=? WHERE id=?")) { ps.setObject(1, "Bob"); // 注意:索引从1开始 ps.setObject(2, 999); int n = ps.executeUpdate(); // 返回更新的行数 } }

  • 删除

    java try (Connection conn = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD)) { try (PreparedStatement ps = conn.prepareStatement("DELETE FROM students WHERE id=?")) { ps.setObject(1, 999); // 注意:索引从1开始 int n = ps.executeUpdate(); // 删除的行数 } }

四、事务

基础语法使用try

Connection conn = openConnection();
try {
    // 关闭自动提交:
    conn.setAutoCommit(false);
    // 执行多条SQL语句:
    insert(); update(); delete();
    // 提交事务:
    conn.commit();
} catch (SQLException e) {
    // 回滚事务:
    conn.rollback();
} finally {
    conn.setAutoCommit(true);
    conn.close();
}

普通项目可以使用封装来实现简化编写

Spring则只需使用@Transactional 注解

五、批处理

对于一系列只有参数不同的SQL语句的执行,使用专门的批处理而非循环遍历。

  1. 对于pstmt反复执行setXxx()操作
  2. 使用pstmt的executeBatch()方法,返回一维数组
try (PreparedStatement ps = conn.prepareStatement("INSERT INTO students (name, gender, grade, score) VALUES (?, ?, ?, ?)")) {
    // 对同一个PreparedStatement反复设置参数并调用addBatch():
    for (Student s : students) {
        ps.setString(1, s.name);
        ps.setBoolean(2, s.gender);
        ps.setInt(3, s.grade);
        ps.setInt(4, s.score);
        ps.addBatch(); // 添加到batch
    }
    // 执行batch:
    int[] ns = ps.executeBatch();
    for (int n : ns) {
        System.out.println(n + " inserted."); // batch中每个SQL执行的结果数量
    }
}

连接池

  • 依赖

    xml <dependency> <groupId>com.zaxxer</groupId> <artifactId>HikariCP</artifactId> <version>2.7.1</version> </dependency>

  • 创建链接池

    ```java HikariConfig config = new HikariConfig(); config.setJdbcUrl("jdbc:mysql://localhost:3306/test"); config.setUsername("root"); config.setPassword("password"); config.addDataSourceProperty("connectionTimeout", "1000"); // 连接超时:1秒 config.addDataSourceProperty("idleTimeout", "60000"); // 空闲超时:60秒 config.addDataSourceProperty("maximumPoolSize", "10"); // 最大连接数:10

    // 全局DataSource DataSource ds = new HikariDataSource(config); ```

  • 获取与使用连接

    ```java // 从连接池获取连接(推荐使用 try-with-resources 自动释放) try (Connection conn = ds.getConnection()) { // 执行数据库操作 PreparedStatement ps = conn.prepareStatement("SELECT * FROM users WHERE id = ?"); ps.setInt(1, 100); ResultSet rs = ps.executeQuery();

    while (rs.next()) {
        String name = rs.getString("name");
        System.out.println(name);
    }
    // 自动归还连接到池(无需手动 conn.close()

    } catch (SQLException e) { e.printStackTrace(); } ```

    conn.close() 实际是将连接归还到池中,而非真正关闭数据库连接。


  • HikariCP 完整配置参数

    ```java HikariConfig config = new HikariConfig();

    // 基本配置 config.setJdbcUrl("jdbc:mysql://localhost:3306/test"); config.setUsername("root"); config.setPassword("password"); config.setDriverClassName("com.mysql.cj.jdbc.Driver"); // MySQL 8.0 驱动

    // 连接池核心参数 config.setMaximumPoolSize(10); // 最大连接数(默认 10) config.setMinimumIdle(5); // 最小空闲连接数(默认同 max) config.setIdleTimeout(600000); // 空闲超时(毫秒,默认 600000,10分钟) config.setConnectionTimeout(30000); // 获取连接超时(毫秒,默认 30000) config.setMaxLifetime(1800000); // 连接最大生命周期(毫秒,默认 1800000,30分钟)

    // 连接校验 config.setConnectionTestQuery("SELECT 1"); // 校验 SQL(MySQL) config.setValidationTimeout(5000); // 校验超时(毫秒)

    // 性能优化 config.setLeakDetectionThreshold(10000); // 连接泄漏检测阈值(毫秒,0 表示禁用) config.setAutoCommit(false); // 关闭自动提交,手动管理事务 config.setReadOnly(false); // 是否只读模式

    config.setPoolName("MyHikariCP"); // 连接池名称(便于监控) ```

    tsx 连接数 = (核心数 × 2) + 有效磁盘数量 例如:8 核 CPU + 1 个 SSD → 8×2+1 = 17 个连接


本文由 tazume-sans 原创,转载请注明出处。

相关推荐

评论

0
暂无评论,来发表第一条评论吧

发表评论

登录 后发表评论