###### 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表示可能是在第一列之前或最後一列之後