2.4 JDBC的诞生

从各数据库各说各话走到 JDBC 统一接口,用谈判、工厂、驱动、参数和事务追踪一次安全查询。

学习目标

  • 能沿配置、工厂、DataSource、Connection、PreparedStatement 和 ResultSet 追踪一次 JDBC 查询
  • 能用统一接口、面向接口编程、简单工厂和工厂方法解释应用如何隔离具体数据库驱动
  • 能在正常、边界和单一故障样本中定位参数注入、连接泄漏、驱动不匹配和事务提交错误

2.4 JDBC的诞生

本页依据刘欣《码农翻身》(2018 年第 1 版)及出版社公开书目信息,独立重构 2.4 JDBC的诞生。正文、代码、图示、实验和练习都是本课程重新设计的教学材料,不复制原书正文、插图、练习答案或代码。

数据库厂商各自拥有协议、类型和连接方式,应用如果直接调用每家 API,就会被实现细节锁住。JDBC 的关键不是“把所有数据库变成一样”,而是定义一组稳定的接口和责任:驱动负责翻译,DataSource 负责获得连接,Connection 负责事务边界,PreparedStatement 负责参数化语句,ResultSet 负责游标式读取。应用依赖接口,具体驱动由配置和工厂选择。

JDBC 访问链:抽象统一,责任不消失工厂选实现,驱动做翻译,连接管事务,语句与结果管资源1配置驱动与 URL选择证据2工厂DataSource选择证据3连接事务边界资源证据4语句参数化 SQL当前输入边界5结果游标与关闭资源证据统一 API 不代表方言、驱动版本、权限和事务语义完全相同
专属图示:一次 JDBC 调用的每个抽象层都有可验证的责任。

三个会让 JDBC 抽象失真的陷阱

七个目录节点到接口证据

谈判

不是让所有数据库失去特色,而是确定应用必须依赖的共同能力。接口名称、参数类型、资源关闭和异常传播要稳定;方言能力则通过扩展或明确降级表达。

统一接口

让业务代码不必直接知道每家驱动的类名。但接口调用仍有状态:连接可能在事务中,语句可能已关闭,结果集必须按游标读取。把接口当成无状态函数会隐藏资源和时序错误。

面向接口编程

把驱动选择放在组合根或工厂边界。测试可以注入假的 DataSource,生产环境注入真实池;替换实现仍要验证 SQL 方言、事务和错误语义。

简单工厂

适合把“选择哪种实现”从业务代码中移走。它不应把任意类名和凭据交给外部输入,也不应在每次查询中随意创建连接;配置校验、池生命周期和失败诊断属于工厂合同。

数据驱动

是真正面对数据库差异的地方。驱动版本、数据库版本、字符编码、时区、TLS 和错误映射都可能影响行为;应用要保存驱动身份和连接参数摘要,不能把接口层日志当成全部事实。

工厂方法

适合区分单元测试、池化生产连接、只读连接和事务连接。创建策略改变时,必须重新验证关闭、超时、隔离和回滚;“能 new 出对象”不等于生命周期合同成立。

2.4 JDBC的诞生

2.4 JDBC的诞生的结果是一条可替换但不失真的访问链:共同接口负责形状,工厂负责选择,驱动负责翻译,连接负责事务,参数化语句负责输入边界,结果集负责读取。抽象减少耦合,却把驱动、资源和事务的验证责任变得更明确。

最小 JDBC 访问合同

interface QueryPort {
  List<String> findNames(DataSource dataSource, String prefix) throws SQLException;
}
 
final class JdbcQuery implements QueryPort {
  public List<String> findNames(DataSource dataSource, String prefix) throws SQLException {
    String sql = "SELECT name FROM account WHERE name LIKE ? ORDER BY name";
    try (Connection connection = dataSource.getConnection();
         PreparedStatement statement = connection.prepareStatement(sql)) {
      statement.setString(1, prefix + "%");
      try (ResultSet result = statement.executeQuery()) {
        List<String> names = new ArrayList<>();
        while (result.next()) names.add(result.getString("name"));
        return names;
      }
    }
  }
}

这里的 ? 是输入边界,try-with-resources 是资源边界,DataSource 是实现替换边界。若要执行多条写入,先明确 setAutoCommit(false)、提交、回滚和异常传播;查询成功不自动意味着业务事务已经完成。

四步复核一次 JDBC 调用

分步1 / 4

1. 谈判:固定共同能力和数据库差异

列出目标数据库、驱动版本、支持的 SQL、类型、隔离级别和错误映射。把必须跨库的能力放进接口,把方言能力标成显式扩展。

