You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

实体数据查询与更新流程的设计模式及最佳实践

架构优化方案与适用设计模式

你的核心问题是违反了单一职责原则——把实体类(Dog)和数据访问、序列化逻辑绑定在一起,导致类臃肿、耦合度高。下面是分层架构的落地方案和对应设计模式:

一、分层拆解核心流程

把整个流程拆成4个独立职责的模块,每个模块只聚焦一件事:

1. 实体层(Domain Model)

  • 只保留纯业务属性和业务行为:比如Dog类仅包含name、race、age、ownersDogHasHad这些字段,以及和狗本身相关的业务方法(比如calculateHumanAge()),完全剥离数据访问、序列化逻辑。
  • 这层是业务的核心,不依赖任何框架或外部服务。

2. 数据访问层(Repository 模式)

  • 专门负责与数据库/仓库的交互:定义DogRepository接口,包含findById()、save()、update()等方法,具体实现(比如JDBC、JPA、ORM框架代码)放在独立的实现类中。
  • 好处:实体类无需关心数据存储细节,更换数据库或存储方式时,仅修改Repository实现即可,不影响业务逻辑。

3. 序列化/反序列化层(DTO + 转换器模式)

  • 定义数据传输对象(DTO):比如DogDto,结构与API接口的JSON完全对应(不需要包含业务方法),前端请求的JSON直接映射到DTO,后端返回给前端的也是DTO。
  • 用**转换器(Converter)**处理实体与DTO的互转:编写DogConverter类,包含toDto(Dog dog)和toEntity(DogDto dto)方法,把JSON与实体的转换逻辑集中管理。
  • 为什么不用实体直接序列化?实体可能包含业务逻辑、关联对象的敏感字段,DTO可灵活控制暴露给前端的字段,避免数据泄露或序列化异常。

4. API层(Controller/Resource)

  • 处理HTTP请求:接收前端的GET/POST/PUT请求,调用Repository获取或更新实体,通过Converter转成DTO返回给前端;接收前端的DTO,转成实体后交给Repository完成更新。
  • 这层仅负责HTTP协议相关逻辑(参数校验、状态码返回、请求路由),不处理业务或数据访问。

二、适用的设计模式

  1. Repository模式:封装数据访问逻辑,隔离实体与存储细节,符合依赖倒置原则——业务层依赖Repository接口,而非具体实现。
  2. DTO(数据传输对象)模式:解耦前端API与后端实体,避免实体结构变化直接影响前端,同时精准控制数据暴露范围。
  3. 转换器(Converter)模式:抽离实体与DTO的转换逻辑,避免在Controller或实体中编写大量转换代码,保持代码整洁。
  4. 单一职责原则:虽不属于GoF设计模式,但它是整个架构的基础,每个类/模块仅负责一个职责,大幅提升可维护性与可测试性。

三、代码示例(伪代码)

实体类

public class Dog {
    private String id;
    private String name;
    private String race;
    private int age;
    private List<Owner> ownersDogHasHad;

    // 业务方法:计算狗对应的人类年龄
    public int calculateHumanAge() {
        return age * 7;
    }

    // 仅保留getter/setter,无数据访问或序列化方法
}

Repository接口与实现

public interface DogRepository {
    Dog findById(String id);
    void save(Dog dog);
    void update(Dog dog);
}

// JPA实现示例
public class JpaDogRepository implements DogRepository {
    private EntityManager em;

    @Override
    public Dog findById(String id) {
        return em.find(Dog.class, id);
    }

    @Override
    public void save(Dog dog) {
        em.persist(dog);
    }

    // 其他实现方法...
}

DTO与转换器

public class DogDto {
    private String id;
    private String name;
    private String race;
    private int age;
    private List<String> ownerNames; // 仅返回所有者名字,不暴露Owner的全部信息

    // getter/setter
}

public class DogConverter {
    public static DogDto toDto(Dog dog) {
        DogDto dto = new DogDto();
        dto.setId(dog.getId());
        dto.setName(dog.getName());
        dto.setRace(dog.getRace());
        dto.setAge(dog.getAge());
        // 将Owner列表转换为名字列表
        dto.setOwnerNames(dog.getOwnersDogHasHad().stream()
            .map(Owner::getName)
            .collect(Collectors.toList()));
        return dto;
    }

    public static Dog toEntity(DogDto dto) {
        Dog dog = new Dog();
        dog.setId(dto.getId());
        dog.setName(dto.getName());
        dog.setRace(dto.getRace());
        dog.setAge(dto.getAge());
        // 如需从DTO重建Owner关联,可在此通过OwnerRepository查询处理
        return dog;
    }
}

API Controller

public class DogController {
    private DogRepository dogRepo;

    public DogController(DogRepository dogRepo) {
        this.dogRepo = dogRepo;
    }

    // GET请求获取单条狗的信息
    @GetMapping("/dogs/{id}")
    public ResponseEntity<DogDto> getDog(@PathVariable String id) {
        Dog dog = dogRepo.findById(id);
        if (dog == null) {
            return ResponseEntity.notFound().build();
        }
        return ResponseEntity.ok(DogConverter.toDto(dog));
    }

    // PUT请求更新狗的信息
    @PutMapping("/dogs/{id}")
    public ResponseEntity<Void> updateDog(@PathVariable String id, @RequestBody DogDto dto) {
        Dog existingDog = dogRepo.findById(id);
        if (existingDog == null) {
            return ResponseEntity.notFound().build();
        }
        // 将DTO属性更新到现有实体(避免创建新实体导致主键问题)
        existingDog.setName(dto.getName());
        existingDog.setRace(dto.getRace());
        existingDog.setAge(dto.getAge());
        // 处理Owner关联的更新逻辑...
        dogRepo.update(existingDog);
        return ResponseEntity.noContent().build();
    }
}

四、额外最佳实践

  • 参数校验:在DTO层做参数校验(比如使用JSR-380注解),避免非法数据进入业务层。
  • 事务管理:在Repository或业务服务层处理事务,确保数据操作的原子性。
  • 测试隔离:各层职责分离后,单元测试可单独测试实体的业务方法、Repository的数据访问逻辑、Converter的转换逻辑,无需启动整个应用。

内容的提问来源于stack exchange,提问作者Markus B

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 00:15:37