手動下載驅動
請先到 https://dev.mysql.com/downloads/connector/j/ 下載最新的驅動程式,然後選取 Platform independent。下載完後直接解壓縮。
NetBeans 加入 jar Library
在 NetBeans中開啟新專案,然後於 NetBeans 的專案名稱下的 Libraries 按右鍵 Add JAR/Forder,再選取剛剛解壓縮的 .jar 檔。
自動下載驅動程式
如果是使用 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; } }