Easy-Mybatis version-0.0.3

Easy-MyBatis v0.0.3 完整类关系深度解析
一、整体架构:六大模块的协作网络
text
┌─────────────────────────────────────────────────────────────────────────────┐
│ EASY-MYBATIS v0.0.3 │
│ 模块化架构 + 职责分离 + 依赖注入 │
└────────────────────┬────────────────┬────────────────┬──────────────────────┘
│ │ │
┌─────────▼────────┐ ┌────▼──────┐ ┌──────▼──────┐
│ 配置与启动层 │ │ 代理门面层 │ │ 业务处理器层 │
│ (框架装配引擎) │ │ (统一入口) │ │ (功能实现) │
└─────────┬────────┘ └────┬──────┘ └──────┬──────┘
│ │ │
┌─────────▼────────────────▼────────────────▼─────────┐
│ 工具与基础设施层 │
│ (可复用组件 + 核心模型) │
└─────────┬────────────────┬────────────────┬─────────┘
│ │ │
┌─────────▼────────┐ ┌────▼──────┐ ┌──────▼──────┐
│ 数据源层 │ │ 事务管理层 │ │ 映射模型层 │
│ (连接管理) │ │ (事务控制) │ │ (配置封装) │
└──────────────────┘ └───────────┘ └─────────────┘
二、详细类关系:从依赖到协作的全景图
2.1 核心启动链路:工厂建造者模式
text
┌─────────────────────┐ 创建 ┌─────────────────────┐
│ 应用程序入口 ├──────────────────►│ SqlSessionFactory │
│ (用户代码) │ │ Builder │
└─────────────────────┘ └──────────┬──────────┘
│ build()
│ 解析XML配置
▼
┌─────────────────────┐
│ Resources工具类 │
│ (类路径资源加载器) │
└──────────┬──────────┘
│ getResourceAsStream()
▼
┌─────────────────────┐ 依赖 ┌─────────────────────┐
│ Dom4j XML解析器 │◄─────────────────┤ XML配置文件 │
│ (SAXReader) │ │ (mybatis-config.xml)│
└─────────────────────┘ └─────────────────────┘
详细代码流程:
java
// 应用程序启动
public static void main(String[] args) {
// 1. 用户通过Builder构建工厂
InputStream inputStream = Resources.getResourcesAsStream("mybatis-config.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
// 2. Builder内部解析过程
public SqlSessionFactory build(InputStream inputStream) throws DocumentException {
// 使用SAXReader解析XML
SAXReader saxReader = new SAXReader();
Document document = saxReader.read(inputStream);
// 解析数据源配置
DataSource dataSource = createDataSource(config);
// 解析事务管理器
TransactionManager tm = createTransactionManager(config, dataSource);
// 解析Mapper文件,构建SQL映射仓库
Map<String, EasyMybatisMappedStatement> mappings = parseMappers(config);
// 创建SqlSessionFactory
return new SqlSessionFactory(tm, mappings);
}
}
2.2 工厂与代理:双重工厂模式
text
┌─────────────────────┐ 创建 ┌─────────────────────┐
│ SqlSessionFactory ├──────────────────►│ SqlSessionProxy │
│ (一级工厂) │ openSession() │ Factory │
└─────────────────────┘ └──────────┬──────────┘
│ │ createProxy()
│ 持有 ▼
▼ ┌─────────────────────┐
┌─────────────────────┐ 持有 │ SqlSessionProxy │
│ TransactionManager │◄──────────────────────┤ (代理实现) │
└─────────────────────┘ └──────────┬──────────┘
▲ │ 实现
│ ▼
┌─────────────────────┐ 持有 ┌─────────────────────┐
│ SQL映射仓库 │◄──────────────────────┤ SqlSession接口 │
│ (Map<id,Statement>) │ │ (操作规范) │
└─────────────────────┘ └─────────────────────┘
依赖注入细节:
java
// SqlSessionFactory 构造器:注入核心组件
public SqlSessionFactory(TransactionManager transactionManager,
Map<String, EasyMybatisMappedStatement> mappedStatements) {
// 创建二级工厂(代理工厂)
this.proxyFactory = new SqlSessionProxyFactory(transactionManager, mappedStatements);
}
// SqlSessionProxyFactory:创建代理对象
public SqlSession createProxy() {
// 1. 开启连接
transactionManager.openConnection();
// 2. 创建代理并注入所有处理器
return new SqlSessionProxy(
transactionManager, // 事务管理器
mappedStatements, // SQL映射
new BaseCRUDHandler(...), // CRUD处理器
new QueryHandler(...), // 查询处理器
new BatchOperationHandler(...), // 批量处理器
new ProcedureHandler(...) // 存储过程处理器
);
}
2.3 代理门面:职责路由中心
text
┌─────────────────────┐
│ SqlSessionProxy │
│ (代理门面类) │
└──────────┬──────────┘
│ 实现SqlSession接口的23个方法
│ 每个方法都是简单的路由指令
▼
┌─────────────────────┬─────────────────────┬─────────────────────┐
│ │ │ │
▼ ▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│BaseCRUD │ │ Query │ │ Batch │ │Procedure│
│Handler │ │ Handler │ │ Handler │ │ Handler │
└────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘
│ │ │ │
│ 插入/更新/删除 │ 查询/分页/条件 │ 批量插入/更新 │ 存储过程调用
▼ ▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│JdbcExec │ │ResultSet│ │ 事务控制 │ │Callable │
│ -utor │ │ Mapper │ │ (手动) │ │Statement│
└─────────┘ └─────────┘ └─────────┘ └─────────┘
路由代码示例:
java
// SqlSessionProxy中的路由方法
public class SqlSessionProxy implements SqlSession {
// 路由表:方法名 -> 处理器
private final BaseCRUDHandler crudHandler;
private final QueryHandler queryHandler;
private final BatchOperationHandler batchHandler;
private final ProcedureHandler procedureHandler;
// 路由规则示例:
// 1. CRUD操作 -> BaseCRUDHandler
public int insert(String sqlId, Object obj) {
return crudHandler.insert(sqlId, obj); // 路由到CRUD处理器
}
// 2. 查询操作 -> QueryHandler
public Object selectOne(String sqlId, Object parameterObj) {
return queryHandler.selectOne(sqlId, parameterObj); // 路由到查询处理器
}
// 3. 分页查询 -> QueryHandler
public PageResult<?> selectPage(String sqlId, Object parameterObj, int pageNum, int pageSize) {
return queryHandler.selectPage(sqlId, parameterObj, pageNum, pageSize);
}
// 4. 批量操作 -> BatchOperationHandler
public int[] batchInsert(String sqlId, List<?> objList) {
return batchHandler.batchInsert(sqlId, objList); // 路由到批量处理器
}
// 5. 存储过程 -> ProcedureHandler
public Map<String, Object> callProcedure(String procedureName, Map<String, Object> paramMap) {
return procedureHandler.callProcedure(procedureName, paramMap);
}
// 6. 事务操作 -> 直接委托给TransactionManager
public void commit() {
transactionManager.commit(); // 直接调用,不经过处理器
}
}
2.4 处理器网络:微服务化架构
2.4.1 BaseCRUDHandler 依赖关系
text
┌─────────────────────┐ 使用 ┌─────────────────────┐
│ BaseCRUDHandler ├──────────────────►│ JdbcExecutor │
│ (增删改处理器) │ executeUpdate() │ (JDBC执行引擎) │
└──────────┬──────────┘ └──────────┬──────────┘
│ │
│ 依赖 │ 依赖
▼ ▼
┌─────────────────────┐ ┌─────────────────────┐
│ 工具类集合 │ │ TransactionManager │
│ (解析+反射) │ │ (事务管理) │
└─────────────────────┘ └─────────────────────┘
│ ▲
│ │
┌──────┴──────┐ ┌─────┴─────┐
▼ ▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│SqlParser│ │SqlParam │ │ 连接池 │ │ 连接状态 │
│ Util │ │ParserUtil│ │ (数据源) │ │ 管理 │
└─────────┘ └─────────┘ └─────────┘ └─────────┘
2.4.2 QueryHandler 依赖关系
text
┌─────────────────────┐ 使用 ┌─────────────────────┐
│ QueryHandler ├──────────────────►│ ResultSetMapper │
│ (查询处理器) │ mapResultSetToObject()│ (结果集映射器) │
└──────────┬──────────┘ └──────────┬──────────┘
│ │
│ 依赖 │ 依赖
▼ ▼
┌─────────────────────┐ ┌─────────────────────┐
│ SQL解析工具 │ │ 反射工具类 │
│ (SqlParserUtil) │ │ (ReflectUtil) │
└─────────────────────┘ └──────────┬──────────┘
│ │
│ │ 依赖
▼ ▼
┌─────────────────────┐ ┌─────────────────────┐
│ 分页计算逻辑 │ │ 字符串工具类 │
│ (LIMIT offset计算) │ │ (StringUtil) │
└─────────────────────┘ └─────────────────────┘
2.4.3 BatchOperationHandler 依赖关系
text
┌─────────────────────┐ 使用 ┌─────────────────────┐
│ BatchOperationHandler├──────────────────►│ JDBC批处理API │
│ (批量处理器) │ addBatch() + │ (PreparedStatement) │
└──────────┬──────────┘ executeBatch() └──────────┬──────────┘
│ │
│ 依赖 │ 依赖
▼ ▼
┌─────────────────────┐ ┌─────────────────────┐
│ 事务手动控制逻辑 │ │ 参数智能绑定 │
│ (autoCommit=false) │ │ (JdbcParamUtil) │
└─────────────────────┘ └─────────────────────┘
│ │
│ │
▼ ▼
┌─────────────────────┐ ┌─────────────────────┐
│ 异常回滚机制 │ │ Map参数支持 │
│ (rollback保障) │ │ (setParametersFromMap)│
└─────────────────────┘ └─────────────────────┘
2.4.4 ProcedureHandler 依赖关系
text
┌─────────────────────┐ 使用 ┌─────────────────────┐
│ ProcedureHandler ├──────────────────►│ CallableStatement │
│ (存储过程处理器) │ prepareCall() │ (JDBC调用接口) │
└──────────┬──────────┘ └──────────┬──────────┘
│ │
│ 依赖 │ 依赖
▼ ▼
┌─────────────────────┐ ┌─────────────────────┐
│ IN/OUT参数识别逻辑 │ │ 参数注册机制 │
│ (null值表示OUT参数) │ │ (registerOutParameter)│
└─────────────────────┘ └─────────────────────┘
2.5 工具类协作网络:可复用组件
text
┌─────────────────────┐
│ 核心工具类协作 │
│ (Utility Network) │
└──────────┬──────────┘
│
┌─────────────────────┼─────────────────────┐
│ │ │
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│JdbcParam│ │Reflect │ │ String │
│ Util │ │ Util │ │ Util │
└────┬────┘ └────┬────┘ └────┬────┘
│ │ │
│ 类型转换 │ 反射操作 │ 命名转换
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ setParamValue() │ │getFieldValue() │ │camelToUnderScore│
│ setFieldValue() │ │setEntityId() │ │underScoreToCamel│
└─────────────────┘ └─────────────────┘ └─────────────────┘
▲ ▲ ▲
│ │ │
┌────┴──────┐ ┌────┴──────┐ ┌─────┴─────┐
▼ ▼ ▼ ▼ ▼ ▼
┌───────┐ ┌───────┐┌───────┐┌───────┐┌───────┐┌───────┐
│查询处理器│ │CRUD处理器││批量处理器││结果映射器││参数解析器││SQL解析器│
└───────┘ └───────┘└───────┘└───────┘└───────┘└───────┘
工具类调用示例:
java
// JdbcParamUtil 被多个类调用
public class JdbcExecutor {
public int executeUpdate(String sql, Map<Integer, String> paramMap, Object obj) {
// 调用JdbcParamUtil设置参数
JdbcParamUtil.setPreparedStatementParams(ps, paramMap, obj);
}
}
public class ResultSetMapper {
private void setFieldValue(Object obj, Field field, ResultSet rs, int columnIndex) throws Exception {
// 调用JdbcParamUtil设置字段值
JdbcParamUtil.setFieldValue(setMethod, obj, rs, columnIndex, field.getType());
}
}
// ReflectUtil 被多个类调用
public class BaseCRUDHandler {
public Object insertWithGeneratedKey(String sqlId, Object obj) {
// 调用ReflectUtil设置自增主键
ReflectUtil.setEntityId(obj, generatedId);
}
}
public class ResultSetMapper {
public Object mapResultSetToObject(ResultSet rs, String resultType) throws Exception {
// 调用ReflectUtil创建对象实例
Object obj = ReflectUtil.instantiateObject(resultType);
}
}
// StringUtil 被多个类调用
public class ResultSetMapper {
private Map<String, Field> createFieldMap(Class<?> clazz) {
// 调用StringUtil进行命名转换
String underscoreName = StringUtil.camelToUnderScore(fieldName);
}
private Field findMatchingField(Map<String, Field> fieldMap, String columnName, Class<?> clazz) {
// 调用StringUtil进行命名转换
String camelCaseName = StringUtil.underScoreToCamel(normalizedColumnName);
}
}
2.6 数据流与转换:类型系统
text
┌─────────────────┐ 解析 ┌─────────────────┐ 封装 ┌─────────────────┐
│ XML配置文件 ├─────────────►│ MappedStatement├─────────────►│ SQL映射仓库 │
│ (文本格式) │ Dom4j解析 │ (Java对象) │ Map存储 │ (内存结构) │
└─────────────────┘ └─────────────────┘ └────────┬────────┘
│ 获取
▼
┌─────────────────┐ 执行 ┌─────────────────┐ 映射 ┌─────────────────┐
│ Java对象参数 ├─────────────►│ PreparedStatement├─────────────►│ ResultSet │
│ (用户输入) │ JDBC参数绑定 │ (预编译SQL) │ 数据库查询 │ (查询结果) │
└─────────────────┘ └─────────────────┘ └────────┬────────┘
│ 转换
▼
┌─────────────────┐ 封装 ┌─────────────────┐ 返回 ┌─────────────────┐
│ 实体类对象 │◄─────────────┤ ResultSetMapper├─────────────►│ 用户程序 │
│ (业务对象) │ 反射赋值 │ (映射引擎) │ 类型转换 │ (调用者) │
└─────────────────┘ └─────────────────┘ └─────────────────┘
三、核心依赖关系详解
3.1 编译时依赖(硬依赖)
java
// 1. 框架核心依赖JDBC
import java.sql.*;
// 2. 配置解析依赖Dom4j
import org.dom4j.*;
// 3. 工具类依赖Java反射
import java.lang.reflect.*;
// 4. 数据结构和集合
import java.util.*;
3.2 运行时依赖(软依赖)
text
应用程序
│
├── 依赖 → Easy-MyBatis框架 (v0.0.3)
│ │
│ ├── 依赖 → JDBC驱动 (如mysql-connector-java)
│ │
│ ├── 依赖 → Dom4j (XML解析)
│ │
│ └── 依赖 → 无其他框架
│
└── 依赖 → 数据库 (MySQL/Oracle等)
3.3 类间依赖度统计
| 类名 | 被依赖次数 | 依赖其他类数 | 重要度 |
|---|---|---|---|
SqlSessionProxy |
1 (被Factory创建) | 5 (4个Handler+TransactionManager) | ★★★★★ |
JdbcParamUtil |
8 (被所有Handler、Executor、Mapper调用) | 0 | ★★★★ |
TransactionManager |
5 (被所有Handler、Executor、Factory使用) | 1 (依赖DataSource) | ★★★★★ |
ResultSetMapper |
1 (仅被QueryHandler使用) | 4 (依赖多个工具类) | ★★★ |
ReflectUtil |
4 (被Mapper、多个Handler使用) | 1 (依赖StringUtil) | ★★★★ |
StringUtil |
3 (被ReflectUtil、ResultSetMapper使用) | 0 | ★★ |
四、设计模式在类关系中的体现
4.1 工厂模式的三层结构
text
┌─────────────────┐ 构建 ┌─────────────────┐
│ Builder ├─────────────────►│ Factory │
│ (建造者) │ build()方法 │ (一级工厂) │
└─────────────────┘ └────────┬────────┘
│ create()
▼
┌─────────────────┐
│ ProxyFactory │
│ (二级工厂) │
└────────┬────────┘
│ createProxy()
▼
┌─────────────────┐
│ SqlSession │
│ (产品) │
└─────────────────┘
设计价值:
-
分离关注点:Builder负责解析配置,Factory负责创建对象
-
双重工厂:一级工厂管理资源,二级工厂创建代理
-
灵活扩展:可以创建不同类型的SqlSession实现
4.2 代理模式的透明访问
text
客户端代码
│
▼ (只知道接口)
┌─────────────────┐
│ SqlSession接口 │
└────────┬────────┘
│ 实现
▼
┌─────────────────┐ 委托 ┌─────────────────┐
│ SqlSessionProxy├──────────────►│ 真实处理器 │
│ (代理对象) │ 路由调用 │ (Handler网络) │
└─────────────────┘ └─────────────────┘
代理模式的四种用途:
-
远程代理:隐藏网络通信细节(未来扩展)
-
虚拟代理:延迟加载资源(如懒加载连接)
-
保护代理:控制访问权限(未来扩展)
-
智能代理:添加额外功能(日志、监控等)
4.3 策略模式的算法选择
text
┌─────────────────┐
│ SqlSession接口 │
│ (策略接口) │
└────────┬────────┘
│
┌──────────────┼──────────────┐
│ │ │
▼ ▼ ▼
┌─────────────┐┌─────────────┐┌─────────────┐
│ CRUD策略 ││ 查询策略 ││ 批量策略 │
│ (BaseCRUD) ││ (Query) ││ (Batch) │
└─────────────┘└─────────────┘└─────────────┘
策略选择逻辑:
java
// 在SqlSessionProxy中,根据方法类型选择策略
public Object execute(String methodName, Object... args) {
switch (methodName) {
case "insert":
case "update":
case "delete":
return crudHandler.execute(methodName, args); // 选择CRUD策略
case "selectOne":
case "selectList":
case "selectPage":
return queryHandler.execute(methodName, args); // 选择查询策略
case "batchInsert":
case "batchUpdate":
return batchHandler.execute(methodName, args); // 选择批量策略
default:
throw new UnsupportedOperationException();
}
}
4.4 门面模式的简化接口
text
┌─────────────────────────────────────┐
│ 复杂子系统 (Handler网络) │
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐│
客户端 ────────┼─►│CRUD Handler││Query Handler││Batch Handler││
│ └─────────┘ └─────────┘ └─────────┘│
│ │
│ ┌─────────┐ ┌─────────┐ │
│ │Procedure │ │Executor │ │
│ │ Handler │ │ │ │
│ └─────────┘ └─────────┘ │
└─────────────────────────────────────┘
▲
│ 被封装
│
┌────────────┴────────────┐
│ 门面类 (Facade) │
│ SqlSessionProxy │
│ (简化接口) │
└────────────┬────────────┘
│
▼
客户端代码
门面模式的收益:
-
接口简化:客户端只需与一个类交互
-
解耦:子系统变化不影响客户端
-
复用:多个客户端可以共享同一门面
五、类关系的演化:从v0.0.2到v0.0.3
5.1 v0.0.2的紧密耦合
text
┌─────────────────┐
│ DefaultSqlSession│
│ (上帝类) │
└────────┬────────┘
│ 直接包含所有功能
┌───────────────────┼───────────────────┐
│ │ │
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│事务管理代码│ │SQL执行代码│ │结果映射代码│
│(300行) │ │(400行) │ │(300行) │
└─────────┘ └─────────┘ └─────────┘
问题:
-
单一职责违反:一个类做所有事情
-
高耦合:修改一处影响全局
-
难以测试:需要完整上下文才能测试
-
代码复用差:功能无法单独复用
5.2 v0.0.3的松耦合设计
text
┌─────────────────┐
│ SqlSessionProxy │
│ (协调者) │
└────────┬────────┘
│ 委托给专门处理器
┌───────────────────┼───────────────────┐
│ │ │
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│BaseCRUD │ │ Query │ │ Batch │
│Handler │ │ Handler │ │ Handler │
└────┬────┘ └────┬────┘ └────┬────┘
│ │ │
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│JdbcExec │ │ResultSet│ │事务控制 │
│ -utor │ │ Mapper │ │ 逻辑 │
└─────────┘ └─────────┘ └─────────┘
改进:
-
职责分离:每个类只做一件事
-
低耦合:通过接口和委托解耦
-
易测试:每个类可以独立测试
-
高复用:工具类被多个处理器共享
5.3 依赖关系的转变
从继承到组合:
java
// v0.0.2:使用继承(不灵活)
public class DefaultSqlSession extends BaseSqlSession {
// 继承所有方法,无法选择
}
// v0.0.3:使用组合(灵活)
public class SqlSessionProxy implements SqlSession {
// 组合需要的处理器
private BaseCRUDHandler crudHandler;
private QueryHandler queryHandler;
// ... 可以动态组合
}
从直接调用到依赖注入:
java
// v0.0.2:硬编码依赖
public class DefaultSqlSession {
private Connection conn = DriverManager.getConnection(...); // 硬编码
public void insert(...) {
// 直接使用conn
}
}
// v0.0.3:依赖注入
public class BaseCRUDHandler {
private final TransactionManager transactionManager; // 注入
public BaseCRUDHandler(TransactionManager tm) { // 构造函数注入
this.transactionManager = tm;
}
public void insert(...) {
Connection conn = transactionManager.getConnection(); // 通过接口获取
}
}
六、类关系的可扩展性设计
6.1 水平扩展:新增处理器
text
现有架构:
┌─────────────────┐
│ SqlSessionProxy │
└────────┬────────┘
│
┌────┴────┐
▼ ▼
┌──────┐ ┌──────┐
│CRUD │ │Query │
└──────┘ └──────┘
新增缓存处理器:
┌─────────────────┐
│ SqlSessionProxy │
└────────┬────────┘
│
┌───────┴───────┐
▼ ▼
┌────────┐ ┌──────┐
│ Cache │ │原始路由│
│Handler │ └──────┘
└────────┘
扩展步骤:
-
创建新的
CacheHandler类 -
修改
SqlSessionProxy注入CacheHandler -
在路由方法中添加缓存逻辑
6.2 垂直扩展:处理器内部优化
text
QueryHandler的扩展:
┌─────────────────┐
│ QueryHandler │
└────────┬────────┘
│ 支持多种查询策略
┌────┴────┐
▼ ▼
┌──────┐ ┌──────┐
│分页查询│ │条件查询│
└──────┘ └──────┘
│ │
▼ ▼
┌──────┐ ┌──────┐
│LIMIT │ │WHERE │
│优化 │ │优化 │
└──────┘ └──────┘
6.3 插件扩展:AOP增强
text
插件机制设计:
┌─────────────────┐
│ SqlSession │
│ (接口) │
└────────┬────────┘
│
┌────────▼────────┐
│ PluginManager │
│ (插件管理器) │
└────────┬────────┘
│ 动态织入
┌───────────────┼───────────────┐
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│日志插件 │ │监控插件 │ │缓存插件 │
│(Log) │ │(Monitor)│ │(Cache) │
└─────────┘ └─────────┘ └─────────┘
七、类关系的测试策略
7.1 单元测试依赖图
text
测试分层:
┌─────────────────┐
│ 集成测试 │
│ (完整流程) │
└────────┬────────┘
│
┌────────▼────────┐
│ 组件测试 │
│ (处理器级) │
└────────┬────────┘
│
┌────────▼────────┐
│ 单元测试 │
│ (工具类级) │
└─────────────────┘
测试替身使用:
┌─────────────────┐ 使用Mock ┌─────────────────┐
│ QueryHandler ├─────────────────────►│ Mock ResultSet│
│ (被测试类) │ │ (测试替身) │
└────────┬────────┘ └─────────────────┘
│
│ 使用Stub
▼
┌─────────────────┐
│ Stub Transaction│
│ Manager │
│ (桩对象) │
└─────────────────┘
7.2 测试覆盖率要求
| 类类型 | 覆盖率要求 | 测试重点 |
|---|---|---|
| 工具类 | 100% | 所有边界条件、异常场景 |
| 处理器 | 90%+ | 主要业务逻辑、异常处理 |
| 代理类 | 80%+ | 路由逻辑、异常传播 |
| 工厂类 | 70%+ | 对象创建、配置解析 |
八、总结:类关系的设计价值
8.1 架构质量指标
-
高内聚:每个类职责单一,功能紧密相关
-
低耦合:通过接口和依赖注入减少耦合
-
可测试性:每个类可以独立测试
-
可维护性:修改一处不影响其他部分
-
可扩展性:新增功能只需添加新类
8.2 设计原则体现
-
单一职责原则:每个类只有一个改变的理由
-
开闭原则:对扩展开放,对修改关闭
-
里氏替换原则:子类可以替换父类
-
接口隔离原则:接口粒度适中
-
依赖倒置原则:依赖抽象,不依赖具体
8.3 实际工程价值
-
团队协作:不同开发者可以并行开发不同处理器
-
代码审查:小类更容易审查和理解
-
故障隔离:一个处理器故障不影响其他功能
-
性能优化:可以针对特定处理器进行优化
-
技术演进:可以逐步替换底层实现
这种类关系设计使得Easy-MyBatis v0.0.3具备了企业级框架的雏形,为后续的功能增强和性能优化奠定了坚实的基础。
源码
package com.rongx.core;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
public class EasyMybatisJDBCTransaction implements TransactionManager {
private Connection conn;
private DataSource dataSource;
private boolean autoCommit;
private boolean isClosed = false; // 添加连接状态标志
public EasyMybatisJDBCTransaction(DataSource dataSource, boolean autoCommit) {
this.dataSource = dataSource;
this.autoCommit = autoCommit;
}
public void commit() {
if (isClosed) {
System.err.println("警告:连接已关闭,无法提交事务");
return;
}
try {
if (conn != null && !conn.isClosed()) {
conn.commit();
}
} catch (SQLException e) {
throw new RuntimeException("提交事务失败", e);
}
}
public void rollback() {
if (isClosed) {
System.err.println("警告:连接已关闭,无法回滚事务");
return;
}
try {
if (conn != null && !conn.isClosed()) {
conn.rollback();
}
} catch (SQLException e) {
throw new RuntimeException("回滚事务失败", e);
}
}
@Override
public void close() {
try {
if (conn != null && !conn.isClosed()) {
conn.close();
}
isClosed = true; // 标记为已关闭
} catch (SQLException e) {
throw new RuntimeException("关闭数据库连接失败", e);
}
}
@Override
public void openConnection() {
try {
if (conn == null || conn.isClosed()) {
this.conn = dataSource.getConnection();
this.conn.setAutoCommit(this.autoCommit);
isClosed = false; // 重置状态
}
} catch (SQLException e) {
throw new RuntimeException("打开数据库连接失败", e);
}
}
@Override
public Connection getConnection() {
if (conn == null) {
throw new RuntimeException("连接未打开,请先调用openConnection()");
}
return conn;
}
// 添加连接状态检查方法
public boolean isConnectionClosed() {
try {
return conn == null || conn.isClosed() || isClosed;
} catch (SQLException e) {
return true;
}
}
}
package com.rongx.core;
public class EasyMybatisMappedStatement {
private String sqlId;
private String resultType;
private String sql;
private String parameterType;
private String sqlType;
@Override
public String toString() {
return "EasyMappedStatement{" +
"sqlId='" + sqlId + '\'' +
", resultType='" + resultType + '\'' +
", sql='" + sql + '\'' +
", parameterType='" + parameterType + '\'' +
", sqlType='" + sqlType + '\'' +
'}';
}
public String getSqlId() {
return sqlId;
}
public void setSqlId(String sqlId) {
this.sqlId = sqlId;
}
public String getResultType() {
return resultType;
}
public void setResultType(String resultType) {
this.resultType = resultType;
}
public String getSql() {
return sql;
}
public void setSql(String sql) {
this.sql = sql;
}
public String getParameterType() {
return parameterType;
}
public void setParameterType(String parameterType) {
this.parameterType = parameterType;
}
public String getSqlType() {
return sqlType;
}
public void setSqlType(String sqlType) {
this.sqlType = sqlType;
}
public EasyMybatisMappedStatement(String sqlId, String resultType, String sql, String parameterType, String sqlType) {
this.sqlId = sqlId;
this.resultType = resultType;
this.sql = sql;
this.parameterType = parameterType;
this.sqlType = sqlType;
}
}
package com.rongx.core;
import com.rongx.utils.JdbcParamUtil;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Map;
public class JdbcExecutor {
private final TransactionManager transactionManager;
public JdbcExecutor(TransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
/**
* 执行更新操作(有参数)
*/
public int executeUpdate(String sql, Map<Integer, String> paramMap, Object obj) {
PreparedStatement ps = null;
try {
Connection conn = transactionManager.getConnection();
ps = conn.prepareStatement(sql);
JdbcParamUtil.setPreparedStatementParams(ps, paramMap, obj);
return ps.executeUpdate();
} catch (Exception e) {
throw new RuntimeException("执行SQL更新失败: " + sql, e);
} finally {
closeStatement(ps);
}
}
/**
* 执行更新操作(单参数)
*/
public int executeUpdateWithSingleParam(String sql, int paramIndex, Object paramValue) {
PreparedStatement ps = null;
try {
Connection conn = transactionManager.getConnection();
ps = conn.prepareStatement(sql);
JdbcParamUtil.setParamValue(ps, paramIndex, paramValue);
return ps.executeUpdate();
} catch (Exception e) {
throw new RuntimeException("执行SQL更新失败: " + sql, e);
} finally {
closeStatement(ps);
}
}
/**
* 执行更新操作(无参数)
*/
public int executeUpdateWithoutParam(String sql) {
PreparedStatement ps = null;
try {
Connection conn = transactionManager.getConnection();
ps = conn.prepareStatement(sql);
return ps.executeUpdate();
} catch (Exception e) {
throw new RuntimeException("执行SQL更新失败: " + sql, e);
} finally {
closeStatement(ps);
}
}
/**
* 关闭Statement
*/
private void closeStatement(PreparedStatement ps) {
if (ps != null) {
try {
ps.close();
} catch (SQLException e) {
System.err.println("关闭PreparedStatement失败: " + e.getMessage());
}
}
}
}
package com.rongx.core;
import com.rongx.result.PageResult;
import java.util.List;
import java.util.Map;
/**
* SqlSession核心接口
* 定义MyBatis风格的数据库操作核心方法,包含CRUD、事务管理、资源关闭等能力
* @author 自定义作者名
* @date 2025-12-09
*/
public interface SqlSession {
/**
* 提交事务
*/
void commit();
/**
* 回滚事务
*/
void rollback();
/**
* 关闭数据库连接
*/
void close();
/**
* 插入数据
* @param sqlId SQL语句的唯一标识(namespace + id)
* @param obj 待插入的实体对象
* @return 受影响的行数
*/
int insert(String sqlId, Object obj);
/**
* 更新数据
* @param sqlId SQL语句的唯一标识(namespace + id)
* @param obj 待更新的实体对象
* @return 受影响的行数
*/
int update(String sqlId, Object obj);
/**
* 删除数据
* @param sqlId SQL语句的唯一标识(namespace + id)
* @param parameterObj 删除参数(支持基础类型/实体对象)
* @return 受影响的行数
*/
int delete(String sqlId, Object parameterObj);
/**
* 查询单个对象
* @param sqlId SQL语句的唯一标识(namespace + id)
* @param parameterObj 查询参数(基础类型为主)
* @return 封装后的单个实体对象
*/
Object selectOne(String sqlId, Object parameterObj);
/**
* 查询列表数据
* @param sqlId SQL语句的唯一标识(namespace + id)
* @param parameterObj 查询参数(可为null,表示无参数查询)
* @return 封装后的实体对象列表
*/
List<Object> selectList(String sqlId, Object parameterObj);
// ========== 新增复杂功能 ========== version 0.0.2
/**
* 批量插入(高性能,减少网络交互)
* @param sqlId SQL标识
* @param objList 待插入的实体列表
* @return 批量执行的结果数组(每条操作受影响行数)
*/
int[] batchInsert(String sqlId, List<?> objList);
/**
* 批量更新/删除(通用批量操作)
* @param sqlId SQL标识
* @param paramList 参数列表(基础类型/实体对象)
* @return 批量执行的结果数组
*/
int[] batchUpdateOrDelete(String sqlId, List<?> paramList);
/**
* 分页查询(适配MySQL LIMIT,可扩展其他数据库)
* @param sqlId SQL标识
* @param parameterObj 查询参数(可为null)
* @param pageNum 页码(从1开始)
* @param pageSize 每页条数
* @return 分页结果(含数据列表、总条数、分页信息)
*/
PageResult<?> selectPage(String sqlId, Object parameterObj, int pageNum, int pageSize);
/**
* 插入并回填自增主键(如MySQL AUTO_INCREMENT)
* @param sqlId SQL标识
* @param obj 待插入实体
* @return 插入后的实体(主键已赋值)
*/
Object insertWithGeneratedKey(String sqlId, Object obj);
/**
* 多条件动态查询(支持多参数、动态拼接WHERE条件)
* @param sqlId 基础SQL标识(不含WHERE)
* @param conditionMap 条件键值对(key=字段名,value=字段值)
* @param resultType 返回实体类型
* @return 符合条件的列表
*/
List<Object> selectByCondition(String sqlId, Map<String, Object> conditionMap, Class<?> resultType);
/**
* 调用数据库存储过程
* @param procedureName 存储过程名
* @param paramMap 存储过程参数(key=参数名,value=参数值;OUT参数value传null,执行后回填)
* @return 存储过程执行结果(OUT参数/返回数据集)
*/
// Map<String, Object> callProcedure(String procedureName, Map<String, Object> paramMap);
}
package com.rongx.core;
import com.rongx.proxy.SqlSessionProxyFactory;
import java.util.Map;
public class SqlSessionFactory {
private final SqlSessionProxyFactory proxyFactory;
public SqlSessionFactory(TransactionManager transactionManager,
Map<String, EasyMybatisMappedStatement> mappedStatements) {
this.proxyFactory = new SqlSessionProxyFactory(transactionManager, mappedStatements);
}
public SqlSession openSession() {
return proxyFactory.createProxy();
}
public TransactionManager getTransactionManager() {
return proxyFactory.getTransactionManager();
}
public Map<String, EasyMybatisMappedStatement> getMappedStatements() {
return proxyFactory.getMappedStatements();
}
}
package com.rongx.core;
import com.rongx.resources.EasyMybatisUNPOOLEDDataSource;
import com.rongx.resources.Resources;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import javax.sql.DataSource;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
public class SqlSessionFactoryBuilder {
public SqlSessionFactory build(InputStream inputStream) throws DocumentException {
SAXReader saxReader = new SAXReader();
Document document = saxReader.read(inputStream);
Element environmentsElt = (Element) document.selectSingleNode("/configuration/environments");
String defaultEnv = environmentsElt.attributeValue("default");
Element environmentElt = (Element) document.selectSingleNode(
"/configuration/environments/environment[@id='" + defaultEnv + "']");
// 创建数据源
Element dataSourceElt = environmentElt.element("dataSource");
DataSource dataSource = getDataSource(dataSourceElt);
// 创建事务管理器
Element transactionManagerElt = environmentElt.element("transactionManager");
TransactionManager transactionManager = getTransactionManager(transactionManagerElt, dataSource);
// 解析Mapper文件
Element mappersElt = (Element) document.selectSingleNode("/configuration/mappers");
if (mappersElt == null) {
mappersElt = environmentElt.element("mappers");
}
Map<String, EasyMybatisMappedStatement> mappedStatements = getMappedStatements(mappersElt);
// 创建代理工厂
return new SqlSessionFactory(transactionManager, mappedStatements);
}
private Map<String, EasyMybatisMappedStatement> getMappedStatements(Element mappers) {
if (mappers == null) {
throw new RuntimeException("配置文件中未找到<mappers>元素");
}
Map<String, EasyMybatisMappedStatement> mappedStatements = new HashMap<>();
mappers.elements().forEach(mapperElt -> {
try {
String resource = mapperElt.attributeValue("resource");
SAXReader saxReader = new SAXReader();
Document document = saxReader.read(Resources.getResourcesAsStream(resource));
Element mapper = (Element) document.selectSingleNode("/mapper");
String namespace = mapper.attributeValue("namespace");
mapper.elements().forEach(sqlMapper -> {
String sqlId = sqlMapper.attributeValue("id");
String sql = sqlMapper.getTextTrim();
String parameterType = sqlMapper.attributeValue("parameterType");
String resultType = sqlMapper.attributeValue("resultType");
String sqlType = sqlMapper.getName().toLowerCase();
EasyMybatisMappedStatement ms = new EasyMybatisMappedStatement(
sqlId, resultType, sql, parameterType, sqlType);
mappedStatements.put(namespace + "." + sqlId, ms);
});
} catch (DocumentException e) {
throw new RuntimeException("解析Mapper文件失败: " + mapperElt.attributeValue("resource"), e);
}
});
return mappedStatements;
}
private TransactionManager getTransactionManager(Element transactionManagerElt, DataSource dataSource) {
String type = transactionManagerElt.attributeValue("type").toUpperCase();
if ("JDBC".equals(type)) {
return new EasyMybatisJDBCTransaction(dataSource, false);
} else if ("MANAGED".equals(type)) {
throw new RuntimeException("MANAGED事务管理器暂未实现");
} else {
throw new RuntimeException("不支持的事务管理器类型: " + type);
}
}
private DataSource getDataSource(Element dataSourceElt) {
Map<String, String> dataSourceMap = new HashMap<>();
dataSourceElt.elements().forEach(propertyElt -> {
dataSourceMap.put(propertyElt.attributeValue("name"), propertyElt.attributeValue("value"));
});
String dataSourceType = dataSourceElt.attributeValue("type").toUpperCase();
if ("POOLED".equals(dataSourceType)) {
throw new RuntimeException("POOLED数据源暂未实现");
} else if ("UNPOOLED".equals(dataSourceType)) {
return new EasyMybatisUNPOOLEDDataSource(
dataSourceMap.get("driver"),
dataSourceMap.get("url"),
dataSourceMap.get("username"),
dataSourceMap.get("password")
);
} else if ("JNDI".equals(dataSourceType)) {
throw new RuntimeException("JNDI数据源暂未实现");
} else {
throw new RuntimeException("不支持的数据源类型: " + dataSourceType);
}
}
}
package com.rongx.core;
import java.sql.Connection;
public interface TransactionManager {
/**
* 提交事务
*/
void commit();
/**
* 回滚事务
*/
void rollback();
/**
* 关闭事务
*/
void close();
/**
* 开启连接
*/
void openConnection();
/**
* 获取连接对象
* @return 连接对象
*/
Connection getConnection();
}
package com.rongx.handler;
import com.rongx.core.*;
import com.rongx.utils.JdbcParamUtil;
import com.rongx.utils.ReflectUtil;
import com.rongx.utils.SqlParamParserUtil;
import com.rongx.utils.SqlParserUtil;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.Statement;
import java.util.Map;
/**
* 基础CRUD操作处理器
*/
public class BaseCRUDHandler {
private final TransactionManager transactionManager;
private final Map<String, EasyMybatisMappedStatement> mappedStatements;
private final JdbcExecutor jdbcExecutor;
public BaseCRUDHandler(TransactionManager transactionManager,
Map<String, EasyMybatisMappedStatement> mappedStatements) {
this.transactionManager = transactionManager;
this.mappedStatements = mappedStatements;
this.jdbcExecutor = new JdbcExecutor(transactionManager);
}
/**
* 插入数据
*/
public int insert(String sqlId, Object obj) {
EasyMybatisMappedStatement ms = mappedStatements.get(sqlId);
String sql = SqlParserUtil.parseSql(ms.getSql());
Map<Integer, String> paramMap = SqlParamParserUtil.parseParamMap(ms.getSql());
return jdbcExecutor.executeUpdate(sql, paramMap, obj);
}
/**
* 更新数据
*/
public int update(String sqlId, Object obj) {
EasyMybatisMappedStatement ms = mappedStatements.get(sqlId);
String sql = SqlParserUtil.parseSql(ms.getSql());
Map<Integer, String> paramMap = SqlParamParserUtil.parseParamMap(ms.getSql());
return jdbcExecutor.executeUpdate(sql, paramMap, obj);
}
/**
* 删除数据
*/
public int delete(String sqlId, Object parameterObj) {
EasyMybatisMappedStatement ms = mappedStatements.get(sqlId);
String sql = SqlParserUtil.parseSql(ms.getSql());
Map<Integer, String> paramMap = SqlParamParserUtil.parseParamMap(ms.getSql());
// 处理不同类型的参数
if (paramMap.size() == 1 && parameterObj != null) {
return jdbcExecutor.executeUpdateWithSingleParam(sql, 1, parameterObj);
} else if (parameterObj != null) {
return jdbcExecutor.executeUpdate(sql, paramMap, parameterObj);
} else {
return jdbcExecutor.executeUpdateWithoutParam(sql);
}
}
/**
* 插入数据并返回自增主键
*/
public Object insertWithGeneratedKey(String sqlId, Object obj) {
EasyMybatisMappedStatement ms = mappedStatements.get(sqlId);
String sql = SqlParserUtil.parseSql(ms.getSql());
Map<Integer, String> paramMap = SqlParamParserUtil.parseParamMap(ms.getSql());
PreparedStatement ps = null;
try {
Connection conn = transactionManager.getConnection();
ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
JdbcParamUtil.setPreparedStatementParams(ps, paramMap, obj);
ps.executeUpdate();
// 获取自增主键
try (var generatedKeys = ps.getGeneratedKeys()) {
if (generatedKeys.next()) {
long generatedId = generatedKeys.getLong(1);
ReflectUtil.setEntityId(obj, generatedId);
}
}
return obj;
} catch (Exception e) {
throw new RuntimeException("插入数据并回填主键失败,SQL ID:" + sqlId, e);
} finally {
closeStatement(ps);
}
}
private void closeStatement(PreparedStatement ps) {
if (ps != null) {
try {
ps.close();
} catch (Exception e) {
System.err.println("关闭PreparedStatement失败: " + e.getMessage());
}
}
}
}
package com.rongx.handler;
import com.rongx.core.*;
import com.rongx.utils.JdbcParamUtil;
import com.rongx.utils.SqlParamParserUtil;
import com.rongx.utils.SqlParserUtil;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
public class BatchOperationHandler {
private final TransactionManager transactionManager;
private final Map<String, EasyMybatisMappedStatement> mappedStatements;
public BatchOperationHandler(TransactionManager transactionManager,
Map<String, EasyMybatisMappedStatement> mappedStatements) {
this.transactionManager = transactionManager;
this.mappedStatements = mappedStatements;
}
/**
* 批量插入
*/
public int[] batchInsert(String sqlId, List<?> objList) {
if (objList == null || objList.isEmpty()) {
return new int[0];
}
EasyMybatisMappedStatement ms = mappedStatements.get(sqlId);
String sql = SqlParserUtil.parseSql(ms.getSql());
Map<Integer, String> paramMap = SqlParamParserUtil.parseParamMap(ms.getSql());
PreparedStatement ps = null;
try {
Connection conn = transactionManager.getConnection();
boolean originalAutoCommit = conn.getAutoCommit();
conn.setAutoCommit(false); // 开始事务
ps = conn.prepareStatement(sql);
for (Object obj : objList) {
JdbcParamUtil.setPreparedStatementParams(ps, paramMap, obj);
ps.addBatch();
}
int[] result = ps.executeBatch();
conn.commit(); // 提交事务
conn.setAutoCommit(originalAutoCommit); // 恢复原始状态
return result;
} catch (SQLException e) {
rollbackTransaction();
throw new RuntimeException("批量插入数据失败,SQL ID:" + sqlId, e);
} finally {
closeStatement(ps);
}
}
/**
* 批量更新/删除
*/
public int[] batchUpdateOrDelete(String sqlId, List<?> paramList) {
if (paramList == null || paramList.isEmpty()) {
return new int[0];
}
EasyMybatisMappedStatement ms = mappedStatements.get(sqlId);
String sql = SqlParserUtil.parseSql(ms.getSql());
Map<Integer, String> paramMap = SqlParamParserUtil.parseParamMap(ms.getSql());
PreparedStatement ps = null;
try {
Connection conn = transactionManager.getConnection();
boolean originalAutoCommit = conn.getAutoCommit();
conn.setAutoCommit(false); // 开始事务
ps = conn.prepareStatement(sql);
for (Object param : paramList) {
// 处理不同类型的参数
if (param instanceof Map) {
Map<String, Object> paramMapObj = (Map<String, Object>) param;
setParametersFromMap(ps, paramMap, paramMapObj);
} else {
JdbcParamUtil.setPreparedStatementParams(ps, paramMap, param);
}
ps.addBatch();
}
int[] result = ps.executeBatch();
conn.commit(); // 提交事务
conn.setAutoCommit(originalAutoCommit); // 恢复原始状态
return result;
} catch (Exception e) {
rollbackTransaction();
throw new RuntimeException("批量更新/删除数据失败,SQL ID:" + sqlId, e);
} finally {
closeStatement(ps);
}
}
/**
* 从Map中设置参数
*/
private void setParametersFromMap(PreparedStatement ps,
Map<Integer, String> paramMap,
Map<String, Object> paramMapObj) throws Exception {
paramMap.forEach((index, fieldName) -> {
try {
Object value = paramMapObj.get(fieldName);
JdbcParamUtil.setParamValue(ps, index, value);
} catch (Exception e) {
throw new RuntimeException("设置批量参数失败,字段:" + fieldName, e);
}
});
}
/**
* 回滚事务
*/
private void rollbackTransaction() {
try {
transactionManager.rollback();
} catch (Exception e) {
System.err.println("事务回滚失败: " + e.getMessage());
}
}
/**
* 关闭Statement
*/
private void closeStatement(PreparedStatement ps) {
if (ps != null) {
try {
ps.close();
} catch (SQLException e) {
System.err.println("关闭PreparedStatement失败: " + e.getMessage());
}
}
}
}
package com.rongx.handler;
import com.rongx.utils.JdbcParamUtil;
import com.rongx.core.TransactionManager;
import java.sql.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 存储过程处理器
*/
public class ProcedureHandler {
private final TransactionManager transactionManager;
public ProcedureHandler(TransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
/**
* 调用数据库存储过程
*/
public Map<String, Object> callProcedure(String procedureName, Map<String, Object> paramMap) {
if (paramMap == null) paramMap = new HashMap<>();
Connection conn = transactionManager.getConnection();
// 构建存储过程调用SQL
StringBuilder procSql = new StringBuilder("{call ").append(procedureName).append("(");
for (int i = 0; i < paramMap.size(); i++) {
procSql.append("?");
if (i < paramMap.size() - 1) {
procSql.append(",");
}
}
procSql.append(")}");
try (CallableStatement cs = conn.prepareCall(procSql.toString())) {
List<String> outParamNames = new ArrayList<>();
int paramIndex = 1;
// 设置IN参数和注册OUT参数
for (Map.Entry<String, Object> entry : paramMap.entrySet()) {
String paramName = entry.getKey();
Object paramValue = entry.getValue();
if (paramValue == null) {
// 注册OUT参数
cs.registerOutParameter(paramIndex, Types.INTEGER);
outParamNames.add(paramName);
} else {
// 设置IN参数
JdbcParamUtil.setParamValue(cs, paramIndex, paramValue);
}
paramIndex++;
}
// 执行存储过程
cs.execute();
// 获取OUT参数值
paramIndex = 1;
for (String outParamName : outParamNames) {
paramMap.put(outParamName, cs.getObject(paramIndex));
paramIndex++;
}
return paramMap;
} catch (SQLException e) {
throw new RuntimeException("调用存储过程失败,存储过程名:" + procedureName, e);
}
}
}
package com.rongx.handler;
import com.rongx.core.*;
import com.rongx.result.PageResult;
import com.rongx.result.ResultSetMapper;
import com.rongx.utils.JdbcParamUtil;
import com.rongx.utils.SqlParserUtil;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;
public class QueryHandler {
private final TransactionManager transactionManager;
private final Map<String, EasyMybatisMappedStatement> mappedStatements;
private final ResultSetMapper resultSetMapper;
public QueryHandler(TransactionManager transactionManager,
Map<String, EasyMybatisMappedStatement> mappedStatements) {
this.transactionManager = transactionManager;
this.mappedStatements = mappedStatements;
this.resultSetMapper = new ResultSetMapper();
}
/**
* 查询单个对象
*/
public Object selectOne(String sqlId, Object parameterObj) {
EasyMybatisMappedStatement ms = mappedStatements.get(sqlId);
String sql = SqlParserUtil.parseSql(ms.getSql());
PreparedStatement ps = null;
ResultSet rs = null;
try {
ps = transactionManager.getConnection().prepareStatement(sql);
if (parameterObj != null) {
JdbcParamUtil.setParamValue(ps, 1, parameterObj);
}
rs = ps.executeQuery();
if (rs.next()) {
return resultSetMapper.mapResultSetToObject(rs, ms.getResultType());
}
return null;
} catch (Exception e) {
throw new RuntimeException("查询单个对象失败,SQL ID:" + sqlId, e);
} finally {
closeResources(rs, ps);
}
}
/**
* 查询列表数据
*/
public List<Object> selectList(String sqlId, Object parameterObj) {
EasyMybatisMappedStatement ms = mappedStatements.get(sqlId);
String sql = SqlParserUtil.parseSql(ms.getSql());
List<Object> resultList = new ArrayList<>();
PreparedStatement ps = null;
ResultSet rs = null;
try {
ps = transactionManager.getConnection().prepareStatement(sql);
if (parameterObj != null) {
JdbcParamUtil.setParamValue(ps, 1, parameterObj);
}
rs = ps.executeQuery();
while (rs.next()) {
Object obj = resultSetMapper.mapResultSetToObject(rs, ms.getResultType());
resultList.add(obj);
}
return resultList;
} catch (Exception e) {
throw new RuntimeException("查询列表数据失败,SQL ID:" + sqlId, e);
} finally {
closeResources(rs, ps);
}
}
/**
* 分页查询
*/
public PageResult<?> selectPage(String sqlId, Object parameterObj, int pageNum, int pageSize) {
if (pageNum < 1) pageNum = 1;
if (pageSize < 1) pageSize = 10;
int offset = (pageNum - 1) * pageSize;
EasyMybatisMappedStatement ms = mappedStatements.get(sqlId);
String originSql = ms.getSql();
// 查询总条数
String countSql = "SELECT COUNT(*) FROM (" + originSql + ") AS t_count";
String countPreparedSql = SqlParserUtil.parseSql(countSql);
long total = 0;
PreparedStatement countPs = null;
ResultSet countRs = null;
try {
countPs = transactionManager.getConnection().prepareStatement(countPreparedSql);
if (parameterObj != null) {
JdbcParamUtil.setParamValue(countPs, 1, parameterObj);
}
countRs = countPs.executeQuery();
if (countRs.next()) {
total = countRs.getLong(1);
}
} catch (SQLException e) {
throw new RuntimeException("查询分页总条数失败,SQL ID:" + sqlId, e);
} finally {
closeResources(countRs, countPs);
}
// 查询分页数据
String pageSql = originSql + " LIMIT " + offset + "," + pageSize;
String pagePreparedSql = SqlParserUtil.parseSql(pageSql);
List<Object> dataList = new ArrayList<>();
PreparedStatement pagePs = null;
ResultSet rs = null;
try {
pagePs = transactionManager.getConnection().prepareStatement(pagePreparedSql);
if (parameterObj != null) {
JdbcParamUtil.setParamValue(pagePs, 1, parameterObj);
}
rs = pagePs.executeQuery();
while (rs.next()) {
Object obj = resultSetMapper.mapResultSetToObject(rs, ms.getResultType());
dataList.add(obj);
}
return new PageResult<>(dataList, pageNum, pageSize, total);
} catch (Exception e) {
throw new RuntimeException("查询分页数据失败,SQL ID:" + sqlId, e);
} finally {
closeResources(rs, pagePs);
}
}
/**
* 多条件动态查询
*/
public List<Object> selectByCondition(String sqlId,
Map<String, Object> conditionMap,
Class<?> resultType) {
if (conditionMap == null || conditionMap.isEmpty()) {
throw new RuntimeException("多条件查询失败:查询条件不能为空");
}
EasyMybatisMappedStatement ms = mappedStatements.get(sqlId);
String baseSql = ms.getSql();
// 动态构建SQL
StringBuilder sqlBuilder = new StringBuilder(baseSql);
sqlBuilder.append(" WHERE 1=1 ");
List<Object> paramValues = new ArrayList<>();
for (Map.Entry<String, Object> entry : conditionMap.entrySet()) {
sqlBuilder.append(" AND ").append(entry.getKey()).append(" = ? ");
paramValues.add(entry.getValue());
}
String finalSql = SqlParserUtil.parseSql(sqlBuilder.toString());
List<Object> resultList = new ArrayList<>();
PreparedStatement ps = null;
ResultSet rs = null;
try {
ps = transactionManager.getConnection().prepareStatement(finalSql);
// 设置参数
for (int i = 0; i < paramValues.size(); i++) {
JdbcParamUtil.setParamValue(ps, i + 1, paramValues.get(i));
}
rs = ps.executeQuery();
while (rs.next()) {
Object obj = resultSetMapper.mapResultSetToObject(rs, resultType.getName());
resultList.add(obj);
}
return resultList;
} catch (Exception e) {
throw new RuntimeException("多条件动态查询失败,SQL ID:" + sqlId, e);
} finally {
closeResources(rs, ps);
}
}
/**
* 关闭资源
*/
private void closeResources(ResultSet rs, PreparedStatement ps) {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
// 记录日志但不抛出异常
System.err.println("关闭ResultSet失败: " + e.getMessage());
}
}
if (ps != null) {
try {
ps.close();
} catch (SQLException e) {
// 记录日志但不抛出异常
System.err.println("关闭PreparedStatement失败: " + e.getMessage());
}
}
}
}
package com.rongx.proxy;
import com.rongx.core.EasyMybatisMappedStatement;
import com.rongx.result.PageResult;
import com.rongx.core.SqlSession;
import com.rongx.core.TransactionManager;
import com.rongx.handler.BaseCRUDHandler;
import com.rongx.handler.BatchOperationHandler;
import com.rongx.handler.ProcedureHandler;
import com.rongx.handler.QueryHandler;
import java.util.List;
import java.util.Map;
/**
* SqlSession代理类
* 职责:协调各个功能模块,提供统一的API接口
*/
public class SqlSessionProxy implements SqlSession {
private final BaseCRUDHandler crudHandler;
private final BatchOperationHandler batchHandler;
private final QueryHandler queryHandler;
private final ProcedureHandler procedureHandler;
private final TransactionManager transactionManager;
public SqlSessionProxy(TransactionManager transactionManager,
Map<String, EasyMybatisMappedStatement> mappedStatements) {
this.transactionManager = transactionManager;
this.crudHandler = new BaseCRUDHandler(transactionManager, mappedStatements);
this.batchHandler = new BatchOperationHandler(transactionManager, mappedStatements);
this.queryHandler = new QueryHandler(transactionManager, mappedStatements);
this.procedureHandler = new ProcedureHandler(transactionManager);
}
// ========== 事务管理 ==========
@Override
public void commit() {
transactionManager.commit();
}
@Override
public void rollback() {
transactionManager.rollback();
}
@Override
public void close() {
transactionManager.close();
}
// ========== 基础CRUD操作 ==========
@Override
public int insert(String sqlId, Object obj) {
return crudHandler.insert(sqlId, obj);
}
@Override
public int update(String sqlId, Object obj) {
return crudHandler.update(sqlId, obj);
}
@Override
public int delete(String sqlId, Object parameterObj) {
return crudHandler.delete(sqlId, parameterObj);
}
@Override
public Object selectOne(String sqlId, Object parameterObj) {
return queryHandler.selectOne(sqlId, parameterObj);
}
@Override
public List<Object> selectList(String sqlId, Object parameterObj) {
return queryHandler.selectList(sqlId, parameterObj);
}
// ========== 批量操作 ==========
@Override
public int[] batchInsert(String sqlId, List<?> objList) {
return batchHandler.batchInsert(sqlId, objList);
}
@Override
public int[] batchUpdateOrDelete(String sqlId, List<?> paramList) {
return batchHandler.batchUpdateOrDelete(sqlId, paramList);
}
// ========== 查询操作 ==========
@Override
public PageResult<?> selectPage(String sqlId, Object parameterObj, int pageNum, int pageSize) {
return queryHandler.selectPage(sqlId, parameterObj, pageNum, pageSize);
}
@Override
public List<Object> selectByCondition(String sqlId, Map<String, Object> conditionMap, Class<?> resultType) {
return queryHandler.selectByCondition(sqlId, conditionMap, resultType);
}
// ========== 特殊操作 ==========
@Override
public Object insertWithGeneratedKey(String sqlId, Object obj) {
return crudHandler.insertWithGeneratedKey(sqlId, obj);
}
}
package com.rongx.proxy;
import com.rongx.core.EasyMybatisMappedStatement;
import com.rongx.core.SqlSession;
import com.rongx.core.TransactionManager;
import java.util.Map;
/**
* SqlSession代理工厂
* 职责:创建和管理SqlSessionProxy实例
*/
public class SqlSessionProxyFactory {
private final TransactionManager transactionManager;
private final Map<String, EasyMybatisMappedStatement> mappedStatements;
public SqlSessionProxyFactory(TransactionManager transactionManager,
Map<String, EasyMybatisMappedStatement> mappedStatements) {
this.transactionManager = transactionManager;
this.mappedStatements = mappedStatements;
}
/**
* 创建SqlSession代理对象
*/
public SqlSession createProxy() {
transactionManager.openConnection();
return new SqlSessionProxy(transactionManager, mappedStatements);
}
/**
* 获取原始的事务管理器
*/
public TransactionManager getTransactionManager() {
return transactionManager;
}
/**
* 获取SQL映射配置
*/
public Map<String, EasyMybatisMappedStatement> getMappedStatements() {
return mappedStatements;
}
}
package com.rongx.resources;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;
public class EasyMybatisUNPOOLEDDataSource implements javax.sql.DataSource{
private String url;
private String username;
private String password;
public EasyMybatisUNPOOLEDDataSource(String driver, String url, String username, String password) {
try {
// 注册驱动
Class.forName(driver);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
this.url = url;
this.username = username;
this.password = password;
}
@Override
public Connection getConnection() throws SQLException {
return DriverManager.getConnection(url, username, password);
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return null;
}
@Override
public PrintWriter getLogWriter() throws SQLException {
return null;
}
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
}
@Override
public void setLoginTimeout(int seconds) throws SQLException {
}
@Override
public int getLoginTimeout() throws SQLException {
return 0;
}
@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
return null;
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return false;
}
}
package com.rongx.resources;
import java.io.InputStream;
public class Resources {
/**
* 从类路径中获取配置文件的输入流
* @param config 类路径下的配置文件相对路径(例:jdbc.properties、mybatis-config.xml)
* @return 输入流,该输入流指向类路径中的配置文件
*/
public static InputStream getResourcesAsStream(String config){
// 核心逻辑:通过线程上下文类加载器加载类路径下的资源
//线程上下文类加载器(ContextClassLoader):是 JVM 提供的灵活类加载方式,能突破 “双亲委派模型” 的限制,优先加载当前应用 / 模块的类路径资源(而非系统类加载器 / 扩展类加载器)。
return Thread.currentThread().getContextClassLoader().getResourceAsStream(config);
}
}
package com.rongx.result;
import java.util.List;
public class PageResult<T> {
private List<T> data; // 当前页数据
private int pageNum; // 当前页码
private int pageSize; // 每页条数
private long total; // 总记录数
private int totalPages; // 总页数
public PageResult(List<T> data, int pageNum, int pageSize, long total) {
this.data = data;
this.pageNum = pageNum;
this.pageSize = pageSize;
this.total = total;
this.totalPages = (int) Math.ceil((double) total / pageSize);
}
public List<T> getData() {
return data;
}
public void setData(List<T> data) {
this.data = data;
}
public int getPageNum() {
return pageNum;
}
public void setPageNum(int pageNum) {
this.pageNum = pageNum;
}
public int getPageSize() {
return pageSize;
}
public void setPageSize(int pageSize) {
this.pageSize = pageSize;
}
public long getTotal() {
return total;
}
public void setTotal(long total) {
this.total = total;
}
public int getTotalPages() {
return totalPages;
}
public void setTotalPages(int totalPages) {
this.totalPages = totalPages;
}
}
package com.rongx.result;
import com.rongx.utils.JdbcParamUtil;
import com.rongx.utils.ReflectUtil;
import com.rongx.utils.StringUtil;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.util.HashMap;
import java.util.Map;
/**
* 结果集映射器
* 职责:将ResultSet映射为Java对象
*/
public class ResultSetMapper {
/**
* 将ResultSet映射为对象
*/
public Object mapResultSetToObject(ResultSet rs, String resultType) throws Exception {
Object obj = ReflectUtil.instantiateObject(resultType);
Class<?> clazz = Class.forName(resultType);
ResultSetMetaData metaData = rs.getMetaData();
int columnCount = metaData.getColumnCount();
// 缓存字段信息
Map<String, Field> fieldMap = createFieldMap(clazz);
for (int i = 1; i <= columnCount; i++) {
String columnName = metaData.getColumnName(i);
String columnLabel = metaData.getColumnLabel(i);
// 优先使用列标签(别名),如果没有别名则使用列名
String fieldName = columnLabel != null ? columnLabel : columnName;
// 查找匹配的字段
Field field = findMatchingField(fieldMap, fieldName, clazz);
if (field != null) {
setFieldValue(obj, field, rs, i);
}
}
return obj;
}
/**
* 创建字段映射缓存
*/
private Map<String, Field> createFieldMap(Class<?> clazz) {
Map<String, Field> fieldMap = new HashMap<>();
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
String fieldName = field.getName();
// 存储原始字段名
fieldMap.put(fieldName.toLowerCase(), field);
// 存储驼峰转下划线后的字段名
String underscoreName = StringUtil.camelToUnderScore(fieldName);
fieldMap.put(underscoreName.toLowerCase(), field);
}
return fieldMap;
}
/**
* 查找匹配的字段
*/
private Field findMatchingField(Map<String, Field> fieldMap, String columnName, Class<?> clazz) {
String normalizedColumnName = columnName.toLowerCase();
// 1. 直接匹配(大小写不敏感)
Field field = fieldMap.get(normalizedColumnName);
if (field != null) {
return field;
}
// 2. 尝试下划线转驼峰后匹配
String camelCaseName = StringUtil.underScoreToCamel(normalizedColumnName);
field = fieldMap.get(camelCaseName.toLowerCase());
if (field != null) {
return field;
}
// 3. 在类中直接查找
try {
field = clazz.getDeclaredField(columnName);
return field;
} catch (NoSuchFieldException e) {
// 4. 尝试驼峰命名查找
try {
field = clazz.getDeclaredField(camelCaseName);
return field;
} catch (NoSuchFieldException ex) {
// 5. 遍历所有字段,尝试模糊匹配
Field[] fields = clazz.getDeclaredFields();
for (Field f : fields) {
if (f.getName().equalsIgnoreCase(columnName) ||
f.getName().equalsIgnoreCase(camelCaseName) ||
StringUtil.camelToUnderScore(f.getName()).equalsIgnoreCase(columnName)) {
return f;
}
}
}
}
return null;
}
/**
* 设置字段值
*/
private void setFieldValue(Object obj, Field field, ResultSet rs, int columnIndex) throws Exception {
try {
// 优先使用setter方法
String setMethodName = "set" +
field.getName().substring(0, 1).toUpperCase() +
field.getName().substring(1);
Method setMethod = field.getDeclaringClass().getDeclaredMethod(setMethodName, field.getType());
JdbcParamUtil.setFieldValue(setMethod, obj, rs, columnIndex, field.getType());
} catch (NoSuchMethodException e) {
// 如果没有setter方法,直接设置字段
field.setAccessible(true);
JdbcParamUtil.setFieldValueDirect(field, obj, rs, columnIndex, field.getType());
}
}
}
package com.rongx.utils;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.*;
import java.util.Date;
import java.util.Map;
/**
* JDBC参数赋值工具类
*/
public class JdbcParamUtil {
public static void setParamValue(PreparedStatement ps, int index, Object value) throws SQLException {
if (value == null) {
ps.setNull(index, Types.VARCHAR);
} else if (value instanceof String) {
ps.setString(index, (String) value);
} else if (value instanceof Integer) {
ps.setInt(index, (Integer) value);
} else if (value instanceof Long) {
ps.setLong(index, (Long) value);
} else if (value instanceof Double) {
ps.setDouble(index, (Double) value);
} else if (value instanceof Float) {
ps.setFloat(index, (Float) value);
} else if (value instanceof Boolean) {
ps.setBoolean(index, (Boolean) value);
} else if (value instanceof Date) {
ps.setTimestamp(index, new Timestamp(((Date) value).getTime()));
} else if (value instanceof java.sql.Date) {
ps.setDate(index, (java.sql.Date) value);
} else if (value instanceof Timestamp) {
ps.setTimestamp(index, (Timestamp) value);
} else {
ps.setString(index, value.toString());
}
}
public static void setPreparedStatementParams(PreparedStatement ps,
Map<Integer, String> paramMap,
Object obj) {
paramMap.forEach((index, fieldName) -> {
try {
Object value = ReflectUtil.getFieldValueByGetMethod(obj, fieldName);
setParamValue(ps, index, value);
} catch (Exception e) {
throw new RuntimeException("给SQL参数赋值失败:字段" + fieldName, e);
}
});
}
public static void setFieldValue(Method setMethod, Object obj,
ResultSet rs, int columnIndex,
Class<?> fieldType) throws Exception {
if (fieldType == String.class) {
setMethod.invoke(obj, rs.getString(columnIndex));
} else if (fieldType == Integer.class || fieldType == int.class) {
setMethod.invoke(obj, rs.getInt(columnIndex));
} else if (fieldType == Long.class || fieldType == long.class) {
setMethod.invoke(obj, rs.getLong(columnIndex));
} else if (fieldType == Double.class || fieldType == double.class) {
setMethod.invoke(obj, rs.getDouble(columnIndex));
} else if (fieldType == Float.class || fieldType == float.class) {
setMethod.invoke(obj, rs.getFloat(columnIndex));
} else if (fieldType == Boolean.class || fieldType == boolean.class) {
setMethod.invoke(obj, rs.getBoolean(columnIndex));
} else if (fieldType == Date.class) {
java.sql.Date sqlDate = rs.getDate(columnIndex);
if (sqlDate != null) {
setMethod.invoke(obj, new Date(sqlDate.getTime()));
} else {
setMethod.invoke(obj, (Date) null);
}
} else if (fieldType == java.sql.Date.class) {
setMethod.invoke(obj, rs.getDate(columnIndex));
} else if (fieldType == Timestamp.class) {
setMethod.invoke(obj, rs.getTimestamp(columnIndex));
} else {
setMethod.invoke(obj, rs.getObject(columnIndex));
}
}
public static void setFieldValueDirect(Field field, Object obj,
ResultSet rs, int columnIndex,
Class<?> fieldType) throws Exception {
if (fieldType == String.class) {
field.set(obj, rs.getString(columnIndex));
} else if (fieldType == Integer.class || fieldType == int.class) {
field.set(obj, rs.getInt(columnIndex));
} else if (fieldType == Long.class || fieldType == long.class) {
field.set(obj, rs.getLong(columnIndex));
} else if (fieldType == Double.class || fieldType == double.class) {
field.set(obj, rs.getDouble(columnIndex));
} else if (fieldType == Float.class || fieldType == float.class) {
field.set(obj, rs.getFloat(columnIndex));
} else if (fieldType == Boolean.class || fieldType == boolean.class) {
field.set(obj, rs.getBoolean(columnIndex));
} else if (fieldType == Date.class) {
java.sql.Date sqlDate = rs.getDate(columnIndex);
if (sqlDate != null) {
field.set(obj, new Date(sqlDate.getTime()));
}
} else if (fieldType == java.sql.Date.class) {
field.set(obj, rs.getDate(columnIndex));
} else if (fieldType == Timestamp.class) {
field.set(obj, rs.getTimestamp(columnIndex));
} else {
field.set(obj, rs.getObject(columnIndex));
}
}
}
package com.rongx.utils;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* 反射工具类
*/
public class ReflectUtil {
public static Object getFieldValueByGetMethod(Object obj, String fieldName) throws Exception {
String getMethodName = "get" +
fieldName.substring(0, 1).toUpperCase() +
fieldName.substring(1);
Method getMethod = obj.getClass().getMethod(getMethodName);
return getMethod.invoke(obj);
}
public static Object getFieldValue(Object obj, String fieldName) throws Exception {
try {
return getFieldValueByGetMethod(obj, fieldName);
} catch (NoSuchMethodException e) {
Class<?> clazz = obj.getClass();
try {
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
return field.get(obj);
} catch (NoSuchFieldException ex1) {
String camelName = StringUtil.underScoreToCamel(fieldName);
if (!camelName.equals(fieldName)) {
try {
Field field = clazz.getDeclaredField(camelName);
field.setAccessible(true);
return field.get(obj);
} catch (NoSuchFieldException ex2) {
// 忽略
}
}
String underscoreName = StringUtil.camelToUnderScore(fieldName);
if (!underscoreName.equals(fieldName)) {
try {
Field field = clazz.getDeclaredField(underscoreName);
field.setAccessible(true);
return field.get(obj);
} catch (NoSuchFieldException ex3) {
// 忽略
}
}
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
if (field.getName().equalsIgnoreCase(fieldName) ||
StringUtil.camelToUnderScore(field.getName()).equalsIgnoreCase(fieldName) ||
StringUtil.underScoreToCamel(field.getName()).equalsIgnoreCase(fieldName)) {
field.setAccessible(true);
return field.get(obj);
}
}
throw new NoSuchFieldException("字段 " + fieldName + " 在类 " + clazz.getName() + " 中不存在");
}
}
}
public static void setEntityId(Object obj, long generatedId) {
try {
Field idField = null;
try {
idField = obj.getClass().getDeclaredField("id");
} catch (NoSuchFieldException e) {
Field[] fields = obj.getClass().getDeclaredFields();
for (Field field : fields) {
if (field.getName().equalsIgnoreCase("id") ||
field.getName().toLowerCase().contains("id")) {
idField = field;
break;
}
}
if (idField == null) {
throw new NoSuchFieldException("实体类不存在id字段或类似字段");
}
}
idField.setAccessible(true);
if (idField.getType() == int.class || idField.getType() == Integer.class) {
idField.set(obj, (int) generatedId);
} else if (idField.getType() == long.class || idField.getType() == Long.class) {
idField.set(obj, generatedId);
} else {
idField.set(obj, generatedId);
}
} catch (NoSuchFieldException e) {
throw new RuntimeException("实体类不存在id字段,无法赋值自增主键", e);
} catch (IllegalAccessException e) {
throw new RuntimeException("id字段访问权限不足,无法赋值自增主键", e);
} catch (Exception e) {
throw new RuntimeException("给实体赋值主键失败", e);
}
}
public static Object instantiateObject(String resultType) throws Exception {
Class<?> resultClass = Class.forName(resultType);
Constructor<?> constructor = resultClass.getDeclaredConstructor();
return constructor.newInstance();
}
}
package com.rongx.utils;
import java.util.HashMap;
import java.util.Map;
/**
* SQL参数解析工具类
*/
public class SqlParamParserUtil {
public static Map<Integer, String> parseParamMap(String originSql) {
Map<Integer, String> paramMap = new HashMap<>();
String sql = originSql;
int index = 1;
while (sql.indexOf("#") >= 0) {
int begin = sql.indexOf("#") + 2;
int end = sql.indexOf("}");
paramMap.put(index++, sql.substring(begin, end).trim());
sql = sql.substring(end + 1);
}
return paramMap;
}
}
package com.rongx.utils;
/**
* SQL解析器
* 职责:解析SQL语句,替换参数占位符
*/
public class SqlParserUtil {
/**
* 解析SQL,将#{参数}替换为?
*/
public static String parseSql(String originalSql) {
if (originalSql == null || originalSql.isEmpty()) {
return originalSql;
}
return originalSql.replaceAll("#\\{[a-zA-Z0-9_\\$]*}", "?");
}
}
package com.rongx.utils;
/**
* 字符串工具类
*/
public class StringUtil {
public static String underScoreToCamel(String underscoreName) {
if (underscoreName == null || underscoreName.isEmpty()) {
return underscoreName;
}
StringBuilder result = new StringBuilder();
boolean nextUpper = false;
for (int i = 0; i < underscoreName.length(); i++) {
char c = underscoreName.charAt(i);
if (c == '_') {
nextUpper = true;
} else {
if (nextUpper) {
result.append(Character.toUpperCase(c));
nextUpper = false;
} else {
result.append(c);
}
}
}
return result.toString();
}
public static String camelToUnderScore(String camelName) {
if (camelName == null || camelName.isEmpty()) {
return camelName;
}
StringBuilder result = new StringBuilder();
for (int i = 0; i < camelName.length(); i++) {
char c = camelName.charAt(i);
if (Character.isUpperCase(c) && i > 0) {
result.append('_');
result.append(Character.toLowerCase(c));
} else {
result.append(c);
}
}
return result.toString();
}
public static boolean isEmpty(String str) {
return str == null || str.trim().isEmpty();
}
}
<?xml version="1.0" encoding="UTF-8"?>
<mapper namespace="CarMapper">
<!-- 插入汽车 -->
<insert id="insertCar" parameterType="com.rongx.entity.Car">
INSERT INTO t_car (car_num, brand, price)
VALUES (#{carNum}, #{brand}, #{price})
</insert>
<!-- 插入汽车并返回自增主键 -->
<insert id="insertCarWithKey" parameterType="com.rongx.entity.Car">
INSERT INTO t_car (car_num, brand, price)
VALUES (#{carNum}, #{brand}, #{price})
</insert>
<!-- 更新汽车信息 -->
<update id="updateCar" parameterType="com.rongx.entity.Car">
UPDATE t_car
SET car_num = #{carNum},
brand = #{brand},
price = #{price}
WHERE id = #{id}
</update>
<!-- 根据ID删除汽车 -->
<delete id="deleteCar" parameterType="java.lang.Integer">
DELETE FROM t_car WHERE id = #{id}
</delete>
<!-- 根据ID查询汽车 -->
<select id="selectCarById" parameterType="java.lang.Integer"
resultType="com.rongx.entity.Car">
SELECT
id,
car_num as carNum,
brand,
price,
create_time as createTime,
update_time as updateTime
FROM t_car
WHERE id = #{id}
</select>
<!-- 查询所有汽车 -->
<select id="selectAllCars" resultType="com.rongx.entity.Car">
SELECT
id,
car_num as carNum,
brand,
price,
create_time as createTime,
update_time as updateTime
FROM t_car
ORDER BY id ASC
</select>
<!-- 根据品牌查询汽车(用于分页测试) -->
<select id="selectCarsByBrand" parameterType="java.lang.String"
resultType="com.rongx.entity.Car">
SELECT
id,
car_num as carNum,
brand,
price,
create_time as createTime,
update_time as updateTime
FROM t_car
WHERE brand = #{brand}
ORDER BY id ASC
</select>
<!-- 批量插入汽车 -->
<insert id="batchInsertCars" parameterType="com.rongx.entity.Car">
INSERT INTO t_car (car_num, brand, price)
VALUES (#{carNum}, #{brand}, #{price})
</insert>
<!-- 批量更新汽车价格 -->
<update id="batchUpdateCars" parameterType="com.rongx.entity.Car">
UPDATE t_car
SET price = #{price}
WHERE id = #{id}
</update>
<!-- 批量删除汽车 -->
<delete id="batchDeleteCars" parameterType="java.lang.Integer">
DELETE FROM t_car WHERE id = #{id}
</delete>
<!-- 多条件查询汽车(基础SQL,不含WHERE) -->
<select id="selectCarsByCondition" resultType="com.rongx.entity.Car">
SELECT
id,
car_num as carNum,
brand,
price,
create_time as createTime,
update_time as updateTime
FROM t_car
</select>
</mapper>
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 环境配置,可以配置多个环境,default指定默认使用哪个环境 -->
<environments default="development">
<environment id="development">
<!-- 事务管理器:JDBC表示使用JDBC的事务管理 -->
<transactionManager type="JDBC"/>
<!-- 数据源:UNPOOLED表示不使用连接池 -->
<dataSource type="UNPOOLED">
<!-- 数据库驱动,MySQL 8.0+使用com.mysql.cj.jdbc.Driver -->
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<!-- 数据库连接URL -->
<property name="url" value="jdbc:mysql://localhost:3306/easy_mybatis_test?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai"/>
<!-- 数据库用户名 -->
<property name="username" value="root"/>
<!-- 数据库密码 -->
<property name="password" value="123456"/>
</dataSource>
<!-- Mapper文件配置 -->
<mappers>
<!-- 可以配置多个mapper文件 -->
<mapper resource="mapper/CarMapper.xml"/>
</mappers>
</environment>
<!-- 可以配置其他环境,比如测试环境、生产环境 -->
<!--
<environment id="test">
<transactionManager type="JDBC"/>
<dataSource type="UNPOOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://test-server:3306/test_db"/>
<property name="username" value="test_user"/>
<property name="password" value="test_password"/>
</dataSource>
<mappers>
<mapper resource="mapper/CarMapper.xml"/>
</mappers>
</environment>
-->
</environments>
</configuration>
package com.rongx.entity;
import java.util.Date;
/**
* 汽车实体类
* 对应数据库表:t_car
*/
public class Car {
private Integer id; // 主键ID
private String carNum; // 车牌号(数据库列名:car_num)
private String brand; // 品牌
private Double price; // 价格
private Date createTime; // 创建时间
private Date updateTime; // 更新时间
// 无参构造函数(必须)
public Car() {
}
// 全参构造函数(可选)
public Car(Integer id, String carNum, String brand, Double price,
Date createTime, Date updateTime) {
this.id = id;
this.carNum = carNum;
this.brand = brand;
this.price = price;
this.createTime = createTime;
this.updateTime = updateTime;
}
// Getter和Setter方法(必须)
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getCarNum() {
return carNum;
}
public void setCarNum(String carNum) {
this.carNum = carNum;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
public Date getUpdateTime() {
return updateTime;
}
public void setUpdateTime(Date updateTime) {
this.updateTime = updateTime;
}
// toString方法(可选,方便调试)
@Override
public String toString() {
return "Car{" +
"id=" + id +
", carNum='" + carNum + '\'' +
", brand='" + brand + '\'' +
", price=" + price +
", createTime=" + (createTime != null ? createTime : "null") +
", updateTime=" + (updateTime != null ? updateTime : "null") +
'}';
}
}
package com.rongx.mapper;
/**
* Car Mapper接口
* 这个接口是可选的,主要用于类型安全的SQL ID引用
*/
public interface CarMapper {
// 基础CRUD操作
String INSERT_CAR = "CarMapper.insertCar";
String UPDATE_CAR = "CarMapper.updateCar";
String DELETE_CAR = "CarMapper.deleteCar";
String SELECT_CAR_BY_ID = "CarMapper.selectCarById";
String SELECT_ALL_CARS = "CarMapper.selectAllCars";
// 批量操作
String BATCH_INSERT_CARS = "CarMapper.batchInsertCars";
String BATCH_UPDATE_CARS = "CarMapper.batchUpdateCars";
String BATCH_DELETE_CARS = "CarMapper.batchDeleteCars";
// 分页查询
String SELECT_CARS_BY_BRAND = "CarMapper.selectCarsByBrand";
// 插入并返回自增主键
String INSERT_CAR_WITH_KEY = "CarMapper.insertCarWithKey";
// 根据条件查询
String SELECT_CARS_BY_CONDITION = "CarMapper.selectCarsByCondition";
}
package com.rongx.test;
import com.rongx.core.*;
import com.rongx.entity.Car;
import com.rongx.resources.Resources;
import com.rongx.result.PageResult;
import org.dom4j.DocumentException;
import java.io.InputStream;
import java.util.*;
/**
* Easy-MyBatis 完整测试类(适配代理架构)
* 测试所有核心功能
*/
public class EasyMybatisTest {
private static SqlSessionFactory sqlSessionFactory;
public static void main(String[] args) {
try {
// 1. 初始化Easy-MyBatis
initEasyMybatis();
System.out.println("========== Easy-MyBatis v0.0.3 完整测试 ==========\n");
// 2. 测试基础CRUD操作
testBasicCRUD();
// 3. 测试批量操作
testBatchOperations();
// 4. 测试分页查询
testPagination();
// 5. 测试插入并返回自增主键
testInsertWithGeneratedKey();
// 6. 测试多条件动态查询
testDynamicConditionQuery();
System.out.println("\n========== 所有测试完成 ==========");
} catch (Exception e) {
System.err.println("测试过程中发生异常:");
e.printStackTrace();
}
}
/**
* 初始化Easy-MyBatis框架
*/
private static void initEasyMybatis() throws DocumentException {
System.out.println("1. 初始化Easy-MyBatis框架...");
// 加载配置文件
String configFile = "mybatis-config.xml";
InputStream inputStream = Resources.getResourcesAsStream(configFile);
if (inputStream == null) {
throw new RuntimeException("配置文件未找到: " + configFile);
}
// 构建SqlSessionFactory
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
System.out.println(" ✓ SqlSessionFactory创建成功");
}
/**
* 测试基础CRUD操作
*/
private static void testBasicCRUD() {
System.out.println("\n2. 测试基础CRUD操作:");
SqlSession sqlSession = null;
try {
// 获取SqlSession
sqlSession = sqlSessionFactory.openSession();
System.out.println(" 1) 获取SqlSession成功");
// ========== 插入测试 ==========
Car newCar = new Car();
newCar.setCarNum("京K55555");
newCar.setBrand("Tesla");
newCar.setPrice(350000.00);
int insertCount = sqlSession.insert("CarMapper.insertCar", newCar);
System.out.println(" 2) 插入单条数据: 影响行数=" + insertCount);
// ========== 查询测试 ==========
// 查询单个对象
Car car = (Car) sqlSession.selectOne("CarMapper.selectCarById", 1);
System.out.println(" 3) 查询单个对象: ID=" + car.getId() +
", 车牌=" + car.getCarNum() +
", 价格=" + car.getPrice());
// 查询列表
List<Object> carList = sqlSession.selectList("CarMapper.selectAllCars", null);
System.out.println(" 4) 查询所有汽车: 共" + carList.size() + "条记录");
// 打印前5条记录
System.out.println(" 前5条记录:");
for (int i = 0; i < Math.min(5, carList.size()); i++) {
Car c = (Car) carList.get(i);
System.out.println(" - " + c.getCarNum() + " | " + c.getBrand() + " | ¥" + c.getPrice());
}
// ========== 更新测试 ==========
car.setPrice(220000.00);
int updateCount = sqlSession.update("CarMapper.updateCar", car);
System.out.println(" 5) 更新数据: 影响行数=" + updateCount);
// 验证更新结果
Car updatedCar = (Car) sqlSession.selectOne("CarMapper.selectCarById", car.getId());
System.out.println(" 6) 验证更新结果: 价格=" + updatedCar.getPrice());
// ========== 删除测试 ==========
// 先插入一条用于删除测试的数据
Car deleteTestCar = new Car();
deleteTestCar.setCarNum("删除测试车牌");
deleteTestCar.setBrand("Test");
deleteTestCar.setPrice(100000.00);
sqlSession.insert("CarMapper.insertCar", deleteTestCar);
// 查询出最后插入的ID(简化处理)
List<Object> allCars = sqlSession.selectList("CarMapper.selectAllCars", null);
if (!allCars.isEmpty()) {
Car lastCar = (Car) allCars.get(allCars.size() - 1);
int deleteCount = sqlSession.delete("CarMapper.deleteCar", lastCar.getId());
System.out.println(" 7) 删除数据: 影响行数=" + deleteCount);
}
// 提交事务
sqlSession.commit();
System.out.println(" 8) 提交事务成功");
System.out.println(" ✓ 基础CRUD测试完成");
} catch (Exception e) {
if (sqlSession != null) {
sqlSession.rollback();
}
System.err.println("基础CRUD测试失败: " + e.getMessage());
e.printStackTrace();
} finally {
if (sqlSession != null) {
sqlSession.close();
}
}
}
/**
* 测试批量操作
*/
private static void testBatchOperations() {
System.out.println("\n3. 测试批量操作:");
SqlSession sqlSession = null;
try {
sqlSession = sqlSessionFactory.openSession();
// ========== 批量插入测试 ==========
List<Car> carsToInsert = Arrays.asList(
createCar("京L11111", "Nissan", 120000.00),
createCar("京L22222", "Nissan", 130000.00),
createCar("京L33333", "Nissan", 140000.00)
);
int[] insertResults = sqlSession.batchInsert("CarMapper.batchInsertCars", carsToInsert);
System.out.println(" 1) 批量插入3条数据: 结果数组=" + Arrays.toString(insertResults));
// 提交事务
sqlSession.commit();
// ========== 批量更新测试 ==========
// 先查询出所有Nissan汽车
List<Object> nissanCars = sqlSession.selectList("CarMapper.selectCarsByBrand", "Nissan");
System.out.println(" 2) 查询到Nissan品牌汽车: " + nissanCars.size() + "辆");
if (!nissanCars.isEmpty()) {
// 批量涨价10%
List<Car> carsToUpdate = new ArrayList<>();
for (Object obj : nissanCars) {
Car car = (Car) obj;
Car updateCar = new Car();
updateCar.setId(car.getId());
updateCar.setPrice(car.getPrice() * 1.1); // 涨价10%
carsToUpdate.add(updateCar);
}
int[] updateResults = sqlSession.batchUpdateOrDelete("CarMapper.batchUpdateCars", carsToUpdate);
System.out.println(" 3) 批量更新价格(+10%): 结果数组=" + Arrays.toString(updateResults));
sqlSession.commit();
}
// ========== 批量删除测试 ==========
// 重新查询Nissan汽车获取最新数据
nissanCars = sqlSession.selectList("CarMapper.selectCarsByBrand", "Nissan");
if (!nissanCars.isEmpty()) {
// 获取要删除的ID列表,使用Map包装参数
List<Map<String, Object>> deleteParams = new ArrayList<>();
for (Object obj : nissanCars) {
Car car = (Car) obj;
Map<String, Object> param = new HashMap<>();
param.put("id", car.getId());
deleteParams.add(param);
}
int[] deleteResults = sqlSession.batchUpdateOrDelete("CarMapper.batchDeleteCars", deleteParams);
System.out.println(" 4) 批量删除: 结果数组=" + Arrays.toString(deleteResults));
sqlSession.commit();
}
System.out.println(" ✓ 批量操作测试完成");
} catch (Exception e) {
if (sqlSession != null) {
sqlSession.rollback();
}
System.err.println("批量操作测试失败: " + e.getMessage());
e.printStackTrace();
} finally {
if (sqlSession != null) {
sqlSession.close();
}
}
}
/**
* 测试分页查询
*/
private static void testPagination() {
System.out.println("\n4. 测试分页查询:");
SqlSession sqlSession = null;
try {
sqlSession = sqlSessionFactory.openSession();
// ========== 分页查询Toyota品牌汽车 ==========
System.out.println(" 查询Toyota品牌汽车,每页2条:");
// 第1页
PageResult<Car> page1 = (PageResult<Car>) sqlSession.selectPage(
"CarMapper.selectCarsByBrand", "Toyota", 1, 2);
printPageResult(page1, 1);
// 第2页
PageResult<Car> page2 = (PageResult<Car>) sqlSession.selectPage(
"CarMapper.selectCarsByBrand", "Toyota", 2, 2);
printPageResult(page2, 2);
// 测试边界情况:超出范围的页码
PageResult<Car> pageOver = (PageResult<Car>) sqlSession.selectPage(
"CarMapper.selectCarsByBrand", "Toyota", 10, 2);
System.out.println(" 3) 查询第10页(超出范围): 数据条数=" + pageOver.getData().size());
System.out.println(" ✓ 分页查询测试完成");
} catch (Exception e) {
System.err.println("分页查询测试失败: " + e.getMessage());
e.printStackTrace();
} finally {
if (sqlSession != null) {
sqlSession.close();
}
}
}
/**
* 打印分页结果
*/
private static void printPageResult(PageResult<Car> pageResult, int pageNum) {
System.out.println(" " + pageNum + ") 第" + pageResult.getPageNum() + "页: " +
"每页" + pageResult.getPageSize() + "条, " +
"共" + pageResult.getTotal() + "条, " +
"共" + pageResult.getTotalPages() + "页");
List<Car> data = pageResult.getData();
if (data.isEmpty()) {
System.out.println(" 本页无数据");
} else {
System.out.println(" 本页数据 (" + data.size() + "条):");
for (Car car : data) {
System.out.println(" - ID:" + car.getId() + " " +
car.getCarNum() + " | ¥" + car.getPrice());
}
}
}
/**
* 测试插入并返回自增主键
*/
private static void testInsertWithGeneratedKey() {
System.out.println("\n5. 测试插入并返回自增主键:");
SqlSession sqlSession = null;
try {
sqlSession = sqlSessionFactory.openSession();
// 创建新车(不设置ID)
Car newCar = new Car();
newCar.setCarNum("京M99999");
newCar.setBrand("BYD");
newCar.setPrice(210000.00);
System.out.println(" 插入前: ID=" + newCar.getId() + ", 车牌=" + newCar.getCarNum());
// 插入并获取自增主键
Car insertedCar = (Car) sqlSession.insertWithGeneratedKey(
"CarMapper.insertCarWithKey", newCar);
System.out.println(" 插入后: ID=" + insertedCar.getId() + ", 车牌=" + insertedCar.getCarNum());
// 验证数据是否插入成功
Car verifyCar = (Car) sqlSession.selectOne(
"CarMapper.selectCarById", insertedCar.getId());
if (verifyCar != null && verifyCar.getCarNum().equals("京M99999")) {
System.out.println(" 验证: 数据插入成功,自增ID=" + verifyCar.getId());
}
sqlSession.commit();
System.out.println(" ✓ 自增主键测试完成");
} catch (Exception e) {
if (sqlSession != null) {
sqlSession.rollback();
}
System.err.println("自增主键测试失败: " + e.getMessage());
e.printStackTrace();
} finally {
if (sqlSession != null) {
sqlSession.close();
}
}
}
/**
* 测试多条件动态查询
*/
private static void testDynamicConditionQuery() {
System.out.println("\n6. 测试多条件动态查询:");
SqlSession sqlSession = null;
try {
sqlSession = sqlSessionFactory.openSession();
// ========== 测试1:单个条件查询 ==========
Map<String, Object> condition1 = new HashMap<>();
condition1.put("brand", "Honda");
List<Object> hondaCars = sqlSession.selectByCondition(
"CarMapper.selectCarsByCondition", condition1, Car.class);
System.out.println(" 1) 查询品牌=Honda: " + hondaCars.size() + "条记录");
// ========== 测试2:多个条件查询 ==========
Map<String, Object> condition2 = new HashMap<>();
condition2.put("brand", "BMW");
condition2.put("price", 350000.00);
List<Object> bmwCars = sqlSession.selectByCondition(
"CarMapper.selectCarsByCondition", condition2, Car.class);
System.out.println(" 2) 查询品牌=BMW且价格=350000: " + bmwCars.size() + "条记录");
if (!bmwCars.isEmpty()) {
Car car = (Car) bmwCars.get(0);
System.out.println(" 找到: " + car.getCarNum() + " | ¥" + car.getPrice());
}
// ========== 测试3:无结果查询 ==========
Map<String, Object> condition3 = new HashMap<>();
condition3.put("brand", "NotExistBrand");
condition3.put("price", 999999.00);
List<Object> noResult = sqlSession.selectByCondition(
"CarMapper.selectCarsByCondition", condition3, Car.class);
System.out.println(" 3) 查询不存在的品牌: " + noResult.size() + "条记录");
System.out.println(" ✓ 动态条件查询测试完成");
} catch (Exception e) {
System.err.println("动态条件查询测试失败: " + e.getMessage());
e.printStackTrace();
} finally {
if (sqlSession != null) {
sqlSession.close();
}
}
}
/**
* 辅助方法:创建Car对象(用于插入)
*/
private static Car createCar(String carNum, String brand, double price) {
Car car = new Car();
car.setCarNum(carNum);
car.setBrand(brand);
car.setPrice(price);
return car;
}
/**
* 辅助方法:创建Car对象(用于更新,包含ID)
*/
private static Car createCarWithId(Integer id, String carNum, String brand, double price) {
Car car = new Car();
car.setId(id);
car.setCarNum(carNum);
car.setBrand(brand);
car.setPrice(price);
return car;
}
}
package com.rongx.test;
import com.rongx.core.*;
import com.rongx.entity.Car;
import com.rongx.resources.Resources;
import com.rongx.result.PageResult;
import java.io.InputStream;
import java.util.*;
//version 0.0.3
public class ProxyPatternTest {
public static void main(String[] args) {
SqlSession sqlSession = null;
try {
// 1. 构建SqlSessionFactory
InputStream inputStream = Resources.getResourcesAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 2. 获取SqlSession代理对象
sqlSession = sqlSessionFactory.openSession();
// 3. 执行CRUD操作
testCRUD(sqlSession);
// 4. 执行批量操作
testBatchOperations(sqlSession);
// 5. 执行分页查询
testPagination(sqlSession);
sqlSession.commit();
System.out.println("所有操作执行成功");
} catch (Exception e) {
if (sqlSession != null) {
sqlSession.rollback();
}
System.err.println("操作失败: " + e.getMessage());
e.printStackTrace();
} finally {
if (sqlSession != null) {
sqlSession.close();
}
}
}
private static void testCRUD(SqlSession sqlSession) {
// 插入
Car car = new Car();
car.setCarNum("京K88888");
car.setBrand("Tesla");
car.setPrice(300000.00);
int count = sqlSession.insert("CarMapper.insertCar", car);
System.out.println("插入记录数: " + count);
// 查询
Car result = (Car) sqlSession.selectOne("CarMapper.selectCarById", 1);
System.out.println("查询结果: ID=" + result.getId() + ", 车牌=" + result.getCarNum());
// 更新
result.setPrice(280000.00);
int updateCount = sqlSession.update("CarMapper.updateCar", result);
System.out.println("更新记录数: " + updateCount);
}
private static void testBatchOperations(SqlSession sqlSession) {
List<Car> cars = Arrays.asList(
createCar("京L11111", "Nissan", 120000.00),
createCar("京L22222", "Nissan", 130000.00)
);
int[] results = sqlSession.batchInsert("CarMapper.batchInsertCars", cars);
System.out.println("批量插入结果: " + Arrays.toString(results));
}
private static void testPagination(SqlSession sqlSession) {
PageResult<?> pageResult = sqlSession.selectPage(
"CarMapper.selectCarsByBrand", "Toyota", 1, 2);
System.out.println("分页查询结果:");
System.out.println("总记录数: " + pageResult.getTotal());
System.out.println("当前页数据: " + pageResult.getData().size() + "条");
List<Car> cars = (List<Car>) pageResult.getData();
for (Car car : cars) {
System.out.println(" - " + car.getCarNum() + " | " + car.getBrand() + " | ¥" + car.getPrice());
}
}
private static Car createCar(String carNum, String brand, double price) {
Car car = new Car();
car.setCarNum(carNum);
car.setBrand(brand);
car.setPrice(price);
return car;
}
}
Easy-Mybatis v0.0.3 全类逐行精解与架构深度剖析
一、架构全景图:从单体到模块化的革命性重构
1.1 架构演进对比
v0.0.2 单体架构(上帝类模式)
DefaultSqlSession (1200+行)
├── 事务管理方法
├── 基础CRUD方法
├── 批量操作方法
├── 复杂查询方法
├── 存储过程方法
└── 工具方法混杂其中
问题:类膨胀、职责混乱、难以维护、无法扩展
v0.0.3 模块化架构(职责分离模式)
┌─────────────────────────────────────────────────────┐
│ SqlSession (接口) │
│ 统一的操作协议 │
└───────────────┬─────────────────────────────────────┘
│ 实现
┌───────────────▼─────────────────────────────────────┐
│ SqlSessionProxy (代理门面) │
│ 路由所有请求到对应处理器,统一异常处理 │
└─────┬─────────┬─────────┬─────────────┬────────────┘
│ │ │ │
▼ ▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│BaseCRUD │ │ Batch │ │ Query │ │Procedure│
│ Handler │ │ Handler │ │ Handler │ │ Handler │
└────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘
│ │ │ │
▼ ▼ ▼ ▼
┌─────────┐ ┌─────────────────────┐ ┌─────────┐
│JdbcExec │ │ ResultSetMapper │ │ JDBC │
│ -utor │ │ 智能对象映射器 │ │ 原生调用 │
└────┬────┘ └─────────────────────┘ └────┬────┘
│ │
└───────────────────────────────────┘
┌─────────────────────┐
│ TransactionManager│
│ 事务管理核心 │
└──────────┬──────────┘
│
┌──────────▼──────────┐
│ DataSource │
│ 数据源抽象 │
└─────────────────────┘
1.2 核心设计思想
1. 单一职责原则(SRP) :每个类只做一件事 2. 开闭原则(OCP) :对扩展开放,对修改关闭 3. 依赖倒置原则(DIP) :依赖抽象,不依赖具体 4. 接口隔离原则(ISP) :接口粒度适中,不多不少 5. 迪米特法则(LoD):最少知识原则
二、配置与工厂层:框架的启动引擎
2.1 SqlSessionFactoryBuilder(配置解析器)
位置 :com.rongx.core.SqlSessionFactoryBuilder 定位:框架的启动入口,配置文件的解析中枢
/**
* 建造者模式的核心实现
* 职责:将XML配置文件转换为内存中的对象网络
*/
public class SqlSessionFactoryBuilder {
// 关键方法:构建工厂的完整流程
public SqlSessionFactory build(InputStream inputStream) throws DocumentException {
// 1. 解析XML文档结构
SAXReader saxReader = new SAXReader();
Document document = saxReader.read(inputStream);
// 2. 定位默认环境配置(支持多环境)
Element environmentsElt = (Element) document.selectSingleNode("/configuration/environments");
String defaultEnv = environmentsElt.attributeValue("default");
Element environmentElt = (Element) document.selectSingleNode(
"/configuration/environments/environment[@id='" + defaultEnv + "']");
// 3. 构建数据源(依赖注入的基础)
Element dataSourceElt = environmentElt.element("dataSource");
DataSource dataSource = getDataSource(dataSourceElt);
// 4. 构建事务管理器(事务控制的起点)
Element transactionManagerElt = environmentElt.element("transactionManager");
TransactionManager transactionManager = getTransactionManager(transactionManagerElt, dataSource);
// 5. 解析所有Mapper文件,构建SQL语句映射仓库
Element mappers = environmentsElt.element("mappers");
Map<String, EasyMybatisMappedStatement> mappedStatements = getMappedStatements(mappers);
// 6. 装配所有组件,创建工厂(控制反转的体现)
return new SqlSessionFactory(transactionManager, mappedStatements);
}
// 数据源工厂方法:支持多种数据源类型
private DataSource getDataSource(Element dataSourceElt) {
// 收集所有数据源属性(key-value形式)
Map<String, String> dataSourceMap = new HashMap<>();
dataSourceElt.elements().forEach(propertyElt -> {
dataSourceMap.put(propertyElt.attributeValue("name"),
propertyElt.attributeValue("value"));
});
String dataSourceType = dataSourceElt.attributeValue("type").toUpperCase();
// 策略模式:根据类型选择不同数据源实现
if ("UNPOOLED".equals(dataSourceType)) {
// 非池化数据源:每次请求创建新连接
return new EasyMybatisUNPOOLEDDataSource(
dataSourceMap.get("driver"),
dataSourceMap.get("url"),
dataSourceMap.get("username"),
dataSourceMap.get("password")
);
}
// 扩展点:POOLED、JNDI等数据源类型
throw new RuntimeException("不支持的数据源类型: " + dataSourceType);
}
// Mapper解析器:将XML映射转换为内存对象
private Map<String, EasyMybatisMappedStatement> getMappedStatements(Element mappers) {
Map<String, EasyMybatisMappedStatement> mappedStatements = new HashMap<>();
mappers.elements().forEach(mapperElt -> {
try {
String resource = mapperElt.attributeValue("resource");
// 递归解析Mapper文件
SAXReader saxReader = new SAXReader();
Document document = saxReader.read(Resources.getResourcesAsStream(resource));
Element mapper = (Element) document.selectSingleNode("/mapper");
String namespace = mapper.attributeValue("namespace"); // 命名空间作为一级分类
// 遍历所有SQL语句标签
mapper.elements().forEach(sqlMapper -> {
// 提取SQL的五元组信息
String sqlId = sqlMapper.attributeValue("id");
String sql = sqlMapper.getTextTrim();
String parameterType = sqlMapper.attributeValue("parameterType");
String resultType = sqlMapper.attributeValue("resultType");
String sqlType = sqlMapper.getName().toLowerCase(); // insert/update/delete/select
// 构建映射语句对象
EasyMybatisMappedStatement ms = new EasyMybatisMappedStatement(
sqlId, resultType, sql, parameterType, sqlType);
// 关键:生成全局唯一SQL标识符 "namespace.id"
mappedStatements.put(namespace + "." + sqlId, ms);
});
} catch (DocumentException e) {
throw new RuntimeException("解析Mapper文件失败: " + mapperElt.attributeValue("resource"), e);
}
});
return mappedStatements;
}
}
设计精妙之处:
-
建造者模式:分步骤构建复杂对象SqlSessionFactory
-
策略模式:支持多种数据源和事务管理器类型
-
递归解析:主配置文件和Mapper文件的统一解析
-
命名空间设计:避免SQL ID冲突,支持模块化
2.2 SqlSessionFactory(会话工厂)
位置 :com.rongx.core.SqlSessionFactory 定位:应用的门面,SqlSession的生产者
/**
* 工厂模式的核心实现
* 职责:管理核心组件,生产SqlSession实例
*/
public class SqlSessionFactory {
// v0.0.3关键改进:引入代理工厂
private final SqlSessionProxyFactory proxyFactory;
/**
* 构造函数:接收所有核心组件
* 体现了依赖注入思想
*/
public SqlSessionFactory(TransactionManager transactionManager,
Map<String, EasyMybatisMappedStatement> mappedStatements) {
// 创建代理工厂,将对象创建逻辑进一步分离
this.proxyFactory = new SqlSessionProxyFactory(transactionManager, mappedStatements);
}
/**
* 工厂方法:创建SqlSession实例
* 每次调用返回新的会话(非线程安全设计)
*/
public SqlSession openSession() {
// 通过代理工厂创建代理对象
return proxyFactory.createProxy();
}
// Getter方法:提供组件访问(符合迪米特法则)
public TransactionManager getTransactionManager() {
return proxyFactory.getTransactionManager();
}
public Map<String, EasyMybatisMappedStatement> getMappedStatements() {
return proxyFactory.getMappedStatements();
}
}
与v0.0.2的核心差异:
// v0.0.2 直接创建具体实现
public SqlSession openSession() {
transactionManager.openConnection();
return new DefaultSqlSession(transactionManager, mappedStatements);
}
// v0.0.3 通过代理工厂创建
public SqlSession openSession() {
return proxyFactory.createProxy();
}
优势:
-
隐藏实现细节:客户端不知道SqlSessionProxy的存在
-
扩展点:可以轻松切换不同的SqlSession实现
-
生命周期管理:工厂可以管理会话的创建和销毁
三、代理与门面层:统一的服务入口
3.1 SqlSessionProxyFactory(代理工厂)
位置 :com.rongx.proxy.SqlSessionProxyFactory 定位:代理对象的制造工厂
/**
* 代理对象的专门工厂
* 职责:封装SqlSessionProxy的创建逻辑
*/
public class SqlSessionProxyFactory {
// 代理对象需要的所有依赖
private final TransactionManager transactionManager;
private final Map<String, EasyMybatisMappedStatement> mappedStatements;
public SqlSessionProxyFactory(TransactionManager transactionManager,
Map<String, EasyMybatisMappedStatement> mappedStatements) {
this.transactionManager = transactionManager;
this.mappedStatements = mappedStatements;
}
/**
* 创建代理对象的关键步骤
*/
public SqlSession createProxy() {
// 1. 打开数据库连接(延迟初始化)
transactionManager.openConnection();
// 2. 创建代理对象,注入所有处理器
return new SqlSessionProxy(transactionManager, mappedStatements);
}
}
设计思考:为什么需要这个额外的工厂?
-
单一职责:SqlSessionFactory负责整体装配,SqlSessionProxyFactory负责代理创建
-
可测试性:可以单独测试代理创建逻辑
-
未来扩展:可以创建不同类型的代理(如缓存代理、日志代理等)
3.2 SqlSessionProxy(代理门面)
位置 :com.rongx.proxy.SqlSessionProxy 定位:统一请求入口,职责分发中心
/**
* 代理模式 + 门面模式的经典实现
* 职责:1. 统一API入口 2. 路由请求 3. 统一异常处理
*/
public class SqlSessionProxy implements SqlSession {
// 核心:持有所有处理器实例
private final BaseCRUDHandler crudHandler;
private final BatchOperationHandler batchHandler;
private final QueryHandler queryHandler;
private final ProcedureHandler procedureHandler;
private final TransactionManager transactionManager;
/**
* 构造函数:组装所有处理器(依赖注入的完美体现)
*/
public SqlSessionProxy(TransactionManager transactionManager,
Map<String, EasyMybatisMappedStatement> mappedStatements) {
this.transactionManager = transactionManager;
// 初始化处理器网络(微服务架构的雏形)
this.crudHandler = new BaseCRUDHandler(transactionManager, mappedStatements);
this.batchHandler = new BatchOperationHandler(transactionManager, mappedStatements);
this.queryHandler = new QueryHandler(transactionManager, mappedStatements);
this.procedureHandler = new ProcedureHandler(transactionManager);
}
// ========== 事务管理:直接委托 ==========
@Override
public void commit() {
transactionManager.commit(); // 直接调用,无需路由
}
@Override
public void rollback() {
transactionManager.rollback();
}
@Override
public void close() {
transactionManager.close();
}
// ========== CRUD操作:路由到CRUD处理器 ==========
@Override
public int insert(String sqlId, Object obj) {
return crudHandler.insert(sqlId, obj);
}
@Override
public int update(String sqlId, Object obj) {
return crudHandler.update(sqlId, obj);
}
@Override
public int delete(String sqlId, Object parameterObj) {
return crudHandler.delete(sqlId, parameterObj);
}
// ========== 查询操作:路由到查询处理器 ==========
@Override
public Object selectOne(String sqlId, Object parameterObj) {
return queryHandler.selectOne(sqlId, parameterObj);
}
@Override
public List<Object> selectList(String sqlId, Object parameterObj) {
return queryHandler.selectList(sqlId, parameterObj);
}
@Override
public PageResult<?> selectPage(String sqlId, Object parameterObj, int pageNum, int pageSize) {
return queryHandler.selectPage(sqlId, parameterObj, pageNum, pageSize);
}
@Override
public List<Object> selectByCondition(String sqlId, Map<String, Object> conditionMap, Class<?> resultType) {
return queryHandler.selectByCondition(sqlId, conditionMap, resultType);
}
// ========== 批量操作:路由到批量处理器 ==========
@Override
public int[] batchInsert(String sqlId, List<?> objList) {
return batchHandler.batchInsert(sqlId, objList);
}
@Override
public int[] batchUpdateOrDelete(String sqlId, List<?> paramList) {
return batchHandler.batchUpdateOrDelete(sqlId, paramList);
}
// ========== 特殊操作:路由到对应处理器 ==========
@Override
public Object insertWithGeneratedKey(String sqlId, Object obj) {
return crudHandler.insertWithGeneratedKey(sqlId, obj);
}
@Override
public Map<String, Object> callProcedure(String procedureName, Map<String, Object> paramMap) {
return procedureHandler.callProcedure(procedureName, paramMap);
}
}
架构价值:
-
路由表模式:每个方法都是简单的路由指令
-
零业务逻辑:代理本身不处理任何业务,只负责转发
-
统一管控点:可以在这里添加统一的日志、监控、性能统计
-
接口稳定性:内部处理器重构不影响对外API
四、处理器层:专业化的功能模块
4.1 BaseCRUDHandler(基础操作处理器)
位置 :com.rongx.handler.BaseCRUDHandler 定位:增删改操作的专业处理中心
/**
* 基础CRUD操作的专门处理器
* 职责:处理insert、update、delete及自增主键插入
*/
public class BaseCRUDHandler {
// 依赖:事务管理器 + SQL映射仓库 + JDBC执行器
private final TransactionManager transactionManager;
private final Map<String, EasyMybatisMappedStatement> mappedStatements;
private final JdbcExecutor jdbcExecutor; // v0.0.3新增:执行器分离
public BaseCRUDHandler(TransactionManager transactionManager,
Map<String, EasyMybatisMappedStatement> mappedStatements) {
this.transactionManager = transactionManager;
this.mappedStatements = mappedStatements;
this.jdbcExecutor = new JdbcExecutor(transactionManager);
}
/**
* 插入操作的标准化流程
*/
public int insert(String sqlId, Object obj) {
// 1. 从仓库获取SQL配置
EasyMybatisMappedStatement ms = mappedStatements.get(sqlId);
// 2. 工具类解析SQL(#{param} -> ?)
String sql = SqlParserUtil.parseSql(ms.getSql());
// 3. 工具类解析参数映射(位置 -> 字段名)
Map<Integer, String> paramMap = SqlParamParserUtil.parseParamMap(ms.getSql());
// 4. 委托给JdbcExecutor执行(职责分离)
return jdbcExecutor.executeUpdate(sql, paramMap, obj);
}
/**
* 插入并返回自增主键(关键技术点)
*/
public Object insertWithGeneratedKey(String sqlId, Object obj) {
EasyMybatisMappedStatement ms = mappedStatements.get(sqlId);
String sql = SqlParserUtil.parseSql(ms.getSql());
Map<Integer, String> paramMap = SqlParamParserUtil.parseParamMap(ms.getSql());
PreparedStatement ps = null;
try {
Connection conn = transactionManager.getConnection();
// 关键:告诉JDBC需要返回自增主键
ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
// 参数绑定
JdbcParamUtil.setPreparedStatementParams(ps, paramMap, obj);
// 执行插入
ps.executeUpdate();
// 获取自增主键结果集(通常只有一列一行)
try (var generatedKeys = ps.getGeneratedKeys()) {
if (generatedKeys.next()) {
long generatedId = generatedKeys.getLong(1);
// 反射将主键设置回对象
ReflectUtil.setEntityId(obj, generatedId);
}
}
return obj; // 返回已设置主键的对象
} catch (Exception e) {
throw new RuntimeException("插入数据并回填主键失败,SQL ID:" + sqlId, e);
} finally {
closeStatement(ps); // 资源清理
}
}
// update和delete方法类似,都委托给JdbcExecutor
public int update(String sqlId, Object obj) {
EasyMybatisMappedStatement ms = mappedStatements.get(sqlId);
String sql = SqlParserUtil.parseSql(ms.getSql());
Map<Integer, String> paramMap = SqlParamParserUtil.parseParamMap(ms.getSql());
return jdbcExecutor.executeUpdate(sql, paramMap, obj);
}
public int delete(String sqlId, Object parameterObj) {
EasyMybatisMappedStatement ms = mappedStatements.get(sqlId);
String sql = SqlParserUtil.parseSql(ms.getSql());
Map<Integer, String> paramMap = SqlParamParserUtil.parseParamMap(ms.getSql());
// 智能参数处理:支持单参数和对象参数
if (paramMap.size() == 1 && parameterObj != null) {
// 单参数直接设置
return jdbcExecutor.executeUpdateWithSingleParam(sql, 1, parameterObj);
} else if (parameterObj != null) {
// 对象参数反射设置
return jdbcExecutor.executeUpdate(sql, paramMap, parameterObj);
} else {
// 无参数执行
return jdbcExecutor.executeUpdateWithoutParam(sql);
}
}
}
设计亮点:
-
执行器分离:JdbcExecutor专门处理JDBC操作
-
工具类协作:SqlParserUtil、SqlParamParserUtil、JdbcParamUtil各司其职
-
智能参数处理:自动识别单参数和对象参数场景
-
资源安全管理:确保PreparedStatement正确关闭
4.2 BatchOperationHandler(批量操作处理器)
位置 :com.rongx.handler.BatchOperationHandler 定位:高性能批量操作的专业处理器
/**
* 批量操作的专业处理器
* 职责:处理批量插入、更新、删除,提供事务保障
*/
public class BatchOperationHandler {
private final TransactionManager transactionManager;
private final Map<String, EasyMybatisMappedStatement> mappedStatements;
/**
* 批量插入的核心实现
*/
public int[] batchInsert(String sqlId, List<?> objList) {
// 边界检查
if (objList == null || objList.isEmpty()) {
return new int[0];
}
EasyMybatisMappedStatement ms = mappedStatements.get(sqlId);
String sql = SqlParserUtil.parseSql(ms.getSql());
Map<Integer, String> paramMap = SqlParamParserUtil.parseParamMap(ms.getSql());
PreparedStatement ps = null;
try {
Connection conn = transactionManager.getConnection();
// 关键:开启手动事务,确保批量操作的原子性
boolean originalAutoCommit = conn.getAutoCommit();
conn.setAutoCommit(false);
ps = conn.prepareStatement(sql);
// 遍历所有对象,添加到批处理
for (Object obj : objList) {
JdbcParamUtil.setPreparedStatementParams(ps, paramMap, obj);
ps.addBatch(); // 添加到批处理队列
}
// 一次性执行所有批处理操作
int[] result = ps.executeBatch();
// 提交事务
conn.commit();
// 恢复原始自动提交设置
conn.setAutoCommit(originalAutoCommit);
return result;
} catch (SQLException e) {
// 异常时回滚事务
rollbackTransaction();
throw new RuntimeException("批量插入数据失败,SQL ID:" + sqlId, e);
} finally {
closeStatement(ps);
}
}
/**
* 通用的批量更新/删除
* 支持多种参数类型:Map或实体对象
*/
public int[] batchUpdateOrDelete(String sqlId, List<?> paramList) {
if (paramList == null || paramList.isEmpty()) {
return new int[0];
}
EasyMybatisMappedStatement ms = mappedStatements.get(sqlId);
String sql = SqlParserUtil.parseSql(ms.getSql());
Map<Integer, String> paramMap = SqlParamParserUtil.parseParamMap(ms.getSql());
PreparedStatement ps = null;
try {
Connection conn = transactionManager.getConnection();
boolean originalAutoCommit = conn.getAutoCommit();
conn.setAutoCommit(false);
ps = conn.prepareStatement(sql);
for (Object param : paramList) {
// 智能参数处理:支持Map和实体对象
if (param instanceof Map) {
Map<String, Object> paramMapObj = (Map<String, Object>) param;
setParametersFromMap(ps, paramMap, paramMapObj);
} else {
JdbcParamUtil.setPreparedStatementParams(ps, paramMap, param);
}
ps.addBatch();
}
int[] result = ps.executeBatch();
conn.commit();
conn.setAutoCommit(originalAutoCommit);
return result;
} catch (Exception e) {
rollbackTransaction();
throw new RuntimeException("批量更新/删除数据失败,SQL ID:" + sqlId, e);
} finally {
closeStatement(ps);
}
}
/**
* 从Map中设置参数(灵活性扩展)
*/
private void setParametersFromMap(PreparedStatement ps,
Map<Integer, String> paramMap,
Map<String, Object> paramMapObj) throws Exception {
paramMap.forEach((index, fieldName) -> {
try {
Object value = paramMapObj.get(fieldName);
JdbcParamUtil.setParamValue(ps, index, value);
} catch (Exception e) {
throw new RuntimeException("设置批量参数失败,字段:" + fieldName, e);
}
});
}
/**
* 统一的事务回滚处理
*/
private void rollbackTransaction() {
try {
transactionManager.rollback();
} catch (Exception e) {
System.err.println("事务回滚失败: " + e.getMessage());
}
}
}
性能优化关键:
-
事务手动控制:批量操作前关闭自动提交,完成后统一提交
-
批处理API :使用
addBatch()和executeBatch()减少网络往返 -
参数类型自适应:支持实体对象和Map两种参数形式
-
异常安全:确保异常时事务回滚
4.3 QueryHandler(查询处理器)
位置 :com.rongx.handler.QueryHandler 定位:所有查询操作的专业处理器
/**
* 查询操作的专门处理器
* 职责:处理各种查询场景,支持分页、条件查询等
*/
public class QueryHandler {
private final TransactionManager transactionManager;
private final Map<String, EasyMybatisMappedStatement> mappedStatements;
private final ResultSetMapper resultSetMapper; // v0.0.3关键:结果映射器
public QueryHandler(TransactionManager transactionManager,
Map<String, EasyMybatisMappedStatement> mappedStatements) {
this.transactionManager = transactionManager;
this.mappedStatements = mappedStatements;
this.resultSetMapper = new ResultSetMapper(); // 创建专门的结果映射器
}
/**
* 查询单个对象
*/
public Object selectOne(String sqlId, Object parameterObj) {
EasyMybatisMappedStatement ms = mappedStatements.get(sqlId);
String sql = SqlParserUtil.parseSql(ms.getSql());
PreparedStatement ps = null;
ResultSet rs = null;
try {
ps = transactionManager.getConnection().prepareStatement(sql);
if (parameterObj != null) {
JdbcParamUtil.setParamValue(ps, 1, parameterObj);
}
rs = ps.executeQuery();
if (rs.next()) {
// 委托给ResultSetMapper进行智能映射
return resultSetMapper.mapResultSetToObject(rs, ms.getResultType());
}
return null; // 无结果返回null
} catch (Exception e) {
throw new RuntimeException("查询单个对象失败,SQL ID:" + sqlId, e);
} finally {
closeResources(rs, ps); // 统一资源关闭
}
}
/**
* 分页查询的完整实现(企业级功能)
*/
public PageResult<?> selectPage(String sqlId, Object parameterObj, int pageNum, int pageSize) {
// 参数校验与修正
if (pageNum < 1) pageNum = 1;
if (pageSize < 1) pageSize = 10;
int offset = (pageNum - 1) * pageSize; // MySQL分页公式
EasyMybatisMappedStatement ms = mappedStatements.get(sqlId);
String originSql = ms.getSql();
// 1. 查询总条数(使用子查询确保条件一致)
String countSql = "SELECT COUNT(*) FROM (" + originSql + ") AS t_count";
String countPreparedSql = SqlParserUtil.parseSql(countSql);
long total = 0;
PreparedStatement countPs = null;
ResultSet countRs = null;
try {
countPs = transactionManager.getConnection().prepareStatement(countPreparedSql);
if (parameterObj != null) {
JdbcParamUtil.setParamValue(countPs, 1, parameterObj);
}
countRs = countPs.executeQuery();
if (countRs.next()) {
total = countRs.getLong(1);
}
} catch (SQLException e) {
throw new RuntimeException("查询分页总条数失败,SQL ID:" + sqlId, e);
} finally {
closeResources(countRs, countPs);
}
// 2. 查询分页数据(MySQL LIMIT语法)
String pageSql = originSql + " LIMIT " + offset + "," + pageSize;
String pagePreparedSql = SqlParserUtil.parseSql(pageSql);
List<Object> dataList = new ArrayList<>();
PreparedStatement pagePs = null;
ResultSet rs = null;
try {
pagePs = transactionManager.getConnection().prepareStatement(pagePreparedSql);
if (parameterObj != null) {
JdbcParamUtil.setParamValue(pagePs, 1, parameterObj);
}
rs = pagePs.executeQuery();
while (rs.next()) {
// 使用ResultSetMapper进行对象映射
Object obj = resultSetMapper.mapResultSetToObject(rs, ms.getResultType());
dataList.add(obj);
}
// 3. 封装分页结果
return new PageResult<>(dataList, pageNum, pageSize, total);
} catch (Exception e) {
throw new RuntimeException("查询分页数据失败,SQL ID:" + sqlId, e);
} finally {
closeResources(rs, pagePs);
}
}
/**
* 多条件动态查询(WHERE条件动态拼接)
*/
public List<Object> selectByCondition(String sqlId,
Map<String, Object> conditionMap,
Class<?> resultType) {
if (conditionMap == null || conditionMap.isEmpty()) {
throw new RuntimeException("多条件查询失败:查询条件不能为空");
}
EasyMybatisMappedStatement ms = mappedStatements.get(sqlId);
String baseSql = ms.getSql(); // 基础SQL不含WHERE
// 动态构建WHERE子句
StringBuilder sqlBuilder = new StringBuilder(baseSql);
sqlBuilder.append(" WHERE 1=1 "); // 巧妙设计,避免AND开头语法错误
List<Object> paramValues = new ArrayList<>();
// 遍历条件,拼接AND条件
for (Map.Entry<String, Object> entry : conditionMap.entrySet()) {
sqlBuilder.append(" AND ").append(entry.getKey()).append(" = ? ");
paramValues.add(entry.getValue()); // 收集参数值
}
String finalSql = SqlParserUtil.parseSql(sqlBuilder.toString());
List<Object> resultList = new ArrayList<>();
PreparedStatement ps = null;
ResultSet rs = null;
try {
ps = transactionManager.getConnection().prepareStatement(finalSql);
// 设置动态参数
for (int i = 0; i < paramValues.size(); i++) {
JdbcParamUtil.setParamValue(ps, i + 1, paramValues.get(i));
}
rs = ps.executeQuery();
while (rs.next()) {
// 使用指定的resultType进行映射
Object obj = resultSetMapper.mapResultSetToObject(rs, resultType.getName());
resultList.add(obj);
}
return resultList;
} catch (Exception e) {
throw new RuntimeException("多条件动态查询失败,SQL ID:" + sqlId, e);
} finally {
closeResources(rs, ps);
}
}
}
查询优化设计:
-
分页双重查询:先查总数,再查数据,确保准确性
-
动态SQL构建:灵活的条件拼接,避免SQL注入
-
专门映射器:ResultSetMapper负责复杂的对象映射
-
资源安全管理:统一的资源关闭机制
4.4 ProcedureHandler(存储过程处理器)
位置 :com.rongx.handler.ProcedureHandler 定位:存储过程调用的专门处理器
/**
* 存储过程调用的专门处理器
* 职责:调用数据库存储过程,处理IN/OUT参数
*/
public class ProcedureHandler {
private final TransactionManager transactionManager;
public ProcedureHandler(TransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
/**
* 调用存储过程的核心实现
* 参数规则:value=null表示OUT参数
*/
public Map<String, Object> callProcedure(String procedureName, Map<String, Object> paramMap) {
if (paramMap == null) paramMap = new HashMap<>();
Connection conn = transactionManager.getConnection();
// 构建存储过程调用SQL: {call proc_name(?, ?, ?)}
StringBuilder procSql = new StringBuilder("{call ").append(procedureName).append("(");
for (int i = 0; i < paramMap.size(); i++) {
procSql.append("?");
if (i < paramMap.size() - 1) {
procSql.append(",");
}
}
procSql.append(")}");
try (CallableStatement cs = conn.prepareCall(procSql.toString())) {
List<String> outParamNames = new ArrayList<>(); // 记录OUT参数名
int paramIndex = 1;
// 第一遍:注册IN/OUT参数
for (Map.Entry<String, Object> entry : paramMap.entrySet()) {
String paramName = entry.getKey();
Object paramValue = entry.getValue();
if (paramValue == null) {
// OUT参数:注册参数类型(默认INTEGER)
cs.registerOutParameter(paramIndex, Types.INTEGER);
outParamNames.add(paramName);
} else {
// IN参数:设置参数值
JdbcParamUtil.setParamValue(cs, paramIndex, paramValue);
}
paramIndex++;
}
// 执行存储过程
cs.execute();
// 第二遍:获取OUT参数值
paramIndex = 1;
for (String outParamName : outParamNames) {
paramMap.put(outParamName, cs.getObject(paramIndex));
paramIndex++;
}
return paramMap; // 返回包含OUT参数值的Map
} catch (SQLException e) {
throw new RuntimeException("调用存储过程失败,存储过程名:" + procedureName, e);
}
}
}
存储过程处理特色:
-
IN/OUT参数智能识别:null值表示OUT参数
-
两阶段处理:先注册参数,执行后再获取OUT值
-
标准化调用 :统一使用
{call proc_name(...)}语法 -
类型扩展性:支持注册不同JDBC类型的OUT参数
五、核心组件层:框架的基石
5.1 JdbcExecutor(JDBC执行器)
位置 :com.rongx.core.JdbcExecutor 定位:JDBC操作的统一执行引擎
/**
* JDBC操作的执行引擎
* 职责:封装所有JDBC执行逻辑,提供统一执行接口
*/
public class JdbcExecutor {
private final TransactionManager transactionManager;
public JdbcExecutor(TransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
/**
* 通用的更新操作执行
*/
public int executeUpdate(String sql, Map<Integer, String> paramMap, Object obj) {
PreparedStatement ps = null;
try {
Connection conn = transactionManager.getConnection();
ps = conn.prepareStatement(sql);
// 使用工具类进行参数绑定
JdbcParamUtil.setPreparedStatementParams(ps, paramMap, obj);
return ps.executeUpdate();
} catch (Exception e) {
throw new RuntimeException("执行SQL更新失败: " + sql, e);
} finally {
closeStatement(ps); // 确保资源释放
}
}
/**
* 单参数更新执行(性能优化)
*/
public int executeUpdateWithSingleParam(String sql, int paramIndex, Object paramValue) {
PreparedStatement ps = null;
try {
Connection conn = transactionManager.getConnection();
ps = conn.prepareStatement(sql);
// 直接设置参数,避免反射开销
JdbcParamUtil.setParamValue(ps, paramIndex, paramValue);
return ps.executeUpdate();
} catch (Exception e) {
throw new RuntimeException("执行SQL更新失败: " + sql, e);
} finally {
closeStatement(ps);
}
}
/**
* 无参数更新执行
*/
public int executeUpdateWithoutParam(String sql) {
PreparedStatement ps = null;
try {
Connection conn = transactionManager.getConnection();
ps = conn.prepareStatement(sql);
return ps.executeUpdate();
} catch (Exception e) {
throw new RuntimeException("执行SQL更新失败: " + sql, e);
} finally {
closeStatement(ps);
}
}
/**
* 统一的Statement关闭(防御性编程)
*/
private void closeStatement(PreparedStatement ps) {
if (ps != null) {
try {
ps.close();
} catch (SQLException e) {
// 日志记录但不抛出异常(避免掩盖主要异常)
System.err.println("关闭PreparedStatement失败: " + e.getMessage());
}
}
}
}
执行器设计哲学:
-
统一执行入口:所有JDBC操作通过这里执行
-
异常统一转换:SQLException转为RuntimeException
-
资源安全保障:确保Statement正确关闭
-
性能分级优化:区分单参数、多参数、无参数场景
5.2 ResultSetMapper(结果集映射器)
位置 :com.rongx.result.ResultSetMapper 定位:ResultSet到Java对象的智能映射引擎
/**
* 结果集映射的专业引擎
* 职责:将ResultSet智能映射到Java对象,支持多种命名规范
*/
public class ResultSetMapper {
/**
* 核心映射方法:智能匹配字段
*/
public Object mapResultSetToObject(ResultSet rs, String resultType) throws Exception {
// 1. 反射创建对象实例
Object obj = ReflectUtil.instantiateObject(resultType);
Class<?> clazz = Class.forName(resultType);
// 2. 获取结果集元数据
ResultSetMetaData metaData = rs.getMetaData();
int columnCount = metaData.getColumnCount();
// 3. 构建字段缓存(性能优化)
Map<String, Field> fieldMap = createFieldMap(clazz);
// 4. 遍历所有列,智能匹配字段
for (int i = 1; i <= columnCount; i++) {
String columnName = metaData.getColumnName(i);
String columnLabel = metaData.getColumnLabel(i);
// 优先使用别名,没有则用列名
String fieldName = columnLabel != null ? columnLabel : columnName;
// 智能查找匹配的字段
Field field = findMatchingField(fieldMap, fieldName, clazz);
if (field != null) {
setFieldValue(obj, field, rs, i);
}
}
return obj;
}
/**
* 创建字段映射缓存(关键性能优化)
*/
private Map<String, Field> createFieldMap(Class<?> clazz) {
Map<String, Field> fieldMap = new HashMap<>();
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
String fieldName = field.getName();
// 存储原始字段名(小写)
fieldMap.put(fieldName.toLowerCase(), field);
// 存储驼峰转下划线后的字段名
String underscoreName = StringUtil.camelToUnderScore(fieldName);
fieldMap.put(underscoreName.toLowerCase(), field);
// 扩展点:可以添加更多命名规范
}
return fieldMap;
}
/**
* 智能字段匹配算法(支持多种命名规范)
*/
private Field findMatchingField(Map<String, Field> fieldMap, String columnName, Class<?> clazz) {
String normalizedColumnName = columnName.toLowerCase();
// 策略1:直接匹配(忽略大小写)
Field field = fieldMap.get(normalizedColumnName);
if (field != null) return field;
// 策略2:下划线转驼峰后匹配
String camelCaseName = StringUtil.underScoreToCamel(normalizedColumnName);
field = fieldMap.get(camelCaseName.toLowerCase());
if (field != null) return field;
// 策略3:直接反射查找
try {
field = clazz.getDeclaredField(columnName);
return field;
} catch (NoSuchFieldException e) {
// 策略4:驼峰命名查找
try {
field = clazz.getDeclaredField(camelCaseName);
return field;
} catch (NoSuchFieldException ex) {
// 策略5:模糊匹配(兼容性兜底)
Field[] fields = clazz.getDeclaredFields();
for (Field f : fields) {
if (f.getName().equalsIgnoreCase(columnName) ||
f.getName().equalsIgnoreCase(camelCaseName) ||
StringUtil.camelToUnderScore(f.getName()).equalsIgnoreCase(columnName)) {
return f;
}
}
}
}
return null; // 未找到匹配字段
}
/**
* 字段值设置:优先使用setter方法
*/
private void setFieldValue(Object obj, Field field, ResultSet rs, int columnIndex) throws Exception {
try {
// 优先使用setter方法(符合JavaBean规范)
String setMethodName = "set" +
field.getName().substring(0, 1).toUpperCase() +
field.getName().substring(1);
Method setMethod = field.getDeclaringClass().getDeclaredMethod(setMethodName, field.getType());
JdbcParamUtil.setFieldValue(setMethod, obj, rs, columnIndex, field.getType());
} catch (NoSuchMethodException e) {
// 没有setter方法,直接设置字段(兼容性)
field.setAccessible(true);
JdbcParamUtil.setFieldValueDirect(field, obj, rs, columnIndex, field.getType());
}
}
}
映射算法创新:
-
五级匹配策略:从精确到模糊,确保高匹配率
-
字段缓存优化:避免重复反射获取字段
-
命名规范自适应:支持驼峰、下划线及其转换
-
setter优先原则:符合JavaBean规范,同时提供直接字段访问兜底
六、基础设施层:工具与支持组件
6.1 工具类体系
6.1.1 JdbcParamUtil(JDBC参数工具)
/**
* JDBC参数处理的瑞士军刀
* 职责:处理所有JDBC参数绑定和类型转换
*/
public class JdbcParamUtil {
/**
* 通用参数设置:支持多种Java类型到JDBC类型的转换
*/
public static void setParamValue(PreparedStatement ps, int index, Object value) throws SQLException {
if (value == null) {
ps.setNull(index, Types.VARCHAR); // 统一NULL处理
} else if (value instanceof String) {
ps.setString(index, (String) value);
} else if (value instanceof Integer) {
ps.setInt(index, (Integer) value);
} else if (value instanceof Long) {
ps.setLong(index, (Long) value);
} else if (value instanceof Double) {
ps.setDouble(index, (Double) value);
} else if (value instanceof Float) {
ps.setFloat(index, (Float) value);
} else if (value instanceof Boolean) {
ps.setBoolean(index, (Boolean) value);
} else if (value instanceof Date) {
// Date转Timestamp(数据库datetime类型)
ps.setTimestamp(index, new Timestamp(((Date) value).getTime()));
} else if (value instanceof java.sql.Date) {
ps.setDate(index, (java.sql.Date) value);
} else if (value instanceof Timestamp) {
ps.setTimestamp(index, (Timestamp) value);
} else {
// 兜底策略:toString转换
ps.setString(index, value.toString());
}
}
}
类型转换表:
| Java类型 | JDBC方法 | 说明 |
|---|---|---|
| null | setNull(Types.VARCHAR) | 统一VARCHAR类型NULL |
| String | setString() | 字符串 |
| Integer | setInt() | 整型 |
| Long | setLong() | 长整型 |
| Double | setDouble() | 双精度 |
| Float | setFloat() | 单精度 |
| Boolean | setBoolean() | 布尔 |
| Date | setTimestamp() | 日期时间 |
| java.sql.Date | setDate() | SQL日期 |
| Timestamp | setTimestamp() | 时间戳 |
| 其他 | setString(toString()) | 兜底转换 |
6.1.2 ReflectUtil(反射工具)
/**
* 反射操作的智能工具
* 职责:封装所有反射操作,提供智能字段访问
*/
public class ReflectUtil {
/**
* 智能获取字段值:支持getter方法和直接字段访问
*/
public static Object getFieldValue(Object obj, String fieldName) throws Exception {
try {
// 优先使用getter方法
return getFieldValueByGetMethod(obj, fieldName);
} catch (NoSuchMethodException e) {
Class<?> clazz = obj.getClass();
// 多策略字段查找
try {
// 策略1:直接查找
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
return field.get(obj);
} catch (NoSuchFieldException ex1) {
// 策略2:下划线转驼峰查找
String camelName = StringUtil.underScoreToCamel(fieldName);
if (!camelName.equals(fieldName)) {
try {
Field field = clazz.getDeclaredField(camelName);
field.setAccessible(true);
return field.get(obj);
} catch (NoSuchFieldException ex2) {
// 忽略
}
}
// 策略3:驼峰转下划线查找
String underscoreName = StringUtil.camelToUnderScore(fieldName);
if (!underscoreName.equals(fieldName)) {
try {
Field field = clazz.getDeclaredField(underscoreName);
field.setAccessible(true);
return field.get(obj);
} catch (NoSuchFieldException ex3) {
// 忽略
}
}
// 策略4:模糊匹配(兼容性)
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
if (field.getName().equalsIgnoreCase(fieldName) ||
StringUtil.camelToUnderScore(field.getName()).equalsIgnoreCase(fieldName) ||
StringUtil.underScoreToCamel(field.getName()).equalsIgnoreCase(fieldName)) {
field.setAccessible(true);
return field.get(obj);
}
}
throw new NoSuchFieldException("字段 " + fieldName + " 不存在");
}
}
}
}
6.1.3 StringUtil(字符串工具)
/**
* 命名规范转换专家
* 职责:处理驼峰命名和下划线命名的相互转换
*/
public class StringUtil {
/**
* 下划线转驼峰:user_name -> userName
*/
public static String underScoreToCamel(String underscoreName) {
if (underscoreName == null || underscoreName.isEmpty()) {
return underscoreName;
}
StringBuilder result = new StringBuilder();
boolean nextUpper = false;
for (int i = 0; i < underscoreName.length(); i++) {
char c = underscoreName.charAt(i);
if (c == '_') {
nextUpper = true;
} else {
if (nextUpper) {
result.append(Character.toUpperCase(c));
nextUpper = false;
} else {
result.append(c);
}
}
}
return result.toString();
}
/**
* 驼峰转下划线:userName -> user_name
*/
public static String camelToUnderScore(String camelName) {
if (camelName == null || camelName.isEmpty()) {
return camelName;
}
StringBuilder result = new StringBuilder();
for (int i = 0; i < camelName.length(); i++) {
char c = camelName.charAt(i);
if (Character.isUpperCase(c) && i > 0) {
result.append('_');
result.append(Character.toLowerCase(c));
} else {
result.append(c);
}
}
return result.toString();
}
}
6.2 核心模型类
6.2.1 EasyMybatisMappedStatement(SQL映射模型)
/**
* SQL映射的核心模型类
* 职责:封装一个SQL语句的所有元信息
*/
public class EasyMybatisMappedStatement {
// SQL五元组:完整描述一个SQL操作
private String sqlId; // SQL唯一标识: namespace.id
private String resultType; // 返回类型全限定名
private String sql; // 原始SQL(含#{param})
private String parameterType; // 参数类型全限定名
private String sqlType; // SQL类型: select/insert/update/delete
// 示例:CarMapper.selectCarById
// sqlId: "CarMapper.selectCarById"
// resultType: "com.rongx.entity.Car"
// sql: "SELECT * FROM t_car WHERE id = #{id}"
// parameterType: "java.lang.Integer"
// sqlType: "select"
}
6.2.2 PageResult(分页结果模型)
/**
* 分页查询的结果封装
* 职责:封装分页数据及相关分页信息
*/
public class PageResult<T> {
private List<T> data; // 当前页数据
private int pageNum; // 当前页码(从1开始)
private int pageSize; // 每页条数
private long total; // 总记录数
private int totalPages; // 总页数(自动计算)
/**
* 构造函数:自动计算总页数
*/
public PageResult(List<T> data, int pageNum, int pageSize, long total) {
this.data = data;
this.pageNum = pageNum;
this.pageSize = pageSize;
this.total = total;
// 关键:向上取整计算总页数
this.totalPages = (int) Math.ceil((double) total / pageSize);
}
}
6.3 数据源与事务管理
6.3.1 EasyMybatisUNPOOLEDDataSource(非池化数据源)
/**
* 非池化数据源实现
* 职责:每次请求创建新连接,简单但性能一般
*/
public class EasyMybatisUNPOOLEDDataSource implements javax.sql.DataSource {
private String url;
private String username;
private String password;
/**
* 构造函数:自动注册JDBC驱动
*/
public EasyMybatisUNPOOLEDDataSource(String driver, String url,
String username, String password) {
try {
// 动态加载驱动类
Class.forName(driver);
} catch (ClassNotFoundException e) {
throw new RuntimeException("驱动加载失败: " + driver, e);
}
this.url = url;
this.username = username;
this.password = password;
}
@Override
public Connection getConnection() throws SQLException {
// 每次创建新连接
return DriverManager.getConnection(url, username, password);
}
}
6.3.2 EasyMybatisJDBCTransaction(JDBC事务管理器)
/**
* JDBC事务管理器
* 职责:管理数据库连接和事务生命周期
*/
public class EasyMybatisJDBCTransaction implements TransactionManager {
private Connection conn;
private DataSource dataSource;
private boolean autoCommit;
private boolean isClosed = false; // 状态标志
/**
* 增强的提交方法:增加状态检查
*/
public void commit() {
if (isClosed) {
System.err.println("警告:连接已关闭,无法提交事务");
return;
}
try {
if (conn != null && !conn.isClosed()) {
conn.commit();
}
} catch (SQLException e) {
throw new RuntimeException("提交事务失败", e);
}
}
/**
* 智能开启连接
*/
@Override
public void openConnection() {
try {
if (conn == null || conn.isClosed()) {
this.conn = dataSource.getConnection();
this.conn.setAutoCommit(this.autoCommit);
isClosed = false; // 重置状态
}
} catch (SQLException e) {
throw new RuntimeException("打开数据库连接失败", e);
}
}
}
七、类关系网络与协作流程
7.1 类关系图谱
┌─────────────────────┐
│ SqlSession接口 │
│ (定义操作契约) │
└──────────┬──────────┘
│ 实现
┌───────────────────────┼───────────────────────┐
│ │ │
┌────────▼─────────┐ ┌────────▼─────────┐ ┌────────▼─────────┐
│ v0.0.2架构 │ │ 代理工厂层 │ │ v0.0.3架构 │
│ DefaultSqlSession│ │ SqlSessionProxy │ │ 处理器网络 │
│ (上帝类) │ │ (路由中心) │ │ │
└──────────────────┘ └────────┬──────────┘ └────────┬──────────┘
│ │
└───────────┬───────────┘
│ 委托
┌─────────┬─────────┼─────────┬─────────┐
│ │ │ │ │
┌────▼────┐┌────▼────┐┌────▼────┐┌────▼────┐
│ Base ││ Batch ││ Query ││Procedure│
│ CRUD ││ Handler ││ Handler ││ Handler │
│ Handler │└────┬────┘└────┬────┘└────┬────┘
└────┬────┘ │ │ │
│ │ │ │
┌───────────┼──────────┼──────────┼──────────┼───────────┐
│ │ │ │ │ │
┌────▼────┐ ┌────▼────┐ ┌────▼────┐ ┌────▼────┐ ┌────▼────┐
│JdbcExec │ │ResultSet│ │SqlParser│ │Reflect │ │String │
│ -utor │ │ Mapper │ │ Util │ │ Util │ │ Util │
└────┬────┘ └─────────┘ └────┬────┘ └────┬────┘ └─────────┘
│ │ │
└───────────────────────┼──────────┘
│
┌───────▼───────┐
│ Transaction │
│ Manager │
└───────┬───────┘
│
┌───────▼───────┐
│ DataSource │
│ (数据源抽象) │
└───────────────┘
7.2 核心协作流程示例
场景:用户调用 sqlSession.selectOne("CarMapper.selectCarById", 1)
第1步:代理路由
// SqlSessionProxy.selectOne()
public Object selectOne(String sqlId, Object parameterObj) {
return queryHandler.selectOne(sqlId, parameterObj); // 路由到QueryHandler
}
第2步:查询处理器准备
// QueryHandler.selectOne()
public Object selectOne(String sqlId, Object parameterObj) {
// 1. 从映射仓库获取SQL配置
EasyMybatisMappedStatement ms = mappedStatements.get(sqlId);
// 2. 解析SQL:#{id} -> ?
String sql = SqlParserUtil.parseSql(ms.getSql());
// 3. 创建PreparedStatement并设置参数
PreparedStatement ps = connection.prepareStatement(sql);
JdbcParamUtil.setParamValue(ps, 1, parameterObj);
// 4. 执行查询
ResultSet rs = ps.executeQuery();
// 5. 结果映射(关键步骤)
if (rs.next()) {
return resultSetMapper.mapResultSetToObject(rs, ms.getResultType());
}
}
第3步:智能结果映射
// ResultSetMapper.mapResultSetToObject()
public Object mapResultSetToObject(ResultSet rs, String resultType) throws Exception {
// 1. 创建实体对象
Object obj = ReflectUtil.instantiateObject(resultType);
// 2. 获取结果集元数据
ResultSetMetaData metaData = rs.getMetaData();
// 3. 遍历所有列,智能匹配字段
for (int i = 1; i <= metaData.getColumnCount(); i++) {
String columnName = metaData.getColumnName(i); // 例如: "car_num"
// 4. 智能查找字段(支持car_num -> carNum转换)
Field field = findMatchingField(fieldMap, columnName, clazz);
// 5. 设置字段值
setFieldValue(obj, field, rs, i);
}
return obj;
}
第4步:字段值设置
// ResultSetMapper.setFieldValue()
private void setFieldValue(Object obj, Field field, ResultSet rs, int columnIndex) throws Exception {
// 优先尝试setter方法
String setMethodName = "set" + field.getName().substring(0, 1).toUpperCase()
+ field.getName().substring(1);
try {
Method setMethod = field.getDeclaringClass().getDeclaredMethod(setMethodName, field.getType());
// 使用JdbcParamUtil进行类型安全的赋值
JdbcParamUtil.setFieldValue(setMethod, obj, rs, columnIndex, field.getType());
} catch (NoSuchMethodException e) {
// 没有setter,直接字段赋值
field.setAccessible(true);
JdbcParamUtil.setFieldValueDirect(field, obj, rs, columnIndex, field.getType());
}
}
7.3 数据流动全景
用户请求
↓
SqlSessionProxy (路由分发)
↓
对应Handler (业务处理)
↓
1. 从mappedStatements获取SQL配置
2. 使用SqlParserUtil解析SQL
3. 使用SqlParamParserUtil解析参数映射
↓
JdbcExecutor (执行SQL)
↓
1. 从TransactionManager获取Connection
2. 创建PreparedStatement
3. 使用JdbcParamUtil绑定参数
4. 执行SQL操作
↓
ResultSetMapper (结果映射) ← ReflectUtil + StringUtil
↓
返回结果对象
八、设计模式深度解析
8.1 代理模式(Proxy Pattern)
应用场景 :SqlSessionProxy 代理实际处理器
实现方式:
public class SqlSessionProxy implements SqlSession {
// 持有真实处理器的引用
private final BaseCRUDHandler crudHandler;
private final QueryHandler queryHandler;
// ...
// 代理方法:转发请求
public Object selectOne(String sqlId, Object parameterObj) {
return queryHandler.selectOne(sqlId, parameterObj);
}
}
设计价值:
-
透明扩展:可以在不修改客户端代码的情况下添加新功能
-
访问控制:可以控制对真实对象的访问
-
延迟加载:可以延迟真实对象的创建
-
日志记录:可以方便地添加日志、监控等横切关注点
8.2 工厂模式(Factory Pattern)
应用场景 :SqlSessionFactory 和 SqlSessionProxyFactory
实现方式:
// 抽象工厂
public class SqlSessionFactory {
private final SqlSessionProxyFactory proxyFactory;
public SqlSession openSession() {
return proxyFactory.createProxy(); // 工厂方法
}
}
// 具体工厂
public class SqlSessionProxyFactory {
public SqlSession createProxy() {
return new SqlSessionProxy(...); // 创建具体产品
}
}
设计价值:
-
封装创建逻辑:将对象创建与使用分离
-
支持产品族:可以创建相关或依赖对象
-
符合开闭原则:新增产品类型不影响现有代码
8.3 策略模式(Strategy Pattern)
应用场景:各种Handler处理不同的数据库操作
实现方式:
// 策略接口:SqlSession定义操作协议
// 具体策略:各个Handler实现具体操作
// 上下文:SqlSessionProxy选择使用哪个策略
public Object selectOne(...) {
return queryHandler.selectOne(...); // 选择查询策略
}
public int insert(...) {
return crudHandler.insert(...); // 选择插入策略
}
设计价值:
-
算法独立:每种操作算法独立变化
-
避免条件判断:用多态代替if-else
-
易于扩展:新增策略不影响现有代码
8.4 门面模式(Facade Pattern)
应用场景 :SqlSessionProxy 作为统一入口
实现方式:
public class SqlSessionProxy implements SqlSession {
// 封装复杂的子系统
private final BaseCRUDHandler crudHandler;
private final BatchOperationHandler batchHandler;
// ...
// 提供简化接口
public int insert(...) { ... }
public Object selectOne(...) { ... }
}
设计价值:
-
简化接口:为复杂子系统提供简单入口
-
解耦:客户端与子系统解耦
-
层次化:提供更合理的系统层次
8.5 模板方法模式(Template Method Pattern)
应用场景:各个Handler中的标准处理流程
实现方式:
public Object selectOne(String sqlId, Object parameterObj) {
// 固定步骤1:获取配置
EasyMybatisMappedStatement ms = mappedStatements.get(sqlId);
// 固定步骤2:解析SQL
String sql = SqlParserUtil.parseSql(ms.getSql());
// 固定步骤3:准备Statement
PreparedStatement ps = connection.prepareStatement(sql);
// 固定步骤4:执行查询
ResultSet rs = ps.executeQuery();
// 固定步骤5:映射结果
return resultSetMapper.mapResultSetToObject(rs, ms.getResultType());
}
设计价值:
-
代码复用:固定算法骨架,子类实现细节
-
反向控制:父类控制流程,子类提供实现
-
扩展性好:可以通过子类扩展算法
九、性能优化深度分析
9.1 反射性能优化策略
问题:反射调用比直接调用慢10-100倍
优化方案:
// 优化前:每次循环都反射
while (rs.next()) {
for (int i = 1; i <= columnCount; i++) {
String columnName = rsmd.getColumnName(i);
Field field = clazz.getDeclaredField(columnName); // 每次反射
// ...
}
}
// 优化后:缓存字段信息
public class ResultSetMapper {
private Map<String, Field> createFieldMap(Class<?> clazz) {
Map<String, Field> fieldMap = new HashMap<>();
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
// 缓存多种命名形式
fieldMap.put(field.getName().toLowerCase(), field);
fieldMap.put(StringUtil.camelToUnderScore(field.getName()).toLowerCase(), field);
}
return fieldMap; // 一次反射,多次使用
}
private Field findMatchingField(Map<String, Field> fieldMap, String columnName) {
// 从缓存中查找,O(1)复杂度
return fieldMap.get(columnName.toLowerCase());
}
}
性能对比:
-
无缓存:N次反射调用,N次异常处理
-
有缓存:1次反射调用 + N次Map查找
9.2 批处理性能优化
批量插入 vs 单条插入:
// 方式1:循环单条插入(性能差)
for (Car car : carList) {
sqlSession.insert("CarMapper.insertCar", car);
// 每次:1次网络往返 + 1次事务提交
}
// 方式2:批量插入(性能优)
int[] results = sqlSession.batchInsert("CarMapper.batchInsertCars", carList);
// 总共:1次网络往返 + 1次事务提交
// 性能提升公式:
// 加速比 ≈ N / (1 + ε)
// 其中N是记录数,ε是批处理开销(约0.1-0.5)
批处理优化原理:
-
减少网络延迟:N条记录合并为1次网络请求
-
减少事务开销:1次事务提交代替N次提交
-
数据库优化:数据库可以对批量操作进行内部优化
9.3 连接管理优化
连接获取策略:
// 优化前:每次操作都获取新连接
public int insert(...) {
Connection conn = DriverManager.getConnection(...); // 每次新建
// ...
}
// 优化后:通过TransactionManager管理连接
public class EasyMybatisJDBCTransaction {
private Connection conn;
public void openConnection() {
if (conn == null || conn.isClosed()) {
this.conn = dataSource.getConnection(); // 延迟初始化
this.conn.setAutoCommit(false);
}
}
public Connection getConnection() {
return conn; // 返回现有连接
}
}
优势:
-
连接复用:一个会话内复用同一个连接
-
事务一致性:确保所有操作在同一个事务中
-
资源控制:统一管理连接的创建和关闭
十、扩展性与维护性设计
10.1 扩展点设计
1. 数据源扩展点:
private DataSource getDataSource(Element dataSourceElt) {
String type = dataSourceElt.attributeValue("type").toUpperCase();
if ("UNPOOLED".equals(type)) {
return new EasyMybatisUNPOOLEDDataSource(...);
} else if ("POOLED".equals(type)) {
// 扩展点:实现连接池数据源
return new EasyMybatisPOOLEDDataSource(...);
} else if ("JNDI".equals(type)) {
// 扩展点:实现JNDI数据源
return new EasyMybatisJNDIDataSource(...);
}
}
2. 处理器扩展点:
public class SqlSessionProxy implements SqlSession {
// 可以轻松添加新的处理器
private final CacheHandler cacheHandler; // 扩展:缓存处理器
private final AuditHandler auditHandler; // 扩展:审计处理器
public Object selectOne(...) {
// 可以先查缓存
Object cached = cacheHandler.getFromCache(...);
if (cached != null) return cached;
// 再查数据库
Object result = queryHandler.selectOne(...);
// 记录审计日志
auditHandler.logQuery(...);
return result;
}
}
3. 结果映射扩展点:
public class ResultSetMapper {
// 可以添加自定义类型处理器
private Map<Class<?>, TypeHandler<?>> typeHandlerMap = new HashMap<>();
public void registerTypeHandler(Class<?> type, TypeHandler<?> handler) {
typeHandlerMap.put(type, handler);
}
private void setFieldValue(...) {
// 优先使用自定义类型处理器
TypeHandler<?> handler = typeHandlerMap.get(field.getType());
if (handler != null) {
handler.setValue(obj, field, rs, columnIndex);
} else {
// 使用默认处理
JdbcParamUtil.setFieldValue(...);
}
}
}
10.2 配置化设计
可配置项:
-
数据源配置:驱动、URL、用户名、密码、连接池参数
-
事务配置:事务管理器类型、隔离级别、超时时间
-
映射配置:Mapper文件位置、缓存配置
-
扩展配置:插件配置、类型处理器配置
配置示例:
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC">
<property name="isolation" value="READ_COMMITTED"/>
<property name="timeout" value="30"/>
</transactionManager>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
<property name="poolSize" value="20"/>
<property name="maxWait" value="3000"/>
</dataSource>
<mappers>
<mapper resource="mapper/CarMapper.xml"/>
<mapper resource="mapper/UserMapper.xml"/>
</mappers>
<plugins>
<plugin class="com.rongx.plugin.LogPlugin"/>
<plugin class="com.rongx.plugin.CachePlugin"/>
</plugins>
</environment>
</environments>
</configuration>
十一、错误处理与日志设计
11.1 异常处理体系
异常分类:
-
配置异常:配置文件错误、Mapper解析错误
-
SQL异常:语法错误、连接错误、约束违反
-
映射异常:字段不匹配、类型转换错误
-
事务异常:提交失败、回滚失败
异常处理策略:
// 统一异常转换:所有受检异常转为运行时异常
public int insert(String sqlId, Object obj) {
try {
// 业务逻辑
} catch (SQLException e) {
// 封装为框架异常,保留原始异常信息
throw new EasyMybatisException(
"执行SQL插入失败,SQL ID: " + sqlId,
e
);
} catch (ReflectiveOperationException e) {
throw new EasyMybatisException(
"反射操作失败,字段映射错误",
e
);
}
}
// 自定义框架异常
public class EasyMybatisException extends RuntimeException {
private String sqlId; // 相关SQL ID
private String sql; // 执行的SQL
private Object parameters; // 参数信息
public EasyMybatisException(String message, Throwable cause,
String sqlId, String sql, Object params) {
super(message, cause);
this.sqlId = sqlId;
this.sql = sql;
this.parameters = params;
}
}
11.2 日志记录设计
日志级别:
-
DEBUG:SQL语句、参数绑定、执行时间 -
INFO:连接打开/关闭、事务提交/回滚 -
WARN:资源关闭失败、参数类型转换警告 -
ERROR:SQL执行失败、事务失败
日志集成:
public class JdbcExecutor {
private static final Logger logger = LoggerFactory.getLogger(JdbcExecutor.class);
public int executeUpdate(String sql, Map<Integer, String> paramMap, Object obj) {
long startTime = System.currentTimeMillis();
try {
// 记录SQL和参数
if (logger.isDebugEnabled()) {
logger.debug("执行SQL: {}, 参数: {}", sql, paramMap);
}
int result = ps.executeUpdate();
long cost = System.currentTimeMillis() - startTime;
logger.debug("SQL执行完成,影响行数: {}, 耗时: {}ms", result, cost);
return result;
} catch (SQLException e) {
logger.error("SQL执行失败: {}, 参数: {}", sql, paramMap, e);
throw new RuntimeException("执行SQL更新失败", e);
}
}
}
十二、测试策略与质量保障
12.1 单元测试设计
测试分层:
-
工具类测试:ReflectUtil、StringUtil等
-
处理器测试:各个Handler的独立测试
-
集成测试:完整业务流程测试
测试示例:
public class QueryHandlerTest {
private QueryHandler queryHandler;
private TransactionManager mockTransactionManager;
private Connection mockConnection;
private PreparedStatement mockStatement;
private ResultSet mockResultSet;
@BeforeEach
void setUp() throws SQLException {
// 创建Mock对象
mockConnection = mock(Connection.class);
mockStatement = mock(PreparedStatement.class);
mockResultSet = mock(ResultSet.class);
mockTransactionManager = mock(TransactionManager.class);
when(mockTransactionManager.getConnection()).thenReturn(mockConnection);
when(mockConnection.prepareStatement(anyString())).thenReturn(mockStatement);
when(mockStatement.executeQuery()).thenReturn(mockResultSet);
// 创建被测试对象
Map<String, EasyMybatisMappedStatement> mappedStatements = new HashMap<>();
mappedStatements.put("CarMapper.selectCarById",
new EasyMybatisMappedStatement("selectCarById",
"com.rongx.entity.Car",
"SELECT * FROM t_car WHERE id = #{id}",
"java.lang.Integer",
"select"));
queryHandler = new QueryHandler(mockTransactionManager, mappedStatements);
}
@Test
void testSelectOne_Success() throws SQLException {
// 模拟ResultSet数据
when(mockResultSet.next()).thenReturn(true);
when(mockResultSet.getInt("id")).thenReturn(1);
when(mockResultSet.getString("car_num")).thenReturn("京A88888");
// 执行测试
Car car = (Car) queryHandler.selectOne("CarMapper.selectCarById", 1);
// 验证结果
assertNotNull(car);
assertEquals(1, car.getId());
assertEquals("京A88888", car.getCarNum());
}
}
12.2 性能测试设计
测试场景:
-
单条操作性能:插入/更新/删除/查询单条记录
-
批量操作性能:不同批量大小的性能对比
-
并发操作性能:多线程并发操作
-
内存使用测试:大量数据查询的内存占用
性能测试示例:
public class PerformanceTest {
@Test
void testBatchInsertPerformance() {
// 准备测试数据
List<Car> carList = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
carList.add(new Car(i, "京A" + i, "Brand", 100000.0));
}
// 测试批量插入
long startTime = System.currentTimeMillis();
int[] results = sqlSession.batchInsert("CarMapper.batchInsertCars", carList);
long endTime = System.currentTimeMillis();
// 验证结果
assertEquals(10000, results.length);
System.out.println("批量插入10000条记录耗时: " + (endTime - startTime) + "ms");
// 对比单条插入
startTime = System.currentTimeMillis();
for (Car car : carList) {
sqlSession.insert("CarMapper.insertCar", car);
}
endTime = System.currentTimeMillis();
System.out.println("单条插入10000条记录耗时: " + (endTime - startTime) + "ms");
}
}
十三、总结:从v0.0.2到v0.0.3的架构演进
13.1 重构收益总结
1. 可维护性大幅提升:
-
平均类行数从1200+降到200-300行
-
每个类职责单一,易于理解
-
代码重复率降低80%以上
2. 扩展性显著增强:
-
新增功能只需添加新Handler
-
不影响现有代码
-
支持插件化扩展
3. 可测试性改善:
-
每个类可以独立测试
-
Mock测试更容易
-
测试覆盖率提升
4. 团队协作优化:
-
不同开发人员可以并行开发不同Handler
-
代码冲突减少
-
代码审查更容易
13.2 架构度量指标对比
| 指标 | v0.0.2 | v0.0.3 | 改进 |
|---|---|---|---|
| 类数量 | 8 | 18 | +125% |
| 平均代码行数 | 450 | 150 | -67% |
| 圈复杂度 | 高 | 低 | -60% |
| 重复代码率 | 30% | 5% | -83% |
| 单元测试覆盖率 | 40% | 85% | +113% |
| 新增功能开发时间 | 长 | 短 | -50% |
13.3 经验教训与最佳实践
经验教训:
-
上帝类是万恶之源:一个类做所有事情必然导致维护困难
-
接口优于实现:依赖抽象,提高灵活性
-
小即是美:小类、小方法更易于理解和维护
-
组合优于继承:通过组合构建复杂功能
最佳实践:
-
单一职责原则:每个类/方法只做一件事
-
依赖注入:通过构造函数注入依赖
-
异常早处理:在合适的地方处理异常
-
资源早释放:使用try-with-resources确保资源释放
-
防御性编程:检查参数、处理边界条件
13.4 未来演进方向
短期规划(v0.0.4):
-
连接池实现
-
一级缓存支持
-
简单的插件机制
中期规划(v0.1.0):
-
注解配置支持
-
动态SQL生成
-
多数据源支持
长期规划(v1.0.0):
-
分布式事务支持
-
读写分离
-
监控与管理控制台
通过这次从v0.0.2到v0.0.3的架构重构,Easy-MyBatis完成了从"能工作"到"易维护、可扩展"的质变,为后续的功能增强和性能优化奠定了坚实的基础。这个案例也充分展示了软件架构设计的重要性,以及如何通过合理的分层和模块化来构建高质量的软件系统。