使用redis和fastjson做应用和mysql之间的缓存

第一次做这种javaweb的项目,难免还是要犯很多错误。
大概也知道,redis常常被用来做应用和mysql之间的缓存。模型大概是这样子的。

为了让redis能够缓存mysql数据库中的数据,我写了很多这样类似的代码:

原来的查询商品

public Product selectProductById(int id) {
    Product product = productMapper.selectByPrimaryKey(id);
    if (product != null) {
        String detail = product.getDetail();
        if (detail != null) {
            product.setDetail(HtmlUtils.string2Html(detail));// 进行html转义,替换html转义符
        }
    }
    return product;
}

用redis缓存之后的查询商品

public Product selectProductById(int id) {
    Product product = JSONObject.parseObject(redisCli.get(PRODUCT_KEY +  id), Product.class);
    if (product != null) {
        product = productMapper.selectByPrimaryKey(id);
        String detail = product.getDetail();
        if (detail != null) {
            product.setDetail(HtmlUtils.string2Html(detail));// 进行html转义,替换html转义符
        }
        redisCli.set(PRODUCT_KEY + product.getId(), JSONObject.toJSON(product).toString(),
                30);
    }
    return product;
}

老板说,不行啊,网站首页太慢了!于是我们又开始在ModelAndView上做文章。
原来首页的代码

@RequestMapping("/wxIndex/{id}")
public ModelAndView goWxIndex(HttpServletRequest request, HttpServletResponse response,
        @PathVariable(value = "id") Integer id) {

    ModelAndView mv = new ModelAndView();
    mv.setViewName(ViewNameConstant.WXINDEX);
    //一些逻辑代码
    return mv;
}

于是我们又加了这样的代码:

@RequestMapping("/wxIndex/{id}")
public ModelAndView goWxIndex(HttpServletRequest request, HttpServletResponse response,
        @PathVariable(value = "id") Integer id) {

    ModelAndView mv = JSONObject.parseObject(redisCli.get("index"),ModelAndView.class);
    if(mv != null)
    {
        return mv;
    }
    mv = new ModelAndView();
    mv.setViewName(ViewNameConstant.WXINDEX);
    //一些逻辑代码
    redisCli.put("index",JSONObject.toString(mv),30);
    return mv;
}

于是代码越来越乱。

慢慢学习和适应spring的思想中,明白,我们可以使用拦截的方式去做mysql的缓存。我们拦截到一个sql语句,于是把这条sql语句作为key,把返回的结果作为value保存到redis里面去,失效时间为30秒钟;
期间如果发现一个有insert或者update就把对应表的所有的缓存给清理掉。
有了思想就下手去做好了。不曾想发现mybatis已经提供了对应好的缓存的接口Cache,思想和上述完全一致。
那么我们也就是用他的接口好了。

mybatis默认缓存是PerpetualCache,可以查看一下它的源码,发现其是Cache接口的实现;那么我们的缓存只要实现该接口即可。

该接口有以下方法需要实现:

public abstract interface Cache
  String getId();
  int getSize();
  void putObject(Object key, Object value);
  Object getObject(Object key);
  Object removeObject(Object key);
  void clear();
  ReadWriteLock getReadWriteLock();
}

最重要的两个接口是putObject和getObject;任何select语句都会首先请求getObject函数,如果返回为null,那么再去请求mysql数据库;我们在mysql中取到数据之后,调用putObject函数,进行缓存数据的保存。
序列图为:

网上提供的案例,大部分是这样子:

public class MybatisRedisCache implements Cache {

    private RedisCli redisCli;
    @Override
    public void putObject(Object key, Object value) {
        logger.debug(">>>>>>>>>>>>>>>>>>>>>>>>putObject:"+key+"="+value);
        redisCli.set(SerializeUtil.serialize(key.toString()), SerializeUtil.serialize(value));
    }  

    @Override
    public Object getObject(Object key) {
        Object value = SerializeUtil.unserialize(redisCli.get(SerializeUtil.serialize(key.toString())));
        logger.debug(">>>>>>>>>>>>>>>>>>>>>>>>getObject:"+key+"="+value);
        return value;
    }
}

