MySQL for Java

      在〈MySQL for Java〉中尚無留言

手動下載驅動

請先到 https://dev.mysql.com/downloads/connector/j/ 下載最新的驅動程式,然後選取 Platform independent。下載完後直接解壓縮。

NetBeans 加入 jar Library

在 NetBeans中開啟新專案,然後於 NetBeans 的專案名稱下的 Libraries 按右鍵 Add JAR/Forder,再選取剛剛解壓縮的 .jar 檔。

java_mysql

自動下載驅動程式

如果是使用 gradle 的話,請可於 build.gradle 下加入如下藍色部份。

dependencies {
    testImplementation platform('org.junit:junit-bom:5.10.0')
    testImplementation 'org.junit.jupiter:junit-jupiter'
    implementation("mysql:mysql-connector-java:8.0.33")
}

最新的版本可由如下網址查詢 https://repo.maven.apache.org/maven2/mysql/mysql-connector-java/

載入驅動程式

使用 Java 6 及以後的版本,不需要此步驟,Java 會自動載入驅動程式。這是因為 JDBC driver.jar 中有 META-INF/service/java.sql.Driver 這個檔,裏面已設定如下。

com.mysql.cj.jdbc.Driver

如果是 Java 6 之前的版本,就需使用 Class.forName 載入驅動程式。

try {
    Class.forName("com.mysql.cj.jdbc.Driver");
} 
catch (ClassNotFoundException ex) {}

請注意前後都要用 try-catch包含,抓取 ClassNotFoundException 例外。

建立連線

在NetBeans12 中,可能有些 bug,無法用 Alt+Enter 自動輸入 java.sql 套件,所以要手動自已打上去。

import java.sql.*;

連線字串有沒有加 useSSL=false 及 serverTimezone=UTC 好像都沒差。

Connection conn=DriverManager.getConnection("jdbc:mysql://localhost/cloud?useSSL=false&serverTimezone=UTC", "帳號","密碼");

如果出現如下的錯誤,才加入 serverTimezone=UTC。

The server time zone value '�x�_�зǮɶ�' is unrecognized or represents more than one time zone. You must configure either the server or JDBC driver (via the 'serverTimezone' configuration property) to use a more specifc time zone value if you want to utilize time zone support

在 Android 中,連線的建立及其他的SQL語法操作,都必需在新的執行緒上執行。在 Java 中是沒有強制性規定,但如果操作時間過久,也要考慮使用新的執行緒

PreparedStatement 命令物件

PreparedStatement 為命令物件,此物件有防止 SQL Injection 的機制,是比較安全的用法。 stmt.executeQuery 執行 SQL 語法,最後產生 ResultSet 資料集。

資料集的取得可以使用 rs.getString(欄位數) or rs.getString(欄位名)。請注意欄位數是從 1 開始。

String cmd="select * from 台灣股市");
PreparedStatement stmt = conn.PreparedStatement(cmd);
ResultSet rs = stmt.executeQuery("select * from 台灣股市");
while (rs.next()) {
    System.out.printf("%s, %s, %s\n", 
    rs.getString(1), 
    rs.getString("護照"), 
    rs.getString(3));
}

Statement 命令物件

Statement 亦為另一種命令物件,但沒有 SQL Injection 的防止機制,比較危險,故不建議使用。

Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("select * from 台灣股市");
while (rs.next()) {
    System.out.printf("%s, %s, %s\n",
            rs.getString(2),
            rs.getFloat("開盤"),
            rs.getFloat("收盤"));
}

關閉連線

在取得 ResultSet 資料集後,記得要把連線關閉。如果在處理 ResultSet 之前就關閉連線,則 ResultSet 會一並關掉,結果就是無法取得資料。

while (rs.next()) {
    System.out.printf("%s, %s, %s\n",
            rs.getString(2),
            rs.getFloat("開盤"),
            rs.getFloat("收盤"));
}
conn.close();

取得資料表欄位資料

想要知道資料表有那些欄位及欄位資料型別,可在取得 ResultSet 後使用 rs.getMetaData,然後再由 meta 取得欄位名及資料型別,如下代碼所示。請注意第 i 欄位的索引是從 1 開始

while (rs.next()) {
    datas.add(new Item(
            rs.getString(2),
            rs.getFloat(3),
            rs.getFloat(4),
            rs.getFloat(5),
            rs.getFloat(6)
    ));
}
ResultSetMetaData meta=rs.getMetaData();
for (int i=1;i<=meta.getColumnCount();i++){
    System.out.printf("%s, %s\n",meta.getColumnName(i), meta.getColumnTypeName(i));
}
conn.close();

讀取資料技巧

底下的完整代碼能從本站的 cloud 資料庫讀取台灣股市資料表,此資料表有「日期」、「開盤」、「最高」、「最低」及「收盤」等欄位。

Java 的 ResultSet 僅供查詢用,且需在連線關閉前就處理完畢,不像 Python 使用二維 list 那麼方便。因此通常會使用自訂資料結構 class Item,然後儲存在 ArrayList 中。

package net.ddns.mahaljsp;

import java.sql.*;
import java.util.LinkedList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        try {
            Connection conn= DriverManager.getConnection(
                    "jdbc:mysql://ip/cloud?useSSL=false&serverTimezone=UTC",
                    "帳號",
                    "密碼"
            );
            String cmd="select * from 台灣股市";
            PreparedStatement stmt=conn.prepareStatement(cmd);
            ResultSet rs=stmt.executeQuery();
            List<Item> datas = new LinkedList<>();
            while (rs.next()) {
                datas.add(new Item(
                        rs.getString(2),
                        rs.getFloat(3),
                        rs.getFloat(4),
                        rs.getFloat(5),
                        rs.getFloat(6)
                ));
            }
            conn.close();

            //依日期升冪
            datas.sort((a,b) -> a.date.compareTo(b.date));
            //依日期降冪
            //datas.sort((a,b) -> b.date.compareTo(a.date));

            for (Item item : datas) {
                System.out.printf("%s, %.2f, %.2f, %.2f, %.2f\n",
                    item.date,item.open, item.height, item.low, item.close
                );
            }
        }
        catch (SQLException e){System.out.println("SQL Error");}
    }
}
class Item{
    String date;
    float open, height, low, close;
    public Item(String date, float open, float height, float low, float close){
        this.date=date;
        this.open=open;
        this.height=height;
        this.low=low;
        this.close=close;
    }
}

新增資料

上述的 datas 經由本站資料庫取得後,再把 datas 寫入自已的資料庫。寫入資料庫使用 stmt.executeUpdate 執行 SQL 語法,如下代碼所示。

conn= DriverManager.getConnection(
    "jdbc:mysql://localhost/cloud?useSSL=false&serverTimezone=UTC",
        "帳號",
        "密碼"
);

stmt=conn.prepareStatement("truncate table 台灣股市");
stmt.execute();

for (Item item:datas) {
    cmd = String.format("insert into 台灣股市 (日期, 開盤, 最高, 最低, 收盤) values ('%s', %.2f, %.2f, %.2f, %.2f)",
            item.date, item.open, item.height, item.low, item.close);
    stmt = conn.prepareStatement(cmd);
    int rows=stmt.executeUpdate();
    System.out.println(cmd);
}
conn.close();

stmt.execute 方法會回傳 true/false 佈林值來表示執行 SQL 是否成功。stmt.executeUpdate 則回傳受影響的列數。通常使用 executeQuery/executeUpdate 執行 select、insert、update、delete 四大 SQL 語法,其它 SQL 語法則使用 execute 執行。

快速新增資料

上面的寫法是一筆一筆寫入,效能非常的差,如果預到大型資料表絕對不是好方法,需改成如下方式。

conn= DriverManager.getConnection(
    "jdbc:mysql://localhost/cloud?useSSL=false&serverTimezone=UTC",
        "帳號",
        "密碼"
);

stmt=conn.prepareStatement("truncate table 台灣股市");
stmt.execute();

StringBuilder sb = new StringBuilder("insert into 台灣股市 (日期, 開盤, 最高, 最低, 收盤) values ");
for (Item item:datas){
    sb.append(String.format("('%s', %.2f, %.2f, %.2f, %.2f),",
            item.date, item.open, item.height, item.low, item.close));
}
sb.deleteCharAt(sb.length()-1);

stmt=conn.prepareStatement(sb.toString());
stmt.executeUpdate();
conn.close();

此方法是在 values 後面新增多筆資料,因為命令字串相當長,使用 String 的效能非常差,需改用 StringBuilder 改善效能。最後使用 conn.prepareStatememt 時,需把 StringBuilder 轉成 String。

