通过jdbc使用PreparedStatement,提升性能,防止sql注入
为什么要使用PreparedStatement?
一、通过PreparedStatement提升性能
Statement主要用于执行静态SQL语句,即内容固定不变的SQL语句。Statement每执行一次都要对传入的SQL语句编译一次,效率较差。
某些情况下,SQL语句只是其中的参数有所不同,其余子句完全相同,适用于PreparedStatement。
PreparedStatement的另外一个好处就是预防sql注入攻击
PreparedStatement是接口,继承自Statement接口。
使用PreparedStatement时,SQL语句已提前编译,三种常用方法 execute、 executeQuery 和 executeUpdate 已被更改,以使之不再需要参数。
PreparedStatement 实例包含已事先编译的 SQL 语句,SQL 语句可有一个或多个 IN 参数,IN参数的值在 SQL 语句创建时未被指定。该语句为每个 IN 参数保留一个问号(“?”)作为占位符。
每个问号的值必须在该语句执行之前,通过适当的setInt或者setString 等方法提供。
由于 PreparedStatement 对象已预编译过,所以其执行速度要快于 Statement 对象。因此,多次执行的 SQL 语句经常创建为 PreparedStatement 对象,以提高效率。
通常批量处理时使用PreparedStatement。
//SQL语句已发送给数据库,并编译好为执行作好准备
PreparedStatement pstmt = con.prepareStatement(
"UPDATE emp SET job= ? WHERE empno = ?");
//对占位符进行初始化
pstmt.setLong(1, "Manager");
pstmt.setInt(2,1001);
//执行SQL语句
pstmt.executeUpdate();
小案例:分别向数据库插入1000条记录。分别记录执行时间,然后进行比较。
新建项目:

使用Statement的 执行效率的类INSERT3:
package com.cnblogs.daliu_it; import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.Statement; /**
* 使用Statement的 执行效率
*
*/
public class INSERT3 { public static void main(String[] args) {
Connection conn = null;
try {
conn = DBUtility.getConnection();
Statement state = conn.createStatement();
long start = System.currentTimeMillis(); for (int i = 7000; i < 8000; i++) {
String sql = "INSERT INTO user VALUES" + "(" + i + ","
+ "'test" + i + "'," + "'12345'," + "5000," + "'test"
+ i + "@qq.com'" + ")";
state.executeUpdate(sql);
}
System.out.println("插入完毕");
long end = System.currentTimeMillis();
System.out.println("耗时:" + (end - start));
} catch (Exception e) {
System.out.println("插入数据失败");
e.printStackTrace();
} finally {
DBUtility.closeConnection(conn);
}
}
}
测试数据:

使用预编译PreparedStatement SQL提高执行效率的类INSERT2:
package com.cnblogs.daliu_it; import java.sql.Connection;
import java.sql.PreparedStatement; /**
* 使用预编译PreparedStatement SQL提高执行效率
*
*/
public class INSERT2 {
public static void main(String[] args) {
Connection conn = null;
try {
conn = DBUtility.getConnection();
// Statement state= conn.createStatement(); String sql = "INSERT INTO user VALUES(?,?,'123456',?,?)";
/*
* 根据给定的预编译SQL语句创建一个 PreparedStatement
*/
PreparedStatement ps = conn.prepareStatement(sql); long start = System.currentTimeMillis(); for (int i = 9000; i < 10000; i++) {
ps.setInt(1, i);
ps.setString(2, "test" + i);
ps.setInt(3, 5000);
ps.setString(4, "test" + i + "@qq.com");
ps.executeUpdate();
}
System.out.println("插入完毕");
long end = System.currentTimeMillis();
System.out.println("耗时:" + (end - start));
} catch (Exception e) {
System.out.println("插入数据失败");
e.printStackTrace();
} finally {
DBUtility.closeConnection(conn);
}
}
}
测试效果:

