核心是用泛型接口解耦操作行为、实体类型与数据源上下文,分层定义idao、idatasourceaware等接口,为jdbc/mybatis-plus/jpa提供专用泛型基类,结合反射与threadlocal实现运行时数据源路由,并统一异常与事务语义。

核心是用泛型接口把“操作行为”“实体类型”和“数据源上下文”三者解耦,不是写一个万能DAO去适配所有库,而是让每种数据源有自己的一套泛型实现,上层只依赖统一接口。
分层定义泛型接口,明确职责边界
接口按能力逐级抽象,避免大而全:
-
IDataSourceAware
:仅声明数据源识别能力,比如提供 withDataSource(String name)或useTenant(String tenantId)方法,不执行SQL,只做上下文标记 -
IDao
:纯行为契约,含 save(T)、findById(ID)、update(T)等,其中T extends BaseEntity,确保实体具备getId()和getVersion() -
业务接口(如 IUserDao):继承
IDao<user> & IDataSourceAware<user></user></user>,可扩展语义方法,例如findByEmailAndTenant(String email, String tenantId),不包含SQL细节
为不同数据源提供专用泛型基类
不同数据库能力差异明显,不能靠一套实现通吃。应按技术栈建抽象基类,复用框架能力并注入路由逻辑:
- JDBC系(MySQL/PostgreSQL):写
AbstractJdbcDao<t></t>,内部持有DataSourceRouter,所有执行前调用lookupDataSource(name)获取连接 - MyBatis-Plus:每个
Mapper继承BaseMapper<t></t>,再用@MapperScan指向动态SqlSessionTemplate - JPA:定义
JpaDao<t id> extends JpaRepository<t id></t></t>,配合AbstractRoutingDataSource重写determineCurrentLookupKey(),根据线程变量返回数据源名
运行时联动泛型类型与数据源路由
泛型本身不参与运行时决策,但可通过反射+上下文完成绑定:
- 在
AbstractJdbcDao<t></t>中,通过ParameterizedType获取子类传入的T,查其注解如@TableSchema("user_db")或@TenantIsolated,自动匹配数据源名 - 用
ThreadLocal存当前租户ID或业务域标识,DataSourceRouter根据T.class.getSimpleName() + 租户ID拼 key,查预加载的Map<string datasource></string> - 避免每次反射:启动时扫描所有 DAO 实现类,缓存
Class> → DataSourceKey映射表,后续直接查表
统一异常与事务语义,屏蔽底层差异
上层业务不感知 SQL 抛错是 MySQL 还是 ClickHouse,也不关心事务由 Spring 管理还是手动控制:
- 所有 DAO 实现将原生异常(如
SQLException、MyBatisSystemException)统一转为自定义运行时异常DataAccessException - 事务入口统一由接口方法签名体现,例如
saveInTransaction(T entity)或默认开启事务的save(T),具体是否传播、只读等由实现类结合 Spring 注解或模板方法控制 - 异常信息中自动携带泛型类型名、数据源名、操作动作(如 “save User to user_slave failed”),便于快速定位问题链路
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











