JDBC詳解

Brewin 發表於 2020-09-15

1.什麼是JDBC

DBC(Java Data Base Connectivity,java資料庫連線)是一種用於執行SQL語句的Java API,可以為多種關聯式資料庫提供統一訪問,它由一組用Java語言編寫的類和介面組成。JDBC提供了一種基準,據此可以構建更高階的工具和介面,使資料庫開發人員能夠編寫資料庫應用程式。

在程式中包含資料庫程式設計所需的JDBC類。大多數情況下,使用import java.sql.*

1.Driver介面

  Driver介面由資料庫廠家提供,作為java開發人員,只需要使用Driver介面就可以了。在程式設計中要連線資料庫,必須先裝載特定廠商的資料庫驅動程式,不同的資料庫有不同的裝載方法。如:

// Oracle (thin)資料庫
Class.forName("oracle.jdbc.driver.OracleDriver");

// MySQL資料庫
Class.forName("com.mysql.jdbc.Driver");

// SQLServer2000資料庫 
Class.forName("com.microsoft.jdbc.sqlserver.SQLServerDriver"); 

2.Connection介面

  Connection與特定資料庫的連線(會話),在連線上下文中執行sql語句並返回結果。DriverManager.getConnection(url, user, password)方法建立在JDBC URL中定義的資料庫Connection連線上。

// Oracle (thin)資料庫
String URL="jdbc:oracle:thin:@localhost:1521:myDB";

// MySQL資料庫
String URL = "jdbc:mysql://localhost:3306/myDB";

// SQLServer2000資料庫
String URL ="jdbc:microsoft:sqlserver://localhost:1433;DatabaseName=myDB";   
//建立連線
Connection con = DriverManager.getConnection(URL,UserName,Password);

常用方法:

方法名 說明
Statement createStatement() 建立向資料庫傳送sql的statement物件
PreparedStatement prepareStatement(String sql) 建立向資料庫傳送預編譯sql的PrepareSatement物件
prepareCall(sql) 建立執行儲存過程的callableStatement物件
setAutoCommit(boolean autoCommit) 設定事務是否自動提交
boolean isClosed() 查詢此Connection物件是否已經被關閉
commit() 在連結上提交事務
rollback() 在此連結上回滾事務

3.Statement介面

用於執行靜態SQL語句並返回它所生成結果的物件。

三種Statement類:

類名 說明
Statement 由createStatement建立,用於傳送簡單的SQL語句(不帶引數)
PreparedStatement 繼承自Statement介面,由preparedStatement建立,用於傳送含有一個或多個引數的SQL語句。PreparedStatement物件比Statement物件的效率更高,並且可以防止SQL隱碼攻擊,所以我們一般都使用PreparedStatement
CallableStatement 繼承自PreparedStatement介面,由方法prepareCall建立,用於呼叫儲存過程

常用Statement方法:

方法 說明
boolean execute(String SQL) 執行語句,返回是否有結果集
ResultSet executeQuery(String SQL) 執行select語句,返回ResultSet結果集
int executeUpdate(String SQL) 執行insert/update/delete操作,返回更新的行數
addBatch(String sql) 把多條sql語句放到一個批處理中
executeBatch() 向資料庫傳送一批sql語句執行

4.ResultSet介面

ResultSet提供檢索不同型別欄位的方法,常用的有:

方法 說明
getString(int index)、getString(String columnName) 獲得在資料庫裡是varchar、char等型別的資料物件
getFloat(int index)、getFloat(String columnName) 獲得在資料庫裡是Float型別的資料物件
getDate(int index)、getDate(String columnName) 獲得在資料庫裡是Date型別的資料
getBoolean(int index)、getBoolean(String columnName) 獲得在資料庫裡是Boolean型別的資料
getObject(int index)、getObject(String columnName) 獲取在資料庫裡任意型別的資料

JDBC資料型別:

