通过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 ...
随机推荐
- 编写 Matlab mexFunction (C mex)
资料一 MATLAB的MEX文件编写和调试 1. MEX的编写格式 写MEX程序其实就是写一个DLL程序,所以你可以使用C,C++,Fortran等多种编程语言来写. 编写MEX程序的编辑器可以使用M ...
- Windows Phone 7 开发环境的搭建
本节开始进行Windows Phone 开发环境的搭建,包括所需要的操作系统及硬件的介绍,开发工具的下载与安装,以及开发工具的介绍等.由于Jake Lin老师的视频中讲解的是早期的Windows Ph ...
- python垃圾回收杂谈
当创建对象时Python立即向操作系统请求内存.每当对象的引用数减为0,Python垃圾回收器立刻挺身而出,立即将其释放,把内存还给操作系统.在Python中,每个对象都保存了一个称为引用计数的整数值 ...
- Guava Files 源码分析(一)
Files中的工厂 Files类中对InputStream, OutputStream以及Reader,Writer的操作封装了抽象工厂模式,抽象工厂是InputSupplier与OutputSupp ...
- ubuntu下mongodb启动脚本
run-mongodb.sh #!/bin/bash mongod --dbpath /usr/local/mongodb/data1 --logpath /usr/local/mongodb/log ...
- 线程池的corePoolSize、maximumPoolSize和poolSize
什么是线程池: 为了避免系统频繁的创建和销毁线程,我们可以将创建的线程进行复用.在线程池中总有那么几个活跃的线程,也有一定的最大值限制,一个业务使用完线程之后,不是立即销毁而是将其放入到线程池中,从而 ...
- Java文件操作大全
//1.创建文件夹 //import java.io.*; File myFolderPath = new File(str1); try { if (!myFolderPath.exists()) ...
- tensorflow 之常见模块conv,bn...实现
使用tensorflow时,会发现tf.nn,tf.layers, tf.contrib模块有很多功能是重复的,尤其是卷积操作,在使用的时候,我们可以根据需要现在不同的模块.但有些时候可以一起混用. ...
- Android数据解析-JSON解析
JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式,基于JavaScript(Standard ECMA-262 3rd Edition - December ...
- 有关于腾讯地图服务端IP定位接口的获取当前城市的处理
接口说明:http://apis.map.qq.com/ws/location/v1/ip 说明里面写了ip可以缺省,然并卵,经过测试的到结果并不能获取到当前城市,理由是腾讯ip库的对应ip精度没有定 ...