springboot 的单体服务 字典参数转译

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
云数据库 Tair(兼容Redis),内存型 2GB
简介: 本文介绍了如何在Spring Boot项目中使用缓存来管理字典参数,并确保前后端数据一致性。首先,通过`@EnableCaching`启用缓存功能,接着创建一个自定义的字典缓存类`DicCache`。然后,通过配置类将`DicCache`添加到`cacheManager`中。此外,对字典服务进行改造,使用`@CachePut`和`@CacheEvict`注解保证数据一致性。最后,实现自定义注解`@DicSerializer`和序列化处理类`DictSerializerHandel`,用于在序列化过程中自动转换字典值。通过这种方式,可最小化代码改动并提高系统性能。

问题难点:

  1. 公司的字典参数是保存在表里面的,所以增删改都需要保持一致。
  2. 这个字典释义是给前端展示给用户用的,我后台写接口时不用做转换工作。
  3. 如何代码改动最小...

code.........

开启缓存

使用springboot 自带的缓存;在项目代码中添加 @EnableCaching

java

代码解读

复制代码

@EnableCaching
@SpringBootApplication
public class MainApplication {

    public static void main(String[] args) {
       SpringApplication.run(MainApplication.class, args);
    }

}

创建一个字典缓存类

看了springboot 自带的缓存框架只需要实现Cache就可以啦;由于我这里为了方便,你可以直接继承ConcurrentMapCache就可以啦;为了防止缓存被更改我这里都是拷贝副本。

java

代码解读

复制代码

/**
 * 字典缓存
 * <br/>
 * date: 2024/5/24<br/>
 * version 0.1
 *
 * @author ls<br />
 */
@Component
public class DicCache implements Cache {

    /**
     * cache name
     */
    public static final String name = "DIC_CACHE";

    private final ConcurrentMap<String, CacheValue> store;

    public DicCache() {
        store = new ConcurrentHashMap<>();
    }

    public void put(String key, EntityDicAO value) {
        if (null == key || null == value) return;
        EntityDicAO newValue = (EntityDicAO) copy(value);
        this.store.put(key, new CacheValue(newValue));
    }

    public CacheValue putIfAbsent(String key, EntityDicAO value) {
        if (null == key || null == value) return null;

        EntityDicAO newValue = (EntityDicAO) copy(value);
        return this.store.putIfAbsent(key, new CacheValue(newValue));
    }

    /**
     * 从缓存中删除
     *
     * @param key index
     */
    public void evict(String key) {
        this.store.remove(key);
    }

    /**
     * 从缓存中删除
     *
     * @param key index
     */
    public void remove(String key) {
        this.evict(key);
    }


    /**
     * Return the cache name.
     */
    @Override
    public String getName() {
        return name;
    }

    /**
     * Return the underlying native cache provider.
     */
    @Override
    public Object getNativeCache() {
        return this.store;
    }

    public EntityDicAO get(String key) {
        CacheValue cacheValue = this.store.get(key);
        return (EntityDicAO) copy(cacheValue.getValue());
    }

    @Override
    public ValueWrapper get(Object key) {
        if (!this.store.containsKey(key)) {
            return null;
        }

        CacheValue cacheValue = this.store.get(key);
        return (ValueWrapper) copy(cacheValue);
    }

    @Override
    public <T> T get(Object key, Class<T> type) {
        return (T) this.get((String) key);
    }

    @Override
    public <T> T get(Object key, Callable<T> valueLoader) {
        CacheValue value = this.store.computeIfAbsent((String) key, r -> {
            try {
                T loaderValue = valueLoader.call();
                if ((loaderValue instanceof EntityDicAO)) {
                    EntityDicAO loaderValueDic = (EntityDicAO) loaderValue;
                    CacheValue cacheValue = new CacheValue((EntityDicAO) this.copy(loaderValueDic));
                    return cacheValue;
                }
                return null;
            } catch (Exception ex) {
                throw new ValueRetrievalException(key, valueLoader, ex);
            }
        });
        return (T) Optional.ofNullable(value).map(CacheValue::getValue).orElse(null);
    }

    @Override
    public void put(Object key, Object value) {
        if (value instanceof EntityDicAO) {
            this.put((String) key, (EntityDicAO) value);
            return;
        }

        if (value instanceof ServiceResult) {
            ServiceResult valueSt = (ServiceResult) value;
            Object data = valueSt.getData();
            if (data instanceof EntityDicAO) {
                this.put((String) key, (EntityDicAO) data);
            }
        }
    }

    @Override
    public ValueWrapper putIfAbsent(Object key, Object value) {
        CacheValue cacheValue = this.putIfAbsent((String) key, (EntityDicAO) value);
        return new SimpleValueWrapper(cacheValue.getValue());
    }

    @Override
    public void evict(Object key) {
        this.evict((String) key);
    }

    @PreDestroy
    public void clear() {
        this.store.clear();
    }

    public EntityDicAO findTypeWithCode(String type, String code) {
        Optional<CacheValue> value = this.store.values().stream().filter(e -> e.getKey().equals(CacheValue.generateKey(type, code))).findFirst();

        return (EntityDicAO) value.map(CacheValue::getValue).map(this::copy).orElse(null);
    }

    /**
     * 缓存包装
     */
    static class CacheValue implements ValueWrapper, Serializable {

        private final String key;

        private final EntityDicAO value;

        CacheValue(EntityDicAO value) {
            this.key = generateKey(value);
            this.value = value;
        }

        public String getKey() {
            return key;
        }

        public EntityDicAO getValue() {
            return value;
        }

        private static String generateKey(EntityDicAO value) {
            // 确保使用用户的唯一标识作为缓存键的一部分
            return generateKey(value.getType(), value.getCode());
        }

        protected static String generateKey(String type, String code) {
            // 确保使用用户的唯一标识作为缓存键的一部分
            return MessageFormat.format("{0}@{1}", type, code);
        }

        /**
         * Return the actual value in the cache.
         */
        @Override
        public Object get() {
            return this.getValue();
        }
    }

    /**
     * 复制字典
     *
     * @param value 字典
     * @return 新的字典
     */
    private <T extends Serializable> Serializable copy(T value) {
        return SerializationUtils.clone(value);
    }


}

添加一个 cache 配置类

DicCache 添加到cacheManager

java

代码解读

复制代码

@Configuration
public class DicCacheConfig {


    @Autowired
    private DicCache dicCache;


    @Bean
    public CacheManager cacheManager() {
        SimpleCacheManager cacheManager = new SimpleCacheManager();
        cacheManager.setCaches(Collections.singletonList(dicCache));
        return cacheManager;
    }

}

字典服务改造

现在只需要在字典服务中以最小的改动进行修改,添加@CachePut、@CacheEvict 注解就可以实现增删改一致啦

java

代码解读

复制代码

public class DicService extends ServiceImpl<EntityDicGeneratedMapper, EntityDicAO> {

/**
 * 保存参数信息
 *
 * @param dicAO 参数信息
 * @return 是否成功
 */
@CachePut(value = DicCache.name,key = "#dicAO.id",condition = "#result.succeed == true ")
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
public ServiceResult<EntityDicAO> createOrUpdateDic(EntityDicAO dicAO) {
    //......业务逻辑代码
}

/**
 * 删除参数信息
 *
 * @param id 参数信息ID
 * @return 是否成功
 */
@CacheEvict(value = DicCache.name,key = "#id",condition = "#result.succeed == true ")
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
public ServiceResult<Boolean> deleteDic(String id) {
   //......业务逻辑代码
}
}

实现字典转译

字典释义是给前端展示用的,那是不是数据序列化的时候再做字典转译就可以啦;springboot 默认使用Jackson来做序列化,实现一个jackson 自定义序列化

实现一个自定义注解

java

代码解读

复制代码

package x.x.x;

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;

/**
 * 字典序列化
 * @author ls
 * 2024/5/27
 * @version 1.0
 */
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonSerialize(using = DictSerializerHandel.class) //这里绑定处理类
public @interface DicSerializer {

    /** 字典类型 */
    String type();
    
    /**
    * 字典释义后的json 字段名
    * 默认是原始字段拼接一个Name
     */
    String name() default "";

    /**
     * 值类型
     */
    Type  valueType() default Type.SINGLE;

    /**
    *多值时的分隔符
     */
    String separator() default ",";

    enum Type{
        /**
        *单值
        */
        SINGLE,
         /**
        *多值
        */
        MULTI_VALUE
    }
}

实现一个基于注解的jackson 自定义序列化,这里需要注意一下不要导错包 com.fasterxml.jackson.databind 这个包下的。

java

代码解读

复制代码

package x.x.x;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;

import java.io.IOException;
import java.util.Arrays;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * 字典序列化处理
 * @author ls
 * 2024/5/27
 * @version 1.0
 */
@JacksonStdImpl
public class DictSerializerHandel extends StdSerializer<String> implements ContextualSerializer {

    private String type;

    private String name;

    private DicSerializer.Type valueType;

    private String separator;

    public DictSerializerHandel() {
        super(String.class);
    }

    @Override
    public void serialize(String value, JsonGenerator gen, SerializerProvider provider) throws IOException {
        if (Objects.isNull(value)) {
            gen.writeObject(null);
            return;
        }

        String currentName = gen.getOutputContext().getCurrentName();

        if(StringUtils.isEmpty(value) || StringUtils.isEmpty(currentName)){
            gen.writeString("");
            return;
        }

        gen.writeString(value);

        // 通过数据字典类型和value获取name
        DicService dicService = SpringUtils.getBean(DicService.class);

        String translatedValue = "";

        switch (valueType){
            case SINGLE:
                EntityDicAO dic = dicService.getDicByTypeWithCode(type, value).getData();
                translatedValue= Optional.ofNullable(dic).map(EntityDicAO::getName).orElse("");
                break;
            case MULTI_VALUE:
                translatedValue = Arrays.stream(value.split(separator)).map(e->dicService.getDicByTypeWithCode(type, e).getData()).filter(Objects::nonNull).map(EntityDicAO::getName).filter(StringUtils::isNotBlank)
                        .distinct().collect(Collectors.joining(separator));
                break;
            default:
        }

        gen.writeStringField(
                StringUtils.isBlank(name) ? currentName.concat("Name") : name,
                translatedValue
        );
    }

    @Override
    public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty beanProperty) {
        if (beanProperty != null && beanProperty.getType().getRawClass().equals(String.class)) {
            // 获取注解的参数
            DicSerializer annotation = beanProperty.getAnnotation(DicSerializer.class);
            if (null != annotation) {
                type = annotation.type();
                name = annotation.name();
                valueType = annotation.valueType();
                separator = annotation.separator();
                return this;
            }
        }
        return new ToStringSerializer();
    }
}

到此就大致可以1啦..................

使用

java

代码解读

复制代码

public class EntityEvaluation  {
    
    // ............
    
    /**
     * 评估类型
     */
    @DicSerializer(type = "assessment")
    private String type;

    /**
     * 评估分析-长期目标 多选 多个用‘,‘号分割
     */
    @DicSerializer(type = "PATIENT_CONCERNS",valueType = DicSerializer.Type.MULTI_VALUE)
    private String patientConcerns;

    // ............
}


转载来源:https://juejin.cn/post/7377589884187967498

相关文章
|
1月前
|
JSON Java 网络架构
elasticsearch学习四:使用springboot整合 rest 进行搭建elasticsearch服务
这篇文章介绍了如何使用Spring Boot整合REST方式来搭建和操作Elasticsearch服务。
114 4
elasticsearch学习四:使用springboot整合 rest 进行搭建elasticsearch服务
|
1月前
|
JSON NoSQL Java
springBoot:jwt&redis&文件操作&常见请求错误代码&参数注解 (九)
该文档涵盖JWT(JSON Web Token)的组成、依赖、工具类创建及拦截器配置,并介绍了Redis的依赖配置与文件操作相关功能,包括文件上传、下载、删除及批量删除的方法。同时,文档还列举了常见的HTTP请求错误代码及其含义,并详细解释了@RequestParam与@PathVariable等参数注解的区别与用法。
|
1月前
|
监控 Dubbo Java
dubbo学习三:springboot整合dubbo+zookeeper,并使用dubbo管理界面监控服务是否注册到zookeeper上。
这篇文章详细介绍了如何将Spring Boot与Dubbo和Zookeeper整合,并通过Dubbo管理界面监控服务注册情况。
70 0
dubbo学习三:springboot整合dubbo+zookeeper,并使用dubbo管理界面监控服务是否注册到zookeeper上。
|
1月前
|
自然语言处理 Java Maven
elasticsearch学习二:使用springboot整合TransportClient 进行搭建elasticsearch服务
这篇博客介绍了如何使用Spring Boot整合TransportClient搭建Elasticsearch服务,包括项目创建、Maven依赖、业务代码和测试示例。
78 0
elasticsearch学习二:使用springboot整合TransportClient 进行搭建elasticsearch服务
|
2月前
|
JSON Java 数据格式
springboot 参数统一处理
springboot 参数统一处理
|
30天前
|
监控 Java Maven
springboot学习二:springboot 初创建 web 项目、修改banner、热部署插件、切换运行环境、springboot参数配置,打包项目并测试成功
这篇文章介绍了如何快速创建Spring Boot项目,包括项目的初始化、结构、打包部署、修改启动Banner、热部署、环境切换和参数配置等基础操作。
111 0
|
2月前
|
Java Spring
spring boot 启动项目参数的设定
spring boot 启动项目参数的设定
|
3月前
|
小程序 JavaScript Java
微信小程序+SpringBoot接入后台服务,接口数据来自后端
这篇文章介绍了如何将微信小程序与SpringBoot后端服务进行数据交互,包括后端接口的编写、小程序获取接口数据的方法,以及数据在小程序中的展示。同时,还涉及到了使用Vue搭建后台管理系统,方便数据的查看和管理。
微信小程序+SpringBoot接入后台服务,接口数据来自后端
|
3月前
|
NoSQL Java Linux
springboot+redis+虚拟机 springboot连接linux虚拟机中的redis服务
该博客文章介绍了如何在Spring Boot项目中通过配置和代码实现连接运行在Linux虚拟机上的Redis服务,并提供了详细的步骤和测试结果截图。
springboot+redis+虚拟机 springboot连接linux虚拟机中的redis服务