JDBC操作全攻略

JDBC操作全攻略

JDBC簡介

JDBC(Java Database Connectivity),也稱為Java數(shù)據(jù)庫連接,是Java用于連接數(shù)據(jù)庫的接口規(guī)范,需要注意的是,JDBC并沒有提供的實(shí)現(xiàn),具體的實(shí)現(xiàn)是由數(shù)據(jù)庫提供商,比如MySQL,Oracle等負(fù)責(zé)提供,JDBC提供了一整套完整的連接規(guī)范,包括了Driver,DriverManagerConnection,Statement,PreparedStatement,ResultSet,DatabaseMetaData,ResultSetMetaData,ParameterMetaData等等

連接數(shù)據(jù)庫

在Java程序中,操作數(shù)據(jù)庫時(shí),通常需要幾個(gè)步驟

  1. 導(dǎo)入對應(yīng)的JDBC實(shí)現(xiàn):由于JDBC本身沒有提供對應(yīng)的實(shí)現(xiàn),也不太可能提供對應(yīng)的實(shí)現(xiàn),所以需要導(dǎo)入對應(yīng)數(shù)據(jù)庫供應(yīng)商提供的實(shí)現(xiàn),比如MySQL的實(shí)現(xiàn)
  2. 在程序中手動(dòng)注冊對應(yīng)的數(shù)據(jù)庫驅(qū)動(dòng)
  3. 建立并且打開對應(yīng)的連接
  4. 通過連接獲取一個(gè)Statement對象,用于發(fā)送SQL,并且獲得對應(yīng)的結(jié)果
  5. 從Statement中獲得結(jié)果集對象
  6. 關(guān)閉對應(yīng)的結(jié)果集、Statement以及連接

接下來通過代碼來具體查看如何操作

首先是導(dǎo)入MySQL的實(shí)現(xiàn),對應(yīng)的Maven依賴為


    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.38</version>
    </dependency>

然后建立測試類TestJDBC.java

注冊MySQL驅(qū)動(dòng)


    // 注冊MySQL驅(qū)動(dòng)
    Class.forName("com.mysql.jdbc.Driver");

建立連接


    private static final String SQL_URL = "jdbc:mysql://localhost:3306/spring"; // 數(shù)據(jù)庫連接地址
    private static final String SQL_USER_NAME = "root"; // 賬號
    private static final String SQL_PASSWORD = "huanfeng"; // 密碼

    // 建立連接
    Connection connection = DriverManager.getConnection(SQL_URL, SQL_USER_NAME, SQL_PASSWORD);

    // 獲得Statment對象
    Statement statement = connection.createStatement();

