--- tags: JDBC --- # <font class=o>JDBC1</font> # <font class=g style="background-color:#abd7;">JDBC API 簡介</font> {%hackmd BJrTq20hE %} <style> .o{ color:orange; } .k{ color:black; } .f{ color:#abd7; } .p{ color:#6d7c; } .y { color: yellow; } .g { color:greenyellow; } .b { color:#6b70fa; } .r{ color:red; } //底線顏色 .mark{ color: #22053b; background: linear-gradient(gray 50%,gray 50%); border-radius: 5px; } </style> ## [CRUD](https://zh.wikipedia.org/wiki/%E5%A2%9E%E5%88%AA%E6%9F%A5%E6%94%B9) 增加(Create,意為「建立」) 刪除(Delete) 查詢(Read,意為「讀取」) 改正(Update,意為「更新」) 多層式架構就是為了解決<span class=g>相依性過高</span>的問題 JDBC是用於<span class=g>執行SQL的解決方案</span>關聯式資料庫 JDBC API已包含在JDK裡 --- ## <span class=g>java.sql</span>:JDBC的核心API,用來存取資料庫資料,基本功能 ## <span class=g>javax.sql</span>:JDBC Extension API,支援進階資料庫存取功能 JDBC API使得應用系統開發者能使用<font color=gree>相同的介面名稱與方法來存取資料庫的資料</font>,讓程式設計師能專心於應用系統的開發,<font color=gree>無需顧慮到不同廠商所提供的不同資料庫系統</font> [介面的五大特性](https://hackmd.io/@XucbOJaPQomAPvBTtrCZ8Q/HJcz9BJ_t) 定義規格 型別轉換 貼標籤 降低相依性 多重繼承 ![](https://i.imgur.com/1jKmqdC.png) ![](https://i.imgur.com/TH2OApA.png) # ==connection 非常重要== ```java ResultSet 對應Select指令 ,代表查詢結果 statement 對應SQL指令的執行 SQLException 一定要處理的例外 ``` ![](https://i.imgur.com/toYSrW6.png) # JDBC Driver Type 4 Driver Native Protocol Driver driver可以完全用Java技術實現 達到跨平台功能,效能也有不錯表現, 為業界最常見的driver類型 mysql jdbc driver type 4 oracle 11g jdbc driver type 4 sqlserver jdbc driver type 4 --- # <span class = y>載入JDBC Driver&lt;/span&gt; ### ==第1種做法: <span class= g>class loader方式 常用</span>== ## class loader 類別載入器 ```java Class.forName(“com.mysql.cj.jdbc.Driver”); (MySQL 8以後的驅動名稱) com.mysql.cj.jdbc.Driver 廠商實作Driver介面的類別完整名稱(套件名+類別名) ``` 步驟 ```java public class HelloJDBC { //驅動 public static final String DRIVER = "com.mysql.cj.jdbc.Driver"; Class.forName(DRIVER); /*ClassNotFoundException*/ ``` ```java try { //step 1:載入驅動 Class.forName(DRIVER); System.out.println("載入成功"); } catch (ClassNotFoundException e) { e.printStackTrace(); } ``` JDK 8以前類別載入器可分為三個層級: Bootstrap Loader → ExtClassLoader →AppClassLoader AppClassLoader -&gt;對應到的就是我們自己的類別路徑java-1 (模組28-3) 環境變數的classpath設定 ### ==第2種做法: <span class= g>~~register~~</span> 不建議使用== :需要手動new 新的物件 因為new也是載入類別, 那載入時就已產生物件,這樣就會建立第二個驅動物件 ### ==第3種作法: System Property方式 看情況== ```java System.setProperty(“jdbc.drivers”, “com.mysql.jdbc.Driver”); “jdbc.drivers” -> 固定 “com.mysql.jdbc.Driver” ->要一次載入的各種驅動名稱 可以'包含數個drivers',彼此之間以冒號(:)隔開 ``` # <div style="background-color:#abd7;" > <span class=k>建立與關閉資料庫連線</span> </div> # 資料庫位置(URL) 資料庫URL為一字串,用以說明連接某一特定資料庫所需要的資訊,包含 -&gt;資料庫位址<span class=g>(IP)</span> -&gt;埠號(Port Number ,從0-65535,有65536個 ) 潛規則0-1024不使用 -&gt;名稱等 ![](https://i.imgur.com/TbtYRZz.png) 3306 mysql預設的埠號 <span class=g>(註:MySQL 8 以後的驅動版本需在URL後接著?serverTimezone=Asia/Taipei)</span> 時區 ```java public static final String URL = "jdbc:mysql://localhost:3306/jdbcsample?serverTimezone=Asia/Taipei"; ``` ```java public static final String USER ="David"; public static final String PASSWORD ="123456"; /*mysql設定的David*/ ``` ![](https://i.imgur.com/RvSSzlO.png) # Connection ```java /*JDBC Api所以要選擇java.sql*/ import java.sql.Connection; ``` ```java //step 2:建立連線 Connection con = DriverManager.getConnection(URL, USER, PASSWORD); ``` ![](https://i.imgur.com/d2rEvza.png) ```java /*SQLException例外*/ } catch (SQLException se) { se.printStackTrace(); } ``` ### <span class=g>Connection非常珍貴,使用完畢後務必完成歸還的動作!</span> ![](https://i.imgur.com/Km9ghKM.png) ![](https://i.imgur.com/RnCWfUZ.png) ![](https://i.imgur.com/rWxmlcm.png) ```java= con.close(); /*跟區域變數有關 ,con變數宣告的位置在try*/ /*區域變數沒有預設的起始值*/ ``` 但問題是con如果沒有拿到 con.close()沒執行到依然會跳例外在finally 所以修改方式是: ## 1 ```java import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class HelloJDBC { // 驅動 public static final String DRIVER = "com.mysql.cj.jdbc.Driver" public static final String URL = "jdbc:mysql://localhost:3306/jdbcsample?serverTimezone=Asia/Taipei"; public static final String USER = "David"; public static final String PASSWORD = "123456"; public static void main(String[] args) { try { // step 1:載入驅動 Class.forName(DRIVER); System.out.println("載入成功"); // step 2:建立連線 Connection con = DriverManager.getConnection(URL, USER, PASSWORD); System.out.println("連線成功"); } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (SQLException se) { se.printStackTrace(); } } } ``` --- #### 2 ```java Connection con = null; try { // step 1:載入驅動 Class.forName(DRIVER); System.out.println("載入成功"); // step 2:建立連線 con = DriverManager.getConnection(URL, USER, PASSWORD); System.out.println("連線成功"); } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (SQLException se) { se.printStackTrace(); } finally { if(con!=null) { try { con.close();/*跟區域變數有關 ,con變數宣告的位置在try*/ /*區域變數沒有預設的起始值*/ System.out.println("連線關閉"); }catch(SQLException se) { se.printStackTrace(); } } } ``` --- ```java import java.sql.Statement; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class HelloJDBC { // 驅動 public static final String DRIVER = "com.mysql.cj.jdbc.Driver"; public static final String URL = "jdbc:mysql://localhost:3306/jdbcsample?serverTimezone=Asia/Taipei"; public static final String USER = "David"; public static final String PASSWORD = "123456"; public static void main(String[] args) { Connection con = null; Statement stmt = null; try { // step 1:載入驅動 Class.forName(DRIVER); System.out.println("載入成功"); // step 2:建立連線 con = DriverManager.getConnection(URL, USER, PASSWORD); System.out.println("連線成功"); // con.close();/*關閉資源*/ // step 3:送出SQL指令 stmt = con.createStatement(); int count = stmt.executeUpdate("INSERT INTO department VALUES(50,'總務部';,'中壢')"); System.out.println(count +"row(s) updated."); } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (SQLException se) { se.printStackTrace(); } finally { if(stmt!=null) { try { stmt.close();/*跟區域變數有關 ,con變數宣告的位置在try*/ /*區域變數沒有預設的起始值*/ }catch(SQLException se) { se.printStackTrace(); } } if(con!=null) { try { con.close();/*跟區域變數有關 ,con變數宣告的位置在try*/ /*區域變數沒有預設的起始值*/ System.out.println("連線關閉"); }catch(SQLException se) { se.printStackTrace(); } } } } } ``` # <span class = g style="background-color:#abd7;"> Statement介面</span> 藉由Connection的方法:Statement createStatement()來建立與取得Statement物件 藉由Statement物件,才能執行<span class=g>靜態</span>的SQL指令並與資料庫交談 ```java Statement stmt = null; step 3:送出SQL指令 stmt = con.createStatement(); int count = stmt.executeUpdate("INSERT INTO department VALUES(50,'總務部','中壢')"); System.out.println(count +"row(s) updated"); if(stmt!=null) { try { stmt.close();/*跟區域變數有關 ,con變數宣告的位置在try*/ /*區域變數沒有預設的起始值*/ }catch(SQLException se) { se.printStackTrace(); } } ``` ## 執行SQL指令 ```java ResultSet rs = null; stmt = con.createStatement(); rs = stmt.executeQuery("SELECT * FROM department ORDER BY deptno DESC"); if(rs!=null) { try { rs.close();/*跟區域變數有關 ,con變數宣告的位置在try*/ /*區域變數沒有預設的起始值*/ }catch(SQLException se) { se.printStackTrace(); } } ``` # <span class = g style="background-color:#abd7;"> ResultSet介面</span> 游標起始位置: 在查詢結果第一列的上方 會有一個ResultSet的游標 移動資料列游標 移動資料列游標的相關方法 ### <span class=g>next()</span> next()傳回boolean值 true代表游標停留位置有指向一個資料列,false即代表沒有指向資料列 取出查詢結果資料欄的方法 getXXX(); <span class=p>index從1開始,一般來說使用index較有效率</span> ```java while(rs.next()) { int deptno = rs.getInt(1); String dname = rs.getString(2); String loc = rs.getString(3); ``` 順序對應關係 ```java rs = stmt.executeQuery("SELECT dname,deptno, loc FROM department ORDER BY deptno DESC"); ``` ![](https://i.imgur.com/8835Udl.png) ![](https://i.imgur.com/l36qIYD.png) ![](https://i.imgur.com/HalIsfd.png) ```java * 駭客們所稱呼的"填空遊戲",將鍵盤輸入資料當做變數所組合成的SQL句子: SELECT * FROM USER WHERE * (NAME = '1' OR '1'='1') and (PASSWORD = '1' OR '1'='1'); ``` # <span class = g style="background-color:#abd7;"> PreparedStatement 介面</span> 因為可以動態處理SQL指令,可避免SQL Injection攻擊 ```java pstmt = con.prepareStatement("INSERT INTO department VALUES(?,?,?)"); ``` ![](https://i.imgur.com/0CPLsC7.png) 無須再傳參數 ,因為指令已經預先交給資料庫 # <span class = g style="background-color:#abd7;"> CallableStatement 介面</span> 預設schema ![](https://i.imgur.com/Kkt4rvv.png) delimiter # ,end # 最後還是要切換分號 IN -> input 輸入 ![](https://i.imgur.com/ZWYC0ER.png) 固定內容 ![](https://i.imgur.com/PT6zg3h.png) ![](https://i.imgur.com/qztBo3Q.png) {call } ![](https://i.imgur.com/gpSRvwu.png) <font class=p>CallableStatement prepareCall(String sql)</font>:使用<span class=p>預存程序(stored procedure)</span>,預存程序已事先內建在資料庫中,通常比預先編譯的效能佳 <font color=gree>excute</font> ---> CallableStatement boolean excute(): 用來執行&#34;未知&#34;的SQL指令,例如預存程序 ```java cstmt.registerOutParameter(2, Types.DOUBLE);//第2個? 註冊成為輸出的參數 (後面定義型別) ``` # <span class = g style="background-color:#abd7;"> ResultSet進階操作</span> ![](https://i.imgur.com/6oP6sKb.png) ## 游標進階操作 ### <font color=gree>ResultSetType</font> <span class=b>TYPE_FORWARD_ONLY (只能前進)</span> <span class=b>TYPE_SCROLL_INSENSITIVE (可前後移動,不反應資料修改)</span> ->當前 <span class='b'>TYPE_SCROLL_SENSITIVE (可前後移動,反應資料修改)</span> 若是查詢結果有發生更新的情況 當游標移動到該筆資料時 是否要反應更新的結果狀態 註:若沒設定,<font color=gree>預設</font>為<font color=gree>TYPE_FORWARD_ONLY & CONCUR_READ_ONLY</font>只能前進 ,資料取得不能作修改 int getRow():取得目前的列編號,若傳回0,表示游標不在任一列上,可能在第一列前或最後一列後 <span class=g>index從1開始</span>,一般來說使用index較有效率 ```java while (rs.next()) { System.out.print(rs.getRow() + ""); System.out.print("EMPNO = " + rs.getInt(1)); System.out.print("ENAME = " + rs.getString(2) + "\n"); } ``` ```java while (rs.previous()) {//上一列 System.out.print(rs.getRow() + ""); System.out.print(" EMPNO = " + rs.getInt(1)); System.out.print(" ENAME = " + rs.getString(2) + "\n"); } ``` --- ```java rs.absolute(10);//但不能超出範圍(資料的數量),會發生例外 System.out.print(rs.getRow() + ""); System.out.print(" EMPNO = " + rs.getInt(1)); System.out.print(" ENAME = " + rs.getString(2) + "\n"); ``` ## absolute -->絕對位置 ![](https://i.imgur.com/ukn3ksq.png) ```java rs.relative(2); //相對位置 搭配absolute(10) 結果會是10+2 //也可以給負數 ex:relative(-2) 搭配absolute(10) 結果會是10-2 ``` ## relative --> 相對位置 ![](https://i.imgur.com/gJ6n1nx.png) ![](https://i.imgur.com/ee8CaD3.png) --- ```java System.out.println("----------------4----------------"); rs.last(); int len = rs.getRow(); System.out.println("資料共" + len + "筆" + " [使用ResultSet游標移動的方式]"); rs.beforeFirst(); // System.out.println("----------------5----------------''); ResultSet rs2 = stmt.executeQuery("SELECT count(*) AS count FROM EMPLOYEE"); rs2.next(); int len2 = rs2.getInt("count"); System.out.println("資料共" + len2 + "筆" + " [直接下SQL指令的方式]"); rs2.close(); ``` ![](https://i.imgur.com/L2HG1Hl.png)