Java基礎教程:JDBC編程
1、什么是JDBC
JDBC 指 Java 數據庫連接,是一種標准Java應用編程接口( JAVA API),用來連接 Java 編程語言和廣泛的數據庫。
JDBC API 庫包含下面提到的每個任務,都是與數據庫相關的常用用法。
- 制作到數據庫的連接。
- 創建 SQL 或 MySQL 語句。
- 執行 SQL 或 MySQL 查詢數據庫。
- 查看和修改所產生的記錄。
從根本上來說,JDBC 是一種規范,它提供了一套完整的接口,允許便攜式訪問到底層數據庫,因此可以用 Java 編寫不同類型的可執行文件,例如:
- Java 應用程序
- Java Applets
- Java Servlets
- Java ServerPages (JSPs)
- Enterprise JavaBeans (EJBs)
所有這些不同的可執行文件就可以使用 JDBC 驅動程序來訪問數據庫,這樣可以方便的訪問數據。JDBC 具有 ODBC 一樣的性能,允許 Java 程序包含與數據庫無關的代碼。
2、JDBC架構
-
JDBC API: 提供了應用程序對 JDBC 管理器的連接。
- JDBC Driver API: 提供了 JDBC 管理器對驅動程序連接。
- JDBC 驅動程序管理器可確保正確的驅動程序來訪問每個數據源。該驅動程序管理器能夠支持連接到多個異構數據庫的多個並發的驅動程序。
3、常見的JDBC組件
如下所示為JDBC開發中主要用到的類。
- DriverManager :這個類管理一系列數據庫驅動程序。匹配連接使用通信子協議從 JAVA 應用程序中請求合適的數據庫驅動程序。識別 JDBC 下某個子協議的第一驅動程序將被用於建立數據庫連接。
- Driver : 這個接口處理與數據庫服務器的通信。你將很少直接與驅動程序互動。相反,你使用 DriverManager 中的對象,它管理此類型的對象。它也抽象與驅動程序對象工作相關的詳細信息。
- Connection : 此接口具有接觸數據庫的所有方法。該連接對象表示通信上下文,即,所有與數據庫的通信僅通過這個連接對象進行。
- Statement : 使用創建於這個接口的對象將 SQL 語句提交到數據庫。除了執行存儲過程以外,一些派生的接口也接受參數。
- ResultSet : 在你使用語句對象執行 SQL 查詢后,這些對象保存從數據獲得的數據。它作為一個迭代器,讓您可以通過它的數據來移動。
- SQLException : 這個類處理發生在數據庫應用程序的任何錯誤。
4、開發流程
1.下載JDBC-MySQL官方驅動
2.將JDBC驅動加入到環境變量中
3.示例代碼
package com.company;
//第一步:導入必須的包
import java.sql.*;
public class Main {
//第二步:說明JDBC驅動的名稱和數據庫的地址
static final String JDBC_DRIVER = "com.mysql.jdbc.Driver";
static final String DB_URL = "jdbc:mysql://localhost:3306/test";
//第三步:說明數據庫的認證賬戶及密碼
static final String USER = "root";
static final String PASS = "123456";
public static void main(String[] args) {
//第四步:注冊JDBC驅動
try {
Class.forName(JDBC_DRIVER);
} catch (ClassNotFoundException e) {
//這里會發生類沒有找到的異常!
e.printStackTrace();
}
//第五步:獲得數據庫連接
try {
Connection connection = DriverManager.getConnection(DB_URL,USER,PASS);
//第六步:執行查詢語句
Statement statement = connection.createStatement();
String sql = "SELECT * FROM crawler_article";
ResultSet rs =statement.executeQuery(sql);
while (rs.next())
{
String title = rs.getString("title");
String author = rs.getString("author");
System.out.println(title+":"+author);
}
//第七步:關閉連接資源
rs.close();
statement.close();
connection.close();
} catch (SQLException e) {
e.printStackTrace();
//這里會發生SQL異常,因為我們提供的的賬戶和密碼不一定能連接成功
}
}
}
5、進階內容
數據類型
Java數據類型與SQL數據類型的轉換:
結果集
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 接口中包括如下幾種方法涉及移動光標-
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 方法,以及常見的類型,比如 java.lang.String,java.lang.Object 和 java.net.URL。
也有用於獲取 SQL 數據類型 java.sql.Date, java.sql.Time, java.sql.Timestamp, java.sql.Clob,java.sql.Blob 中的方法。查看文檔可以了解使用這些 SQL 數據類型的更多的信息。
更新的結果集
ResultSet 接口包含了一系列的更新方法,該方法用於更新結果集中的數據。
用 get 方法可以有兩個更新方法來更新任一數據類型-
- 一個需要列名。
- 一個需要列的索引。
例如,要更新一個結果集的當前行的 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() 在數據庫中插入一行。本方法只有在光標指向插入行的時候才能被調用。 |
事物
如果你的 JDBC 連接是處於自動提交模式下,該模式為默認模式,那么每句 SQL 語句都是在其完成時提交到數據庫。
對簡單的應用程序來說這種模式相當好,但有三個原因你可能想關閉自動提交模式,並管理你自己的事務-
- 為了提高性能
- 為了保持業務流程的完整性
- 使用分布式事務
你可以通過事務在任意時間來控制以及更改應用到數據庫。它把單個 SQL 語句或一組 SQL 語句作為一個邏輯單元,如果其中任一語句失敗,則整個事務失敗。
若要啟用手動事務模式來代替 JDBC 驅動程序默認使用的自動提交模式的話,使用 Connection 對象的的 setAutoCommit()方法。如果傳遞一個布爾值 false 到 setAutoCommit()方法,你就關閉自動提交模式。你也可以傳遞一個布爾值 true 將其再次打開。
例如,如果有一個名為 conn 的 Connection 對象,以下的代碼將關閉自動提交模式-
conn.setAutoCommit(false);
提交和回滾
當你完成了你的修改,並且要提交你的修改,可以在 connection 對象里調用 commit()方法,如下所示-
conn.commit( );
另外,用名為 conn 的連接回滾數據到數據庫,使用如下所示的代碼-
conn.rollback( );
下面的例子說明了如何使用提交和回滾對象-
try{
//Assume a valid connection object conn
conn.setAutoCommit(false);
Statement stmt = conn.createStatement();
String SQL = "INSERT INTO Employees " +
"VALUES (106, 20, 'Rita', 'Tez')";
stmt.executeUpdate(SQL);
//Submit a malformed SQL statement that breaks
String SQL = "INSERTED IN Employees " +
"VALUES (107, 22, 'Sita', 'Singh')";
stmt.executeUpdate(SQL);
// If there is no error.
conn.commit();
}catch(SQLException se){
// If there is any error.
conn.rollback();
}
在這種情況下,之前的 INSERT 語句不會成功,一切都將被回滾到最初狀態。
使用還原點
新的 JDBC 3.0 還原點接口提供了額外的事務控制。大部分現代的數據庫管理系統的環境都支持設定還原點,例如 Oracle 的 PL/SQL。
當你在事務中設置一個還原點來定義一個邏輯回滾點。如果在一個還原點之后發生錯誤,那么可以使用 rollback 方法來撤消所有的修改或在該還原點之后所做的修改。
Connection 對象有兩個新的方法來管理還原點-
-
setSavepoint(String savepointName): 定義了一個新的還原點。它也返回一個 Savepoint 對象。
- releaseSavepoint(Savepoint savepointName): 刪除一個還原點。請注意,它需要一個作為參數的 Savepoint 對象。這個對象通常是由 setSavepoint() 方法生成的一個還原點。
有一個 rollback (String savepointName) 方法,該方法可以回滾到指定的還原點。
下面的例子說明了如何使用 Savepoint 對象-
try{
//Assume a valid connection object conn
conn.setAutoCommit(false);
Statement stmt = conn.createStatement();
//set a Savepoint
Savepoint savepoint1 = conn.setSavepoint("Savepoint1");
String SQL = "INSERT INTO Employees " +
"VALUES (106, 20, 'Rita', 'Tez')";
stmt.executeUpdate(SQL);
//Submit a malformed SQL statement that breaks
String SQL = "INSERTED IN Employees " +
"VALUES (107, 22, 'Sita', 'Tez')";
stmt.executeUpdate(SQL);
// If there is no error, commit the changes.
conn.commit();
}catch(SQLException se){
// If there is any error.
conn.rollback(savepoint1);
}
在這種情況下,之前的 INSERT 語句不會成功,一切都將被回滾到最初狀態。
批處理
批處理是指你將關聯的 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() 方法提交所有的更改。
示例
下面的代碼段提供了一個使用 Statement 對象批量更新的例子-
// Create statement object
Statement stmt = conn.createStatement();
// Set auto-commit to false
conn.setAutoCommit(false);
// Create SQL statement
String SQL = "INSERT INTO Employees (id, first, last, age) " +
"VALUES(200,'Zia', 'Ali', 30)";
// Add above SQL statement in the batch.
stmt.addBatch(SQL);
// Create one more SQL statement
String SQL = "INSERT INTO Employees (id, first, last, age) " +
"VALUES(201,'Raj', 'Kumar', 35)";
// Add above SQL statement in the batch.
stmt.addBatch(SQL);
// Create one more SQL statement
String SQL = "UPDATE Employees SET age = 35 " +
"WHERE id = 100";
// Add above SQL statement in the batch.
stmt.addBatch(SQL);
// Create an int[] to hold returned values
int[] count = stmt.executeBatch();
//Explicitly commit statements to apply changes
conn.commit();
批處理和 PrepareStatement 對象
使用 prepareStatement 對象來使用批處理需要的典型步驟如下所示-
- 使用占位符創建 SQL 語句。
- 使用任一 prepareStatement() 方法創建 prepareStatement 對象。
- 使用 setAutoCommit() 方法將自動提交設為 false。
- 被創建的 Statement 對象可以使用 addBatch() 方法來添加你想要的所有 SQL 語句。
- 被創建的 Statement 對象可以用 executeBatch() 將所有的 SQL 語句執行。
- 最后,使用 commit() 方法提交所有的更改。
下面的代碼段提供了一個使用 PrepareStatement 對象批量更新的示例-
// Create SQL statement
String SQL = "INSERT INTO Employees (id, first, last, age) " +
"VALUES(?, ?, ?, ?)";
// Create PrepareStatement object
PreparedStatemen pstmt = conn.prepareStatement(SQL);
//Set auto-commit to false
conn.setAutoCommit(false);
// Set the variables
pstmt.setInt( 1, 400 );
pstmt.setString( 2, "Pappu" );
pstmt.setString( 3, "Singh" );
pstmt.setInt( 4, 33 );
// Add it to the batch
pstmt.addBatch();
// Set the variables
pstmt.setInt( 1, 401 );
pstmt.setString( 2, "Pawan" );
pstmt.setString( 3, "Singh" );
pstmt.setInt( 4, 31 );
// Add it to the batch
pstmt.addBatch();
//add more batches
.
.
.
.
//Create an int[] to hold returned values
int[] count = stmt.executeBatch();
//Explicitly commit statements to apply changes
conn.commit();
存儲過程
創建 CallableStatement 對象
假設,你需要執行下面的 Oracle 存儲過程-
CREATE OR REPLACE PROCEDURE getEmpName
(EMP_ID IN NUMBER, EMP_FIRST OUT VARCHAR) AS
BEGIN
SELECT first INTO EMP_FIRST
FROM Employees
WHERE ID = EMP_ID;
END;
注意:上面的存儲過程是在 Oracle 使用的,但我們使用的是 MySQL 數據庫,所以我們在 MySQL 的環境下需要重新寫出相同功能的代碼,下面的代碼是在 EMP 數據庫中創建相同功能的代碼-
DELIMITER $$
DROP PROCEDURE IF EXISTS `EMP`.`getEmpName` $$
CREATE PROCEDURE `EMP`.`getEmpName`
(IN EMP_ID INT, OUT EMP_FIRST VARCHAR(255))
BEGIN
SELECT first INTO EMP_FIRST
FROM Employees
WHERE ID = EMP_ID;
END $$
DELIMITER ;
當前有三種類型的參數:IN,OUT 和 INOUT。PreparedStatement 對象只能使用 IN 參數。CallableStatement 對象可以使用所有的三種類型。
下面是三種類型參數的定義
CallableStatement cstmt = null;下面的代碼片段展示了如何使用 Connection.prepareCall() 方法實現一個基於上述存儲過程的 CallableStatement 對象-
try {
String SQL = "{call getEmpName (?, ?)}";
cstmt = conn.prepareCall (SQL);
. . .
}
catch (SQLException e) {
. . .
}
finally {
. . .
}
字符串變量 SQL 使用參數占位符來表示存儲過程。
使用 CallableStatement 對象就像使用 PreparedStatement 對象。在執行該語句前,你必須將值綁定到所有的參數,否則你將收到一個 SQL 異常。
如果你有 IN 參數,只要按照適用於 PreparedStatement 對象相同的規則和技巧;用 setXXX()方法來綁定對應的 Java 數據類型。
當你使用 OUT 和 INOUT 參數就必須采用額外的 CallableStatement 方法:registerOutParameter()。registerOutParameter() 方法將 JDBC 數據類型綁定到存儲過程返回的數據類型。
一旦你調用了存儲過程,你可以用適當的 getXXX()方法從 OUT 參數參數中檢索數值。這種方法將檢索出來的 SQL 類型的值映射到 Java 數據類型。
關閉 CallableStatement 對象
正如你關閉其它的 Statement 對象,出於同樣的原因,你也應該關閉 CallableStatement 對象。
close()方法簡單的調用就可以完成這項工作。如果你先關閉了 Connection 對象,那么它也會關閉 CallableStatement 對象。然而,你應該始終明確關閉 CallableStatement 對象,以確保該對象被徹底關閉。
CallableStatement cstmt = null;
try {
String SQL = "{call getEmpName (?, ?)}";
cstmt = conn.prepareCall (SQL);
. . .
}
catch (SQLException e) {
. . .
}
finally {
cstmt.close();
}
流數據
PreparedStatement 對象必須具備使用輸入和輸出流來提供參數數據的能力。這使你能夠將整個文件存儲到數據庫列中,這樣數據庫就能存儲大型數據,例如 CLOB 和 BLOB 數據類型。
用於流數據有下列幾種方法-
- setAsciiStream(): 該方法是用來提供較大的 ASCII 值。
- setCharacterStream(): 該方法是用來提供較大的 UNICODE 值。
- setBinaryStream(): 該方法是用來提供較大的二進制值。
setXXXStream()方法需要一個額外的參數,該參數是除了參數占位符的文件大小。這個參數通知驅動程序通過使用流有多少數據被發送到數據庫中。
示例
假如我們到要上傳一個名為 XML_Data.xml 的 XML 文件到數據庫的表中。下面是該 XML 文件的內容-
<?xml version="1.0"?>
<Employee>
<id>100</id>
<first>Zara</first>
<last>Ali</last>
<Salary>10000</Salary>
<Dob>18-08-1978</Dob>
<Employee>
將該 XML 文件和你要運行的示例保存在相同的目錄的。
這個示例將創建一個數據庫表 XML_Data ,然后 XML_Data.xml 將被上傳到該表中。
將下面的示例拷貝並粘帖到 JDBCExample.java 中,編譯並運行它,如下所示-
// Import required packages
import java.sql.*;
import java.io.*;
import java.util.*;
public class JDBCExample {
// JDBC driver name and database URL
static final String JDBC_DRIVER = "com.mysql.jdbc.Driver";
static final String DB_URL = "jdbc:mysql://localhost/EMP";
// Database credentials
static final String USER = "username";
static final String PASS = "password";
public static void main(String[] args) {
Connection conn = null;
PreparedStatement pstmt = null;
Statement stmt = null;
ResultSet rs = null;
try{
// Register JDBC driver
Class.forName("com.mysql.jdbc.Driver");
// Open a connection
System.out.println("Connecting to database...");
conn = DriverManager.getConnection(DB_URL,USER,PASS);
//Create a Statement object and build table
stmt = conn.createStatement();
createXMLTable(stmt);
//Open a FileInputStream
File f = new File("XML_Data.xml");
long fileLength = f.length();
FileInputStream fis = new FileInputStream(f);
//Create PreparedStatement and stream data
String SQL = "INSERT INTO XML_Data VALUES (?,?)";
pstmt = conn.prepareStatement(SQL);
pstmt.setInt(1,100);
pstmt.setAsciiStream(2,fis,(int)fileLength);
pstmt.execute();
//Close input stream
fis.close();
// Do a query to get the row
SQL = "SELECT Data FROM XML_Data WHERE id=100";
rs = stmt.executeQuery (SQL);
// Get the first row
if (rs.next ()){
//Retrieve data from input stream
InputStream xmlInputStream = rs.getAsciiStream (1);
int c;
ByteArrayOutputStream bos = new ByteArrayOutputStream();
while (( c = xmlInputStream.read ()) != -1)
bos.write(c);
//Print results
System.out.println(bos.toString());
}
// Clean-up environment
rs.close();
stmt.close();
pstmt.close();
conn.close();
}catch(SQLException se){
//Handle errors for JDBC
se.printStackTrace();
}catch(Exception e){
//Handle errors for Class.forName
e.printStackTrace();
}finally{
//finally block used to close resources
try{
if(stmt!=null)
stmt.close();
}catch(SQLException se2){
}// nothing we can do
try{
if(pstmt!=null)
pstmt.close();
}catch(SQLException se2){
}// nothing we can do
try{
if(conn!=null)
conn.close();
}catch(SQLException se){
se.printStackTrace();
}//end finally try
}//end try
System.out.println("Goodbye!");
}//end main
public static void createXMLTable(Statement stmt)
throws SQLException{
System.out.println("Creating XML_Data table..." );
//Create SQL Statement
String streamingDataSql = "CREATE TABLE XML_Data " +
"(id INTEGER, Data LONG)";
//Drop table first if it exists.
try{
stmt.executeUpdate("DROP TABLE XML_Data");
}catch(SQLException se){
}// do nothing
//Build table.
stmt.executeUpdate(streamingDataSql);
}//end createXMLTable
}//end JDBCExample
現在,讓我們用下面的命令編譯上面的代碼-
C:\>javac JDBCExample.java
C:\>
當你運行 JDBCExample 時,它將展示下面的結果-
C:\>java JDBCExample
Connecting to database...
Creating XML_Data table...
<?xml version="1.0"?>
<Employee>
<id>100</id>
<first>Zara</first>
<last>Ali</last>
<Salary>10000</Salary>
<Dob>18-08-1978</Dob>
<Employee>
Goodbye!