当前位置: 首页 > news >正文

BaseDao 通用查询方法设计与实现

BaseDao 通用查询方法设计与实现

一、通用查询方法设计思路

1. 核心查询功能矩阵

查询类型方法名功能说明复杂度
主键查询findById()根据主键获取单个实体
全量查询findAll()获取全部实体
条件查询findByCondition()动态条件查询⭐⭐⭐
分页查询findPage()分页结果集⭐⭐⭐⭐
排序查询findWithOrder()带排序的结果集⭐⭐
投影查询findProjection()返回指定字段⭐⭐⭐
聚合查询executeAggregate()执行聚合函数⭐⭐⭐⭐

2. 类关系设计

«abstract»
BaseDao<T, ID>
# DataSource dataSource
# Class entityClass
# String tableName
# String primaryKey
+BaseDao(DataSource dataSource)
+T findById(ID id)
+List findAll()
+List findByCondition(String condition, Object... params)
+Page findPage(int pageNum, int pageSize)
+Page findPageByCondition(int pageNum, int pageSize, String condition, Object... params)
+List> findProjection(String[] columns, String condition, Object... params)
+R executeAggregate(String function, String column, String condition, Class resultType, Object... params)
#T mapRowToEntity(ResultSet rs)
#String buildSelectSql(String[] columns, String condition)
UserDao
+UserDao(DataSource dataSource)
+List findActiveUsers()
Page<T>
- int pageNum
- int pageSize
- int total
- List content
+getTotalPages()

二、完整实现代码

1. Page 分页对象

public class Page<T> {private int pageNum;     // 当前页码private int pageSize;    // 每页大小private int total;       // 总记录数private List<T> content; // 当前页数据public Page(int pageNum, int pageSize, int total, List<T> content) {this.pageNum = pageNum;this.pageSize = pageSize;this.total = total;this.content = content;}// 计算总页数public int getTotalPages() {return (int) Math.ceil((double) total / pageSize);}// 是否第一页public boolean isFirst() {return pageNum == 1;}// 是否最后一页public boolean isLast() {return pageNum >= getTotalPages();}// Getters and setters...
}

2. BaseDao 通用查询实现

import javax.sql.DataSource;
import java.lang.reflect.Field;
import java.sql.*;
import java.util.*;
import java.util.stream.Collectors;public abstract class BaseDao<T, ID> {protected final DataSource dataSource;protected final Class<T> entityClass;protected final String tableName;protected final String primaryKey;// 通过构造函数获取实体类型@SuppressWarnings("unchecked")public BaseDao(DataSource dataSource) {this.dataSource = dataSource;this.entityClass = (Class<T>) ((java.lang.reflect.ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];this.tableName = resolveTableName();this.primaryKey = resolvePrimaryKey();}// ========== 核心查询方法 ========== ///*** 根据主键查询单个实体*/public T findById(ID id) {String condition = primaryKey + " = ?";List<T> result = findByCondition(condition, id);return result.isEmpty() ? null : result.get(0);}/*** 查询所有实体*/public List<T> findAll() {return findByCondition(null);}/*** 条件查询* @param condition SQL条件部分 (不包含WHERE关键字)* @param params 条件参数*/public List<T> findByCondition(String condition, Object... params) {String sql = buildSelectSql(null, condition);try (Connection conn = dataSource.getConnection();PreparedStatement pstmt = conn.prepareStatement(sql)) {setParameters(pstmt, params);try (ResultSet rs = pstmt.executeQuery()) {List<T> result = new ArrayList<>();while (rs.next()) {result.add(mapRowToEntity(rs));}return result;}} catch (SQLException e) {throw new RuntimeException("Query by condition failed", e);}}/*** 分页查询所有记录*/public Page<T> findPage(int pageNum, int pageSize) {return findPageByCondition(pageNum, pageSize, null);}/*** 带条件的分页查询*/public Page<T> findPageByCondition(int pageNum, int pageSize, String condition, Object... params) {// 计算偏移量int offset = (pageNum - 1) * pageSize;// 构建分页SQLString sql = buildSelectSql(null, condition) + " LIMIT " + pageSize + " OFFSET " + offset;// 查询数据List<T> content;try (Connection conn = dataSource.getConnection();PreparedStatement pstmt = conn.prepareStatement(sql)) {setParameters(pstmt, params);try (ResultSet rs = pstmt.executeQuery()) {content = new ArrayList<>();while (rs.next()) {content.add(mapRowToEntity(rs));}}} catch (SQLException e) {throw new RuntimeException("Paged query failed", e);}// 查询总数int total = countByCondition(condition, params);return new Page<>(pageNum, pageSize, total, content);}/*** 投影查询 - 返回指定字段的Map集合* @param columns 要查询的列名* @param condition 查询条件* @param params 条件参数*/public List<Map<String, Object>> findProjection(String[] columns, String condition, Object... params) {String sql = buildSelectSql(columns, condition);try (Connection conn = dataSource.getConnection();PreparedStatement pstmt = conn.prepareStatement(sql)) {setParameters(pstmt, params);try (ResultSet rs = pstmt.executeQuery()) {List<Map<String, Object>> result = new ArrayList<>();ResultSetMetaData metaData = rs.getMetaData();int columnCount = metaData.getColumnCount();while (rs.next()) {Map<String, Object> row = new LinkedHashMap<>();for (int i = 1; i <= columnCount; i++) {String columnName = metaData.getColumnLabel(i);row.put(columnName, rs.getObject(i));}result.add(row);}return result;}} catch (SQLException e) {throw new RuntimeException("Projection query failed", e);}}/*** 执行聚合函数查询* @param function 聚合函数 (COUNT, SUM, AVG, MAX, MIN)* @param column 聚合列* @param condition 查询条件* @param resultType 返回结果类型* @param params 条件参数*/public <R> R executeAggregate(String function, String column, String condition, Class<R> resultType,Object... params) {String sql = "SELECT " + function + "(" + column + ") FROM " + tableName;if (condition != null && !condition.trim().isEmpty()) {sql += " WHERE " + condition;}try (Connection conn = dataSource.getConnection();PreparedStatement pstmt = conn.prepareStatement(sql)) {setParameters(pstmt, params);try (ResultSet rs = pstmt.executeQuery()) {if (rs.next()) {return resultType.cast(rs.getObject(1));}return null;}} catch (SQLException e) {throw new RuntimeException("Aggregate query failed", e);}}/*** 条件统计*/public int countByCondition(String condition, Object... params) {return executeAggregate("COUNT", primaryKey, condition, Integer.class, params);}// ========== 辅助方法 ========== ///*** 构建SELECT语句*/protected String buildSelectSql(String[] columns, String condition) {String selectedColumns = "*";if (columns != null && columns.length > 0) {selectedColumns = String.join(", ", columns);}StringBuilder sql = new StringBuilder("SELECT ").append(selectedColumns).append(" FROM ").append(tableName);if (condition != null && !condition.trim().isEmpty()) {sql.append(" WHERE ").append(condition);}return sql.toString();}/*** 设置PreparedStatement参数*/protected void setParameters(PreparedStatement pstmt, Object... params) throws SQLException {if (params != null) {for (int i = 0; i < params.length; i++) {pstmt.setObject(i + 1, params[i]);}}}/*** 结果集映射到实体对象*/protected T mapRowToEntity(ResultSet rs) throws SQLException {try {T entity = entityClass.getDeclaredConstructor().newInstance();ResultSetMetaData metaData = rs.getMetaData();int columnCount = metaData.getColumnCount();for (int i = 1; i <= columnCount; i++) {String columnName = metaData.getColumnLabel(i);Object value = rs.getObject(i);// 查找对应字段Field field = findFieldForColumn(columnName);if (field != null) {field.setAccessible(true);// 处理特殊类型转换if (value instanceof java.sql.Timestamp && field.getType() == java.time.LocalDateTime.class) {value = ((java.sql.Timestamp) value).toLocalDateTime();} else if (value instanceof java.sql.Date && field.getType() == java.time.LocalDate.class) {value = ((java.sql.Date) value).toLocalDate();}field.set(entity, value);}}return entity;} catch (Exception e) {throw new SQLException("Failed to map row to entity", e);}}/*** 根据列名查找实体字段*/protected Field findFieldForColumn(String columnName) {// 尝试直接匹配字段名try {return entityClass.getDeclaredField(columnName);} catch (NoSuchFieldException e1) {// 尝试匹配驼峰转下划线String camelCaseName = snakeToCamel(columnName);try {return entityClass.getDeclaredField(camelCaseName);} catch (NoSuchFieldException e2) {// 遍历所有字段查找for (Field field : entityClass.getDeclaredFields()) {if (field.getName().equalsIgnoreCase(columnName) || field.getName().equalsIgnoreCase(camelCaseName)) {return field;}}return null;}}}/*** 解析表名*/protected String resolveTableName() {// 如果有@Table注解,优先使用注解值if (entityClass.isAnnotationPresent(Table.class)) {return entityClass.getAnnotation(Table.class).name();}// 默认:类名驼峰转下划线return camelToSnake(entityClass.getSimpleName());}/*** 解析主键名*/protected String resolvePrimaryKey() {// 查找带@Id注解的字段for (Field field : entityClass.getDeclaredFields()) {if (field.isAnnotationPresent(Id.class)) {return field.getName();}}// 默认使用"id"return "id";}/*** 驼峰转下划线*/protected String camelToSnake(String str) {return str.replaceAll("([a-z])([A-Z])", "$1_$2").toLowerCase();}/*** 下划线转驼峰*/protected String snakeToCamel(String str) {StringBuilder result = new StringBuilder();boolean nextUpper = false;for (int i = 0; i < str.length(); i++) {char c = str.charAt(i);if (c == '_') {nextUpper = true;} else {if (nextUpper) {result.append(Character.toUpperCase(c));nextUpper = false;} else {result.append(Character.toLowerCase(c));}}}return result.toString();}
}