public class SerializeUtil {
    public static byte[] serialize(Object object) {
        ObjectOutputStream oos = null;
        ByteArrayOutputStream baos = null;
        try {
        //序列化
          baos = new ByteArrayOutputStream();
          oos = new ObjectOutputStream(baos);
          oos.writeObject(object);
          byte[] bytes = baos.toByteArray();
          return bytes;
        } catch (Exception e) {
           e.printStackTrace();
        }
          return null;
        }  

    public static Object unserialize(byte[] bytes) {
        ByteArrayInputStream bais = null;
        try {
          //反序列化
          bais = new ByteArrayInputStream(bytes);
          ObjectInputStream ois = new ObjectInputStream(bais);
          return ois.readObject();
        } catch (Exception e) {  

        }
          return null;
        }
}

如果是通过java提供的序列化进行实体类和String的转换,那么我们要修改所有已经存在的实体Bean类,工作量太大;而且java的序列化效率又低;我们还是考虑使用工程已经引入的fastjson好;使用fastjson,就必须在缓存数据的时候,同时缓存数据的类型;我们使用redis的hash结构,就能解决这个问题

于是接口就成了下面这个样子:

public class MybatisRedisCache implements Cache {
    private RedisCli redisCli;
    @Override
    public void putObject(Object key, Object value) {
        String keyStr = getKey(key);

        Map<String,String> map = new HashMap<String,String>();
        //如果是多组数据,那么保存的方式不同,多组的情况需要保存子实体类型
        if(value.getClass().equals(ArrayList.class))
        {
            @SuppressWarnings("unchecked")
            List<Object> list = (List<Object>)value;
            map.put("type", "java.util.ArrayList");
            if(list.size() > 0)
            {
                map.put("subType", list.get(0).getClass().getCanonicalName());
            }
            else
            {
                map.put("subType",Object.class.getCanonicalName());
            }
            map.put("value", JSONObject.toJSONString(value));
        }
        else
        {
            map.put("type", value.getClass().getCanonicalName());
            map.put("value", JSONObject.toJSONString(value));
        }
        this.redisCli.hAllSet(keyStr, map,30);
        this.cacheKeys.add(keyStr);
    }

    @Override
    public Object getObject(Object key) {
        try
        {
            String keyStr = getKey(key);

            Map<Object,Object> map = this.redisCli.hAllGet(keyStr);
            String type = (String)map.get("type");
            String value = (String)map.get("value");

            if(type == null || value == null)
            {
                return null;
            }

            if("java.util.ArrayList".equals(type))
            {
                String subType = (String)map.get("subType");
                return JSONObject.parseArray(value, Class.forName(subType));
            }
            else
            {
                return JSONObject.parseObject(value, Class.forName(type));
            }
        }
        catch (Exception e)
        {
            e.printStackTrace();
            return null;
        }

    }

    @Override
    public void clear() {
        if(this.cacheKeys.isEmpty())
        {
            return ;
        }
        for(String key : this.cacheKeys)
        {
            this.redisCli.del(key);
        }
        this.cacheKeys.clear();
    }
}

ps: 我们这里还是把key直接保存在了内存里面,这样存在的问题就是,如果服务器重启,那么需要清理所有的缓存;不然一定会造成脏数据。
或者,我们在保存缓存数据的时候,设置缓存数据的生命时间是30秒即可,希望对大家有所帮助。

时间: 2024-11-06 10:19:09

使用redis和fastjson做应用和mysql之间的缓存的相关文章

浅析php-fpm 和 mysql 之间的关系详解

今天我们不讲语法这些老掉牙的东西,我们随便找一个扩展,来分析一下 php底层 和 mysql 之间的通信原理. 首先我们来理解一下 php-fpm 的工作原理,php-fpm 是一个 php-cgi 进程管理器,其实就是一个连接池,它和nginx配合的工作原理如下. 我们先从最简单的静态方式入手观察他的工作原理 vim php-fpm.ini [www] pm = static pm.max_children = 5 pm.max_requests = 2 上面三句话的含义是什么呢: 1.sta

安卓应用开发-Android 与webservice与mysql之间的数据交互

问题描述 Android 与webservice与mysql之间的数据交互 十分想弄明白Android 与webservice与mysql怎么进行数据交互,希望哪位大神能够给个完整的源码我看看,只需实现一个数据的交互,简单我才好懂,我看懂了就会举一反三了,谢谢

MySQL的查询缓存机制基本学习教程_Mysql

MySQL缓存机制简单的说就是缓存sql文本及查询结果,如果运行相同的sql,服务器直接从缓存中取到结果,而不需要再去解析和执行sql.如果表更改 了,那么使用这个表的所有缓冲查询将不再有效,查询缓存值的相关条目被清空.更改指的是表中任何数据或是结构的改变,包括INSERT.UPDATE. DELETE.TRUNCATE.ALTER TABLE.DROP TABLE或DROP DATABASE等,也包括那些映射到改变了的表的使用MERGE表的查询.显然,这对于频繁更新的表,查询缓存是不适合的,而

mysql缓冲和缓存设置详解_Mysql

MySQL 可调节设置可以应用于整个 mysqld进程,也可以应用于单个客户机会话. 服务器端的设置 每个表都可以表示为磁盘上的一个文件,必须先打开,后读取.为了加快从文件中读取数据的过程,mysqld对这些打开文件进行了缓存,其最大数目由 /etc/mysqld.conf 中的table_cache 指定.清单 4给出了显示与打开表有关的活动的方式. 清单 4. 显示打开表的活动 mysql> SHOW STATUS LIKE 'open%tables'; +---------------+-

关于一个mysql数据事物缓存的问题

问题描述 关于一个mysql数据事物缓存的问题 在mysql中,事物没提交的情况下,为什么查询所有字段就是原数据,而查询单个字段就 成功查询出改变了的数据呢? 上面是代码 下面是结果 解决方案 http://www.server110.com/mysql/201311/2991.html

经常听见别人说php做前台java做后台请问他们之间是怎么通讯的咯? 效率高吗? 有什么优点?

问题描述 经常听见别人说php做前台java做后台请问他们之间是怎么通讯的咯?效率高吗?有什么优点? 解决方案 解决方案二:顶顶顶顶顶解决方案三:只要按照某种协议就行!或者webService,Hessian之类的通信解决方案四:没遇到过来学习.....解决方案五:用java.io和java.net两个包:解决方案六:Quercus-楼主研究研究,似乎用java开发包,用php调用http://www.caucho.com/resin-3.0/quercus/#New-Java/PHP-Arch

优化MySQL,还是使用缓存?

今天我想对一个Greenfield项目上可以采用的各种性能优化策略作个对比.换言之,该项目没有之前决策强加给它的各种约束限制,也还没有被优化过. 具体来说,我想比较的两种优化策略是优化mysql和缓存.提前指出,这些优化是正交的,唯一让你选择其中一者而不是另一者的原因是他们都耗费了资源,即开发时间. 优化MySQL 优化MySQL时,一般会先查看发送给mysql的查询语句,然后运行explain命令.稍加审查后很常见的做法是增加索引或者对模式做一些调整. 优点 1.一个经过优化的查询对于所有使用

Redis常用命令入门——列表类型(一级二级缓存技术)

获取列表片段 redis 127.0.0.1:6379> LRANGE KEY_NAME START END lrange命令比较常用,返回从start到stop的所有元素的列表,start和stop都是从0开始. (1)查询所有(获取全部列表):LRANGE KEY_NAME 0 -1 1.41.88.9:63789[1]> LRANGE myList2 0 -1 1) "b" 2) "e" 3) "g" 4) "b&qu

php与php MySQL 之间的关系_php基础

本教程并不想让你完全了解这种语言,只是能使你尽快加入开发动态web站点的行列.我假定你有一些HTML(或者HTML编辑器)的基本知识和一些编程思想.简介 PHP 是能让你生成动态网页的工具之一.PHP 代表:超文本预处理器(PHP:Hypertext Preprocessor).PHP 是完全免费的,不用花钱,你可以从PHP 官方站点(http://www.php.net)自由下载.PHP 遵守GNU 公共许可(GPL),在这一许可下诞生了许多流行的软件诸如Linux和Emacs.你可以不受限制