在前几篇文章中,我们已经完成了 Javelin 框架的核心功能构建,包括 HTTP 路由注册、注解扫描与 IoC 容器等内容。本篇将深入探讨 数据访问层(DAL,Data Access Layer) 的设计与实现,从理念到代码,全面展示如何在 Javelin 中构建一个灵活、可维护、可扩展的数据访问模块。
痛点驱动设计
在构建 Javelin 的数据访问层过程中,我们并不是一开始就拥有完整的设计蓝图,而是源于开发过程中反复遇到的一些实际痛点。下面列举几个典型问题,并结合示例代码说明 Javelin 是如何逐一解决这些问题的:
1. JDBC 样板代码冗余、可读性差
原始写法:
Connection conn = DriverManager.getConnection(url, user, pwd);
PreparedStatement ps = conn.prepareStatement("SELECT * FROM users WHERE id = ?");
ps.setInt(1, id);
ResultSet rs = ps.executeQuery();
if (rs.next()) {
User user = new User();
user.setId(rs.getInt("id"));
user.setName(rs.getString("name"));
return user;
}
问题:SQL 与 Java 混杂、资源管理复杂、字段映射重复。
Javelin 写法:
User user = CPQueryFactory.create()
.sql("SELECT * FROM users WHERE id = ?")
.params(id)
.queryOne(User.class);
通过 CPQuery
和 BeanPropertyRowMapper
简化了所有模板逻辑。
2. 手动管理连接生命周期,易出错
原始 JDBC 写法中,开发者需要自行处理连接关闭,若中间有异常,极易遗漏关闭:
try {
Connection conn = ...
// do something
} catch (Exception e) {
// handle
} finally {
conn.close(); // 容易忘记
}
Javelin 使用 DbContext
实现 AutoCloseable
,并结合 try-with-resources 自动释放资源:
try (DbContext db = DbConnManager.createAppDb("test")) {
// safe use
}
3. 事务控制分散,耦合混乱
在 JDBC 中常见写法:
conn.setAutoCommit(false);
try {
// 执行多条语句
conn.commit();
} catch(Exception e) {
conn.rollback();
}
Javelin 中的 DbContext
可以结合 ThreadLocal
保证一个线程内的事务连接复用,并支持后续统一事务管理器拓展。
4. 单元测试困难
传统 JDBC 紧耦合真实连接,测试时需要真实数据库支持。
Javelin 的抽象如 DbContext
与 CPQuery
均可通过接口注入和 Mock 替换,适合单元测试或内存数据库测试。
正是以上这些痛点——重复性高、维护成本大、可测试性差、事务管理易错——推动我们一步步抽象出 CPQuery、DbContext、EntityFactory 等组件,形成 Javelin 当前结构清晰、职责明确的数据访问模型。
整体设计思路
Javelin 的数据访问层设计秉承“职责清晰、分层解耦、灵活适配”的核心思想,整个数据访问层围绕以下几个核心目标展开:
-
统一数据库连接管理:抽象数据库类型与连接池配置。
-
简化 SQL 执行逻辑:封装 JDBC 的模板化操作。
-
对象关系映射(ORM)初探:通过 BeanPropertyRowMapper 映射结果集。
-
提供声明式查询构造器:提升 SQL 的可读性与复用性。
数据访问层主要由以下几个模块组成:
data/
├── DbConnManager.java // 数据库连接池统一管理
├── DbContext.java // 提供数据库上下文环境(连接获取、关闭)
├── command/
│ ├── CPQuery.java // 核心查询对象,封装 SQL 执行模板
│ ├── CPQueryFactory.java // 工厂类,创建 CPQuery 实例
│ └── BeanPropertyRowMapper.java // 结果集映射工具
├── config/
│ └── DbConfigProvider.java // 数据库配置信息提供器
└── DatabaseType.java // 支持的数据库类型枚举
核心类解析
DbConnManager:连接池统一管理
DbConnManager
是一个典型的连接管理器类,负责初始化并维护数据库连接池(如 Druid、HikariCP)。它的职责包括:
- 从配置加载数据库信息;
- 创建与缓存连接池实例;
- 提供
DataSource
给上层组件调用。
设计亮点:
- 将连接池隔离成可插拔模块,支持多数据库;
- 懒加载初始化,优化资源占用;
- 支持连接池复用,避免重复创建。
DbContext:数据库上下文载体
DbContext
承担着线程安全的数据库连接提供工作。它采用 ThreadLocal
管理当前线程的数据库连接,确保每个线程使用独立连接,避免并发问题。
设计亮点:
- 使用
ThreadLocal<Connection>
实现事务管理; - 提供
getConnection()
、close()
等静态方法统一访问; - 解耦 SQL 执行逻辑与连接生命周期管理。
CPQuery / CPQueryFactory:SQL 执行模板
CPQuery
是项目中的 JDBC 模板核心类,类似 Spring 的 JdbcTemplate
。它封装了 PreparedStatement 的参数绑定、SQL 执行、结果映射等常见流程:
CPQuery query = CPQueryFactory.create()
.sql("SELECT * FROM user WHERE id = ?")
.params(1);
User user = query.queryOne(User.class);
设计亮点:
- 提供链式编程接口,构造直观简洁;
- 支持
queryList
,queryOne
,update
,batch
等通用操作; - 与
DbContext
解耦,使用者无需关心连接。
BeanPropertyRowMapper:结果集自动映射
这个类实现了一个轻量级的 ORM 映射逻辑。它根据 JDBC 返回的 ResultSet
字段,自动匹配 Java Bean 的属性并注入值,实现对象-关系映射:
public class User {
private Integer id;
private String name;
}
通过反射完成映射,无需手动解析 ResultSet。
设计亮点:
- 利用反射技术,提高代码复用;
- 遵循 JavaBean 命名规范,便于扩展;
- 可替换为更复杂的 ORM 实现,如 MyBatis。
数据访问示例
原生 SQL 查询
- 保留原生 SQL 的控制力
- 支持条件拼接、动态查询、分页等复杂需求
- 设计灵活,可配合事务控制使用
public class EmployeeService {
public Employee getEmployee(int id) throws Exception {
try(DbContext db = DbConnManager.createAppDb("test")) {
CPQuery query = db.CPQuery().create("SELECT * FROM employees WHERE id = ?", new Object[]{id});
return query.toSingle(Employee.class);
}
}
public int update(User user) {
return CPQueryFactory.create()
.sql("UPDATE user SET name = ? WHERE id = ?")
.params(user.getName(), user.getId())
.update();
}
}
整个过程无需编写冗余的 JDBC 模板代码,调用逻辑清晰直观。
基于实体的 CRUD 操作
Javelin 框架的 DAL 设计支持基于实体的 CRUD 操作,包括:
- 实体类定义与表结构映射
- 基于实体的查询构造器
- 统一的 CRUD 操作接口
public class EmployeeService {
public void create(Employee emp) throws Exception {
try(DbContext db = DbConnManager.createAppDb("test")) {
db.Entity().create(Employee.class).insert(emp);
}
}
public int update(@FromBody Employee employee) throws Exception {
try(DbContext db = DbConnManager.createAppDb("test")) {
return db.Entity().create(Employee.class).update(employee);
}
}
public Employee getById(@FromRoute int id) throws Exception {
return employeeService.getEmployeeEntity(id);
}
public int delete(@FromRoute int id) throws Exception {
try(DbContext db = DbConnManager.createAppDb("test")) {
return db.Entity().create(Employee.class).delete(id);
}
}
}
链式查询与条件拼接
- 支持
.where(...)
、.andWhere(...)
等链式查询 - 通过注解映射自动完成字段匹配
这种“类 Repository”风格的设计让每个实体的访问操作集中在一起,符合“按实体聚合 DAL 行为”的设计理念。虽然 Javelin 目前没有实现类似 Spring Data 的 Repository 自动代理机制,但通过 EntityFactory
创建出的 Entity<T>
实例,已经具备了类似的功能:
- 用户无需手写 CRUD 实现,只需定义好实体类(如 User、Order)
- 通过链式 API 即可完成查询、插入、更新、删除操作
public class EmployeeService {
public List<Employee> list() throws Exception {
try(DbContext db = DbConnManager.createAppDb("test")) {
return db.Entity().create(Employee.class)
.where("age >?", 18)
.andWhere("name like?", "%张%")
.orderBy("create_time desc")
}
}
}
因此,即便未实现自动代理机制,Javelin 仍然实现了 Repository 模式的核心价值:简洁、统一、按需调用。
未来也可加入如下 DSL 风格支持:
.where(e -> e.getAge() > 18)
.and(e -> e.getName().contains("张"))
总结与展望
通过本篇文章,我们构建了一个结构清晰、职责明确、支持类 LINQ 查询与原生 SQL 混用的微型 ORM 模块,涵盖:
- 数据上下文(DbContext)
- 注解驱动实体映射
- 类 LINQ 查询接口
- 原生 SQL 兼容查询器(CPQuery)
下一篇文章中,我们将继续拓展对事务、Repository 模式以及自动代理注册的支持,进一步增强框架的工程化能力。
敬请期待。
由于篇幅原因,示例中的代码仅展示了部分关键实现细节,完整代码请参考GitHub仓库。