JDBC 【SQL注入】

一、SQL注入

(一)、SQL注入问题

1.向jdbc_user表中 插入两条数据

# 插入2条数据 
INSERT INTO jdbc_user VALUES(NULL,'jack','123456','2020/2/24'); 
INSERT INTO jdbc_user VALUES(NULL,'tom','123456','2020/2/24');

2.SQL注入演示

# SQL注入演示 -- 填写一个错误的密码 
SELECT * FROM jdbc_user 
WHERE username = 'tom' AND PASSWORD = '123' OR '1' = '1';

如果这是一个登陆操作,那么用户就登陆成功了.显然这不是我们想要看到的结果

(二)、注入案例:用户登陆

需求:
用户在控制台上输入用户名和密码, 然后使用 Statement 字符串拼接的方式 实现用户的登录

步骤

  1. 得到用户从控制台上输入的用户名和密码来查询数据库
  2. 写一个登录的方法
    a) 通过工具类得到连接
    b) 创建语句对象,使用拼接字符串的方式生成 SQL 语句
    c) 查询数据库,如果有记录则表示登录成功,否则登录失败
    d) 释放资源
Sql注入方式: 123' or '1'='1

代码示例

public class TestLogin01 { 
/**
* 用户登录案例 
* 使用 Statement字符串拼接的方式完成查询 
* @param args 
*/ 
public static void main(String[] args) throws SQLException { 
//1.获取连接 
Connection connection = JDBCUtils.getConnection(); 
//2.获取Statement 
Statement statement = connection.createStatement(); 
//3.获取用户输入的用户名和密码 
Scanner sc = new Scanner(System.in); 
System.out.println("请输入用户名: "); 
String name = sc.nextLine(); 
System.out.println("请输入密码: "); 
String pass = sc.nextLine(); 
System.out.println(pass); 
//4.拼接Sql,执行查询
// String pass = "xxx' or '1'='1"; xxx' or '1' = '1
String sql = "select * from jdbc_user " + "where username = " + " '" + name +"' " +" and password = " +" '" + pass +"'"; 
System.out.println(sql); 
ResultSet resultSet = statement.executeQuery(sql); 
//5.处理结果集,判断结果集是否为空 
if(resultSet.next()){ 
	System.out.println("登录成功! 欢迎您: " + name); 
}else { 
	System.out.println("登录失败!"); 
}
//释放资源 
JDBCUtils.close(connection,statement,resultSet); 
} 
}

问题分析:

  1. 什么是SQL注入?
    我们让用户输入的密码和 SQL 语句进行字符串拼接。用户输入的内容作为了 SQL 语句语法的一部分,改变了 原有SQL 真正的意义,以上问题称为 SQL 注入 .
  2. 如何实现的注入
    根据用户输入的数据,拼接处的字符串
select * from jdbc_user 
where username = 'abc' and password = 'abc' or '1'='1' 

name='abc' and password='abc' 为假 '1'='1' 真 
相当于 select * from user where true=true; 查询了所有记录

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

(三)、 预处理对象

  1. PreparedStatement 接口介绍
    PreparedStatement 是 Statement 接口的子接口,继承于父接口中所有的方法。它是一个预编译的 SQL 语句对象.
    预编译: 是指SQL 语句被预编译,并存储在 PreparedStatement 对象中。然后可以使用此对象多次高效地执行该语句。

  2. PreparedStatement 特点
    因为有预先编译的功能,提高 SQL 的执行效率。
    可以有效的防止 SQL 注入的问题,安全性更高

  3. 获取PreparedStatement对象
    通过Connection创建PreparedStatement对象

JDBC 【SQL注入】_第1张图片

  1. PreparedStatement接口常用方法
    JDBC 【SQL注入】_第2张图片

  2. 使用PreparedStatement的步骤

  • 编写 SQL 语句,未知内容使用?占位:
"SELECT * FROM jdbc_user WHERE username=? AND password=?";
  • 获得 PreparedStatement 对象
  • 设置实际参数:setXxx( 占位符的位置, 真实的值)
  • 执行参数化 SQL 语句
  • 关闭资源
    JDBC 【SQL注入】_第3张图片

二、使用PreparedStatement完成登录案例

使用 PreparedStatement 预处理对象,可以有效的避免SQL注入

步骤:
1.获取数据库连接对象
2.编写SQL 使用? 占位符方式
3.获取预处理对象 (预编译对象会将Sql发送给数据库 进行预编译)
4.提示用户输入用户名 & 密码
5.设置实际参数:setXxx(占位符的位置, 真实的值)
6.执行查询获取结果集
7.判断是否查询到数据
8.关闭资源

/*** 使用预编译对象 PrepareStatement 完成登录案例 * @param args * @throws SQLException */ 
public static void main(String[] args) throws SQLException { 
//1.获取连接 
Connection connection = JDBCUtils.getConnection(); 
//2.获取Statement 
Statement statement = connection.createStatement(); 
//3.获取用户输入的用户名和密码
Scanner sc = new Scanner(System.in); 
System.out.println("请输入用户名: "); 
String name = sc.nextLine(); 
System.out.println("请输入密码: "); 
String pass = sc.nextLine(); 
System.out.println(pass); 
//4.获取 PrepareStatement 预编译对象 
//4.1 编写SQL 使用 ? 占位符方式 
String sql = "select * from jdbc_user where username = ? and password = ?"; 
PreparedStatement ps = connection.prepareStatement(sql); 
//4.2 设置占位符参数 
ps.setString(1,name); 
ps.setString(2,pass); 
//5. 执行查询 处理结果集 
ResultSet resultSet = ps.executeQuery(); 
if(resultSet.next()){ 
	System.out.println("登录成功! 欢迎您: " + name); 
}else{
	System.out.println("登录失败!"); 
}
//6.释放资源 
JDBCUtils.close(connection,statement,resultSet); 
} 

(一)、PreparedStatement的执行原理

分别使用 Statement对象 和 PreparedStatement对象进行插入操作

public static void main(String[] args) throws SQLException { 
Connection con = JDBCUtils.getConnection(); 
//获取 Sql语句执行对象 
Statement st = con.createStatement(); 
//插入两条数据 
st.executeUpdate("insert into jdbc_user values(null,'张三','123','1992/12/26')"); 
st.executeUpdate("insert into jdbc_user values(null,'李四','123','1992/12/26')");

//获取预处理对象 
PreparedStatement ps = con.prepareStatement("insert into jdbc_user values(?,?,?,?)"); 
//第一条数 设置占位符对应的参数 
ps.setString(1,null); 
ps.setString(2,"hd"); 
ps.setString(3,"qwer"); 
ps.setString(4,"2000/1/10"); 
//执行插入 
ps.executeUpdate(); 
//第二条数据 
ps.setString(1,null); 
ps.setString(2,"jc"); 
ps.setString(3,"1122"); 
ps.setString(4,"2000/1/10"); 
//执行插入 
ps.executeUpdate(); 
//释放资源 
st.close(); 
ps.close(); 
con.close(); 
} 

JDBC 【SQL注入】_第4张图片

(二)、 Statement 与 PreparedStatement的区别?

  1. Statement用于执行静态SQL语句,在执行时,必须指定一个事先准备好的SQL语句。
  2. PrepareStatement是预编译的SQL语句对象,语句中可以包含动态参数“?”,在执行时可以为“?”动态设置参数值。
  3. PrepareStatement可以减少编译次数提高数据库性能。

三、JDBC 控制事务

之前我们是使用 MySQL 的命令来操作事务。接下来我们使用 JDBC 来操作银行转账的事务。

(一)、数据准备

-- 创建账户表 
CREATE TABLE account( 
	-- 主键 
	id INT PRIMARY KEY AUTO_INCREMENT, 
	-- 姓名 
	NAME VARCHAR(10), 
	-- 转账金额 
	money DOUBLE 
);

-- 添加两个用户 
INSERT INTO account (NAME, money) VALUES ('tom', 1000), ('jack', 1000);

(二)、事务相关API

JDBC 【SQL注入】_第5张图片

(三)、 开发步骤

  1. 获取连接
  2. 开启事务
  3. 获取到 PreparedStatement , 执行两次更新操作
  4. 正常情况下提交事务
  5. 出现异常回滚事务
  6. 最后关闭资源
//JDBC 操作事务 
public static void main(String[] args) { 
Connection con = null; 
PreparedStatement ps = null; 
try {
	//1. 获取连接 
	con = JDBCUtils.getConnection(); 
	//2. 开启事务 
	con.setAutoCommit(false); 
	//3. 获取到 PreparedStatement 执行两次更新操作 
	//3.1 tom 账户 -500 
	ps = con.prepareStatement("update account set money = money - ? where name = ? "); 
	ps.setDouble(1,500.0); 
	ps.setString(2,"tom"); 
	ps.executeUpdate(); 
	//模拟tom转账后 出现异常 
	System.out.println(1 / 0); 
	//3.2 jack 账户 +500 
	ps = con.prepareStatement("update account set money = money + ? where name = ? "); 
	ps.setDouble(1,500.0); 
	ps.setString(2,"jack"); 
	ps.executeUpdate(); 
	//4. 正常情况下提交事务 
	con.commit(); 
	System.out.println("转账成功!"); 
} catch (SQLException e) { 
	e.printStackTrace(); 
	try {
		//5. 出现异常回滚事务 
		con.rollback(); 
	} catch (SQLException ex) { 
		ex.printStackTrace(); 
	} 
} finally { 
	//6. 最后关闭资源 
	JDBCUtils.close(con,ps); 
} 
} 

四、数据库连接池

连接池介绍

什么是连接池

实际开发中“获得连接”或“释放资源”是非常消耗系统资源的两个过程,为了解决此类性能问题,通常情况我们 采用连接池技术,来共享连接Connection。这样我们就不需要每次都创建连接、释放连接了,这些操作都交给了连接池.

连接池的好处
用池来管理Connection,这样可以重复使用Connection。 当使用完Connection后,调用Connection的close()方法也不会真的关闭Connection,而是把Connection“归还”给池。

Java为数据库连接池提供了公共的接口:javax.sql.DataSource,各个厂商需要让自己的连接池实现这个接口。这样应用程序可以方便的切换不同厂商的连接池!

常见的连接池有 DBCP连接池, C3P0连接池, Druid连接池, 接下里我们就详细学习一下

(一)、 数据准备

#创建数据库 
CREATE DATABASE db5 CHARACTER SET utf8; 
#使用数据库 
USE db5; 
#创建员工表 
CREATE TABLE employee ( 
	eid INT PRIMARY KEY AUTO_INCREMENT , 
	ename VARCHAR (20), -- 员工姓名 
	age INT , -- 员工年龄 
	sex VARCHAR (6), -- 员工性别 
	salary DOUBLE , -- 薪水 
	empdate DATE -- 入职日期 
);

# 插入数据 
INSERT INTO employee (eid, ename, age, sex, salary, empdate) VALUES(NULL,'李清照',22,'女',4000,'2018-11-12'); 
INSERT INTO employee (eid, ename, age, sex, salary, empdate) VALUES(NULL,'林黛玉',20,'女',5000,'2019-03-14'); 
INSERT INTO employee (eid, ename, age, sex, salary, empdate) VALUES(NULL,'杜甫',40,'男',6000,'2020-01-01'); 
INSERT INTO employee (eid, ename, age, sex, salary, empdate) VALUES(NULL,'李白',25,'男',3000,'2017-10-01');


Druid(德鲁伊)是阿里巴巴开发的号称为监控而生的数据库连接池,Druid是目前最好的数据库连接池。在功能、性能、扩展性方面,都超过其他数据库连接池,同时加入了日志监控,可以很好的监控DB池连接和SQL的执行情况。

使用
导入 jar包 配置文件

JDBC 【SQL注入】_第6张图片

driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql:///db1?useSSL=false&useServerPrepStmts=true&characterEncoding=utf-8
username=root
password=123456
# 初始化连接数量
initialSize=5
# 最大连接数
maxActive=10
# 最大等待时间
maxWait=3000

(二)、 编写Druid工具类

获取数据库连接池对象
通过工厂来来获取 DruidDataSourceFactory类的createDataSource方法createDataSource(Properties p) 方法参数可以是一个属性集对象

public class DruidUtils { 
	//1.定义成员变量 
	public static DataSource dataSource; 
	//2.静态代码块 
	static{ 
		try {
			//3.创建属性集对象 
			Properties p = new Properties(); 
			//4.加载配置文件 Druid 连接池不能够主动加载配置文件 ,需要指定文件 
			InputStream inputStream = DruidUtils.class.getClassLoader().getResourceAsStream("druid.properties"); 
			//5. 使用Properties对象的 load方法 从字节流中读取配置信息 
			p.load(inputStream); 
			//6. 通过工厂类获取连接池对象 
			dataSource = DruidDataSourceFactory.createDataSource(p); 
		} catch (Exception e) { 
			e.printStackTrace(); 
		} 
	}
	
	//获取连接的方法 
	public static Connection getConnection(){ 
		try {
			return dataSource.getConnection(); 
		} catch (SQLException e) { 
			e.printStackTrace(); 
			return null; 
		} 
	}
	//释放资源 
	public static void close(Connection con, Statement statement){ 
		if(con != null && statement != null){ 
			try {
				statement.close(); 
				//归还连接 
				con.close(); 
			} catch (SQLException e) { 
				e.printStackTrace(); 
			} 
		} 
	}
	public static void close(Connection con, Statement statement, ResultSet resultSet){ 
		if(con != null && statement != null && resultSet != null){ 
			try {
				resultSet.close(); 
				statement.close(); 
				//归还连接 
				con.close(); 
			} catch (SQLException e) { 
				e.printStackTrace(); 
			} 
		} 
	} 
}

(三)、 测试工具类

需求: 查询薪资在3000 - 5000元之间的员工姓名

// 需求 查询 薪资在3000 到 5000之间的员工的姓名 
public static void main(String[] args) throws SQLException { 
//1.获取连接 
Connection con = DruidUtils.getConnection(); 
//2.获取Statement对象 
Statement statement = con.createStatement(); 
//3.执行查询 
ResultSet resultSet = statement.executeQuery("select ename from employee where salary between 3000 and 5000"); 
//4.处理结果集 
while(resultSet.next()){ 
	String ename = resultSet.getString("ename"); 
	System.out.println(ename); 
}
//5.释放资源 
DruidUtils.close(con,statement,resultSet); 
} 

你可能感兴趣的:(sql,java,python)