关键在于解耦操作契约、数据类型与数据源路由:定义分层泛型接口(idatasourceaware、idao、业务接口),按数据源提供专用泛型基类,联动泛型类型与运行时路由,并统一异常与事务语义。

用泛型接口构建多数据源统一的通用数据访问层,关键在于把“操作契约”“数据类型”和“数据源路由”三者解耦。不是让一个DAO适配所有库,而是让每种数据源有自己的一套泛型实现,再通过统一接口暴露行为,上层业务只面向接口编程,不感知底层是MySQL、ClickHouse还是租户动态库。
定义分层泛型接口:从行为到实体再到数据源
先划清职责边界:
-
IDataSourceAware
:声明该DAO具备数据源识别能力,例如提供 withDataSource(String name)或useTenant(String tenantId)方法,不执行SQL,只做上下文标记 -
IDao
:纯行为接口,含 save(T)、findById(ID)、update(T)等,T通常约束为extends BaseEntity,确保有getId()和getVersion() -
IUserDao extends IDao
& IDataSourceAware :业务接口,既继承通用能力,又声明支持切换数据源,不包含具体SQL,只扩展语义方法如findByEmailAndTenant(String email, String tenantId)
按数据源类型提供泛型实现基类
不同数据库能力差异大,不能强求一套实现通吃。应为每类数据源建抽象基类,复用框架能力并注入路由逻辑:
- 对JDBC系(MySQL/PostgreSQL):写
AbstractJdbcDao<t></t>,内部持DataSourceRouter,所有execute前调用lookupDataSource(name)获取连接 - 对MyBatis-Plus:每个Mapper继承
BaseMapper<t></t>,再用@MapperScan(basePackages = "xxx", sqlSessionTemplateRef = "tenantSqlSession")指向动态session模板 - 对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映射表,后续直接查表
统一异常与事务语义,屏蔽底层差异
多数据源下,事务无法跨库,必须明确边界。泛型层要收口错误处理和传播规则:
- 定义
DataAccessException作为顶层异常,各实现类将SQLException、JpaSystemException等转为此类,上层无需import数据库特有异常 - 在Service层用
@Transactional(value = "userTransactionManager")显式指定事务管理器,DAO层不开启事务,只执行单语句 - 对需跨源操作(如主库写+从库删缓存),拆成多个带
@Transactional(propagation = Propagation.REQUIRES_NEW)的独立方法,由编排层保证最终一致性