二、通过PreparedStatement防止SQL Injection
对JDBC而言,SQL注入攻击只对Statement有效,对PreparedStatement无效,因为PreparedStatement不允许在插入参数时改变SQL语句的逻辑结构。
使用预编译的语句对象时,用户传入的任何数据不会和原SQL语句发生匹配关系,无需对输入的数据做过滤。如果用户将”or 1 = 1”传入赋值给占位符,下述SQL语句将无法执行:select * from t where username = ? and password = ?;
PreparedStatement是Statement的子类,表示预编译的SQL语句的对象。在使用PreparedStatement对象执行SQL命令时,命令被数据库编译和解析,并放入命令缓冲区。缓冲区中的预编译SQL命令可以重复使用。
sql = "select * from users where NAME = ? and PWD = ?";
System.out.println(sql); con = DBUtility.getConnection(); //通过Statement 的改为prepareStatement
stmt = con.prepareStatement(sql); // rs = stmt.executeQuery(sql); stmt.setString(1, username);
stmt.setString(2, password);
rs = stmt.executeQuery();
使用PreparedStatement来执行SQL语句。在SQL语句中有2个问号,在代码中要给它们分别设置值,规则是:从左到右,对应1,2,...。
对于JDBC而言,SQL注入攻击只对Statement有效,对PreparedStatement是无效的,这是因为PreparedStatement不允许在插入时改变查询的逻辑结构。
例子:使用PreparedStatement实现用户名和密码的验证功能。
(1) 使用Statement实现用户名和密码的验证功能,并测试用户名为“Tom”、密码为“123”以及用户名为“Tom”、密码为“a' OR 'b'='b”是否能登录成功。
(2)使用PreparedStatement实现用户名和密码的验证功能,并测试用户名为“Tom”、密码为“a' OR 'b'='b”是否能登录成功。
1.新建一个java项目,配置文件,导入所需要的jar包。如下图:

