我有两个控制器。他们看起来几乎一样。第一个控制器(WaterController)负责接受用户的用水量数据并将其保存在数据库中。第二个(GasController)仅对气体执行相同的操作。除了实体(水和天然气)之外,这两个控制器的代码看起来相同。
我的问题是 - 也许我必须以某种方式使用工厂方法模式来消除这两个控制器中的代码重复? 我不确定工厂模式是否适用于控制器。两个控制器看起来几乎相同,但每个控制器都使用特定的实体(水和天然气)进行操作,并将其存储在数据库中。
水控制器
@RestController
@RequestMapping("/water")
public class WaterController {
private WaterRepository waterRepository;
private CustomerRepository customerRepository;
@Autowired
public WaterController(WaterRepository waterRepository, CustomerRepository customerRepository) {
this.waterRepository = waterRepository;
this.customerRepository = customerRepository;
}
@GetMapping("/{id}")
public ResponseEntity<?> getWaterById(@PathVariable Long id) {
Optional<Water> water = waterRepository.findById(id);
if (water.isEmpty()) {
return new ResponseEntity<>("water record did not found by id " + id, HttpStatus.BAD_REQUEST);
}
return new ResponseEntity<>(water.get(), HttpStatus.OK);
}
@PostMapping
public ResponseEntity<String> createWaterConsumption(@Valid @RequestBody DataDto dataDto) {
Optional<Customer> customer = customerRepository.findById(dataDto.getCustomerId());
if (customer.isEmpty()) {
return new ResponseEntity<>("customerId " + dataDto.getCustomerId() + " did not found", HttpStatus.BAD_REQUEST);
}
Water water = new Water(dataDto.getConsumedAmount(), dataDto.getMonth(), customer.get());
Water savedWater = waterRepository.save(water);
return new ResponseEntity<>(savedWater.getId().toString(), HttpStatus.OK);
}
@GetMapping("/{id}/data")
public List<Water> getWaterConsumptionByCustomerId(@PathVariable Long id) {
List<Water> list = waterRepository.findAllByCustomerId(id);
return list;
}
}
气体控制器
@RestController
@RequestMapping("/gas")
public class GasController {
private GasRepository gasRepository;
private CustomerRepository customerRepository;
@Autowired
public GasController(GasRepository gasRepository, CustomerRepository customerRepository) {
this.gasRepository = gasRepository;
this.customerRepository = customerRepository;
}
@GetMapping("/{id}")
public ResponseEntity<?> getGasById(@PathVariable Long id) {
Optional<Gas> gas = gasRepository.findById(id);
if (gas.isEmpty()) {
return new ResponseEntity<>("gas record did not found by id " + id, HttpStatus.BAD_REQUEST);
}
return new ResponseEntity<>(gas.get(), HttpStatus.OK);
}
@PostMapping
public ResponseEntity<String> createGasConsumption(@Valid @RequestBody DataDto dataDto) {
Optional<Customer> customer = customerRepository.findById(dataDto.getCustomerId());
if (customer.isEmpty()) {
return new ResponseEntity<>("customerId " + dataDto.getCustomerId() + " did not found", HttpStatus.BAD_REQUEST);
}
Gas gas = new Gas(dataDto.getConsumedAmount(), dataDto.getMonth(), customer.get());
Gas savedGas = gasRepository.save(gas);
return new ResponseEntity<>(savedGas.getId().toString(), HttpStatus.OK);
}
@GetMapping("/{id}/data")
public List<Gas> getGasConsumptionByCustomerId(@PathVariable Long id) {
List<Gas> list = gasRepository.findAllByCustomerId(id);
return list;
}
}
我最近在我正在从事的项目中看到了类似的问题,最后我创建了一个通用堆栈(控制器/服务/持久性)。
我从实体的基本定义开始,在我的例子中,我使用 Mongo,但这适用于任何持久性提供程序。它需要一个通用参数,该参数将表示用于 id 的类型。
public abstract class BaseMongoDocument<ID> {
@Id
@Field("_id")
@JsonSerialize(using = ToStringSerializer.class)
private ID id;
@CreatedBy
@Field("created_by")
private String createdBy;
@CreatedDate
@Field("created_date")
private LocalDateTime createdDate;
@LastModifiedBy
@Field("modified_by")
private String modifiedBy;
@LastModifiedDate
@Field("modified_date")
private LocalDateTime modifiedDate;
@Version
@Field("version")
private Long version;
}
接下来,我将创建一个通用服务。您会注意到构造函数采用 MongoRepository,这可以很容易地是任何 Spring 数据存储库。因为 Spring 将泛型参数视为注入的限定符,所以这将很好地工作,稍后您将看到。有意需要重写两个抽象方法,这是为了允许您添加一些客户行为(例如在页面上进行过滤并在更新时检查乐观锁定错误)。大部分持久化逻辑将在这个抽象类中实现。
public abstract class BaseService<T extends BaseMongoDocument<ID>, ID> {
final MongoRepository<T, ID> repository;
private final ObjectMapper objectMapper;
public BaseService(MongoRepository<T, ID> repository, ObjectMapper objectMapper) {
this.repository = repository;
this.objectMapper = objectMapper;
}
public List<T> findAll() {
return repository.findAll();
}
public T delete(ID id) {
T entityToDelete = findById(id);
repository.deleteById(id);
return entityToDelete;
}
public T findById(ID id) {
return repository.findById(id).orElseThrow(() -> new EntityNotFoundException("Unable to find record with ID of " + id));
}
public abstract Page<T> find(Pageable pageable, String filter);
public T create(T entity) {
return repository.save(entity);
}
public T update(ID id, JsonPatch jsonPatch) throws JsonPatchException, JsonProcessingException {
T entityToPatch = findById(id);
JsonNode patched = jsonPatch.apply(objectMapper.convertValue(entityToPatch, JsonNode.class));
T patchedEntity = objectMapper.treeToValue(patched, (Class<T>) entityToPatch.getClass());
return repository.save(patchedEntity);
}
public abstract T put(T object);
}
现在我有了服务以及它可以执行的操作,让我们创建一个通用控制器。首先从控制器开始,我将为传输创建一个 DTO 接口,它将用作控制器的参数。这是一个简单的接口,只有一个方法,即返回其 DTO 实体的方法。
public interface DTO<T> {
T toEntity();
}
现在我们要定义接口,该接口将定义我想要的控制器方法(这些将是端点)。作为参数,它采用实体的类型、DTO 和标识符。
public interface CrudControllerMethods<T, E extends DTO<T>, ID> {
T delete(ID id);
Page<T> find(Pageable pageable, String filter);
List<T> findAll();
T findById(ID id);
T create(E dto);
T patch(ID id, JsonPatch jsonPatch) throws JsonPatchException, JsonProcessingException;
T put(T object);
}
现在,让我们创建一个 BaseController 来实现大部分这些方法。
public abstract class BaseController<T extends BaseMongoDocument<ID>, E extends DTO<T>, ID>
implements CrudControllerMethods<T, E, ID> {
private final BaseService<T, ID> baseService;
public BaseController(BaseService<T, ID> service) {
this.baseService = service;
}
@Override
@DeleteMapping("/{id}")
public T delete(@PathVariable ID id) {
return baseService.delete(id);
}
@Override
@GetMapping("/{id}")
public T findById(@PathVariable ID id) {
return baseService.findById(id);
}
@Override
@GetMapping("/page")
public Page<T> find(Pageable pageable, @RequestParam(required = false) String filter) {
return baseService.find(pageable, filter);
}
@Override
@GetMapping
public List<T> findAll() {
return baseService.findAll();
}
@Override
@PostMapping
public T create(@RequestBody E dto) {
return baseService.create(dto.toEntity());
}
@Override
@PatchMapping("/{id}")
public T patch(@PathVariable ID id, @RequestBody JsonPatch jsonPatch) throws JsonPatchException, JsonProcessingException {
return baseService.update(id, jsonPatch);
}
@Override
@PutMapping
public T put(@RequestBody T object) {
return baseService.put(object);
}
}
这就是整个堆栈的具体实现(使用您的 Water 实体)。
@Document("Water")
@Getter
@Setter
@ToString(of = {"empty"})
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Water extends BaseMongoDocument<ObjectId> {
private Boolean empty;
}
接下来,创建 DTO。
@Getter
@Setter
public class WaterDTO implements DTO<Water> {
private Boolean empty;
@Override
public Water toEntity() {
return Water.builder().empty(empty).build();
}
}
接下来,定义存储库来保存实体。
public interface WaterRepository extends MongoRepository<Water, ObjectId> {
}
接下来我们将创建服务,扩展我们的 BaseService。
@Service
public class WaterService extends BaseService<Water, ObjectId> {
private final WaterRepository waterRepository;
public WaterService(WaterRepository waterRepository, ObjectMapper objectMapper) {
super(waterRepository, objectMapper);
this.waterRepository = waterRepository;
}
@Override
public Page<Water> find(Pageable pageable, String filter) {
return waterRepository.findAll(pageable);
}
@Override
public Water put(Water water) {
Water waterToUpdate = findById(water.getId());
if (!water.getVersion().equals(waterToUpdate.getVersion())) {
throw new RuntimeException("The record has changed since you last retrieved it");
}
waterToUpdate.setEmpty(water.getEmpty());
return waterRepository.save(waterToUpdate);
}
}
最后一步是定义您的控制器。
@RestController
@RequestMapping("/water")
public class WaterController extends BaseController<Water, WaterDTO,
ObjectId> {
public WaterController(WaterService waterService) {
super(waterService);
}
}
并且,就像这样,您将定义以下映射:
GET /water/{id}
DELETE /water/{id}
GET /water/find
POST /water which takes the WaterDTO as the RequestBody
PATCH /water/{id}
需要注意的是,如果您使用 JPA,您可能需要在基本实体类上使用
@MappedSuperclass
。每次我看到这个,我都会发现一些额外的东西需要清理或添加,但我认为它给你一个很好的开始。