###### tags:`JDBC` # JDBC(1)-基本介紹與操作 # API設計目的 ## ✨資料庫種類 - 關聯性資料庫(Relational Database Management System): 使用表格的方式來儲存和呈現資料,並且透過建立表格與表格之間相關聯的資訊,來處理資料 像是Oracle Dataase、MySQL、微軟SQL Server、DB2等等 - 非關聯性資料庫(NoSQL) 把資料分散式的進行儲存,隨著BigData的流行跟著發展起來。 主要可以分成三種 : 文檔儲存、圖形關係儲存、鍵值儲存(key-value) 像是google的BigTable、MongoDB、Cassandra、Redis等等 ## ✨程式與資料庫通訊 資料庫本身也是能夠運行的應用程式,所以我們所寫的程式要透過網路通訊協定,來跟資料庫進行指令的交換,以達到資料的CRUD(代表對於資料的基本操作,也就是新增、修改、刪除、查詢) 但不同的廠商資料庫的通訊協定也不同,會需要用到不同的程式類別庫,且如果要更換資料庫,程式碼要整個重寫 為了考慮到跨平台的相容性,Java提供了一組資料庫來存取API,名字就叫JDBC(Java DataBase Connectivity) 透過把原本的不同類別庫拆成JDBC驅動程式和JDBC標準API兩塊,各個廠商負責製作各自的驅動程式,Java程式開發人員只需要使用JDBC的標準API來開發即可,不需要接觸到底層的資料庫驅動程式設計。 ## ✨JDBC的API套件 JDBC的API已經都放在JDK內,如下所示 - java.sql : JDBC的核心API,能夠存取資料庫的資料和一些基本功能 - <interface>Driver 驅動程式本體 - <interface>**Connection(重要)** 指操作資料庫的根源,代表和資料庫之間的連接通道 - <interface>ResultSet 對應到select指令的結果操作 - <interface>Statement 對應到要執行的SQL指令,下面兩個類別為其子類別 - <interface<PreparedStatement> - <interface>CallableStatement - DriverManager 管理Driver - SQLException SQL例外,屬於一定要處理的例外 大多使用try catch 做處理 - Date 記錄日期(年月日) - Time 紀錄時間到(時分) - Timestamp 時間戳記,能夠記錄完整時間點到毫秒 - javax.sql : JDBC的延伸API,能夠支援進階的資料庫儲存功能 # JDBC Driver註冊 ## ✨JDBC Driver ### Type 1 Drive (JDBC-ODBC Bridge Driver): JDK安裝的時候就附在裡面了,能夠把JDBC轉成ODBC的機制再連接資料庫,因此彈性和效率極慢,如果沒有其他選擇才考慮使用) 註 : 在JDK8後的版本ODBC相關的類別庫已經**被完全移除** ### Type 2 Drive (Native API Driver) 以原生的地方呼叫資料庫提供的的原生程式庫(通常以C/C++程式語言寫成) 但因為接近底層,因此無法跨平台,各平台只能用各自的原生程式庫。速度比Driver 1 快。 ### Type 3 Driver (JDBC-Net Driver) 把JDBC轉成特定的網路協定(Protocol),由中介的伺服器或原件來跟資料庫進行操作,有不錯的彈性,但因為中間還要透過網路互動,因此速度較慢 ### Type 4 Driver (Native Protocol Driber) Driver本身由資料庫廠商直接提供,會把JDBC轉換成和資料庫特定的網路協定 且此類的驅動全部由java所寫成,加上java原本就有跨平台的特性,所以效能和跨平台的特色都具備。是**業界最常見的Driver類型** ## ✨載入JDBC Driver 載入JDBC Driver有三種方式: - Class Loader方式(常用) 透過Class Loader方式產生Driver實體,並且註冊到Driver Manager的驅動程式註冊表單中 ```java public class HelloJDBC { public static void main(String[] args) { // step 1 : 載入驅動 try { Class.forName("com.mysql.cj.jdbc.Driver"); //注意此行 System.out.println("載入成功"); } catch (ClassNotFoundException ce) { ce.printStackTrace(); } } } ``` - register方式**(不建議使用)** 透過register方法產生Driver實體,並把自己註冊到Driver Manager的驅動程式表單中 ```java DriverManager.register(new com.mysql.cj.jdbc.Driver()); //調用register方法時,就會自動產生一個Driver實體,且使用關鍵字new也會產生一個Driver實體 //因此會有Driver實體,這可能會導致最後資料釋放的不完整 ``` - System Property方式 看需求,需要同時註冊多個驅動資訊時可以使用(System.setProperty) ```java public class HelloJDBC { public static void main(String[] args) { // step 1 : 載入驅動 try { System.setProperty("jdbc.drivers","com.mysql.jdbc.Driver"); //注意此行 System.out.println("載入成功"); } catch (ClassNotFoundException ce) { ce.printStackTrace(); } } } ``` ## ✨類別載入器(Class Loader) Java執行的時候,不會啟動所有的類別,而是等到要使用時才會把某類別的.class檔啟動 而只要啟動.class檔就會自動產生一個java.lang.Class的物件實體來對應該檔案 我們也可以利用這個Class物件,來找到這個類別相關的資訊,這個機制稱為**映射(Reflection)** JDK8之前類別載入器有三個層級: 1. Bootstrap Loader : 負責載入java內部rt.jar(核心)裡面的類別 2. ExtClassLoader : 負責載入延伸資料庫裡面的類別 3. AppClassLoader : 負責載入使用者自己設定的類別 以下是Reflection的實際使用方式: ```java public class HelloJDBC { public static void main(String[] args) { Class<String> cls = String.class; //創建一個類別物件,並使用String作為泛型 Method[] m =cls.getMethods(); //建立陣列,調用getMethods方法將類別相關的資訊存入陣列 for (Method method : m) { System.out.println(method); //利用迴圈讓螢幕顯示陣列內容 } } } ``` # 建立和關閉資料庫連線 ## ✨建立資料庫連線 ### 資料庫位置(URL) 資料庫的位置實際上就是一個字串,記錄了某一個資料庫連接時所需要的資訊,包含了資料庫位址(IP或localhost,本機)、埠號(port number)、名稱 - 埠號 : 網路通訊一定會用到的機制(0~65535,共65536個) 但0~1024是不使用的,主要由其他程式和協定等等所使用或註冊,因為一個port只能給一個通訊協定使用 資料庫的URL格式: ```java public static final String URL = "jdbc:mysql://localhost:3306/jdbcsample?serverTimezone=Asia/Taipei"; //從MySQL8之後的版本URL需要加上時區設定的資訊 //ex : 資料庫URL ?serverTimezone=Asia/Taipei ``` 不同的資料庫URL的格式也會不相同,相關資訊可以在各資料庫的公開文件中找到 ### 建立資料庫連線 在資料庫中使用Connection來代表資料庫的一個連線通道,並且傳遞SQL的指令給資料庫,以及進行指令的認可或終止 ```java Connection con =DriverManager.getConnection(URL, USER, PASSWORD); //用法是利用Connection建立物件實體,透過DriverManager調用getConnect方法 //並且輸入資料庫的URL、使用者名稱和密碼作為參數 //DriverManager會透過上面提到的驅動註冊表單來搜尋driver,因此註冊完畢後就不需更改 //因為一個資料庫能夠connection的資源很珍貴,使用後務必要進行關閉(close)的動作 ``` ## ✨關閉資料庫連線 ### close() 任何資料庫連線後都一定要做close的動作,來將連線的資源歸還,因為同時間能夠連線的人數有限 也因為一定要做close,所以會建議寫在finally區塊內,不管程式有沒有出現例外都會被執行到 程式碼如下: ```java finally{ if (con != null) { //防止沒有連線前就產生例外,但依然進行close,導致多個例外堆疊在一起 try { con.close(); } catch (SQLException se) { se.printStackTrace(); } } } ``` ### try-with-resources (Java7之後適用) 從Java7之後可以透過try-with-resources的方式進行程式撰寫,能夠保證物件資源最後一定會被close 只要將物件放在try{ }裡面,等到try區塊執行結束就會自動進行close 註 : 前提是該物件要實作**java.lang.Autocloseable**或**java.io.Closeable** 程式碼如下: ```java try (BufferedReader in = new BufferedReader(new FileReader(file))) { //注意此行,將要被關閉的的物件放在try後面的括弧內 String sql = ""; while ((sql = in.readLine()) != null) { System.out.println(sql); } //try區塊執行完畢,BufferedReader的物件in被進行close() } catch (IOException ex) { ex.printStackTrace(); } ``` # 靜態SQL指令 - Statement ## ✨取得Statement物件 透過Connection中的方法"Statement createStatement()來建立Statement物件 並且使用此物件來執行靜態的SQL指令,使用完畢後同樣也要close歸還資源 **註 : 越晚建立的物件資源越早歸還** 程式碼如下: ```java Statement stmt = con.createStatement(); //建立名為stmt的物件和資料庫進行溝通 ``` ## ✨透過物件執行SQL指令 物件執行SQL指令的方法有兩種,能夠根據需求來自行選擇 1. ResultSet executeQuery(String sql) : 能夠查詢資料庫,對應到SQL的select指令 會**傳回ResultSet** 2. int executeUpdate(String sql) : 能夠更新資料庫,對用到SQL的insert、update、delete指令 會**傳回一個整數,表示成功更新的筆數** executeUpdate程式碼如下: ```java stmt = con.createStatement(); //剛剛創建的stmt物件,負責進行溝通 int count = stmt.executeUpdate("insert into DEPARTMENT values (50,'人事部','南京復興')"); //小括弧內放的是SQL指令,會作為參數傳給方法,並與資料庫溝通 //這邊要新增的資料改用單引號,是因為在java程式中,雙引號會被當成字串處理 ``` executeQuery程式碼如下: ```java stmt = con.createStatement(); ResultSet rs = stmt.executeQuery("select * from DEPARTMENT"); //對應到select指令使用executeQuery ``` # 載入驅動、建立連線、執行SQL指令並close資源 以下是上面提到的內容全部程式碼串連起來,完成一筆新增資料的程式碼: ```java import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; public class HelloJDBC { public static final String URL = "jdbc:mysql://localhost:3306/jdbcsample?serverTimezone=Asia/Taipei"; // sql8.0.13版本後除了資料庫位址外,接著輸入一個問號並且要加上資料庫所在的時區 public static final String USER = "xxxx"; public static final String PASSWORD = "xxxx"; public static void main(String[] args) { Connection con = null; // 因為區域變數沒有預設的初始值 Statement stmt = null; try { // step 1 : 載入驅動 Class.forName("com.mysql.cj.jdbc.Driver"); System.setProperty("jdbc.drivers", "com.mysql.jdbc.Driver"); System.out.println("載入成功"); // step 2 : 建立連線 con = DriverManager.getConnection(URL, USER, PASSWORD); System.out.println("連線成功"); // step 3 : 執行(登出)SQL指令 stmt = con.createStatement(); int count = stmt.executeUpdate("insert into name values (1,'Jason','man')"); //這邊要新增的資料改用單引號,是因為在java程式中,雙引號會被當成字串處理 System.out.println(count + "資料更新成功"); //把回傳的影響資料筆數印出來 } catch (ClassNotFoundException ce) { //從此開始為例外處理相關程式碼 ce.printStackTrace(); } catch (SQLException se) { se.printStackTrace(); } finally { if (stmt != null) { //防止沒有連線前就產生例外,但依然進行close,導致多個例外堆疊在一起 try { stmt.close(); } catch (SQLException se) { se.printStackTrace(); } } if (con != null) { //防止沒有連線前就產生例外,但依然進行close,導致多個例外堆疊在一起 try { con.close(); } catch (SQLException se) { se.printStackTrace(); } } } } } ``` # ResultSet介面 ## ✨ResultSet的游標機制 當我們執行executeQuery()方法的時候,就會回傳資料庫查詢結果的資料列 在查詢結果第一列的上方會有一個游標(不管多少資料都是以第一列上方為預設位置) 藉由游標的操作,能夠讓這個游標不斷的往下移動到下一列,來讀取每一列的所有欄位值,直到最後一列資料的下面(也就表示沒有指向資料列) ## ✨移動游標操作 Java提供很多移動資料列游標的方法,像next()、previous()、last()、beforeFirst()、afterLast()...等 **next()方法很常用,此外重要的是next()也會回傳一個布林值**,這個布林值能夠當作我們進行迴圈取每一列欄位值的中止條件 至於查看目前資料列游標位置的方法有下列幾種: isFirst()、isLast()、isBeforeFirst()、isAfertLast() ## ✨使用游標取得欄位資料 當要對資料列中取得特定資料欄的時候,有以下的方法 type getType(int columnIndex)或type getType(String columnName) getType實際上是限制所取得欄位資料的資料型別,因此實際上應該會是填入getBoolean()、getByte()、getShort()、getInt()、getLong()....等get後面加上資料型別的方式 - 注意若是填入的是index的話,要小心查詢欄位的順序,因為查詢結果的欄位順序會跟下SQL指令的順序有直接關係 程式碼如下: ```java stmt = con.createStatement(); rs = stmt.executeQuery("select * from DEPARTMENT"); //對應到select指令使用executeQuery //rs = stmt.executeQuery("select dname, deptno, loc from DEPARTMENT"); // 此行可能報錯,當直接查詢欄位時,會根據我們SQL指令的順序來訂定欄位索引值順序 //因此使用索引值時須注意 while(rs.next()) { //next本身就代表兩種意思 //1.將游標移動到下一行; 2.回傳布林值作為迴圈終止條件 int deptno = rs.getInt("DEPTNO"); //取出指定欄位名稱(或索引值,JDBC的索引值從1開始)的值,給索引值的效率會比欄位名稱好 //並且依照取出的資料設定對應的型別 } ``` 其他方法補充 : int getRow() 方法可以取得目前所在的列編號,如果回傳是0表示可能是在第一列之前或最後一列之後