相關學習資料
http://zh.wikipedia.org/wiki/Java數據庫連接 http://lavasoft.blog.51cto.com/62575/20588 http://blog.csdn.net/cxwen78/article/details/6863696 http://www.ibm.com/developerworks/cn/java/jdbc-objects/index.html?ca=drs http://www.moon-soft.com/doc/37897.htm
目錄
1. Java JDBC簡介 2. Java JDBC下執行SQL的不同方式 3. Java JDBC編程實踐
1. Java JDBC簡介
數據庫驅動程序是JDBC程序和數據庫之間的轉換層,數據庫驅動程序負責將JDBC調用映射成特定的數據庫調用,類似PHP中的"數據庫抽象層"
http://www.php.net/manual/zh/refs.database.vendors.php
使用Java JDBC API進行編程,可以為多種關系數據庫提供統一訪問,為我們帶來跨平台、跨數據庫系統的好處
總的來說,JDBC驅動通常有如下4種類型
1. JDBC-ODPC橋: 它將JDBC API映射到ODPC API。再讓JDBC-ODPC調用數據庫本地驅動代碼(也就是數據庫廠商提供的數據庫操作二進制代碼庫,例如Oracle中的oci.dll) 2. 本地API驅動 直接將JDBC API映射成數據庫特定的客戶端API,即通過客戶端加載數據庫廠商提供的本地代碼庫(C/C++等) 3. 網絡協議驅動 這種類型的驅動給客戶端提供了一個網絡API,客戶端上的JDBC驅動程序使用套接字(Socket)來調用服務器上的中間件程序,后者在將其請求轉化為所需的具體API調用。 4. 本地協議驅動 這種類型的驅動使用Socket,直接在客戶端和數據庫間通信。它是一種直接與數據庫實例交互的JDBC 這種驅動是智能的,它知道數據庫使用的底層協議,也是目前最主流使用的JDBC驅動,我們本章的重點就是它
JDBC編程(連接數據庫)步驟
1) 加載數據庫驅動 通常我們使用Class類的forName()靜態方法來加載驅動(由各個數據庫廠商自己實現) Class.forName("com.mysql.jdbc.Driver"); Class.forName("oracle.jdbc.driver.OracleDriver"); "com.mysql.jdbc.Driver"、"oracle.jdbc.driver.OracleDriver"代表了數據庫驅動類對應的字符串 2) 通過DriverManager獲取數據庫連接 DriverManager.getConnection(String url, String user, String pass); 2.1) url: 數據庫連接字符串 2.1.1) Mysql jdbc:mysql://hostname:port/databasename 2.1.2) Oracle jdbc:oracle:thin:@hostname:port:databasename 2.2) user: 數據庫的系統用戶名 2.3) pass: 數據庫的系統密碼 3) 通過Connection對象創建Statement對象,Connection創建Statement對象的方法有如下3個 3.1) createStatement(String sql):創建基本的Statement對象 3.2) prepareStatement(String sql): 根據傳入的SQL語句創建預編譯的Statement對象 3.3) prepareCall(String sql): 根據傳入的SQL語句創建CallableStatement對象 4) 使用Statement執行SQL語句 所有的Statement都有如下3個方法來執行SQL語句 4.1) execute(): 可以執行任何SQL語句,但比較麻煩 4.2) executeUpdate(): 主要用於執行DML和DDL語句。執行DML語句返回受SQL影響的行數,執行DDL語句返回0 4.3) executeQuery(): 只能執行查詢語句,執行后返回代表查詢結果的ResultSet對象 5) 操作結果集 如果執行的SQL語句是查詢語句,則執行結果將返回一個ResultSet對象,該對象里保存了SQL語句查詢的結果。程序可以通過操作該ResultSet對象來取出查詢結果。ResultSet對象主要提供了如
下方法 5.1) 移動記錄指針的方法 5.1.1) next() 5.1.2) previous() 5.1.3) first() 5.1.4) last() 5.1.5) beforeFirst() 5.1.6) afterLast() 5.1.7) absolute() 5.2) 獲取指針指向的某行的"特定的列值" 5.2.1) getInt()
5.2.2) getString()
... 該方法既可以使用列索引作為參數,也可以使用列名作為參數 6) 回收數據庫資源 包括關閉ResultSet、Statement、Connection等資源
2. Java JDBC下執行SQL的不同方式
JDBC是一個相對比較底層的API接口,它只提供給我們一個執行原生SQL語句的輸入接口。所以和JDBC的API接口都定義在
java.sql
javax.sql
我們知道,Java JDBC編程中有3種方法進行DDL、DML語句的執行
1. createStatement 返回Statement 1) execute() 2) executeUpdate() 3) executeQuery() 2. prepareStatement 返回PrepareStatement 1) execute() 2) executeUpdate() 3) executeQuery() 3. prepareCall 返回CallableStatement
我們來逐一學習
0x1: 基本的Statement對象
Statement提供了3個方法來執行SQL語句,它們都可以用於執行DDL(執行后返回值為0)、DML(執行后返回受影響行數)語句
使用execute執行DDL、DML語句
Statement的execute()方法幾乎可以執行任何原生SQL語句,但它執行SQL語句時比較麻煩(它無法直接返回一個ResultSet,而是需要我們手工再次獲取),通常情況下,使用executeQuery()、或executeUpdate()方法更加簡單。但如果程序員不清楚SQL語句的類型,則只能使用execute()方法來執行該SQL語句了。
注意,使用execute()方法執行SQL語句的返回值"只是"boolean值,它表明了執行該SQL語句是否返回了ResultSet對象,而我們要得到具體的ResultSet對象還需要額外的方法
1. getResult(): 獲取該Statement執行查詢語句所返回的ResultSet對象 2. getUpdateCount(): 獲取該Statement執行DML語句所影響的記錄行數
code:
import java.util.*; import java.io.*; import java.sql.*; public class ExecuteSQL { private String driver; private String url; private String user; private String pass; public void initParam(String paramFile)throws Exception { // 使用Properties類來加載屬性文件 Properties props = new Properties(); props.load(new FileInputStream(paramFile)); driver = props.getProperty("driver"); url = props.getProperty("url"); user = props.getProperty("user"); pass = props.getProperty("pass"); } public void executeSql(String sql)throws Exception { // 加載驅動 Class.forName(driver); try( // 獲取數據庫連接 Connection conn = DriverManager.getConnection(url , user , pass); // 使用Connection來創建一個Statement對象 Statement stmt = conn.createStatement()) { // 執行SQL,返回boolean值表示是否包含ResultSet boolean hasResultSet = stmt.execute(sql); // 如果執行后有ResultSet結果集 if (hasResultSet) { try( // 獲取結果集 ResultSet rs = stmt.getResultSet()) { // ResultSetMetaData是用於分析結果集的元數據接口 ResultSetMetaData rsmd = rs.getMetaData(); int columnCount = rsmd.getColumnCount(); // 迭代輸出ResultSet對象 while (rs.next()) { // 依次輸出每列的值 for (int i = 0 ; i < columnCount ; i++ ) { System.out.print(rs.getString(i + 1) + "\t"); } System.out.print("\n"); } } } else { System.out.println("該SQL語句影響的記錄有" + stmt.getUpdateCount() + "條"); } } } public static void main(String[] args) throws Exception { ExecuteSQL es = new ExecuteSQL(); es.initParam("mysql.ini"); System.out.println("------執行刪除表的DDL語句-----"); es.executeSql("drop table if exists my_test"); System.out.println("------執行建表的DDL語句-----"); es.executeSql("create table my_test" + "(test_id int auto_increment primary key, " + "test_name varchar(255))"); System.out.println("------執行插入數據的DML語句-----"); es.executeSql("insert into my_test(test_name) " + "select student_name from student_table"); System.out.println("------執行查詢數據的查詢語句-----"); es.executeSql("select * from my_test"); } }
使用executeUpdate執行DDL、DML語句
import java.util.*; import java.io.*; import java.sql.*; public class ExecuteDDL { private String driver; private String url; private String user; private String pass; public void initParam(String paramFile) throws Exception { // 使用Properties類來加載屬性文件 Properties props = new Properties(); props.load(new FileInputStream(paramFile)); driver = props.getProperty("driver"); url = props.getProperty("url"); user = props.getProperty("user"); pass = props.getProperty("pass"); } public void createTable(String sql)throws Exception { // 加載驅動 Class.forName(driver); try( // 獲取數據庫連接 Connection conn = DriverManager.getConnection(url , user , pass); // 使用Connection來創建一個Statment對象 Statement stmt = conn.createStatement()) { // 執行DDL,創建數據表 stmt.executeUpdate(sql); } } public static void main(String[] args) throws Exception { ExecuteDDL ed = new ExecuteDDL(); ed.initParam("mysql.ini"); ed.createTable("create table jdbc_test " + "( jdbc_id int auto_increment primary key, " + "jdbc_name varchar(255), " + "jdbc_desc text);"); System.out.println("-----建表成功-----"); } }
從代碼中我們可以看到,我們並沒有把數據庫連接信息寫在程序里,而是使用一個mysql.ini文件來保存數據庫連接信息,這樣當需要把程序從開發環境移植到生產環境時,無須修改源代碼,只需要修改mysql.ini配置文件即可
mysql.ini:
driver=com.mysql.jdbc.Driver url=jdbc:mysql://127.0.0.1:3306/select_test user=root pass=111
使用executeQuery執行DDL、DML語句
import java.sql.*; public class ConnMySql { public static void main(String[] args) throws Exception { // 1.加載驅動,使用反射的知識,現在記住這么寫。 Class.forName("com.mysql.jdbc.Driver"); try( // 2.使用DriverManager獲取數據庫連接, // 其中返回的Connection就代表了Java程序和數據庫的連接 // 不同數據庫的URL寫法需要查驅動文檔知道,用戶名、密碼由DBA分配 Connection conn = DriverManager.getConnection( "jdbc:mysql://127.0.0.1:3306/company" , "root" , "111"); // 3.使用Connection來創建一個Statment對象 Statement stmt = conn.createStatement(); // 4.執行SQL語句 /* Statement有三種執行sql語句的方法: 1 execute 可執行任何SQL語句。- 返回一個boolean值, 如果執行后第一個結果是ResultSet,則返回true,否則返回false 2 executeQuery 執行Select語句 - 返回查詢到的結果集 3 executeUpdate 用於執行DML語句。- 返回一個整數, 代表被SQL語句影響的記錄條數 */ ResultSet rs = stmt.executeQuery("select *" + " from p8_ad_user")) { // ResultSet有系列的getXxx(列索引 | 列名),用於獲取記錄指針 // 指向行、特定列的值,不斷地使用next()將記錄指針下移一行, // 如果移動之后記錄指針依然指向有效行,則next()方法返回true。 while(rs.next()) { System.out.println(rs.getInt(1) + "\t" + rs.getString(2) + "\t" + rs.getString(3) + "\t" + rs.getString(4)); } } } }
0x2: prepareStatement預編譯對象
JDBC中的這個類對於安全人員、和黑客都需要重點關注,它不僅可以提供SQL執行性能,同時還有防御SQL注入的功能。
當我們的業務層中和數據庫相關的代碼經常要執行一些相似度很高的SQL語句,它們的結果基本相似,只是插入時插入的值不同而已
insert into student_table values(null, "LittleHann", 1); insert into student_table values(null, "LittleHann", 2);
在這種情況下,我們可以使用帶占位符(?)參數的SQL語句來代替它
insert into student_table values(null, ?, ?);
為了滿足這種功能,JDBC提供了prepareStatement接口,它是Statement接口的子接口,它可以預編譯SQL語句,預編譯后的SQL語句被存儲在prepareStatement對象中,然后可以使用該對象多次高效地執行該語句。prepareStatement同樣也使用execute()、executeUpdate()、executeQuery()來執行SQL語句,但這三個方法無須參數,因為prepareStatement在創建的時候已經存儲了預編譯的SQL語句,在執行SQL語句的時候只要傳入參數值即可(setXxx)
Statement和prepareStatement的性能對比
import java.util.*; import java.io.*; import java.sql.*; public class PreparedStatementTest { private String driver; private String url; private String user; private String pass; public void initParam(String paramFile)throws Exception { // 使用Properties類來加載屬性文件 Properties props = new Properties(); props.load(new FileInputStream(paramFile)); driver = props.getProperty("driver"); url = props.getProperty("url"); user = props.getProperty("user"); pass = props.getProperty("pass"); // 加載驅動 Class.forName(driver); } public void insertUseStatement()throws Exception { long start = System.currentTimeMillis(); try( // 獲取數據庫連接 Connection conn = DriverManager.getConnection(url , user , pass); // 使用Connection來創建一個Statment對象 Statement stmt = conn.createStatement()) { // 需要使用100條SQL語句來插入100條記錄 for (int i = 0; i < 1000 ; i++ ) { stmt.executeUpdate("insert into student_table values(" + " null ,'姓名" + i + "' , 1)"); } System.out.println("使用Statement費時:" + (System.currentTimeMillis() - start)); } } public void insertUsePrepare()throws Exception { long start = System.currentTimeMillis(); try( // 獲取數據庫連接 Connection conn = DriverManager.getConnection(url , user , pass); // 使用Connection來創建一個PreparedStatement對象 PreparedStatement pstmt = conn.prepareStatement( "insert into student_table values(null,?,1)")) { // 100次為PreparedStatement的參數設值,就可以插入100條記錄 for (int i = 0; i < 1000 ; i++ ) { pstmt.setString(1 , "姓名" + i); pstmt.executeUpdate(); } System.out.println("使用PreparedStatement費時:" + (System.currentTimeMillis() - start)); } } public static void main(String[] args) throws Exception { PreparedStatementTest pt = new PreparedStatementTest(); pt.initParam("mysql.ini"); pt.insertUseStatement(); pt.insertUsePrepare(); } }
mysql.ini:
driver=com.mysql.jdbc.Driver url=jdbc:mysql://127.0.0.1:3306/select_test user=root pass=111
result:
使用Statement費時:71269 使用PreparedStatement費時:69226
除了性能上的優勢,prepareStatement還有另一個優勢: 無須在SQL語句中"拼接"SQL參數,從安全的角度來看,這就從很大程序上避免了"SQL注入"的發生。使用prepareStatement時,所有的參數都變成了"問號占位符",也就避免了數據和代碼的混淆(這是造成注入的根本原因)
關於使用參數化預編譯防御SQL注入,這里要注意幾點:
1. 參數化預編譯之所以能防御住SQL注入,只要是基於以下2點: 1) setString(): WEB程序接收字符串的場景 將用戶輸入的參數全部強制轉換為字符串,並進行適當的轉義,防止了閉合的產生 2) setInt(): WEB程序接收整型的場景 將用戶輸入的非整型參數強制轉換為整型,並去除潛在的"非整型注入字符",類似與PHP中的intVal()防御思路 2. 並不是說使用了參數化預編譯方法執行SQL,就不會有注入的發生了,當WEB系統和DataBase系統的字符集配置不當,可能會導致寬字節注入的發生
0x3: prepareCall存儲過程對象
調用存儲過程可以使用CallableStatement,程序員通過Connection的prepareCall()方法創建CallableStatement對象
創建存儲過程
delimiter // create procedure add_pro(a int, b int, out sum int) begin set sum = a + b end; //
通過Connection的prepareCall()方法創建CallableStatement對象時需要傳入"調用存儲過程的SQL命令語句":
cstmt = conn.prepareCall("{call add_pro(?, ?, ?)}");
存儲過程的參數既有:
1. 傳入參數 java程序必須為這些參數傳入值,可以通過CallableStatement的setXxx()方法為"傳入參數"設置值 2. 傳出參數 java程序可以通過該參數獲取存儲過程里的值,CallableStatement需要調用registerOutParameter()方法來注冊該參數,執行結束后調用CallableStatement對象的getXxx(int index)
方法來獲取指定傳出參數的值
code:
import java.awt.*; import java.awt.event.*; import javax.swing.*; import java.util.*; import java.io.*; import java.sql.*; public class CallableStatementTest { private String driver; private String url; private String user; private String pass; public void initParam(String paramFile)throws Exception { // 使用Properties類來加載屬性文件 Properties props = new Properties(); props.load(new FileInputStream(paramFile)); driver = props.getProperty("driver"); url = props.getProperty("url"); user = props.getProperty("user"); pass = props.getProperty("pass"); } public void callProcedure()throws Exception { // 加載驅動 Class.forName(driver); try( // 獲取數據庫連接 Connection conn = DriverManager.getConnection(url , user , pass); // 使用Connection來創建一個CallableStatment對象 CallableStatement cstmt = conn.prepareCall( "{call add_pro(?,?,?)}")) { cstmt.setInt(1, 4); cstmt.setInt(2, 5); // 注冊CallableStatement的第三個參數是int類型 cstmt.registerOutParameter(3, Types.INTEGER); // 執行存儲過程 cstmt.execute(); // 獲取,並輸出存儲過程傳出參數的值。 System.out.println("執行結果是: " + cstmt.getInt(3)); } } public static void main(String[] args) throws Exception { CallableStatementTest ct = new CallableStatementTest(); ct.initParam("mysql.ini"); ct.callProcedure(); } }
3. Java JDBC編程實踐
0x1: 下載MySQL支持JDBC的驅動程序
我們前面說過,Java JDBC現在主流的做法是"本地協議驅動",為此,Java需要借助不同廠商提供的數據庫驅動(本質上是一個JAR包)來和數據庫進行連接,我們這里以Mysql為例,
前往MySQL官網(http://www.mysql.com/products/connector/ )下載驅動程序,,MySQL針對不同的平台提供了不同的連接器,我們需要的是JDBC Driver for MySQL (Connector/J)
0x2: 在MyEclips中創建工程項目,並添加驅動JAR包
import java.sql.*; public class ConnMySql { public static void main(String[] args) throws Exception { // 1.加載驅動,使用反射的知識,現在記住這么寫。 Class.forName("com.mysql.jdbc.Driver"); try( // 2.使用DriverManager獲取數據庫連接, // 其中返回的Connection就代表了Java程序和數據庫的連接 // 不同數據庫的URL寫法需要查驅動文檔知道,用戶名、密碼由DBA分配 Connection conn = DriverManager.getConnection( "jdbc:mysql://127.0.0.1:3306/company" , "root" , "111"); // 3.使用Connection來創建一個Statment對象 Statement stmt = conn.createStatement(); // 4.執行SQL語句 /* Statement有三種執行sql語句的方法: 1 execute 可執行任何SQL語句。- 返回一個boolean值, 如果執行后第一個結果是ResultSet,則返回true,否則返回false 2 executeQuery 執行Select語句 - 返回查詢到的結果集 3 executeUpdate 用於執行DML語句。- 返回一個整數, 代表被SQL語句影響的記錄條數 */ ResultSet rs = stmt.executeQuery("select *" + " from p8_ad_user where u_id=1")) { // ResultSet有系列的getXxx(列索引 | 列名),用於獲取記錄指針 // 指向行、特定列的值,不斷地使用next()將記錄指針下移一行, // 如果移動之后記錄指針依然指向有效行,則next()方法返回true。 while(rs.next()) { System.out.println(rs.getInt(1) + "\t" + rs.getString(2) + "\t" + rs.getString(3) + "\t" + rs.getString(4)); } } } }
將mysql數據庫連接驅動添加到當前項目的CLASSPATH中
0x3: 編譯、運行Java代碼
Copyright (c) 2014 LittleHann All rights reserved