3. 注解定义

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Table {String name();
}@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTentionPolicy.RUNTIME)
public @interface Id {}

4. 实体类示例

@Table(name = "user_info")
public class User {@Idprivate Long id;private String userName;private String email;private LocalDateTime createTime;private Integer status;// 构造器、getter、setter...
}

三、使用示例

1. 基础查询

// 初始化数据源
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/mydb");
dataSource.setUsername("user");
dataSource.setPassword("password");// 创建UserDao
UserDao userDao = new UserDao(dataSource);// 根据ID查询
User user = userDao.findById(1L);
System.out.println("User: " + user.getUserName());// 查询所有用户
List<User> allUsers = userDao.findAll();
System.out.println("Total users: " + allUsers.size());// 条件查询
List<User> activeUsers = userDao.findByCondition("status = ? AND create_time > ?", 1, LocalDateTime.now().minusMonths(1)
);
System.out.println("Active users: " + activeUsers.size());

2. 分页查询

// 分页查询(第2页,每页10条)
Page<User> userPage = userDao.findPage(2, 10);
System.out.println("Page " + userPage.getPageNum() + " of " + userPage.getTotalPages());
System.out.println("Records: " + userPage.getContent().size());// 带条件的分页查询
Page<User> activeUserPage = userDao.findPageByCondition(1, 20, "status = ?", 1
);
System.out.println("Active users: " + activeUserPage.getTotal());

3. 投影查询

// 投影查询(只获取用户名和邮箱)
List<Map<String, Object>> userProjections = userDao.findProjection(new String[]{"user_name", "email"}, "status = ?", 1
);userProjections.forEach(projection -> {System.out.println(projection.get("user_name") + ": " + projection.get("email"));
});

4. 聚合查询

// 统计活跃用户数量
Integer activeCount = userDao.executeAggregate("COUNT", "id", "status = ?", Integer.class, 1
);
System.out.println("Active users: " + activeCount);// 获取最新注册时间
LocalDateTime lastRegistration = userDao.executeAggregate("MAX", "create_time", null, LocalDateTime.class
);
System.out.println("Last registration: " + lastRegistration);

四、高级查询功能扩展

1. 动态排序支持

/*** 带排序的条件查询*/
public List<T> findByConditionWithOrder(String condition, String orderBy, Object... params) {String sql = buildSelectSql(null, condition);if (orderBy != null && !orderBy.trim().isEmpty()) {sql += " ORDER BY " + orderBy;}return executeQuery(sql, params);
}private List<T> executeQuery(String sql, Object... params) {try (Connection conn = dataSource.getConnection();PreparedStatement pstmt = conn.prepareStatement(sql)) {setParameters(pstmt, params);try (ResultSet rs = pstmt.executeQuery()) {List<T> result = new ArrayList<>();while (rs.next()) {result.add(mapRowToEntity(rs));}return result;}} catch (SQLException e) {throw new RuntimeException("Query failed", e);}
}

2. 联表查询支持

/*** 自定义SQL查询*/
public List<T> findBySql(String sql, Object... params) {return executeQuery(sql, params);
}/*** 联表查询映射*/
protected T mapRowToEntityWithJoins(ResultSet rs) throws SQLException {// 需要子类覆盖此方法实现复杂映射return mapRowToEntity(rs);
}

3. 缓存集成

public class CachedBaseDao<T, ID> extends BaseDao<T, ID> {private final Cache<ID, T> cache;public CachedBaseDao(DataSource dataSource, Cache<ID, T> cache) {super(dataSource);this.cache = cache;}@Overridepublic T findById(ID id) {// 先从缓存获取T cached = cache.get(id);if (cached != null) {return cached;}// 数据库查询T entity = super.findById(id);if (entity != null) {cache.put(id, entity);}return entity;}@Overridepublic void update(T entity) {super.update(entity);// 更新缓存ID id = (ID) ReflectionUtils.getFieldValue(entity, primaryKey);cache.put(id, entity);}
}

五、性能优化策略

1. 查询性能优化矩阵

场景优化策略实现方式
频繁查询缓存结果集成Caffeine/Redis
大结果集流式处理使用ResultSet流式读取
复杂查询预编译SQL缓存PreparedStatement
字段映射反射缓存缓存Field元数据
批量查询IN查询优化使用JOIN代替多个OR

2. 流式查询实现

/*** 流式查询(处理大结果集)*/
public void streamByCondition(String condition, Consumer<T> consumer, Object... params) {String sql = buildSelectSql(null, condition);try (Connection conn = dataSource.getConnection();PreparedStatement pstmt = conn.prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY)) {pstmt.setFetchSize(Integer.MIN_VALUE); // MySQL流式读取setParameters(pstmt, params);try (ResultSet rs = pstmt.executeQuery()) {while (rs.next()) {consumer.accept(mapRowToEntity(rs));}}} catch (SQLException e) {throw new RuntimeException("Stream query failed", e);}
}

3. 预编译语句缓存

// 在BaseDao中添加缓存
private final Map<String, PreparedStatement> statementCache = new ConcurrentHashMap<>();protected PreparedStatement prepareStatement(Connection conn, String sql) throws SQLException {return statementCache.computeIfAbsent(sql, key -> {try {return conn.prepareStatement(key);} catch (SQLException e) {throw new RuntimeException("Failed to prepare statement", e);}});
}

六、安全注意事项

1. 查询安全防护

风险防护措施实现方式
SQL注入参数化查询使用PreparedStatement
敏感数据字段过滤投影查询指定字段
批量查询结果集限制添加MAX_ROWS限制
日志泄露脱敏处理不记录完整结果集
权限控制行级权限基础条件自动附加

2. 自动条件附加

/*** 自动附加安全条件*/
protected String applySecurityConditions(String originalCondition) {// 示例:只允许查询当前用户的数据String userId = SecurityContext.getCurrentUserId();if (userId == null) {throw new SecurityException("Unauthorized access");}String securityCondition = "user_id = '" + userId + "'";if (originalCondition == null || originalCondition.isEmpty()) {return securityCondition;}return "(" + originalCondition + ") AND " + securityCondition;
}

七、总结与最佳实践

1. BaseDao查询方法使用场景

查询类型适用场景性能建议
findById单条记录获取添加缓存
findAll小型表全量查询避免大表使用
findByCondition动态条件查询确保条件字段索引
findPage列表展示优化分页SQL
findProjection报表生成只查询必要字段
executeAggregate统计分析数据库聚合优于内存计算

2. 设计原则检查表

- [ ] 接口与实现分离
- [ ] 支持动态条件查询
- [ ] 分页查询独立封装
- [ ] 安全参数绑定
- [ ] 类型安全结果映射
- [ ] 支持投影查询
- [ ] 提供聚合函数支持
- [ ] 异常统一处理
- [ ] 扩展点开放(如自定义映射)

3. 性能优化检查表

- [ ] 大结果集使用流式处理
- [ ] 频繁查询添加缓存
- [ ] 预编译语句重用
- [ ] 反射元数据缓存
- [ ] 避免N+1查询问题
- [ ] 分页查询优化(Keyset分页)
主键查询
条件查询
分页查询
聚合查询
命中
未命中
查询请求
查询类型
findById
findByCondition
findPage
executeAggregate
缓存检查
返回缓存
数据库查询
构建SQL
执行查询
查询数据
查询总数
构建分页对象
执行聚合函数
结果映射
返回结果

最佳实践总结:BaseDao的通用查询方法为数据访问层提供了强大的基础能力,但在实际项目中需根据具体需求进行扩展和优化。对于复杂查询场景,建议结合使用MyBatis等专业ORM框架,同时注意查询性能和安全防护。

http://www.dtcms.com/a/276334.html

相关文章:

  • 快速过一遍Python基础语法
  • 015---全面理解交易:区块链状态转移的原子单位与链上执行全流程图解
  • 【AI News | 20250711】每日AI进展
  • APP Inventor使用指南
  • LeetCode 3169.无需开会的工作日:排序+一次遍历——不需要正难则反,因为正着根本不难
  • 【使用Pyqt designer时pyside2安装失败】
  • 如何彻底禁用 Chrome 自动更新
  • C++实现二叉树左右子树交换算法
  • vuecil3+版本下,兼容ie等不支持es6的低版本浏览器
  • 内容总监的效率革命:用Premiere Pro AI,实现视频画幅“一键重构”
  • 四、深度学习——CNN
  • 快速上手UniApp(适用于有Vue3基础的)
  • 服务器ssh连接防护指南
  • 软件测试基础1-软件测试需求分析
  • Python技巧记录
  • 详细理解向量叉积
  • CVPR2025 Mamba系列
  • 内容总结I
  • 我的LeetCode刷题笔记——树(2)
  • 带货视频评论洞察 Baseline 学习笔记 (Datawhale Al夏令营)
  • [动态规划]1900. 最佳运动员的比拼回合
  • Matplotlib 模块入门
  • 非欧几里得空间图卷积算子设计:突破几何限制的图神经网络新范式
  • Linux系统中部署Redis详解
  • python作业2
  • 【时间之外】AI在农机配件设计场景的应用
  • 【详解ProTable源码】高级筛选栏如何实现一行五列
  • Elasticsearch 的 `modules` 目录
  • AMD 锐龙 AI MAX+ 395 处理器与端侧 AI 部署的行业实践
  • 【华为OD】MVP争夺战2(C++、Java、Python)