核心是业务逻辑层仅依赖接口契约,不感知底层技术;接口按业务语义设计(如registernewmember)、参数用不可变dto、返回统一result类型、命名体现领域职责(如memberrepository);多实现(mybatis/jpa/内存)共存且service无需修改;依赖注入解耦,接口演进保守,优先拆新接口而非修改旧接口。

核心是让业务逻辑层只依赖接口契约,不感知底层是 JDBC、MyBatis 还是 MongoDB —— 接口定义的是“要做什么”,不是“怎么做”。
接口按业务语义设计,不暴露技术细节
DAO 接口应聚焦领域动作,而非数据库操作动词。比如不用 insertUser() 或 updateById(),而用 registerNewMember(RegistrationRequest)、deactivateAccount(MemberId)。这样 Service 层调用时,表达的是业务意图,而非数据表操作。
- 参数封装为不可变 DTO(如
RegistrationRequest),避免传Map或原始 ID - 返回统一业务结果类型(如
Result<memberprofile></memberprofile>),不直接返回UserEntity或抛出SQLException - 接口名体现职责边界,例如
MemberRepository比UserDao更贴近领域,也更利于后续替换为事件溯源或缓存实现
实现类彻底隔离持久化技术栈
每个 DAO 接口可有多个实现:一个基于 MyBatis,一个基于 JPA,一个甚至基于内存 Map(用于单元测试)。只要它们都遵守同一接口契约,Service 层完全无需修改。
- MyBatis 实现类里写
@Mapper注解和 XML/注解 SQL,但对外只暴露接口方法 - JPA 实现类用
CrudRepository组合,内部调用save()或findById(),但 Service 不知道这些 - 测试用的 Stub 实现直接返回预设对象,不连任何数据库,验证业务流程是否正确
依赖注入确保运行时解耦
Service 类不 new 任何 DAO 实现,而是通过构造器接收接口类型:
- Spring 中用
@Autowired或构造器注入(推荐 final 字段 + 构造器) - 避免在 Service 内部判断
if (env == "test")切换实现 —— 那会把耦合从代码移到配置逻辑 - 不同环境通过 Spring Profile 或条件 Bean 控制哪个实现被注册进容器,Service 始终面向接口编程
接口演进要保守,避免牵一发而动全身
一旦 DAO 接口上线,新增方法需谨慎。比起改现有接口,更推荐:
- 拆新接口(如从
MemberRepository拆出MembershipFeeCalculator),保持单一职责 - 用 default 方法仅补充极简、无副作用的兼容逻辑(如空实现),不放核心业务
- 废弃旧方法时加
@Deprecated并给出迁移路径,给调用方留出升级窗口
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











