executequery传参如何正确使用?

99ANYc3cd6
预计阅读时长 31 分钟
位置: 首页 参数 正文

ExecuteQuery 是一个强大的方法,它允许你执行一个 SQL 查询并直接将结果映射到自定义的对象集合中。核心优势在于它极大地简化了数据访问,并且通过参数化查询有效防止了 SQL 注入攻击。

executequery 传参数
(图片来源网络,侵删)

我们将重点讨论 参数化查询,这是传递参数最安全、最推荐的方式。


核心概念:为什么使用参数化查询?

  1. 安全性:直接拼接 SQL 字符串("SELECT * FROM Users WHERE Name = '" + userName + "'")是极其危险的。userName 包含恶意代码(如 ' OR '1'='1),整个 SQL 逻辑就会被破坏,导致数据泄露(SQL 注入),参数化查询将 SQL 语句和数据(参数)分开处理,数据库引擎会将其视为纯粹的值,从而杜绝了注入风险。
  2. 性能:对于需要重复执行的查询(只是参数值不同),数据库可以缓存编译后的 SQL 计划,提高执行效率。
  3. 可读性和维护性:代码更清晰,SQL 语句结构一目了然,避免了复杂的字符串拼接。

参数化查询的通用语法

参数化查询在 SQL 语句中使用占位符来表示参数,不同的数据库和框架使用不同的占位符风格:

  • @paramName (SQL Server, C#):最常见的方式,使用 符号后跟参数名。
  • (OleDb, ODBC, Java JDBC):使用问号作为位置占位符,参数顺序必须和添加的顺序一致。
  • paramName (Oracle, SQLite):使用冒号后跟参数名。
  • 或 (MySql Connector/NET):支持两种风格,但推荐使用 。

在不同环境中的具体实现

下面我们以最常用的 @paramName 风格为例,展示在几种主流技术栈中的用法。

C# (ADO.NET)

这是最基础也是最经典的用法,通常使用 SqlCommandSqlParameter

executequery 传参数
(图片来源网络,侵删)

场景:查询指定 ID 的用户信息。

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
}
public class UserRepository
{
    private readonly string _connectionString;
    public UserRepository(string connectionString)
    {
        _connectionString = connectionString;
    }
    public User GetUserById(int userId)
    {
        // 1. 定义 SQL 查询,使用 @paramName 作为占位符
        string sql = "SELECT Id, Name, Email FROM Users WHERE Id = @UserId AND IsActive = 1";
        User user = null;
        // 2. 使用 'using' 语句确保连接、命令和读取器被正确释放
        using (SqlConnection connection = new SqlConnection(_connectionString))
        using (SqlCommand command = new SqlCommand(sql, connection))
        {
            // 3. 添加参数
            //    - 第一个参数是 SQL 中的占位符名称
            //    - 第二个参数是要传入的值
            command.Parameters.AddWithValue("@UserId", userId);
            try
            {
                connection.Open();
                using (SqlDataReader reader = command.ExecuteReader())
                {
                    if (reader.Read())
                    {
                        user = new User
                        {
                            Id = reader.GetInt32(0),
                            Name = reader.GetString(1),
                            Email = reader.GetString(2)
                        };
                    }
                }
            }
            catch (Exception ex)
            {
                // 处理异常
                Console.WriteLine($"数据库查询出错: {ex.Message}");
            }
        }
        return user;
    }
}

关键点

  • command.Parameters.AddWithValue("@UserId", userId); 是核心,它将变量 userId 的值安全地绑定到 SQL 语句的 @UserId 占位符上。
  • AddWithValue 方法会自动推断参数的数据类型,非常方便。

C# (Dapper - 一个轻量级 ORM)

Dapper 极大地简化了 ADO.NET 的操作,ExecuteQuery(在 Dapper 中通常叫 Query)是其核心功能之一。

场景:查询所有属于指定部门的员工。

executequery 传参数
(图片来源网络,侵删)
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using Dapper;
public class Employee
{
    public int EmployeeId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int DepartmentId { get; set; }
}
public class EmployeeRepository
{
    private readonly string _connectionString;
    public EmployeeRepository(string connectionString)
    {
        _connectionString = connectionString;
    }
    public IEnumerable<Employee> GetEmployeesByDepartment(int departmentId)
    {
        // 1. 定义 SQL 查询
        string sql = "SELECT EmployeeId, FirstName, LastName, DepartmentId FROM Employees WHERE DepartmentId = @DeptId";
        // 2. 定义参数对象
        // Dapper 会自动查找 SQL 中 @ 开头的属性名并与对象属性匹配
        var parameters = new { DeptId = departmentId };
        IEnumerable<Employee> employees = new List<Employee>();
        // 3. 执行查询
        using (IDbConnection connection = new SqlConnection(_connectionString))
        {
            try
            {
                // Query<T> 会自动将查询结果映射到 Employee 对象的集合中
                employees = connection.Query<Employee>(sql, parameters);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Dapper 查询出错: {ex.Message}");
            }
        }
        return employees;
    }
}

