教程中心 / Java教程 / JDBC查询

JDBC查询

15分钟 数据库编程

为什么要用PreparedStatement?

PreparedStatement是预编译的SQL语句对象,它不仅能提高SQL执行效率,更重要的是能防止SQL注入攻击,保护数据库安全。

SQL注入攻击比喻

想象你是一家餐厅的员工点餐系统。顾客输入"宫保鸡丁"你就直接做这道菜。但恶意顾客输入"宫保鸡丁; DELETE ALL ORDERS",如果系统直接执行,后果不堪设想!PreparedStatement就像一个严格的点餐系统——它只接受"菜品名称",不接受任何其他"命令"。

PreparedStatement vs Statement对比

特性StatementPreparedStatement
SQL注入不安全,容易被攻击安全,自动转义
性能每次执行都编译预编译,重复执行高效
参数绑定字符串拼接占位符? + setXxx()
可读性差,SQL和代码混杂好,SQL和参数分离
使用场景不推荐推荐使用

重要:永远不要用字符串拼接SQL!

// 危险!SQL注入风险
String sql = "SELECT * FROM users WHERE name = '" + username + "'";
// 如果username = "'; DROP TABLE users; --",整个表都没了!

// 安全!使用PreparedStatement
String sql = "SELECT * FROM users WHERE name = ?";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1, username);  // 自动转义

PreparedStatement完整使用流程

JDBC查询步骤:

1. 编写SQL(使用?作为占位符)
   String sql = "SELECT * FROM users WHERE age > ? AND status = ?";

2. 预编译SQL(创建PreparedStatement)
   PreparedStatement ps = conn.prepareStatement(sql);

3. 绑定参数(按顺序设置?的值)
   ps.setInt(1, 18);         // 第一个?的值
   ps.setString(2, "active"); // 第二个?的值

4. 执行查询
   ResultSet rs = ps.executeQuery();

5. 处理结果
   while (rs.next()) {
       String name = rs.getString("name");
       // ...
   }

6. 关闭资源
   rs.close();
   ps.close();

完整查询示例

import java.sql.*;
import java.util.ArrayList;
import java.util.List;

