JDBC教程——檢視閱讀
參考
JDBC教程——W3Cschool
JDBC教程——一點教程,有高階部分
JDBC教程——易百
JDBC入門教程 – 終極指南
略讀
三層架構詳解,JDBC在資料訪問層:
UI(表現層): 主要是指與使用者互動的介面。用於接收使用者輸入的資料和顯示處理後使用者需要的資料。
BLL:(業務邏輯層): UI層和DAL層之間的橋樑。實現業務邏輯。業務邏輯具體包含:驗證、計算、業務規則等等。
DAL:(資料訪問層): 與資料庫打交道。主要實現對資料的增、刪、改、查。將儲存在資料庫中的資料提交給業務層,同時將業務層處理的資料儲存到資料庫。(當然這些操作都是基於UI層的。使用者的需求反映給介面(UI),UI反映給BLL,BLL反映給DAL,DAL進行資料的操作,操作後再一一返回,直到將使用者所需資料反饋給使用者)
使用三層架構的目的:解耦 。
三層架構詳解
驅動程式(Drivers)
JDBC驅動管理器(java.sql.DriverManager)是JDBC API中最重要的元素之一。 它是用來處理一系列JDBC驅動程式的基本服務。它含有很多機制和物件,能夠將Java應用程式連線到所需的JDBC驅動程式。它負責管理這些不同型別的JDBC資料庫驅動程式。總結一下驅動管理器的主要功能就是:獲取當前可用的驅動列表;處理特定的的驅動程式和資料庫之間的連線。
我們通過方法DriverManager.registerDriver()來註冊驅動程式:
new org.hsqldb.jdbc.JDBCDriver();
DriverManager.registerDriver( new org.hsqldb.jdbc.JDBCDriver() );
我們也可以呼叫方法Class.forName() 載入驅動程式:
// Loading the HSQLDB JDBC driver
Class.forName( "org.hsqldb.jdbc.JDBCDriver" );
// connection to JDBC using mysql driver
Class.forName( "com.mysql.jdbc.Driver" );
這二者的主要區別是:前者方法registerDerver()需要保證驅動程式在編譯時就是可用的;後者載入驅動程式類檔案的方式,不需要驅動程式在編譯時是可用的。JDBC 4版本後,實際上沒有必要呼叫這些方法,應用程式不需要單獨註冊這些驅動,也不需要載入驅動類。我們也不推薦使用方法registerDriver()來手動載入驅動程式。
JDBC 指南
JDBC 簡介
什麼是 JDBC?
JDBC (Java Database Connectivity )指 Java 資料庫連線,是一種標準Java應用程式設計介面( JAVA API),用來連線 Java 程式語言和廣泛的資料庫。
JDBC API 庫包含下面提到的每個任務,都是與資料庫相關的常用用法。
- 製作到資料庫的連線。
- 建立 SQL 或 MySQL 語句。
- 執行 SQL 或 MySQL 查詢資料庫。
- 檢視和修改所產生的記錄。
從根本上來說,JDBC 是一種規範,它提供了一套完整的介面,允許行動式訪問到底層資料庫,因此可以用 Java 編寫不同型別的可執行檔案.
JDBC 架構
JDBC 的 API 支援兩層和三層處理模式進行資料庫訪問,但一般的 JDBC 架構由兩層處理模式組成:
- JDBC API: 提供了應用程式對 JDBC 管理器的連線(crud資料庫操作)。
- JDBC Driver API: 提供了 JDBC 管理器對驅動程式連線(驅動連線)。
JDBC API 使用驅動程式管理器和資料庫特定的驅動程式來提供各種各樣的 (heterogeneous)資料庫的透明連線。
JDBC 驅動程式管理器可確保正確的驅動程式來訪問每個資料來源。該驅動程式管理器能夠支援連線到多個各種各樣的資料庫的多個併發的驅動程式。
以下是結構圖,其中顯示了驅動程式管理器相對於在 JDBC 驅動程式和 Java 應用程式所處的位置。
常見的 JDBC 元件
JDBC 的 API 提供了以下介面和類:
DriverManager :這個類管理一系列資料庫驅動程式。匹配連線使用通訊子協議從 JAVA 應用程式中請求合適的資料庫驅動程式。識別 JDBC 下某個子協議的第一驅動程式將被用於建立資料庫連線。
Driver : 這個介面處理與資料庫伺服器的通訊**。你將很少直接與驅動程式互動。相反,你使用 DriverManager 中的物件,它管理此型別的物件。它也抽象與驅動程式物件工作相關的詳細資訊。
Connection : 此介面具有接觸資料庫的所有方法。該連線物件表示通訊上下文,即,所有與資料庫的通訊僅通過這個連線物件進行。
Statement : 使用建立於這個介面的物件將 SQL 語句提交到資料庫。除了執行儲存過程以外,一些派生的介面也接受引數。
ResultSet : 在你使用語句物件執行 SQL 查詢後,這些物件儲存從資料獲得的資料。它作為一個迭代器,讓您可以通過它的資料來移動。
SQLException : 這個類處理髮生在資料庫應用程式的任何錯誤。
例項:
public class JDBCTest {
private static final String JDBC_DRIVER = "com.mysql.jdbc.Driver";
private static final String DATABASE_URL = "jdbc:mysql://localhost:3306/hello_mybatis";
private static final String USER = "root";
private static final String PASSWORD = "123456";
public static void main(String[] args) throws ClassNotFoundException, SQLException {
//STEP 2: Register JDBC driver
Class.forName(JDBC_DRIVER);
ResultSet rs = null;
Connection conn = null;
Statement stmt = null;
try {
//STEP 3: Open a connection
conn = DriverManager.getConnection(DATABASE_URL, USER, PASSWORD);
//STEP 4: Execute a query
stmt = conn.createStatement();
String sql;
sql = "SELECT id, name, dept, phone FROM t_user";
rs = stmt.executeQuery(sql);
//STEP 5: Extract data from result set
//呼叫rs.next()時,執行this.thisRow = this.rowData.next();
//這時候返回值會把當前行資料指向遊標的下一行資料
while (rs.next()) {
//Retrieve by column name 通過列名獲取值
/* int id = rs.getInt("id");
String name = rs.getString("name");
String dept = rs.getString("dept");
String phone = rs.getString("phone");*/
//Retrieve by columnIndex 通過列的下標獲取值
int id = rs.getInt(1);
String name = rs.getString(2);
String dept = rs.getString(3);
String phone = rs.getString(4);
//Display values
System.out.println("id: " + id + "name: " + name + "dept: " + dept + "phone: " + phone);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
rs.close();
stmt.close();
conn.close();
}
System.out.println("over !");
}
}
輸出:
id: 1name: 大青山dept: amy empirephone: 18956563228
id: 2name: 艾米哈珀dept: amy empirephone: 18956563228
id: 3name: 池寒楓dept: amy empirephone: 22056545
id: 4name: 霍恩斯dept: 森林矮人王國phone: 852-253521
over !
報錯:
1、java.sql.SQLException: Column Index out of range, 0 < 1.
這是因為ResultSet結果集是獲取列是從1開始的。
int id = rs.getInt(0);//java.sql.SQLException: Column Index out of range, 0 < 1.
//正確操作如下:
int id = rs.getInt(1);
JDBC 驅動型別
什麼是 JDBC 驅動程式?
JDBC 驅動實現了 JDBC API 中定義的介面,該介面用於與資料庫伺服器進行互動。
例如,使用 JDBC 驅動程式可以讓你開啟資料庫連線,並通過傳送 SQL 或資料庫命令,然後通過 Java 接收結果。
java.sql 包中附帶的 JDK,包含了定義各種類與他們的行為和實際實現,這些類都在第三方驅動程式中完成。第三方供應商在他們的資料庫驅動程式中都實現了 java.sql.Driver 介面。
JDBC 驅動程式型別
JDBC 驅動程式的實現,因為各種各樣的作業系統和 Java 執行在硬體平臺的不同而不同。Sun 公司將實現型別分為四類:型別1,2,3,4,其解釋如下.
型別1:JDBC-ODBC 橋驅動程式:
在型別1驅動程式中,一個 JDBC 橋接器是用來訪問安裝在每個客戶機上的 ODBC 驅動程式。為了使用 ODBC,需要在目標資料庫上配置系統資料來源名稱(DSN)。
當 Java 剛出來時,這是一個很有用的驅動程式,因為大多數的資料庫只支援 ODBC 訪問,但現在此型別的驅動程式僅適用於實驗用途或在沒有其他選擇的情況。
自帶 JDK 1.2 中的 JDBC-ODBC 橋是這類驅動程式的一個很好的例子。
型別2:JDBC-Native API
在型別2驅動程式中,JDBC API 呼叫轉換成原生的 C/C++ API 呼叫,這對於資料庫來說具有唯一性。這些驅動程式通常由資料庫供應商提供,並和 JDBC-ODBC 橋驅動程式同樣的方式使用。該供應商的驅動程式必須安裝在每臺客戶機上。
如果我們改變了當前資料庫,我們必須改變原生 API ,因為它是具體到某一個資料庫,並且他們大多已經失效了。即使這樣用型別2驅動程式也能提高一些速度,因為他消除了 ODBC 的開銷。
Oracle 呼叫介面(OCI)驅動程式是一個型別2驅動程式的示例。
型別3:JDBC-Net 純 Java
在型別3驅動程式中,一般用三層方法來訪問資料庫。JDBC 客戶端使用標準的網路套接字與中介軟體應用伺服器進行通訊。套接字的相關資訊被中介軟體應用伺服器轉換為資料庫管理系統所要求的的呼叫格式,並轉發到資料庫伺服器。
這種驅動程式是非常靈活的,因為它不需要在客戶端上安裝程式碼,而且單個驅動程式能提供訪問多個資料庫。
你可以將應用伺服器作為一個 JDBC “代理”,這意味著它會呼叫客戶端應用程式。因此,你需要一些有關伺服器配置方面的知識,這樣可以高效地使用此驅動程式型別。
你的應用伺服器可能使用型別1,2,或4驅動程式與資料庫進行通訊,瞭解它們的細微之處將會很有幫助。
型別4:100%純 Java
在型別4驅動程式中,一個純粹的基於 Java 的驅動程式通過 socket 連線與供應商的資料庫進行通訊。這是可用於資料庫的最高效能的驅動程式,並且通常由供應商自身提供。
這種驅動器是非常靈活的,你不需要在客戶端或服務端上安裝特殊的軟體。此外,這些驅動程式是可以動態下載的。
MySQL Connector/J 的驅動程式是一個型別4驅動程式。因為它們的網路協議的專有屬性,資料庫供應商通常提供型別4的驅動程式。
該使用哪種驅動程式?
如果你正在訪問一個資料庫,如 Oracle,Sybase 或 IBM,首選的驅動程式是型別4。
如果你的 Java 應用程式同時訪問多個資料庫型別,型別3是首選的驅動程式。
型別2驅動程式是在你的資料庫沒有提供型別3或型別4驅動程式時使用的。
型別1驅動程式不被認為是部署級的驅動程式,它存在的目的通常僅用於開發和測試。
JDBC 連線資料庫
連線資料庫
在你安裝相應的驅動程式後,就可以用 JDBC 建立一個資料庫連線。
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.31</version>
</dependency>
編寫建立一個 JDBC 連線的程式是相當簡單的。下面是簡單的四個步驟:
- 匯入 JDBC 包:在你的 Java 程式碼中,用 import 語句新增你所需的類。
- 註冊 JDBC 驅動程式:這一步會導致 JVM 載入所需的驅動程式到記憶體中執行,因此它可以實現你的 JDBC 請求。
- 資料庫 URL 制定:這是用來建立格式正確的地址指向你想要連線的資料庫。
- 建立連線物件:最後,程式碼呼叫 DriverManager 物件的 getConnection() 方法來建立實際的資料庫連線。
匯入包:
註冊 JDBC 驅動程式
//方法1 - Class.forName()
//這種方法更優越一些,因為它允許你對驅動程式的註冊資訊進行配置,便於移植。
Class.forName("oracle.jdbc.driver.OracleDriver");//推薦
Class.forName("oracle.jdbc.driver.OracleDriver").newInstance();
//方法2 - DriverManager.registerDriver()
//如果你使用的是不相容 JVM 的非 JDK,比如微軟提供的,你必須使用 registerDriver() 方法。
Driver myDriver = new oracle.jdbc.driver.OracleDriver();
DriverManager.registerDriver( myDriver );
資料庫 URL 制定
當你載入了驅動程式之後,你可以通過 DriverManager.getConnection() 方法建立一個連線。為方便參考,以下列出了三個載入 DriverManager.getConnection() 方法:
- getConnection(String url)
- getConnection(String url, Properties prop)
- getConnection(String url, String user, String password)
下表列出了常用的 JDBC 驅動程式名和資料庫URL。
RDBMS JDBC 驅動程式名稱 URL 格式
MySQL com.mysql.jdbc.Driver jdbc:mysql://hostname/ databaseName
ORACLE oracle.jdbc.driver.OracleDriver jdbc:oracle:thin:@hostname:port Number:databaseName
DB2 COM.ibm.db2.jdbc.net.DB2Driver jdbc:db2:hostname:port Number/databaseName
Sybase com.sybase.jdbc.SybDriver jdbc:sybase:Tds:hostname: port Number/databaseName
URL 格式所有加粗的部分都是靜態的,你需要將剩餘部分按照你的資料庫實際情況進行設定。
//使用資料庫 URL 和 Properties 物件
Properties info = new Properties();
info.put("user", USER);
info.put("password", PASSWORD);
conn = DriverManager.getConnection(DATABASE_URL, info);
關閉 JDBC 連線
//Java 垃圾收集器也會關閉連線,它會完全清除過期的物件。但最好用try finally來關閉連線。
conn.close();
JDBC Statement 物件
Statement 物件
一旦我們獲得了資料庫的連線,我們就可以和資料庫進行互動。JDBC 的 Statement,CallableStatement 和 PreparedStatement 介面定義的方法和屬性,可以讓你傳送 SQL 命令或 PL/SQL 命令到資料庫,並從你的資料庫接收資料。
下表提供了每個介面的用途概要,根據實際目的決定使用哪個介面。
StatementImpl stmt = new StatementImpl(this.getLoadBalanceSafeProxy(), this.database);
介面 推薦使用
Statement 可以正常訪問資料庫,適用於執行靜態 SQL 語句。 Statement 介面不接受引數。
PreparedStatement 計劃多次使用 SQL 語句, PreparedStatement 介面執行時接受輸入的引數。
CallableStatement 適用於當你要訪問資料庫儲存過程的時候, CallableStatement 介面執行時也接受輸入的引數。
當你建立了一個 Statement 物件之後,你可以用它的三個執行方法的任一方法來執行 SQL 語句。
- boolean execute(String SQL) : 如果 ResultSet 物件可以被檢索,則返回的布林值為 true ,否則返回 false 。當你需要使用真正的動態 SQL 時,可以使用這個方法來執行 SQL DDL 語句。
- int executeUpdate(String SQL) : 返回執行 SQL 語句影響的行的數目。使用該方法來執行 SQL 語句,是希望得到一些受影響的行的數目,例如,INSERT,UPDATE 或 DELETE 語句。
- ResultSet executeQuery(String SQL) : 返回一個 ResultSet 物件。當你希望得到一個結果集時使用該方法,就像你使用一個 SELECT 語句。
關閉 Statement 物件
正如你關閉一個 Connection 物件來節約資料庫資源,出於同樣的原因你也應該關閉 Statement 物件。
簡單的呼叫 close() 方法就可以完成這項工作。如果你關閉了 Connection 物件,那麼它也會關閉 Statement 物件。然而,你應該始終明確關閉 Statement 物件,以確保真正的清除。
PreparedStatement 物件
PreparedStatement 介面擴充套件了 Statement 介面,它讓你用一個常用的 Statement 物件增加幾個高階功能。
JDBC 中所有的引數都被用 ? 符號表示,這是已知的引數標記。在執行 SQL 語句之前,你必須賦予每一個引數確切的數值。
setXXX() 方法將值繫結到引數,其中 XXX 表示你希望繫結到輸入引數的 Java 資料型別。如果你忘了賦予值,你將收到一個 SQLException。
每個引數標記對映它的序號位置。第一標記表示位置 1 ,下一個位置為 2 .
prepareStatement物件防止sql注入的方式是把使用者非法輸入的單引號用\反斜槓做了轉義,從而達到了防止sql注入的目的 。
例項:
public class JDBCTest2 {
private static final String JDBC_DRIVER = "com.mysql.jdbc.Driver";
private static final String DATABASE_URL = "jdbc:mysql://localhost:3306/hello_mybatis";
private static final String USER = "root";
private static final String PASSWORD = "123456";
public static void main(String[] args) throws ClassNotFoundException, SQLException {
//STEP 2: Register JDBC driver
Class.forName(JDBC_DRIVER);
ResultSet rs = null;
Connection conn = null;
PreparedStatement pstmt = null;
PreparedStatement pstmtUp = null;
try {
//STEP 3: Open a connection
//conn = DriverManager.getConnection(DATABASE_URL, USER, PASSWORD);
//使用資料庫 URL 和 Properties 物件
Properties info = new Properties();
info.put("user", USER);
info.put("password", PASSWORD);
conn = DriverManager.getConnection(DATABASE_URL, info);
pstmtUp = conn.prepareStatement("UPDATE t_user SET phone = ? WHERE id = ?");
pstmtUp.setString(1,"13565422554");
pstmtUp.setInt(2,2);
int execute = pstmtUp.executeUpdate();
System.out.println("execute update :"+execute);
//STEP 4: Execute a query
pstmt = conn.prepareStatement("SELECT id, name, dept, phone FROM t_user WHERE id = ? and name = ?");
pstmt.setInt(1,2);
pstmt.setString(2,"艾米哈珀");
rs = pstmt.executeQuery();
while (rs.next()) {
//Retrieve by column name 通過列名獲取值
int id = rs.getInt("id");
String name = rs.getString("name");
String dept = rs.getString("dept");
String phone = rs.getString("phone");
//Display values
System.out.println("id: " + id + "name: " + name + "dept: " + dept + "phone: " + phone);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
rs.close();
//關閉 PreparedStatement 物件
pstmt.close();
pstmtUp.close();
conn.close();
}
System.out.println("over !");
}
}
輸出:
execute update :1
id: 2name: 艾米哈珀dept: amy empirephone: 13565422554
over !
CallableStatement 物件
CallableStatement是建立被用來執行呼叫資料庫儲存過程的 CallableStatement 物件。
三種型別的引數有:IN,OUT 和 INOUT。PreparedStatement 物件只使用 IN 引數。CallableStatement 物件可以使用所有的三個引數。
這裡是每個引數的定義:
引數 描述
IN 在 SQL 語句建立的時候該引數是未知的。你可以用 setXXX() 方法將值繫結到IN引數中。
OUT 該引數由 SQL 語句的返回值提供。你可以用 getXXX() 方法獲取 OUT 引數的值。
INOUT 該引數同時提供輸入輸出的值。你可以用 setXXX() 方法將值繫結引數,並且用 getXXX() 方法獲取值。
前提:
儲存過程
DELIMITER $$
DROP PROCEDURE IF EXISTS `hello_mybatis`.`getNameById` $$
CREATE PROCEDURE `hello_mybatis`.`getNameById`
(IN USER_ID INT, OUT USER_NAME varchar(64))
BEGIN
SELECT NAME INTO USER_NAME
FROM t_user
WHERE ID = USER_ID;
END $$
DELIMITER ;
示例:
public class JDBCTest3 {
private static final String JDBC_DRIVER = "com.mysql.jdbc.Driver";
private static final String DATABASE_URL = "jdbc:mysql://localhost:3306/hello_mybatis";
private static final String USER = "root";
private static final String PASSWORD = "123456";
public static void main(String[] args) throws ClassNotFoundException, SQLException {
//STEP 2: Register JDBC driver
Class.forName(JDBC_DRIVER);
Connection conn = null;
CallableStatement cstmt= null;
try {
//STEP 3: Open a connection
//conn = DriverManager.getConnection(DATABASE_URL, USER, PASSWORD);
//使用資料庫 URL 和 Properties 物件
Properties info = new Properties();
info.put("user", USER);
info.put("password", PASSWORD);
conn = DriverManager.getConnection(DATABASE_URL, info);
//注意,不管是入參出參都有有?佔位符,否則會執行失敗
String sql ="{call getNameById (?,?)}";
cstmt = conn.prepareCall(sql);
cstmt.setInt("USER_ID",2);
cstmt.registerOutParameter("USER_NAME",Types.VARCHAR);
//也可以根據列的索引,1,2這樣進行設定引數
cstmt.execute();
String userName = cstmt.getString("USER_NAME");
System.out.println("CallableStatement userName: "+userName);
} catch (SQLException e) {
e.printStackTrace();
} finally {
cstmt.close();
conn.close();
}
System.out.println("over !");
}
}
輸出:
CallableStatement userName: 艾米哈珀
over !
報錯:
java.sql.SQLException: Can't find local placeholder mapping for parameter named "USER_NAME".
這是因為執行儲存過程時,不管是入參出參都有有?佔位符,否則會執行失敗
//錯誤
String sql ="{call getNameById (?)}";
//正確
String sql ="{call getNameById (?,?)}";
CallableStatement參考
JDBC 結果集
SQL 語句從資料庫查詢中獲取資料,並將資料返回到結果集中。SELECT 語句是一種標準的方法,它從一個資料庫中選擇行記錄,並顯示在一個結果集中。 java.sql.ResultSet 介面表示一個資料庫查詢的結果集。
一個 ResultSet 物件控制一個游標指向當前行的結果集。術語“結果集”是指包含在 ResultSet 物件中的行和列的資料。
ResultSet 介面的方法可細分為三類-
- 導航方法:用於移動游標。
- 獲取方法:用於檢視當前行被游標所指向的列中的資料。
- 更新方法:用於更新當前行的列中的資料。這些更新也會更新資料庫中的資料。
游標的移動基於 ResultSet 的屬性。用相應的語句生成 ResultSet 物件時,同時生成 ResultSet 的屬性。
JDBC 提供了連線方法通過下列建立語句來生成你所需的 ResultSet 物件:
- createStatement(int RSType, int RSConcurrency);
- prepareStatement(String SQL, int RSType, int RSConcurrency);
- prepareCall(String sql, int RSType, int RSConcurrency);
第一個參數列示 ResultSet 物件的型別,第二個引數是兩個 ResultSet 常量之一,該常量用於判斷該結果集是隻讀的還是可修改的。
ResultSet 的型別
可能的 RSType 如下所示。如果你不指定 ResultSet 型別,將自動獲得的值是 TYPE_FORWARD_ONLY。
型別 描述
ResultSet.TYPE_FORWARD_ONLY 游標只能在結果集中向前移動。
ResultSet.TYPE_SCROLL_INSENSITIVE 游標可以向前和向後移動。當結果集建立後,其他人對資料庫的操作不會影響結果集的資料。
ResultSet.TYPE_SCROLL_SENSITIVE. 游標可以向前和向後移動。當結果集建立後,其他人對資料庫的操作會影響結果集的資料。
ResultSet 的併發性
RSConcurrency 的值如下所示,如果你不指定併發型別,將自動獲得的值是 CONCUR_READ_ONLY。
併發性 描述
ResultSet.CONCUR_READ_ONLY 建立一個只讀結果集,這是預設的值。
ResultSet.CONCUR_UPDATABLE 建立一個可修改的結果集。
//預設初始化一個 Statement 物件來建立一個只能前進,而且只讀的 ResultSet 物件。
conn.createStatement();
在 ResultSet 介面中包括如下幾種方法涉及移動游標:
S.N. 方法 & 描述
1 public void beforeFirst() throws SQLException將游標移動到第一行之前。
2 public void afterLast() throws SQLException將游標移動到最後一行之後。
3 public boolean first() throws SQLException將游標移動到第一行。
4 public void last() throws SQLException將游標移動到最後一行。
5 public boolean absolute(int row) throws SQLException將游標移動到指定的第 row 行。
6 public boolean relative(int row) throws SQLException將游標移動到當前指向的位置往前或往後第 row 行的位置。
7 public boolean previous() throws SQLException將游標移動到上一行,如果超過結果集的範圍則返回 false。
8 public boolean next() throws SQLException將游標移動到下一行,如果是結果集的最後一行則返回 false。
9 public int getRow() throws SQLException返回當前游標指向的行數的值。
10 public void moveToInsertRow() throws SQLException將游標移動到結果集中指定的行,可以在資料庫中插入新的一行。當前游標位置將被記住。
11 public void moveToCurrentRow() throws SQLException如果游標處於插入行,則將游標返回到當前行,其他情況下,這個方法不執行任何操作。
檢視結果集
ResultSet介面中含有幾十種從當前行獲取資料的方法。
每個可能的資料型別都有一個 get 方法,並且每個 get 方法有兩個版本-
- 一個需要列名。
- 一個需要列的索引。
例如,如果你想檢視的列包含一個 int 型別,你需要在 ResultSet 中呼叫 getInt()方法-
S.N. 方法 & 描述
1 public int getInt(String columnName) throws SQLException返回當前行中名為 columnName 的列的 int 值。
2 public int getInt(int columnIndex) throws SQLException返回當前行中指定列的索引的 int 值。列索引從 1 開始,意味著行中的第一列是 1 ,第二列是 2 ,以此類推。
同樣的,在 ResultSet 介面中還有獲取八個 Java 原始型別的 get 方法,以及常見的型別。
更新的結果集(一般不這樣更新,瞭解)
ResultSet 介面包含了一系列的更新方法,該方法用於更新結果集中的資料。
用 updateString 方法可以有兩個更新方法來更新任一資料型別-
- 一個需要列名。
- 一個需要列的索引。
例如,要更新一個結果集的當前行的 String 列,你可以使用任一如下所示的 updateString()方法-
S.N. 方法 & 描述
1 public void updateString(int columnIndex, String s) throws SQLException將指定列的字串的值改為 s。
2 public void updateString(String columnName, String s) throws SQLException類似於前面的方法,不同之處在於指定的列是用名字來指定的,而不是它的索引。
八個原始資料型別都有其更新方法,比如 String,Object,URL,和在 java.sql 包中的 SQL 資料型別。
更新結果集中的行將改變當前行的列中的 ResultSet 物件,而不是基礎資料庫中的資料。要更新資料庫中一行的資料,你需要呼叫以下的任一方法。
S.N. 方法 & 描述
1 public void updateRow()通過更新資料庫中相對應的行來更新當前行。
2 public void deleteRow()從資料庫中刪除當前行。
3 public void refreshRow()在結果集中重新整理資料,以反映資料庫中最新的資料變化。
4 public void cancelRowUpdates()取消對當前行的任何修改。
5 public void insertRow()在資料庫中插入一行。本方法只有在游標指向插入行的時候才能被呼叫。
當使用ResultSet的時候,當查詢出來的資料集記錄很多,有一千萬條的時候,那rs所指的物件是否會佔用很多記憶體,如果記錄過多,那程式會不會把系統的記憶體用光呢 ?
不會,ResultSet表面看起來是一個記錄集,其實這個物件中只是記錄了結果集的相關資訊,具體的記錄並沒有存放在物件中,具體的記錄內容直到你通過next方法提取的時候,再通過相關的getXXXXX方法提取欄位內容的時候才能從資料庫中得到,這些並不會佔用記憶體,具體消耗記憶體是由於你將記錄集中的資料提取出來加入到你自己的集合中的時候才會發生,如果你沒有使用集合記錄所有的記錄就不會發生消耗記憶體厲害的情況。
JDBC 資料型別
JDBC 驅動程式在將 Java 資料型別傳送到資料庫之前,會將其轉換為相應的 JDBC 型別。對於大多數資料型別都採用了預設的對映關係。例如,一個 Java int 資料型別轉換為 SQL INTEGER。通過預設的對映關係來提供驅動程式之間的一致性。
ResultSet 物件為任一資料型別提供相應的 getXXX()方法,該方法可以獲取任一資料型別的列值。上述任一方法的使用需要列名或它的順序位置。
SQL JDBC/Java setXXX getXXX
VARCHAR java.lang.String setString getString
CHAR java.lang.String setString getString
LONGVARCHAR java.lang.String setString getString
BIT boolean setBoolean getBoolean
NUMERIC java.math.BigDecimal setBigDecimal getBigDecimal
TINYINT byte setByte getByte
SMALLINT short setShort getShort
INTEGER int setInt getInt
BIGINT long setLong getLong
REAL float setFloat getFloat
FLOAT float setFloat getFloat
DOUBLE double setDouble getDouble
VARBINARY byte[ ] setBytes getBytes
BINARY byte[ ] setBytes getBytes
DATE java.sql.Date setDate getDate
TIME java.sql.Time setTime getTime
TIMESTAMP java.sql.Timestamp setTimestamp getTimestamp
CLOB java.sql.Clob setClob getClob
BLOB java.sql.Blob setBlob getBlob
ARRAY java.sql.Array setARRAY getARRAY
REF java.sql.Ref SetRef getRef
STRUCT java.sql.Struct SetStruct getStruct
處理 NULL 值
SQL 使用 NULL 值和 Java 使用 null 是不同的概念。那麼,你可以使用三種策略來處理 Java 中的 SQL NULL 值-
- 避免使用返回原始資料型別的 getXXX()方法。
- 使用包裝類的基本資料型別,並使用 ResultSet 物件的 wasNull()方法來測試收到 getXXX()方法返回的值是否為 null,如果是 null,該包裝類變數則被設定為 null。
- 使用原始資料型別和 ResultSet 物件的 wasNull()方法來測試通過 getXXX()方法返回的值,如果是 null,則原始變數應設定為可接受的值來代表 NULL。
JDBC 事務
如果你的 JDBC 連線是處於自動提交模式下,該模式為預設模式,那麼每句 SQL 語句都是在其完成時提交到資料庫。
對簡單的應用程式來說這種模式相當好,但有三個原因你可能想關閉自動提交模式,並管理你自己的事務-
- 為了提高效能
- 為了保持業務流程的完整性
- 使用分散式事務
JDBC 驅動程式預設使用的自動提交模式 ,需要conn.setAutoCommit(false);關閉自動提交模式 .
提交和回滾
conn.commit( );
conn.rollback( );
還原點
Connection 物件有兩個新的方法來管理還原點-
- setSavepoint(String savepointName): 定義了一個新的還原點。它也返回一個 Savepoint 物件。
- releaseSavepoint(Savepoint savepointName): 刪除一個還原點。請注意,它需要一個作為引數的 Savepoint 物件。這個物件通常是由 setSavepoint() 方法生成的一個還原點。
有一個 rollback (String savepointName) 方法,該方法可以回滾到指定的還原點。
示例:
public class JDBCTranctionTest {
private static final String JDBC_DRIVER = "com.mysql.jdbc.Driver";
private static final String DATABASE_URL = "jdbc:mysql://localhost:3306/hello_mybatis";
private static final String USER = "root";
private static final String PASSWORD = "123456";
public static void main(String[] args) throws ClassNotFoundException, SQLException {
//STEP 2: Register JDBC driver
Class.forName(JDBC_DRIVER);
ResultSet rs = null;
Connection conn = null;
PreparedStatement pstmt = null;
PreparedStatement pstmtUp = null;
Savepoint savepoint = null;
try {
//STEP 3: Open a connection
//conn = DriverManager.getConnection(DATABASE_URL, USER, PASSWORD);
//使用資料庫 URL 和 Properties 物件
Properties info = new Properties();
info.put("user", USER);
info.put("password", PASSWORD);
conn = DriverManager.getConnection(DATABASE_URL, info);
//關閉自動提交模式
conn.setAutoCommit(false);
pstmtUp = conn.prepareStatement("UPDATE t_user SET phone = ? WHERE id = ?");
pstmtUp.setString(1,"22025555");
pstmtUp.setInt(2,2);
int execute = pstmtUp.executeUpdate();
System.out.println("execute update1 :"+execute);
//想回滾到還原點,那麼之前執行的sql要提交事務,否則這個還原點就沒有意義,因為一整個事務都沒有提交
conn.commit();
//建立還原點
savepoint = conn.setSavepoint("updateSavepoint1");
pstmtUp.setString(1,"22026666");
pstmtUp.setInt(2,2);
execute = pstmtUp.executeUpdate();
System.out.println("execute update2 :"+execute);
//報錯
//System.out.println(1/0);
//STEP 4: Execute a query
pstmt = conn.prepareStatement("SELECT id, name, dept, phone FROM t_user WHERE id = ? and name = ?");
pstmt.setInt(1,2);
pstmt.setString(2,"艾米哈珀");
rs = pstmt.executeQuery();
while (rs.next()) {
//Retrieve by column name 通過列名獲取值
int id = rs.getInt("id");
String name = rs.getString("name");
String dept = rs.getString("dept");
String phone = rs.getString("phone");
//Display values
System.out.println("id: " + id + "name: " + name + "dept: " + dept + "phone: " + phone);
}
//提交事務
conn.commit();
//當我們設定不提交時,雖然查出來的是最新更改的值,但資料庫其實並沒有更改,因為我們沒有手動提交
//查出來的是最新更改的值是事務隔離型別的原因
} catch (SQLException e) {
//回滾事務
//conn.rollback();
//回滾到還原點
e.printStackTrace();
conn.rollback(savepoint);
} finally {
//關閉 PreparedStatement 物件
pstmt.close();
pstmtUp.close();
conn.close();
}
System.out.println("over !");
}
}
注意:還原點使用特別注意。
//想回滾到還原點,那麼之前執行的sql要提交事務,否則這個還原點就沒有意義,因為一整個事務都沒有提交
conn.commit();
//建立還原點
savepoint = conn.setSavepoint("updateSavepoint1");
//回滾到還原點
conn.rollback(savepoint);
JDBC 批處理
Statement不能執行帶引數佔位符?的sql語句。
批處理注意用於增刪改,因為返回的不是物件。
批處理是指你將關聯的 SQL 語句組合成一個批處理,並將他們當成一個呼叫提交給資料庫。
當你一次傳送多個 SQL 語句到資料庫時,可以減少通訊的資源消耗,從而提高了效能。
- JDBC 驅動程式不一定支援該功能。你可以使用 DatabaseMetaData.supportsBatchUpdates() 方法來確定目標資料庫是否支援批處理更新。如果你的JDBC驅動程式支援此功能,則該方法返回值為 true。
- Statement,PreparedStatement 和 CallableStatement 的 addBatch() 方法用於新增單個語句到批處理。
- executeBatch() 方法用於啟動執行所有組合在一起的語句。
- executeBatch() 方法返回一個整數陣列,陣列中的每個元素代表了各自的更新語句的更新數目。
- 正如你可以新增語句到批處理中,你也可以用 clearBatch() 方法刪除它們。此方法刪除所有用 addBatch() 方法新增的語句。但是,你不能有選擇性地選擇要刪除的語句。
批處理和 Statement 物件
注意可以不手動控制事務。
使用 Statement 物件來使用批處理所需要的典型步驟如下所示-
- 使用 createStatement() 方法建立一個 Statement 物件。
- 使用 setAutoCommit() 方法將自動提交設為 false。
- 被建立的 Statement 物件可以使用 addBatch() 方法來新增你想要的所有SQL語句。
- 被建立的 Statement 物件可以用 executeBatch() 將所有的 SQL 語句執行。
- 最後,使用 commit() 方法提交所有的更改。
批處理和 PrepareStatement 物件
使用 prepareStatement 物件來使用批處理需要的典型步驟如下所示-
- 使用佔位符建立 SQL 語句。
- 使用任一 prepareStatement() 方法建立 prepareStatement 物件。
- 使用 setAutoCommit() 方法將自動提交設為 false。
- 被建立的 Statement 物件可以使用 addBatch() 方法來新增你想要的所有 SQL 語句。
- 被建立的 Statement 物件可以用 executeBatch() 將所有的 SQL 語句執行。
- 最後,使用 commit() 方法提交所有的更改。
示例:
public class JDBCBatchTest {
private static final String JDBC_DRIVER = "com.mysql.jdbc.Driver";
private static final String DATABASE_URL = "jdbc:mysql://localhost:3306/hello_mybatis";
private static final String USER = "root";
private static final String PASSWORD = "123456";
public static void main1(String[] args) throws ClassNotFoundException, SQLException {
//STEP 2: Register JDBC driver
Class.forName(JDBC_DRIVER);
Connection conn = null;
PreparedStatement pstmtUp = null;
try {
//STEP 3: Open a connection
//conn = DriverManager.getConnection(DATABASE_URL, USER, PASSWORD);
//使用資料庫 URL 和 Properties 物件
Properties info = new Properties();
info.put("user", USER);
info.put("password", PASSWORD);
conn = DriverManager.getConnection(DATABASE_URL, info);
//關閉自動提交模式
//conn.setAutoCommit(false);
pstmtUp = conn.prepareStatement("UPDATE t_user SET phone = ? WHERE id = ?");
pstmtUp.setString(1,"22025555");
pstmtUp.setInt(2,2);
pstmtUp.addBatch();
pstmtUp.setString(1,"22026666");
pstmtUp.setInt(2,1);
pstmtUp.addBatch();
pstmtUp.setString(1,"22027777");
pstmtUp.setInt(2,3);
pstmtUp.addBatch();
int[] ints = pstmtUp.executeBatch();
System.out.println(Arrays.toString(ints));
//提交事務
//conn.commit();
//當我們設定不提交時,雖然查出來的是最新更改的值,但資料庫其實並沒有更改,因為我們沒有手動提交
//查出來的是最新更改的值是事務隔離型別的原因
} catch (Exception e) {
//回滾事務
//conn.rollback();
//回滾到還原點
e.printStackTrace();
} finally {
//關閉 PreparedStatement 物件
pstmtUp.close();
conn.close();
}
System.out.println("over !");
}
public static void main(String[] args) throws ClassNotFoundException, SQLException {
//STEP 2: Register JDBC driver
Class.forName(JDBC_DRIVER);
Connection conn = null;
Statement stmt = null;
try {
//STEP 3: Open a connection
//conn = DriverManager.getConnection(DATABASE_URL, USER, PASSWORD);
//使用資料庫 URL 和 Properties 物件
Properties info = new Properties();
info.put("user", USER);
info.put("password", PASSWORD);
conn = DriverManager.getConnection(DATABASE_URL, info);
//關閉自動提交模式
//conn.setAutoCommit(false);
//Statement只能執行不帶引數的sql
stmt = conn.createStatement();
String sql;
sql = "UPDATE t_user SET phone = '123456' WHERE id = 1";
stmt.addBatch(sql);
sql = "UPDATE t_user SET phone = '123456' WHERE id = 2";
stmt.addBatch(sql);
sql = "UPDATE t_user SET phone = '123456' WHERE id = 3";
stmt.addBatch(sql);
//批量處理
int[] ints = stmt.executeBatch();
System.out.println(Arrays.toString(ints));
//提交事務
//conn.commit();
//當我們設定不提交時,雖然查出來的是最新更改的值,但資料庫其實並沒有更改,因為我們沒有手動提交
//查出來的是最新更改的值是事務隔離型別的原因
} catch (Exception e) {
//回滾事務
//conn.rollback();
//回滾到還原點
e.printStackTrace();
} finally {
//關閉 PreparedStatement 物件
stmt.close();
conn.close();
}
System.out.println("over !");
}
}
JDBC 流資料
PreparedStatement 物件必須具備使用輸入和輸出流來提供引數資料的能力。這使你能夠將整個檔案儲存到資料庫列中,這樣資料庫就能儲存大型資料,例如 CLOB 和 BLOB 資料型別。
用於流資料有下列幾種方法-
- setAsciiStream(): 該方法是用來提供較大的 ASCII 值。
- setCharacterStream(): 該方法是用來提供較大的 UNICODE 值。
- setBinaryStream(): 該方法是用來提供較大的二進位制值。
setXXXStream()方法需要一個額外的引數,該引數是除了引數佔位符的檔案大小。這個引數通知驅動程式通過使用流有多少資料被髮送到資料庫中。
一般不會用這個,都是直接把資料轉為String型別直接存到text欄位裡吧?
JDBC 建立資料庫例項
stmt = conn.createStatement();
String sql = "CREATE DATABASE hello_spring";
stmt.executeUpdate(sql);
JDBC 刪除資料庫例項
使用的使用者必須擁有刪除資料庫的許可權,這也側面反映當我們給專案設定資料庫訪問使用者時,要注意許可權的賦予,像資料庫操作、DDL這樣的許可權是不應該有的。
stmt = conn.createStatement();
String sql = "DROP DATABASE STUDENTS";
stmt.executeUpdate(sql);
JDBC 建立表例項
stmt = conn.createStatement();
String sql = "CREATE TABLE REGISTRATION " +
"(id INTEGER not NULL, " +
" first VARCHAR(255), " +
" last VARCHAR(255), " +
" age INTEGER, " +
" PRIMARY KEY ( id ))";
stmt.executeUpdate(sql);
System.out.println("Create
JDBC 刪除表例項
stmt = conn.createStatement();
String sql = "DROP TABLE REGISTRATION ";
stmt.executeUpdate(sql);
JDBC 插入記錄例項
下面這個示例在專案運用中其實應該使用PreparedStatement來插入資料,用佔位符的形式來建立sql。
pstmtUp = conn.prepareStatement("INSERT INTO Registration " +
"VALUES (?, ?, ?, ?)");
stmt = conn.createStatement();
String sql = "INSERT INTO Registration " +
"VALUES (100, 'Zara', 'Ali', 18)";
stmt.executeUpdate(sql);
sql = "INSERT INTO Registration " +
"VALUES (101, 'Mahnaz', 'Fatma', 25)";
stmt.executeUpdate(sql);
sql = "INSERT INTO Registration " +
"VALUES (102, 'Zaid', 'Khan', 30)";
stmt.executeUpdate(sql);
sql = "INSERT INTO Registration " +
"VALUES(103, 'Sumit', 'Mittal', 28)";
stmt.executeUpdate(sql);
JDBC 查詢記錄例項
pstmt = conn.prepareStatement("SELECT id, name, dept, phone FROM t_user WHERE id = ? and name = ?");
pstmt.setInt(1,2);
pstmt.setString(2,"艾米哈珀");
rs = pstmt.executeQuery();
while (rs.next()) {
//Retrieve by column name 通過列名獲取值
int id = rs.getInt("id");
String name = rs.getString("name");
String dept = rs.getString("dept");
String phone = rs.getString("phone");
//Display values
System.out.println("id: " + id + "name: " + name + "dept: " + dept + "phone: " + phone);
}
JDBC 更新記錄例項
pstmtUp = conn.prepareStatement("UPDATE t_user SET phone = ? WHERE id = ?");
pstmtUp.setString(1,"22025555");
pstmtUp.setInt(2,2);
int execute = pstmtUp.executeUpdate();
System.out.println("execute update1 :"+execute);
JDBC 刪除記錄例項
應該使用PreparedStatement來刪除資料,用佔位符的形式來建立sql。
stmt = conn.createStatement();
String sql = "DELETE FROM Registration " +
"WHERE id = 101";
stmt.executeUpdate(sql);
JDBC LIKE 子句例項
LIKE查詢推薦使用:%放在佔位符中或使用concat函式。
1:%放在佔位符中
parameters.add("%"+familyMemberQueryBean.getFullName()+"%");
sql+=" and t.full_name like ?";
2:使用concat函式
parameters.add(familyMemberQueryBean.getFullName());
sql+=" and t.full_name like concat('%',?,'%')";
3:使用轉義字元\,百分號直接寫在sql語句中
parameters.add(familyMemberQueryBean.getFullName());
sql+=" and t.full_name like \"%\"?\"%\"";
直接查詢的SQL語句如下:SELECT id,full_name,email,phone,remark FROM family_member t WHERE 1 = 1
AND t.full_name LIKE "%"'李'"%" AND t.email LIKE "%"'qq'"%"
4:直接拼接SQL
sql+=" and t.full_name like '%"+familyMemberQueryBean.getFullName()+"%'";
參考:JDBC模糊查詢的4種方式
示例:
public class JDBCTranctionTest {
private static final String JDBC_DRIVER = "com.mysql.jdbc.Driver";
private static final String DATABASE_URL = "jdbc:mysql://localhost:3306/hello_mybatis";
private static final String USER = "root";
private static final String PASSWORD = "123456";
public static void main(String[] args) throws ClassNotFoundException, SQLException {
//STEP 2: Register JDBC driver
Class.forName(JDBC_DRIVER);
ResultSet rs = null;
Connection conn = null;
PreparedStatement pstmt = null;
PreparedStatement pstmtUp = null;
Savepoint savepoint = null;
try {
//STEP 3: Open a connection
//conn = DriverManager.getConnection(DATABASE_URL, USER, PASSWORD);
//使用資料庫 URL 和 Properties 物件
Properties info = new Properties();
info.put("user", USER);
info.put("password", PASSWORD);
conn = DriverManager.getConnection(DATABASE_URL, info);
//關閉自動提交模式
conn.setAutoCommit(false);
pstmtUp = conn.prepareStatement("UPDATE t_user SET phone = ? WHERE id = ?");
pstmtUp.setString(1,"22025555");
pstmtUp.setInt(2,2);
int execute = pstmtUp.executeUpdate();
System.out.println("execute update1 :"+execute);
//想回滾到還原點,那麼之前執行的sql要提交事務,否則這個還原點就沒有意義,因為一整個事務都沒有提交
conn.commit();
//建立還原點
savepoint = conn.setSavepoint("updateSavepoint1");
pstmtUp.setString(1,"22026666");
pstmtUp.setInt(2,2);
execute = pstmtUp.executeUpdate();
System.out.println("execute update2 :"+execute);
//報錯
//System.out.println(1/0);
//STEP 4: Execute a query
//pstmt = conn.prepareStatement("SELECT id, name, dept, phone FROM t_user WHERE id = ? and name = ?");
//JDBC模糊查詢的4種方式
//1、%放在佔位符中
/* pstmt = conn.prepareStatement("SELECT id, name, dept, phone FROM t_user WHERE id = ? and name like ? ");
pstmt.setInt(1,1);
String userName = "青";
pstmt.setString(2,"%" + userName + "%");
rs = pstmt.executeQuery();*/
//2、使用concat函式
/* pstmt = conn.prepareStatement("SELECT id, name, dept, phone FROM t_user WHERE id = ? and name like concat('%',?,'%')");
pstmt.setInt(1,1);
String userName = "青";
pstmt.setString(2,userName);
rs = pstmt.executeQuery();*/
//3、使用轉義字元\,百分號直接寫在sql語句中
/*pstmt = conn.prepareStatement("SELECT id, name, dept, phone FROM t_user WHERE id = ? and name like \"%\"?\"%\"");
pstmt.setInt(1,1);
String userName = "青";
pstmt.setString(2,userName);
rs = pstmt.executeQuery();*/
//4、直接拼接SQL
Statement stmt = conn.createStatement();
String sql ="SELECT id, name, dept, phone FROM t_user WHERE id = 1 and name like '%青%' ";
rs = stmt.executeQuery(sql);
while (rs.next()) {
//Retrieve by column name 通過列名獲取值
int id = rs.getInt("id");
String name = rs.getString("name");
String dept = rs.getString("dept");
String phone = rs.getString("phone");
//Display values
System.out.println("id: " + id + "name: " + name + "dept: " + dept + "phone: " + phone);
}
//提交事務
conn.commit();
//當我們設定不提交時,雖然查出來的是最新更改的值,但資料庫其實並沒有更改,因為我們沒有手動提交
//查出來的是最新更改的值是事務隔離型別的原因
} catch (Exception e) {
//回滾事務
//conn.rollback();
//回滾到還原點
e.printStackTrace();
conn.rollback(savepoint);
} finally {
//關閉 PreparedStatement 物件
pstmt.close();
pstmtUp.close();
conn.close();
}
System.out.println("over !");
}
}
JDBC 排序例項
sql語句加入 order by column desc 或 order by column asc;
stmt = conn.createStatement();
String sql = "SELECT id, first, last, age FROM Registration" +
" ORDER BY first ASC";
ResultSet rs = stmt.executeQuery(sql);
疑問:
Q:ResultSet 這個結果集是個怎麼樣的結構,為什麼要這樣設計,內部是什麼容器?
ResultSet rs = stmt.executeQuery(sql);
Q:PreparedStatement計劃多次使用 SQL 語句的時候使用是什麼意思?每條sql都會被多次使用啊?
Q:conn.createStatement();預設初始化一個 Statement 物件來建立一個只能前進,而且只讀的 ResultSet 物件。那為什麼可以使用這些前後移動的方法呢?
rs.previous();
rs.absolute(1);
Q:JDBC 流資料還有用到麼?什麼場景裡會用?一般不會用這個,都是直接把資料轉為String型別直接存到text欄位裡吧?