---
# System prepended metadata

title: JDBC & JDBC TEMPLATE 介紹
tags: [' Database', Java, ' Spring Boot']

---

---
tags: Java, Database, Spring Boot
---

# JDBC & JDBC TEMPLATE 介紹

## 前言

資料庫操作是開發程式中不可或缺的一環，在 Java 中我們經常使用 JDBC 以及延伸的 JDBC Template，本篇將透過情境模擬來學習如何建置環境與實際應用。

## 目錄

* [JDBC & JDBC TEMPLATE 介紹](#JDBC-&-JDBC-TEMPLATE-介紹)
  * [前言](#前言)
  * [目錄](#目錄)
  * [介紹/基本概念](#介紹基本概念)
    * [JDBC](#JDBC)
    * [JDBC Template](#JDBC-Template)
    * [DAO](#DAO)
    * [Driver](#Driver)
    * [常見的關鍵詞](#常見的關鍵詞)
  * [應用情境說明](#應用情境說明)
  * [建置環境](#建置環境)
    * [MariaDB + HeidiSQL 安裝](#MariaDB-+-HeidiSQL-安裝)
    * [DB 準備與建立專案](#DB-準備與建立專案)
  * [實作過程](#實作過程)
    * [JDBC - 建立 Java 專案](#JDBC---建立-Java-專案)
    * [JDBC Template - 建立 Spring Boot 專案](#JDBC-Template---建立-Spring-Boot-專案)
  * [參考資料](#參考資料)
  * [撰寫紀錄](#撰寫紀錄)

## 介紹/基本概念

### JDBC

全名為 *Java Database Connectivity*，是 Java 提供客戶端連接資料庫的 API，根據不同廠商的資料庫只需要更換對應的 Driver 就可以成功連接。

### JDBC Template

Spring 封裝加工後的一個 class，使用上大同小異但是方便許多，也避免掉一些撰寫 JDBC 容易出錯的地方。

![1](https://i.imgur.com/KQtizoV.png)

### DAO

全名為 *Data Access Object*（資料存取物件），實作 DAO 能夠將資料庫存取與業務邏輯分離，操作資料庫只需要提供參數，無須理解背後運作。

### Driver

通常為資料庫廠商提供，負責告知 Spring Boot 該如何設定與操作資料庫。

### 常見的關鍵詞

* DriverManager：負責載入 Driver 與連接資料庫的動作
* Connection：負責與資料庫溝通，所有對資料庫的操作要藉此實現
* Statement：用於執行 SQL 操作
* PreparedStatement：用於執行 SQL 操作（參數化，防止 SQL Injection）
* ResultSet：用於存放查詢結果
* SQLException：SQL 操作上發生了例外狀況

## 應用情境說明

JDBC 為 Java 中最基本的資料庫操作方式，JDBC Template 雖然較麻煩但是能夠做到很好的權責分離。

## 建置環境

### MariaDB + HeidiSQL 安裝

* Step 1 - [安裝 MariaDB :link:](https://mariadb.org/download/)

:warning: 設立 root 帳號的密碼

![1](https://i.imgur.com/ICJkJrG.png)

:warning: 確認 port 是否有阻擋 (預設 3306)

![2](https://i.imgur.com/F5QMXG7.png)

* Step 2 - [安裝 HeidiSQL :link:](https://www.heidisql.com/download.php)

* Step 3 - 測試 HeidiSQL 連線

![3](https://i.imgur.com/BhECcGV.png)

### DB 準備與建立專案

請複製這段 table 的建立指令

```sql=
CREATE TABLE `mytable` (
 `name` VARCHAR(50) NULL DEFAULT NULL COLLATE 'utf8_general_ci',
 `id` VARCHAR(50) NULL DEFAULT NULL COLLATE 'utf8_general_ci'
)
COLLATE='utf8_general_ci'mytable3
ENGINE=InnoDB
;
```

開啟 HeidiSQL，在 MySQL 的查詢頁籤上執行這段程式碼

![1](https://i.imgur.com/PUELZt7.png)

## 實作過程

### JDBC - 建立 Java 專案

專案結構如下

![1](https://i.imgur.com/m5dP1az.png)

建立一個 package 名稱是 jdbc > JDBCDemo.java

![2](https://i.imgur.com/XLtKWeX.png)

建立完成後選擇 Driver 載入方式

1. 無 Maven -> 下載 Driver 並匯入專案 library

  | Database | Driver Name                                        |
  | -------- |:-------------------------------------------------- |
  | MySQL    | [Connector for MySQL :link:][DolphinDL]            |
  | MariaDB  | [Connector for MariaDB :link:][MariaDL]            |
  | MSSQL    | [Microsoft JDBC Driver for SQL Server :link:][MSDL]|

  [DolphinDL]: https://dev.mysql.com/downloads/connector/j/
  [MariaDL]: https://mariadb.com/downloads/#connectors
  [MSDL]: https://docs.microsoft.com/zh-tw/sql/connect/jdbc/download-microsoft-jdbc-driver-for-sql-server?view=sql-server-ver15

  放入專案同階層的資料夾底下

  ![3](https://i.imgur.com/QyjwJk6.png)

  File > Project Structure > Libraries > + > Java > 選擇 jar
  
  ![4](https://i.imgur.com/u5YCGh2.png)
  
  ![5](https://i.imgur.com/OgXJObj.jpg)

2.有 Maven -> [透過 Maven 自動下載 :link:](https://mvnrepository.com/artifact/mysql/mysql-connector-java/8.0.22)

![1](https://i.imgur.com/juucIG7.png)

* pom.xml

```xml=
<dependencies>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.22</version>
    </dependency>
</dependencies>
```

#### JDBC 部分

##### 前置動作 : 宣告 Connection 物件並使用 DriverManager 建立連線

```java
Connection conn = null;

// 使用 Class.forName() 載入 Driver 給 DriverManager
try {
    Class.forName("com.mysql.cj.jdbc.Driver");
    System.out.println("Driver loaded!");
} catch (ClassNotFoundException e) {
    System.out.println("找不到驅動程式類別");
    e.printStackTrace();
}

// 再使用 DriverManager.getConnection() 取得有效的 Connection 物件
// jdbc 格式 : jdbc:DB 廠商名/辨識字詞:URL
try {
    conn = DriverManager.getConnection
    (
     "jdbc:mysql://localhost:3306/mysql?serverTimezone=UTC", // URL
     "root", // 用户名
     "********"// 密碼
    ); 
} catch (SQLException e) {
    System.out.println("連線失敗");
    e.printStackTrace();
}
```

##### 實作 : 使用 Statement & PreparedStatement

```java=
// 建立 Statement 物件
Statement stmt = conn.createStatement();
```

新刪查改範例

```java=
// 新增一筆資料
stmt.executeUpdate("INSERT INTO MyTable( name ) VALUES ( 'my name' ) ");

// 修改一筆資料
stmt.executeUpdate("UPDATE MyTable SET name = 'Another name' WHERE name = 'my name' ");

// 刪除一筆資料
stmt.executeUpdate("DELETE FROM MyTable WHERE name = 'Another name' ");

// 查詢一筆資料
ResultSet rs = stmt.executeQuery("SELECT * FROM MyTable");

// 確認結果集是否為空
while (rs.next()) {
    // 取得一筆資料的欄位數量
    int numColumns = rs.getMetaData().getColumnCount();
    // 使用迴圈將資料印出
    for (int i = 1; i <= numColumns; i++) {
        System.out.println("COLUMN " + i + " = " + rs.getObject(i));
    }
}
```

使用後關閉物件

```java
rs.close();
stmt.close();
```

PrepareStatement

```java
// 建立 PreparedStatement 物件
PreparedStatement ps = null;
```

新刪查改範例

```java
// 新增一筆資料
ps = conn.prepareStatement("INSERT INTO MyTable(id, name) VALUES (?, ?)");
ps.setString(1, "2000159");
ps.setString(2, "Eddie");
ps.executeUpdate();

// 修改一筆資料
ps = conn.prepareStatement("UPDATE MyTable SET name = ? WHERE id = ? ");
ps.setString(1, "Eddie2");
ps.setString(2, "2000159");
ps.executeUpdate();

// 刪除一筆資料
ps = conn.prepareStatement("DELETE FROM MyTable WHERE id = ?");
ps.setString(1, "2000159");
ps.executeUpdate();

// 查詢一筆資料
ps = conn.prepareStatement("SELECT * FROM MyTable WHERE id = ?");
// 填入參數
ps.setString(1, "2000159");

// 執行
rs = ps.executeQuery();

// 確認結果集是否為空
while (rs.next()) {
    // 取得一筆資料的欄位數量
    int numColumns = rs.getMetaData().getColumnCount();
    // 使用迴圈將資料印出
    for (int i = 1; i <= numColumns; i++) {
        System.out.println("COLUMN " + i + " = " + rs.getObject(i));
     }
}
```

原始碼

* JDBCDemo.java

```java=1
package jdbc;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class JDBCDemo {

    public static void main(String[] args) throws SQLException {

      Connection conn = null;

                // 使用 Class.forName() 載入 Driver 給 DriverManager
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
            System.out.println("Driver loaded!");
        } catch (ClassNotFoundException e) {
            System.out.println("找不到驅動程式類別");
            e.printStackTrace();
        }

        // 再使用 DriverManager.getConnection() 取得有效的 Connection 物件
        // jdbc 格式 : jdbc:DB 廠商名/辨識字詞:URL
        try {
            conn = DriverManager.getConnection
                (
                "jdbc:mysql://localhost:3306/mysql?serverTimezone=UTC", // URL
                "root", // 用户名
                "********" // 密碼
                );
            } catch (SQLException e) {
            System.out.println("連線失敗");
            e.printStackTrace();
        }

        // 建立 Statement 物件
        Statement stmt = conn.createStatement();

        // 新增一筆資料
        stmt.executeUpdate("INSERT INTO MyTable( name ) VALUES ( 'my name' ) ");

        // 修改一筆資料
        stmt.executeUpdate("UPDATE MyTable SET name = 'Another name' WHERE name = 'my name' ");

        // 刪除一筆資料
        stmt.executeUpdate("DELETE FROM MyTable WHERE name = 'Another name' ");

        // 查詢一筆資料
        ResultSet rs = stmt.executeQuery("SELECT * FROM MyTable");

        // 確認結果集是否為空
        while (rs.next()) {
        // 取得一筆資料的欄位數量
        int numColumns = rs.getMetaData().getColumnCount();
            // 使用迴圈將資料印出
            for (int i = 1; i <= numColumns; i++) {
            System.out.println("COLUMN " + i + " = " + rs.getObject(i));
            }
        }
        // 記得關閉物件
        rs.close();
        stmt.close();


            /*
                實務上為了防止 SQL Injection 而偏好使用能夠將
                查詢條件參數化的 PreparedStatement 物件
             */

        // 建立 PreparedStatement 物件
        PreparedStatement ps = null;

        // 新增一筆資料
        ps = conn.prepareStatement("INSERT INTO MyTable(id, name) VALUES (?1, ?2)");
        ps.setString(1, "2000159");
        ps.setString(2, "Eddie");
        rs = ps.executeUpdate();

        // 修改一筆資料
        ps = conn.prepareStatement("UPDATE MyTable SET name = ?1 WHERE id = ?2 ");
        ps.setString(1, "Eddie2");
        ps.setString(2, "2000159");
        rs = ps.executeUpdate();

        // 刪除一筆資料
        ps = conn.prepareStatement("DELETE FROM MyTable WHERE id = ?1");
        ps.setString(1, "2000159");
        rs = ps.executeUpdate();

        // 查詢一筆資料
        // 使用問號作為參數
        ps = conn.prepareStatement("SELECT * FROM MyTable WHERE id = ?");
        // 填入參數
        ps.setString(1, "2000159");

        // 執行
        rs = ps.executeQuery();

        // 確認結果集是否為空
        while (rs.next()) {
            // 取得一筆資料的欄位數量
            int numColumns = rs.getMetaData().getColumnCount();
            // 使用迴圈將資料印出
            for (int i = 1; i <= numColumns; i++) {
                System.out.println("COLUMN " + i + " = " + rs.getObject(i));
            }
        }
    }
}
```

### JDBC Template - 建立 Spring Boot 專案

我們先建立一個簡易的 Spring Boot Web 專案
並選擇需要採用的 dependencies

![1](https://i.imgur.com/fCXi9y2.png)

如果要手動加入 Spring JDBC 依賴，需要在 pom.xml 加入

```xml=
<!-- JDBC API -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
```

專案結構如下

![2](https://i.imgur.com/RI2o0Gg.png)

#### JDBC Template 部分

Application.properties DB 連線設定參數

```java=
spring.datasource.url=jdbc:mysql://localhost:3306/mysql?serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=********
```

##### Controller

* DemoController.java

```java=
package com.systex.demo.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import com.systex.demo.db.MyTableDAOImpl;
import com.systex.demo.db.People;

@RestController
public class DemoController {

    // 注入 DAO 物件
    @Autowired
    MyTableDAOImpl dao;

    /* 新增接口
     * Method : post
     * parameter : People
     * return : String
     */
    @PostMapping("/add")
    public String create(@RequestBody People people) {
        dao.addPeople(people);
        return "OK";
    }

    /* 刪除接口
     * Method : get
     * parameter : String
     * return : String
     */
    @GetMapping("/delete/{name}")
    public String delete(@PathVariable String name) {
        dao.deletePeople(name);
        return "OK";
    }

    /* 查詢接口
     * Method : get
     * parameter : String
     * return : String
     */
    @GetMapping("/search/{id}")
    public String search(@PathVariable String id) {
        People p = dao.searchPeople(id);
        return p.getName();
    }

    /* 修改接口
     * Method : post
     * parameter : People
     * return : String
     */
    @PostMapping("/update")
    public String update(@RequestBody People people) {
        dao.modifyPeople(people);
        return "OK";
    }
}
```

##### DAO 介面與對應資料表的物件

* IMyTableDAO.java

    定義新刪查改方法

```java=
package com.systex.demo.db;

public interface IMyTableDAO {

    // 新增人員
    public void addPeople(People p);
    // 刪除人員
    public void deletePeople(String name);
    // 查詢人員
    public People searchPeople(String id);
    // 修改人員
    public void modifyPeople(People p);

}
```

* People.java

    定義資料庫結構

```java=
package com.systex.demo.db;

public class People {
    
    // 欄位 : 人員 ID
    private String id;
    
    // 欄位 : 人員姓名
    private String name;

    public void setId(String id) {
        this.id = id;
    }

    public String getId() {
        return id;
    }
    
    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

}
```

##### 實作 DAO 介面

* MyTableDAOImpl.java

```java=
package com.systex.demo.db;

import java.sql.ResultSet;
import java.sql.SQLException;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowCallbackHandler;
import org.springframework.stereotype.Service;

@Service
public class MyTableDAOImpl implements IMyTableDAO {

    public MyTableDAOImpl() {

    }

    // 使用 Spring bean 自動注入 jdbcTemplate 物件
    @Autowired
    private JdbcTemplate jdbcTemplate;

    public void addPeople(People p) {
        String SQL = "INSERT INTO MyTable(id, name) VALUES (?,?) ";
        jdbcTemplate.update(SQL, p.getId(), p.getName());
    }

    @SuppressWarnings("deprecation")
    public People searchPeople(String id) {
        final People people = new People();
        jdbcTemplate.query("SELECT * FROM MyTable WHERE id = ?", new Object[] { id },
                new RowCallbackHandler() {
                    public void processRow(ResultSet rs) throws SQLException {
                        System.out.println(rs.getString("id"));
                        System.out.println(rs.getString("name"));
                        people.setId(rs.getString("id"));
                        people.setName(rs.getString("name"));
                    }
                });
        return people;
    };

    public void deletePeople(String name) {
        String SQL = "DELETE FROM MyTable WHERE name = ? ";
        jdbcTemplate.update(SQL, name);
    };

    public void modifyPeople(People p) {
        String SQL = "Update MyTable SET name = ? WHERE id = ?";
        jdbcTemplate.update(SQL, p.getName(), p.getId());
    };

}

```

##### 細部說明 - 新增

新增 Method 使用 jdbcTemplate.update（SQL 指令 (String),參數 (any)args[]）

```java=
public void addPeople(People p) {
        String SQL = "INSERT INTO MyTable(id, name) VALUES (?,?) ";
        jdbcTemplate.update(SQL, p.getId(), p.getName());
    }
```

執行結果

![1](https://i.imgur.com/NcKPfJc.png)

##### 細部說明 - 刪除

新增 Method 使用 jdbcTemplate.update(SQL 指令 (String),參數 (any)args[])

```java=
public void deletePeople(String name) {
    String SQL = "DELETE FROM MyTable WHERE name = ? ";
    jdbcTemplate.update(SQL, name);
};

```

執行結果

![2](https://i.imgur.com/eUfXmj9.png)

##### 細部說明 - 查詢

新增 Method 使用 jdbcTemplate.update（SQL 指令 (String),參數 (any)args[]）

```java=
public People searchPeople(String id) {
    String SQL = "SELECT * FROM MyTable WHERE id = ?";

    // 這裡使用 Mapper 自動映射，不擅使用的話也可以使用 jdbcTemplate.queryForList() 自行包裝 People 物件
    @SuppressWarnings("deprecation")
    public People searchPeople(String id) {
        People people = new People();
        jdbcTemplate.query("SELECT * FROM MyTable WHERE id = ?", new Object[] { id },
                new RowCallbackHandler() {
                    public void processRow(ResultSet rs) throws SQLException {
                        people.setId(rs.getString("id"));
                        people.setName(rs.getString("name"));
                    }
                });
        return people;
    };
```

執行結果

![1](https://i.imgur.com/T9Vbey0.png)

##### 細部說明 - 修改

新增 Method 使用 jdbcTemplate.update（SQL 指令 (String),參數 (any)args[]）

```java=
public void modifyPeople(People p) {
        String SQL = "Update MyTable SET name = ? WHERE id = ?";
        jdbcTemplate.update(SQL, p.getName(), p.getId());
};
```

執行結果

![1](https://i.imgur.com/EFtSY9O.png)

---

## 參考資料

* [關於 JdbcTemplate 你只需要這一篇](https://medium.com/@steph.c/jdbctemplate-%E7%AF%84%E4%BE%8B-2c9a1f3718ba)
* [使用 JdbcTemplate](https://openhome.cc/Gossip/SpringGossip/UseJdbcTemplate.html)
* [Java Tutorial 第三堂（2）使用 spring-jdbc 存取資料庫](https://openhome.cc/Gossip/CodeData/JavaTutorial/SpringJdbc.html)

## 撰寫紀錄

| 人員 | 日期 | 修改紀錄 |
| - | - | - |
| 懿修 | 2022/7 | 初版 |
