『JavaWeb』JDBC編程

本篇博客主要介紹JDBC的簡單使用。

什麼是JDBC


JDBC,即Java DataBase Connectivity,Java數據庫連接。是一種用於執行SQL語句的Java API,它是Java中的數據庫連接規範。這個API由java.sql.*javax.sql.*包中的一些類和接口組成,它爲Java開發人員操作數據庫提供了一個標準的API,可以爲多種關係型數據庫提供統一訪問

數據庫編程必備條件


  • 編程語言:如Java、C++、Python等;
  • 數據庫:如MySQL、Oracle、SQL Server等;
  • 數據庫驅動包不同的數據庫,對應不同的編程語言提供了不同的數據庫驅動包,如:MySQL提供了Java的驅動包mysql-connector-java,如果需要使用Java操作MySQL則需要該驅動包。同樣的,如果要操作Oracle數據庫則需要Oracle數據庫驅動包。

JDBC工作原理


JDBC爲多種數據庫提供了統一訪問方式,作爲特定廠商數據庫訪問API的一種高級抽象,它主要包含一些通用的接口類。
JDBC訪問數據庫層次結構
在這裏插入圖片描述

JDBC優勢


  • Java語言訪問數據庫操作完全面向抽象接口編程
  • 開發數據庫應用不用限定在特定數據庫廠商的API
  • 程序的可移植性大大增強

JDBC的簡單使用


首先,我們準備數據庫驅動包,並添加到項目的依賴中
MySQL數據庫驅動包
項目中創建文件夾lib,並將依賴包mysql-connector-java-5.1.47.jar複製到lib中右鍵項目選擇Open Modules Settings
在這裏插入圖片描述
點擊Dependencies,然後點擊右邊的加號,選擇JARs or directiories…
在這裏插入圖片描述
選擇前面拷貝到lib中的.jar文件,然後點擊Apply,最後點擊OK
在這裏插入圖片描述
然後,我們建立一個數據庫連接

// 加載JDBC驅動程序:反射,這樣調用初始化com.mysql.jdbc.Driver類
// 即將該類加載到JVM方法區,並執行該類的靜態方法塊、靜態屬性
Class.forName("com.mysql.jdbc.Driver");

// 創建數據庫連接
// MySQL數據庫連接的URL參數格式如下:
// jbdc:mysql://服務器地址:端口號/數據庫名?參數名=參數值
Connection connection = DriverManager.getConnection(url, user, password);

然後,我們創建操作命令

Statement statement = connection.createStatement();

然後,我們執行SQL語句

ResultSet resultSet = statement.executeQuery(
	"select id, name from student"
);

最後,我們來處理結果集

while (resultSet.next()) {
    int id = resultSet.getInt("id");
    String name = resultSet.getString("name");

    System.out.printf("id: %d, name: %s\n", id, name);
}

還有,不要忘了釋放資源

// 關閉結果集
if (resultSet != null) {
	try {
		resultSet.close();
	} catch (SQLException e) {
		e.printStackTrace();
	}
}

// 關閉命令
if (statement != null) {
	try {
		statement.close();
	} catch (SQLExecption e) {
		e.printStackTrace();
	}
}

// 關閉連接命令
if (connection != null) {
	try {
		connection.close();
	} catch (SQLExecption e) {
		e.printStackTrace();
	}
}

完整代碼如下

import java.sql.*;

public class Test {
    private static final String URL = "jdbc:mysql://localhost:3306/test?useSSL=false";
    private static final String USER_NAME = "root";
    private static final String PASSWORD = "1";

    public static void main(String[] args) {

        Connection connection = null;
        Statement statement = null;
        ResultSet resultSet = null;

        try {
            try {
                Class.forName("com.mysql.jdbc.Driver");

                connection = DriverManager.getConnection(URL, USER_NAME, PASSWORD);
                statement = connection.createStatement();

                String sql = "select id, name from student";
                resultSet = statement.executeQuery(sql);
                while (resultSet.next()) {
                    int id = resultSet.getInt("id");
                    String name = resultSet.getString("name");

                    System.out.printf("id: %d, name: %s\n", id, name);
                }
            } finally {
                if (resultSet != null)
                    resultSet.close();
                if (statement != null)
                    statement.close();
                if (connection != null)
                    connection.close();
            }
        } catch (ClassNotFoundException | SQLException e) {
            e.printStackTrace();
        }
    }
}

在這裏插入圖片描述

JDBC使用步驟總結如下


  • 創建數據庫連接connection
  • 創建操作命令Statement
  • 使用操作命令來執行SQL
  • 處理結果集ResultSet
  • 釋放資源

兩種創建數據庫連接的方式


通過DriverManager(驅動管理類)的靜態方法獲取


我們直接來看代碼

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class DBUtil {
    private static final String URL = "jdbc:mysql://localhost:3306/test?useSSL=false";
    private static final String USER_NAME = "root";
    private static final String PASSWORD = "1";

    public static Connection getConnection() {
        Connection connection = null;

        try {
            Class.forName("com.mysql.jdbc.Driver");
            connection = DriverManager.getConnection(URL, USER_NAME, PASSWORD);
            return connection;
        } catch (ClassNotFoundException | SQLException e) {
            e.printStackTrace();
        }

        throw new RuntimeException("數據庫連接失敗");
    }
}
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class Test {
    public static void main(String[] args) {
        Connection connection = null;
        Statement statement = null;
        ResultSet resultSet = null;

        try {
            try {
                connection = DBUtil.getConnection();
                statement = connection.createStatement();

                String sql = "select id, name from student";

                resultSet = statement.executeQuery(sql);
                while (resultSet.next()) {
                    int id = resultSet.getInt("id");
                    String name = resultSet.getString("name");

                    System.out.printf("id: %d, name: %s\n", id, name);
                }
            } finally {
                if (resultSet != null)
                    resultSet.close();
                if (statement != null)
                    statement.close();
                if (connection != null)
                    connection.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

在這裏插入圖片描述

通過DataSource對象獲取


通過DataSource獲取,要求是同一個DataSource對象,所以這裏,我們有兩種實現方式

使用靜態屬性的方式實現


import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

public class DBUtil {
    private static final String URL = "jdbc:mysql://localhost:3306/test?useSSL=false";
    private static final String USER_NAME = "root";
    private static final String PASSWORD = "1";

    private static DataSource dataSource = new MysqlDataSource();
    static {
        ((MysqlDataSource)dataSource).setUrl(URL);
        ((MysqlDataSource)dataSource).setUser(USER_NAME);
        ((MysqlDataSource)dataSource).setPassword(PASSWORD);
    }

    public static Connection getConnection() {
        try {
            return dataSource.getConnection();
        } catch (SQLException e) {
            e.printStackTrace();
        }

        throw new RuntimeException("數據庫連接失敗");
    }
}
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class Test {
    public static void main(String[] args) {
        Connection connection = null;
        Statement statement = null;
        ResultSet resultSet = null;

        try {
            try {
                connection = DBUtil.getConnection();
                statement = connection.createStatement();

                String sql = "select id, name from student";

                resultSet = statement.executeQuery(sql);
                while (resultSet.next()) {
                    int id = resultSet.getInt("id");
                    String name = resultSet.getString("name");

                    System.out.printf("id: %d, name: %s\n", id, name);
                }
            } finally {
                if (resultSet != null)
                    resultSet.close();
                if (statement != null)
                    statement.close();
                if (connection != null)
                    connection.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

在這裏插入圖片描述

使用單例模式實現


import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;

import javax.sql.DataSource;

public class DBUtil {
    private static final String URL = "jdbc:mysql://localhost:3306/java12?useSSL=false";
    private static final String USER_NAME = "root";
    private static final String PASSWORD = "1";

    private DBUtil() {}

    private static DataSource dataSource = null;

    public static DataSource getDataSource() {
        if (dataSource == null) {
            dataSource = new MysqlDataSource();
            ((MysqlDataSource)dataSource).setUrl(URL);
            ((MysqlDataSource)dataSource).setUser(USER_NAME);
            ((MysqlDataSource)dataSource).setPassword(PASSWORD);
        }
        return dataSource;
    }
}
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class Test {
    public static void main(String[] args) {
        Connection connection = null;
        Statement statement = null;
        ResultSet resultSet = null;

        try {
            try {
                DataSource dataSource = DBUtil.getDataSource();
                connection = dataSource.getConnection();
                statement = connection.createStatement();

                String sql = "select id, name from student";

                resultSet = statement.executeQuery(sql);
                while (resultSet.next()) {
                    int id = resultSet.getInt("id");
                    String name = resultSet.getString("name");

                    System.out.printf("id: %d, name: %s\n", id, name);
                }
            } finally {
                if (resultSet != null)
                    resultSet.close();
                if (statement != null)
                    statement.close();
                if (connection != null)
                    connection.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

在這裏插入圖片描述

兩種方式的區別


  • 使用DriverManager類來獲取的Connection連接,是無法重複利用的,每次使用完以後釋放資源時,通過connection.close()都是關閉物理連接
  • DataSource提供連接池的支持。連接池在初始化時將創建一定量的數據庫連接,這些連接是可以重複利用的,每次使用完數據庫連接,釋放資源調用connection.close()都是將Connection連接對象回收

DBUtil類


我們設計一個DBUtil類用來獲取數據庫連接。包含上述幾種方式

import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;

import javax.sql.DataSource;
import java.sql.*;

public class DBUtil {
    private static final String URL = "jdbc:mysql://localhost:3306/test?useSSL=false";
    private static final String USER_NAME = "root";
    private static final String PASSWORD = "1";

    public static Connection getConnection1() {
        Connection connection = null;

        try {
            Class.forName("com.mysql.jdbc.Driver");
            connection = DriverManager.getConnection(URL, USER_NAME, PASSWORD);
        } catch (ClassNotFoundException | SQLException e) {
            e.printStackTrace();
        }

        throw new RuntimeException("數據庫連接失敗");
    }

    private static DataSource dataSource = new MysqlDataSource();
    static {
        ((MysqlDataSource)dataSource).setUrl(URL);
        ((MysqlDataSource)dataSource).setUser(USER_NAME);
        ((MysqlDataSource)dataSource).setPassword(PASSWORD);
    }
    public static Connection getConnection2() {
        try {
            return dataSource.getConnection();
        } catch (SQLException e) {
            e.printStackTrace();
        }

        throw new RuntimeException("數據庫連接失敗");
    }

    private DBUtil() {}
    private static DataSource dataSource2 = null;
    public static DataSource getDataSource() {
        if (dataSource2 == null) {
            dataSource2 = new MysqlDataSource();
            ((MysqlDataSource)dataSource2).setUrl(URL);
            ((MysqlDataSource)dataSource2).setUser(USER_NAME);
            ((MysqlDataSource)dataSource2).setPassword(PASSWORD);
        }

        return dataSource2;
    }

    public static void Close(Connection connection, Statement statement, ResultSet resultSet) {
        try {
            if (resultSet != null) {
                resultSet.close();
            }
            if (statement != null) {
                statement.close();
            }
            if (connection != null) {
                connection.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
            throw new RuntimeException("數據庫資源釋放失敗");
        }
    }
}

可以看到,我們在DBUtil類中添加了一個Close方法,該方法用來關閉數據庫的一些資源在數據庫不再使用的時候
因爲前面的關閉是有問題的,假如resultSet.close()操作拋了異常,那麼後續的兩個關閉操作將不會被執行,此時就會造成資源泄露。因此我們在DBUtil中封裝一個方法專門用來釋放資源,如果中間有資源釋放失敗,就向上層拋一個運行時異常來提醒調用者資源釋放失敗

JDBC常用的接口和類


Statement對象


Statement對象主要是將SQL語句發送到數據庫中JDBC API中主要提供了三種Statement對象

  • Statement用於執行不帶參數的簡單SQL語句
  • PreparedStatement用於執行帶或者不帶參數的SQL語句;SQL語句會預編譯在數據庫系統;執行速度快於Statement對象
  • CallableStatement用於執行數據庫存儲過程的調用

SQL注入問題


下面我們來封裝一個類,用戶可以通過傳入一個姓名然後就可以獲得一個指定姓名的信息

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

public class Query {
    public static void main(String[] args) {
        query("範莊元");
    }

    public static void query(String stuName) {
        Connection connection = null;
        Statement statement = null;
        ResultSet resultSet = null;

        try {
            try {
                connection = DBUtil.getConnection2();
                statement = connection.createStatement();
                String sql = "select id, name, chinese, math, english " +
                        "from exam_result where name='" + stuName + "'";
                resultSet = statement.executeQuery(sql);

                while (resultSet.next()) {
                    int id = resultSet.getInt("id");
                    String name = resultSet.getString("name");
                    BigDecimal chinese = resultSet.getBigDecimal("chinese");
                    BigDecimal math = resultSet.getBigDecimal("math");
                    BigDecimal english = resultSet.getBigDecimal("english");

                    System.out.printf("(%s)[%s]{%s, %s, %s}\n", id, name, chinese, math, english);
                }
            } finally {
                DBUtil.Close(connection, statement, resultSet);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

在這裏插入圖片描述
從代碼可以看出,藉助query方法我們可以通過傳入一個姓名來獲得該學生的信息,但是這個query方法是有問題的,我們來將代碼修改一下,來觀察一下:

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

public class Query {
    public static void main(String[] args) {
        query("範莊元' or '1' = '1");
    }

    public static void query(String stuName) {
        Connection connection = null;
        Statement statement = null;
        ResultSet resultSet = null;

        try {
            try {
                connection = DBUtil.getConnection2();
                statement = connection.createStatement();
                String sql = "select id, name, chinese, math, english " +
                        "from exam_result where name='" + stuName + "'";
                resultSet = statement.executeQuery(sql);

                while (resultSet.next()) {
                    int id = resultSet.getInt("id");
                    String name = resultSet.getString("name");
                    BigDecimal chinese = resultSet.getBigDecimal("chinese");
                    BigDecimal math = resultSet.getBigDecimal("math");
                    BigDecimal english = resultSet.getBigDecimal("english");

                    System.out.printf("(%s)[%s]{%s, %s, %s}\n", id, name, chinese, math, english);
                }
            } finally {
                DBUtil.Close(connection, statement, resultSet);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

在這裏插入圖片描述
可以看到我們通過傳入"範莊元' or '1' = '1"這麼一個字符串就會導致查詢到表中所有的數據,這種問題我們稱之爲SQL注入

什麼是SQL注入

  • SQL注入是將Web頁面的原URL、表單域或數據包輸入的參數,修改拼接成SQL語句,傳遞給Web服務器,進而傳給數據庫服務器以執行數據庫命令
  • 如果Web應用程序的開發人員對用戶輸入的數據或cookie等內容不進行過濾或驗證(即存在注入點)就直接傳輸給數據庫,就可能導致拼接的SQL被執行,獲取數據庫的信息或對數據庫的提權,發生SQL注入攻擊

如何解決上述問題呢?我們可以使用PreparedStatement,下面來看代碼:

import java.math.BigDecimal;
import java.sql.*;

public class Query {
    public static void main(String[] args) {
        query("範莊元' or '1'='1");
    }

    public static void query(String stuName) {
        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet resultSet = null;

        try {
            try {
                connection = DBUtil.getConnection2();
                String sql = "select id, name, chinese, math, english " +
                        "from exam_result where name=?";
                // 此處會講SQL語句送往數據庫進行預編譯
                statement = connection.prepareStatement(sql);
                statement.setString(1, stuName);

                resultSet = statement.executeQuery();

                while (resultSet.next()) {
                    int id = resultSet.getInt("id");
                    String name = resultSet.getString("name");
                    BigDecimal chinese = resultSet.getBigDecimal("chinese");
                    BigDecimal math = resultSet.getBigDecimal("math");
                    BigDecimal english = resultSet.getBigDecimal("english");

                    System.out.printf("(%s)[%s]{%s, %s, %s}\n", id, name, chinese, math, english);
                }
            } finally {
                DBUtil.Close(connection, statement, resultSet);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

在這裏插入圖片描述
此時就解決了上述的問題

ResultSet對象


ResultSet對象被稱爲結加粗樣式果集,它代表符合SQL語句條件的所有行,並且它通過一套getXXX方法提供了對這些行中數據的訪問。可以將它理解成List<Map<String, Object>>
ResultSet裏的數據一行一行排列,每行有多個字段,並且有一個記錄指針,指針所指的數據行叫做當前數據行,我們只能來操作當前的數據行。我們如果想要取得某一條記錄,就要使用ResultSet的next()方法,如果我們想要得到ResultSet裏的所有記錄,就應該使用while循環

兩種執行SQL的方法


  • executeQuery()方法執行後返回結果集通常用於select語句
  • executeUpdate()方法返回值是一個整數指示受影響的行數,通常用於update、insert、delete語句

下面我們封裝三個類來方便實現insert、update、delete操作

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

public class Insert {
    public static void main(String[] args) {
        insert(9, "張欣", 99, 99, 99);
    }

    public static void insert(
        int stdId, String stuName,
        int stuChinese, int stuMath, int stuEnglish
    ) {
        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet resultSet = null;

        try {
            try {
                connection = DBUtil.getConnection2();
                String sql = "insert into exam_result(id, name, chinese, math, english) " +
                        "values (?, ?, ?, ?, ?)";
                statement = connection.prepareStatement(sql);
                statement.setInt(1, stdId);
                statement.setString(2, stuName);
                statement.setInt(3, stuChinese);
                statement.setInt(4, stuMath);
                statement.setInt(5, stuEnglish);

                int ret = statement.executeUpdate();

                if (ret == 1) {
                    System.out.printf("Insert Successful, %d row affected!", ret);
                }

            } finally {
                DBUtil.Close(connection, statement, resultSet);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

在這裏插入圖片描述
在這裏插入圖片描述

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

public class Update {
    public static void main(String[] args) {
        update("張蔚瀾", 66, 66, 66);
    }

    public static void update(
            String stuName, int stuChinese, int stuMath, int stuEnglish
    ) {
        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet resultSet = null;

        try {
            try {
                connection = DBUtil.getConnection2();
                String sql = "update exam_result " +
                        "set chinese = ?, math = ?, english = ? " +
                        "where name = ?";
                statement = connection.prepareStatement(sql);
                statement.setInt(1, stuChinese);
                statement.setInt(2, stuMath);
                statement.setInt(3, stuEnglish);
                statement.setString(4, stuName);

                int ret = statement.executeUpdate();

                if (ret == 1) {
                    System.out.printf("Update Successful, %d row affected!", ret);
                }

            } finally {
                DBUtil.Close(connection, statement, resultSet);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

在這裏插入圖片描述
在這裏插入圖片描述

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

public class Delete {
    public static void main(String[] args) {
        delete("張欣");
    }

    public static void delete(String stuName) {
        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet resultSet = null;

        try {
            try {
                connection = DBUtil.getConnection2();
                String sql = "delete from exam_result where name = ?";
                statement = connection.prepareStatement(sql);
                statement.setString(1, stuName);

                int ret = statement.executeUpdate();

                if (ret == 1) {
                    System.out.printf("Delete Successful, %d row affected!", ret);
                }

            } finally {
                DBUtil.Close(connection, statement, resultSet);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

在這裏插入圖片描述
在這裏插入圖片描述

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