JDBC编程之预编译SQL与防注入式攻击以及PreparedStatement

18 篇文章 0 订阅

  在JDBC编程中,常用Statement、PreparedStatement 和 CallableStatement三种方式来执行查询语句,其中 Statement 用于通用查询, PreparedStatement 用于执行参数化查询,而 CallableStatement则是用于存储过程。

      1、Statement 
      该对象用于执行静态的 SQL 语句,并且返回执行结果。 此处的SQL语句必须是完整的,有明确的数据指示。查的是哪条记录?改的是哪条记录?都要指示清楚。
     通过调用 Connection 对象的 createStatement 方法创建该对象 
查询:ResultSet excuteQuery(String sql)——返回查询结果的封装对象ResultSet. 用next()遍历结果集,getXX()获取记录数据。
修改、删除、增加:int excuteUpdate(String sql)——返回影响的数据表记录数. 

      2、PreparedStatement 
    SQL 语句被预编译并存储在 PreparedStatement 对象中。然后可以使用此对象多次高效地执行该语句。 
    可以通过调用 Connection 对象的 preparedStatement() 方法获取 PreparedStatement 对象 
    PreparedStatement 对象所执行的 SQL 语句中,参数用问号(?)来表示,调用 PreparedStatement 对象的 setXXX() 方法来设置这些参数. setXXX() 方法有两个参数,第一个参数是要设置的 SQL 语句中的参数的索引(从 1 开始),第二个是设置的 SQL 语句中的参数的值,注意用setXXX方式设置时,需要与数据库中的字段类型对应,例如mysql中字段为varchar,就需要使用setString方法,如果为Date类型,就需要使用setDate方法来设置具体sql的参数。

    简单来说就是,预编译的SQL语句不是有具体数值的语句,而是用(?)来代替具体数据,然后在执行的时候再调用setXX()方法把具体的数据传入。同时,这个语句只在第一次执行的时候编译一次,然后保存在缓存中。之后执行时,只需从缓存中抽取编译过了的代码以及新传进来的具体数据,即可获得完整的sql命令。这样一来就省下了后面每次执行时语句的编译时间。

    使用预编译分4步走:

    1:定义预编译的sql语句,其中待填入的参数用  ?  占位。注意,?无关类型,不需要加分号之类。其具体数据类型在下面setXX()时决定。

    2:创建预编译Statement,并把sql语句传入。此时sql语句已与此preparedStatement绑定。所以第4步执行语句时无需再把sql语句作为参数传入execute()。

    3:填入具体参数。通过setXX(问号下标,数值)来为sql语句填入具体数据。注意:问号下标从1开始,setXX与数值类型有关,字符串就是setString(index,str).

    4:执行预处理对象。主要有:

 booleanexecute() 
          在此 PreparedStatement 对象中执行 SQL 语句,该语句可以是任何种类的 SQL 语句。
 ResultSetexecuteQuery() 
          在此 PreparedStatement 对象中执行 SQL 查询,并返回该查询生成的 ResultSet 对象。
 intexecuteUpdate() 
          在此 PreparedStatement 对象中执行 SQL 语句,该语句必须是一个 SQL 数据操作语言(Data Manipulation Language,DML)语句,比如 INSERTUPDATE 或 DELETE 语句;或者是无返回内容的 SQL 语句,比如 DDL 语句。

    注意,前面创建preparedstatement时已经把sql语句传入了,此时执行不需再把sql语句传入,这是与一般statement执行sql语句所不同之处。

    比如:

/**
	 * 由于每次addNewStudent()都要写sql语句,写起来比较繁琐,所以我用PreparedStatement()进行优化
	 * 1. PreparedStatement:是Statement的子接口,可以传入带占位符的SQL语句,并且提供了补充占位符变量的方法
	 * 2. 调用PreparedStatement的setXxx(int index, Object val)设置占位符从1开始,val表示要插入的数据
	 * 3.使用Statement需要拼写SQL语句,很辛苦,容易出错
	 * 4. 可以有效的防止SQL注入
	 */
	@Test
	public void testPreparedStatement() {
		Connection connection = null;
		PreparedStatement preparedStatement = null;
		
		try {
			connection = JDBCTools.getConnection();
			String sql = "INSERT INTO customers (id, name, email, birth) VALUES(?, ?, ?, ?)";
			
			preparedStatement = (PreparedStatement) connection.prepareStatement(sql);
			preparedStatement.setString(1, "2018");//1--对应id
			preparedStatement.setString(2, "ATGUIGU");//2--对应name
			preparedStatement.setString(3, "simpleit@163.com");//3--对应email
			preparedStatement.setDate(4, new java.sql.Date(new java.util.Date().getTime()));//4--对应birth
			
			//执行executeUpdate()或者executeQuery()不需要在传入sql
			preparedStatement.executeUpdate();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			JDBCTools.release(null, preparedStatement, connection);
		}
	}

使用预编译的好处:

1:PreparedStatement比 Statement 更快
使用 PreparedStatement 最重要的一点好处是它拥有更佳的性能优势,SQL语句会预编译在数据库系统中。执行计划同样会被缓存起来,它允许数据库做参数化查询。使用预处理语句比普通的查询更快,因为它做的工作更少(数据库对SQL语句的分析,编译,优化已经在第一次查询前完成了)。

2:PreparedStatement可以防止SQL注入式攻击

SQL 注入攻击:SQL 注入是利用某些系统没有对用户输入的数据进行充分的检查,而在用户输入数据中注入非法的 SQL 语句段或命令,从而利用系统的 SQL 引擎完成恶意行为的做法。

比如:某个网站的登录验证SQL查询代码为:

 sql = "SELECT * FROM users WHERE username = '" + username + "' AND " +"password = '" + password + "'";

恶意填入:

String username = "a' OR PASSWORD = ";
String password = "OR '1' = '1";

那么最终SQL语句变成了:

sql = "SELECT * FROM users WHERE username = '" + a' OR PASSWORD =  + "' AND " +"password = '" +OR '1' = '1 + "'";

因为WHERE条件恒为真,这就相当于执行:

sql = "SELECT * FROM users;"

因此可以达到无账号密码亦可登录网站。

如果恶意用户要是更坏一点,SQL语句变成

sql = "SELECT * FROM users WHERE username = 'any_value' and password = ''; DROP TABLE users"


     使用PreparedStatement的参数化的查询可以阻止大部分的SQL注入。在使用参数化查询的情况下,数据库系统不会将参数的内容视为SQL指令的一部分来处理,而是在数据库完成SQL指令的编译后,才套用参数运行,因此就算参数中含有破坏性的指令,也不会被数据库所运行。因为对于参数化查询来说,查询SQL语句的格式是已经规定好了的,需要查的数据也设置好了,缺的只是具体的那几个数据而已。所以用户能提供的只是数据,而且只能按需提供,无法更进一步做出影响数据库的其他举动来。这样一来,虽然没有登录,但是数据表都被删除了。S

Statement方法的完整代码:(容易SQL注入,不太好,不推荐)

/**
	 * Statement方法容易被修改sql语句造成不必要的麻烦
	 * SQL注入
	 */
	@Test
	public void testSQLInjection() {
		String username = "a' OR PASSWORD = ";
		String password = "OR '1' = '1";
		String sql = "SELECT * FROM users WHERE username = '" 
				+ username + "' AND " +"password = '" + password + "'";
		
		Connection connection = null;
		java.sql.Statement statement = null;
		ResultSet resultSet = null;
		try {
			connection = JDBCTools.getConnection();
			statement = connection.createStatement();
			resultSet = statement.executeQuery(sql);
			
			if(resultSet.next()) {
				System.err.println("登录成功!");
			} else {
				System.out.println("用户名或者密码不正确!");
			}
			
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			JDBCTools.release(resultSet, statement, connection);
		}
	}

PreparedStatement方法的完整代码:(较好推荐用)

/**
	 * PreparedStatement方法就不会产生上面的麻烦
	 */
	@Test
	public void testSQLInjection2() {
		String username = "Tom";
		String password = "123456";
		String sql = "SELECT * FROM users WHERE username = ? AND password = ?";
		
		Connection connection = null;
		java.sql.PreparedStatement preparedstatement = null;
		ResultSet resultSet = null;
		try {
			connection = JDBCTools.getConnection();
			preparedstatement = connection.prepareStatement(sql);
			preparedstatement.setString(1, username);
			preparedstatement.setString(2, password);
			resultSet = preparedstatement.executeQuery();
			
			if(resultSet.next()) {
				System.err.println("登录成功!");
			} else {
				System.out.println("用户名或者密码不正确!");
			}
			
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			JDBCTools.release(resultSet, preparedstatement, connection);
		}
	}

注意执行SQL语句前记得现在数据库里创建一张users表,再将数据预先录入,不然上面无法验证:

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

金州饿霸

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值