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();
}
}
}