package com.jdbc.dbutils;
import java.lang.reflect.Field;
import java.sql.*;
import java.util.*;
public class JdbcUtils {
// 配置常量 (生產環境應外部化到配置文件/環境變量)
private static final String DRIVER = "com.mysql.cj.jdbc.Driver"; // MySQL 8.0+
private static final String URL = "jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=UTC&characterEncoding=utf8";
private static final String USERNAME = "root";
private static final String PASSWORD = "your_password";
private Connection conn;
private PreparedStatement pstmt;
private ResultSet rs;
public JdbcUtils() {
try {
Class.forName(DRIVER);
} catch (ClassNotFoundException e) {
throw new RuntimeException("JDBC驅動加載失敗", e);
}
}
/** 獲取連接 (建議改為從 DataSource 獲取) */
public Connection getConnection() throws SQLException {
if (conn == null || conn.isClosed()) {
conn = DriverManager.getConnection(URL, USERNAME, PASSWORD);
}
return conn;
}
/** 通用增刪改 (INSERT/UPDATE/DELETE) */
public boolean update(String sql, List<Object> params) throws SQLException {
pstmt = getConnection().prepareStatement(sql);
bindParams(params);
int rows = pstmt.executeUpdate();
return rows > 0;
}
/** 查詢單條 -> Map */
public Map<String, Object> queryForMap(String sql, List<Object> params) throws SQLException {
List<Map<String, Object>> list = queryForList(sql, params);
return list.isEmpty() ? null : list.get(0);
}
/** 查詢多條 -> List<Map> */
public List<Map<String, Object>> queryForList(String sql, List<Object> params) throws SQLException {
pstmt = getConnection().prepareStatement(sql);
bindParams(params);
rs = pstmt.executeQuery();
return mapResultSet(rs);
}
/** 反射查詢單條 -> JavaBean */
public <T> T queryForObject(String sql, List<Object> params, Class<T> clazz) throws Exception {
List<T> list = queryForList(sql, params, clazz);
return list.isEmpty() ? null : list.get(0);
}
/** 反射查詢多條 -> List<JavaBean> */
public <T> List<T> queryForList(String sql, List<Object> params, Class<T> clazz) throws Exception {
pstmt = getConnection().prepareStatement(sql);
bindParams(params);
rs = pstmt.executeQuery();
return mapResultSetToBean(rs, clazz);
}
/** 綁定參數 */
private void bindParams(List<Object> params) throws SQLException {
if (params != null) {
for (int i = 0; i < params.size(); i++) {
pstmt.setObject(i + 1, params.get(i));
}
}
}
/** ResultSet -> List<Map> */
private List<Map<String, Object>> mapResultSet(ResultSet rs) throws SQLException {
List<Map<String, Object>> list = new ArrayList<>();
ResultSetMetaData meta = rs.getMetaData();
int colCount = meta.getColumnCount();
while (rs.next()) {
Map<String, Object> row = new HashMap<>();
for (int i = 1; i <= colCount; i++) {
String colName = meta.getColumnLabel(i); // 別名優先
Object val = rs.getObject(i);
row.put(colName, val);
}
list.add(row);
}
return list;
}
/** ResultSet -> List<JavaBean> (反射核心) */
private <T> List<T> mapResultSetToBean(ResultSet rs, Class<T> clazz) throws Exception {
List<T> list = new ArrayList<>();
ResultSetMetaData meta = rs.getMetaData();
int colCount = meta.getColumnCount();
while (rs.next()) {
T obj = clazz.getDeclaredConstructor().newInstance(); // 需無參構造器
for (int i = 1; i <= colCount; i++) {
String colName = meta.getColumnLabel(i);
Object val = rs.getObject(i);
if (val == null) continue;
try {
Field field = clazz.getDeclaredField(colName);
field.setAccessible(true); // 破解 private
// 類型轉換處理 (簡化版,生產需完善)
field.set(obj, convertValue(val, field.getType()));
} catch (NoSuchFieldException ignored) {
// 列名與字段名不匹配時忽略 (或拋出異常)
}
}
list.add(obj);
}
return list;
}
/** 簡單類型轉換 (擴展點) */
private Object convertValue(Object val, Class<?> targetType) {
if (targetType.isInstance(val)) return val;
if (targetType == Integer.class || targetType == int.class) return ((Number) val).intValue();
if (targetType == Long.class || targetType == long.class) return ((Number) val).longValue();
if (targetType == String.class) return val.toString();
if (targetType == Boolean.class || targetType == boolean.class) return ((Number) val).intValue() != 0;
if (targetType == java.util.Date.class && val instanceof java.sql.Date) return new java.util.Date(((java.sql.Date) val).getTime());
// ... 更多類型
return val;
}
/** 釋放資源 (按逆序關閉) */
public void close() {
try { if (rs != null) rs.close(); } catch (SQLException e) { e.printStackTrace(); }
try { if (pstmt != null) pstmt.close(); } catch (SQLException e) { e.printStackTrace(); }
try { if (conn != null) conn.close(); } catch (SQLException e) { e.printStackTrace(); }
}
// ========== 測試主方法 ==========
public static void main(String[] args) {
JdbcUtils db = new JdbcUtils();
try {
// 增
db.update("INSERT INTO userinfo(username, pswd) VALUES(?,?)", Arrays.asList("張三", "123456"));
// 查 Map
List<Map<String, Object>> list = db.queryForList("SELECT * FROM userinfo", null);
System.out.println("Map模式: " + list);
// 查 Bean
List<UserInfo> users = db.queryForList("SELECT * FROM userinfo", null, UserInfo.class);
System.out.println("Bean模式: " + users);
} catch (Exception e) {
e.printStackTrace();
} finally {
db.close();
}
}
}
package domain;
public class UserInfo {
private int id;
private String username;
private String pswd;
// 必須有無參構造器
public UserInfo() {}
// Getter/Setter (反射調用 setXxx)
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getPswd() { return pswd; }
public void setPswd(String pswd) { this.pswd = pswd; }
@Override public String toString() {
return "UserInfo{id=" + id + ", username='" + username + "', pswd='" + pswd + "'}";
}
}
CREATE TABLE `userinfo` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL, `pswd` varchar(100) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
clazz.getDeclaredConstructor().newInstance() 實例化 Bean (要求無參構造器)。rs.getMetaData().getColumnLabel(i) 獲取列名/別名。clazz.getDeclaredField(colName) 獲取字段 (含 private)。field.setAccessible(true) 破解訪問權限。field.set(obj, value) 賦值,**自動調用 Setter** (若存在) 或直接寫字段。Class.forName(DRIVER) (觸發靜態塊註冊 Driver)DriverManager.getConnection(URL, USER, PASS)conn.prepareStatement(sql) (預處理)pstmt.setObject(index, val)executeUpdate() / executeQuery()ResultSet、映射對象ResultSet → Statement → Connection (逆序,finally 塊保證)| 問題 | 解決方案 |
|---|---|
| 頻繁創建連接開銷大 | 引入 **連接池** (HikariCP / Druid / DBCP2),DataSource.getConnection() 替代 DriverManager |
| 工具類線程不安全 | **每請求一實例** 或 **ThreadLocal 綁定連接** (Spring JdbcTemplate 模式) |
| 異常處理粗糙 | 自定義 DaoException 包裝 SQLException,保留堆棧,上層統一處理 |
| 類型轉換不全 | 完善 convertValue 支持 BigDecimal, LocalDateTime, Enum, JSON 等 |
| SQL 注入風險 | **堅決禁止字符串拼接 SQL**,全量使用 ? 佔位符 + setObject |
| 事務管理缺失 | 添加 beginTransaction(), commit(), rollback() 方法,配合 ThreadLocal 管理事務邊界 |
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.0.33</version>
</dependency>
<!-- 連接池推薦 HikariCP -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>5.0.1</version>
</dependency>
JdbcUtils 展示了 JDBC 核心 API 與 Java 反射結合的經典模式。理解其原理有助於深入掌握 Spring JdbcTemplate、MyBatis、Hibernate 等 ORM 框架的底層實現。**生產項目請直接使用成熟框架**,勿重複造輪子。