自定义注解+Aop+Redis+SpringBoot应用于数据字典

我们在项目的开发中,一般都会用到数据字典,但这有一点比较麻烦的是,数据库存的是数字,但在页面上进行展示的是汉字,通常的做法是把数字返给前端,然后前端再把数字进行转义;还有就是用SQL进行联表查询,把汉字查出来一起返给前端。其实还有更好的解决方案,那就是用自定义注解+Aop

先来看表设计:

t_annotation_data_dict表

t_annotation_data_item表

自定义注解+Aop+Redis+SpringBoot应用于数据字典_第1张图片

t_annotation_student表

自定义注解+Aop+Redis+SpringBoot应用于数据字典_第2张图片

现在要做的效果就是不用联表查询,将t_annotation_student表里用数字存放的字段相对应的汉字一起返给前端

相关的pom依赖:


    com.alibaba
    fastjson
    1.2.4

相关准备工作代码:

application.yml

server:
  servlet:
    context-path: /annotation
  port: 8080

spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/demoSite1?useUnicode=true&characterEncoding=utf8
    username: root
    password: root
  redis:
    database: 10
    host: 192.168.255.101
    port: 6379
    password: 123456
    jedis:
      pool:
        max-active: 100
        max-idle: 3
        max-wait: -1
        min-idle: 0
    timeout: 10000

mybatis:
  type-aliases-package: com.ue.mapper
  mapper-locations: classpath:mapper/*.xml
  configuration:
    map-underscore-to-camel-case: true

logging:
  level:
    com.ue.mapper: debug

pagehelper:
  helperDialect: mysql
  reasonable: true
  supportMethodsArguments: true
  params: count=countSql

实体类代码

package com.ue.entity;

public class DataItem {
    private Integer id;

    private String datasource;

    private String code;

    private String val;

    public DataItem(Integer id, String datasource, String code, String val) {
        this.id = id;
        this.datasource = datasource;
        this.code = code;
        this.val = val;
    }

    public DataItem() {
        super();
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getDatasource() {
        return datasource;
    }

    public void setDatasource(String datasource) {
        this.datasource = datasource;
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public String getVal() {
        return val;
    }

    public void setVal(String val) {
        this.val = val;
    }
}
package com.ue.entity;

import com.ue.annotation.Dict;

public class Student {
    private Integer id;

    private String name;

    @Dict(dictDataSource = "stu_level")
    private Integer stuLevel;

    @Dict(dictDataSource = "stu_english",dictText = "stuEnglishDictText")
    private Integer englishLevel;

    @Dict(dictDataSource = "stu_hobby")
    private String stuHobby;

    public Student(Integer id, String name, Integer stuLevel, Integer englishLevel, String stuHobby) {
        this.id = id;
        this.name = name;
        this.stuLevel = stuLevel;
        this.englishLevel = englishLevel;
        this.stuHobby = stuHobby;
    }

    public Student() {
        super();
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getStuLevel() {
        return stuLevel;
    }

    public void setStuLevel(Integer stuLevel) {
        this.stuLevel = stuLevel;
    }

    public Integer getEnglishLevel() {
        return englishLevel;
    }

    public void setEnglishLevel(Integer englishLevel) {
        this.englishLevel = englishLevel;
    }

    public String getStuHobby() {
        return stuHobby;
    }

    public void setStuHobby(String stuHobby) {
        this.stuHobby = stuHobby;
    }
}

Mapper.java代码

package com.ue.mapper;

import com.ue.entity.DataItem;
import org.springframework.stereotype.Repository;

@Repository
public interface DataItemMapper {
    /**
     * 根据datasource与code查询字典的方法
     * @author LiJun
     * @Date 2019/11/28
     * @Time 10:40
     * @param dataItem
     * @return com.ue.entity.DataItem
     */
    DataItem selectByDatasourceCode(DataItem dataItem);
}

自定义注解+Aop+Redis+SpringBoot应用于数据字典_第3张图片

package com.ue.mapper;

import com.ue.entity.Student;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface StudentMapper {
    List listPager(Student student);
}

自定义注解+Aop+Redis+SpringBoot应用于数据字典_第4张图片

service层代码

package com.ue.service;

import org.springframework.cache.annotation.Cacheable;

public interface DataItemService {
    /**
     * 将查询出来的字典文本值存到Redis里
     * @author LiJun
     * @Date 2019/11/28
     * @Time 10:40
     * @param dataSource
     * @param key
     * @return java.lang.String
     */
    @Cacheable(value = "my-redis-cache2")
    String selectByDatasourceKey(String dataSource, String key);
}
package com.ue.service;

import com.ue.entity.Student;
import com.ue.util.PageBean;

import java.util.List;

public interface StudentService {
    List listPager(Student student);
}

service实现类代码

package com.ue.service.impl;

import com.ue.entity.DataItem;
import com.ue.mapper.DataItemMapper;
import com.ue.service.DataItemService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class DataItemServiceImpl implements DataItemService {
    @Autowired
    private DataItemMapper dataItemMapper;

    @Override
    public String selectByDatasourceKey(String dataSource, String key) {
        DataItem dataItem = new DataItem();
        dataItem.setDatasource(dataSource);
        dataItem.setCode(key);
        return dataItemMapper.selectByDatasourceCode(dataItem).getVal();
    }
}
package com.ue.service.impl;

import com.ue.entity.Student;
import com.ue.mapper.StudentMapper;
import com.ue.service.StudentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class StudentServiceImpl implements StudentService {
    @Autowired
    private StudentMapper studentMapper;
    @Override
    public List listPager(Student student) {
        return studentMapper.listPager(student);
    }
}

Redis配置类代码

package com.ue.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.lang.reflect.Method;
import java.time.Duration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * redis配置类
 * @author LiJun
 * @Date 2019/11/27
 * @Time 17:44
 */
@Configuration
@EnableCaching//开启注解式缓存
public class RedisConfig extends CachingConfigurerSupport {
 
    /**
     * 生成key的策略 根据类名+方法名+所有参数的值生成唯一的一个key
     * @author LiJun
     * @Date 2019/11/28
     * @Time 9:12
     * @param
     * @return org.springframework.cache.interceptor.KeyGenerator
     */
    @Bean
    @Override
    public KeyGenerator keyGenerator() {
        return new KeyGenerator() {
            @Override
            public Object generate(Object target, Method method, Object... params) {
                StringBuilder sb = new StringBuilder();
                sb.append(target.getClass().getName());
                sb.append(method.getName());
                for (Object obj : params) {
                    sb.append(obj.toString());
                }
                return sb.toString();
            }
        };
    }
 
    /**
     * 管理缓存
     * @author LiJun
     * @Date 2019/11/28
     * @Time 9:12
     * @param redisConnectionFactory
     * @return org.springframework.cache.CacheManager
     */
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        //通过Spring提供的RedisCacheConfiguration类,构造一个自己的redis配置类,从该配置类中可以设置一些初始化的缓存命名空间
        //及对应的默认过期时间等属性,再利用RedisCacheManager中的builder.build()的方式生成cacheManager:
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();//生成一个默认配置,通过config对象即可对缓存进行自定义配置
        config = config.entryTtl(Duration.ofMinutes(1))//设置缓存的默认过期时间,也是使用Duration设置
                .disableCachingNullValues();//不缓存空值
 
        //设置一个初始化的缓存空间set集合
        Set cacheNames = new HashSet<>();
        cacheNames.add("my-redis-cache1");
        cacheNames.add("my-redis-cache2");
 
        //对每个缓存空间应用不同的配置
        Map configMap = new HashMap<>();
        configMap.put("my-redis-cache1", config);
        configMap.put("my-redis-cache2", config.entryTtl(Duration.ofDays(3)));
 
        RedisCacheManager cacheManager = RedisCacheManager.builder(redisConnectionFactory)//使用自定义的缓存配置初始化一个cacheManager
                .initialCacheNames(cacheNames)//注意这两句的调用顺序,一定要先调用该方法设置初始化的缓存名,再初始化相关的配置
                .withInitialCacheConfigurations(configMap)
                .build();
        return cacheManager;
    }
 
    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);
 
        //使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式)
        Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class);
 
        ObjectMapper mapper = new ObjectMapper();
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        serializer.setObjectMapper(mapper);
 
        template.setValueSerializer(serializer);
        //使用StringRedisSerializer来序列化和反序列化redis的key值
        template.setKeySerializer(new StringRedisSerializer());
        template.afterPropertiesSet();
        return template;
    }
 
    @Bean
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory factory) {
        StringRedisTemplate stringRedisTemplate = new StringRedisTemplate();
        stringRedisTemplate.setConnectionFactory(factory);
        return stringRedisTemplate;
    }
}

重点代码:

studentController.java

package com.ue.controller;

import com.ue.entity.Student;
import com.ue.service.StudentService;
import com.ue.util.PageBean;
import com.ue.util.PageUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import java.util.List;

@Controller
@RequestMapping("/student")
public class StudentController {
    @Autowired
    private StudentService studentService;

    @ResponseBody
    @RequestMapping("/listPager")
    public PageUtils listPager(Student student, HttpServletRequest req){
        PageBean pageBean = new PageBean();
        pageBean.setRequest(req);
        List list = this.studentService.listPager(student);
        pageBean.setTotal(list.size());
        PageUtils pageUtils = new PageUtils(list,pageBean.getTotal());
        return pageUtils;
    }
}

DictAspect.java

package com.ue.aspect;

import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ue.annotation.Dict;
import com.ue.service.DataItemService;
import com.ue.util.ObjConvertUtils;
import com.ue.util.PageUtils;
import com.ue.util.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * 用于翻译字典的切面类
 * @author LiJun
 * @Date 2019/11/28
 * @Time 9:55
 */
@Aspect
@Component
@Slf4j
public class DictAspect {
    private static String DICT_TEXT_SUFFIX = "_dictText";

    @Autowired
    private DataItemService dataItemService;

    //定义切点Pointcut拦截所有对服务器的请求
    @Pointcut("execution( * com.ue.controller.*.*(..))")
    public void excudeService() {

    }

    /**
     * 这是触发excudeService的时候会执行的,在环绕通知中目标对象方法被调用后的结果进行再处理
     * @param pjp
     * @return
     * @throws Throwable
     */
    @Around("excudeService()")
    public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
        //这是定义开始事件
        long time1 = System.currentTimeMillis();
        //这是方法并获取返回结果
        Object result = pjp.proceed();
        //这是获取到结束时间
        long time2 = System.currentTimeMillis();
        log.info("获取JSON数据耗时:" + (time2 - time1) + "ms");
        //解析开始时间
        long start = System.currentTimeMillis();
        //开始解析(翻译字段内部的值凡是打了@Dict这玩意的都会被翻译)
        this.parseDictText(result);
        //解析结束时间
        long end = System.currentTimeMillis();
        log.info("解析注入JSON数据耗时:" + (end - start) + "ms");
        return result;
    }

    /**
     * 本方法针对返回对象为Result的PageUtils的分页列表数据进行动态字典注入
     * 字典注入实现 通过对实体类添加注解@Dict来标识需要的字典内容,字典分为单字典dataSource即可
     * 示例为Student   
     * 字段为stu_sex添加了注解@Dict(dicDataSource = "stu_sex")会在字典服务立马查出来对应的text然后在请求list的时候将这个字典text,以字段名称加_dictText形式返回到前端
     * 例如输入当前返回值的就会多出一个stu_sex_dictText字段
     * {
     *      stu_sex:1,
     *      stu_sex_dictText:"男"
     * }
     * 前端直接取stu_sex_dictText的值在table里面进行展示,无需再进行前端的字典转换了
     * customRender:function (text) {
     *      if(text==1){
     *          return "男";
     *      }else if(text==2){
     *          return "女";
     *      }else{
     *          return text;
     *      }
     * }
     * 目前vue是这么进行字典渲染到table上的多了就很麻烦了 这个直接在服务端渲染完成前端可以直接用
     * @param result
     */
    private void parseDictText(Object result) {
        if (result instanceof PageUtils) {
            List items = new ArrayList<>();
            PageUtils pageUtils = (PageUtils) result;
            //循环查找出来的数据
            for (Object record : pageUtils.getData()) {
                ObjectMapper mapper = new ObjectMapper();
                String json = "{}";
                try {
                    //解决@JsonFormat注解解析不了的问题详见SysAnnouncement类的@JsonFormat
                    json = mapper.writeValueAsString(record);
                } catch (JsonProcessingException e) {
                    log.error("Json解析失败:" + e);
                }
                JSONObject item = JSONObject.parseObject(json);

                //解决继承实体字段无法翻译问题
                for (Field field : ObjConvertUtils.getAllFields(record)) {
                    //解决继承实体字段无法翻译问题
                    if (field.getAnnotation(Dict.class) != null) {//如果该属性上面有@Dict注解,则进行翻译
                        String datasource = field.getAnnotation(Dict.class).dictDataSource();//拿到注解的dictDataSource属性的值
                        String text = field.getAnnotation(Dict.class).dictText();//拿到注解的dictText属性的值
                        //获取当前带翻译的值
                        String key = String.valueOf(item.get(field.getName()));
                        //翻译字典值对应的text值
                        String textValue = translateDictValue(datasource, key);
                        //DICT_TEXT_SUFFIX的值为,是默认值:
                        //public static final String DICT_TEXT_SUFFIX = "_dictText";
                        log.debug("字典Val: " + textValue);
                        log.debug("翻译字典字段:" + field.getName() + DICT_TEXT_SUFFIX + ": " + textValue);
                        //如果给了文本名
                        if (!StringUtils.isBlank(text)) {
                            item.put(text, textValue);
                        } else {
                            //走默认策略
                            item.put(field.getName() + DICT_TEXT_SUFFIX, textValue);
                        }
                    }
                    //date类型默认转换string格式化日期
                    if (field.getType().getName().equals("java.util.Date") && field.getAnnotation(JsonFormat.class) == null && item.get(field.getName()) != null) {
                        SimpleDateFormat aDate = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                        item.put(field.getName(), aDate.format(new Date((Long) item.get(field.getName()))));
                    }
                }
                items.add(item);
            }
            pageUtils.setData(items);
        }
    }

    /**
     * 翻译字典文本
     * @param datasource
     * @param key
     * @return
     */
    private String translateDictValue(String datasource, String key) {
        //如果key为空直接返回就好了
        if (ObjConvertUtils.isEmpty(key)) {
            return null;
        }
        StringBuffer textValue = new StringBuffer();
        //分割key值
        String[] keys = key.split(",");
        //循环keys中的所有值
        for (String k : keys) {
            String tmpValue = null;
            log.debug("字典key:" + k);
            if (k.trim().length() == 0) {
                continue;//跳过循环
            }
            tmpValue = dataItemService.selectByDatasourceKey(datasource, k.trim());

            if (tmpValue != null) {
                if (!"".equals(textValue.toString())) {
                    textValue.append(",");
                }
                textValue.append(tmpValue);
            }
        }
        //返回翻译的值
        return textValue.toString();
    }
}

Dict.java

package com.ue.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 专门用于数据字典中的数字转汉字的自定义注解
 * @author LiJun
 * @Date 2019/11/27
 * @Time 16:08
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Dict {

    /**
     * 方法描述:数据dataSource
     * @return 返回类型: String
     */
    String dictDataSource();

    /**
     * 方法描述:这是返回后Put到josn中的文本key值
     * @return 返回类型: String
     */
    String dictText() default "";
}

PageUtils.java

package com.ue.util;

import java.io.Serializable;
import java.util.List;

public class PageUtils implements Serializable {
    private static final long serialVersionUID = 1L;
    //这是总行数
    private long total;
    //这是保持查询出来的数据
    private List data;

    /**
     * @param list 保存数据
     * @param total 查到多行数据
     */
    public PageUtils(List list, long total) {
        this.data = list;
        this.total = total;
    }

    public long getTotal() {
        return total;
    }

    public void setTotal(int total) {
        this.total = total;
    }

    public List getData() {
        return data;
    }

    public void setData(List data) {
        this.data = data;
    }

    public static long getSerialVersionUID() {
        return serialVersionUID;
    }
}

ObjConvertUtils.java

package com.ue.util;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class ObjConvertUtils {
    /**
     * 获取类的所有属性,包括父类
     * @param object
     * @return
     */
    public static Field[] getAllFields(Object object) {
        Class clazz = object.getClass();
        List fieldList = new ArrayList<>();
        while (clazz != null) {
            fieldList.addAll(new ArrayList<>(Arrays.asList(clazz.getDeclaredFields())));
            clazz = clazz.getSuperclass();
        }
        Field[] fields = new Field[fieldList.size()];
        fieldList.toArray(fields);
        return fields;
    }

    public static boolean isEmpty(Object object) {
        if (object == null) {
            return (true);
        }
        if ("".equals(object)) {
            return (true);
        }
        if ("null".equals(object)) {
            return (true);
        }
        return (false);
    }
}

然后启动项目访问http://localhost:8080/annotation/student/listPager

返回的数据如下:

自定义注解+Aop+Redis+SpringBoot应用于数据字典_第5张图片

从下图中的打印可以看到如果不使用缓存技术的话,每次将数字转义为汉字的时候都会去查一次数据库,性能比较低

自定义注解+Aop+Redis+SpringBoot应用于数据字典_第6张图片

所以真正应用到项目中的话,还需要使用缓存技术,下图为使用了Redis将数据字典的数据进行缓存后的效果

自定义注解+Aop+Redis+SpringBoot应用于数据字典_第7张图片

源码地址:https://github.com/java-LJ/annotation-demo.git

你可能感兴趣的:(springboot,总结)