完整的過程如下所示


    private static final String SQL_URL = "jdbc:mysql://localhost:3306/spring";
    private static final String SQL_USER_NAME = "root";
    private static final String SQL_PASSWORD = "huanfeng";

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

    try {

        // 注冊MySQL驅(qū)動(dòng)
        Class.forName("com.mysql.jdbc.Driver");

        // 建立連接
        connection = DriverManager.getConnection(SQL_URL, SQL_USER_NAME, SQL_PASSWORD);

        String sql = "SELECT * FROM user";
        
        // 獲得Statement
        statement = connection.createStatement();
    
        // 獲得ResultSet
        resultSet = statement.executeQuery(sql);

        // 操作ResultSet

    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } catch (SQLException e) {
        e.printStackTrace();
    }finally {
        // 注意關(guān)閉的順序,應(yīng)該從ResultSet再到Statement再到Connection
        // 關(guān)閉ResultSet
        if (resultSet != null){
            try {
                resultSet.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        // 關(guān)閉Statement
        if (statement != null){
            try {
                statement.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        // 關(guān)閉Connection
        if (connection != null){
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

這里一定要注意,資源用完之后一定要關(guān)閉,因?yàn)榻⒁粋€(gè)對應(yīng)的連接是非常消耗資源的,而如果使用完之后不關(guān)閉資源,會(huì)造成非常大的資源浪費(fèi),為了證實(shí)這一點(diǎn),下面進(jìn)行一個(gè)簡單的測試,測試建立一個(gè)Connection所消耗的時(shí)間


 @Test
    public void testConnection() throws ClassNotFoundException, SQLException {

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

        long start = System.currentTimeMillis();
        Connection connection = DriverManager.getConnection(SQL_URL, SQL_USER_NAME, SQL_PASSWORD);
        long end = System.currentTimeMillis();
        connection.close();
        System.out.printf("cost time %d ms\n", (end - start));

    }

對應(yīng)的輸出結(jié)果如下所示


    cost time 360 ms

可以看到,建立一個(gè)Connection連接需要消耗非常多的時(shí)間,原因在于,建立連接的時(shí)候,本質(zhì)上是通過驅(qū)動(dòng)程序與數(shù)據(jù)庫之間建立一個(gè)Socket連接,對于Socket有了解的朋友應(yīng)該知道,建立Socket連接是比較消耗時(shí)間的,而且維持一個(gè)Socket連接也是非常消耗資源的,所以當(dāng)資源使用完畢之后,應(yīng)該關(guān)閉對應(yīng)的資源,當(dāng)然,更好的做法是使用數(shù)據(jù)庫連接池技術(shù),將Connection對象緩存起來,來避免頻繁創(chuàng)建Connection對象。

JDBC實(shí)戰(zhàn)

上面大概了解了JDBC的操作步驟以及基本的注意事項(xiàng),接下來通過一個(gè)例子來使用JDBC操作數(shù)據(jù)庫,加深對JDBC使用的了解

建立連接的過程同上,這里不重復(fù)敘述

插入數(shù)據(jù)

    @Test
    public void testInsert() throws ClassNotFoundException, SQLException {

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

        Connection connection = DriverManager.getConnection(SQL_URL, SQL_USER_NAME, SQL_PASSWORD);

        String sql = "Insert into user(id, name) value(4, 'Tom')";
        Statement statement = connection.createStatement();
        int result = statement.executeUpdate(sql);
        System.out.println(result);
        statement.close();
        connection.close();

    }

上面是最簡單的插入操作,不過一般來說,我們會(huì)插入?yún)?shù)而不是固定的數(shù)值,所以上面的內(nèi)容會(huì)變成


    int id = 4;
    String name = "Tom";
    // 拼接SQL語句
    String sql = "Insert into user(id, name) value("+ id +", '"+ name +"')";

通過拼接SQL語句,然后使用Statement來執(zhí)行語句是解決插入?yún)?shù)的一種方式,但是,通過這種方式進(jìn)行數(shù)據(jù)庫操作會(huì)出現(xiàn)SQL注入的危險(xiǎn),比如說,當(dāng)有人把參數(shù)name的內(nèi)容填寫為 Tom'); DELETE FROM USER; --,那么此時(shí)拼接后的SQL語句就會(huì)變成insert into user(id, name) values(4, 'tom'); delete from user; -- 此時(shí)就非常危險(xiǎn)了,雖然這里是不會(huì)出現(xiàn)問題的,因?yàn)閟tatement一次只能執(zhí)行一條語句,但是如果是查詢后面被加上這些內(nèi)容,那就非常危險(xiǎn)了。為了避免被注入的情況發(fā)生,JDBC提供了另外一種方式,采用預(yù)編譯處理SQL,然后使用PreparedStatement設(shè)置參數(shù)以及執(zhí)行對應(yīng)的SQL語句,如下。


    // 采用占位符來處理
    String sql = "Insert into user(id, name) value(?, ?)";

    PreparedStatement statement = connection.prepareStatement(sql);
    // 設(shè)置對應(yīng)的參數(shù),注意這里是從1開始,不是從0開始
    statement.setInt(1, id);
    statement.setString(2, name);
    // 這里也可以直接使用
    // statement.setObject(1, id);
    // statement.setObject(2, name);
    // 不需要指定對應(yīng)的參數(shù)類型,將其交給JDBC進(jìn)行判斷即可

JDBC是推薦使用PreparedStatement來進(jìn)行SQL處理,而不是Statement來處理,原因在于PreparedStatement提供了預(yù)處理機(jī)制,可以預(yù)防SQL注入的發(fā)送,而且由于是采用預(yù)處理機(jī)制,所以PreparedStatement的執(zhí)行效率要高于Statement

刪除數(shù)據(jù)


    @Test
    public void testDelete() throws ClassNotFoundException, SQLException {
        Class.forName("com.mysql.jdbc.Driver");

        Connection connection = DriverManager.getConnection(SQL_URL, SQL_USER_NAME, SQL_PASSWORD);

        int id = 3;
        String sql = "DELETE FROM  USER where id = ?";

        PreparedStatement statement = connection.prepareStatement(sql);
        statement.setObject(1, id);

        int result = statement.executeUpdate();

        statement.close();
        connection.close();
    }

修改數(shù)據(jù)


    @Test
    public void testUpdate() throws ClassNotFoundException, SQLException {
        Class.forName("com.mysql.jdbc.Driver");

        Connection connection = DriverManager.getConnection(SQL_URL, SQL_USER_NAME, SQL_PASSWORD);

        int id = 2;
        String name = "Jack";
        String sql = "UPDATE USER SET NAME = ? where id = ?";

        PreparedStatement statement = connection.prepareStatement(sql);
        statement.setObject(1, name);
        statement.setObject(2, id);

        int result = statement.executeUpdate();

        statement.close();
        connection.close();
    }

查詢數(shù)據(jù)

談到查詢數(shù)據(jù),這里就有一個(gè)新的對象需要介紹,ResultSet,在JDBC中,查詢的結(jié)果一般都是以行為單位的,可以是單行也可以是多行,JDBC將每一行都封裝成一個(gè)ResultSet對象,對于ResultSet的操作,其實(shí)就是一個(gè)迭代的過程,在獲取的時(shí)候,需要通過 ResultSet.getXX(ID) 或者ResultSet.getXX(CLO_NAME)來獲取對應(yīng)行的數(shù)據(jù),然后通過ResultSet.next()將指針移動(dòng)到下一行。


    @Test
    public void testQuery() throws ClassNotFoundException, SQLException {

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

        Connection connection = DriverManager.getConnection(SQL_URL, SQL_USER_NAME, SQL_PASSWORD);

        int id = 1;
        String sql = "SELECT id, name FROM USER where id = ?";

        PreparedStatement statement = connection.prepareStatement(sql);
        statement.setObject(1, id);

        // 查詢返回的ResultSet
        ResultSet resultSet = statement.executeQuery();

        // 遍歷ResultSet
        while (resultSet.next()){
            // 取出對應(yīng)的數(shù)據(jù)
            int n_id = resultSet.getInt("id");
            String n_name = resultSet.getString("name");
            // 封裝成對應(yīng)的對象
            User user = new User(n_id, n_name);
            System.out.println("user: " + user);
        }
        // 關(guān)閉對應(yīng)的連接
        resultSet.close();
        statement.close();
        connection.close();
    }

大對象處理

所謂的大對象,指的是數(shù)據(jù)庫中定義的CLOB(Character Large Object )、BLOB(Binary Large Object),這兩個(gè)數(shù)據(jù)類型是用于把包含信息量比較大的數(shù)據(jù)存儲(chǔ)在列中,當(dāng)做一個(gè)屬性,其中CLOB主要是用于存放字符類型大對象,比如說一本書的內(nèi)容,BLOB主要用于存放二進(jìn)制類型的大對象,比如說一部小電影。JDBC同樣為操作這兩種數(shù)據(jù)類型提供支持,不過,操作起來跟普通的數(shù)據(jù)類型有所不同,具體如下所示


    @Test
    public void testBLOB() throws ClassNotFoundException, SQLException, IOException, InterruptedException {
        Class.forName("com.mysql.jdbc.Driver");

        Connection connection = DriverManager.getConnection(SQL_URL, SQL_USER_NAME, SQL_PASSWORD);

        // 存入Blob類型的數(shù)據(jù)
        File file = new File("d:/img.jpg");
        int id = 121;
        String name = "jack";
        String sql = "insert into user(id, name, b_data) values(?, ?, ?)";

        PreparedStatement statement = connection.prepareStatement(sql);

        statement.setObject(1, id);
        statement.setObject(2, name);
        // 這里需要注意,類型應(yīng)該為Blob,直接傳一個(gè)輸入流即可
        statement.setBlob(3, new FileInputStream(file));

        int result = statement.executeUpdate();
        System.out.println(result);

        // 讀取Blob類型的數(shù)據(jù)
        sql = "SELECT b_data FROM user WHERE id = ?";
        statement = connection.prepareStatement(sql);
        statement.setObject(1, id);

        ResultSet resultSet = statement.executeQuery();

        if (resultSet.next()){
            // 注意這里,返回的類型是BLob
            Blob b_data = resultSet.getBlob("b_data");
            InputStream inputStream = b_data.getBinaryStream();
            byte[] buf = new byte[inputStream.available()];
            FileOutputStream fileOutputStream = new FileOutputStream(new File("d:/new_img.jpg"));
            inputStream.read(buf);
            fileOutputStream.write(buf);
            fileOutputStream.flush();
            System.out.println("finish");
        }
        resultSet.close();
        statement.close();
        connection.close();
    }

CLOB的操作基本同BLOB,只是對應(yīng)的類型設(shè)置為CLOB即可,需要注意的是,在MySQL中,沒有數(shù)據(jù)類型為CLOB的數(shù)據(jù)類型,其對應(yīng)的是text,這里在創(chuàng)建數(shù)據(jù)表的時(shí)候需要注意一下。

批量處理

所謂的批處理,其實(shí)就是同時(shí)指定多個(gè)SQL語句,通常由兩種做法,一種是循環(huán)執(zhí)行多個(gè)SQL語句,另外一種則是采用JDBC提供的Batch功能,具體如下所示

 @Test
    public void testBatch() throws ClassNotFoundException, SQLException {

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

        Connection connection = DriverManager.getConnection(SQL_URL, SQL_USER_NAME, SQL_PASSWORD);

        String sql = "insert into user(id, name) values(?, 'Tom')";
        PreparedStatement statement = connection.prepareStatement(sql);
        long start = System.currentTimeMillis();
        // 普通循環(huán)執(zhí)行
        for (int i = 0; i < 1000; i++){
            statement.setObject(1, i);
            statement.executeUpdate();
        }

        long end = System.currentTimeMillis();

        System.out.printf("cost time %d\n", end - start);

        statement.close();

        statement = connection.prepareStatement(sql);

        start = System.currentTimeMillis();
        // 使用Batch執(zhí)行
        for (int i = 1001 ; i < 2000; i++){
            statement.setObject(1, i);
            statement.addBatch();
        }
        statement.executeBatch();
        end = System.currentTimeMillis();

        System.out.printf("cost time %d\n", end - start);

        statement.close();
        connection.close();
    }

有點(diǎn)可惜的是,經(jīng)過測試,這兩種方式的性能很接近,不知道是我操作的問題還是事實(shí)確實(shí)是如此,還望有懂這個(gè)的朋友指點(diǎn)指點(diǎn),

元數(shù)據(jù)

所謂的元數(shù)據(jù),也就是是用于定義其他數(shù)據(jù)的數(shù)據(jù),在SQL中,元數(shù)據(jù)可以理解為就是數(shù)據(jù)庫、表的定義數(shù)據(jù),在JDBC中,有三種類型的元數(shù)據(jù),分別是DatabaseMetaData,ResultSetMetaData,ParameterMetaData,分別是數(shù)據(jù)庫相關(guān)信息,結(jié)果集相關(guān)信息,以及PreparedStatement語句中的相關(guān)參數(shù)信息,具體操作如下所示


    @Test
    public void testMetaData() throws ClassNotFoundException, SQLException {

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

        Connection connection = DriverManager.getConnection(SQL_URL, SQL_USER_NAME, SQL_PASSWORD);

        // DatabaseMetaData 相關(guān)的信息,比如數(shù)據(jù)庫供應(yīng)商名稱,版本等
        DatabaseMetaData databaseMetaData = connection.getMetaData();
        System.out.println("version : " + databaseMetaData.getDatabaseMajorVersion());
        System.out.println("name : " + databaseMetaData.getDatabaseProductName());

        int id = 4;
        String sql = "select id, name from user where id < ? ";
        PreparedStatement statement = connection.prepareStatement(sql);
        statement.setObject(1, id);
        
        // ParameterMetaData 相關(guān)信息,比如參數(shù)個(gè)數(shù)
        ParameterMetaData parameterMetaData = statement.getParameterMetaData();
        int paramCount = parameterMetaData.getParameterCount();
        System.out.println("parameter count : " + paramCount);

        ResultSet resultSet = statement.executeQuery();
      
        // ResultSetMetaData 相關(guān)信息,比如取出來的數(shù)據(jù)的列名
        ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
        int resultCount = resultSetMetaData.getColumnCount();
        System.out.println("resultCount : " + resultCount);
        for (int i = 0; i < resultCount; i++) {
            System.out.print(resultSetMetaData.getColumnName(i + 1) + "  ");
        }
        System.out.println();
    }

事務(wù)管理

所謂的事務(wù),是指做一件事情,這件事情有多個(gè)步驟,這多個(gè)步驟這件,要么都完成,要么都不完成,事務(wù)具有四個(gè)特性ACID,分別是原子性,一致性,獨(dú)立性,持久性
在JDBC中,可以通過connection.setAutoCommit(true/false);開控制事務(wù),默認(rèn)為true,也就是默認(rèn)自動(dòng)提交事務(wù)。通過connection.commit()手動(dòng)提交事務(wù),connection.setSavepoint();設(shè)置保存點(diǎn),connection.rollback();回滾到保存到。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • JDBC概述 在Java中,數(shù)據(jù)庫存取技術(shù)可分為如下幾類:JDBC直接訪問數(shù)據(jù)庫、JDO技術(shù)、第三方O/R工具,如...
    usopp閱讀 3,641評論 3 75
  • 本節(jié)介紹Statement接口及其子類PreparedStatement和CallableStatement。 它...
    zlb閱讀 1,244評論 0 0
  • 1 引言# 本文主要講解JDBC怎么演變到Mybatis的漸變過程,重點(diǎn)講解了為什么要將JDBC封裝成Mybait...
    七寸知架構(gòu)閱讀 77,578評論 36 979
  • 1. 簡介 1.1 什么是 MyBatis ? MyBatis 是支持定制化 SQL、存儲(chǔ)過程以及高級映射的優(yōu)秀的...
    笨鳥慢飛閱讀 6,248評論 0 4
  • 你用月光杯 干盡了一江春水 而我想用一首詩 起舞你波濤洶涌的江湖 不必介懷 是什么弄傷了你的心 以文字做針線吧 縫...
    花千魂閱讀 284評論 0 0

友情鏈接更多精彩內(nèi)容