下表列出了預設的JDBC資料型別與Java資料型別轉換,當使用PreparedStatement或CallableStatement物件時可呼叫setXXX()方法或ResultSet.updateXXX()方法。

SQL JDBC/Java setXXX getXXX updateXXX
VARCHAR java.lang.String setString getString updateString
CHAR java.lang.String setString getString updateString
LONGVARCHAR java.lang.String setString getString updateString
BIT boolean setBoolean getBoolean updateBoolean
NUMERIC java.math.BigDecimal setBigDecimal getBigDecimal updateBigDecimal
TINYINT byte setByte getByte updateByte
SMALLINT short setShort getShort updateShort
INTEGER int setInt getInt updateInt
BIGINT long setLong getLong updateLong
REAL float setFloat getFloat updateFloat
FLOAT float setFloat getFloat updateFloat
DOUBLE double setDouble getDouble updateDouble
VARBINARY byte[ ] setBytes getBytes updateBytes
BINARY byte[ ] setBytes getBytes updateBytes
DATE java.sql.Date setDate getDate updateDate
TIME java.sql.Time setTime getTime updateTime
TIMESTAMP java.sql.Timestamp setTimestamp getTimestamp updateTimestamp
CLOB java.sql.Clob setClob getClob updateClob
BLOB java.sql.Blob setBlob getBlob updateBlob
ARRAY java.sql.Array setARRAY getARRAY updateARRAY
REF java.sql.Ref SetRef getRef updateRef
STRUCT java.sql.Struct SetStruct getStruct updateStruct

JDBC 3.0 增強了對 BLOB,CLOB,ARRAY 和 REF 資料型別的支援。 ResultSet 物件現在有 UPDATEBLOB(),updateCLOB(), updateArray(),和 updateRef()方法,通過這些方法可以直接操作伺服器上的相應資料。

java.sql.Date 類對映 SQL DATE 型別,java.sql.Time 類和 java.sql.Timestamp 類也分別對映 SQL TIME 資料型別和 SQL TIMESTAMP 資料型別。

ResultSet還提供了對結果集進行滾動的方法:

方法 說明
boolean next() 將游標移動到下一行,如果是結果集的最後一行則返回 false
boolean previous() 將游標移動到上一行,如果超過結果集的範圍則返回 false
void close() 關閉 ResultSet 物件
int getRow() 返回當前游標指向的行數的值
void beforeFirst() 將游標移動到第一行之前
void afterLast() 將游標移動到最後一行之後
boolean first() 將游標移動到第一行
void last() 將游標移動到最後一行
boolean relative(int row) 將游標移動到當前指向的位置往前或往後第 row 行的位置
boolean absolute(int row) 將游標移動到指定的第 row 行
void moveToInsertRow() 將游標移動到結果集中指定的行,可以在資料庫中插入新的一行。當前游標位置將被記住
void moveToCurrentRow() 如果游標處於插入行,則將游標返回到當前行,其他情況下,這個方法不執行任何操作

使用後依次關閉物件及連線:ResultSet → Statement → Connection

處理 NULL 值

SQL 使用 NULL 值和 Java 使用 null 是不同的概念。可以使用三種策略來處理 Java 中的 SQL NULL 值-

  • 避免使用返回原始資料型別的 getXXX()方法。
  • 使用包裝類的基本資料型別,並使用 ResultSet 物件的 wasNull()方法來測試收到 getXXX()方法返回的值是否為 null,如果是 null,該包裝類變數則被設定為 null。
  • 使用原始資料型別和 ResultSet 物件的 wasNull()方法來測試通過 getXXX()方法返回的值,如果是 null,則原始變數應設定為可接受的值來代表 NULL。
Statement stmt = conn.createStatement( );
String sql = "SELECT id, first, last, age FROM Employees";
ResultSet rs = stmt.executeQuery(sql);

int id = rs.getInt(1);
if( rs.wasNull( ) ) {
   id = 0;
}

載入JDBC驅動程式 → 建立資料庫連線Connection → 建立執行SQL的語句Statement → 處理執行結果ResultSet → 釋放資源

(賈璉欲執事)

1.載入JDBC驅動

使用Class.forName()方法將給定的JDBC驅動類載入到Java虛擬機器中。若系統中不存在給定的類,則會引發異常,異常型別為ClassNotFoundException。

方式一:推薦這種方式,不會對具體的驅動類產生依賴。
Class.forName(“com.MySQL.jdbc.Driver”);

方式二:會造成DriverManager中產生兩個一樣的驅動,並會對具體的驅動類產生依賴。
DriverManager.registerDriver(com.mysql.jdbc.Driver);

2.建立連線

DriverManager類是JDBC的管理層,作用於使用者和驅動程式之間。 DriverManager類跟蹤可用的驅動程式,並在資料庫和相應的驅動程式之間建立連線。當呼叫getConnection()方法時, DriverManager類首先從已載入的驅動程式列表中找到一個可以接收該資料庫URL的驅動程式,然後請求該驅動程式使用相關的URL、使用者名稱和密碼連線到資料庫中,於是就建立了與資料庫的連線,建立連線物件並返回引用。

Connection conn = DriverManager.getConnection(url, user, password); 

  URL用於標識資料庫的位置,通過URL地址告訴JDBC程式連線哪個資料庫,URL的寫法為:

  

  其他引數如:useUnicode=true&characterEncoding=utf8

3.建立執行SQL語句的statement

//Statement  
String id = "5";
String sql = "delete from table where id=" +  id;
Statement st = conn.createStatement();  
st.executeQuery(sql);  
//存在sql注入的危險
//如果使用者傳入的id為“5 or 1=1”,那麼將刪除表中的所有記錄
//PreparedStatement 有效的防止sql注入(SQL語句在程式執行前已經進行了預編譯,當執行時動態地把引數傳給PreprareStatement時,即使引數裡有敏感字元如 or '1=1'也資料庫會作為一個引數一個欄位的屬性值來處理而不會作為一個SQL指令)
String sql = “insert into user (name,pwd) values(?,?);  
PreparedStatement ps = conn.preparedStatement(sql);  
ps.setString(1, “col_value”);  //佔位符順序從1開始
ps.setString(2,123456); //也可以使用setObject
ps.executeQuery();

4.處理執行結果(ResultSet)

ResultSet rs = ps.executeQuery();  
While(rs.next()){  
    rs.getString(“col_name”);  
    rs.getInt(1);  
    //…
}  

5.釋放資源