2.首先创建user表:
create table users(
id int(4) auto_increment,
name varchar(50),
pwd varchar(50),
phone varchar(50)
);
desc users;
select * from users;
insert into users(id,username,password)values(1,'Tom','','');
insert into users(id,username,password)values(2,'Jerry','abc','');
insert into users(id,username,password)values(3,'Andy','','');
select * from users;
3.连接数据库类DBUtility:
package com.cnblogs.daliu_it; import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties; import org.apache.commons.dbcp.BasicDataSource;
/**
* 工具类
* @author daliu_it
*
*/
public class DBUtility {
private static BasicDataSource dataSource = null; public DBUtility() {
}
public static void init() { Properties dbProps = new Properties();
// 取配置文件可以根据实际的不同修改
try {
dbProps.load(DBUtility.class.getClassLoader().getResourceAsStream(
"com/cnblogs/daliu_it/db.properties"));
} catch (IOException e) {
e.printStackTrace();
} try {
String driveClassName = dbProps.getProperty("jdbc.driverClassName");
String url = dbProps.getProperty("jdbc.url");
String username = dbProps.getProperty("jdbc.username");
String password = dbProps.getProperty("jdbc.password"); String initialSize = dbProps.getProperty("dataSource.initialSize");
String minIdle = dbProps.getProperty("dataSource.minIdle");
String maxIdle = dbProps.getProperty("dataSource.maxIdle");
String maxWait = dbProps.getProperty("dataSource.maxWait");
String maxActive = dbProps.getProperty("dataSource.maxActive"); dataSource = new BasicDataSource();
dataSource.setDriverClassName(driveClassName);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password); // 初始化连接数
if (initialSize != null)
dataSource.setInitialSize(Integer.parseInt(initialSize)); // 最小空闲连接
if (minIdle != null)
dataSource.setMinIdle(Integer.parseInt(minIdle)); // 最大空闲连接
if (maxIdle != null)
dataSource.setMaxIdle(Integer.parseInt(maxIdle)); // 超时回收时间(以毫秒为单位)
if (maxWait != null)
dataSource.setMaxWait(Long.parseLong(maxWait)); // 最大连接数
if (maxActive != null) {
if (!maxActive.trim().equals("0"))
dataSource.setMaxActive(Integer.parseInt(maxActive));
}
} catch (Exception e) {
e.printStackTrace();
System.out.println("创建连接池失败!请检查设置!!!");
}
} /**
* 数据库连接
* @return
* @throws SQLException
*/
public static synchronized Connection getConnection() throws SQLException {
if (dataSource == null) {
init();
}
Connection conn = null;
if (dataSource != null) {
conn = dataSource.getConnection();
}
return conn;
} /**
* 关闭数据库
* @param conn
*/
public static void closeConnection(Connection conn){
if(conn!=null){
try {
conn.close();
} catch (SQLException e) {
System.out.println("关闭资源失败");
e.printStackTrace();
}
}
} }
4.使用Statement实现验证用户名密码是否存在的方法的类UserDAO:
package com.cnblogs.daliu_it; import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement; public class UserDAO { /**
* 使用Statement实现验证用户名密码是否存在的方法
*
* @param username
* @param password
*/
public void login(String username, String password) { // Statement
Connection con = null;
Statement stmt = null;
ResultSet rs = null; // 定义sql语句,用来查询用户名和密码
String sql = null; try {
sql = "select * from users where NAME = '" + username
+ "' and PWD= '" + password + "'"; // 检查一下sql语句是否拼写正确
System.out.println(sql); // 获得数据库的连接
con = DBUtility.getConnection(); stmt = con.createStatement(); // 执行sql语句
rs = stmt.executeQuery(sql); // 进行结果的遍历,并给出相应的提示
if (rs.next()) {
System.out.println("登录成功!");
} else {
System.out.println("登录失败!");
} } catch (SQLException e) { System.out.println("数据库访问异常!");
throw new RuntimeException(e); } finally { // 最后关闭一下资源
if (con != null) {
DBUtility.closeConnection(con);
}
}
}
}
5.使用PreparedStatement实现验证用户名密码是否存在的方法的类 UserDAO2:
package com.cnblogs.daliu_it; import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException; public class UserDAO2 { /**
* 使用PreparedStatement实现验证用户名密码是否存在的方法
*
* @param username
* @param password
*/
public void login(String username, String password) { Connection con = null; // 通过Statement 的改为prepareStatement
PreparedStatement stmt = null;
ResultSet rs = null; String sql = null; try {
// sql = "select * from users where NAME = '" + username+
// "' and PWD= '" + password + "'";
sql = "select * from users where NAME = ? and PWD = ?";
// 使用PreparedStatement是将 "aa' or '1' = '1"
// 作为一个字符串赋值给问号“?”,使其作为"用户名"字段的对应值,这样来防止SQL注入。 System.out.println(sql);
con = DBUtility.getConnection(); // 对于JDBC而言,SQL注入攻击只对Statement有效,对PreparedStatement是无效的,这是因为PreparedStatement不允许在插入时改变查询的逻辑结构。
stmt = con.prepareStatement(sql);
// rs = stmt.executeQuery(sql);
stmt.setString(1, username);
stmt.setString(2, password);
rs = stmt.executeQuery(); // 进行结果的遍历,并给出相应的提示
if (rs.next()) {
System.out.println("登录成功!");
} else {
System.out.println("登录失败!");
} System.out.println("执行完毕!");
} catch (SQLException e) { System.out.println("数据库访问异常!");
throw new RuntimeException(e); } finally { // 最后关闭一下资源
if (con != null) {
DBUtility.closeConnection(con);
}
}
}
}
6.配置文件db.properties:
#Oracle
#jdbc.driverClassName=oracle.jdbc.OracleDriver
#jdbc.url=jdbc:oracle:thin:@localhost:1521:orcl
#jdbc.username=root
#jdbc.password=123456 #Mysql
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/csdn
jdbc.username=root
jdbc.password=123456 dataSource.initialSize=10
dataSource.maxIdle=20
dataSource.minIdle=5
dataSource.maxActive=50
dataSource.maxWait=1000
7.测试类testCase:
package com.daliu_it.test; import java.sql.SQLException; import org.junit.Test; import com.cnblogs.daliu_it.DBUtility;
import com.cnblogs.daliu_it.UserDAO;
import com.cnblogs.daliu_it.UserDAO2; public class testCase { /**
* 测试是否连接
*
* @throws SQLException
*/
@Test
public void testgetConnection() throws SQLException {
DBUtility db = new DBUtility();
System.out.println(db.getConnection());
} /**
* 测试使用Statement实现验证用户名密码是否存在的方法
*/
@Test
public void testStatementLogin() { UserDAO dao = new UserDAO();
// 用户名不正确
dao.login("Tom1", "123");
// 用户名不正确
dao.login("Tom", "1234");
// 正确
dao.login("Tom", "123"); /**
* 这个也能登陆成功,不过这里会存在一个sql注入的问题
*/
dao.login("Tom", " a' OR 'b'='b "); } @Test
public void testPreparedStatementLogin() { UserDAO2 dao = new UserDAO2();
// 用户名不正确
dao.login("Tom1", "123");
// 用户名不正确
dao.login("Tom", "1234");
// 正确
dao.login("Tom", "123");
// 测试是否还存在sql注入问题,不能登陆成功,说明我们已经解决了sql注入问题
dao.login("Tom", " a' OR 'b'='b ");
/**
* 实现机制不同,注入只对SQL语句的准备(编译)过程有破坏作用,而PreparedStatement已经准备好了,
* 执行阶段只是把输入串作为数据处理,不再需要对SQL语句进行解析、准备,因此也就避免了SQL注入问题。
*/ } }
测试效果:
(1)连接效果:

(2)测试使用Statement实现验证用户名密码是否存在的方法

(3)测试使用PreparedStatement实现验证用户名密码是否存在的方法