关键点

  • 简洁:Dapper 的 Query<T> 方法一行代码就完成了数据读取和对象映射。
  • 匿名对象new { DeptId = departmentId } 创建一个匿名对象,其属性名 DeptId 必须与 SQL 中的占位符 @DeptId 完全匹配(不区分大小写,但最好保持一致)。
  • 强类型:查询结果被直接转换成 IEnumerable<Employee> 集合,类型安全。

Java (JDBC)

Java 的 JDBC API 本身没有 ExecuteQuery,但 PreparedStatement 就是实现参数化查询的标准方式。

场景:根据用户名和密码查找用户。

import java.sql.*;
import java.util.ArrayList;
import java.util.List;
public class UserDAO {
    private final String jdbcUrl;
    private final String jdbcUsername;
    private final String jdbcPassword;
    public UserDAO(String jdbcUrl, String jdbcUsername, String jdbcPassword) {
        this.jdbcUrl = jdbcUrl;
        this.jdbcUsername = jdbcUsername;
        this.jdbcPassword = jdbcPassword;
    }
    public User findUser(String username, String password) throws SQLException {
        // 1. 定义 SQL 查询,使用 ? 作为占位符
        String sql = "SELECT id, username, email FROM users WHERE username = ? AND password = ?";
        User user = null;
        // 使用 try-with-resources 确保 Connection, PreparedStatement, ResultSet 被关闭
        try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUsername, jdbcPassword);
             PreparedStatement pstmt = conn.prepareStatement(sql)) {
            // 2. 设置参数 (索引从 1 开始)
            pstmt.setString(1, username);
            pstmt.setString(2, password);
            // 3. 执行查询
            try (ResultSet rs = pstmt.executeQuery()) {
                if (rs.next()) {
                    user = new User();
                    user.setId(rs.getInt("id"));
                    user.setUsername(rs.getString("username"));
                    user.setEmail(rs.getString("email"));
                }
            }
        }
        return user;
    }
}
// 假设的 User 类
class User {
    private int id;
    private String username;
    private String email;
    // getters and setters...
}

关键点

  • PreparedStatement 是关键,它预编译了 SQL 语句。
  • pstmt.setString(1, username); 的第一个参数是占位符的位置(从 1 开始),第二个参数是值。
  • 必须按顺序设置所有参数。

Python (PyMySQL / sqlite3)

在 Python 中,使用数据库驱动(如 PyMySQL 或内置的 sqlite3)的 execute 方法。

场景:查找所有价格高于某个值的商品。

import sqlite3
# 假设我们有一个 SQLite 数据库
def find_products_above_price(min_price):
    products = []
    # 1. 定义 SQL 查询,使用 ? 作为占位符
    sql = "SELECT id, name, price FROM products WHERE price > ?"
    # 2. 使用 try-finally 或 with 语句确保连接关闭
    try:
        # with 语句会自动处理连接的关闭
        with sqlite3.connect('my_database.db') as conn:
            conn.row_factory = sqlite3.Row  # 允许通过列名访问
            cursor = conn.cursor()
            # 3. 执行查询,参数作为元组传递
            #    注意:即使只有一个参数,也必须写成 (value,) 的形式
            cursor.execute(sql, (min_price,))
            # 4. 获取所有结果
            for row in cursor.fetchall():
                products.append({
                    'id': row['id'],
                    'name': row['name'],
                    'price': row['price']
                })
    except sqlite3.Error as e:
        print(f"数据库错误: {e}")
    return products
# 示例调用
expensive_products = find_products_above_price(100.0)
for product in expensive_products:
    print(product)

关键点

  • cursor.execute(sql, (min_price,)) 是核心,参数必须作为一个元组传递。
  • 特别注意:如果只有一个参数,元组中必须有一个逗号,即 (value,),否则 Python 会将其视为单个值而不是元组。

总结与最佳实践

技术 核心类/方法 参数占位符 参数传递方式
C# (ADO.NET) SqlCommand + Parameters.AddWithValue @ParamName command.Parameters.AddWithValue("@Param", value);
C# (Dapper) IDbConnection.Query @ParamName 匿名对象 new { Param = value }
Java (JDBC) PreparedStatement pstmt.setX(index, value); (索引从1开始)
Python (sqlite3) cursor.execute 元组 cursor.execute(sql, (value,))

黄金法则

  1. 永远不要使用字符串拼接来构建 SQL 查询,除非你 100% 确保输入数据是绝对安全的(硬编码的数字)。
  2. 始终优先使用参数化查询,这是防御 SQL 注入攻击最有效的方法。
  3. 保持代码简洁:在 C# 中,优先考虑使用 Dapper 等 ORM 库;在 Java 中,考虑 MyBatis 或 JPA;在 Python 中,考虑 SQLAlchemy,它们能让你用更少的代码完成更复杂的数据库操作。
-- 展开阅读全文 --
头像
cdlinux reaver参数
« 上一篇 01-04
HONOR SCL-TLOOH拆机有何亮点?
下一篇 » 01-05

相关文章

取消
微信二维码
支付宝二维码

最近发表

标签列表

目录[+]