JDBC本质上是Java为数据库操作定义的一套接口规范。想象一下,如果每个数据库厂商都使用自己独特的通信协议,Java开发者就需要为MySQL、Oracle、SQL Server等分别学习不同的API。JDBC的出现就像给所有数据库厂商发了一套标准插头规格,只要厂商按照这个规格生产插座(实现JDBC接口),Java程序就能用统一的方式连接各种数据库。
关键接口解析:
DriverManager:早期版本的核心类,负责管理数据库驱动DataSource:更现代的连接方式,支持连接池等高级特性Connection:代表与数据库的物理连接Statement/PreparedStatement:SQL语句执行载体ResultSet:查询结果集游标实际开发中推荐使用DataSource而非DriverManager,因为前者支持连接池、分布式事务等企业级特性,性能更好。
xml复制<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</version>
<scope>runtime</scope>
</dependency>
ClassNotFoundException: com.mysql.jdbc.Driver
No suitable driver found
标准连接URL格式:
code复制jdbc:mysql://[host][:port]/[database][?参数1=值1&参数2=值2...]
关键参数说明:
| 参数名 | 推荐值 | 作用 |
|---|---|---|
| useSSL | false | 禁用SSL加密(内网环境) |
| characterEncoding | UTF-8 | 字符集编码 |
| serverTimezone | Asia/Shanghai | 时区设置 |
| allowPublicKeyRetrieval | true | MySQL 8.0需要 |
java复制// 现代连接方式示例
MysqlDataSource ds = new MysqlDataSource();
ds.setServerName("127.0.0.1");
ds.setPortNumber(3306);
ds.setDatabaseName("jdbc");
ds.setUser("root");
ds.setPassword("123456");
ds.setCharacterEncoding("UTF-8");
ds.setConnectTimeout(5000); // 5秒连接超时
原生JDBC连接的创建和销毁非常耗时,生产环境必须使用连接池:
java复制// HikariCP配置示例(目前性能最好的连接池)
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/jdbc");
config.setUsername("root");
config.setPassword("123456");
config.setMaximumPoolSize(20); // 最大连接数
config.setMinimumIdle(5); // 最小空闲连接
config.setConnectionTimeout(30000); // 30秒超时
HikariDataSource ds = new HikariDataSource(config);
连接池使用后需要调用close()归还连接,而不是关闭物理连接。忘记归还会导致连接泄漏!
PreparedStatement的?占位符机制如何防止SQL注入:
java复制// 危险!存在SQL注入风险
String sql = "SELECT * FROM users WHERE username='" + input + "'";
// 安全写法
String sql = "SELECT * FROM users WHERE username=?";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1, input);
java复制// 批量插入示例
String sql = "INSERT INTO orders(product_id, quantity) VALUES(?,?)";
PreparedStatement ps = conn.prepareStatement(sql);
for(Order order : orders) {
ps.setInt(1, order.getProductId());
ps.setInt(2, order.getQuantity());
ps.addBatch(); // 添加到批处理
if(i % 1000 == 0) {
ps.executeBatch(); // 每1000条执行一次
}
}
ps.executeBatch(); // 执行剩余记录
批处理关键参数:
rewriteBatchedStatements=true:MySQL批处理优化参数useServerPrepStmts=true:启用服务端预编译java复制try(ResultSet rs = stmt.executeQuery()) {
ResultSetMetaData meta = rs.getMetaData();
int colCount = meta.getColumnCount();
while(rs.next()) {
for(int i=1; i<=colCount; i++) {
String colName = meta.getColumnLabel(i);
Object value = rs.getObject(i);
// 处理数据...
}
}
}
高级特性:
TYPE_SCROLL_INSENSITIVE:可滚动的结果集CONCUR_UPDATABLE:可更新的结果集setFetchSize(1000):控制每次从数据库获取的行数java复制conn.setAutoCommit(false); // 开启事务
try {
// 执行多个SQL操作
updateInventory(conn, productId, -quantity);
createOrder(conn, userId, productId, quantity);
conn.commit(); // 提交事务
} catch(SQLException e) {
conn.rollback(); // 回滚事务
throw e;
} finally {
conn.setAutoCommit(true); // 恢复自动提交
}
事务隔离级别:
TRANSACTION_READ_COMMITTED(推荐)TRANSACTION_REPEATABLE_READ(MySQL默认)TRANSACTION_SERIALIZABLE(严格串行)java复制try {
// JDBC操作...
} catch(SQLException e) {
logger.error("SQL执行失败", e);
// 获取详细错误信息
System.out.println("Error Code: " + e.getErrorCode());
System.out.println("SQL State: " + e.getSQLState());
System.out.println("Message: " + e.getMessage());
// 处理特定错误
if(e.getErrorCode() == 1062) {
throw new DuplicateKeyException("主键冲突");
}
}
常见错误代码:
java复制// 使用JDBC Proxy检测未关闭的连接
public class LeakDetectionProxy {
public static Connection wrap(Connection realConn) {
return (Connection) Proxy.newProxyInstance(
LeakDetectionProxy.class.getClassLoader(),
new Class[]{Connection.class},
(proxy, method, args) -> {
if(method.getName().equals("close")) {
leakedConnections.remove(realConn);
}
return method.invoke(realConn, args);
});
}
}
java复制// 使用Filter统计SQL执行时间
public class SlowQueryFilter implements Statement {
private Statement target;
private long threshold;
@Override
public ResultSet executeQuery(String sql) throws SQLException {
long start = System.currentTimeMillis();
try {
return target.executeQuery(sql);
} finally {
long cost = System.currentTimeMillis() - start;
if(cost > threshold) {
logSlowQuery(sql, cost);
}
}
}
}
java复制// 完美实践示例
public List<User> getUsers(int minId) throws SQLException {
String sql = "SELECT id, name FROM users WHERE id > ? ORDER BY id";
try(Connection conn = dataSource.getConnection();
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setFetchSize(100);
ps.setInt(1, minId);
List<User> users = new ArrayList<>();
try(ResultSet rs = ps.executeQuery()) {
while(rs.next()) {
users.add(new User(
rs.getInt("id"),
rs.getString("name")
));
}
}
return users;
}
}
我在实际项目中总结的经验是:JDBC看似简单,但要写出生产级代码需要关注连接管理、异常处理、性能优化等方方面面。特别是事务边界一定要清晰,否则会出现难以排查的数据一致性问题。建议在复杂业务场景下结合Spring的事务管理来简化开发。