JdbcTemplate

本文详细介绍了PreparedSatement在防止SQL注入中的作用,解释了其执行原理,强调了预编译带来的效率提升和安全性增强。通过实例展示了如何创建、设置参数、执行SQL以及实现增删查改操作,帮助读者掌握使用PreparedSatement来提高数据库操作的安全性和性能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

PreparedSatement预编译对象

目标

能够理解什么是SQL注入

能够理解PreparedSatement的执行原理

讲解

SQL注入问题

在我们前一天JDBC实现登录案例中,当我们输入以下密码,我们发现我们账号和密码都不对竟然登录成功了

请输入用户名:
hehe
请输入密码:
a' or '1'='1

问题分析:

// 代码中的SQL语句
"SELECT * FROM user WHERE name='" + name + "' AND password='" + password + "';";
// 将用户输入的账号密码拼接后
"SELECT * FROM user WHERE name='hehe' AND password='a' or '1'='1';"

我们让用户输入的密码和SQL语句进行字符串拼接。用户输入的内容作为了SQL语句语法的一部分,改变了原有SQL真正的意义,以上问题称为SQL注入。

要解决SQL注入就不能让用户输入的密码和我们的SQL语句进行简单的字符串拼接。需要使用PreparedSatement类解决SQL注入。

PreparedSatement的执行原理

继承结构:
在这里插入图片描述
我们写的SQL语句让数据库执行,数据库不是直接执行SQL语句字符串。和Java一样,数据库需要执行编译后的SQL语句(类似Java编译后的字节码文件)。

  1. Satement对象每执行一条SQL语句都会先将这条SQL语句发送给数据库编译,数据库再执行。
Statement stmt = conn.createStatement();
stmt.executeUpdate("INSERT INTO users VALUES (1, '张三', '123456');");
stmt.executeUpdate("INSERT INTO users VALUES (2, '李四', '666666');");

上面2条SQL语句我们可以看到大部分内容是相同的,只是数据略有不一样。数据库每次执行都编译一次。如果有1万条类似的SQL语句,数据库需要编译1万次,执行1万次,显然效率就低了。

  1. prepareStatement()会先将SQL语句发送给数据库预编译。PreparedStatement会引用着预编译后的结果。可以多次传入不同的参数给PreparedStatement对象并执行。相当于调用方法多次传入不同的参数。
String sql = "INSERT INTO users VALUES (?, ?, ?);";
// 会先将SQL语句发送给数据库预编译。PreparedStatement会引用着预编译后的结果。
PreparedStatement pstmt = conn.prepareStatement(sql);

// 设置参数
pstmt.setString(1, 1);
pstmt.setInt(2, "张三");
pstmt.setString(3, "123456");
pstmt.executeUpdate();

// 再次设置参数
pstmt.setString(1, 2);
pstmt.setInt(2, "李四");
pstmt.setString(3, "66666");
pstmt.executeUpdate();

上面预编译好一条SQL,2次传入了不同的参数并执行。如果有1万条类似的插入数据的语句。数据库只需要预编译一次,传入1万次不同的参数并执行。减少了SQL语句的编译次数,提高了执行效率。

示意图
在这里插入图片描述

PreparedSatement的好处
  1. prepareStatement()会先将SQL语句发送给数据库预编译。PreparedStatement会引用着预编译后的结果。可以多次传入不同的参数给PreparedStatement对象并执行。减少SQL编译次数,提高效率。
  2. 安全性更高,没有SQL注入的隐患。
  3. 提高了程序的可读性## PreparedSatement的API介绍

目标

能够掌握PreparedSatement相应的API

讲解

获取PreparedSatement

java.sql.Connection有获取PreparedSatement对象的方法

PreparedStatement prepareStatement(String sql) 
会先将SQL语句发送给数据库预编译。PreparedStatement对象会引用着预编译后的结果。
PreparedSatement的API介绍

java.sql.PreparedStatement中有设置SQL语句参数,和执行参数化的SQL语句的方法

  1. void setDouble(int parameterIndex, double x) 将指定参数设置为给定 Java double 值。
    
  2. void setFloat(int parameterIndex, float x) 将指定参数设置为给定 Java REAL 值。 
    
  3. void setInt(int parameterIndex, int x) 将指定参数设置为给定 Java int 值。
    
  4. void setLong(int parameterIndex, long x) 将指定参数设置为给定 Java long 值。 
    
  5. void setObject(int parameterIndex, Object x) 使用给定对象设置指定参数的值。  
    
  6. void setString(int parameterIndex, String x) 将指定参数设置为给定 Java String 值。 
    
  7. ResultSet executeQuery() 在此 PreparedStatement 对象中执行 SQL 查询,并返回该查询生成的ResultSet对象。 
    
  8. int executeUpdate() 在此 PreparedStatement 对象中执行 SQL 语句,该语句必须是一个 SQL 数据操作语言DML语句,比如 INSERT、UPDATE 或 DELETE 语句;或者是无返回内容的 SQL 语句,比如 DDL 语句。 
    
PreparedSatement使用步骤
  1. 编写SQL语句,未知内容使用?占位:"SELECT * FROM user WHERE name=? AND password=?;";
  2. 获得PreparedStatement对象
  3. 设置实际参数
  4. 执行参数化SQL语句
  5. 关闭资源## PreparedSatement实现增删查改

目标

能够掌握PreparedSatement实现增删查改

讲解

