问题
- 很多业务数据中,存的都是
字典代码,但是 API 查询返回时,需要将原始字典名称 Title 返回
- 关联查询其实很简单,仅仅是增加一个字典表,条件为筛选
字典类型与字典代码
- 但是存在很多业务数据都需要这么关联,有时一次查询,可能要关联很多次字典表,导致 SQL 臃肿
解决方案
- 源码:登录 · 极狐GitLab
- 可以利用自定义序列化时,增加字段来解决此问题,示例:
- 查询用户数据时,返回的数据格式
[
{
"id": 1,
"username": "Alice",
"sex": "2"
}
]
- 我们希望,返回数据增加字段:
sexTitle,并且无需在原始对象中增加属性(如果返回对象是 domain/entity,并且使用了 MyBatis Plus,增加字段还要使用 @TableField(exist = false) 排除属性,防止 MyBatis Plus 反射时,查询数据库异常:字段不存在),返回数据格式[
{
"id": 1,
"username": "Alice",
"sex": "2",
"sexTitle": "女"
}
]
- 其他字典类型同理,以下功能,可以实现返回任意字典 Title,仅需两行代码
@DictAnnotation(type = "字典类型", fieldName = "字典代码对应的 title 字段名称")
@JsonSerialize(using = DictJsonSerializer.class)
DictAnnotation 注解
package cn.com.xuxiaowei.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 字典注解:用于在属性上添加注解,可自动查询字典数据
*
* @author xuxiaowei
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DictAnnotation {
/**
* 字典类型
*/
String type();
/**
* 字典代码对应的 title 字段名称
*/
String fieldName();
}
User 接口返回数据的对象
package cn.com.xuxiaowei.domain;
import cn.com.xuxiaowei.annotation.DictAnnotation;
import cn.com.xuxiaowei.serializer.DictJsonSerializer;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import lombok.Data;
import java.io.Serializable;
/**
* @author xuxiaowei
*/
@Data
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
private String username;
/**
* @see DictAnnotation 字典注解
* @see JsonSerialize 自定义序列化,用于增加 {@link DictAnnotation#fieldName()} 字段、查询数据库的核心逻辑
*/
@DictAnnotation(type = "sex", fieldName = "sexTitle")
@JsonSerialize(using = DictJsonSerializer.class)
private String sex;
}
DictMapper 字典查询
package cn.com.xuxiaowei.mapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
/**
* @author xuxiaowei
*/
@Mapper
public interface DictMapper {
/**
* 根据 字典类型、字典代码 查询 字典 Title
* @param type 字典类型
* @param code 字典代码
* @return 字典 Title
*/
String selectTitleByTypeAndCode(@Param("type") String type, @Param("code") String code);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="cn.com.xuxiaowei.mapper.DictMapper">
<resultMap id="UserResultMap" type="Dict">
<result column="dict_type" property="dictType"/>
<result column="dict_code" property="dictCode"/>
<result column="dict_title" property="dictTitle"/>
</resultMap>
<select id="selectTitleByTypeAndCode" resultType="java.lang.String">
select dict_title
from dict
where dict_type = #{type}
and dict_code = #{code}
</select>
</mapper>
DictJsonSerializer 自定义序列化,增加字典代码对应的 Title
package cn.com.xuxiaowei.serializer;
import cn.com.xuxiaowei.annotation.DictAnnotation;
import cn.com.xuxiaowei.mapper.DictMapper;
import cn.com.xuxiaowei.utils.SpringContextUtils;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
/**
* 自定义序列化,用于增加 {@link DictAnnotation#fieldName()} 字段、查询数据库的核心逻辑
*
* @author xuxiaowei
*/
@Slf4j
public class DictJsonSerializer extends JsonSerializer<String> implements ContextualSerializer {
private final DictAnnotation dictAnnotation;
public DictJsonSerializer() {
this.dictAnnotation = null;
}
public DictJsonSerializer(DictAnnotation dictAnnotation) {
this.dictAnnotation = dictAnnotation;
}
@Override
public void serialize(String value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider)
throws IOException {
// 字典代码:原始值,无需处理
jsonGenerator.writeString(value);
// 自定义字典注解存在 && 字典代码不为空
if (dictAnnotation != null && value != null) {
// 字典类型
String type = dictAnnotation.type();
// 字典代码对应的 title 字段名称
String fieldName = dictAnnotation.fieldName();
// TODO 增加 Redis,减少数据库压力
// 获取查询数据库的 Bean
DictMapper dictMapper = SpringContextUtils.getBean(DictMapper.class);
// 查询数据库,获取 字典 title
String title = dictMapper.selectTitleByTypeAndCode(type, value);
// 序列化时,增加 字典 title 字段
jsonGenerator.writeStringField(fieldName, title);
}
}
@Override
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property)
throws JsonMappingException {
if (property == null) {
// return this;
return new DictJsonSerializer();
}
// 获取 @JsonSerialize 序列化字段上的其他注解
DictAnnotation annotation = property.getAnnotation(DictAnnotation.class);
return new DictJsonSerializer(annotation);
}
}
SpringContextUtils 用于普通类获取 Bean
package cn.com.xuxiaowei.utils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
/**
* 用于普通类获取 Bean
*
* @author xuxiaowei
*/
@Component
public class SpringContextUtils implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SpringContextUtils.applicationContext = applicationContext;
}
/**
* 通过名称获取 Bean
*/
public static Object getBean(String name) {
return applicationContext.getBean(name);
}
/**
* 通过类型获取 Bean
*/
public static <T> T getBean(Class<T> clazz) {
return applicationContext.getBean(clazz);
}
/**
* 通过名称和类型获取 Bean
*/
public static <T> T getBean(String name, Class<T> clazz) {
return applicationContext.getBean(name, clazz);
}
}
1 个赞