MySQL Day10 JDBC (內含SQL注入問題)

1 數據庫驅動

驅動:聲卡、顯卡、數據庫

我們的程序會通過數據庫驅動來和數據庫打交道!

在這裏插入圖片描述

2 JDBC

SUN公司爲了簡化開發人員的(對數據庫的統一)操作,提供了一個(Java操作數據庫)的規範,俗稱JDBC

這些規範的實現由具體的廠商去做~

對於開發人員來說,我們只需要掌握JDBC接口的操作即可!

在這裏插入圖片描述

Idea中內置了java.sql 、javax.sql包可供我們使用!

除此之外,還需要下載一個數據庫驅動包:下載地址鏈接

3 第一個JDBC程序

1、首先準備要連接的數據庫(代碼如下)

CREATE DATABASE jdbcStudy CHARACTER SET utf8 COLLATE utf8_general_ci;

USE jdbcStudy;

CREATE TABLE `users`(
id INT PRIMARY KEY,
NAME VARCHAR(40),
PASSWORD VARCHAR(40),
email VARCHAR(60),
birthday DATE
);

INSERT INTO `users`(id,NAME,PASSWORD,email,birthday)
VALUES(1,'zhansan','123456','[email protected]','1980-12-04'),
(2,'lisi','123456','[email protected]','1981-12-04'),
(3,'wangwu','123456','[email protected]','1979-12-04')

2、導入數據庫驅動

在新建的工程下創建lib目錄,並將下載的數據庫驅動.jar複製到lib目錄下,如圖:

在這裏插入圖片描述

右鍵單擊lib目錄,選擇ADD as Library...導入完成後就和上圖一樣了!

在這裏插入圖片描述

隨後在src目錄下創建自己的類,可以參考我的創建,其中demut是我的賬戶名,可以自行修改:

在這裏插入圖片描述

3、編寫測試代碼

package com.demut.demo;

// 我的第一個JDBC程序

import java.sql.*;

public class jdbcFirstDemo {
    public static void main(String[] args) throws ClassNotFoundException, SQLException {
        // 1.加載驅動
        Class.forName("com.mysql.cj.jdbc.Driver"); // 固定寫法,加載驅動

        // 2.用戶信息和url
        //?useUnicode=true&characterEncoding=utf8&useSSL=true
        //支持中文編碼&設置中文集&設置安全連接
        String url = "jdbc:mysql://localhost:3306/jdbcStudy?useUnicode=true&characterEncoding=utf8&useSSL=true";

        String username = "root";
        String password = "123456";

        // 3.連接數據庫, 使用DriverManager.getConnection()連接。
        Connection connection = DriverManager.getConnection(url,username,password);

        // 4.創建執行SQL的對象 Statement爲執行sql的對象類
        Statement statement = connection.createStatement();

        // 5.執行SQL對象去執行SQL
        String sql = "SELECT * FROM users";

        ResultSet resultSet = statement.executeQuery(sql);//返回的結果集,結果集中封裝了查詢出來的所有結果

        while (resultSet.next()){
            System.out.println("id = " + resultSet.getObject("id"));
            System.out.println("name = " + resultSet.getObject("name"));
            System.out.println("pwd = " + resultSet.getObject("password"));
            System.out.println("email = " + resultSet.getObject("email"));
            System.out.println("birth = " + resultSet.getObject("birthday"));
            System.out.println("==========================");
        }
        // 6.釋放連接
        resultSet.close();
        statement.close();
        connection.close();
    }
}
/*運行結果
id = 1
name = zhansan
password = 123456
email = [email protected]
birthday = 1980-12-04
--------------------------
id = 2
name = lisi
password = 123456
email = [email protected]
birthday = 1981-12-04
--------------------------
id = 3
name = wangwu
password = 123456
email = [email protected]
birthday = 1979-12-04
--------------------------
*/

步驟總結:

1、加載驅動

2、連接數據庫DriverManager

3、獲取執行SQL的對象 Statement

4、獲得返回的結果集 resultSet

5、釋放連接

以上代碼中用到的對象詳解:

DriverManager

//DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());
Class.forName("com.mysql.cj.jdbc.Driver");//加載驅動

Connection connection = DriverManager.getConnection(url, username, password);
//connection代表數據庫
//數據庫設置自動提交
//事務提交
//事務回滾
connection.setAutoCommit();
connection.rollback();
connection.commit();

