我有 REST
api,当客户端使用正文调用 POST
请求时,反序列化后的后端应区分 null
和缺少值。
因为如果JSON
中的值为空,那么DB中的value
应该为空。
如果 JSON
中的值不存在,则数据库中的值
应保持不变。
JSON:
{
"id" : 1,
"name" : "sample name",
"value" : null
}
或
{
"id" : 1,
"name" : "sample name"
}
对于反序列化后的Java,它看起来像:value=null;
Java:
@Entity
@Table("sample")
public class Sample {
@Id
@Column
private Long id;
@Column
private String name;
@Column
private Integer value;
// getters / setters
}
样本剩余请求:
@PutMapping
public ResponseEntity<SampleDto> updateSample(@RequestBody SampleDto dto) {
return ResponseEntity.ok(service.updateSample(dto));
}
示例服务impl:
public SampleDto updateSample(SampleDto dto) {
Sample sample = sampleRepository.findById(dto.getId);
sample.setName(dto.getName());
sample.setValue(dto.getValue());
//In this operation back need understand: value is null or absence
//Because if value in JSON is null, then value in DB should become null
//If value in JSON absence, then value in DB should remain unchanged
Sample newSample = sampleRepository.save(sample);
return modelMapper.map(newSample, SampleDto.class);
}
项目使用Spring Data
。
也许我应该使用@JsonDeserialize
注解或其他Hibernate
注解
我尝试使用< code>@JsonDeserialize,但它不是解决方案。
部分更新不同于完全资源更新,我们应该以不同的方式实现它。让我们创建两个request POJO
类。一个类将用于创建和更新资源,另一个将用于部分更新给定资源。为了强调这一点,我们将使用不同的< code>HTTP方法。为了区分< code>null和< code >缺勤,我们可以使用< code>java.util.Optional类。
为了避免本例中的样板代码,我使用了Lombok和MapStruct,但这不是必需的。
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
@Data
public class SampleCompleteRequest {
@NotBlank
private String name;
private String value;
}
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import java.util.Optional;
@Data
public class SamplePartialRequest {
private Optional<@NotBlank String> name;
private Optional<String> value;
}
import lombok.Data;
@Data
public class SampleResponse {
private Long id;
private String name;
private String value;
}
import lombok.Data;
@Data
public class Sample {
//@Id - Hibernate annotations are removed
private Long id;
private String name;
private String value;
}
在 MapStruct
中,我们需要定义一个包含我们需要的所有方法的接口。
import com.example.demo.model.SampleCompleteRequest;
import com.example.demo.model.SamplePartialRequest;
import com.example.demo.model.SampleResponse;
import jakarta.annotation.Nullable;
import org.mapstruct.BeanMapping;
import org.mapstruct.Mapper;
import org.mapstruct.MappingTarget;
import org.mapstruct.ReportingPolicy;
import java.util.Optional;
import static org.mapstruct.MappingConstants.ComponentModel.SPRING;
import static org.mapstruct.NullValueCheckStrategy.ALWAYS;
import static org.mapstruct.NullValuePropertyMappingStrategy.IGNORE;
@Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE, componentModel = SPRING)
public interface SamplesMapper {
@BeanMapping(nullValueCheckStrategy = ALWAYS, nullValuePropertyMappingStrategy = IGNORE)
Sample patch(SamplePartialRequest input, @MappingTarget Sample target);
Sample update(SampleCompleteRequest input, @MappingTarget Sample target);
SampleResponse mapToResponse(Sample input);
default String optionalToString(@Nullable Optional<String> nullable) {
return nullable == null ? null : nullable.orElse(null);
}
}
插件将为我们生成样板代码。下面的类是自动生成的,我们不需要手动实现。
@Component
public class SamplesMapperImpl implements SamplesMapper {
@Override
public Sample patch(SamplePartialRequest input, Sample target) {
if ( input == null ) {
return target;
}
if ( input.getName() != null ) {
target.setName( optionalToString( input.getName() ) );
}
if ( input.getValue() != null ) {
target.setValue( optionalToString( input.getValue() ) );
}
return target;
}
@Override
public Sample update(SampleCompleteRequest input, Sample target) {
if ( input == null ) {
return target;
}
target.setName( input.getName() );
target.setValue( input.getValue() );
return target;
}
@Override
public SampleResponse mapToResponse(Sample input) {
if ( input == null ) {
return null;
}
SampleResponse sampleResponse = new SampleResponse();
sampleResponse.setId( input.getId() );
sampleResponse.setName( input.getName() );
sampleResponse.setValue( input.getValue() );
return sampleResponse;
}
}
控制器类很容易实现:
import com.example.demo.model.SampleCompleteRequest;
import com.example.demo.model.SamplePartialRequest;
import com.example.demo.model.SampleResponse;
import com.example.service.SamplesMapper;
import com.example.service.SamplesService;
import jakarta.validation.Valid;
import lombok.AllArgsConstructor;
import org.springframework.hateoas.CollectionModel;
import org.springframework.hateoas.EntityModel;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@AllArgsConstructor
@RestController
@RequestMapping(value = "/api/v1/samples")
public class SamplesResource {
private final SamplesMapper mapper;
private final SamplesService samplesService;
@GetMapping
public CollectionModel<SampleResponse> listAll() {
List<SampleResponse> entities = samplesService.list().stream().map(mapper::mapToResponse).toList();
return CollectionModel.of(entities);
}
@PostMapping
public EntityModel<SampleResponse> addSample(@Valid @RequestBody SampleCompleteRequest request) {
var entity = samplesService.create(request);
var response = mapper.mapToResponse(entity);
return EntityModel.of(response);
}
@PutMapping(path = "{id}")
public EntityModel<SampleResponse> updateSample(@PathVariable Long id, @Valid @RequestBody SampleCompleteRequest request) {
var entity = samplesService.update(id, request);
var response = mapper.mapToResponse(entity);
return EntityModel.of(response);
}
@PatchMapping(path = "{id}")
public EntityModel<SampleResponse> partiallyUpdateSample(@PathVariable Long id, @Valid @RequestBody SamplePartialRequest request) {
var entity = samplesService.patch(id, request);
var response = mapper.mapToResponse(entity);
return EntityModel.of(response);
}
}
服务类也很简单:
import com.example.demo.model.SampleCompleteRequest;
import com.example.demo.model.SamplePartialRequest;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
@AllArgsConstructor
public class SamplesService {
private final SamplesMapper mapper;
private final SamplesRepository repository;
public List<Sample> list() {
return repository.listAll();
}
public Sample create(SampleCompleteRequest request) {
var sample = mapper.update(request, new Sample());
return repository.save(sample);
}
public Sample update(Long id, SampleCompleteRequest request) {
var sample = repository.find(id).orElseThrow();
mapper.update(request, sample);
return repository.save(sample);
}
public Sample patch(Long id, SamplePartialRequest request) {
var sample = repository.find(id).orElseThrow();
mapper.patch(request, sample);
return repository.save(sample);
}
}
另请参阅: