雖然生產多用連接池(HikariCP/Druid)+ ORM(MyBatis/JPA),但理解 JDBC 原生流程對排查連接洩漏、事務邊界、SQL 注入等問題至關重要。本文從驅動加載到工具類封裝,還原最核心路徑。
// 1. 註冊驅動(JDBC 4.0+ 可省,Class.forName 觸發 Driver 靜態塊自動註冊)
Class.forName("com.mysql.jdbc.Driver"); // 5.1.x
// Class.forName("com.mysql.cj.jdbc.Driver"); // 8.0+
// 2. 獲取連接
Connection conn = DriverManager.getConnection(
"jdbc:mysql://localhost:3308/lab?useSSL=false&serverTimezone=UTC",
"lmapp", "lmapp");
// 3. 預編譯語句(防注入、提升性能)
PreparedStatement ps = conn.prepareStatement(
"INSERT INTO lab_static_attribute(uid,login_time,out_time,last_time) VALUES(?,?,?,?)");
ps.setInt(1, uid);
ps.setTimestamp(2, loginTime);
ps.setTimestamp(3, outTime);
ps.setLong(4, lastTime);
ps.executeUpdate();
// 4. 釋放資源(反向順序,finally 或 try-with-resources)
ps.close();
conn.close();
public class Const {
public static final String IP = "localhost";
public static final String PORT = "3308";
public static final String DB = "lab";
public static final String USER = "lmapp";
public static final String PWD = "lmapp";
public static final String DRIVER = "com.mysql.jdbc.Driver";
public static final String URL = "jdbc:mysql://" + IP + ":" + PORT + "/" + DB
+ "?useSSL=false&serverTimezone=UTC&characterEncoding=UTF-8";
}
public class MysqlUtil {
// 單例連接(生產禁用,必須用連接池)
private static Connection conn;
public static Connection getConnection() throws SQLException {
if (conn == null || conn.isClosed()) {
conn = DriverManager.getConnection(Const.URL, Const.USER, Const.PWD);
}
return conn;
}
// 增刪改 通用
public static boolean executeUpdate(String sql, List<Object> params) {
try (PreparedStatement ps = getConnection().prepareStatement(sql)) {
setParams(ps, params);
return ps.executeUpdate() > 0;
} catch (SQLException e) {
e.printStackTrace();
return false;
}
}
// 查詢單條 -> Map
public static Map<String, Object> queryOne(String sql, List<Object> params) {
try (PreparedStatement ps = getConnection().prepareStatement(sql);
ResultSet rs = ps.executeQuery()) {
setParams(ps, params);
if (rs.next()) return resultSetToMap(rs);
} catch (SQLException e) { e.printStackTrace(); }
return null;
}
// 查詢多條 -> List<Map>
public static List<Map<String, Object>> queryList(String sql, List<Object> params) {
List<Map<String, Object>> list = new ArrayList<>();
try (PreparedStatement ps = getConnection().prepareStatement(sql);
ResultSet rs = ps.executeQuery()) {
setParams(ps, params);
while (rs.next()) list.add(resultSetToMap(rs));
} catch (SQLException e) { e.printStackTrace(); }
return list;
}
private static void setParams(PreparedStatement ps, List<Object> params) throws SQLException {
if (params != null) {
for (int i = 0; i < params.size(); i++) ps.setObject(i + 1, params.get(i));
}
}
private static Map<String, Object> resultSetToMap(ResultSet rs) throws SQLException {
ResultSetMetaData md = rs.getMetaData();
int cols = md.getColumnCount();
Map<String, Object> map = new LinkedHashMap<>();
for (int i = 1; i <= cols; i++) map.put(md.getColumnLabel(i), rs.getObject(i));
return map;
}
}
public class UserDao {
public int add(User u) {
String sql = "INSERT INTO lab_static_attribute(uid,login_time,out_time,last_time) VALUES(?,?,?,?)";
List<Object> params = Arrays.asList(u.getUid(), u.getLoginTime(), u.getOutTime(), u.getLastTime());
return MysqlUtil.executeUpdate(sql, params) ? 1 : 0;
}
}
| 項目 | 說明 |
|---|---|
| 驅動版本匹配 | MySQL 5.x 用 5.1.x,8.x 用 8.0+,類名不同 |
| URL 參數 | useSSL=false、serverTimezone=UTC、characterEncoding=UTF-8、allowPublicKeyRetrieval=true (8.0) |
| 預編譯 | 必須用 PreparedStatement,防 SQL 注入、複用執行計劃 |
| 資源關閉 | Java 7+ try-with-resources 自動關閉 ResultSet/Statement/Connection |
| 事務控制 | conn.setAutoCommit(false) → 操作 → commit()/rollback() |
| 連接池 | 生產必須用 HikariCP/Druid,配置最大/最小連接、超時、驗證查詢 |
| 異常處理 | 捕獲 SQLException 後轉換為業務異常,保留堆棧 |
rs.getMetaData() 獲取列名/類型,實現通用 Map/Bean 映射rs.wasNull() 判斷數據庫 NULL(基本類型 getInt 返回 0 無法區分)rs.getObject(columnIndex) 通用取值,配合類型轉換stmt.setFetchSize(Integer.MIN_VALUE) (MySQL) 流式讀取,避免 OOM掌握 JDBC 底層細節,能在 ORM 出問題時「看穿本質」,也是面試高頻考點的硬核加分項。