URL

String url = "jdbc:mysql://localhost:3306/jdbcStudy?useUnicode=true&characterEncoding=utf8&useSSL=true";

//mysql -- 3306
//jdbc:mysql://主機地址:端口號/數據庫名?參數1&參數2&參數3

//oralce -- 1521
//jdbc:oracle:thin:@localhost:1521:sid

Statement 執行SQL的對象

String sql = "SELECT * FROM users"; //編寫SQL

statement.executeQuery(); //查詢操作,返回ResultSet
statement.execute(); // 執行任何SQL
statement.executeUpdate(); // 更新、插入、刪除都是用這個,返回一個受影響的行數

ResultSet 查詢的結果集:封裝了所有的查詢結果

獲得指定的數據類型

resultSet.getObject(); //在不知道列類型時使用
resultSet.getString();
resultSet.getInt();
resultSet.getFloat();
resultSet.getDate();

遍歷指針:

resultSet.beforeFirst(); //指針移動到最前面
resultSet.afterLast(); //移動到最後面
resultSet.next(); //移動到下一個數據
resultSet.previous(); //移動到前一行
resultSet.absolute(row); //移動到指定行

釋放資源!

resultSet.close();
statement.close();
connection.close();

4 JDBC常用語句的封裝與測試!

4.1 封裝

測試了以上程序後,相信心中一定會有So tm What?的感覺~好在上述代碼其實是可以封裝的,以下爲封裝文件說明:

  • db.properties文件:內含driver、url、username、password信息。

    注意:將該文件直接創建在src目錄下!

  • JdbcUtils類:內含加載數據庫驅動獲取瞭解釋放連接方法

目錄展示:

在這裏插入圖片描述

源碼:

db.properties文件:

driver = com.mysql.cj.jdbc.Driver
url = jdbc:mysql://localhost:3306/jdbcStudy?useUnicode=true&characterEncoding=utf8&useSSL=true
username = root
password = 123456

JdbcUtils類:

package com.demut.demo02.utils;

import java.io.InputStream;
import java.sql.*;
import java.util.Properties;

public class JdbcUtils {

    private static String driver = null;
    private static String url = null;
    private static String username = null;
    private static String password = null;
    //加載數據庫驅動
    static {
        try {
            //獲取文件流
            InputStream in = JdbcUtils.class.getClassLoader().getResourceAsStream("db.properties");
            Properties properties = new Properties();
            properties.load(in); //加載流
            driver = properties.getProperty("driver");
            url = properties.getProperty("url");
            username = properties.getProperty("username");
            password = properties.getProperty("password");
            //驅動只需要加載一次
            Class.forName(driver);
        }catch (Exception e){
            e.printStackTrace();
        }

    }

    //獲取連接
    public static Connection getConnection() throws SQLException {
        return DriverManager.getConnection(url, username, password);
    }

