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

通用 JDBC 工具類封裝:從基礎連線到反射自動 CRUD

     編輯:Java 相關知識

設計目標

  • 簡化 JDBC 樣板碼(連線、PreparedStatement、ResultSet 關閉)
  • 支援屬性檔配置資料庫連線資訊
  • 利用反射實現實體類與資料表的自動映射(基本 CRUD)
  • 作為學習 ORM 原理的教學範例,非生產級替代品

1. 設定檔與 Config 類

# dbconfig.properties
CLASS_NAME=com.mysql.cj.jdbc.Driver
DATABASE_URL=jdbc:mysql
SERVER_IP=localhost
SERVER_PORT=3306
DATABASE_SID=employees
USERNAME=root
PASSWORD=123456
public class Config {
    private static final Properties PROP = new Properties();
    static {
        try (InputStream is = Config.class.getResourceAsStream("/dbconfig.properties")) {
            PROP.load(is);
        } catch (IOException e) {
            throw new ExceptionInInitializerError("載入 dbconfig.properties 失敗", e);
        }
    }
    public static final String CLASS_NAME = PROP.getProperty("CLASS_NAME");
    public static final String URL = PROP.getProperty("DATABASE_URL") + "://" +
            PROP.getProperty("SERVER_IP") + ":" + PROP.getProperty("SERVER_PORT") + "/" +
            PROP.getProperty("DATABASE_SID");
    public static final String USERNAME = PROP.getProperty("USERNAME");
    public static final String PASSWORD = PROP.getProperty("PASSWORD");
}

2. 底層連線工具 DBConn

public class DBConn {
    private Connection conn;
    private PreparedStatement pstmt;
    private ResultSet rs;

    public Connection getConnection() throws SQLException {
        if (conn == null || conn.isClosed()) {
            Class.forName(Config.CLASS_NAME);
            conn = DriverManager.getConnection(Config.URL, Config.USERNAME, Config.PASSWORD);
        }
        return conn;
    }

    public void closeAll() {
        closeQuietly(rs);
        closeQuietly(pstmt);
        closeQuietly(conn);
        rs = null; pstmt = null; conn = null;
    }

    public int executeUpdate(PreparedStatement ps) {
        try { return ps.executeUpdate(); }
        catch (SQLException e) { throw new RuntimeException(e); }
    }

    public ResultSet executeQuery(PreparedStatement ps) {
        try { return ps.executeQuery(); }
        catch (SQLException e) { throw new RuntimeException(e); }
    }

    private void closeQuietly(AutoCloseable c) {
        if (c != null) try { c.close(); } catch (Exception ignored) {}
    }
}

3. 通用 BaseDAO(反射自動映射)

public abstract class BaseDAO<T> {
    protected final DBConn db = new DBConn();
    protected final Class<T> entityClass;

    @SuppressWarnings("unchecked")
    protected BaseDAO() {
        this.entityClass = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass())
                .getActualTypeArguments()[0];
    }

    // 取得所有 getter 方法(用於 INSERT/UPDATE)
    private List<Method> getGetters(T entity) {
        return Arrays.stream(entity.getClass().getMethods())
                .filter(m -> m.getName().startsWith("get") && m.getParameterCount() == 0)
                .collect(Collectors.toList());
    }

    // 取得所有 setter 方法(用於查詢映射)
    private List<Method> getSetters(T entity) {
        return Arrays.stream(entity.getClass().getMethods())
                .filter(m -> m.getName().startsWith("set") && m.getParameterCount() == 1)
                .collect(Collectors.toList());
    }

    // 找出主鍵欄位(慣例:getId 或 get+類名+Id)
    private Method findIdGetter(T entity) {
        return getGetters(entity).stream()
                .filter(m -> "getId".equals(m.getName()) ||
                        ("get" + entityClass.getSimpleName() + "Id").equalsIgnoreCase(m.getName()))
                .findFirst().orElseThrow(() -> new IllegalArgumentException("找不到主鍵 getter"));
    }

    public void save(T entity) {
        List<Method> getters = getGetters(entity);
        // 排除主鍵(假設自增)
        Method idGetter = findIdGetter(entity);
        getters.remove(idGetter);

        String sql = "INSERT INTO " + tableName() + " (" +
                getters.stream().map(m -> columnName(m)).collect(Collectors.joining(",")) +
                ") VALUES (" + getters.stream().map(m -> "?").collect(Collectors.joining(",")) + ")";

        try (Connection conn = db.getConnection();
             PreparedStatement ps = conn.prepareStatement(sql)) {
            int i = 1;
            for (Method m : getters) {
                setParameter(ps, i++, m.invoke(entity));
            }
            db.executeUpdate(ps);
        } catch (Exception e) { throw new RuntimeException(e); }
    }

    public void update(T entity) {
        List<Method> getters = getGetters(entity);
        Method idGetter = findIdGetter(entity);
        getters.remove(idGetter);

        String sql = "UPDATE " + tableName() + " SET " +
                getters.stream().map(m -> columnName(m) + "=?").collect(Collectors.joining(",")) +
                " WHERE " + columnName(idGetter) + "=?";

        try (Connection conn = db.getConnection();
             PreparedStatement ps = conn.prepareStatement(sql)) {
            int i = 1;
            for (Method m : getters) setParameter(ps, i++, m.invoke(entity));
            setParameter(ps, i, idGetter.invoke(entity));
            db.executeUpdate(ps);
        } catch (Exception e) { throw new RuntimeException(e); }
    }

    public void delete(T entity) {
        Method idGetter = findIdGetter(entity);
        String sql = "DELETE FROM " + tableName() + " WHERE " + columnName(idGetter) + "=?";
        try (Connection conn = db.getConnection();
             PreparedStatement ps = conn.prepareStatement(sql)) {
            setParameter(ps, 1, idGetter.invoke(entity));
            db.executeUpdate(ps);
        } catch (Exception e) { throw new RuntimeException(e); }
    }

    public T findById(Object id) {
        Method idGetter = findIdGetter(entityClass);
        String sql = "SELECT * FROM " + tableName() + " WHERE " + columnName(idGetter) + "=?";
        try (Connection conn = db.getConnection();
             PreparedStatement ps = conn.prepareStatement(sql)) {
            setParameter(ps, 1, id);
            try (ResultSet rs = db.executeQuery(ps)) {
                if (rs.next()) return mapRow(rs);
            }
        } catch (Exception e) { throw new RuntimeException(e); }
        return null;
    }

    protected T mapRow(ResultSet rs) throws Exception {
        T entity = entityClass.getDeclaredConstructor().newInstance();
        List<Method> setters = getSetters(entity);
        for (Method setter : setters) {
            String col = columnName(setter);
            Class<?> paramType = setter.getParameterTypes()[0];
            Object val = getResult(rs, col, paramType);
            if (val != null) setter.invoke(entity, val);
        }
        return entity;
    }

    // 輔助方法:駝峰轉下劃線、參數綁定、結果取值等略...
}

4. 具體 DAO 繼承

public class EmployeeDAO extends BaseDAO<Employee> {
    // 直接繼承即可使用 save/update/delete/findById
    // 可額外加入 findAll、findByName 等自定義查詢
}

5. 單元測試範例

public class EmployeeDAOTest {
    private EmployeeDAO dao = new EmployeeDAO();

    @Test
    public void testCRUD() {
        Employee e = new Employee();
        e.setName("張三"); e.setDept("研發"); e.setSalary(80000);
        dao.save(e);
        assertNotNull(e.getId());

        e.setSalary(85000);
        dao.update(e);

        Employee found = dao.findById(e.getId());
        assertEquals(85000, found.getSalary());

        dao.delete(e);
        assertNull(dao.findById(e.getId()));
    }
}

局限性與改進方向

  • 無連線池、無事務管理、無批次操作、無樂觀鎖、無關聯映射。
  • 生產環境請直接使用成熟框架:MyBatis、JdbcTemplate、Hibernate、JPA。
  • 本範例適合理解「反射 + JDBC + 註解/慣例」如何構建簡易 ORM 原型。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved