1. 程式人生 > >執行sql語句為什麽?用PreparedStatement要比Statement好用

執行sql語句為什麽?用PreparedStatement要比Statement好用

運行 可維護性 一個 nbsp 連接池 conn 應用 body stat

PreparedStatement

public interface PreparedStatement extends Statement;可以看到PreparedStatement是Statement的子接口,我們在執行查詢或者更新數據表數據的時候,拼寫SQL語句是一個很費力並且容易出錯的事情,PreparedStatement可以簡化這樣的一個過程.

PreParedStatement
1).why?我們為什麽要使用它
使用Statement需要進行拼寫SQl語句,辛苦並且容易出錯,之前使用Statement的SQL語句的形式是這樣的

String sql = "insert into examstudent" + " values("
+ student.getFlowId() + "," + student.getType() + ",‘"
+ student.getIdCard() + "‘,‘" + student.getExamCard() + "‘,‘"
+ student.getStudentName() + "‘,‘" + student.getLocation()
+ "‘," + student.getGrade() + ")";

使用PreparedStatement:是Statement的子接口,可以傳入帶占位符的SQL語句,提供了補充占位符變量的方法

PreparedStatement ps=conn.preparedStatement(sql);

可以看到將sql作為參數傳入了,就不需要我們在費力拼寫了。

2)變成了這樣的形式

String sql="insert into examstudent values(?,?,?,?,?,?,?)";

可以調用PreparedStatement的setXxx(int index,Object val)設置占位符的值,其中index的值從1開始

執行SQl語句:excuteQuery()或者excuteUpdate()就可以完成查詢或者數據的更新.【註意】:此時函數的參數位置不需要傳入SQL語句,註意同使用Statement的update函數的差別

具體代碼實現:

技術分享圖片
 1 @Test
 2     public void testPreparedStatement() {
 3         Connection connection = null;
 4         PreparedStatement preparedStatement = null;
 5         try {
 6             // 連接數據庫
 7             connection = JDBCTools.getConnection();
 8             // 使用占位符的SQl語句
 9             String sql = "insert into customers(name,email,birth)"
10                     + "values(?,?,?)";
11             // 使用preparedStatement的setXxx方法設置每一個位置上的值
12             preparedStatement = connection.prepareStatement(sql);
13             // 設置name字段
14             preparedStatement.setString(1, "ATGUIGU");
15             // 設置email字段
16             preparedStatement.setString(2, "[email protected]");
17             // 設置birth字段
18             preparedStatement.setDate(3,
19                     new Date(new java.util.Date().getTime()));
20             // 執行更新操作
21             preparedStatement.executeUpdate();
22         } catch (Exception e) {
23             e.printStackTrace();
24         } finally {
25             // 釋放資源
26             JDBCTools.release(null, preparedStatement, connection);
27         }
28     }
技術分享圖片

使用PreparedStatement執行SQl(更新操作:插入、刪除、更新,但不包括select查詢操作),JDBCTools中的通用函數update更改成下面的形式:這裏使用了可變參數,而不是使用數組

技術分享圖片
 1     public static void update(String sql,Object ...args){
 2         /**
 3          * 執行SQL語句,使用PreparedStatement
 4          */
 5         Connection connection=null;
 6         PreparedStatement preparedStatement=null;
 7         try {
 8             connection=JDBCTools.getConnection();
 9             preparedStatement=connection.prepareStatement(sql);
10             for(int i=0;i<args.length;i++){
11                 preparedStatement.setObject(i+1, args[i]);
12             }
13             preparedStatement.executeUpdate();
14         } catch (Exception e) {
15             e.printStackTrace();
16         }finally{
17             JDBCTools.release(null, preparedStatement, connection);
18         }
19     }
技術分享圖片

使用PreparedStatement的好處:

1).提高代碼的可讀性和可維護性;

2).最大程度的提高性能:JDBC驅動的最佳化是基於使用的是什麽功能. 選擇PreparedStatement還是Statement取決於你要怎麽使用它們. 對於只執行一次的SQL語句選擇Statement是最好的. 相反, 如果SQL語句被多次執行選用PreparedStatement是最好的.PreparedStatement的第一次執行消耗是很高的. 它的性能體現在後面的重復執行(緩存的作用). 例如, 假設我使用Employee ID, 使用prepared的方式來執行一個針對Employee表的查詢. JDBC驅動會發送一個網絡請求到數據解析和優化這個查詢. 而執行時會產生另一個網絡請求. 在JDBC驅動中,減少網絡通訊是最終的目的. 如果我的程序在運行期間只需要一次請求, 那麽就使用Statement. 對於Statement, 同一個查詢只會產生一次網絡到數據庫的通訊.當使用PreparedStatement池時, 如果一個查詢很特殊, 並且不太會再次執行到, 那麽可以使用Statement. 如果一個查詢很少會被執行,但連接池中的Statement池可能被再次執行, 那麽請使用PreparedStatement. 在不是Statement池的同樣情況下, 請使用Statement.

3).可以防止SQL註入

SQL註入指的是通過構建特殊的輸入作為參數傳入Web應用程序,而這些輸入大都是SQL語法裏的一些組合,通過執行SQL語句進而執行攻擊者所要的操作,其主要原因是程序沒有細致地過濾用戶輸入的數據,致使非法數據侵入系統。

比如我們新建一個數據表users,表中有兩個字段username和password;

技術分享圖片

我們在圖形化界面SQLyog的sql語句的查詢界面輸入這樣的查詢語句:select * from users where username=‘a‘ or password=‘and password=‘ or ‘1‘=‘1‘;

執行該語句,會得到我們表中的數據:

技術分享圖片

我們可以分析一下這條語句:where的後面,通過多個字段的組合作為查詢過濾的條件。

字段一:username=‘a‘

字段二:password=‘and password=‘

字段三:‘1‘=‘1‘

因為用邏輯連接符OR來連接的三個字段,只要有一個為真就可以將查詢工作完成.

下面我們看下具體的代碼實現:

技術分享圖片
 1     @Test
 2     public void testSQLinjection() {
 3         String username = "a‘ or password =";
 4         String password = " or ‘1‘=‘1";
 5         String sql = "select * from users where username=‘" + username
 6                 + "‘ AND " + "password=‘" + password + "‘";
 7         System.out.println(sql);
 8         Connection connection = null;
 9         Statement statement = null;
10         ResultSet resultSet = null;
11         try {
12             connection = getConnection();
13             statement = connection.createStatement();
14             resultSet = statement.executeQuery(sql);
15             if (resultSet.next()) {
16                 System.out.println("登陸成功");
17             } else {
18                 System.out.println("不匹配");
19             }
20         } catch (Exception e) {
21             e.printStackTrace();
22         } finally {
23             JDBCTools.release(resultSet, statement, connection);
24         }
25     }
技術分享圖片

運行結果:

select * from users where username=‘a‘ or password =‘ AND password=‘ or ‘1‘=‘1‘
登陸成功

可以看到我們的SQl語句中都沒有明確我們要查的字段的名,但是還是獲取了查詢的結果(SQL語句太能混了)

於是,我們用了PreparedStatement就可以解決SQL註入的問題。

技術分享圖片
 1     @Test
 2     public void testSQLinjection2() {
 3         String username = "a‘ or password =";
 4         String password = " or ‘1‘=‘1";
 5         String sql = "select * from users where username=?" + " and password=?";
 6         System.out.println(sql);
 7         Connection connection = null;
 8         PreparedStatement preparedStatement = null;
 9         ResultSet resultSet = null;
10         try {
11             connection = getConnection();
12             preparedStatement = connection.prepareStatement(sql);
13             preparedStatement.setString(1, username);
14             preparedStatement.setString(2, password);
15             resultSet = preparedStatement.executeQuery();
16             if (resultSet.next()) {
17                 System.out.println("登陸成功");
18             } else {
19                 System.out.println("不匹配");
20             }
21         } catch (Exception e) {
22             e.printStackTrace();
23         } finally {
24             JDBCTools.release(resultSet, preparedStatement, connection);
25         }
26     }
技術分享圖片

執行結果:

select * from users where username=? and password=?
不匹配

可以看到:再次使用偽裝後的SQL語句已經不能獲取我們數據表中的信息,我們這裏在sql語句中使用了占位符。因此使用PreparedStatement可以結解決這裏的SQL註入的問題。

<%@ page language="java" import="java.sql.*" pageEncoding="UTF-8"%>
<html>
  <head>  
    <title>插入界面</title>
  </head>
  
  <body>
  <%
     String driverName = "com.microsoft.sqlserver.jdbc.SQLServerDriver"; 
    //加載JDBC驅動
    String dbURL = "jdbc:sqlserver://localhost:1433; DatabaseName=JSPTest"; 
    //連接服務器和數據庫
    String userName = "sa"; //默認用戶名
    String userPwd = "123456"; //密碼
       Class.forName(driverName);
       Connection Conn = DriverManager.getConnection(dbURL, userName, userPwd);
       System.out.println("Connection Successful!"); 
       String sql="Insert into regist(ID,password) values(?,?)";
       PreparedStatement pstmt=Conn.prepareStatement(sql);
       pstmt.setString(1, "201504");
       pstmt.setInt(2, 111);
       int n=pstmt.executeUpdate();
       if(n==1){%>數據插入成功!<br><%}  
       else{%>數據插入失敗!<br><%}
   %>
  </body>
</html>

執行sql語句為什麽?用PreparedStatement要比Statement好用