//資料庫連線(Connection)非常耗資源,儘量晚建立,儘量早的釋放
//都要加try catch 以防前面關閉出錯,後面的就不執行了
try {
    if (rs != null) {
        rs.close();
    }
} catch (SQLException e) {
    e.printStackTrace();
} finally {
    try {
        if (st != null) {
            st.close();
        }
    } catch (SQLException e) {
        e.printStackTrace();
    } finally {
        try {
            if (conn != null) {
                conn.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}
  • 為了提高效能
  • 為了保持業務流程的完整性
  • 使用分散式事務

可以通過事務在任意時間來控制以及更改應用到資料庫。它把單個 SQL 語句或一組 SQL 語句作為一個邏輯單元,如果其中任一語句失敗,則整個事務失敗。

若要啟用手動事務模式來代替 JDBC 驅動程式預設使用的自動提交模式的話,使用 Connection 物件的的 setAutoCommit()方法。如果傳遞一個布林值 false 到 setAutoCommit()方法,你就關閉自動提交模式。你也可以傳遞一個布林值 true 將其再次開啟。

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) 方法,該方法可以回滾到指定的還原點。

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

異常處理可以允許處理一個異常情況,例如可控方式的程式定義錯誤。

當異常情況發生時,將丟擲一個異常。丟擲這個詞意味著當前執行的程式停止,控制器被重定向到最近的適用的 catch 子句。如果沒有適用的 catch 子句存在,那麼程式執行被終止。

JDBC 的異常處理是非常類似於 Java 的異常處理,但對於 JDBC,最常見的異常是 java.sql.SQLException。

SQLException 異常在驅動程式和資料庫中都可能出現。當出現這個異常時,SQLException 型別的物件將被傳遞到 catch 子句。

傳遞的 SQLException 物件具有以下的方法,以下的方法可用於檢索該異常的額外資訊

方法 說明
getErrorCode( ) 獲取與異常關聯的錯誤號。
getMessage( ) 獲取 JDBC 驅動程式的錯誤資訊,該錯誤是由驅動程式處理的,或者在資料庫錯誤中獲取 Oracl 錯誤號和錯誤資訊。
getSQLState( ) 獲取 XOPEN SQLstate 字串。對於 JDBC 驅動程式錯誤,使用該方法不能返回有用的資訊。對於資料庫錯誤,返回第五位的 XOPEN SQLstate 程式碼。該方法可以返回 null。
getNextException( ) 獲取異常鏈的下一個 Exception 物件。
printStackTrace( ) 列印當前異常或者丟擲,其回溯到標準的流錯誤。
printStackTrace(PrintStream s) 列印該丟擲,其回溯到你指定的列印流。
printStackTrace(PrintWriter w) 列印該丟擲,其回溯到你指定的列印寫入。
//STEP 1. Import required packages
import java.sql.*;

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;
   try{
      //STEP 2: Register JDBC driver
      Class.forName("com.mysql.jdbc.Driver");

      //STEP 3: Open a connection
      System.out.println("Connecting to database...");
      conn = DriverManager.getConnection(DB_URL,USER,PASS);

      //STEP 4: Execute a query
      System.out.println("Creating statement...");
      Statement stmt = conn.createStatement();
      String sql;
      sql = "SELECT id, first, last, age FROM Employees";
      ResultSet rs = stmt.executeQuery(sql);

      //STEP 5: Extract data from result set
      while(rs.next()){
         //Retrieve by column name
         int id  = rs.getInt("id");
         int age = rs.getInt("age");
         String first = rs.getString("first");
         String last = rs.getString("last");

         //Display values
         System.out.print("ID: " + id);
         System.out.print(", Age: " + age);
         System.out.print(", First: " + first);
         System.out.println(", Last: " + last);
      }
      //STEP 6: Clean-up environment
      rs.close();
      stmt.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(conn!=null)
            conn.close();
      }catch(SQLException se){
         se.printStackTrace();
      }//end finally try
   }//end try
   System.out.println("Goodbye!");
}//end main
}//end JDBCExample

批處理是指將關聯的 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() 方法提交所有的更改。
// 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() 方法提交所有的更改。
// 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 物件可以使用所有的三種型別。

下面是三種型別引數的定義

引數 描述
IN 當 SQL 語句建立的時候,該引數的值是未知的。你可以用 setXXX() 方法將值繫結到 IN 引數裡。
OUT 該引數的值是由 SQL 語句的返回值。你可以用 getXXX() 方法從 OUT 引數中檢索值。
INOUT 該引數同時提供輸入和輸出值。你可以用 setXXX() 方法將值繫結到 IN 引數裡,並且也可以用 getXXX() 方法從 OUT 引數中檢索值。

下面的程式碼片段展示瞭如何使用 Connection.prepareCall() 方法實現一個基於上述儲存過程的 CallableStatement 物件

CallableStatement cstmt = null;
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

對於共享資源,有一個很著名的設計模式:資源池(Resource Pool)。

該模式正是為了解決資源的頻繁分配﹑釋放所造成的問題。

連線池技術的核心思想是連線複用,通過建立一個資料庫連線池以及一套連線使用、分配和管理策略,使得該連線池中的連線可以得到高效、安全的複用,避免了資料庫連線頻繁建立、關閉的開銷。

連線池的工作原理

