目录
3.5.4 使用PreparedStatement解决sql注入
一.JDBC概述
1.1 JDBC概念
![](https://img-blog.csdnimg.cn/73bfcde1faab41fa9d4729c59ebc9d3b.png)
1.2 JDBC优点
![](https://img-blog.csdnimg.cn/d0f41fb6f82648f6b7356009304f9335.png)
二.JDBC快速入门
![](https://img-blog.csdnimg.cn/be0a6e19c6224dc5b1d60f936a1bc2e3.png)
2.1 编写Java代码步骤
三.JDBC API详解
3.1 DriverManeger
3.1.1 作用
String userName = "root";
String password = "root";
String url = "jdbc:mysql://127.0.0.1:3306/db1?useSSL=false";
Connection con = DriverManager.getConnection(url,userName,password);
3.2 Connection
3.2.1 作用
3.2.2 获取执行SQL的对象
![](https://img-blog.csdnimg.cn/5ac2e0d75c0843009573d440820dc645.png)
![](https://img-blog.csdnimg.cn/c0853d4aa12d4c1a822ee3f724e8b0c0.png)
![](https://img-blog.csdnimg.cn/f95e47e26aab4afa8851bd2fdabfc0a0.png)
3.2.3 事务管理
/**
* JDBC API 详解:Connection
*/
public class JDBCDemo3_Connection {
public static void main(String[] args) throws Exception {
//1.注册驱动,mysql5之后的驱动包,可以省略注册驱动的步骤
Class.forName("com.mysql.jdbc.Driver");
//2.获取连接对象
String userName = "root";
String password = "root";
//useSSL=false代表禁用安全提示
String url = "jdbc:mysql:///db1?useSSL=false";//连接的是本机的数据库的话,可以不加IP地址或端口号。 localhost是本机的IP地址。
Connection con = DriverManager.getConnection(url,userName,password);
//3.定义sql
String sql1 = "update account set money=2000 where id=1";
String sql2 = "update account set money=2000 where id=2";
//4.获取执行sql的对象Statement
Statement statement = con.createStatement();
try {
//开启事务
con.setAutoCommit(false);
//5.执行sql
int count1 = statement.executeUpdate(sql1);//该方法的返回结果是account表中受影响的行数
//6.处理结果
System.out.println(count1);
//制造个错误
int a = 8/0;
//5.执行sql
int count2 = statement.executeUpdate(sql2);
//6.处理结果
System.out.println(count2);
//提交事务
con.commit();
} catch (Exception e) {
con.rollback();//如果代码出现了异常,就回滚到事务开启前的状态
e.printStackTrace();
}
//7.释放资源
statement.close();
con.close();
}
}
3.3 Statement
3.3.1 作用
3.3.2 案例
![](https://img-blog.csdnimg.cn/67a91c0f2ed5485d8eb269f8f61c0ed3.png)
![](https://img-blog.csdnimg.cn/900293583a1843d0bd37067160e79e44.png)
3.4 ResultSet
3.4.1 概述
![](https://img-blog.csdnimg.cn/e0e8c411a9024035b654b626eb66d4a9.png)
![](https://img-blog.csdnimg.cn/690307887ec14e78b9aba7a51da20b77.png)
3.4.2 案例
![](https://img-blog.csdnimg.cn/e0453ce36afd4cf28a34078f47bb4dbd.png)
![](https://img-blog.csdnimg.cn/0d0ae4a215514b46a275bc22245f4944.png)
3.5 PreparedStatement
3.5.1 SQL注入
SQL注入是通过操作输入来修改事先定义好的SQL语句,用以达到执行代码对服务器进行攻击的方法。
3.5.2 模拟SQL注入问题
1.tb_user表:
2.Java代码:
//用户登录
public class JDBCDemo6_UserLogin {
@Test
public void testUserLogin() throws Exception {
//1.注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2.获取连接对象
String userName = "root";
String password = "root";
String url = "jdbc:mysql://127.0.0.1:3306/db1?useSSL=false";
Connection con = DriverManager.getConnection(url,userName,password);
//接收用户输入的用户名和密码
String name = "hcfshjbcjashn";
String psw = "' or '2' = '2";
//3.定义sql,字符串拼接,分成三部分看
String sql = "select * from tb_user where username='"+name+"' and password='"+psw+"'";
//4.获取Statement对象
Statement statement = con.createStatement();
//5.执行sql
ResultSet resultSet = statement.executeQuery(sql);
//6.处理结果
if(resultSet.next()){
System.out.println("登录成功");
}else{
System.out.println("登陆失败");
}
//7.关闭资源
resultSet.close();
statement.close();
con.close();
}
}
上面代码是将用户名和密码拼接到sql语句中,拼接后的sql语句如 下:
从上面语句可以看出条件 username = 'sjdljfld' and password = '' 不管是否满足,而 or 后面的 '1' = '1' 是始终 满足的,最终条件是成立的,就可以正常的进行登陆了。
3.5.3 PreparedStatement概述
1.作用
①预编译SQL语句并执行
②预防SQL注入问题
2.步骤
// SQL语句中的参数值,使用?占位符替代
String sql = "select * from user where username = ? and password = ?";
// 通过Connection对象获取,并传入对应的sql语句
PreparedStatement pstmt = conn.prepareStatement(sql);
设置参数值 上面的sql语句中参数使用 ? 进行占位,在之前之前肯定要设置 这些 ? 的值。
pstmt.setXXX(1,name);
pstmt.setXXX(2,pwd);
3.5.4 使用PreparedStatement解决sql注入
/**
* JDBC API:PreparedStatement
*/
public class JDBCDemo7_PreparedStatemrnt {
@Test
public void testUserLogin() throws Exception {
//1.注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2.获取连接对象
String userName = "root";
String password = "root";
//useServerPrepStmts=true代表开启预编译功能
String url = "jdbc:mysql://127.0.0.1:3306/db1?useSSL=false";
Connection con = DriverManager.getConnection(url,userName,password);
//接收用户输入的用户名和密码
String name = "zhangsan";
String psw = "123";
//3.定义sql,字符串拼接,分成三部分看
String sql = "select * from tb_user where username=? and password=?";
//4.获取PreparedStatement对象并添加sql的参数
PreparedStatement ps = con.prepareStatement(sql);
ps.setString(1,name);
ps.setString(2,psw);
//5.执行sql
ResultSet resultSet = ps.executeQuery();
//6.处理结果
if(resultSet.next()){
System.out.println("登录成功");
}else{
System.out.println("登陆失败");
}
//7.关闭资源
resultSet.close();
ps.close();
con.close();
}
}
执行上面语句就可以发现不会出现SQL注入漏洞问题了。那么 PreparedStatement又是如何解决的呢?它是将特殊字符进行了转 义,转义的SQL如下:
3.5.5 PreparedStatement原理
Java代码操作数据库流程如图所示:
1.将sql语句发送到MySQL服务器端
2.MySQL服务端会对sql语句进行如下操作
检查SQL语句
检查SQL语句的语法是否正确。
3.编译SQL语句。将SQL语句编译成可执行的函数。
4.检查SQL和编译SQL花费的时间比执行SQL的时间还要长。如果我们只是重新设置参数,那么检查SQL语句和编译SQL语句将不需要重复执行。这样就提高了性能。
5.执行SQL语句
接下来我们通过查询日志来看一下原理。
1.开启预编译功能
在代码中编写url时需要加上以下参数。而我们之前根本就没有开启预编译功能,只是解决了SQL注入漏洞。
2.配置MySQL执行日志(重启mysql服务后生效)
在mysql配置文件(my.ini)中添加如下配置:
log-output=FILE
general-log=1
general_log_file="D:\mysql.log"
slow-query-log=1
slow_query_log_file="D:\mysql_slow.log"
long_query_time=2
java测试代码如下:
/**
* JDBC API:PreparedStatement
*/
public class JDBCDemo7_PreparedStatemrnt {
@Test
public void testUserLogin() throws Exception {
//1.注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2.获取连接对象
String userName = "root";
String password = "root";
//useServerPrepStmts=true代表开启预编译功能
String url = "jdbc:mysql://127.0.0.1:3306/db1?useSSL=false";
Connection con = DriverManager.getConnection(url,userName,password);
//接收用户输入的用户名和密码
String name = "zhangsan";
String psw = "123";
//3.定义sql,字符串拼接,分成三部分看
String sql = "select * from tb_user where username=? and password=?";
//4.获取PreparedStatement对象并添加sql的参数
PreparedStatement ps = con.prepareStatement(sql);
ps.setString(1,name);
ps.setString(2,psw);
//5.执行sql
ResultSet resultSet = ps.executeQuery();
//6.处理结果
if(resultSet.next()){
System.out.println("登录成功");
}else{
System.out.println("登陆失败");
}
//7.关闭资源
resultSet.close();
ps.close();
con.close();
}
}
查看 D:\mysql.log 日志如下:
上图中第三行中的 Prepare 是对SQL语句进行预编译。第四行和第五行是执行了两次SQL语句,而第二次执行前并没有对SQL 进行预编译。
总结:
①在获取PreparedStatement对象时,将sql语句发送给mysql 服务器进行检查,编译(这些步骤很耗时)
②执行时就不用再进行这些步骤了,速度更快
③如果sql模板一样,则只需要进行一次检查、编译