public class JDBCQueryDemo {
    public static void main(String[] args) {
        String url = "jdbc:mysql://localhost:3306/mydb";
        String user = "root";
        String password = "123456";

        String sql = "SELECT id, name, email, age, created_at " +
                     "FROM users WHERE age >= ? AND status = ? " +
                     "ORDER BY created_at DESC LIMIT ?";

        try (Connection conn = DriverManager.getConnection(url, user, password);
             PreparedStatement ps = conn.prepareStatement(sql)) {

            // 设置参数
            ps.setInt(1, 18);           // 年龄 >= 18
            ps.setString(2, "active");  // 状态是active
            ps.setInt(3, 10);           // 最多返回10条

            // 执行查询
            try (ResultSet rs = ps.executeQuery()) {
                List users = new ArrayList<>();

                while (rs.next()) {
                    User user = new User();
                    user.setId(rs.getLong("id"));
                    user.setName(rs.getString("name"));
                    user.setEmail(rs.getString("email"));
                    user.setAge(rs.getInt("age"));
                    user.setCreatedAt(rs.getTimestamp("created_at"));
                    users.add(user);
                }

                System.out.println("查询到 " + users.size() + " 条用户记录");
                users.forEach(System.out::println);
            }

        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

// User类
class User {
    private Long id;
    private String name;
    private String email;
    private int age;
    private Timestamp createdAt;

    // getters, setters, toString省略
}

ResultSet处理详解

ResultSet基本操作

// 游标移动(默认只在第一行之前)
rs.next();      // 移动到下一行,返回是否有数据
rs.previous();  // 移动到上一行(需要支持滚动)
rs.first();     // 移动到第一行
rs.last();      // 移动到最后一行
rs.absolute(5); // 移动到第5行

// 获取数据
while (rs.next()) {
    // 按列名获取(推荐,代码更清晰)
    String name = rs.getString("name");
    int age = rs.getInt("age");

    // 按列索引获取(从1开始)
    String name2 = rs.getString(1);
    int age2 = rs.getInt(2);
}

各种数据类型的获取

while (rs.next()) {
    // 基本类型
    int id = rs.getInt("id");
    long big = rs.getLong("id");
    double price = rs.getDouble("price");
    boolean active = rs.getBoolean("status");

    // 字符串
    String name = rs.getString("name");

    // 日期时间(推荐使用Java 8+的LocalDateTime)
    java.sql.Date sqlDate = rs.getDate("birth_date");        // java.sql.Date
    java.sql.Time sqlTime = rs.getTime("create_time");      // java.sql.Time
    java.sql.Timestamp timestamp = rs.getTimestamp("create_time"); // 日期+时间

    // Java 8+ 推荐
    LocalDate localDate = rs.getObject("birth_date", LocalDate.class);
    LocalDateTime localDateTime = rs.getObject("create_time", LocalDateTime.class);

    // 大数据
    String content = rs.getString("description");  // 短文本
    InputStream is = rs.getBinaryStream("file_data"); // 二进制流
}

参数设置方法一览

方法适用类型示例
setInt(int paramIndex, int value)int, Integerps.setInt(1, 25)
setLong(int paramIndex, long value)long, Longps.setLong(1, 100L)
setDouble(int paramIndex, double value)double, Doubleps.setDouble(1, 99.99)
setString(int paramIndex, String value)Stringps.setString(1, "北京")
setBoolean(int paramIndex, boolean value)booleanps.setBoolean(1, true)
setDate(int paramIndex, Date value)java.sql.Dateps.setDate(1, new Date())
setNull(int paramIndex, int sqlType)NULL值ps.setNull(1, Types.VARCHAR)
setObject(int paramIndex, Object value)通用ps.setObject(1, obj)

常见的SQL查询模式

1. 查询单条记录

public User findById(Long id) {
    String sql = "SELECT * FROM users WHERE id = ?";
    try (Connection conn = getConnection();
         PreparedStatement ps = conn.prepareStatement(sql)) {

        ps.setLong(1, id);

        try (ResultSet rs = ps.executeQuery()) {
            if (rs.next()) {
                return mapToUser(rs);
            }
        }
    }
    return null;  // 未找到
}

private User mapToUser(ResultSet rs) throws SQLException {
    User user = new User();
    user.setId(rs.getLong("id"));
    user.setName(rs.getString("name"));
    user.setEmail(rs.getString("email"));
    user.setAge(rs.getInt("age"));
    return user;
}

2. 模糊查询

public List searchByName(String keyword) {
    String sql = "SELECT * FROM users WHERE name LIKE ?";
    try (Connection conn = getConnection();
         PreparedStatement ps = conn.prepareStatement(sql)) {

        ps.setString(1, "%" + keyword + "%");  // LIKE需要自己加%

        List results = new ArrayList<>();
        try (ResultSet rs = ps.executeQuery()) {
            while (rs.next()) {
                results.add(mapToUser(rs));
            }
        }
        return results;
    }
}

3. 批量查询(IN条件)

public List findByIds(List ids) {
    if (ids == null || ids.isEmpty()) {
        return new ArrayList<>();
    }

    // 构建 ?,,? 形式的占位符
    String placeholders = ids.stream()
        .map(id -> "?")
        .collect(Collectors.joining(","));

    String sql = "SELECT * FROM users WHERE id IN (" + placeholders + ")";

    try (Connection conn = getConnection();
         PreparedStatement ps = conn.prepareStatement(sql)) {

        // 设置每个参数
        for (int i = 0; i < ids.size(); i++) {
            ps.setLong(i + 1, ids.get(i));
        }

        List results = new ArrayList<>();
        try (ResultSet rs = ps.executeQuery()) {
            while (rs.next()) {
                results.add(mapToUser(rs));
            }
        }
        return results;
    }
}

注意事项

ResultSet需要关闭!

ResultSet会在Statement关闭时自动关闭,但最好显式关闭以避免资源泄漏。使用try-with-resources。

索引从1开始!

PreparedStatement的参数索引从1开始,不是0。

小结

  • PreparedStatement防止SQL注入,是推荐做法
  • 使用?占位符代替字符串拼接
  • 通过setXxx()方法按顺序绑定参数
  • ResultSet通过next()遍历,getXxx()获取数据
  • 使用try-with-resources确保资源正确关闭
☕ Java 在线代码编辑器
📝 运行结果