1. 程式人生 > >JDBC簡單使用、工具類構建以及Statement與PreparedStatement區別

JDBC簡單使用、工具類構建以及Statement與PreparedStatement區別

先來 訪問 結構 puts pla line null tar public

相關源碼會在每一個部分的末尾給出

相關表的結構:

技術分享圖片

在介紹具體的工具類之前,先來簡單介紹一下JDBC的連接步驟:

1. 註冊驅動

在註冊驅動以前,你需要先導入mysql-connector-java-8.0.11.jar(使用的是Mysql)的包,來使用與數據庫連接的方法。

DriverManager.registerDriver(new com.mysql.jdbc.Driver());//這種創建方式並不好,後續會介紹其原理,以及如何優化。

2. 建立連接

Connection conn = DriverManager.getConnection("jdbc:mysql://localhost/petshop", "root", "123456");

3. 創建statement

Statement st = conn.createStatement();

4. 執行sql ,得到ResultSet

String sql = "select * from girlfriends";
ResultSet rs = st.executeQuery(sql);

5. 遍歷結果集

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

int age = rs.getInt("age");
System.out.println("id="+id + "=name="+name+"age="+age);
}

6. 釋放資源

conn,st,rs由順序1,2,3創建,就從3,2,1開始順序close

if (rs != null) {
try {
rs.close();
} catch (SQLException sqlEx) { } // ignore

rs = null;
}

//st,conn關閉方式雷同,不贅述

以上就是一般連接的基本步驟,下面來進行工具類的實現,通過分析,實現簡化與優化。

一. 連接工具類實現

首先你從基本步驟2中可以發現,將用戶的sql賬號與密碼暴露在外,是一件很不明智的行為,不僅僅是安全問題,而且在你需要切換登陸或者切換使用別的database時是一件非常不方便的事。

於是我們把這些信息都抽離出來,放到項目的/src/jdbc.properties文件中保存

技術分享圖片

driver的具體內容是由下面這導入的jar包中的配置文件決定的,每種不同的數據庫有不同的driver需要更改。

技術分享圖片

路徑的/後面跟的是database的名字。

urlextra是因為在實際使用過程中,可能需要使用上述語句去避免連接時報錯的問題。

name和password填寫你自己的實際數據。

既然放到了屬性文件中,那麽我們每次進行不同的數據庫連接只需要更改配置文件的內容即可,並且自動從裏面讀取數據,可以根據不同的數據庫來實現不同的JDBC配置,比如服務器遠程訪問不同數據庫時就很有用,需要更改tomcat中xml的配置內容。

Properties properties = new Properties();
InputStream inStream = JDBCUtil.class.getClassLoader().getResourceAsStream("jdbc.properties");//bin文件夾下,加載字節碼時,用類加載器自動添加
properties.load(inStream);

driver = properties.getProperty("driver");
urlextra = properties.getProperty("urlextra");
url = properties.getProperty("url");
username = properties.getProperty("username");
password = properties.getProperty("password");

即可獲得所有的屬性參數。

再來說註冊驅動的方式,上文中使用了

DriverManager.registerDriver(new com.mysql.jdbc.Driver());

這種方式,這種方式和普通的註冊驅動方式

Class.forName(driver);

其實是一樣的,但是其實都是不必要的操作,因為mysql連接的jar在4.0版本後,jar包META-INF文件夾下services的Driver中會自動加載,所以其實會默認幫助我們完成驅動的註冊。因此實際中省略這個步驟,減少new一個對象的開銷。

只需要一句:

conn = DriverManager.getConnection(url+urlextra, username, password);

因此,關於連接的代碼如下(properties文件中的內容上面已給出)

public class JDBCUtil {

    static String driver = null;
    static String urlextra = null;
    static String url = null;
    static String username = null;
    static String password = null;

    static {

        try {
            Properties properties = new Properties();
            InputStream inStream = JDBCUtil.class.getClassLoader().getResourceAsStream("jdbc.properties");//bin文件夾下,加載字節碼時,用類加載器自動添加
            properties.load(inStream);
            
            driver = properties.getProperty("driver");
            urlextra = properties.getProperty("urlextra");
            url = properties.getProperty("url");
            username = properties.getProperty("username");
            password = properties.getProperty("password");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static Connection getConn() {
        Connection conn = null;
        try {
            //Class.forName(driver);//4.0版本後,可以不寫,jar包META-INF文件夾下services的Driver中會自動加載
            conn = DriverManager.getConnection(url+urlextra, username, password);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return conn;
    }
}

二. 釋放連接工具類實現

關於釋放操作,上文已經提到過,由創建順序1,2,3去從3,2,1開始釋放,唯一需要註意的是,需要重載一下release方法,在第四段內容中會提到為何需要寫2種release函數,接收3個參數和2個參數的。

代碼如下:

public class JDBCUtil {   
    public static void release(Connection conn, Statement stmt, ResultSet rs) {
        closeRs(rs);
        closeStmt(stmt);
        closeConn(conn);
    }
    
    public static void release(Connection conn, Statement stmt) {
        closeStmt(stmt);
        closeConn(conn);
    }

    private static void closeRs(ResultSet rs) {
        try {
            if (rs != null) {
                rs.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            rs = null;
        }
    }

    private static void closeStmt(Statement stmt) {
        try {
            if (stmt != null) {
                stmt.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            stmt = null;
        }
    }

    private static void closeConn(Connection conn) {
        try {
            if (conn != null) {
                conn.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            conn = null;
        }
    }
}

三. 增刪查改的細節區別

目前我們都是通過使用Statement來執行sql語句,增刪查改中,增刪改操作所返回的是一個int,即成功執行的次數。而查的方法可以返回一個resultSet集合,所以給出一個增加和一個查詢的示例。

SQL增加:

        conn = JDBCUtil.getConn();
        st = conn.createStatement();

        String sql = "insert into girlfriends values(null , ‘Misaki‘ , 20)";
        int result = st.executeUpdate(sql);
        if (result > 0) {
            System.out.println("添加成功");
        } else {
            System.out.println("添加失敗");
        }

SQL查詢:

conn = JDBCUtil.getConn();
        st = conn.createStatement();

        String sql = "select * from girlfriends";
        rs = st.executeQuery(sql);

        while (rs.next()) {
            String name = rs.getString("name");
            int age = rs.getInt("age");

            System.out.println(name + "   " + age);
        }

最後,第2小節提到的重載釋放函數的答案相信有的人也早已知道了,因為只有在使用查詢操作時可能會需要resultSet返回集,所以需要close掉3個連接,而其他操作只需要close掉2個連接即可。

四. Statement與PreparedStatement的區別

上面所使用的情況都是直接使用Statement語句來執行excuteQuery(傳入一條sql語句)

下面一個查詢該女生是否存在的函數:

@Override
    public void exist2(int id, String girlname) {
        Connection conn = null;
        Statement stmt = null;
        ResultSet rs = null;
        try {
            conn = JDBCUtil.getConn();
            stmt = conn.createStatement();
            String sql = "select * from GirlFriends where id=" + id + " and name=‘" + girlname + "‘";
            rs = stmt.executeQuery(sql);

            if (rs.next()) {
                System.out.println("女生存在!");
            } else {
                System.out.println("女生不存在!");
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtil.release(conn, stmt, rs);
        }

    }

表面上並不存在問題,然後我們使用2條語句來測試這個函數:(測試時運用的是Dao模式,相關的操作方式可以自行搜索了解)

    @org.junit.jupiter.api.Test
    public void test_exist2() {
        
        GirlDao dao=new GirlDaoImpl();
        dao.exist2(3,"Alice‘ or ‘1=1");
        System.out.println("**************************");
        dao.exist2(3,"Alice");
    }

運行結果如下:

技術分享圖片

你發現,第二條正常輸入的語句,結果是不存在

而第一條輸入的語句拼接了一個(or ‘1=1)之後,居然存在了。

這個問題就是Statement的安全性問題。

Statement的執行 ,其實是拼接sql語句的。

它先拼接sql語句,然後再一起執行。 前面拼接sql語句時, 如果變量裏面帶有了 數據庫的關鍵字,那麽一並認為是關鍵字。 不認為是普通的字符串。 認為是一整個語句。由於後面的or被識別為關鍵字了就一並正確執行了。

所以我們不推薦實現Statement,而建議所有代碼更改為PreparedStatement來執行,下面為更改的檢測存在代碼:

@Override
    public void exist3(int id, String girlname) {
        Connection conn = null;
        Statement stmt = null;
        PreparedStatement ppst = null;
        ResultSet rs = null;
        try {
            conn = JDBCUtil.getConn();
            String sql = "select * from GirlFriends where id=? and name=?";
            ppst = conn.prepareStatement(sql);

            ppst.setInt(1, id);
            ppst.setString(2, girlname);

            rs = ppst.executeQuery();
            if (rs.next()) {
                System.out.println("女友存在!");
            } else {
                System.out.println("女友不存在!");
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtil.release(conn, stmt, rs);
        }

    }

運行結果如下:

技術分享圖片

即可正確執行。

在這裏補充一下兩種statement語句的區別

普通的Statement語句是通過stmt本身去excute一個sql語句

而PreparedStatement語句是通過使用問號(占位符)來設置需要更改的參數(在exist3中能看到相應的代碼段),提供給用戶更改問號的方式,即ppst.set方法(下標從1開始,根據實際情況區分類型),然後由ppst的excute方法去執行(這時的執行方法語句中是不允許有參數的,因為已經提前設定好的需要修改的參數)

ppst會把接收到的數據統一變成字符串處理,不會看作是關鍵字,從而解決了問題。

其他語句的ppst版本執行語句就由讀者自己去書寫,兩者的轉化很簡單,並且多去嘗試可以使用的函數,你的編程思路才能更加開拓。

JDBC簡單使用、工具類構建以及Statement與PreparedStatement區別