java中的JDBC詳解 附帶實現配置文件訪問數據庫

JDBC 是Java操作數據庫的規範,它實際上定義了一組標準的數據庫的接口,爲了實現通過java操作數據庫,必須實現這些接口,不同的數據庫廠商都提供了對JDBC接口的實現,這些具體的實現被打包成一個jar包(也就是數據庫驅動),供我們在開發的時候直接使用。
JDBC API 中的主要接口:
第一: Driver接口是所有JDBC程序必須實現的接口,該接口專門提供給數據庫廠商使用,定義了驅動的樣式
第二:DriverManager 用於加載JDBC驅動並創建與數據庫的連接
有兩個重要的方法:
1 DriverManager.registerDriver(Driver driver) // 用於向DriverManager註冊給定的JDBC驅動程序
2 DriverManager.getConnection(String url, String user, String pwd) // 建立與數據庫的連接,返回表示連接的Connection對象
第三: Connection 接口 主要方法有三個
1. Connection.createStatement(); // 創建一個Statement對象,靜態sql語句查詢
2. Connection.prepareStatement(String sql); // 創建一個PreparedStatement對象,實現動態sql語句查詢
3. Connection.prepareCall(String sql); // 創建一個CallableStatement對象來調用數據庫存儲過程
第四:Statement接口 用於執行查詢返回查詢結果
1 Statement.execute(String sql); // 執行各種SQL語句,返回一個boolean類型值,true表示執行的SQL語句具備查詢結果,可通過Statement.getResultSet()方法獲取
2 Statement.executeUpdate(String sql); // 執行SQL中的insert/update/delete語句,返回一個int值,表示受影響的記錄的數目
3 Statement.executeQuery(String sql); // 執行SQL中的select語句,返回一個表示查詢結果的ResultSet對象
第五:ResultSet接口 用於查詢結果的操作
1 ResultSet.next(); // 將遊標由當前位置移動到下一行
2 ResultSet.getString(String columnName); // 獲取指定字段的String類型值
3 ResultSet.getString(int columnIndex); // 獲取指定索引的String類型值
4 ResuleSet.previous(); // 將遊標由當前位置移動到上一行

JDBC操作數據庫的一般步驟
註冊驅動 (只做一次)
建立連接(Connection)
創建執行SQL的語句(Statement)
執行語句並處理執行結果(ResultSet)
釋放資源

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

// 這裏測試的是mysql數據庫名是test; 表是 user; 表的字段 有 id ,name ,age ,salary
public class JDBCTest {
    public static void main(String[] args) {
        // 第一步: 首先註冊驅動, 驅動一般只會註冊一次
        try {
            Class.forName("com.mysql.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            System.out.println("找不到驅動程序類,加載驅動失敗");
            e.printStackTrace();
        }
        // 第二步:建立連接 Connect, 設置url ,用戶名, 密碼
        // url格式:JDBC:子協議:子名稱//主機名:端口/數據庫名?屬性名=屬性值&…
        // 注意的是url中一定不要加多餘的空格,否則會出錯, useSSL=false是爲了解決身份驗證時出現的警告的問題
        // String url = "jdbc:mysql://localhost:3306/test?" + "user=root&password=wsw011152&useUnicode=true&characterEncoding=UTF-8&useSSL=false";
        String url = "jdbc:mysql://localhost:3306/test?useSSL=false";
        String name = "root";
        String psw = "root";
        Connection connect = null;
        try {
            connect = DriverManager.getConnection(url, name, psw);
            // connect = DriverManager.getConnection(url);
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            System.out.println("數據庫連接失敗");
            e.printStackTrace();
        }
        // 第三步: 創建一個 Statement ,一般建議使用 PreparedStatement
        // 1、執行靜態SQL語句。通常通過Statement實例實現。
        // 2、執行動態SQL語句。通常通過PreparedStatement實例實現。
        // 3、執行數據庫存儲過程。通常通過CallableStatement實例實現。
        // String sql = "select * from user where id = ?";
        String sql = "select * from user where id = ?";
        try {
            PreparedStatement ps = connect.prepareStatement(sql);
            ps.setInt(1, 1); // 設置參數
            // 第四步: 執行語句,獲得一個結果集,處理獲得的結果
            ResultSet result = ps.executeQuery();
            while (result.next()) {
                System.out.println(result.getInt("id"));
                System.out.println(result.getString("name"));
                System.out.println(result.getInt("age"));
                System.out.println(result.getString("salary"));
            }
            // 第五步: 關閉資源
            result.close();
            ps.close();
            connect.close();
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

下面講解一下 JDBC爲什麼建議使用PreparedStatement而不是Statement
首先java提供了三種方式來執行sql語句
CallableStatement;主要用於存儲過程的查詢,
Statement;主要用於通用查詢,適合只對數據庫進行一次性存取的時候,使用它會爲每一條sql語句生成一個執行計劃,即使這兩條語句只有參數的不同而已。
PreparedStatement :主要用於參數化查詢,會傳遞參數,反覆查詢

PreparedStatement 相對比與Statement的優勢在下面的幾點:

1. 使用PreparedStatement,數據庫系統會對sql語句進行預編譯(需要JDBC驅動支持預編譯SQL查詢),進行預處理,這條預處理的sql查詢語句可以在將來的查詢中被重用,節省了創建執行計劃的時間,減少了系統的開銷,因此它比Statement的查詢速度更快。
比如使用 Statement進行下面兩句的查詢,則會生成兩個執行計劃,1000個查詢就會生成1000個執行計劃,生成執行計劃十分消耗資源,
select colume from table where colume=1;
select colume from table where colume=2;
但是使用PreparedStatement, 則系統會對sql語句進行預編譯處理,只會生成1個執行計劃,1000個這樣的查詢不會再產生執行計劃,這個執行計劃會被下面同樣的查詢語句所重用,大大提高了速度。
select colume from table where colume=?;
PreparedStatement .setInt(1, 1);
PreparedStatement .setInt(1, 2);
2. 可以寫動態參數化的查詢,用PreparedStatement你可以寫帶參數的sql查詢語句,通過使用相同的sql語句和不同的參數值來做查詢

3. PreparedStatement可以防止SQL注入式攻擊,更加安全
使用PreparedStatement的參數化的查詢可以阻止大部分的SQL注入攻擊。
第一:在使用參數化查詢的情況下,數據庫系統不會將參數的內容視爲SQL指令的一部分來處理,而是在數據庫完成SQL指令的編譯後,才套用參數運行,因此就算參數中含有破壞性的指令,也不會被數據庫所運行
第二:在組合SQL字符串的時候,先對所傳入的參數做字符取代(將單引號字符取代爲連續2個單引號字符,因爲連續2個單引號字符在SQL數據庫中會視爲字符中的一個單引號字符

strSQL = “SELECT * FROM users WHERE name = ‘” + userName + “’;”
傳入字符串:
userName = ” 1’ OR 1=1 “

把userName做字符替換後變成:
userName = ” 1” OR 1=1”

最後生成的SQL查詢語句爲:
strSQL = “SELECT * FROM users WHERE name = ‘1” OR 1=1’
這樣數據庫就會去系統查找name爲“1′ ‘ OR 1=1”的記錄,而避免了SQL注入。

PreparedStatement的侷限性:
爲了防止SQL注入攻擊,PreparedStatement不允許一個佔位符(?)有多個值,在執行有*IN子句查詢的時候這個問題變得棘手起來*。下面這個SQL查詢使用PreparedStatement就不會返回任何結果:
SELECT * FROM loan WHERE loan_type IN (?)
preparedSatement.setString(1, “‘personal loan’, ‘home loan’, ‘gold loan’”);
解決方式:

// 將in 裏面的變量首先存儲成一個數組
String[] in_datas=new String[]{"1", "2", "3"}; 
StringBuffer buffer = new StringBuffer();
for(int i=0;i<in_datas.length-1;i++){ 
    buffer.append("?,"); 
}
buffer.append("?");
// 在in字句 裏面使用N個 ?。 然後爲每一個?賦值
pst = conn.prepareStatement("select id,name from B where id in ( "+buffer.toString()+" )");  //  buffer.toString() ="?,?,?,?...?"
for(int i=0;i<in_datas.length;i++){  
    pst.set(i, in_datas[i]);  
}
// 解決 like 查詢的方式
   String expr = "select * from  table where url like ?";  
   pstmt = con.prepareStatement(expr);  
   String a="a";  
   pstmt.setString(1, "%"+a+"%");//自動添加單引號 (包裝後的參數)  
   pstmt.execute();  

SQL注入:
在SQL注入攻擊裏,惡意用戶通過SQL元數據綁定輸入,比如:某個網站的登錄驗證SQL查詢代碼爲:
strSQL = “SELECT * FROM users WHERE name = ‘” + userName + “’ and pw = ‘”+ passWord +”’;”
惡意填入:
userName = “1’ OR ‘1’=’1”;
passWord = “1’ OR ‘1’=’1”;
那麼最終SQL語句變成了:
strSQL = “SELECT * FROM users WHERE name = ‘1’ OR ‘1’=’1’ and pw = ‘1’ OR ‘1’=’1’;”
因爲WHERE條件恆爲真,這就相當於執行:
strSQL = “SELECT * FROM users;”
因此可以達到無賬號密碼亦可登錄網站。如果惡意用戶要是更壞一點,用戶填入:
strSQL = “SELECT * FROM users;”
SQL語句變成了:
strSQL = “SELECT * FROM users WHERE name = ‘any_value’ and pw = ”; DROP TABLE users”
這樣一來,雖然沒有登錄,但是數據表都被刪除了。

防止SQL注入的幾種方式:
第一種:使用預編譯語句,通過綁定變量的方式使得SQL語句的語義不會發生變化,避免使用動態拼接的sql語句進行查詢’,傳進來的參數只會被當成?代表的變量來使用,這樣就無法改變SQL語句的結構。
具體的實現包括: 使用存儲過程 或者 使用PreparedStatement實現執行參數化的查詢。
第二種: 永遠不要相信用戶的輸入,在web頁面就進行傳入數據的檢驗,使用正則表達式等方式過濾特殊的符號,進行校驗, 但是一旦出現了新的SQL攻擊方式,正則表達式庫也需要隨之修改
第三種: 做好數據庫帳號權限管理,對於用戶,儘量不要賦予其管理員的權限進行登錄訪問。
第四種: 嚴格加密處理用戶的機密信息

下面我自己實現了一個讀取數據庫配置文件進行數據庫的連接,可以連接不同的數據庫的一個實現。

第一步:配置文件 database.properties ,存放了各種數據庫的配置信息

# mysql database driver
MySQLdriverClass=com.mysql.jdbc.Driver
MySQLurl=jdbc:mysql://127.0.0.1/test?useSSL=false
MySQLusername=root
MySQLpassword=wsw011152
# sqlserver 2008 database driver
SqlServerdriverClass=net.sourceforge.jtds.jdbc.Driver
SqlServerurl=jdbc:jtds:sqlserver://localhost:1433;DatabaseName=test;
SqlServerusername=sa
SqlServerpassword=sa
# Oracle  database driver
OracledriverClass=oracle.jdbc.driver.OracleDriver
Oracleurl=jdbc:oracle:thin:@localhost:1521:test
Oracleusername=scott
Oraclerpassword=tiger

第二步: 創建一個工具類用來讀取這個配置文件中的信息

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Properties;

public class PropertiesUtil {
   private static Properties prop;
   static{
        try {
            prop = new Properties();
            prop.load(new FileInputStream("database.properties"));
        } catch (FileNotFoundException e) {
            System.out.println("加載配置文件失敗");
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    // 根據數據庫名獲取其驅動
    public static String getDriverProperties(String key){
        return prop.getProperty(key + "driverClass");
    }
    // 根據數據庫名獲取url
    public static String getUrlProperties(String key){
        return prop.getProperty(key + "url");
    }
    // 獲取用戶名
    public static String getUsernameProperties(String key){
        return prop.getProperty(key + "username");
    }
    // 獲取密碼
    public static String getPasswordProperties(String key){
        return prop.getProperty(key + "password");
    }
}

第三步:連接數據庫

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;

import com.mysql.jdbc.PreparedStatement;

public class JDBCByProperties {
    public static void main(String[] args) {

        // 只需要將不同的驅動放在配置文件中,就可以實現連接不同的數據庫
        try {
            // 1.註冊驅動   
            Class.forName(PropertiesUtil.getDriverProperties("MySQL"));
            // 2. 連接數據庫
            Connection conn = DriverManager.getConnection(
                    PropertiesUtil.getUrlProperties("MySQL"),       PropertiesUtil.getUsernameProperties("MySQL"),              PropertiesUtil.getPasswordProperties("MySQL"));
            // 3. 創建執行的sql語句, 建議使用PreparedStatement
            String sql = "select * from user where id = ?";
            PreparedStatement prep = null;
            prep = (PreparedStatement) conn.prepareStatement(sql);
            prep.setInt(1, 1);
            // 4. 查詢sql 語句, 返回一個結果集
            ResultSet result = prep.executeQuery();
            // 5. 處理結果集, 釋放資源
            while (result.next()) {
                System.out.println(result.getInt("id"));
                System.out.println(result.getString("name"));
                System.out.println(result.getInt("age"));
                System.out.println(result.getString("salary"));
            }
            conn.close();
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            System.out.println("驅動註冊未成功");
            e.printStackTrace();
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

本文參考了博客:
http://www.importnew.com/5006.html

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