一.基础数据回显说明
微服务项目中由于从服务独立的角度考虑,对数据库做了分库的处理。对于基础数据表来说,各个服务都是需要的。项目中在使用基础数据时,往往是在sql中写连接然后获取基础数据的名称。例:
select wi.name,
bc.city_name
from wms_info wi
left join bas_city bc on wi.city_code = bc.city_code
where wi.id=#{id}
由于做了分库处理,要保证各个业务库中都存一份基础数据表并且保证基础数据一致是很难做到的。这里我们用另外一种方式替换sql中的连接处理,实现基础数据回显功能。
实现思路:将基础数据缓存到redis中,数据发送给第三方或者web前端页面时,在做序列化的时间节点将基础数据代码替换为基础数名称。
二.序列化和反序列化
当两个进程进行远程通信时,可以相互发送各种类型的数据,包括文本、图片、音频、视频等, 而这些数据都会以二进制序列的形式在网络上传送。而 java 是面向对象的开发方式,一切都是 java 对象,想要实现 java 对象的网络传输,就可以使用序列化和反序列化来实现。发送方将需要发送的 Java 对象序列化转换为字节序列,然后在网络上传送;接收方接收到字符序列后,使用反序列化从字节序列中恢复出 Java 对象。在网络中数据的传输必须是序列化形式来进行的。
序列化执行时间节点。
响应处理(Response Handling):当一个Spring Boot应用的控制器方法处理完请求后,返回的对象会被自动序列化为JSON(如果使用了Spring MVC的默认配置)。
使用RestTemplate或WebClient:在使用这些Spring提供的REST客户端发送请求时,对象会在发送前被序列化为JSON或其他格式。
三.代码实现
1.基础数据回显类型枚举
package infosky.dmsp.util.redisBas;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 基础数据回显类型枚举
*/
@Getter
@AllArgsConstructor
public enum RedisBasTypeEnum {
/**
* 承运人
*/
DMSP_BAS_CARRIER("dmsp_bas_carrier"),
/**
* 城市
*/
DMSP_BAS_CITY("dmsp_bas_city");
private final String key;
}
2.自定义基础数据回显注解
package infosky.dmsp.util.redisBas;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 基础数据回显注解
* -将基础数据保存到redis中,业务数据中存基础数据主键,通过本注解将主键转换为表示值
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonSerialize(using = RedisBasSerializer.class)
public @interface RedisBas {
//缓存的数据类型
RedisBasTypeEnum type();
}
3.自定义序列化 用于将基础数据key转换成显示值
package infosky.dmsp.util.redisBas;
import java.io.IOException;
import java.util.Objects;
import org.springframework.data.redis.core.RedisTemplate;
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 com.fasterxml.jackson.databind.ser.std.StdSerializer;
import infosky.dmsp.util.SpringContextUtils;
/**
* 自定义序列化 用于将基础数据key转换成显示值
* ※springboot默认使用JsonSerializer作为序列化处理器
* StdSerializer继承自JsonSerializer
*/
public class RedisBasSerializer extends StdSerializer<Object> implements ContextualSerializer {
private static final long serialVersionUID = 1L;
private RedisBasTypeEnum type;
public RedisBasSerializer() {
super(Object.class);
}
public RedisBasSerializer(RedisBasTypeEnum type) {
super(Object.class);
this.type = type;
}
@Override
public void serialize(Object value, JsonGenerator jg, SerializerProvider sp) throws IOException {
if(Objects.isNull(value)) {
jg.writeObject(value);
return;
}
RedisTemplate rt = (RedisTemplate)SpringContextUtils.getBean("redisTemplate");
Object bas = rt.boundHashOps(type.getKey()).get(value);
if(bas != null) {
jg.writeObject(bas);
}else {
jg.writeObject(value);
}
}
/**
* 获取属性上的注解属性
*/
@Override
public JsonSerializer<?> createContextual(SerializerProvider sp, BeanProperty bp) throws JsonMappingException {
if (bp != null) {
if (Objects.equals(bp.getType().getRawClass(), String.class)) {
RedisBas de = bp.getAnnotation(RedisBas.class);
if (de == null) {
de = bp.getContextAnnotation(RedisBas.class);
}
if (de != null) {
return new RedisBasSerializer(de.type());
}
}
return sp.findValueSerializer(bp.getType(), bp);
}
return sp.findNullValueSerializer(null);
}
}
4.项目启动后将基础数据缓存到redis中
package infosky.dmsp.init;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import infosky.dmsp.common.CommonConstants;
import infosky.dmsp.dao.BasCarrierMapper;
import infosky.dmsp.entity.BasCarrier;
import infosky.dmsp.entity.BasCarrierExample;
/**
* 初始化时将基础数据保存到redis中
* ※实现ApplicationRunner接口,项目启动后就会直接这些run方法
*/
@Component
public class InitBas implements ApplicationRunner{
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private BasCarrierMapper basCarrierMapper;
/**
* 加载权限数据
*/
@Override
public void run(ApplicationArguments args) throws Exception {
//■1.删除redis中旧全部航司基础数据 ※dmsp_bas_carrier
redisTemplate.delete("dmsp_bas_carrier");
//■2.加载全部航司基础数据到redis
saveAllBasCarrier2Redis();
}
/**
* 加载全部航司基础数据到redis
*/
private void saveAllBasCarrier2Redis(){
//获取全部航司基础数据
//构造查询条件
BasCarrierExample bce = new BasCarrierExample();
BasCarrierExample.Criteria ca = bce.createCriteria();
List<BasCarrier> lstBc = basCarrierMapper.selectByExample(bce);
//构造redis中需要航司基础数据的map ※"CA":"Air China:中国国际航空公司"
Map<String,String> basCarrierMap = new HashMap<>();
for(BasCarrier bc : lstBc){
basCarrierMap.put(bc.getCarrierId(), bc.getCarrierNameCn());
}
redisTemplate.boundHashOps("dmsp_bas_carrier").putAll(basCarrierMap);
}
}
==以下为测试代码=========================================================
5.测试实体
package infosky.dmsp.util.redisBas;
import lombok.Data;
import lombok.ToString;
/**
* 测试实体
*/
@Data
@ToString
public class ExampleRedisBasEntity {
//基础数据缓存注解,这个type值要和缓存key对应上
@RedisBas(type=RedisBasTypeEnum.DMSP_BAS_CARRIER)
private String carrier;
private String msg;
}
6.测试controller类
package infosky.dmsp.util.redisBas;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 测试接口
* -基础数据表示值从缓存中读出
* -在前端页面 or postman中可确认脱敏数据
*/
@RestController
@RequestMapping("/redisBas")
public class ExampleRedisBasController {
@RequestMapping("/example")
public ExampleRedisBasEntity example() {
ExampleRedisBasEntity ee = new ExampleRedisBasEntity();
ee.setCarrier("CA");
ee.setMsg("测试从缓存中读取基础数据");
System.out.println(ee);
return ee;
}
}
四.测试结果
1.后端日志
2.页面传输结果