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

Java 泛型與反射實現通用 BaseDAO:消除重複 CRUD 代碼

     編輯:Java 綜合教學

問題背景

多表查詢時,findById、findAll 等邏輯高度相似,僅實體類型不同。利用泛型 + 反射可將通用邏輯提取到父類。

領域模型

// User 實體
public class User {
    private Integer id;
    private String username;
    private String password;
    private String hobby;
    // getter/setter 省略
}

// Student 實體
public class Student {
    private Integer id;
    private String name;
    private Integer age;
    // getter/setter 省略
}

DAO 接口體系

// 基礎接口
public interface BaseDao {
    T findById(Integer id);
    List findAll();
    int insert(T entity);
    int update(T entity);
    int deleteById(Integer id);
}

// 具體接口繼承並固定泛型
public interface UserDao extends BaseDao {}
public interface StudentDao extends BaseDao {}

通用實現:BaseDaoImpl

public abstract class BaseDaoImpl implements BaseDao {

    private final String tableName;
    private final Class entityClass;
    private final QueryRunner qr = new QueryRunner(DataSourceUtils.getDataSource());

    @SuppressWarnings("unchecked")
    protected BaseDaoImpl() {
        // 1. 獲取子類泛型實際類型:UserDaoImpl extends BaseDaoImpl
        Type superclass = getClass().getGenericSuperclass();
        ParameterizedType pt = (ParameterizedType) superclass;
        this.entityClass = (Class) pt.getActualTypeArguments()[0];
        this.tableName = entityClass.getSimpleName().toLowerCase(); // 假設表名=類名小寫
    }

    @Override
    public T findById(Integer id) {
        String sql = "SELECT * FROM " + tableName + " WHERE id = ?";
        try {
            return qr.query(sql, new BeanHandler<>(entityClass), id);
        } catch (SQLException e) {
            throw new DaoException(e);
        }
    }

    @Override
    public List findAll() {
        String sql = "SELECT * FROM " + tableName;
        try {
            return qr.query(sql, new BeanListHandler<>(entityClass));
        } catch (SQLException e) {
            throw new DaoException(e);
        }
    }

    @Override
    public int insert(T entity) {
        // 利用反射獲取非空屬性構建 INSERT
        Map props = BeanUtils.describe(entity);
        props.remove("class");
        props.values().removeIf(Objects::isNull);
        
        String columns = String.join(", ", props.keySet());
        String placeholders = props.keySet().stream().map(k -> "?").collect(Collectors.joining(", "));
        String sql = "INSERT INTO " + tableName + " (" + columns + ") VALUES (" + placeholders + ")";
        
        try {
            return qr.update(sql, props.values().toArray());
        } catch (SQLException e) {
            throw new DaoException(e);
        }
    }
    // update/delete 同理...
}

具體實現類(零代碼)

public class UserDaoImpl extends BaseDaoImpl implements UserDao {}
public class StudentDaoImpl extends BaseDaoImpl implements StudentDao {}

測試驗證

@Test
public void testGenericDao() {
    UserDao userDao = new UserDaoImpl();
    User u = userDao.findById(1);
    System.out.println("User: " + u);

    StudentDao studentDao = new StudentDaoImpl();
    Student s = studentDao.findById(1);
    System.out.println("Student: " + s);
}

核心技術點解析

  1. 獲取實際泛型類型:getClass().getGenericSuperclass() → ParameterizedType → getActualTypeArguments()[0]
  2. BeanHandler/BeanListHandler:DbUtils 自動將 ResultSet 映射為實體,依賴無參構造器 + setter
  3. 表名約定:類名小寫,或用註解 @Table("t_user") 顯式指定
  4. 異常轉換:捕獲 SQLException 拋出自定義 DaoException,上層無需處理 SQL 異常

進階擴展方向

  • 分頁查詢:PageHelper 或手寫 LIMIT/OFFSET
  • 動態條件構建:Example 模式或 QueryDSL
  • 事務管理:Spring @Transactional 或手動 Connection.setAutoCommit(false)
  • 緩存整合:Redis 注解攔截 findById

此模式是 MyBatis、Hibernate、JdbcTemplate 等 ORM 框架的核心設計思想縮影。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved