首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

JDBC BaseDao 封裝實作:泛型 CRUD、反射映射、預編譯 SQL 動態生成完整程式碼

     編輯:Java 相關知識

BaseDao 設計目標

  • 泛型支援任意實體類別
  • 反射自動映射欄位/屬性
  • 動態生成預編譯 SQL 防注入
  • 統一資源管理(Connection/Statement/ResultSet)

核心工具類:JdbcUtil

public class JdbcUtil {
    private static final String URL = "jdbc:mysql://localhost:3306/db?useSSL=false";
    private static final String USER = "root";
    private static final String PASS = "123456";
    private static final String DRIVER = "com.mysql.cj.jdbc.Driver";
    
    static { try { Class.forName(DRIVER); } catch (Exception e) { throw new RuntimeException(e); } }
    
    public static Connection getConnection() throws SQLException {
        return DriverManager.getConnection(URL, USER, PASS);
    }
    
    public static void close(ResultSet rs, Statement st, Connection conn) {
        closeQuietly(rs);
        closeQuietly(st);
        closeQuietly(conn);
    }
    
    private static void closeQuietly(AutoCloseable ac) {
        if (ac != null) try { ac.close(); } catch (Exception ignore) {}
    }
}

BaseDao 完整實作

public class BaseDao<T> {
    private final JdbcUtil jdbcUtil = new JdbcUtil();
    private final Class<T> entityClass;
    
    @SuppressWarnings("unchecked")
    public BaseDao() {
        // 取得泛型實際類型:BaseDao → User.class
        ParameterizedType type = (ParameterizedType) getClass().getGenericSuperclass();
        this.entityClass = (Class<T>) type.getActualTypeArguments()[0];
    }
    
    /** 通用:取得所有 getter 方法 */
    private List<Method> getGetters(T entity) {
        List<Method> list = new ArrayList<>();
        for (Method m : entity.getClass().getDeclaredMethods()) {
            if (m.getName().startsWith("get") && m.getParameterCount() == 0) {
                list.add(m);
            }
        }
        return list;
    }
    
    /** 通用:取得 setter 方法(用於查找 ID) */
    private List<Method> getSetters(T entity) {
        List<Method> list = new ArrayList<>();
        for (Method m : entity.getClass().getDeclaredMethods()) {
            if (m.getName().startsWith("set") && m.getParameterCount() == 1) {
                list.add(m);
            }
        }
        return list;
    }
    
    /** 保存:INSERT INTO table (col1,col2) VALUES (?,?) */
    public int save(T entity) throws Exception {
        Connection conn = jdbcUtil.getConnection();
        List<Method> getters = getGetters(entity);
        Object[] params = new Object[getters.size()];
        
        StringBuilder sql = new StringBuilder("INSERT INTO ")
            .append(entityClass.getSimpleName().toLowerCase()).append(" (");
        StringBuilder placeholders = new StringBuilder();
        
        for (int i = 0; i < getters.size(); i++) {
            Method m = getters.get(i);
            String col = m.getName().substring(3).toLowerCase(); // getName → name
            sql.append(col).append(",");
            placeholders.append("?,");
            
            Object val = m.invoke(entity);
            // 日期類型格式化
            if (val instanceof java.util.Date) {
                params[i] = new java.sql.Timestamp(((java.util.Date)val).getTime());
            } else {
                params[i] = val;
            }
        }
        
        sql.setCharAt(sql.length()-1, ')'); // 替換最後逗號
        sql.append(" VALUES (").append(placeholders).setCharAt(placeholders.length()-1, ')');
        
        return executeUpdate(conn, sql.toString(), params);
    }
    
    /** 刪除:DELETE FROM table WHERE id = ? */
    public int deleteById(Object id) throws Exception {
        Connection conn = jdbcUtil.getConnection();
        T entity = entityClass.newInstance();
        Method idSetter = findIdSetter(entity);
        String idCol = idSetter.getName().substring(3).toLowerCase(); // setId → id
        
        String sql = "DELETE FROM " + entityClass.getSimpleName().toLowerCase() 
            + " WHERE " + idCol + " = ?";
        
        return executeUpdate(conn, sql, id);
    }
    
    /** 更新:UPDATE table SET col1=?,col2=? WHERE id=? */
    public int update(T entity) throws Exception {
        Connection conn = jdbcUtil.getConnection();
        List<Method> getters = getGetters(entity);
        List<Object> params = new ArrayList<>();
        
        StringBuilder sql = new StringBuilder("UPDATE ")
            .append(entityClass.getSimpleName().toLowerCase()).append(" SET ");
        
        Method idGetter = null;
        Object idVal = null;
        
        for (Method m : getters) {
            String col = m.getName().substring(3).toLowerCase();
            Object val = m.invoke(entity);
            
            // 識別 ID 欄位(慣例:getId 或 getXxxId)
            if (col.equals("id") || col.equals(entityClass.getSimpleName().toLowerCase() + "id")) {
                idGetter = m;
                idVal = val;
                continue; // ID 不參與 SET,放到 WHERE
            }
            
            sql.append(col).append("=?,");
            if (val instanceof java.util.Date) {
                params.add(new java.sql.Timestamp(((java.util.Date)val).getTime()));
            } else {
                params.add(val);
            }
        }
        
        sql.setCharAt(sql.length()-1, ' '); // 移除最後逗號
        sql.append("WHERE ").append(idGetter.getName().substring(3).toLowerCase()).append(" = ?");
        params.add(idVal);
        
        return executeUpdate(conn, sql.toString(), params.toArray());
    }
    