模擬 Python 用法

上述代碼中,要用 Class.forName 載入驅動,使用 DriverManager.getConnection 建立連線,再使用 conn.createStatement 建立命令物件,實在很煩人,如果能像 Python 那麼直覺就好了。

不過此法是練習物件導向用的,實際上不建議這麼作。

package net.ddns.mahaljsp;
import java.sql.*;
public class Main {
    public static void main(String[] args) {
        try(Connect conn= mysql.connect(
                "ip",
                "帳號",
                "密碼",
                "資料庫"
        )) {
            Cursor cursor=conn.cursor();
            try (ResultSet rs = cursor.executeQuery(
                    "select * from 台灣股市"
            )) {
                while (rs.next()) {
                    System.out.printf("%s, %s, %s\n",
                            rs.getString(2),
                            rs.getFloat("開盤"),
                            rs.getFloat("收盤"));
                }
            }
            catch(SQLException e){
                throw new RuntimeException(e);
            }
        }
        catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}
class Cursor{
    Statement stmt;
    public Cursor(Statement stmt){
        this.stmt=stmt;
    }
    public ResultSet executeQuery(String cmd){
        try {
            return stmt.executeQuery(cmd);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
}
class Connect implements AutoCloseable {
    Connection conn;
    public Connect(Connection conn){
        this.conn=conn;
    }
    public Cursor cursor(){
        try {
            return new Cursor(conn.createStatement());
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void close() throws Exception {
        conn.close();
    }
}
class mysql{
    public static Connect connect(
            String url,
            String userAccount,
            String password,
            String database
    ){
        try {
            Connection conn= DriverManager.getConnection(
                    String.format("jdbc:mysql://%s/%s", url, database),
                    userAccount,
                    password
            );
            return new Connect(conn);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
}

本例完整代碼

底下是本篇說明的完整代碼

package net.ddns.mahaljsp;

import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.sql.*;
import java.util.*;

public class Main {
    public static void main(String[] args) {
        try {
            System.setOut(new PrintStream(System.out,true, "UTF-8"));
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
        try {
            Connection conn= DriverManager.getConnection(
                    "jdbc:mysql://ip/cloud?useSSL=false&serverTimezone=UTC",
                    "帳號",
                    "密碼"
            );
            String cmd= "select * from 台灣股市";
            PreparedStatement stmt=conn.prepareStatement(cmd);
            ResultSet rs=stmt.executeQuery();
            List datas = new LinkedList<>();
            while (rs.next()) {
                datas.add(new Item(
                        rs.getString(2),
                        rs.getFloat(3),
                        rs.getFloat(4),
                        rs.getFloat(5),
                        rs.getFloat(6)
                ));
            }
            ResultSetMetaData meta=rs.getMetaData();
            for (int i=1;i<=meta.getColumnCount();i++){
                System.out.printf("%s, %s\n",meta.getColumnName(i), meta.getColumnTypeName(i));
            }
            conn.close();

            //依日期升冪
            datas.sort((a,b) -> a.date.compareTo(b.date));

            //依日期降冪
            //datas.sort((a,b) -> b.date.compareTo(a.date));

            for (Item item : datas) {
                System.out.printf("%s, %.2f, %.2f, %.2f, %.2f\n",
                    item.date,item.open, item.height, item.low, item.close
                );
            }


            conn= DriverManager.getConnection(
                "jdbc:mysql://localhost/cloud?useSSL=false&serverTimezone=UTC",
                    "帳號",
                    "密碼"
            );

            stmt=conn.prepareStatement("truncate table 台灣股市");
            stmt.execute();

            StringBuilder sb = new StringBuilder("insert into 台灣股市 (日期, 開盤, 最高, 最低, 收盤) values ");
            for (Item item:datas){
                sb.append(String.format("('%s', %.2f, %.2f, %.2f, %.2f),",
                        item.date, item.open, item.height, item.low, item.close));
            }
            sb.deleteCharAt(sb.length()-1);

            stmt=conn.prepareStatement(sb.toString());
            stmt.executeUpdate();
            conn.close();
        }
        catch (SQLException e){System.out.println("SQL Error");}
    }
}
class Item{
    String date;
    float open, height, low, close;
    public Item(String date, float open, float height, float low, float close){
        this.date=date;
        this.open=open;
        this.height=height;
        this.low=low;
        this.close=close;
    }
}

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *