自定义注解&Spring AOP实现为原程序加入Redis缓存支持(可重用)

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 应用场景##数据访问采用ORM方式(Hibernate) 直接访问数据库,在访问量小、并发性小、数据量小时,可正常访问,反之则服务响应能力低。福利彩蛋目标&要解决的问题##自定义注解&Spring AOP为项目加入Redis缓存依赖提高应用程序的响应能力(可重用)项目扩充承接于http://www.

应用场景##

  • 数据访问采用ORM方式(Hibernate) 直接访问数据库,在访问量小、并发性小、数据量小时,可正常访问,反之则服务响应能力低。
  • 福利彩蛋

目标&要解决的问题##

  • 自定义注解&Spring AOP为项目加入Redis缓存依赖提高应用程序的响应能力(可重用)

项目扩充承接于http://www.jianshu.com/p/25039d901ac2

难点##

设置缓存的失效策略,缓存数据的Struct选取,切面(Aspect)的编写

方法&扩充步骤##

1.扩充build.gradle 脚本文件

    //https://mvnrepository.com/artifact/org.springframework.data/spring-data-redis 项目添加redis支持
    compile group: 'org.springframework.data', name: 'spring-data-redis', version: '1.4.1.RELEASE'
    // https://mvnrepository.com/artifact/redis.clients/jedis redis 基于java的Redis客户端调用实现
    compile group: 'redis.clients', name: 'jedis', version: '2.6.1'
    // https://mvnrepository.com/artifact/com.alibaba/fastjson
    // 采用阿里巴巴fastjson 进行对象&json字符串的序列化与反序列化
    compile group: 'com.alibaba', name: 'fastjson', version: '1.2.21'

2.扩充Spring 配置文件,添加Redis相关Java Bean 到Ioc容器中
为了符合开闭原则,重新创建Spring 配置文件 spring-redis.xml




    
        
       
        
    

    

    
        
    


3.自定义两个注解

  • RedisCahe: 标识缓存 注解
  • RedisEvit: 标识缓存清除 注解

代码如下:
RedisCahe.java

package com.fxmms.common.rediscache.redisannotation;

import java.lang.annotation.*;

/**
 * Created by mark on 16/11/29.
 * @usage  缓存注解类
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface RedisCache {
    Class type();//被代理类的全类名,在之后会做为redis hash 的key
}

RedisEvit.java

package com.fxmms.common.rediscache.redisannotation;

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

/**
 * Created by mark on 16/11/29.
 * @usage 清除过期缓存注解,放置于update delete insert 类型逻辑之上
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RedisEvict {
    Class type();
}

4.RedisCacheAspect.java 切面程序

package com.fxmms.common.rediscache.redisaspect;

import com.fxmms.common.rediscache.redisannotation.RedisCache;
import com.fxmms.common.rediscache.redisannotation.RedisEvict;
import com.fxmms.common.util.FastJsonUtil;
import com.fxmms.common.util.JsonUtil;
import org.apache.log4j.Logger;
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.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.List;

/**
 * Created by mark on 16/11/29.
 */
@Aspect
@Component
@SuppressWarnings(value = {"rawtypes", "unchecked"})
public class RedisCacheAspect {

    private static final Logger logger = Logger.getLogger(RedisCacheAspect.class);
    /**
     * 分隔符 生成key 格式为 类全类名|方法名|参数所属类全类名
     **/
    private static final String DELIMITER = "|";
    /**
     * spring-redis.xml配置连接池、连接工厂、Redis模板
     **/
    @Autowired
    @Qualifier("redisTemplateForString")
    StringRedisTemplate srt;

    /**
     * Service层切点 使用到了我们定义的 RedisCache 作为切点表达式。
     * 而且我们可以看出此表达式基于 annotation。
     * 并且用于内建属性为查询的方法之上
     */
    @Pointcut("@annotation(com.fxmms.common.rediscache.redisannotation.RedisCache)")
    public void redisCacheAspect() {
    }

    /**
     * Service层切点 使用到了我们定义的 RedisEvict 作为切点表达式。
     * 而且我们可以看出此表达式是基于 annotation 的。
     * 并且用于内建属性为非查询的方法之上,用于更新表
     */
    @Pointcut("@annotation(com.fxmms.common.rediscache.redisannotation.RedisEvict)")
    public void redisCacheEvict() {
    }

    @Around("redisCacheAspect()")
    public Object cache(ProceedingJoinPoint joinPoint) {
        // 得到类名、方法名和参数
        String clazzName = joinPoint.getTarget().getClass().getName();
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();

        // 根据类名、方法名和参数生成Key
        logger.info("key参数: " + clazzName + "." + methodName);
        //System.out.println("key参数: " + clazzName + "." + methodName);
        String key = getKey(clazzName, methodName, args);
        if (logger.isInfoEnabled()) {
            logger.info("生成key: " + key);
        }

        // 得到被代理的方法
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();

        // 得到被代理的方法上的注解
        Class modelType = method.getAnnotation(RedisCache.class).type();

        // 检查Redis中是否有缓存
        String value = (String) srt.opsForHash().get(modelType.getName(), key);

        // 得到被代理方法的返回值类型
        Class returnType = ((MethodSignature) joinPoint.getSignature()).getReturnType();

        // result是方法的最终返回结果
        Object result = null;
        try {
            if (null == value) {
                if (logger.isInfoEnabled()) {
                    logger.info("缓存未命中");
                }

                // 调用数据库查询方法
                result = joinPoint.proceed(args);

                // 序列化查询结果
                String json = FastJsonUtil.toJsonString(result);
                //String json = GsonUtil.toJson(result);
                System.out.println("打印:"+json);

                // 序列化结果放入缓存
                srt.opsForHash().put(modelType.getName(), key, json);
            } else {

                // 缓存命中
                if (logger.isInfoEnabled()) {
                    logger.info("缓存命中, value = " + value);
                }

                result = value;
                // 反序列化 从缓存中拿到的json字符串
                result = FastJsonUtil.toObject(value, returnType);
                //result = GsonUtil.fromJson(value,returnType);
                System.out.println(result.toString());

                if (logger.isInfoEnabled()) {
                    logger.info("gson反序列化结果 = " + result);
                }
            }
        } catch (Throwable e) {
            logger.error("解析异常",e);
        }
        return result;
    }

    /**
     *      * 在方法调用前清除缓存,然后调用业务方法
     *      * @param joinPoint
     *      * @return
     *      * @throws Throwable
     *      
     */
    @Around("redisCacheEvict()")
    public Object evictCache(ProceedingJoinPoint joinPoint) throws Throwable {
        // 得到被代理的方法
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        // 得到被代理的方法上的注解
        Class modelType = method.getAnnotation(RedisEvict.class).type();
        if (logger.isInfoEnabled()) {
            logger.info("清空缓存 = " + modelType.getName());
        }
        // 清除对应缓存
        srt.delete(modelType.getName());
        return joinPoint.proceed(joinPoint.getArgs());
    }

    /**
     * @param json
     * @param clazz
     * @param modelType
     * @return 反序列化json字符串
     * Question 遇到问题,如何将复杂json字符串解析为复杂java object
     */
    private Object deserialize(String json, Class clazz, Class modelType) {
        // 序列化结果是List对象
        if (clazz.isAssignableFrom(List.class)) {
            return JsonUtil.jsonToList(json, modelType);
        }
        // 序列化结果是普通对象
        return JsonUtil.jsonToPojo(json, clazz);
    }

    private String serialize(Object result, Class clazz) {
        return JsonUtil.objectToJson(result);
    }

    /**
     *      * 根据类名、方法名和参数生成Key
     *      * @param clazzName
     *      * @param methodName
     *      * @param args
     *      * @return key格式:全类名|方法名|参数类型
     *      
     */
    private String getKey(String clazzName, String methodName, Object[] args) {
        StringBuilder key = new StringBuilder(clazzName);
        key.append(DELIMITER);
        key.append(methodName);
        key.append(DELIMITER);

        for (Object obj : args) {
            key.append(obj.getClass().getSimpleName());
            key.append(DELIMITER);
        }

        return key.toString();
    }
}

5.FastJsonUtil.java

 package com.fxmms.common.util;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson.serializer.ValueFilter;

import java.util.List;

/**
 * Created by mark on 16/11/30.
 * 采用阿里巴巴fastjson 进行对象&json字符串的序列化与反序列化
 */
public class FastJsonUtil {
    /**
     * @param object
     * @return 将java对象转化为json字符串
     */
    public static String toJsonString(Object object) {
        return JSON.toJSONString(object,filter,SerializerFeature.DisableCircularReferenceDetect);
    }

    /**
     * 添加过滤器使数据库中字段为NULL的字段为""
     */
    private static ValueFilter filter = new ValueFilter() {
        @Override
        public Object process(Object obj, String s, Object v) {
            if (v == null)
                return "";
            return v;
        }
    };
    /**
     * @param json
     * @param cla
     * @param 
     * @return 将json字符串转化为java对象
     */
    public static  T toObject(String json, Class cla) {
        return JSON.parseObject(json, cla);
    }

    public static  List toList(String json, Class t) {
        return JSON.parseArray(json, t);
    }

}

6.业务逻辑层设置缓存即扩充service-applicationContext.xml加入切面支持





    

    
    
    
        
    
    
    
        
    
    
    
        
    
    
    

    
        
    


7.业务逻辑层应用缓存

package com.fxmms.www.service;

import com.fxmms.common.jniutil.GetDownloadIDUtil;
import com.fxmms.common.macutil.CountBetweenMacByMacStr;
import com.fxmms.common.poiutil.ReadExcelUtil;
import com.fxmms.common.rediscache.redisannotation.RedisCache;
import com.fxmms.common.rediscache.redisannotation.RedisEvict;
import com.fxmms.common.ro.ControllerResult;
import com.fxmms.common.ro.DtoResultWithPageInfo;
import com.fxmms.www.dao.AdminDao;
import com.fxmms.www.dao.MacDao;
import com.fxmms.www.dao.TaskDao;
import com.fxmms.www.domain.Admin;
import com.fxmms.www.domain.Mac;
import com.fxmms.www.domain.Task;
import com.fxmms.www.dto.MacDto;
import com.fxmms.www.qo.MacQo;
import com.fxmms.www.thunderinterfaceutil.VisitThunderInterface;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.File;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * Created by mark on 16/11/7.
 *
 * @usage Mac地址操作业务逻辑层
 */
@Service
public class MacService {
    @Autowired
    MacDao macDao;
    @Autowired
    AdminDao adminDao;
    @Autowired
    TaskDao taskDao;

    /**
     * @param macStr
     * @param username
     * @return mac
     * @usage 判断数据库中是否已经存储过对应的mac
     * 防止数据库中存储多个同样的mac地址
     */
    @Transactional
    @RedisEvict(type=Mac.class)
    public Mac doJudgementBySingleMacStr(String macStr, String username) {
        Mac mac = macDao.getByUniqueKey("macAddr", macStr);
        if (mac == null) {
            //1.单个mac地址转化为downloadId
            String downLoadId = GetDownloadIDUtil.getDownLoadId(macStr);
            Task task = new Task();//单个mac所属task's id
            task.setDate(new Date());
            task.setFlag(0);//录入未成功
            taskDao.save(task);
            Admin admin = adminDao.getByUniqueKey("userName", username);
            mac = new Mac();
            mac.setDownLoadId(downLoadId);
            mac.setAdmin(admin);
            mac.setMacAddr(macStr);
            mac.setDate(new Date());
            //设置mac状态为init状态
            mac.setStatus(0);
            mac.setTask(task);
            macDao.save(mac);
        }
        return mac;
    }

    /**
     * @param macStrList
     * @param username
     * @usage 判断数据库中是否已经存储过对应的mac
     * 防止数据库中存储多个同样的mac地址
     */
    @Transactional
    @RedisEvict(type=Mac.class)
    public void doJudgementBySeriseMacStr(List macStrList, String username) {
        Task task = new Task();//单个mac所属task's id
        task.setDate(new Date());
        task.setFlag(0);//初始化task 状态为录入未成功
        for (String macStr : macStrList) {
            Mac mac = macDao.getByUniqueKey("macAddr", macStr);
            if (mac == null) {
                //1.单个mac地址转化为downloadId
                String downLoadId = GetDownloadIDUtil.getDownLoadId(macStr);
                taskDao.save(task);
                Admin admin = adminDao.getByUniqueKey("userName", username);
                mac = new Mac();
                mac.setDownLoadId(downLoadId);
                mac.setAdmin(admin);
                mac.setMacAddr(macStr);
                mac.setDate(new Date());
                //设置mac状态为init状态
                mac.setStatus(0);
                mac.setTask(task);
                macDao.save(mac);
            }
        }
    }

    /**
     * @param macStr
     * @param username
     * @return 1.单个mac地址转化为downloadId, 并调用迅雷方接口
     * 2.调用接口之前先将地址存储为数据库中一条记录,状态置为0-初始化状态
     * 3.调用完接口根据返回状态,将返回状态为success的数据置为1-正在录入
     */
    @Transactional
    @RedisEvict(type=Mac.class)
    public ControllerResult addSingleMac(String macStr, String username) {
        if (macStr == null || ("".equals(macStr))) {
            return ControllerResult.valueOf(ControllerResult.ERROR, "对不起,MAC地址不能为空");
        }
        if (!CountBetweenMacByMacStr.matchMacAddrByregex(macStr)) {
            return ControllerResult.valueOf(ControllerResult.ERROR, "对不起,MAC地址格式不正确");
        }
        List macStrList = new ArrayList<>();
        macStrList.add(macStr);
        Mac mac = doJudgementBySingleMacStr(macStr, username);
        //调用迅雷录入接口。
        if (VisitThunderInterface.addDownLoadId(macStrList)) {
            Admin admin = adminDao.getByUniqueKey("userName", username);
            if (mac.getStatus() != 2) {
                mac.setStatus(1);
                mac.setDate(new Date());
                mac.setAdmin(admin);
                macDao.update(mac);
            }
            return ControllerResult.valueOf(ControllerResult.SUCCESS, "迅雷录入接口请求成功", mac);
        } else {
            Admin admin = adminDao.getByUniqueKey("userName", username);
            if (mac.getStatus() != 2) {
                mac.setStatus(3);
                mac.setDate(new Date());
                mac.setAdmin(admin);
                macDao.update(mac);
                return ControllerResult.valueOf(ControllerResult.ERROR, "对不起,请求迅雷录入接口失败!重新录入");
            }
            return ControllerResult.valueOf(ControllerResult.ERROR, "此条mac地址已经录入成功");
        }
    }

    /**
     * @param startMacStr
     * @param endMacStr
     * @param username
     * @return
     * @usage 批量区间录入业务逻辑方法
     */
    @Transactional
    @RedisEvict(type=Mac.class)
    public ControllerResult addSeriseMac(String startMacStr, String endMacStr, String username) {
        if (startMacStr == null || ("".equals(startMacStr)) || endMacStr == null || ("".equals(endMacStr))) {
            return ControllerResult.valueOf(ControllerResult.ERROR, "对不起,MAC地址不能为空");
        }
        if (!CountBetweenMacByMacStr.matchMacAddrByregex(startMacStr) || !CountBetweenMacByMacStr.matchMacAddrByregex(endMacStr)) {
            return ControllerResult.valueOf(ControllerResult.ERROR, "对不起,MAC地址格式不正确");
        }
        List macStrList = CountBetweenMacByMacStr.countBetweenMacByMacStr(startMacStr, endMacStr);
        if (macStrList.size() > 1000) {
            return ControllerResult.valueOf(ControllerResult.ERROR, "对不起,MAC区间太长,请拆分后录入。重新录入");
        }
        doJudgementBySeriseMacStr(macStrList, username);
        if (VisitThunderInterface.addDownLoadId(macStrList)) {
            for (String macStr : macStrList) {
                Mac mac = macDao.getByUniqueKey("macAddr", macStr);
                Admin admin = adminDao.getByUniqueKey("userName", username);
                if (mac.getStatus() != 2) {
                    mac.setStatus(1);
                    mac.setDate(new Date());
                    mac.setAdmin(admin);
                    macDao.update(mac);
                }
            }
            return ControllerResult.valueOf(ControllerResult.SUCCESS, "录入成功");
        } else {
            for (String macStr : macStrList) {
                Mac mac = macDao.getByUniqueKey("macAddr", macStr);
                Admin admin = adminDao.getByUniqueKey("userName", username);
                if (mac.getStatus() != 2) {
                    mac.setStatus(3);
                    mac.setDate(new Date());
                    mac.setAdmin(admin);
                    macDao.update(mac);
                }
            }
            return ControllerResult.valueOf(ControllerResult.ERROR, "对不起,请求迅雷录入接口失败!重新录入");
        }
    }

    /**
     * @param macQo
     * @return
     * @usage 获取所有的mac录入状态数据业务逻辑方法
     */
    @RedisCache(type=Mac.class)
    @Transactional
    public ControllerResult getAllMacStatus(MacQo macQo) {
        DtoResultWithPageInfo info = macDao.queryPageListByCriteriaWithQo(macQo, MacDto.class);
        return ControllerResult.valueOf(ControllerResult.SUCCESS, "获取mac录入状态成功", info);
    }

    /**
     * @param serverFile
     * @param username
     * @return
     * @usage 非连续mac地址录入逻辑方法
     */
    @Transactional
    @RedisEvict(type=Mac.class)
    public ControllerResult addNoOrderMac(File serverFile, String username) {
        ReadExcelUtil readExcelUtil = new ReadExcelUtil();
        try {
            List macStrList = readExcelUtil.readUploadMacFile(serverFile);
            if (macStrList.size() == 0 || macStrList == null) {
                return ControllerResult.valueOf(ControllerResult.ERROR, "对不起,文件中MAC数据不能为空");
            }
            if (macStrList.size() > 1000) {
                return ControllerResult.valueOf(ControllerResult.ERROR, "对不起,文件中数据超过1000条,请进行拆分后上传!");
            }
            for (String inFilemacStr : macStrList) {
                if (!CountBetweenMacByMacStr.matchMacAddrByregex(inFilemacStr)) {
                    return ControllerResult.valueOf(ControllerResult.ERROR, "对不起,文件中有不合法的MAC地址");
                }
            }
            doJudgementBySeriseMacStr(macStrList, username);
            if (VisitThunderInterface.addDownLoadId(macStrList)) {
                for (String macStr : macStrList) {
                    Mac mac = macDao.getByUniqueKey("macAddr", macStr);
                    Admin admin = adminDao.getByUniqueKey("userName", username);
                    if (mac.getStatus() != 2) {
                        mac.setStatus(1);
                        mac.setDate(new Date());
                        mac.setAdmin(admin);
                        macDao.update(mac);
                    }
                }
                return ControllerResult.valueOf(ControllerResult.SUCCESS, "请求迅雷录入接口成功");
            } else {
                for (String macStr : macStrList) {
                    Mac mac = macDao.getByUniqueKey("macAddr", macStr);
                    Admin admin = adminDao.getByUniqueKey("userName", username);
                    if (mac.getStatus() != 2) {
                        mac.setStatus(3);
                        mac.setAdmin(admin);
                        mac.setDate(new Date());
                        macDao.update(mac);
                    }
                }
                return ControllerResult.valueOf(ControllerResult.ERROR, "对不起,请求迅雷录入接口失败!重新录入");
            }
        } catch (Exception e) {
            return ControllerResult.valueOf(ControllerResult.ERROR, "文件上传失败");
        }

    }
}

注意:

  • 上述程序中为非查询方法上加上了 @RedisEvict注解,表示删除旧的缓存。
  • 上述程序中为查询方法上加上了 @RedisCache注解,表示为查询业务逻辑应用缓存,应用逻辑为:项目中缓存数据的Struct为Hash,每张表对应的实体类使用一个名为Key的Hash结构来存储数据,当访问的key 存在时,直接从缓存中取出数据,不存在时第一步先从数据库中查询数据,再生成key,并生成对应的filed与value。

程序运行结果:

2016-12-03 20:16:05,212 [INFO]-[com.fxmms.common.rediscache.redisaspect.RedisCacheAspect.cache(RedisCacheAspect.java:67)] key参数: com.fxmms.www.service.MacService.getAllMacStatus
2016-12-03 20:16:05,219 [INFO]-[com.fxmms.common.rediscache.redisaspect.RedisCacheAspect.cache(RedisCacheAspect.java:71)] 生成key: com.fxmms.www.service.MacService|getAllMacStatus|MacQo|
2016-12-03 20:16:05,357 [INFO]-[com.fxmms.common.rediscache.redisaspect.RedisCacheAspect.cache(RedisCacheAspect.java:108)] 缓存命中, value = {"msg":"获取mac录入状态成功","result":"success","rows":{"emptyResult":false,"pageInfo":{"firstPage":true,"firstResultNum":0,"lastPage":false,"lastResultNum":10,"pageNo":1,"pageSize":10,"totalPage":49,"totalQuantity":488},"results":[{"admin":{"enable":1,"id":1,"isDelete":0,"password":"11","role":"admin","userName":"ls"},"date":1479913221000,"dateStr":"2016-11-23 23:00:21","deviceId":"730CBAEA-6954-000A-2D77-BAF544E6F192","downLoadId":"11123E566745FB30FE5C9AC094A1BAA0","id":488,"macAddr":"11:12:3e:56:67:45","status":2,"statusStr":"录入成功","task":{"date":1479913220000,"flag":1,"id":29}},{"admin":{"enable":1,"id":1,"isDelete":0,"password":"11","role":"admin","userName":"ls"},"date":1479448899000,"dateStr":"2016-11-18 14:01:39","deviceId":"","downLoadId":"34BDF9C0B2C1EC6B5CA3B81DCB05241D","id":487,"macAddr":"34:BD:F9:C0:B2:c1","status":3,"statusStr":"录入失败","task":{"date":1479448898000,"flag":0,"id":28}},{"admin":{"enable":1,"id":1,"isDelete":0,"password":"11","role":"admin","userName":"ls"},"date":1479448476000,"dateStr":"2016-11-18 13:54:36","deviceId":"","downLoadId":"11123E586745088C6CAF8E6C2EBDB7A5","id":486,"macAddr":"11:12:3e:58:67:45","status":3,"statusStr":"录入失败","task":{"date":1479448476000,"flag":0,"id":27}},{"admin":{"enable":1,"id":1,"isDelete":0,"password":"11","role":"admin","userName":"ls"},"date":1479447598000,"dateStr":"2016-11-18 13:39:58","deviceId":"","downLoadId":"34BDFAC0B2F01A731572C0BCEC4D26F0","id":485,"macAddr":"34:BD:FA:C0:B2:F0","status":3,"statusStr":"录入失败","task":{"date":1479447598000,"flag":0,"id":26}},{"admin":{"enable":1,"id":1,"isDelete":0,"password":"11","role":"admin","userName":"ls"},"date":1479447575000,"dateStr":"2016-11-18 13:39:35","deviceId":"","downLoadId":"3EBDF9C0B2F02D7F2A6CAC4F2B5121E8","id":484,"macAddr":"3e:BD:F9:C0:B2:F0","status":3,"statusStr":"录入失败","task":{"date":1479447575000,"flag":0,"id":25}},{"admin":{"enable":1,"id":1,"isDelete":0,"password":"11","role":"admin","userName":"ls"},"date":1479446783000,"dateStr":"2016-11-18 13:26:23","deviceId":"","downLoadId":"11128E566749F8776504253D15D8B001","id":483,"macAddr":"11:12:8e:56:67:49","status":3,"statusStr":"录入失败","task":{"date":1479446783000,"flag":0,"id":24}},{"admin":{"enable":1,"id":1,"isDelete":0,"password":"11","role":"admin","userName":"ls"},"date":1479446754000,"dateStr":"2016-11-18 13:25:54","deviceId":"","downLoadId":"11128E566745B130B2E6C6AA8E52EB4A","id":482,"macAddr":"11:12:8e:56:67:45","status":3,"statusStr":"录入失败","task":{"date":1479446753000,"flag":0,"id":23}},{"admin":{"enable":1,"id":1,"isDelete":0,"password":"11","role":"admin","userName":"ls"},"date":1479446736000,"dateStr":"2016-11-18 13:25:36","deviceId":"","downLoadId":"341DF9C0B2F11E391DDA8EDAB78B4162","id":481,"macAddr":"34:1D:F9:C0:B2:F1","status":3,"statusStr":"录入失败","task":{"date":1479446736000,"flag":0,"id":22}},{"admin":{"enable":1,"id":1,"isDelete":0,"password":"11","role":"admin","userName":"ls"},"date":1479437904000,"dateStr":"2016-11-18 10:58:24","deviceId":"","downLoadId":"11446633889613659EE26ABE4FBE28CD","id":480,"macAddr":"11:44:66:33:88:96","status":3,"statusStr":"录入失败","task":{"date":1479437904000,"flag":0,"id":21}},{"admin":{"enable":1,"id":1,"isDelete":0,"password":"11","role":"admin","userName":"ls"},"date":1479437899000,"dateStr":"2016-11-18 10:58:19","deviceId":"","downLoadId":"1144663388947CCC987231F802C72F83","id":479,"macAddr":"11:44:66:33:88:94","status":3,"statusStr":"录入失败","task":{"date":1479437899000,"flag":0,"id":20}}]},"total":0}

完。

福利彩蛋

职位:腾讯OMG 广告后台高级开发工程师;
Base:深圳;
场景:海量数据,To B,To C,场景极具挑战性。
基础要求:
熟悉常用数据结构与算法;
熟悉常用网络协议,熟悉网络编程;
熟悉操作系统,有线上排查问题经验;
熟悉MySQL,oracle;
熟悉JAVA,GoLang,c++其中一种语言均可;
可内推,欢迎各位优秀开发道友私信[微笑]
期待关注我的开发小哥哥,小姐姐们私信我,机会很好,平台对标抖音,广告生态平台,类似Facebook 广告平台,希望你们用简历砸我~
联系方式 微信 13609184526

博客搬家:大坤的个人博客
欢迎评论哦~

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
7天前
|
XML Java 数据格式
SpringBoot入门(8) - 开发中还有哪些常用注解
SpringBoot入门(8) - 开发中还有哪些常用注解
25 0
|
8天前
|
XML Java 数据安全/隐私保护
Spring Aop该如何使用
本文介绍了AOP(面向切面编程)的基本概念和术语,并通过具体业务场景演示了如何在Spring框架中使用Spring AOP。文章详细解释了切面、连接点、通知、切点等关键术语,并提供了完整的示例代码,帮助读者轻松理解和应用Spring AOP。
Spring Aop该如何使用
|
14天前
|
XML JSON Java
SpringBoot必须掌握的常用注解!
SpringBoot必须掌握的常用注解!
41 4
SpringBoot必须掌握的常用注解!
|
14天前
|
Java Spring
[Spring]aop的配置与使用
本文介绍了AOP(面向切面编程)的基本概念和核心思想。AOP是Spring框架的核心功能之一,通过动态代理在不修改原代码的情况下注入新功能。文章详细解释了连接点、切入点、通知、切面等关键概念,并列举了前置通知、后置通知、最终通知、异常通知和环绕通知五种通知类型。
27 1
|
16天前
|
存储 缓存 Java
Spring缓存注解【@Cacheable、@CachePut、@CacheEvict、@Caching、@CacheConfig】使用及注意事项
Spring缓存注解【@Cacheable、@CachePut、@CacheEvict、@Caching、@CacheConfig】使用及注意事项
58 2
|
16天前
|
JSON Java 数据库
SpringBoot项目使用AOP及自定义注解保存操作日志
SpringBoot项目使用AOP及自定义注解保存操作日志
33 1
|
10天前
|
安全 Java 测试技术
Java开发必读,谈谈对Spring IOC与AOP的理解
Spring的IOC和AOP机制通过依赖注入和横切关注点的分离,大大提高了代码的模块化和可维护性。IOC使得对象的创建和管理变得灵活可控,降低了对象之间的耦合度;AOP则通过动态代理机制实现了横切关注点的集中管理,减少了重复代码。理解和掌握这两个核心概念,是高效使用Spring框架的关键。希望本文对你深入理解Spring的IOC和AOP有所帮助。
22 0
|
11天前
|
存储 安全 Java
springboot当中ConfigurationProperties注解作用跟数据库存入有啥区别
`@ConfigurationProperties`注解和数据库存储配置信息各有优劣,适用于不同的应用场景。`@ConfigurationProperties`提供了类型安全和模块化的配置管理方式,适合静态和简单配置。而数据库存储配置信息提供了动态更新和集中管理的能力,适合需要频繁变化和集中管理的配置需求。在实际项目中,可以根据具体需求选择合适的配置管理方式,或者结合使用这两种方式,实现灵活高效的配置管理。
10 0
|
1月前
|
消息中间件 缓存 NoSQL
Redis 是一个高性能的键值对存储系统,常用于缓存、消息队列和会话管理等场景。
【10月更文挑战第4天】Redis 是一个高性能的键值对存储系统,常用于缓存、消息队列和会话管理等场景。随着数据增长,有时需要将 Redis 数据导出以进行分析、备份或迁移。本文详细介绍几种导出方法:1)使用 Redis 命令与重定向;2)利用 Redis 的 RDB 和 AOF 持久化功能;3)借助第三方工具如 `redis-dump`。每种方法均附有示例代码,帮助你轻松完成数据导出任务。无论数据量大小,总有一款适合你。
74 6
|
6天前
|
缓存 NoSQL 关系型数据库
大厂面试高频:如何解决Redis缓存雪崩、缓存穿透、缓存并发等5大难题
本文详解缓存雪崩、缓存穿透、缓存并发及缓存预热等问题,提供高可用解决方案,帮助你在大厂面试和实际工作中应对这些常见并发场景。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:如何解决Redis缓存雪崩、缓存穿透、缓存并发等5大难题

热门文章

最新文章