出于对Mysql数据库减负的想法,我们决定将一些经常读的数据放在自己的json文件服务器中,当然也可以选择redis,但是可能会有较多数据不会读到但必须要存的情况比较耗内存。这里对于json文件的更新就成了一种问题,这里我们介绍下我们使用的SpringBoot+HttpPatch+JsonPatch。
HttpPatchHttp的【RFC2616】原本定义用于上传数据的方法只有POST和PUT,但是考虑到两者的不足,就增加了PATCH方法。
用PATCH方法,默认是以x-www-form-urlencoded的contentType来发送信息,并且信息内容是放在request的body里。
PUT方法和PATCH方法的提交目的地都是直接指向资源,而POST方法提交的数据的目的地是一个行为处理器。
PUT方法用来替换资源,而patch方法用来更新部分资源,然而PATCH和POST都是非幂等的,POST请求服务器执行一个动作,多次请求会多次执行。PATCH提供的实体则需要根据程序或其它协议的定义,解析后在服务器上执行,以此来修改服务器上的数据。也就是说,PATCH请求是会执行某个程序的,如果重复提交,程序可能执行多次,对服务器上的资源就可能造成额外的影响POST方法和PATCH方法它们的实体部分都是结构化的数据,所以PAtch也是非幂等的。POST方法的实体结构一般是 multipart/form-data或 application/x-www-form-urlencoded而PATCH方法的实体结构则随其它规范定义。这和PUT方法的无结构实体相比就是最大的区别。
JsonPatchJSON Patch是一种用于描述对JSON文档所做的更改的格式(JSON Patch本身也是JSON结构)。当只更改了一部分时,可用于避免发送整个文档。可以与HTTP PATCH方法结合使用时,它允许以符合标准的方式对HTTP API进行部分更新。
JSON Patch是在IETF的RFC 6902中指定的。如果了解过linux上的diff、patch,就非常容易理解JSON Patch了,前者是针对普通文本文件的,后者是指针对JSON结构。(前者更通用)
实战1.首先来看看需要导入哪些依赖,为了方便直接贴出全部依赖直接复制即可
UTF-8 1.18.18 27.1-jre 1.3.0.Final 2.9.8 1.1.4 2.1.5.RELEASE 3.8.0 org.springframework.boot spring-boot-dependencies ${spring-boot.version} pom import org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-devtools true org.springframework.boot spring-boot-starter-test test com.google.guava guava ${guava.version} org.mapstruct mapstruct ${mapstruct.version} org.projectlombok lombok ${lombok.version} provided javax.json javax.json-api ${javax-json.version} org.apache.johnzon johnzon-core com.fasterxml.jackson.datatype jackson-datatype-jsr353 ${jackson.version} org.apache.httpcomponents httpclient test com.jayway.jsonpath json-path-assert test org.springframework.boot spring-boot-maven-plugin ${spring-boot.version} repackage org.apache.maven.plugins maven-compiler-plugin ${maven-compiler-plugin.version} 11 org.projectlombok lombok ${lombok.version} org.mapstruct mapstruct-processor ${mapstruct.version} -Amapstruct.suppressGeneratorTimestamp=true -Amapstruct.suppressGeneratorVersionInfoComment=true -Amapstruct.defaultComponentModel=spring
2.准备一个实体类和一个请求信息类
实体类
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class BookInput {
private Long id;
private String bookName;
private String url;
}
请求信息类
public final class PatchMediaType {
public static final String APPLICATION_JSON_PATCH_VALUE = "application/json-patch+json";
public static final String APPLICATION_MERGE_PATCH_VALUE = "application/merge-patch+json";
public static final MediaType APPLICATION_JSON_PATCH;
public static final MediaType APPLICATION_MERGE_PATCH;
static {
APPLICATION_JSON_PATCH = MediaType.valueOf(APPLICATION_JSON_PATCH_VALUE);
APPLICATION_MERGE_PATCH = MediaType.valueOf(APPLICATION_MERGE_PATCH_VALUE);
}
private PatchMediaType() {
throw new AssertionError("No instances of PatchMediaType for you!");
}
}
3.写一个配置类用来对json进行配置
@Configuration
public class JacksonConfig {
@Bean
public ObjectMapper objectMapper() {
return new ObjectMapper()
.setDefaultPropertyInclusion(Include.NON_NULL)
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.findAndRegisterModules();
}
}
注:如果没有这个配置类会报错
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `javax.json.JsonStructure` (no Creators, like default construct, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
4.这些准备好后重点来了
@Component
@RequiredArgsConstructor
public class PatchHelper {
private final ObjectMapper mapper;
private final Validator validator;
public T patch(JsonPatch patch, T targetBean, Class beanClass) {
JsonStructure target = mapper.convertValue(targetBean, JsonStructure.class);
JsonValue patched = applyPatch(patch, target);
return convertAndValidate(patched, beanClass);
}
public T mergePatch(JsonMergePatch mergePatch, T targetBean, Class beanClass) {
JsonValue target = mapper.convertValue(targetBean, JsonValue.class);
JsonValue patched = applyMergePatch(mergePatch, target);
return convertAndValidate(patched, beanClass);
}
private JsonValue applyPatch(JsonPatch patch, JsonStructure target) {
try {
return patch.apply(target);
} catch (Exception e) {
throw new UnprocessableEntityException(e);
}
}
private JsonValue applyMergePatch(JsonMergePatch mergePatch, JsonValue target) {
try {
return mergePatch.apply(target);
} catch (Exception e) {
throw new UnprocessableEntityException(e);
}
}
private T convertAndValidate(JsonValue jsonValue, Class beanClass) {
T bean = mapper.convertValue(jsonValue, beanClass);
validate(bean);
return bean;
}
private void validate(T bean) {
Set> violations = validator.validate(bean);
if (!violations.isEmpty()) {
throw new ConstraintViolationException(violations);
}
}
}
这是封装好的JsonPatch方法直接调用就可以用了。另外两个异常处理类要放在下面
@ResponseStatus(value = HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException {
}
@ResponseStatus(value = HttpStatus.UNPROCESSABLE_ENTITY)
public class UnprocessableEntityException extends RuntimeException {
public UnprocessableEntityException(Throwable cause) {
super(cause);
}
}
5.测试
@RestController
@RequestMapping("/patch")
public class PatchController {
private final PatchHelper patchHelper;
public PatchController(PatchHelper patchHelper) {
this.patchHelper = patchHelper;
}
@PatchMapping(consumes = PatchMediaType.APPLICATION_JSON_PATCH_VALUE)
public void test() {
JsonPatch patch = Json.createPatchBuilder()
//将id替换为3
.replace("/id",3)
//将bookName替换为HAPPYBEAR
.replace("/bookName", "HAPPYBEAR")
//移除路径字段
.remove("/url")
//添加路径字段并赋值https://baike.baidu.com
.add("/url", "https://baike.baidu.com")
.build();
//拿到的json转换为对象
BookInput bookInput =new BookInput(2L,"大话设计模式","https://baike.baidu.com/item/%E5%A4%A7%E8%AF%9D%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/85262?fr=aladdin");
BookInput input = patchHelper.patch(patch, bookInput, BookInput.class);
System.out.println(input);
}
}
6.运行结果
BookInput(id=3, bookName=HAPPYBEAR, url=https://baike.baidu.com)
获取或上传json文件这边就不阐述了,这边只提供SpringBoot+HttpPatch+JsonPatch的大概思路,希望对大家有帮助!!!