原文作者:daliu_it
博文出处:http://www.cnblogs.com/liuhongfeng/p/4175765.html
本文版权归作者和博客园共有,但未经作者同意转载必须保留以上的声明且在放在文章页面明显位置。谢谢合作。
通过jdbc使用PreparedStatement,提升性能,防止sql注入的更多相关文章
- Java JDBC概要总结一(基本操作和SQL注入问题)
JDBC定义: JDBC(Java DataBase Connectivity,java数据库连接)是一种用于执行SQL语句的Java API.JDBC是Java访问数据库的标准规范,可以为不同的关系 ...
- jdbc java数据库连接 8)防止sql注入
回顾下之前jdbc的开发步骤: 1:建项目,引入数据库驱动包 2:加载驱动 Class.forName(..); 3:获取连接对象 4:创建执行sql语句的stmt对象; 写sql 5:执行sql ...
- PreparedStatement是如何防止SQL注入的?
为什么在Java中PreparedStatement能够有效防止SQL注入?这可能是每个Java程序员思考过的问题. 首先我们来看下直观的现象(注:需要提前打开mysql的SQL文日志) 1. 不使用 ...
- [疯狂Java]JDBC:PreparedStatement预编译执行SQL语句
1. SQL语句的执行过程——Statement直接执行的弊病: 1) SQL语句和编程语言一样,仅仅就会普通的文本字符串,首先数据库引擎无法识别这种文本字符串,而底层的CPU更不理解这些文本字符串( ...
- JDBC 用PreparedStatement语句动态操作SQL语句
https://blog.csdn.net/u014453898/article/details/79038187 1.Statement 和 PreparedStatement: Statement ...
- 用java PreparedStatement就不用担心sql注入了吗?
先感慨下,好久没写博客了,一是工作太忙,二是身体不太给力,好在终于查清病因了,趁着今天闲下来,迫不及待与读者交流,最后忠告一句:身体是活着的本钱! 言归正传,对java有了解的同学基本上都体验过JDB ...
- 转!! PreparedStatement是如何防止SQL注入的
SQL注入最简单也是最常见的例子就是用户登陆这一模块,如果用户对SQL有一定的了解,同时系统并没有做防止SQL注入处理,用户可以在输入的时候加上'两个冒号作为特殊字符,这样的话会让计算机认为他输入的是 ...
- preparedstatement 为什么可以防止sql注入
有大神总结的很好,,参考文献 http://www.importnew.com/5006.html preparedstatement优势:sql的预编译(数据库层面完成)提升效率. 为什么可以防止s ...
- jdbc获取PreparedStatement最终执行的sql语句
//直接打印PreparedStatement对象 System.out.println(ps); 输出结果: com.mysql.jdbc.JDBC42PreparedStatement@5f205 ...
随机推荐
- Roundcube login via PHP script
目前正在整合 roundcube 1.0.5 的邮件系统和其他系统,想取消登录过程,发现了这个,先赞一个! 原文地址: http://blog.philippheckel.com/2008/05/16 ...
- vc中nmake.exe cl.exe 的使用
首先简单介绍一下程序是如何编译链接的.程序写好之后,我们进行编译和链接来产生可执行程序.这时候,编译器为了完成编译和链接,需要知道很多信 息.比如要编译的文件是哪一个,使用哪些编译选项进行编译,编译好 ...
- Andorid之使用GMail后台偷偷发送邮件(不要干坏事噢=。 =)
工具类: import java.util.Date; import java.util.Properties; import javax.activation.CommandMap; import ...
- Maclean Liu对Oracle Database 12c新特性研究汇总
Maclean Liu关于DB 12c新特性的研究文章如下: [Oracle Database 12c新特性] In-Database Archiving数据库内归档 [Oracle Database ...
- go语言基础之复合类型
1.分类 类型 名称 长度 默认值 说明 pointer 指针 nil array 数组 0 slice 切片 nil 引⽤类型 map 字典 nil 引⽤类型 struct 结构体 2.指针 指针是 ...
- 使用Spring Security和OAuth2实现RESTful服务安全认证
这篇教程是展示如何设置一个OAuth2服务来保护REST资源. 源代码下载github. (https://github.com/iainporter/oauth2-provider)你能下载这个源码 ...
- 解决WordPress 页面无法评论的问题
最近在使用WordPress制作一个企业网站,因为是企业网站所以文章和页面都不需要评论功能,因此在主题里禁用掉了评论功能 //禁用页面和文章的评论功能//add_filter('the_posts', ...
- [Node.js]26. Level 5 : Route rendering
Instead of just writing out the quote to the response, instead render the quote.ejs template, passin ...
- 如何使用屏幕取色工具ColorPixl
ColorPix可以屏幕取色,假如现在想要取色桌面徽标键的颜色,按任意键可以锁定这个区域(press any key to lock)这样我们就可以在放大的区域更清楚的取色,加号按钮可以设置该软件是否 ...
- CAD批量合并文件
要求:将整饰完成504幅单独的宗地图合并成一张总图,合并后,去掉其他要素,只保留毕合的权属线. 解决: 1.合并dwg文件,除了手工粘贴复制外,最先想到的是插入块,即用Insert命令插入,测试结果可 ...