创建表结构
CREATE TABLE employee (
  id INT PRIMARY KEY AUTO_INCREMENT,
  NAME VARCHAR(20),
  age INT,
  address VARCHAR(50)
);
添加数据
向Employee表添加3条记录
// 添加数据: 向Employee表添加3条记录
public static void addEmployee() throws Exception {
	Connection conn = JDBCUtils.getConnection();
	String sql = "INSERT INTO employee VALUES (NULL, ?, ?, ?);";
	// prepareStatement()会先将SQL语句发送给数据库预编译。
	PreparedStatement pstmt = conn.prepareStatement(sql);

	// 设置参数
	pstmt.setString(1, "刘德华");
	pstmt.setInt(2, 57);
	pstmt.setString(3, "香港");
	int i = pstmt.executeUpdate();
	System.out.println("影响的行数:" + i);

	// 再次设置参数
	pstmt.setString(1, "张学友");
	pstmt.setInt(2, 55);
	pstmt.setString(3, "澳门");
	i = pstmt.executeUpdate();
	System.out.println("影响的行数:" + i);

	// 再次设置参数
	pstmt.setString(1, "黎明");
	pstmt.setInt(2, 52);
	pstmt.setString(3, "香港");
	i = pstmt.executeUpdate();
	System.out.println("影响的行数:" + i);

	JDBCUtils.close(conn, pstmt);
}

效果:
在这里插入图片描述

PreparedSatement修改数据

将id为2的学生地址改成台湾

// 修改数据: 将id为2的学生地址改成台湾
public static void updateEmployee() throws Exception {
	Connection conn = JDBCUtils.getConnection();

	String sql = "UPDATE employee SET address=? WHERE id=?;";
	PreparedStatement pstmt = conn.prepareStatement(sql);
	pstmt.setString(1, "台湾");
	pstmt.setInt(2, 2);
	int i = pstmt.executeUpdate();
	System.out.println("影响的行数:" + i);

	JDBCUtils.close(conn, pstmt);
}

效果:

PreparedSatement删除数据

删除id为2的员工

// 删除数据: 删除id为2的员工
public static void deleteEmployee() throws Exception {
	Connection conn = JDBCUtils.getConnection();

	String sql = "DELETE FROM employee WHERE id=?;";
	PreparedStatement pstmt = conn.prepareStatement(sql);
	pstmt.setInt(1, 2);
	int i = pstmt.executeUpdate();
	System.out.println("影响的行数:" + i);

	JDBCUtils.close(conn, pstmt);
}

效果:
在这里插入图片描述
使用PreparedSatement改写登录案例

PreparedSatement使用步骤
  1. 编写SQL语句,未知内容使用?占位
  2. 获得PreparedStatement对象
  3. 设置实际参数
  4. 执行参数化SQL语句
  5. 关闭资源
案例代码
public class Demo02 {
	public static void main(String[] args) throws Exception {
        // 让用户输入账号和密码
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入账号: ");
        String name = sc.nextLine();
        System.out.println("请输入密码: ");
        String password = sc.nextLine();
        
		// 获取连接
		Connection conn = JDBCUtils.getConnection();
		
		// 编写SQL语句,未知内容使用?占位
		String sql = "SELECT * FROM user WHERE name=? AND password=?;";
		
		// prepareStatement()会先将SQL语句发送给数据库预编译。
		PreparedStatement pstmt = conn.prepareStatement(sql);
		
		// 指定?的值
		// parameterIndex: 第几个?,从1开始算
		// x: 具体的值
		pstmt.setString(1, name);
		pstmt.setString(2, password); // 正确的密码
		
		ResultSet rs = pstmt.executeQuery();
		
		if (rs.next()) {
			String name = rs.getString("name");
			System.out.println("name:" + name);
		} else {
			System.out.println("没有找到数据...");
		}
		
		JDBCUtils.close(conn, pstmt, rs);
	}
}

在这里插入图片描述

PreparedSatement查询数据

目标

能够掌握PreparedSatement实现查询数据
在这里插入图片描述
在这里插入图片描述
查询id小于8的员工信息,并保存到员工类中

实现步骤
  1. 得到连接对象
  2. 得到Statement对象
  3. 编写SQL语句并执行,保存ResultSet
  4. 创建一个集合用于封装所有的记录
  5. 每次循环封装一个学生对象
  6. 把数据放到集合中
  7. 关闭连接
  8. 遍历集合,循环输出学生对象
代码
public class Employee {
	private int id;
	private String name;
	private int age;
	private String address;
	public Employee() {
	}
	
	public Employee(int id, String name, int age, String address) {
		this.id = id;
		this.name = name;
		this.age = age;
		this.address = address;
	}

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	public String getAddress() {
		return address;
	}

	public void setAddress(String address) {
		this.address = address;
	}

	@Override
	public String toString() {
		return "Employee2 [id=" + id + ", name=" + name + ", age=" + age + ", address=" + address + "]";
	}
}
// 查询数据: 查询id小于8的员工信息,并保存到员工类中
public static void queryEmployee() throws Exception {
	Connection conn = JDBCUtils.getConnection();
	String sql = "SELECT * FROM employee WHERE id<?;";
	PreparedStatement pstmt = conn.prepareStatement(sql);
	pstmt.setInt(1, 26);
	ResultSet rs = pstmt.executeQuery();

	// 创建集合存放多个Employee2对象
	ArrayList<Employee> list = new ArrayList<>();

	while (rs.next()) {
		// 移动到下一行有数据,取出这行数据
		int id = rs.getInt("id");
		String name = rs.getString("name");
		int age = rs.getInt("age");
		String address = rs.getString("address");

		// 创建Employee2对象
		Employee e = new Employee(id, name, age, address);
		// 将创建好的员工添加到集合中
		list.add(e);
	}

	// 输出对象
	for (Employee e : list) {
		System.out.println(e);
	}
	
	JDBCUtils.close(conn, pstmt, rs);
}

效果:
在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值