方法一:在类中定义list,mapper中添加对应collection
!!!然后将select方法封装到service层(先mapper,再service)
方法二:使用自定义注解@multiRequestBody
如果不想定义单独的实体类关联其余两个实体类来体现多表关系,可以选择使用自定义注解@multiRequestBody
缺点:
传递参数过多的话,代码冗余,难以维护。
代码如下:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author Anna
* @date 2023/6/9
*/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface MultiRequestBody {
/**
* 解析时用到的 JSON 中的 key
*/
String value() default "";
/**
* 是否必传的参数
*/
boolean required() default true;
/**
* 当 value 的值或者参数名不匹配时,是否允许解析最外层属性得到该对象
*/
boolean parseAllFields() default true;
}
import com.cawei.website.anno.MultiRequestBody;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.core.MethodParameter;
import org.springframework.util.Assert;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
/**
* MultiRequestBody解析器
* 解决的问题:
* 1、单个字符串等包装类型都要写一个对象才可以用@RequestBody接收;
* 2、多个对象需要封装到一个对象里才可以用@RequestBody接收。
* 主要优势:
* 1、支持通过注解的value指定JSON的key来解析对象。
* 2、支持通过注解无value,直接根据参数名来解析对象
* 3、支持基本类型的注入
* 4、支持GET和其他请求方式注入
* 5、支持通过注解无value且参数名不匹配JSON串key时,根据属性解析对象。
* 6、支持多余属性(不解析、不报错)、支持参数“共用”(不指定value时,参数名不为JSON串的key)
* 7、支持当value和属性名找不到匹配的key时,对象是否匹配所有属性。
* @author Anna
* @date 2023/6/9
*/
public class MultiRequestBodyResolver implements HandlerMethodArgumentResolver {
private static final Set<Class> classSet = new HashSet<>(16);
private static ObjectMapper objectMapper = new ObjectMapper();
private static final String JSON_REQUEST_BODY = "JSON_REQUEST_BODY";
static {
classSet.add(Integer.class);
classSet.add(Long.class);
classSet.add(Short.class);
classSet.add(Float.class);
classSet.add(Double.class);
classSet.add(Boolean.class);
classSet.add(Byte.class);
classSet.add(Character.class);
}
/**
* 支持的方法参数类型
* @param methodParameter
* @return
*/
@Override
public boolean supportsParameter(MethodParameter methodParameter) {
return methodParameter.hasParameterAnnotation(MultiRequestBody.class);
}
/**
* 参数解析
* @param methodParameter
* @param modelAndViewContainer
* @param nativeWebRequest
* @param webDataBinderFactory
* @return
* @throws Exception
*/
@Override
public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer,
NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {
Object result;
Object value;
// 获取请求体
String requestBody = getRequestBody(nativeWebRequest);
// 允许使用不带引号的字段
objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
// 解析 JSON 串
JsonNode rootNode = objectMapper.readTree(requestBody);
// JSON 串为空抛出异常
Assert.notNull(rootNode, String.format("param %s parsing failed", requestBody));
// 获取注解
MultiRequestBody multiRequestBody = methodParameter.getParameterAnnotation(MultiRequestBody.class);
Assert.notNull(multiRequestBody, String.format("param %s parsing failed", requestBody));
String key = multiRequestBody.value();
// 根据注解 value 解析 JSON 串,如果没有根据参数的名字解析 JSON
if (StringUtils.isNotBlank(key)) {
value = rootNode.get(key);
// 如果为参数必填但未根据 key 成功得到对应 value 抛出异常
Assert.isTrue(multiRequestBody.required() && Objects.nonNull(value), String.format("required param %s is not present", key));
} else {
key = methodParameter.getParameterName();
value = rootNode.get(key);
}
// 获取参数的类型
Class<?> paramType = methodParameter.getParameterType();
// 成功从 JSON 解析到对应 key 的 value
if (Objects.nonNull(value)) {
return objectMapper.readValue(value.toString(), paramType);
}
// 未从 JSON 解析到对应 key(可能是注解的 value 或者是参数名字) 的值,要么没传值,要么传的名字不对
// 如果参数为基本数据类型,且为必传参数抛出异常
Assert.isTrue(!(isBasicDataTypes(paramType) && multiRequestBody.required()), String.format("required param %s is not present", key));
// 参数非基本数据类型,如果不允许解析外层属性,且为必传参数报错抛出异常
Assert.isTrue(!(!multiRequestBody.parseAllFields() && multiRequestBody.required()), String.format("required param %s is not present", key));
try {
// 既然找不到对应参数,而且非基本类型,我们可以解析外层属性,将整个 JSON 作为参数进行解析。解析失败会抛出异常
result = objectMapper.readValue(requestBody, paramType);
} catch (Exception e) {
throw new RuntimeException(e);
}
// 必填参数的话,看解析出来的参数是否对应,非必填直接返回吧
if (multiRequestBody.required()) {
Field[] declaredFields = paramType.getDeclaredFields();
for (Field field : declaredFields) {
field.setAccessible(true);
Assert.notNull(field.get(result), String.format("required param %s is not present", key));
}
}
return result;
}
/**
* 获取请求 JSON 字符串
*/
private String getRequestBody(NativeWebRequest webRequest) {
HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
String jsonBody = (String) webRequest.getAttribute(JSON_REQUEST_BODY, NativeWebRequest.SCOPE_REQUEST);
if (StringUtils.isEmpty(jsonBody)) {
try (BufferedReader reader = servletRequest.getReader()) {
jsonBody = IOUtils.toString(reader);
webRequest.setAttribute(JSON_REQUEST_BODY, jsonBody, NativeWebRequest.SCOPE_REQUEST);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return jsonBody;
}
/**
* 判断是否为基本数据类型包装类
*/
private boolean isBasicDataTypes(Class clazz) {
return classSet.contains(clazz);
}
}
import com.cawei.website.handler.MultiRequestBodyResolver;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
/**
* @author Anna
* @date 2023/6/9
*/
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
/**
* 参数解析器
* @see MultiRequestBodyResolver
*/
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new MultiRequestBodyResolver());
}
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
MappingJackson2HttpMessageConverter jacksonConver = new MappingJackson2HttpMessageConverter();
ArrayList<MediaType> mediaTypes = new ArrayList<>();
mediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
jacksonConver.setSupportedMediaTypes(mediaTypes);
jacksonConver.setDefaultCharset(Charset.forName("UTF-8"));
converters.add(jacksonConver);
}
}