主要由三部分組成,分別為連線池的建立、連線池中連線的使用管理、連線池的關閉。

1.連線池的建立

​ 一般在系統初始化時,連線池會根據系統配置建立,並在池中建立了幾個連線物件,以便使用時能從連線池中獲取。連線池中的連線不能隨意建立和關閉,這樣避免了連線隨意建立和關閉造成的系統開銷。Java中提供了很多容器類可以方便的構建連線池,例如Vector、Stack等。

2.連線池的管理

​ 連線池管理策略是連線池機制的核心,連線池內連線的分配和釋放對系統的效能有很大的影響。其管理策略是:

​ 當客戶請求資料庫連線時,首先檢視連線池中是否有空閒連線,如果存在空閒連線,則將連線分配給客戶使用;如果沒有空閒連線,則檢視當前所開的連線數是否已經達到最大連線數,如果沒達到就重新建立一個連線給請求的客戶;如果達到就按設定的最大等待時間進行等待,如果超出最大等待時間,則丟擲異常給客戶。

​ 當客戶釋放資料庫連線時,先判斷該連線的引用次數是否超過了規定值,如果超過就從連線池中刪除該連線,否則保留為其他客戶服務。

​ 該策略保證了資料庫連線的有效複用,避免頻繁的建立、釋放連線所帶來的系統資源開銷。

3.連線池的關閉

​ 當應用程式退出時,關閉連線池中所有的連線,釋放連線池相關的資源,該過程正好與建立相反。

連線池的主要優點

1.減少連線建立時間。連線池中的連線是已準備好的、可重複使用的,獲取後可以直接訪問資料庫,因此減少了連線建立的次數和時間。

2.簡化的程式設計模式。當使用連線池時,每一個單獨的執行緒能夠像建立一個自己的JDBC連線一樣操作,允許使用者直接使用JDBC程式設計技術。

3.控制資源的使用。

DBCP 資料庫連線池

import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;

import javax.sql.DataSource;

import org.apache.commons.dbcp.BasicDataSourceFactory;


public class JdbcUtils_DBCP {

    private static DataSource ds = null;
    static{
        try{
            InputStream in = JdbcUtils_DBCP.class.getClassLoader().getResourceAsStream("dbcpconfig.properties");
            Properties prop = new Properties();
            prop.load(in);

            BasicDataSourceFactory factory = new BasicDataSourceFactory();

            ds = factory.createDataSource(prop);
            System.out.println(ds);
        }catch (Exception e) {
            throw new ExceptionInInitializerError(e);
        }
    }

    public static Connection getConnection() throws SQLException{

        return ds.getConnection();
    }

    public static void release(Connection conn,Statement st,ResultSet rs){


        if(rs!=null){
            try{
                rs.close();
            }catch (Exception e) {
                e.printStackTrace();
            }
            rs = null;

        }
        if(st!=null){
            try{
                st.close();
            }catch (Exception e) {
                e.printStackTrace();
            }

        }

        if(conn!=null){
            try{
                conn.close();
            }catch (Exception e) {
                e.printStackTrace();
            }

        }
    }

}

C3P0 資料庫連線池

import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;

import javax.sql.DataSource;

import org.apache.commons.dbcp.BasicDataSourceFactory;

import com.mchange.v2.c3p0.ComboPooledDataSource;


public class JdbcUtils_C3P0 {

    private static ComboPooledDataSource ds = null;
    static{
        try{
            ds = new ComboPooledDataSource();
            ds.setDriverClass("com.mysql.jdbc.Driver");
            ds.setJdbcUrl("jdbc:mysql://localhost:3306/day16");
            ds.setUser("root");
            ds.setPassword("root");

            ds.setInitialPoolSize(10);//最初連線數
            ds.setMinPoolSize(5);//最小連線數
            ds.setMaxPoolSize(20);//最大連線數

        }catch (Exception e) {
            throw new ExceptionInInitializerError(e);
        }
    }