    /** 查詢單筆:SELECT * FROM table WHERE id = ? */
    public T findById(Object id) throws Exception {
        Connection conn = jdbcUtil.getConnection();
        T entity = entityClass.newInstance();
        Method idSetter = findIdSetter(entity);
        String idCol = idSetter.getName().substring(3).toLowerCase();
        
        String sql = "SELECT * FROM " + entityClass.getSimpleName().toLowerCase() 
            + " WHERE " + idCol + " = ?";
        
        try (PreparedStatement ps = conn.prepareStatement(sql)) {
            setParameter(ps, 1, id);
            try (ResultSet rs = ps.executeQuery()) {
                if (rs.next()) {
                    return mapRow(rs, entity);
                }
            }
        }
        return null;
    }
    
    /** ResultSet → 實體映射 */
    private T mapRow(ResultSet rs, T entity) throws Exception {
        List<Method> setters = getSetters(entity);
        for (Method m : setters) {
            String col = m.getName().substring(3).toLowerCase();
            Class<?> paramType = m.getParameterTypes()[0];
            
            try {
                Object val = null;
                if (paramType == String.class) val = rs.getString(col);
                else if (paramType == Integer.class || paramType == int.class) val = rs.getInt(col);
                else if (paramType == Long.class || paramType == long.class) val = rs.getLong(col);
                else if (paramType == Double.class || paramType == double.class) val = rs.getDouble(col);
                else if (paramType == java.util.Date.class) val = rs.getTimestamp(col);
                else if (paramType == Boolean.class || paramType == boolean.class) val = rs.getBoolean(col);
                
                if (val != null) m.invoke(entity, val);
            } catch (SQLException e) {
                // 欄位不存在於結果集,忽略
            }
        }
        return entity;
    }
    
    /** 找出 ID setter */
    private Method findIdSetter(T entity) {
        for (Method m : getSetters(entity)) {
            String name = m.getName().substring(3);
            if (name.equalsIgnoreCase("id") || name.equalsIgnoreCase(entityClass.getSimpleName() + "Id")) {
                return m;
            }
        }
        throw new IllegalStateException("找不到 ID setter");
    }
    
    /** 通用更新執行 */
    private int executeUpdate(Connection conn, String sql, Object... params) throws Exception {
        try (PreparedStatement ps = conn.prepareStatement(sql)) {
            for (int i = 0; i < params.length; i++) {
                setParameter(ps, i+1, params[i]);
            }
            return ps.executeUpdate();
        }
    }
    
    /** 參數綁定(支援常見類型) */
    private void setParameter(PreparedStatement ps, int idx, Object val) throws SQLException {
        if (val == null) { ps.setNull(idx, Types.NULL); return; }
        if (val instanceof String) ps.setString(idx, (String)val);
        else if (val instanceof Integer) ps.setInt(idx, (Integer)val);
        else if (val instanceof Long) ps.setLong(idx, (Long)val);
        else if (val instanceof Double) ps.setDouble(idx, (Double)val);
        else if (val instanceof java.util.Date) ps.setTimestamp(idx, new java.sql.Timestamp(((java.util.Date)val).getTime()));
        else if (val instanceof Boolean) ps.setBoolean(idx, (Boolean)val);
        else ps.setObject(idx, val);
    }
}

實體類別規範(POJO 慣例)

public class User {
    private Long id;
    private String name;
    private String email;
    private java.util.Date createTime;
    
    // 必須提供無參建構子
    public User() {}
    
    // Getter/Setter 命名慣例
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    // ...
}

使用範例

public class UserDao extends BaseDao {
    // 繼承即擁有 save/deleteById/update/findById
}

public class Demo {
    public static void main(String[] args) throws Exception {
        UserDao dao = new UserDao();
        
        // 新增
        User u = new User();
        u.setName("張三");
        u.setEmail("zhang@example.com");
        u.setCreateTime(new Date());
        dao.save(u);
        
        // 查詢
        User found = dao.findById(1L);
        System.out.println(found.getName());
        
        // 修改
        found.setEmail("new@example.com");
        dao.update(found);
        
        // 刪除
        dao.deleteById(1L);
    }
}

擴展建議

功能實作方向
條件查詢新增 findByExample(T example),反射解析非空屬性組裝 WHERE
分頁查詢整合 PageHelper 或手寫 LIMIT/OFFSET
批次操作batchSave(List<T>) 使用 addBatch()
事務管理ThreadLocal 綁定 Connection,提供 begin/commit/rollback
快取支援整合 Ehcache/Caffeine,findById 先查快取
SQL 審計AOP 記錄執行 SQL、耗時、參數

注意事項與限制

  • 表名/欄位名慣例:類名→表名,屬性名→欄位名(駝峰轉下劃線需自行處理)
  • 複雜關聯:不支援 Join、嵌套物件,需手寫 SQL 或升級 MyBatis
  • 效能:反射有開銷,高頻調用建議快取 Method 對象
  • 資料庫移植:SQL 方言差異(分頁、自增 ID)需抽象適配
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved