JDBC基本操作
在 Java 应用程序中与数据库交互,执行 SQL 语句是核心操作之一。本文将详细介绍如何通过 JDBC 执行 SELECT、INSERT、UPDATE、DELETE 等常见 SQL 语句,并提供优化建议和最佳实践。
一、执行 SQL 语句的基本流程
通过 JDBC 执行 SQL 语句的基本步骤:
- 建立数据库连接:获取 Connection 对象
- 创建 Statement 对象:使用 Connection 创建 Statement 或 PreparedStatement
- 执行 SQL 语句:根据语句类型选择合适的执行方法
- 处理结果:对于查询语句,处理 ResultSet;对于更新语句,获取受影响行数
- 关闭资源:按顺序关闭 ResultSet、Statement 和 Connection
下面是一个完整的示例,展示了执行不同类型 SQL 语句的基本结构:
import java.sql.*;public class JdbcSqlExample {private static final String URL = "jdbc:mysql://localhost:3306/mydatabase";private static final String USERNAME = "root";private static final String PASSWORD = "password";public static void main(String[] args) {try (Connection conn = DriverManager.getConnection(URL, USERNAME, PASSWORD)) {// 设置自动提交为 false,开启事务控制conn.setAutoCommit(false);try {// 1. 执行 INSERT 语句insertData(conn);// 2. 执行 UPDATE 语句updateData(conn);// 3. 执行 DELETE 语句deleteData(conn);// 4. 执行 SELECT 语句selectData(conn);// 提交事务conn.commit();} catch (SQLException e) {// 发生异常时回滚事务conn.rollback();e.printStackTrace();}} catch (SQLException e) {e.printStackTrace();}}// 插入数据示例private static void insertData(Connection conn) throws SQLException {String sql = "INSERT INTO users (name, age, email) VALUES (?, ?, ?)";try (PreparedStatement pstmt = conn.prepareStatement(sql)) {pstmt.setString(1, "李四");pstmt.setInt(2, 30);pstmt.setString(3, "lisi@example.com");int rows = pstmt.executeUpdate();System.out.println("插入 " + rows + " 行数据");}}// 更新数据示例private static void updateData(Connection conn) throws SQLException {String sql = "UPDATE users SET age = ? WHERE name = ?";try (PreparedStatement pstmt = conn.prepareStatement(sql)) {pstmt.setInt(1, 31);pstmt.setString(2, "李四");int rows = pstmt.executeUpdate();System.out.println("更新 " + rows + " 行数据");}}// 删除数据示例private static void deleteData(Connection conn) throws SQLException {String sql = "DELETE FROM users WHERE age > ?";try (PreparedStatement pstmt = conn.prepareStatement(sql)) {pstmt.setInt(1, 40);int rows = pstmt.executeUpdate();System.out.println("删除 " + rows + " 行数据");}}// 查询数据示例private static void selectData(Connection conn) throws SQLException {String sql = "SELECT id, name, age, email FROM users WHERE age < ?";try (PreparedStatement pstmt = conn.prepareStatement(sql)) {pstmt.setInt(1, 40);try (ResultSet rs = pstmt.executeQuery()) {while (rs.next()) {int id = rs.getInt("id");String name = rs.getString("name");int age = rs.getInt("age");String email = rs.getString("email");System.out.printf("ID: %d, 姓名: %s, 年龄: %d, 邮箱: %s%n", id, name, age, email);}}}}
}
二、执行不同类型 SQL 语句的详细说明
1. SELECT 查询语句
查询语句用于从数据库中检索数据,返回一个 ResultSet 对象。
String sql = "SELECT * FROM products WHERE price > ? AND category = ?";
try (PreparedStatement pstmt = conn.prepareStatement(sql)) {pstmt.setDouble(1, 100.0);pstmt.setString(2, "电子产品");try (ResultSet rs = pstmt.executeQuery()) {// 处理结果集while (rs.next()) {int id = rs.getInt("id");String name = rs.getString("name");double price = rs.getDouble("price");// 处理其他字段...}}
}
关键方法:
executeQuery()
:执行查询语句,返回 ResultSetResultSet.next()
:移动游标到下一行ResultSet.getXXX()
:获取不同类型的字段值
2. INSERT 插入语句
插入语句用于向数据库表中添加新记录。
String sql = "INSERT INTO orders (order_number, customer_id, order_date) VALUES (?, ?, ?)";
try (PreparedStatement pstmt = conn.prepareStatement(sql)) {pstmt.setString(1, "ORD-2023001");pstmt.setInt(2, 1001);pstmt.setDate(3, new java.sql.Date(System.currentTimeMillis()));int rows = pstmt.executeUpdate();System.out.println("插入 " + rows + " 行数据");
}
关键方法:
executeUpdate()
:执行插入、更新或删除语句,返回受影响的行数
3. UPDATE 更新语句
更新语句用于修改数据库表中的现有记录。
String sql = "UPDATE products SET price = price * ? WHERE category = ?";
try (PreparedStatement pstmt = conn.prepareStatement(sql)) {pstmt.setDouble(1, 1.1); // 价格上涨10%pstmt.setString(2, "书籍");int rows = pstmt.executeUpdate();System.out.println("更新 " + rows + " 行数据");
}
4. DELETE 删除语句
删除语句用于从数据库表中删除记录。
String sql = "DELETE FROM customers WHERE last_login < ?";
try (PreparedStatement pstmt = conn.prepareStatement(sql)) {// 删除一年前登录的客户Calendar cal = Calendar.getInstance();cal.add(Calendar.YEAR, -1);pstmt.setDate(1, new java.sql.Date(cal.getTimeInMillis()));int rows = pstmt.executeUpdate();System.out.println("删除 " + rows + " 行数据");
}
三、高级 SQL 执行技术
1. 批量操作
对于大量数据的插入或更新,使用批量操作可以显著提高性能。
String sql = "INSERT INTO logs (log_time, message, level) VALUES (?, ?, ?)";
try (PreparedStatement pstmt = conn.prepareStatement(sql)) {// 禁用自动提交,开启事务conn.setAutoCommit(false);// 添加多个批处理命令for (LogEntry entry : logEntries) {pstmt.setTimestamp(1, new Timestamp(entry.getTime()));pstmt.setString(2, entry.getMessage());pstmt.setString(3, entry.getLevel());pstmt.addBatch();}// 执行批量操作int[] results = pstmt.executeBatch();// 提交事务conn.commit();
}
2. 获取自动生成的键
当插入记录时,如果表使用自增主键,可以获取生成的主键值。
String sql = "INSERT INTO users (name, email) VALUES (?, ?)";
try (PreparedStatement pstmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {pstmt.setString(1, "王五");pstmt.setString(2, "wangwu@example.com");int rows = pstmt.executeUpdate();if (rows > 0) {// 获取自动生成的键try (ResultSet generatedKeys = pstmt.getGeneratedKeys()) {if (generatedKeys.next()) {long userId = generatedKeys.getLong(1);System.out.println("插入用户的ID是: " + userId);}}}
}
3. 执行存储过程
使用 CallableStatement 执行数据库存储过程。
// 调用带输入输出参数的存储过程
String sql = "{call calculate_discount(?, ?, ?)}";
try (CallableStatement cstmt = conn.prepareCall(sql)) {// 设置输入参数cstmt.setDouble(1, 1000.0); // 订单金额cstmt.setString(2, "VIP"); // 会员等级// 注册输出参数cstmt.registerOutParameter(3, Types.DOUBLE);// 执行存储过程cstmt.execute();// 获取输出参数double discount = cstmt.getDouble(3);System.out.println("折扣金额: " + discount);
}
四、SQL 语句优化建议
1. 使用 PreparedStatement 而非 Statement
PreparedStatement 的优势:
- 防止 SQL 注入攻击
- 预编译 SQL 语句,提高执行效率
- 更好地处理参数类型
2. 优化查询语句
- **避免使用 SELECT ***:只选择需要的字段
- 添加适当的索引:对经常用于 WHERE 子句和 JOIN 的字段添加索引
- 优化 JOIN 语句:确保 JOIN 字段上有索引,使用合适的 JOIN 类型
- 分页查询:使用 LIMIT 和 OFFSET 限制返回的记录数
// 分页查询示例
int pageSize = 20;
int pageNumber = 1;
String sql = "SELECT id, name, price FROM products ORDER BY price DESC LIMIT ? OFFSET ?";
try (PreparedStatement pstmt = conn.prepareStatement(sql)) {pstmt.setInt(1, pageSize);pstmt.setInt(2, (pageNumber - 1) * pageSize);// 执行查询...
}
3. 批量处理数据
对于大量数据操作,使用批量处理代替循环单条操作:
// 批量插入示例 - 每500条记录提交一次
int batchSize = 500;
int count = 0;for (DataRecord record : dataRecords) {pstmt.setString(1, record.getName());pstmt.setInt(2, record.getValue());pstmt.addBatch();if (++count % batchSize == 0) {pstmt.executeBatch();}
}// 处理剩余记录
pstmt.executeBatch();
4. 事务管理
- 将相关操作放在一个事务中
- 根据业务需求设置合适的事务隔离级别
- 确保事务尽快提交,减少锁持有时间
try {conn.setAutoCommit(false);// 执行多个SQL操作updateInventory(conn, productId, quantity);createOrder(conn, orderData);updateCustomerPoints(conn, customerId, points);conn.commit();
} catch (SQLException e) {conn.rollback();throw e;
} finally {conn.setAutoCommit(true);
}
五、常见问题与解决方案
1. SQL 注入问题
错误做法:直接拼接 SQL 语句
// 存在SQL注入风险
String sql = "SELECT * FROM users WHERE username = '" + username + "' AND password = '" + password + "'";
正确做法:使用 PreparedStatement
String sql = "SELECT * FROM users WHERE username = ? AND password = ?";
pstmt.setString(1, username);
pstmt.setString(2, password);
2. 资源泄漏问题
错误做法:不关闭资源
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql);
// 使用结果集,但没有关闭rs和stmt
正确做法:使用 try-with-resources 语句
try (Statement stmt = conn.createStatement();ResultSet rs = stmt.executeQuery(sql)) {// 使用结果集
} // 自动关闭rs和stmt
3. 性能问题
- 问题表现:大量数据查询缓慢、频繁创建连接
- 解决方案:使用连接池、批量操作、优化查询语句、添加适当索引
六、总结
通过 JDBC 执行 SQL 语句是 Java 数据库编程的基础,掌握以下关键点:
- 基本流程:建立连接 → 创建 Statement → 执行 SQL → 处理结果 → 关闭资源
- 不同类型 SQL 的执行:SELECT 使用 executeQuery(),INSERT/UPDATE/DELETE 使用 executeUpdate()
- 高级技术:批量操作、获取自动生成键、执行存储过程
- 优化策略:使用 PreparedStatement、优化查询、批量处理、合理使用事务
- 最佳实践:防止 SQL 注入、正确管理资源、处理异常
合理使用这些技术和策略,可以构建高效、安全、稳定的数据库应用程序。在实际开发中,还可以考虑使用 ORM 框架(如 Hibernate、MyBatis)来进一步简化数据库操作。