基于工厂模式的JDBC连接获取与DML操作实战
简介:工厂模式是软件设计中重要的创建型模式,能够将对象的创建与使用分离,提升代码的可扩展性与维护性。本文深入探讨如何运用简单工厂、工厂方法和抽象工厂三种模式实现JDBC数据库连接的获取,并执行INSERT、UPDATE、DELETE和SELECT等DML操作。通过构建统一的连接与执行接口,结合不同数据库的实现类,系统可灵活切换数据库环境,增强模块化程度。项目结构清晰,包含源码、配置文件及依赖库,适用于Java数据库应用开发中的高内聚低耦合设计。
1. 工厂模式与JDBC连接管理的核心思想
在现代Java应用程序中,数据库操作是系统实现数据持久化的关键环节。传统的JDBC编程常通过 DriverManager.getConnection() 直接获取连接,导致业务代码与数据库驱动强耦合,难以维护和扩展。工厂模式作为创建型设计模式,能够将对象的创建过程封装起来,使客户端面向接口编程,从而解耦数据访问逻辑与具体数据库实现。通过引入工厂模式,不仅可以统一管理Connection、Statement等资源的创建流程,还能支持多数据库环境下的灵活切换,提升系统的可测试性与可维护性。本章将深入剖析工厂模式在JDBC连接管理中的核心价值,并为后续实践奠定理论基础。
2. JDBC连接机制与工厂模式的理论基础
在构建稳定、可扩展的企业级Java应用时,数据库访问层的设计质量直接决定了系统的灵活性和后期维护成本。JDBC(Java Database Connectivity)作为Java平台标准的数据库交互接口,提供了统一的方式来连接和操作各种关系型数据库。然而,原始的JDBC API虽然功能强大,但其使用方式较为底层,容易导致代码冗余、耦合度高以及资源管理不当等问题。为此,结合设计模式中的 工厂模式 ,可以有效提升数据库连接创建过程的抽象层级,实现解耦与复用。本章将系统性地剖析JDBC的核心工作机制,并深入探讨不同类型的工厂模式如何适配不同的项目需求,最终为后续章节中具体实现方案提供坚实的理论支撑。
2.1 JDBC核心组件及其工作原理
JDBC并非一个具体的数据库驱动,而是一套规范化的API集合,定义了Java程序与数据库之间通信的标准方法。它通过一系列核心接口和类来完成数据库连接、SQL执行与结果处理等关键任务。理解这些组件的职责分工与协作流程,是设计高效连接管理机制的前提。
2.1.1 DriverManager、Connection、Statement与ResultSet的角色分工
JDBC中最基础的四个组件—— DriverManager 、 Connection 、 Statement 和 ResultSet ——构成了数据库操作的基本链条。它们各自承担不同的角色,形成一条清晰的数据流通道。
- DriverManager :负责管理一组JDBC驱动程序,根据传入的数据库URL选择合适的驱动来建立连接。它是静态工厂类,调用
DriverManager.getConnection(url, username, password)即可获取一个Connection实例。 - Connection :代表与特定数据库的会话连接,用于创建执行SQL语句的对象(如
Statement),并控制事务提交或回滚。 - Statement :用于向数据库发送SQL语句。其子接口
PreparedStatement支持预编译SQL,提升性能并防止SQL注入。 - ResultSet :封装查询结果集,以表格形式逐行遍历数据。
这四个对象之间的调用链如下图所示:
graph TD
A[DriverManager] -->|getConnection()| B(Connection)
B -->|createStatement()| C(Statement)
C -->|executeQuery()| D(ResultSet)
D -->|next(), getString()等| E[数据提取]
该流程体现了典型的“请求-响应”模型:客户端通过 DriverManager 获取连接,再由连接生成语句对象发送SQL命令,最后从结果集中读取返回数据。
为了更直观展示各组件的功能边界,下表总结了它们的关键方法与用途:
| 组件 | 主要方法 | 功能说明 |
|---|---|---|
| DriverManager | getConnection(String url, String user, String pwd) | 根据URL匹配驱动并返回数据库连接 |
| Connection | createStatement() , prepareStatement(String sql) , setAutoCommit(boolean) | 创建执行器、开启/关闭自动提交、管理事务 |
| Statement | executeQuery(String sql) , executeUpdate(String sql) | 执行SELECT或其他DML语句 |
| ResultSet | next() , getString(int col) , getInt(String name) | 遍历结果行,按列获取字段值 |
这种分层结构使得每层只关注自身职责,符合单一职责原则(SRP)。但在实际编码中,若频繁手动创建这些对象而不加以封装,会导致大量重复代码,且难以应对多数据库切换的需求。
2.1.2 数据库驱动注册与Class.forName()的作用机制
JDBC要求在建立连接前必须确保对应的数据库驱动已被加载到JVM中。传统做法是在代码中显式调用 Class.forName("com.mysql.cj.jdbc.Driver") 来触发驱动类的初始化。
这一机制的背后逻辑在于:当JDBC驱动类被加载时,其静态代码块会自动向 DriverManager 注册自己。例如,MySQL Connector/J 的驱动类源码片段如下:
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException e) {
throw new RuntimeException("Can't register driver!");
}
}
}
因此,调用 Class.forName() 不仅加载类,还执行其静态初始化块,从而完成驱动注册。一旦注册成功, DriverManager.getConnection() 就能在内部遍历已注册的驱动列表,找到能处理指定URL的驱动实例。
现代JDBC 4.0及以上版本支持 服务发现机制(SPI) ,即通过 META-INF/services/java.sql.Driver 文件自动注册驱动,无需显式调用 Class.forName() 。但出于兼容性和明确控制的目的,许多项目仍保留该语句。
以下是一个典型的连接建立代码示例:
try {
Class.forName("com.mysql.cj.jdbc.Driver"); // 显式注册驱动
Connection conn = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/testdb",
"root",
"password"
);
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
}
代码逻辑逐行解读分析 :
- 第1行:捕获两个异常类型,分别对应类找不到和数据库连接失败;
- 第2行:
Class.forName()加载驱动类并触发静态块执行注册;- 第3–5行:使用标准JDBC URL格式连接MySQL数据库,其中:
jdbc:mysql://表示协议;localhost:3306是主机与端口;/testdb指定数据库名;- 第6–8行:异常处理输出堆栈信息,生产环境应使用日志框架替代。
尽管这种方式可行,但它将数据库类型硬编码于代码中,违反了依赖倒置原则。理想情况下,驱动类名和连接参数应当外部化配置,并通过工厂模式动态加载。
2.1.3 连接字符串、用户名、密码的安全传递方式
数据库连接所需的敏感信息包括URL、用户名和密码。若将其硬编码在Java源文件中,不仅不利于多环境部署(开发/测试/生产),也存在严重的安全风险。
推荐的做法是将这些参数存储在外部属性文件中,如 db.properties :
driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/myapp?useSSL=false&serverTimezone=UTC
username=admin
password=securePass123!
然后通过 Properties 类加载:
Properties props = new Properties();
try (InputStream input = getClass().getClassLoader().getResourceAsStream("db.properties")) {
props.load(input);
String driver = props.getProperty("driverClassName");
String url = props.getProperty("url");
String user = props.getProperty("username");
String pwd = props.getProperty("password");
Class.forName(driver);
Connection conn = DriverManager.getConnection(url, user, pwd);
} catch (IOException | ClassNotFoundException | SQLException e) {
throw new RuntimeException("Failed to load database configuration", e);
}
代码逻辑逐行解读分析 :
- 第1行:声明一个
Properties对象用于存储键值对;- 第2–3行:使用类加载器定位资源路径,利用 try-with-resources 自动关闭输入流;
- 第4行:加载
.properties文件内容;- 第5–8行:逐项读取配置项,避免硬编码;
- 第10–11行:先注册驱动,再创建连接;
- 第12–15行:统一异常包装,便于上层捕获与处理。
此外,对于更高安全要求的场景,建议采用以下增强措施:
- 使用 JNDI 或 Spring Boot 的 DataSource 配置;
- 密码加密存储,启动时解密;
- 引入连接池(如 HikariCP、Druid)替代直连,减少频繁创建开销。
2.2 工厂模式分类及应用场景分析
工厂模式是一种创建型设计模式,旨在将对象的创建过程封装起来,使客户端无需关心具体实现类。在JDBC环境中,由于不同数据库(MySQL、Oracle、PostgreSQL等)需要不同的驱动和连接参数,采用工厂模式可显著提高系统的可扩展性与可维护性。本节将详细解析三种主流工厂模式——简单工厂、工厂方法与抽象工厂——在数据库连接管理中的适用场景。
2.2.1 简单工厂模式:集中化创建逻辑的优缺点
简单工厂模式通过一个静态方法根据输入参数决定返回哪种具体类型的对象。在JDBC场景中,可用于根据数据库类型字符串返回相应的 Connection 。
public class SimpleConnectionFactory {
public static Connection getConnection(String dbType) throws Exception {
String driver = "", url = "";
switch (dbType.toLowerCase()) {
case "mysql":
driver = "com.mysql.cj.jdbc.Driver";
url = "jdbc:mysql://localhost:3306/test";
break;
case "oracle":
driver = "oracle.jdbc.driver.OracleDriver";
url = "jdbc:oracle:thin:@localhost:1521:xe";
break;
default:
throw new IllegalArgumentException("Unsupported DB type: " + dbType);
}
Class.forName(driver);
return DriverManager.getConnection(url, "user", "pass");
}
}
代码逻辑逐行解读分析 :
- 方法接收
dbType参数,决定目标数据库;- 使用
switch判断类型,设置对应的驱动类名与URL;- 调用
Class.forName()注册驱动;- 最终通过
DriverManager.getConnection()返回连接实例;- 缺点:新增数据库需修改源码,违反开闭原则。
优点在于实现简单,适合小型项目或固定数据库类型的情况;缺点是扩展性差,每次添加新数据库都需改动工厂类。
2.2.2 工厂方法模式:面向接口编程实现多数据库支持
工厂方法模式引入抽象工厂接口,每个具体数据库实现自己的工厂类,从而实现解耦。
public interface ConnectionFactory {
Connection getConnection() throws SQLException;
}
public class MySQLConnectionFactory implements ConnectionFactory {
public Connection getConnection() throws SQLException {
try {
Class.forName("com.mysql.cj.jdbc.Driver");
return DriverManager.getConnection(
"jdbc:mysql://localhost:3306/mydb", "root", "pwd");
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
}
public class OracleConnectionFactory implements ConnectionFactory {
public Connection getConnection() throws SQLException {
try {
Class.forName("oracle.jdbc.driver.OracleDriver");
return DriverManager.getConnection(
"jdbc:oracle:thin:@localhost:1521:xe", "scott", "tiger");
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
}
代码逻辑逐行解读分析 :
- 定义统一接口
ConnectionFactory,隐藏具体实现细节;- 每个数据库厂商提供独立实现类;
- 客户端只需持有接口引用,无需知道背后是哪种数据库;
- 可配合配置文件动态加载实现类,提升灵活性。
该模式符合“依赖倒置”原则,易于单元测试和替换实现。
2.2.3 抽象工厂模式:统一产品族的创建能力(Connection + Statement)
当系统不仅需要创建 Connection ,还需配套创建特定类型的 Statement 或 PreparedStatement 时,抽象工厂更为合适。
public interface DatabaseFactory {
Connection createConnection() throws Exception;
PreparedStatement createPreparedStatement(Connection conn, String sql) throws SQLException;
}
public class MySQLDatabaseFactory implements DatabaseFactory {
public Connection createConnection() throws Exception {
Class.forName("com.mysql.cj.jdbc.Driver");
return DriverManager.getConnection("jdbc:mysql://localhost:3306/myapp", "root", "123");
}
public PreparedStatement createPreparedStatement(Connection conn, String sql) throws SQLException {
return conn.prepareStatement(sql + " ON DUPLICATE KEY UPDATE ..."); // 特有语法
}
}
代码逻辑逐行解读分析 :
- 接口定义完整的产品族创建方法;
- MySQL工厂可在预编译阶段加入特有优化(如
ON DUPLICATE KEY UPDATE);- Oracle工厂可实现
RETURNING子句支持;- 提供更强的一致性保证,适用于复杂SQL定制场景。
classDiagram
class DatabaseFactory {
<<interface>>
+createConnection() Connection
+createPreparedStatement(Connection, String) PreparedStatement
}
class MySQLDatabaseFactory {
+createConnection()
+createPreparedStatement()
}
class OracleDatabaseFactory {
+createConnection()
+createPreparedStatement()
}
DatabaseFactory <|-- MySQLDatabaseFactory
DatabaseFactory <|-- OracleDatabaseFactory
该结构展示了抽象工厂的继承关系,强调了产品族的整体一致性。
2.3 设计原则支撑:开闭原则与依赖倒置在JDBC层的应用
2.3.1 如何通过接口隔离数据库特异性代码
通过定义 ConnectionFactory 接口,所有数据库实现类均遵循同一契约。客户端代码仅依赖接口,不感知具体数据库类型,从而实现松耦合。
// 客户端代码
ConnectionFactory factory = new MySQLConnectionFactory(); // 可替换为Oracle
Connection conn = factory.getConnection();
即使未来引入 PostgreSQL,只需新增 PostgreSQLConnectionFactory 实现接口,原有代码无需修改,完全符合 开闭原则 (对扩展开放,对修改关闭)。
2.3.2 配置驱动类名与URL的外部化策略(properties文件)
继续深化上述思想,可通过配置文件动态决定加载哪个工厂类:
# app.properties
database.factory.class=com.example.factory.MySQLConnectionFactory
加载逻辑:
String factoryClass = props.getProperty("database.factory.class");
ConnectionFactory factory = (ConnectionFactory) Class.forName(factoryClass).newInstance();
Connection conn = factory.getConnection();
此举实现了运行时装配,极大增强了系统的可配置性与可维护性。
2.4 模式选择决策树:依据项目规模与扩展需求确定工厂类型
| 项目规模 | 推荐模式 | 理由 |
|---|---|---|
| 小型项目,单一数据库 | 简单工厂 | 开发速度快,结构清晰 |
| 中型项目,需支持多种数据库 | 工厂方法模式 | 解耦良好,易于扩展 |
| 大型系统,涉及复杂SQL定制 | 抽象工厂模式 | 统一管理产品族,保持一致性 |
综上所述,合理选择工厂模式类型,能够从根本上改善JDBC连接管理的质量,为构建高内聚、低耦合的数据访问层奠定坚实基础。
3. 基于简单工厂与工厂方法的JDBC连接创建实践
在企业级Java应用开发中,数据库访问是系统稳定运行的核心支撑。尽管JDBC(Java Database Connectivity)提供了标准API用于与关系型数据库交互,但其原始使用方式存在诸多缺陷——例如直接通过 DriverManager.getConnection() 硬编码连接信息、缺乏对多数据库的支持机制、难以统一管理异常处理流程等。为解决这些问题,设计模式成为提升JDBC层结构质量的关键手段。其中, 简单工厂模式 和 工厂方法模式 因其实现简洁、扩展性强而被广泛应用于连接对象的创建管理中。
本章将深入探讨如何结合这两种经典的设计模式构建灵活且可维护的JDBC连接生成体系。我们将从最基础的简单工厂出发,封装不同数据库类型的连接逻辑;随后引入工厂方法模式,利用接口抽象与多态特性支持未来可能新增的数据库类型;最后讨论运行时性能优化策略,包括资源自动释放机制与连接泄漏防护措施。整个过程不仅关注代码功能性,更强调架构层面的可扩展性与健壮性。
3.1 简单工厂模式实现通用Connection生成器
简单工厂模式是一种创建型设计模式,它通过一个统一的“工厂类”来集中创建具体产品实例的逻辑。在JDBC场景下,该模式非常适合用于根据传入的数据库类型参数动态返回对应的 Connection 对象,从而避免客户端代码直接耦合于具体的驱动加载逻辑或连接字符串配置。
3.1.1 定义DatabaseType枚举标识不同数据库类型
为了清晰地区分不同的数据库系统并防止字符串误写导致运行时错误,推荐使用枚举类型来表示支持的数据库种类。这不仅能提高代码可读性,还能借助编译期检查增强程序安全性。
public enum DatabaseType {
MYSQL("com.mysql.cj.jdbc.Driver", "jdbc:mysql://localhost:3306/testdb"),
ORACLE("oracle.jdbc.driver.OracleDriver", "jdbc:oracle:thin:@localhost:1521:orcl"),
POSTGRESQL("org.postgresql.Driver", "jdbc:postgresql://localhost:5432/testdb");
private final String driverClassName;
private final String url;
DatabaseType(String driverClassName, String url) {
this.driverClassName = driverClassName;
this.url = url;
}
public String getDriverClassName() {
return driverClassName;
}
public String getUrl() {
return url;
}
}
参数说明与逻辑分析:
-
driverClassName:每种数据库需要注册的JDBC驱动类全限定名。例如MySQL使用com.mysql.cj.jdbc.Driver,Oracle使用oracle.jdbc.driver.OracleDriver。 -
url:标准JDBC连接URL格式,包含协议、主机、端口及数据库名称。 - 枚举构造函数私有化,确保只能由内部定义的常量调用。
- 提供
getDriverClassName()和getUrl()方法供外部获取对应值。
此设计实现了数据源配置的集中管理,后续若需添加新的数据库类型(如SQL Server),只需在枚举中新增一项即可,无需修改其他业务代码。
3.1.2 封装getConnect(String dbType)方法根据类型返回对应连接
接下来,我们构建一个名为 SimpleConnectionFactory 的简单工厂类,负责根据输入的数据库类型名称返回相应的 Connection 对象。
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class SimpleConnectionFactory {
private static final String USERNAME = "root";
private static final String PASSWORD = "password";
public static Connection getConnection(String dbTypeStr) throws SQLException {
try {
DatabaseType dbType = DatabaseType.valueOf(dbTypeStr.toUpperCase());
// 注册驱动类
Class.forName(dbType.getDriverClassName());
// 建立连接
return DriverManager.getConnection(
dbType.getUrl(),
USERNAME,
PASSWORD
);
} catch (ClassNotFoundException e) {
throw new SQLException("数据库驱动未找到: " + e.getMessage(), e);
} catch (IllegalArgumentException e) {
throw new SQLException("不支持的数据库类型: " + dbTypeStr);
}
}
}
代码逐行解读:
-
DatabaseType.valueOf(...):将字符串转换为枚举实例。若输入非法(如”SQLSERVER”),会抛出IllegalArgumentException。 -
Class.forName(...):显式加载JDBC驱动类,触发其静态块执行DriverManager.registerDriver()操作。 -
DriverManager.getConnection(...):使用预设的用户名密码建立物理连接。 - 异常捕获后包装为
SQLException向上抛出,保持对外接口一致性。
⚠️ 注意:现代JDBC规范(JDBC 4.0+)已支持自动驱动加载(通过
META-INF/services/java.sql.Driver),因此Class.forName()不再是强制要求,但在某些容器环境或旧版本驱动中仍建议保留以确保兼容性。
使用示例:
try (Connection conn = SimpleConnectionFactory.getConnection("MYSQL")) {
System.out.println("成功获取MySQL连接:" + conn.getCatalog());
} catch (SQLException e) {
e.printStackTrace();
}
3.1.3 异常处理机制:SQLException与ClassNotFoundException的捕获与包装
在实际生产环境中,数据库连接失败的原因多种多样,如网络中断、认证失败、驱动缺失、配置错误等。因此,良好的异常处理机制对于快速定位问题至关重要。
| 异常类型 | 触发原因 | 应对策略 |
|---|---|---|
ClassNotFoundException | 指定的JDBC驱动类不存在于类路径中 | 检查依赖是否正确导入(如mysql-connector-java.jar) |
SQLException | 连接超时、用户凭证错误、数据库宕机等 | 记录详细错误码(getErrorCode)、SQL状态(getSQLState) |
IllegalArgumentException | 枚举值不匹配 | 输入校验前置,或提供默认 fallback 行为 |
我们可以进一步优化异常处理流程,引入自定义异常类进行分层封装:
public class DataAccessException extends Exception {
public DataAccessException(String message, Throwable cause) {
super(message, cause);
}
}
改造后的 getConnection 方法可改为:
public static Connection getConnection(String dbTypeStr) throws DataAccessException {
try {
DatabaseType dbType = DatabaseType.valueOf(dbTypeStr.toUpperCase());
Class.forName(dbType.getDriverClassName());
return DriverManager.getConnection(dbType.getUrl(), USERNAME, PASSWORD);
} catch (ClassNotFoundException e) {
throw new DataAccessException("驱动加载失败: " + e.getMessage(), e);
} catch (SQLException e) {
throw new DataAccessException("连接建立失败: " + e.getMessage(), e);
} catch (IllegalArgumentException e) {
throw new DataAccessException("无效数据库类型: " + dbTypeStr, e);
}
}
这样做的好处是:
- 对上层调用者屏蔽底层技术细节;
- 统一异常处理入口,便于日志记录与监控;
- 支持跨模块复用异常体系。
流程图展示连接创建全过程:
flowchart TD
A[开始] --> B{输入数据库类型}
B --> C[查找DatabaseType枚举]
C -- 找到 --> D[加载驱动Class.forName()]
C -- 未找到 --> E[抛出DataAccessException]
D --> F[调用DriverManager.getConnection]
F -- 成功 --> G[返回Connection对象]
F -- 失败 --> H[捕获SQLException并包装]
H --> I[抛出DataAccessException]
G --> J[结束]
该流程图清晰地展现了从用户请求到最终获取连接或抛出异常的完整路径,有助于团队成员理解控制流与异常传播机制。
3.2 工厂方法模式定义可扩展的连接工厂体系
虽然简单工厂适用于固定数量的产品类型,但当系统需要频繁扩展新数据库支持时,每次修改枚举和工厂逻辑都会违反 开闭原则 (对扩展开放,对修改关闭)。为此,应采用更为灵活的 工厂方法模式 ,通过抽象接口定义创建行为,并由子类决定具体实现。
3.2.1 抽象ConnectionFactory接口设计
首先定义一个通用的工厂接口,规定所有数据库连接工厂必须实现的方法:
public interface ConnectionFactory {
Connection getConnection() throws DataAccessException;
}
该接口极为简洁,仅声明了一个核心方法,体现了 接口隔离原则 。任何实现了该接口的类都可以作为合法的连接提供者。
3.2.2 MySQLConnectionFactory与OracleConnectionFactory的具体实现
分别针对MySQL和Oracle实现各自的工厂类:
public class MySQLConnectionFactory implements ConnectionFactory {
private static final String URL = "jdbc:mysql://localhost:3306/testdb";
private static final String USER = "root";
private static final String PASS = "password";
@Override
public Connection getConnection() throws DataAccessException {
try {
Class.forName("com.mysql.cj.jdbc.Driver");
return DriverManager.getConnection(URL, USER, PASS);
} catch (ClassNotFoundException e) {
throw new DataAccessException("MySQL驱动未找到", e);
} catch (SQLException e) {
throw new DataAccessException("MySQL连接失败", e);
}
}
}
public class OracleConnectionFactory implements ConnectionFactory {
private static final String URL = "jdbc:oracle:thin:@localhost:1521:orcl";
private static final String USER = "scott";
private static final String PASS = "tiger";
@Override
public Connection getConnection() throws DataAccessException {
try {
Class.forName("oracle.jdbc.driver.OracleDriver");
return DriverManager.getConnection(URL, USER, PASS);
} catch (ClassNotFoundException e) {
throw new DataAccessException("Oracle驱动未找到", e);
} catch (SQLException e) {
throw new DataAccessException("Oracle连接失败", e);
}
}
}
关键优势分析:
- 各数据库连接逻辑完全解耦,独立维护;
- 新增数据库(如PostgreSQL)只需新增实现类,不影响现有代码;
- 支持依赖注入框架(如Spring)自动装配不同工厂Bean;
- 易于编写单元测试,可通过Mockito模拟返回假连接。
3.2.3 利用配置文件动态加载工厂类提升灵活性
为避免硬编码工厂选择逻辑,可将具体实现类名存储在配置文件中,运行时通过反射动态加载。
配置文件 factory.properties :
database.type=mysql
factory.class=com.example.factory.MySQLConnectionFactory
工厂选择器实现:
public class ConfigurableConnectionFactory {
private static Properties props = new Properties();
static {
try (InputStream is = ConfigurableConnectionFactory.class
.getClassLoader().getResourceAsStream("factory.properties")) {
if (is == null) throw new RuntimeException("配置文件未找到");
props.load(is);
} catch (IOException e) {
throw new ExceptionInInitializerError(e);
}
}
public static ConnectionFactory getFactory() throws DataAccessException {
try {
String className = props.getProperty("factory.class");
Class<?> clazz = Class.forName(className);
return (ConnectionFactory) clazz.getDeclaredConstructor().newInstance();
} catch (Exception e) {
throw new DataAccessException("无法实例化工厂类: " + e.getMessage(), e);
}
}
}
调用方式:
ConnectionFactory factory = ConfigurableConnectionFactory.getFactory();
try (Connection conn = factory.getConnection()) {
System.out.println("当前连接来自: " + conn.getMetaData().getDatabaseProductName());
}
这种方式实现了真正的 运行时可插拔 架构,运维人员仅需更改配置即可切换底层数据库类型,极大提升了系统的部署灵活性。
表格对比两种模式特性:
| 特性 | 简单工厂 | 工厂方法 |
|---|---|---|
| 扩展性 | 低(需修改工厂类) | 高(新增类即可) |
| 开闭原则遵守程度 | 不符合 | 符合 |
| 适用场景 | 固定产品族、小型项目 | 多数据库支持、大型系统 |
| 配置灵活性 | 中等(依赖枚举) | 高(支持外部配置+反射) |
| 学习成本 | 低 | 中等 |
3.3 运行时性能考量:连接创建开销与资源释放规范
尽管上述工厂模式解决了连接创建的灵活性问题,但仍需关注 运行时性能 与 资源管理 两大关键点。频繁创建/销毁物理连接会造成显著性能损耗,同时不当的资源释放可能导致连接泄漏,进而耗尽数据库连接池。
3.3.1 使用try-with-resources确保Connection自动关闭
Java 7引入的 try-with-resources 语句能保证实现了 AutoCloseable 接口的资源在作用域结束时自动关闭,极大降低了资源泄漏风险。
public void queryUserData() {
String sql = "SELECT id, name FROM users WHERE age > ?";
try (Connection conn = MySQLConnectionFactory.getInstance().getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setInt(1, 18);
try (ResultSet rs = pstmt.executeQuery()) {
while (rs.next()) {
System.out.println("ID: " + rs.getInt("id") +
", Name: " + rs.getString("name"));
}
}
} catch (DataAccessException | SQLException e) {
e.printStackTrace();
}
}
执行逻辑说明:
-
Connection和PreparedStatement在try()括号内声明; - JVM会在
try块结束后自动调用close()方法,即使发生异常; -
ResultSet也置于嵌套的try-with-resources中,确保彻底释放; - 所有资源按逆序关闭(先rs → pstmt → conn),符合RAII原则。
✅ 推荐:始终使用
try-with-resources替代传统的finally { conn.close(); }写法。
3.3.2 防止连接泄漏的最佳编码实践
连接泄漏是指 Connection 对象未被及时关闭,导致数据库侧连接数持续增长,最终引发“Too many connections”错误。
常见原因包括:
- 忘记调用 close() ;
- 异常跳过 finally 块(极少见);
- 在循环中重复获取连接却未关闭。
防范策略总结:
| 策略 | 描述 |
|---|---|
✅ 使用 try-with-resources | 编译器保障自动关闭 |
| ✅ 设置连接超时时间 | conn.setNetworkTimeout() |
| ✅ 启用连接池(如HikariCP) | 复用连接,限制最大并发 |
| ✅ 日志监控 | 记录连接获取/释放事件 |
| ❌ 避免全局持有Connection | 容易造成长生命周期引用 |
示例:带超时控制的安全连接获取
public Connection getConnectionWithTimeout(int timeoutSeconds) throws DataAccessException {
try {
Connection conn = DriverManager.getConnection(URL, USER, PASS);
conn.setNetworkTimeout(Executors.newSingleThreadExecutor().submit(() -> {}),
timeoutSeconds * 1000);
return conn;
} catch (SQLException e) {
throw new DataAccessException("设置超时失败", e);
}
}
此方法为连接设置了网络读写超时,防止因数据库无响应而导致线程永久阻塞。
综上所述,无论是采用简单工厂还是工厂方法模式,都应在保证功能正确性的基础上,充分考虑异常处理、配置外化、资源管理和性能优化等多个维度。唯有如此,才能构建出既灵活又可靠的JDBC连接基础设施,为后续的数据操作模块奠定坚实基础。
4. 抽象工厂整合Connection与PreparedStatement生命周期
在企业级Java应用开发中,数据库操作的复杂性不仅体现在连接的获取上,更在于对一系列相关资源(如 Connection 、 Statement 、 PreparedStatement 、 ResultSet )的协同管理。传统的JDBC编程模式往往将这些对象的创建过程分散于业务逻辑之中,导致代码重复、资源泄漏风险增加以及跨数据库适配困难等问题。为解决这一困境, 抽象工厂模式 (Abstract Factory Pattern)提供了一种高层次的设计方案——它允许我们定义一个统一的接口来创建一组具有“家族关系”的对象,而无需指定其具体类。
本章聚焦于如何通过抽象工厂模式整合 Connection 与 PreparedStatement 的完整生命周期管理,构建一套可复用、可扩展且具备安全防护能力的数据访问基础设施。我们将从产品族的设计出发,深入探讨预编译语句的安全优势,并设计通用执行器封装DML操作,最终实现资源的高效管理与上下文保持机制。
4.1 构建统一的数据操作产品族
在多数据库环境中,不同厂商(如MySQL、Oracle、PostgreSQL)提供的驱动实现存在差异,不仅体现在连接URL和驱动类名上,也反映在SQL语法支持、参数绑定行为等方面。若不进行抽象隔离,应用程序将严重依赖具体数据库类型,丧失灵活性。为此,引入“产品族”概念至关重要。
所谓 产品族 ,是指一组在逻辑上属于同一系统的相关对象。在JDBC场景下,一个典型的数据库产品族包括:
- 数据库连接(
Connection) - 预编译语句(
PreparedStatement) - 执行器(
Statement) - 结果集处理器(可选)
抽象工厂的核心思想是: 客户端只面向接口编程,由具体的工厂决定如何创建这一整套关联对象 。这样既保证了对象之间的兼容性,又实现了系统级别的切换自由。
4.1.1 抽象工厂接口DatabaseFactory定义createConnection()与createStatement()
我们首先定义一个顶层接口 DatabaseFactory ,用于声明创建关键JDBC组件的方法。该接口不涉及任何具体实现细节,仅作为契约存在。
public interface DatabaseFactory {
Connection createConnection() throws SQLException;
PreparedStatement createPreparedStatement(Connection conn, String sql) throws SQLException;
}
此接口包含两个核心方法:
- createConnection() :返回符合当前数据库类型的连接实例。
- createPreparedStatement() :基于已有连接和SQL模板生成预编译语句。
这种设计使得调用方可以在不知道底层数据库类型的情况下完成数据操作准备阶段的工作。
参数说明与逻辑分析
| 方法 | 参数 | 作用 |
|---|---|---|
createConnection() | 无 | 封装驱动加载、URL拼接、认证信息读取等细节 |
createPreparedStatement(Connection, String) | conn : 已建立的连接; sql : 带占位符的SQL语句 | 返回可设置参数的预编译对象 |
该接口遵循 依赖倒置原则 (DIP),即高层模块不应依赖低层模块,二者都应依赖于抽象。后续的具体工厂实现类将分别针对MySQL和Oracle提供不同的构造策略。
4.1.2 实现MySQLDatabaseFactory和OracleDatabaseFactory完整产品链
接下来,我们分别实现两种主流数据库的产品工厂。以 MySQLDatabaseFactory 为例:
public class MySQLDatabaseFactory implements DatabaseFactory {
private final String url = "jdbc:mysql://localhost:3306/testdb?useSSL=false&serverTimezone=UTC";
private final String username = "root";
private final String password = "password";
static {
try {
Class.forName("com.mysql.cj.jdbc.Driver");
} catch (ClassNotFoundException e) {
throw new RuntimeException("MySQL JDBC Driver not found", e);
}
}
@Override
public Connection createConnection() throws SQLException {
return DriverManager.getConnection(url, username, password);
}
@Override
public PreparedStatement createPreparedStatement(Connection conn, String sql) throws SQLException {
return conn.prepareStatement(sql);
}
}
类似地, OracleDatabaseFactory 实现如下:
public class OracleDatabaseFactory implements DatabaseFactory {
private final String url = "jdbc:oracle:thin:@localhost:1521:orcl";
private final String username = "scott";
private final String password = "tiger";
static {
try {
Class.forName("oracle.jdbc.OracleDriver");
} catch (ClassNotFoundException e) {
throw new RuntimeException("Oracle JDBC Driver not found", e);
}
}
@Override
public Connection createConnection() throws SQLException {
return DriverManager.getConnection(url, username, password);
}
@Override
public PreparedStatement createPreparedStatement(Connection conn, String sql) throws SQLException {
return conn.prepareStatement(sql);
}
}
代码逐行解读分析
-
静态块注册驱动 :使用
Class.forName()显式加载数据库驱动,确保JDBC能够识别协议前缀(如jdbc:mysql://)。虽然现代驱动支持SPI自动注册,但显式加载仍有助于增强可移植性和调试清晰度。 -
连接字符串配置内联化 :此处硬编码仅为演示目的,在实际项目中应通过
.properties文件外部化(见第五章)。 -
异常包装处理 :
ClassNotFoundException被转为运行时异常,避免强制捕获,同时保留原始堆栈以便追踪。 -
PreparedStatement 创建逻辑一致 :尽管底层驱动不同,但JDBC规范保证了
prepareStatement()行为的一致性,因此方法体内容相同。
⚠️ 注意:某些高级特性(如Oracle的REF CURSOR或MySQL的ON DUPLICATE KEY UPDATE)可能导致SQL结构差异,需在上层SQL构建器中做适配。
Mermaid 流程图:抽象工厂工作流程
graph TD
A[客户端请求数据操作] --> B{选择数据库类型}
B -->|MySQL| C[MySQLDatabaseFactory]
B -->|Oracle| D[OracleDatabaseFactory]
C --> E[createConnection()]
D --> F[createConnection()]
E --> G[获取Connection]
F --> G
G --> H[createPreparedStatement(sql)]
H --> I[设置参数并执行]
I --> J[返回结果]
该流程图展示了客户端如何通过工厂接口间接获得一整套JDBC资源,完全屏蔽了底层实现差异。
对比表格:简单工厂 vs 抽象工厂适用性
| 维度 | 简单工厂 | 抽象工厂 |
|---|---|---|
| 创建对象数量 | 单一对象(如Connection) | 多个相关对象(Connection + PreparedStatement) |
| 扩展性 | 新增类型需修改工厂类 | 新增产品族只需新增工厂实现类 |
| 符合开闭原则 | 否(违反“对修改关闭”) | 是 |
| 使用复杂度 | 低 | 中高 |
| 典型应用场景 | 工具类集中创建 | 多数据库/多平台产品体系 |
抽象工厂更适合需要整体替换技术栈的场景,例如从MySQL迁移到PostgreSQL时,只需更换工厂实例,其余代码无需改动。
4.2 PreparedStatement的预编译优势与SQL注入防护
在动态SQL构建过程中,直接拼接用户输入极易引发 SQL注入攻击 (SQL Injection),这是OWASP Top 10中最常见的安全漏洞之一。而 PreparedStatement 不仅能提升性能,更是防御此类攻击的关键手段。
4.2.1 参数占位符(?)的正确使用方式
PreparedStatement 支持在SQL语句中使用问号 ? 作为参数占位符,之后通过 setXxx() 方法动态赋值。这种方式确保了参数值不会被解释为SQL语法的一部分。
示例代码如下:
String sql = "SELECT id, name, email FROM users WHERE age > ? AND status = ?";
try (Connection conn = factory.createConnection();
PreparedStatement ps = factory.createPreparedStatement(conn, sql)) {
ps.setInt(1, 18); // 第一个? 设置为18
ps.setString(2, "ACTIVE"); // 第二个? 设置为'ACTIVE'
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
System.out.println(rs.getInt("id") + ": " + rs.getString("name"));
}
}
}
参数说明
| 方法 | 参数索引 | 类型约束 | 示例 |
|---|---|---|---|
setInt(index, value) | 1-based | int | ps.setInt(1, 25) |
setString(index, value) | 1-based | String | ps.setString(2, "John") |
setDate(index, value) | 1-based | java.sql.Date | ps.setDate(3, Date.valueOf("2023-01-01")) |
✅ 正确做法:所有动态值均通过
setXxx()方法传入
❌ 错误做法:"WHERE name = '" + userInput + "'"
4.2.2 防止拼接SQL字符串带来的安全风险
假设使用普通 Statement 进行查询:
String userProvidedName = "'; DROP TABLE users; --";
String badSql = "SELECT * FROM users WHERE name = '" + userProvidedName + "'";
stmt.execute(badSql); // 直接执行恶意命令!
上述代码会生成如下SQL:
SELECT * FROM users WHERE name = ''; DROP TABLE users; --'
这将导致表被删除。
而使用 PreparedStatement 时:
ps.setString(1, "'; DROP TABLE users; --");
此时数据库只会查找名为 '; DROP TABLE users; -- 的记录,而不会执行额外命令,因为整个字符串被视为 字面量参数 ,而非SQL代码片段。
4.2.3 批量插入中的addBatch()与executeBatch()高效应用
当需要插入大量数据时,逐条执行效率低下。 PreparedStatement 提供批量处理机制,显著减少网络往返次数。
String insertSql = "INSERT INTO logs (level, message, timestamp) VALUES (?, ?, ?)";
try (Connection conn = factory.createConnection();
PreparedStatement ps = factory.createPreparedStatement(conn, insertSql)) {
conn.setAutoCommit(false); // 关闭自动提交
for (LogEntry entry : logEntries) {
ps.setString(1, entry.getLevel());
ps.setString(2, entry.getMessage());
ps.setTimestamp(3, Timestamp.from(entry.getTimestamp()));
ps.addBatch(); // 添加到批次
}
int[] results = ps.executeBatch(); // 执行全部批次
conn.commit(); // 提交事务
System.out.println("成功插入 " + results.length + " 条日志");
} catch (BatchUpdateException e) {
conn.rollback();
throw e;
}
性能对比表格(10,000条记录)
| 方式 | 平均耗时(ms) | 是否易受注入影响 |
|---|---|---|
| 普通Statement逐条执行 | ~12,500 | 是 |
| PreparedStatement单条执行 | ~8,200 | 否 |
| PreparedStatement批处理 | ~950 | 否 |
可见,批处理可带来超过10倍的性能提升。
4.3 SQLExecutor接口封装DML操作
为了进一步解耦业务逻辑与JDBC API细节,引入 SQLExecutor 接口统一管理数据操作入口,提升代码可读性与维护性。
4.3.1 executeInsert()、executeUpdate()、executeDelete()统一执行入口
定义接口如下:
public interface SQLExecutor {
long executeInsert(String sql, Object... params) throws SQLException;
int executeUpdate(String sql, Object... params) throws SQLException;
int executeDelete(String sql, Object... params) throws SQLException;
<T> List<T> executeQuery(String sql, ResultSetHandler<T> handler, Object... params) throws SQLException;
}
其中:
- executeInsert 返回自增主键(如MySQL的LAST_INSERT_ID)
- executeUpdate/delete 返回受影响行数
- executeQuery 接收一个 ResultSetHandler 实现,负责将结果映射为POJO列表
示例实现类
public class DefaultSQLExecutor implements SQLExecutor {
private final DatabaseFactory factory;
public DefaultSQLExecutor(DatabaseFactory factory) {
this.factory = factory;
}
@Override
public long executeInsert(String sql, Object... params) throws SQLException {
try (Connection conn = factory.createConnection()) {
try (PreparedStatement ps = factory.createPreparedStatement(conn, sql)) {
setParameters(ps, params);
ps.executeUpdate();
try (ResultSet generatedKeys = ps.getGeneratedKeys()) {
if (generatedKeys.next()) {
return generatedKeys.getLong(1);
} else {
throw new SQLException("Failed to get generated key.");
}
}
}
}
}
private void setParameters(PreparedStatement ps, Object[] params) throws SQLException {
for (int i = 0; i < params.length; i++) {
Object param = params[i];
if (param instanceof String) {
ps.setString(i + 1, (String) param);
} else if (param instanceof Integer) {
ps.setInt(i + 1, (Integer) param);
} else if (param instanceof Date) {
ps.setDate(i + 1, (java.sql.Date) param);
} else {
ps.setObject(i + 1, param);
}
}
}
// 其他方法略...
}
4.3.2 executeQuery()结合ResultSetHandler实现结果映射解耦
ResultSetHandler 是一个函数式接口,用于定制结果集转换逻辑:
@FunctionalInterface
public interface ResultSetHandler<T> {
T handle(ResultSet rs) throws SQLException;
}
示例:将用户记录映射为List
ResultSetHandler<List<User>> userHandler = rs -> {
List<User> users = new ArrayList<>();
while (rs.next()) {
User u = new User();
u.setId(rs.getLong("id"));
u.setName(rs.getString("name"));
u.setEmail(rs.getString("email"));
users.add(u);
}
return users;
};
List<User> result = executor.executeQuery(
"SELECT id, name, email FROM users WHERE dept_id = ?",
userHandler,
5
);
优势分析
- 彻底分离SQL执行与数据映射
- 支持泛型返回类型
- 便于单元测试模拟
4.4 资源管理优化:连接复用与轻量级上下文保持
尽管 try-with-resources 可自动关闭资源,但在高频调用场景下频繁创建/销毁连接会造成显著性能损耗。因此,必须引入连接复用机制。
轻量级上下文保持设计
我们可通过ThreadLocal保存当前线程的连接,实现“一次获取,多次使用”的语义:
public class ConnectionContext {
private static final ThreadLocal<Connection> context = new ThreadLocal<>();
public static void bind(Connection conn) {
context.set(conn);
}
public static Connection getConnection() {
return context.get();
}
public static void release() {
Connection conn = context.get();
if (conn != null) {
try {
if (!conn.isClosed()) {
conn.close();
}
} catch (SQLException ignored) {}
context.remove();
}
}
}
结合事务控制:
public void performBusinessOperation() throws SQLException {
Connection conn = null;
try {
conn = factory.createConnection();
conn.setAutoCommit(false);
ConnectionContext.bind(conn);
// 多次DAO调用共享同一连接
userDao.insert(user);
logDao.record("user_created");
conn.commit();
} catch (Exception e) {
if (conn != null) conn.rollback();
throw e;
} finally {
ConnectionContext.release();
}
}
优化前后对比表
| 指标 | 无上下文管理 | 使用ThreadLocal |
|---|---|---|
| 连接创建频率 | 每次DAO调用新建 | 同一线程内复用 |
| 事务一致性 | 难以保障 | 完全可控 |
| 内存开销 | 高(短生命周期对象多) | 降低 |
| 编码复杂度 | 低 | 中等 |
此机制虽非完整连接池,但对于中小型项目已足够有效,也为未来集成HikariCP等专业池化方案打下基础。
5. 模块化项目结构设计与可维护性增强策略
5.1 标准Java项目的目录组织规范
在企业级Java开发中,良好的项目结构是保障代码可读性、可维护性和团队协作效率的基础。一个标准的基于工厂模式管理JDBC连接的模块化项目应遵循清晰的分层与目录划分原则。
典型的项目结构如下所示:
project-root/
│
├── src/ # 源码主目录
│ ├── main/java/ # Java源文件
│ │ └── com/example/jdbc/
│ │ ├── factory/ # 工厂接口与实现类
│ │ ├── connection/ # Connection封装工具
│ │ ├── sql/ # SQL执行器与语句构建
│ │ └── util/ # 配置读取、日志等辅助类
│ │
│ ├── main/resources/ # 资源文件
│ │ └── db.properties # 数据库配置文件
│ │
│ └── test/java/ # 单元测试代码
│ └── com/example/jdbc/test/
│ └── ConnectionFactoryTest.java
│
├── lib/ # 第三方依赖JAR包(如mysql-connector-java-8.0.33.jar)
├── bin/ # 编译后的.class文件输出目录
├── build.xml # Ant构建脚本(可选)
└── .classpath & .project # Eclipse IDE专用配置文件
.classpath 和 .project 是Eclipse IDE自动生成的元数据文件,用于定义项目的构建路径、源码目录和依赖库。例如, .classpath 中的关键条目如下:
<classpathentry kind="src" path="src/main/java"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="lib" path="lib/mysql-connector-java-8.0.33.jar"/>
<classpathentry kind="output" path="bin"/>
这些配置确保了IDE能够正确解析类路径,支持自动补全、编译与调试功能。虽然现代构建工具如Maven或Gradle已逐渐取代手动管理,但在非构建工具项目中,这类文件仍至关重要。
此外,将数据库驱动JAR置于 lib/ 目录并显式添加至构建路径,有助于版本控制与部署一致性,避免“找不到驱动”的运行时异常。
5.2 配置驱动信息的外部化管理
硬编码数据库连接参数(如URL、用户名)会严重降低系统的灵活性与安全性。通过外部化配置,可以在不修改代码的前提下切换环境(开发、测试、生产),提升部署效率。
使用 db.properties 文件存储敏感信息是一种成熟实践。该文件位于 src/main/resources 下,内容示例如下:
# db.properties
driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=UTC
username=root
password=SecurePass123!
initialSize=5
maxTotal=20
通过 java.util.Properties 类加载配置:
// ConfigLoader.java
import java.io.InputStream;
import java.util.Properties;
public class ConfigLoader {
private static final Properties props = new Properties();
static {
try (InputStream is = ConfigLoader.class.getClassLoader()
.getResourceAsStream("db.properties")) {
if (is == null) {
throw new RuntimeException("db.properties not found in classpath");
}
props.load(is);
} catch (Exception e) {
throw new RuntimeException("Failed to load database configuration", e);
}
}
public static String getProperty(String key) {
return props.getProperty(key);
}
}
参数说明:
- getResourceAsStream() :从类路径加载资源,优先级为 /main/resources
- props.load(is) :解析键值对
- 异常捕获确保配置缺失时及时反馈错误
随后,在工厂类中注入配置:
// AbstractConnectionFactory.java
public abstract class AbstractConnectionFactory {
protected String url;
protected String username;
protected String password;
public AbstractConnectionFactory() {
this.url = ConfigLoader.getProperty("url");
this.username = ConfigLoader.getProperty("username");
this.password = ConfigLoader.getProperty("password");
}
public abstract Connection createConnection() throws SQLException;
}
此方式实现了配置与代码分离,符合 12-Factor App 的配置管理原则。
5.3 提升系统的可扩展性与维护性
5.3.1 新增数据库类型仅需扩展工厂实现类,符合开闭原则
当需要支持 PostgreSQL 时,只需新增两个类即可完成扩展:
// PostgreSQLConnectionFactory.java
public class PostgreSQLConnectionFactory extends AbstractConnectionFactory {
@Override
public Connection createConnection() throws SQLException {
try {
Class.forName("org.postgresql.Driver");
} catch (ClassNotFoundException e) {
throw new SQLException("PostgreSQL driver not found", e);
}
return DriverManager.getConnection(url, username, password);
}
}
无需修改任何现有代码,系统对扩展开放,对修改关闭——完美体现 开闭原则(Open-Closed Principle) 。
| 数据库类型 | 驱动类名 | URL前缀 |
|---|---|---|
| MySQL | com.mysql.cj.jdbc.Driver | jdbc:mysql:// |
| Oracle | oracle.jdbc.OracleDriver | jdbc:oracle:thin:@ |
| PostgreSQL | org.postgresql.Driver | jdbc:postgresql:// |
| SQL Server | com.microsoft.sqlserver.jdbc.SQLServerDriver | jdbc:sqlserver:// |
| H2 | org.h2.Driver | jdbc:h2:mem: |
| SQLite | org.sqlite.JDBC | jdbc:sqlite: |
| DB2 | com.ibm.db2.jcc.DB2Driver | jdbc:db2:// |
| MariaDB | org.mariadb.jdbc.Driver | jdbc:mariadb:// |
| Sybase | com.sybase.jdbc4.jdbc.SybDriver | jdbc:sybase:Tds: |
| Derby | org.apache.derby.jdbc.EmbeddedDriver | jdbc:derby: |
5.3.2 日志记录连接状态变化便于故障排查
集成日志框架(如 SLF4J + Logback)追踪连接创建过程:
private static final Logger logger = LoggerFactory.getLogger(MySQLConnectionFactory.class);
@Override
public Connection createConnection() throws SQLException {
logger.info("Creating MySQL connection to {}", url);
long start = System.currentTimeMillis();
Connection conn = null;
try {
conn = DriverManager.getConnection(url, username, password);
logger.debug("Connection established successfully in {} ms", System.currentTimeMillis() - start);
return conn;
} catch (SQLException e) {
logger.error("Failed to create MySQL connection", e);
throw e;
}
}
日志输出示例:
INFO MySQLConnectionFactory - Creating MySQL connection to jdbc:mysql://localhost:3306/mydb
DEBUG MySQLConnectionFactory - Connection established successfully in 42 ms
5.3.3 单元测试验证各工厂类行为一致性与异常路径覆盖
使用 JUnit 编写测试用例确保工厂逻辑稳定:
// ConnectionFactoryTest.java
@Test
public void testMySQLConnectionCreation() throws SQLException {
MySQLConnectionFactory factory = new MySQLConnectionFactory();
Connection conn = factory.createConnection();
assertNotNull(conn);
assertFalse(conn.isClosed());
conn.close();
}
@Test(expected = SQLException.class)
public void testInvalidURLThrowsException() {
MySQLConnectionFactory factory = new MySQLConnectionFactory() {
@Override
public Connection createConnection() throws SQLException {
return DriverManager.getConnection("jdbc:mysql://invalid-host:3306/bad_db", "bad", "bad");
}
};
factory.createConnection(); // Should throw
}
结合 Mockito 可模拟 DriverManager 行为进行更高级测试。
classDiagram
class DatabaseFactory {
<<interface>>
+createConnection() Connection
}
class MySQLDatabaseFactory {
+createConnection() Connection
}
class OracleDatabaseFactory {
+createConnection() Connection
}
class ConfigLoader {
-Properties props
+getProperty(String) String
}
class SQLExecutor {
+executeQuery(String, ResultSetHandler) List~T~
}
DatabaseFactory <|-- MySQLDatabaseFactory
DatabaseFactory <|-- OracleDatabaseFactory
SQLExecutor --> ConfigLoader : reads config
MySQLDatabaseFactory --> DriverManager : creates connection
简介:工厂模式是软件设计中重要的创建型模式,能够将对象的创建与使用分离,提升代码的可扩展性与维护性。本文深入探讨如何运用简单工厂、工厂方法和抽象工厂三种模式实现JDBC数据库连接的获取,并执行INSERT、UPDATE、DELETE和SELECT等DML操作。通过构建统一的连接与执行接口,结合不同数据库的实现类,系统可灵活切换数据库环境,增强模块化程度。项目结构清晰,包含源码、配置文件及依赖库,适用于Java数据库应用开发中的高内聚低耦合设计。
更多推荐
所有评论(0)