01. JDBC 连接数据库
JDBC 是 Java 操作数据库的标准接口,每个数据库厂商提供自己的驱动实现。连接数据库五步走:加载驱动、建立连接、创建 Statement、执行 SQL、处理结果、关闭连接。Connection 是数据库连接对象,Statement 是 SQL 发送器,ResultSet 是结果集。
java
// MySQL 连接示例
String url = "jdbc:mysql://localhost:3306/mydb";
String user = "root";
String password = "123456";
try (Connection conn = DriverManager.getConnection(url, user, password)) {
System.out.println("连接成功");
} catch (SQLException e) {
e.printStackTrace();
}JDBC 4.0 以后不需要 Class.forName 加载驱动了,SPI 机制自动加载。
02. Statement 和 PreparedStatement
Statement 直接拼接 SQL 字符串,有 SQL 注入风险而且每次都要编译。PreparedStatement 用占位符 ? 预编译 SQL,防注入、效率高。为什么防注入?因为 SQL 逻辑和数据是分开的,? 里的值不管写什么都会被当作数据而不是 SQL 片段。
java
// 不推荐:Statement 拼接(有 SQL 注入风险)
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM users WHERE name='"+ name + "'");
// 推荐:PreparedStatement 预编译
String sql = "SELECT * FROM users WHERE name = ? AND age > ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, name);
pstmt.setInt(2, 18);
ResultSet rs = pstmt.executeQuery();
// 增删改用 executeUpdate(),返回受影响行数
int rows = pstmt.executeUpdate();永远不要拼接 SQL 字符串,SQL 注入是最常见的漏洞之一,PreparedStatement 是标配。
03. ResultSet 处理结果
ResultSet 是查询结果集,内部有一个游标,初始指向第一行之前,要 next 一下才到第一行。getString 按列名或列索引获取值,列索引从 1 开始不是 0。读完一行再 next 移到下一行,next 返回 false 表示读完了。循环遍历所有行是最常见的用法。
java
String sql = "SELECT id, name, age FROM users";
PreparedStatement pstmt = conn.prepareStatement(sql);
ResultSet rs = pstmt.executeQuery();
while (rs.next()) {
int id = rs.getInt("id");
String name = rs.getString("name");
int age = rs.getInt(3); // 也可以按列索引
System.out.println(id + ": " + name + ", " + age);
}用列名读取比用列索引更可读,但列索引性能稍好一点点。
04. 事务管理
事务就是把一组 SQL 当作一个整体,要么全成功要么全失败。转账就是典型场景:A 减钱和 B 加钱必须同时成功。conn.setAutoCommit(false) 关闭自动提交,都执行完了 conn.commit() 提交,有异常 conn.rollback() 回滚。事务的 ACID 特性是数据库可靠性的基石。
java
Connection conn = null;
try {
conn = DriverManager.getConnection(url, user, password);
conn.setAutoCommit(false); // 开启事务
// 扣钱
pstmt1.executeUpdate();
// 加钱
pstmt2.executeUpdate();
conn.commit(); // 提交
} catch (SQLException e) {
if (conn != null) conn.rollback(); // 回滚
} finally {
if (conn != null) conn.close();
}事务要尽量短,锁住资源太久会影响并发。不要在事务里做 RPC 调用或发邮件。
05. 连接池
每次请求都创建连接太慢太费资源。连接池预先创建一批连接存着,用时借一根用完还回去。常用的连接池有 HikariCP、Druid。HikariCP 性能最好,Spring Boot 默认就用它。Druid 阿里出品,带监控统计功能,运维方便。
java
// HikariCP 配置
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/mydb");
config.setUsername("root");
config.setPassword("123456");
config.setMaximumPoolSize(20);
config.setMinimumIdle(5);
HikariDataSource ds = new HikariDataSource(config);
Connection conn = ds.getConnection();
// ... 用完记得 close,实际是还回池子连接池的大小一般设为 CPU 核数乘以 2 再加磁盘数,不是越大越好。
知识测验
第 1/4 题正确 0
PreparedStatement 相比 Statement 的两个主要优势?