抽象类封装数据库公共操作,将连接管理、事务流程、异常包装、日志埋点等通用逻辑提取至父类,sql拼装、参数绑定、结果映射等差异部分声明为抽象方法由子类实现,并通过工厂模式动态选择具体实现类。

用抽象类封装数据库底层公共操作,核心是把所有实现类都共用的逻辑(比如连接管理、日志记录、事务开启/关闭)抽到抽象父类里,同时把差异部分(如 SQL 拼装、参数绑定、结果映射)声明为抽象方法,强制子类实现。
明确哪些操作该放进抽象类
不是所有数据库操作都要抽象——只放真正通用、重复、与具体 SQL 无关的部分:
- 连接复用:单例式连接池初始化、连接空闲检测、自动重连逻辑
- 基础执行流程:统一开启事务 → 执行核心逻辑 → 成功提交 / 失败回滚 → 关闭资源
- 通用异常包装:把 JDBC/ PDO 原生异常转成业务友好的统一异常类型
- 日志埋点:记录 SQL 执行耗时、影响行数、参数快照(脱敏后)
设计抽象方法留出扩展点
抽象类不写具体 SQL,只定义“做什么”,不规定“怎么做”。例如:
- insert() 抽象方法接收 $data,但不拼 INSERT 语句;子类决定字段映射、主键生成策略、是否忽略重复
- select() 接收 $options 数组,但不解析 where/order/group;子类按自己方言(MySQL vs PostgreSQL)处理条件树
- update() 要求子类自行判断哪些字段允许更新、是否校验乐观锁版本号
这样既保证调用方式一致,又允许不同数据库或业务场景定制行为。
配合工厂或配置动态选择实现类
抽象类本身不能实例化,需靠具体子类落地。推荐用简单工厂或依赖注入容器来解耦:
- 配置文件指定当前使用
MysqlDb还是PostgreDb - 工厂根据配置 new 对应子类,返回抽象类类型引用
- 上层代码只依赖抽象类声明的方法,完全 unaware 具体实现
例如:$db = DbFactory::create('mysql'); 返回的是 abstract class DbBase 的实例,但实际运行的是 MysqlDb 的 select() 方法。
避免常见陷阱
抽象类不是万能胶,用错容易导致维护反噬:
- 不要在抽象类里写 SQL 字符串模板——那是子类职责
- 不要让抽象方法参数过于宽泛(如传整个 $config 数组),应拆成明确字段($table, $fields, $where)
- 如果某子类需要绕过公共事务流程,别硬塞开关参数,考虑提供 protected 钩子方法(如
beforeExecute()) - 抽象类里的静态方法要谨慎——它会破坏多态,也难 mock 测试