    //釋放連接資源
    public static void release(Connection conn, Statement st, ResultSet rs){
        if (rs!=null) {
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (st !=null) {
            try {
                st.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (conn !=null) {
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

}

4.2 測試

以下爲數據庫增刪改查的測試:(增刪改均類似,查請特殊關注!)

TestInsert類:

package com.demut.demo02;

import com.demut.demo02.utils.JdbcUtils;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class TestInsert {
    public static void main(String[] args) {
        Connection conn = null;
        Statement st = null;
        ResultSet rs = null;
        try {
            conn = JdbcUtils.getConnection(); //獲取數據庫連接
            st = conn.createStatement(); //獲得SQL的執行對象
            String sql = "INSERT INTO users(id,`name`,`password`,`email`,`birthday`)\n" +
                    "VALUES (5, 'demut','123456','[email protected]','20200101')";
            int i = st.executeUpdate(sql); //返回受影響的行數
            if (i>0) {
                System.out.println("插入成功!");
            }

        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            JdbcUtils.release(conn,st,rs);
        }
    }
}

TestDelete類:

package com.demut.demo02;

import com.demut.demo02.utils.JdbcUtils;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class TestDelete {
    public static void main(String[] args) {
        Connection conn = null;
        Statement st = null;
        ResultSet rs = null;
        try {
            conn = JdbcUtils.getConnection(); //獲取數據庫連接
            st = conn.createStatement(); //獲得SQL的執行對象
            String sql = "DELETE FROM users WHERE id = 4";
            int i = st.executeUpdate(sql); //返回受影響的行數
            if (i>0) {
                System.out.println("刪除成功!");
            }

        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            JdbcUtils.release(conn,st,rs);
        }
    }
}

TestUpdate類:

package com.demut.demo02;

import com.demut.demo02.utils.JdbcUtils;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class TestUpdate {
    public static void main(String[] args) {
        Connection conn = null;
        Statement st = null;
        ResultSet rs = null;
        try {
            conn = JdbcUtils.getConnection(); //獲取數據庫連接
            st = conn.createStatement(); //獲得SQL的執行對象
            String sql = "UPDATE users SET `name` = 'DEMUT',`email` = '[email protected]' WHERE id = 5";
            int i = st.executeUpdate(sql); //返回受影響的行數
            if (i>0) {
                System.out.println("更新成功!");
            }

        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            JdbcUtils.release(conn,st,rs);
        }
    }
}

TestSelect類:

package com.demut.demo02;

import com.demut.demo02.utils.JdbcUtils;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class TestSelect {
    public static void main(String[] args) {
        Connection conn = null;
        Statement st = null;
        ResultSet rs = null;
        try {
            conn = JdbcUtils.getConnection();
            st = conn.createStatement();

            //SQL
            String sql = "SELECT * FROM users";
            rs = st.executeQuery(sql);//查詢完畢返回一個結果集
            while (rs.next()){
                System.out.println(rs.getString("name"));
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            JdbcUtils.release(conn, st, rs);
        }
    }
}

5 SQL 注入問題

sql存在漏洞,會被攻擊導致數據泄露! SQL會被拼接(由於OR的存在)

此處使用案例說明:

編寫一個login方法如下:

    //登陸業務
    public static void login(String username, String password){
        Connection conn = null;
        Statement st = null;
        ResultSet rs = null;
        try {
            conn = JdbcUtils.getConnection();
            st = conn.createStatement();
            String sql = "SELECT * FROM users WHERE `name` = '"+username+"' AND `password` = '"+password+"'";
            rs = st.executeQuery(sql);
            while (rs.next()){
                System.out.println(rs.getString("name"));
                System.out.println(rs.getString("password"));
            }

        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            JdbcUtils.release(conn, st, rs);
        }
    }

首先使用數據庫內已知的一條數據測試:

    public static void main(String[] args) {
        login("DEMUT","123456");        
    }

/*結果:
DEMUT
123456
*/

測試成功!語句被成功輸出!

現在我們對SQL進行測試,盜取數據庫中用戶數據~

    public static void main(String[] args) {
        //login("DEMUT","123456");
        login("' or '1=1","' or '1=1"); //此處半個單引號是爲了與sql語句拼接!
    }

/*結果:
zhansan
123456
lisi
123456
wangwu
123456
DEMUT
123456
*/

SQL注入說明:由於我們輸入的是賬戶名與密碼名均爲‘ ’ or '1==1'語句,所以在執行SQL語句時,Statement默認輸入的值全爲true,則將數據庫中所有的用戶信息查詢了出來,造成信息泄露。SQL注入已經危害了很多網站,幾乎所有數據庫都會存在注入問題,爲了規避這種現象,我們今後使用PreparedStatement對象操作!

6 PreparedStatement對象

PreparedStatement可以防止SQL注入,效率更高!

與Statement不同的是,PreparedStatement採用了預編譯,且使用?佔位符代替參數,方便輸入且更加安全!

使用步驟:

  1. 編寫SQL語句
  2. 預編譯
  3. 設置參數
  4. 執行

插入語句測試:

package com.demut.demo03;

import com.demut.demo02.utils.JdbcUtils;

import java.sql.*;
import java.util.Date;

public class TestInsert {
    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement pst = null;
        ResultSet rs = null;
        try {
            conn = JdbcUtils.getConnection();
            //區別
            //使用?佔位符代替參數
            String sql = "INSERT INTO users(id,`name`,`password`,`email`,`birthday`) VALUES (?,?,?,?,?)";
            pst = conn.prepareStatement(sql); //預編譯SQL,先寫sql,不執行

            //手動給參數賦值:
            pst.setInt(1,4); //此處的1表示第一個問好
            pst.setString(2,"Jever");
            pst.setString(3,"111111");
            pst.setString(4,"[email protected]");
            //注意: sql.Date 數據庫     java.sql.Date()
            //      util.Date  Java     new Date().getTime() 獲得時間戳
            pst.setDate(5,new java.sql.Date(new Date().getTime()));

            int i = pst.executeUpdate();
            if (i > 0) {
                System.out.println("插入成功!");
            }

        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            JdbcUtils.release(conn,pst,rs);
        }
    }
}
/*結果:
插入成功!
*/

刪除語句測試:

package com.demut.demo03;

import com.demut.demo02.utils.JdbcUtils;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Date;

public class TestDelete {
    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement pst = null;
        ResultSet rs = null;
        try {
            conn = JdbcUtils.getConnection();
            //區別
            //使用?佔位符代替參數
            String sql = "DELETE FROM users WHERE id=?";
            pst = conn.prepareStatement(sql); //預編譯SQL,先寫sql,不執行

            //手動給參數賦值:
            pst.setInt(1,5); //此處的1表示第一個問好

            int i = pst.executeUpdate();
            if (i > 0) {
                System.out.println("刪除成功!");
            }

        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            JdbcUtils.release(conn,pst,rs);
        }
    }
}
/*結果:
刪除成功!
*/

更新語句測試:

package com.demut.demo03;

import com.demut.demo02.utils.JdbcUtils;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Date;

public class TestUpdate {
    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement pst = null;
        ResultSet rs = null;
        try {
            conn = JdbcUtils.getConnection();
            //區別
            //使用?佔位符代替參數
            String sql = "update users set `name` = ? where id=?;";
            pst = conn.prepareStatement(sql); //預編譯SQL,先寫sql,不執行

            //手動給參數賦值:
            pst.setString(1,"LiHua");
            pst.setInt(2,1);

            int i = pst.executeUpdate();
            if (i > 0) {
                System.out.println("更新成功!");
            }

        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            JdbcUtils.release(conn,pst,rs);
        }
    }
}
/*結果:
更新成功!
*/

查詢語句測試:

package com.demut.demo03;

import com.demut.demo02.utils.JdbcUtils;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class TestSelect {
    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement pst = null;
        ResultSet rs = null;
        try {
            conn = JdbcUtils.getConnection();
            String sql = "SELECT * FROM users WHERE id = ?"; //編寫SQL
            pst = conn.prepareStatement(sql); //預編譯
            pst.setInt(1,1); //傳遞參數
            rs = pst.executeQuery(); //執行
            if (rs.next()) {
                System.out.println(rs.getString("name"));
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            JdbcUtils.release(conn, pst, rs);
        }
    }
}
/*結果
LiHua
*/

我們再來測試一下SQL注入問題:

本文第5部分中分別使用已有的數據與注入數據進行了測試,此處同上述情況,由於使用的是PreparedStatement對象,語句略有變化;同時在main函數內,兩種測試情況筆者均已註釋,據情況解註釋即可!

源碼:

package com.demut.demo03;

import com.demut.demo02.utils.JdbcUtils;

import java.sql.*;

public class SQL注入 {
    public static void main(String[] args) {
        //login("LiHua","123456");
        //login("' or '1=1","' or '1=1"); //此處半個單引號是爲了與sql語句拼接!
    }

    //登陸業務
    public static void login(String username, String password){
        Connection conn = null;
        PreparedStatement st = null;
        ResultSet rs = null;
        try {
            conn = JdbcUtils.getConnection();
            String sql = "SELECT * FROM users WHERE `name`=? AND `password`=?";

            st = conn.prepareStatement(sql);
            st.setString(1,username);
            st.setString(2,password);
            rs = st.executeQuery(); //查詢完畢,返回結果集
            while (rs.next()){
                System.out.println(rs.getString("name"));
                System.out.println(rs.getString("password"));
            }

        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            JdbcUtils.release(conn, st, rs);
        }
    }
}
/*測試結果:
情形一:(使用LiHua,123456)
LiHua
123456

情形二:(使用注入語句)
(無輸出!)
*/

說明:PreparedStatement防止SQL注入的本質是:其把傳遞進來的參數當做字符,如果其中存在轉義字符,比如 ' 會被直接轉義

寫在最後

Be wise in the way you act toward outsiders; make the most of every opportunity. Let your conversation be always full of grace, seasoned with salt, so that you may know how to answer everyone. (Colossians 4)

To Demut and Dottie!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章