    public static Connection getConnection() throws SQLException{

        return ds.getConnection();
    }

    public static void release(Connection conn,Statement st,ResultSet rs){


        if(rs!=null){
            try{
                rs.close();
            }catch (Exception e) {
                e.printStackTrace();
            }
            rs = null;

        }
        if(st!=null){
            try{
                st.close();
            }catch (Exception e) {
                e.printStackTrace();
            }

        }

        if(conn!=null){
            try{
                conn.close();
            }catch (Exception e) {
                e.printStackTrace();
            }

        }
    }

}

也可以將配置資訊放在xml檔案中(src下):c3p0-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
    <default-config>
        <property name="driverClass">com.mysql.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/day16</property>
        <property name="user">root</property>
        <property name="password">root</property>

        <property name="acquireIncrement">5</property>
        <property name="initialPoolSize">10</property>
        <property name="minPoolSize">5</property>
        <property name="maxPoolSize">20</property>
    </default-config>

    <named-config name="mysql">
        <property name="driverClass">com.mysql.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/day16</property>
        <property name="user">root</property>
        <property name="password">root</property>

        <property name="acquireIncrement">5</property>
        <property name="initialPoolSize">10</property>
        <property name="minPoolSize">5</property>
        <property name="maxPoolSize">20</property>
    </named-config>


    <named-config name="oracle">
        <property name="driverClass">com.mysql.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/day16</property>
        <property name="user">root</property>
        <property name="password">root</property>

        <property name="acquireIncrement">5</property>
        <property name="initialPoolSize">10</property>
        <property name="minPoolSize">5</property>
        <property name="maxPoolSize">20</property>
    </named-config>
</c3p0-config>

這樣JdbcUtils_C3p0可以改為:

            /*
            ds = new ComboPooledDataSource();
            ds.setDriverClass("com.mysql.jdbc.Driver");
            ds.setJdbcUrl("jdbc:mysql://localhost:3306/day16");
            ds.setUser("root");
            ds.setPassword("root");

            ds.setInitialPoolSize(10);//最初連線數
            ds.setMinPoolSize(5);//最小連線數
            ds.setMaxPoolSize(20);//最大連線數
            */
             ds = new ComboPooledDataSource(""mysql"");//如果預設()

JDBC工具類

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/store28
user=root
password=root1234
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ResourceBundle;

public final class JDBCUtilsPlus {
    private static final String DRIVER;
    private static final String URL;
    private static final String USER;
    private static final String PASSWORD;

    private JDBCUtilsPlus(){}

    static {
        ResourceBundle bundle = ResourceBundle.getBundle("jdbc");

        DRIVER = bundle.getString("driver");
        URL = bundle.getString("url");
        USER = bundle.getString("user");
        PASSWORD = bundle.getString("password");

        /**
         * 驅動註冊
         */
        try {
            Class.forName(DRIVER);
        } catch (ClassNotFoundException e) {
            throw new ExceptionInInitializerError(e);
        }
    }

    /**
     * 獲取 Connetion
     * @return
     * @throws SQLException
     */
    public static Connection getConnection() throws SQLException{
        return DriverManager.getConnection(URL, USER, PASSWORD);
    }

    /**
     * 釋放資源
     * @param conn
     * @param st
     * @param rs
     */
    public static void colseResource(Connection conn,Statement st,ResultSet rs) {
        closeResultSet(rs);
        closeStatement(st);
        closeConnection(conn);
    }

    /**
     * 釋放連線 Connection
     * @param conn
     */
    public static void closeConnection(Connection conn) {
        if(conn !=null) {
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        //等待垃圾回收
        conn = null;
    }

    /**
     * 釋放語句執行者 Statement
     * @param st
     */
    public static void closeStatement(Statement st) {
        if(st !=null) {
            try {
                st.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        //等待垃圾回收
        st = null;
    }

    /**
     * 釋放結果集 ResultSet
     * @param rs
     */
    public static void closeResultSet(ResultSet rs) {
        if(rs !=null) {
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        //等待垃圾回收
        rs = null;
    }


    /**
     * insert方法
     * @param conn
     * @param sql
     * @param parems
     * @return
     * @throws SQLException
     */
    public static int insert(Connection conn,String sql,Object[] parems) throws SQLException{
        PreparedStatement stmt = null;
        try{
            stmt = conn.prepareStatement(sql);
            if(null != parems && parems.length>0){//判斷是否有資訊增加
                for(int i = 0;i<parems.length;i++){
                    stmt.setObject(i+1, parems[i]);//使用給定物件設定指定引數的值
                }
            }
            int count = stmt.executeUpdate();

            return count;
        }finally{
            close(stmt);
        }
    }
    /**
     * 泛型查詢方法
     * @param conn
     * @param sql
     * @param paramsList
     * @param rm
     * @return
     * @throws SQLException
     */
    public static <T> List<T> executeQuery(Connection conn,String sql,Object[] paramsList,RowsMapper<T> rm) throws SQLException{
        PreparedStatement stmt = null;
        ResultSet rs = null;
        List<T> list = new ArrayList<T>(); 
        try{
            stmt = conn.prepareStatement(sql);
            if(null != paramsList && paramsList.length>0){//判斷是否有資訊增加
                for(int i = 0;i<paramsList.length;i++){
                    stmt.setObject(i+1, paramsList[i]);
                }
            }
            rs = stmt.executeQuery();
            while(rs.next()){
                T t = rm.getEntity(rs);
                list.add(t);
            }
            return list;
        }finally{
            close(rs);
            close(stmt);
        }
    }

}

時間處理(Date和Time以及Timestamp區別、隨機日期生成)

java.util.Date

  • 子類:java.sql.Date
  • 子類:java.sql.Time
  • 子類:java.sql.Timestamp
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.Random;

/**
 * 測試時間處理(java.sql.Date,Time,Timestamp)
 */
public class Demo07 {
    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement ps = null;

        try {
            Class.forName("com.mysql.jdbc.Driver");
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/testjdbc","root","mysql");

            for (int i = 0; i < 1000; i++) {

                ps = conn.prepareStatement("insert into t_user(userName,pwd,regTime,lastLoginTime)values(?,?,?,?)");
                ps.setObject(1, "小高" + i);
                ps.setObject(2, "123");

                //
                int random = 1000000000 + new Random().nextInt(1000000000); //隨機時間

                java.sql.Date date = new java.sql.Date(System.currentTimeMillis() - random);    //插入隨機時間
                java.sql.Timestamp stamp = new Timestamp(System.currentTimeMillis());   //如果需要插入指定時間,可以使用Calendar、DateFormat
                ps.setDate(3, date);
                ps.setTimestamp(4, stamp);
                //
                ps.execute();
            }

            System.out.println("插入");

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally{

            try {
                if (ps!=null) {
                    ps.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
            try {
                if (conn!=null) {
                    conn.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

import java.sql.Connection;
import java.sql.Date;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;

/**
 * 測試時間處理(java.sql.Date,Time,Timestamp),取出指定時間段的資料
 */
public class Demo08 {

    /**
     * 將字串代表的時間轉為long數字(格式:yyyy-MM-dd hh:mm:ss)
     * @param dateStr
     * @return
     */
    public static long str2DateTime(String dateStr){
        DateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");

        try {
            return format.parse(dateStr).getTime();
        } catch (ParseException e) {
            e.printStackTrace();
            return 0;
        }
    }

    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;

        try {
            Class.forName("com.mysql.jdbc.Driver");
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/testjdbc","root","mysql");

            //
            ps = conn.prepareStatement("select * from t_user where regTime > ? and regTime < ?");
            java.sql.Date start = new java.sql.Date(str2DateTime("2016-06-20 00:00:00"));
            java.sql.Date end = new java.sql.Date(str2DateTime("2016-06-24 00:00:00"));

            ps.setObject(1, start);
            ps.setObject(2, end);

            rs = ps.executeQuery();
            while(rs.next()){
                System.out.println(rs.getInt("id") + "--" + rs.getString("userName")+"--"+rs.getDate("regTime"));
            }
            //

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally{

            try {
                if (ps!=null) {
                    ps.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
            try {
                if (conn!=null) {
                    conn.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

CLOB文字大物件操作

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileReader;
import java.io.InputStreamReader;
import java.io.Reader;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

/**
 * 測試CLOB   文字大物件的使用
 * 包含:將字串、檔案內容插入資料庫中的CLOB欄位和將CLOB欄位值取出來的操作。
 */
public class Demo09 {
    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement ps = null;
        PreparedStatement ps2 = null;
        ResultSet rs = null;
        Reader r = null;

        try {
            Class.forName("com.mysql.jdbc.Driver");
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/testjdbc","root","mysql");

            //插入//
            ps = conn.prepareStatement("insert into t_user(userName,myInfo)values(?,?)");
            ps.setString(1, "小高");

            //將文字檔案內容直接輸入到資料庫中
//          ps.setClob(2, new FileReader(new File("G:/JAVA/test/a.txt")));

            //將程式中的字串輸入到資料庫中的CLOB欄位中
            ps.setClob(2, new BufferedReader(new InputStreamReader(new ByteArrayInputStream("aaaa".getBytes()))));

            ps.executeUpdate();
            System.out.println("插入");
            //

            //查詢//
            ps2 = conn.prepareStatement("select * from t_user where id=?");
            ps2.setObject(1, 223021);

            rs = ps2.executeQuery();
            System.out.println("查詢");
            while (rs.next()) {
                Clob c = rs.getClob("myInfo");
                r = c.getCharacterStream();
                int temp = 0;
                while ((temp=r.read())!=-1) {
                    System.out.print((char)temp);
                }
            }

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        } finally{

            try {
                if (r!=null) {
                    r.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            try {
                if (rs!=null) {
                    rs.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
            try {
                if (ps2!=null) {
                    ps2.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
            try {
                if (ps!=null) {
                    ps.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
            try {
                if (conn!=null) {
                    conn.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

BLOB二進位制大物件的使用

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.sql.Blob;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

/**
 * 測試BLOB   二進位制大物件的使用
 */
public class Demo10 {
    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement ps = null;
        PreparedStatement ps2 = null;
        ResultSet rs = null;
        InputStream is = null;
        OutputStream os = null;

        try {
            Class.forName("com.mysql.jdbc.Driver");
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/testjdbc","root","mysql");

            //插入//
            ps = conn.prepareStatement("insert into t_user(userName,headImg)values(?,?)");
            ps.setString(1, "小高");
            ps.setBlob(2, new FileInputStream("G:/JAVA/test/d.jpg"));
            ps.execute();
            //

            //查詢//
            ps2 = conn.prepareStatement("select * from t_user where id=?");
            ps2.setObject(1, 223024);

            rs = ps2.executeQuery();
            System.out.println("查詢");
            while (rs.next()) {
                Blob b = rs.getBlob("headImg");
                is = b.getBinaryStream();
                os = new FileOutputStream("G:/JAVA/test/h.jpg");

                int temp = 0;
                while ((temp=is.read())!=-1) {
                    os.write(temp);
                }
            }

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        } finally{

            try {
                if (os!=null) {
                    os.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            try {
                if (is!=null) {
                    is.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            try {
                if (rs!=null) {
                    rs.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
            try {
                if (ps2!=null) {
                    ps2.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
            try {
                if (ps!=null) {
                    ps.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
            try {
                if (conn!=null) {
                    conn.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

JDBC詳解

JDBC指南

Java中JDBC的使用詳解

JDBC詳細介紹

本作品採用《CC 協議》,轉載必須註明作者和本文連結