JDBC 访问链:抽象统一,责任不消失工厂选实现,驱动做翻译,连接管事务,语句与结果管资源1配置驱动与 URL选择证据2工厂DataSource选择证据3连接事务边界资源证据4语句参数化 SQL当前输入边界5结果游标与关闭资源证据统一 API 不代表方言、驱动版本、权限和事务语义完全相同
专属图示:一次 JDBC 调用的每个抽象层都有可验证的责任。

Lab

JDBC 访问边界实验

只改变一个输入、资源或事务条件,观察接口调用如何分叉。

输入作为参数绑定,不改变 SQL 结构

prepare(sql) → bind(prefix) → execute → close result/statement/connection

判定

accept:查询与资源合同成立

当前样本:参数化查询;保存驱动、参数、借还、事务和异常记录。

正常、边界与单一故障证据

JDBC 证据矩阵:接口调用要落到资源和事务正常样本看合同,边界样本看资源,故障样本看关闭与回滚观察项正常边界故障驱动版本匹配方言差异连接拒绝参数类型绑定空/大值拼接注入资源嵌套关闭池接近上限连接泄漏事务提交完成超时回滚重复写入先区分配置、接口、驱动和资源层,再决定重试或修复
专属图示:把 JDBC 的统一调用与差异、资源、事务证据放在一张表里。
样本只改变的变量预期判定必存证据
正常驱动、配置、参数和资源生命周期匹配查询结果正确,资源归还驱动、SQL 模板、参数类型、池状态
边界空值、大结果、超时或事务冲突明确结果/异常,连接仍可复用行数、游标、超时、事务状态
单一故障驱动断开、参数非法或提交失败拒绝/回滚且关闭资源异常映射、借还记录、重放结果

故障诊断:先分清接口、驱动和资源层

  1. 配置层:检查工厂选择、URL、驱动版本、凭据来源和最小权限;没有连接前不要把问题归咎于 SQL。
  2. 接口层:检查参数类型、绑定顺序、SQL 模板、游标和关闭顺序;PreparedStatement 仍需要正确的类型与语义。
  3. 驱动/数据库层:检查方言、字符集、时区、隔离级别、错误码和服务器日志;接口异常只是入口。
  4. 资源/事务层:检查连接借出与归还、自动提交、提交/回滚和重试;连接泄漏与数据错误要分开定位。

如果同一业务在替身 DataSource 成功、真实驱动失败,先比较驱动和数据库合同;如果查询结果正确但池耗尽,先找关闭路径;如果写入重复,先找自动提交与重试幂等性。JDBC 通过必须同时覆盖输入、实现和生命周期。

最小 JDBC 证据包与反例

证据包包含工厂配置摘要、DataSource/驱动身份、连接池参数、SQL 模板、参数类型与脱敏值、结果行数、游标状态、事务模式、提交/回滚、异常映射、借出/归还和重试记录。不要把密码或完整敏感值写入日志,但要保留能区分一次请求的 ID。

反例一是把用户输入拼进 SQL 导致注入或类型错误;反例二是异常路径没有关闭 ResultSet/Statement/Connection,压力测试后池被耗尽;反例三是自动提交与业务重试共同造成重复写入。每次只改一个边界并从干净连接池重放。

术语表

名词解释

本章出现的专业名词,用大白话再讲一遍。

谈判

应用与数据库厂商共同确定连接、语句、结果、类型和错误的最小契约。

统一接口

用 JDBC 抽象承载不同关系数据库共同调用形状与资源状态。

面向接口编程

业务依赖接口并在组合根注入具体数据源或驱动实现。

简单工厂

根据受控配置集中选择并创建 DataSource 或连接适配对象。

数据驱动

把 JDBC 调用翻译为目标数据库协议、类型和错误的具体实现。

工厂方法

延迟和替换创建连接、语句或数据源的生命周期策略。

练习

练习

问题 1: 为什么 PreparedStatement 比字符串拼接更适合作为 JDBC 输入边界?还需要检查什么?

问题 2: 连接池在高压下耗尽,但数据库查询都返回正确。你会先看哪些记录?

问题 3: 同一套接口换了数据库驱动后,事务隔离和错误码表现不同。如何保持面向接口编程不自欺?

资料与写作方式声明

本章以码农翻身权威目录界定学习范围,并结合正文列出的技术资料独立重写;不宣称复现原书正文,也不沿用原作表述。

原作版权归作者与出版社所有;本站原创教学结构与表述仅供学习交流。

本页小结

2.4 JDBC的诞生的关键不是“一个接口解决所有数据库问题”,而是把谈判、统一接口、接口编程、工厂、驱动、参数和事务边界分开验证。完成标准是能追踪一次安全查询,解释驱动差异,并在异常、池耗尽和重试场景下证明资源与数据都能恢复。

讨论

评论区加载中…