二十三、redis

概述:

1. redis
	1. 概念
	2. 下载安装
	3. 命令操作
		1. 数据结构
	4. 持久化操作
	5. 使用Java客户端操作redis

1、redis概念

在这里插入图片描述
当数据庞大的时候,如果在关系型数据库里检索数据,那么会非常耗时间,检索耗时,加载进内存耗时。
但,如果在内存中开辟一个空间来缓存无关系的数据,那么数据就不需要加载进内存,直接在用就好了(因为在内存里),而且不需要进行关系查询。

2、下载&安装

2. 下载安装
	1. 官网:https://redis.io
	2. 中文网:http://www.redis.net.cn/
	3. 解压直接可以使用:
		* redis.windows.conf:配置文件
		* redis-cli.exe:redis的客户端
		* redis-server.exe:redis服务器端

先打开服务器端,再打开客户端即可。

3、数据结构介绍【敲黑板】

3. 命令操作
	1. redis的数据结构:
		* redis存储的是:key,value格式的数据,其中key都是字符串,value有5种不同的数据结构
			* value的数据结构:
				1) 字符串类型 string
				2) 哈希类型 hash : map格式  
				3) 列表类型 list : linkedlist格式。支持重复元素
				4) 集合类型 set  : 不允许重复元素
				5) 有序集合类型 sortedset:不允许重复元素,且元素有顺序

在这里插入图片描述
下面的命令操作都是在redis数据库客户端上操作的,不是java客户端(Java客户端需要数据库连接对象)

4、命令操作:string&hash

2. 字符串类型 string
		1. 【存储】: set key value
			127.0.0.1:6379> set username zhangsan
		2. 【获取】: get key
			127.0.0.1:6379> get username
		3. 【删除】: del key
			127.0.0.1:6379> del age
	3. 哈希类型 hash
		1. 【存储】: hset key field value
			127.0.0.1:6379> hset myhash username lisi
			127.0.0.1:6379> hset myhash password 123
		2. 【获取】: 
			* hget key field: 获取指定的field对应的值
				127.0.0.1:6379> hget myhash username
			* hgetall key:获取所有的field和value
				127.0.0.1:6379> hgetall myhash
		3. 【删除】: hdel key field
			127.0.0.1:6379> hdel myhash username

field是哈希的value的字段。(这个字段又是另一个key)

5、命令操作:list

4. 列表类型 list:可以添加一个元素到列表的头部(左边)或者尾部(右边)
		1. 【添加】:
			1. lpush key value: 将元素加入列表左表
				
			2. rpush key value:将元素加入列表右边
				
				127.0.0.1:6379> lpush myList a
				127.0.0.1:6379> lpush myList b
				127.0.0.1:6379> rpush myList c
		2. 【获取】:
			* lrange key start end :范围获取
				127.0.0.1:6379> lrange myList 0 -1 【获取全部】
				1) "b"
				2) "a"
				3) "c"
		3. 【删除】:
			* lpop key: 删除列表最左边的元素,并将元素返回
			* rpop key: 删除列表最右边的元素,并将元素返回

流程图:
在这里插入图片描述

6、命令操作:set&sortedset

set

5. 集合类型 set : 不允许重复元素,(无序的,无论取出还是存储)
		1. 【存储:sadd key value】
			127.0.0.1:6379> sadd myset a
			(integer) 1
			127.0.0.1:6379> sadd myset a
			(integer) 0
		2. 【获取:smembers key:获取set集合中所有元素】
			127.0.0.1:6379> smembers myset
			1) "a"
		3. 【删除:srem key value:删除set集合中的某个元素】	
			127.0.0.1:6379> srem myset a
			(integer) 1

sortedset

6. 有序集合类型 sortedset:不允许重复元素,且元素有顺序.

		1. 【存储:zadd key score value】
			127.0.0.1:6379> zadd mysort 60 zhangsan
			(integer) 1
			127.0.0.1:6379> zadd mysort 50 lisi
			(integer) 1
			127.0.0.1:6379> zadd mysort 80 wangwu
			(integer) 1
		2. 【获取:zrange key start end [withscores]127.0.0.1:6379> zrange mysort 0 -1
			1) "lisi"
			2) "zhangsan"
			3) "wangwu"

			127.0.0.1:6379> zrange mysort 0 -1 withscores
			1) "zhangsan"
			2) "60"
			3) "wangwu"
			4) "80"
			5) "lisi"
			6) "500"
		3. 【删除:zrem key value】
			127.0.0.1:6379> zrem mysort lisi
			(integer) 1

为什么是sortedset有序的?

因为sortedset添加元素的时候,每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。(设置分数,再对分数进行排序)

sortedset一般用在搞【排行榜】业务那里。

7、命令操作:通用命令

7. 通用命令
		1. keys * : 查询所有的键
		2. type key : 获取键对应的value的类型
		3. del key:删除指定的key value

持久化

RDB性能会比AOF好点。
因为RDB是按时间的间隔,并判断key是否变化指定次数才把数据存进硬盘里,而AOF是是操作一下就保存一下,就行MySQL一样
但是,使用RDB只能保证大部分数据不丢失,为了解决这种不安全性,redis和关系型的数据库进行配合。

8、持久化:RDB

2. AOF:日志记录的方式,可以记录每一条命令的操作。可以每一次命令操作后,持久化数据
			1. 编辑redis.windwos.conf文件
				appendonly no(关闭aof) --> appendonly yes (开启aof)
				
				# appendfsync always : 每一次操作都进行持久化
				appendfsync everysec : 每隔一秒进行一次持久化(默认)
				# appendfsync no	 : 不进行持久化
4. 持久化
	1. redis是一个内存数据库,当redis服务器重启,或者电脑重启,数据会丢失,我们可以将redis内存中的数据持久化保存到硬盘的文件中。
	2. redis持久化机制:
		1. RDB:默认方式,不需要进行配置,默认就使用这种机制
			* 在一定的间隔时间中,检测key的变化情况,然后持久化数据
			1. 【编辑redis.windwos.conf文件】
				#   after 900 sec (15 min) if at least 1 key changed
				save 900 1
				#   after 300 sec (5 min) if at least 10 keys changed
				save 300 10
				#   after 60 sec if at least 10000 keys changed
				save 60 10000
				
			2. 【重新启动redis服务器,并指定配置文件名称】
				redis-server.exe redis.windows.conf

注意了,当修改了配置文件重启redis服务器的时候,不是直接重启,而是运用当前目录的cmd输入redis-server.exe redis.windows.conf

9、持久化:AOF

2. AOF:日志记录的方式,可以记录每一条命令的操作。可以每一次命令操作后,持久化数据
			1. 编辑redis.windwos.conf文件
				appendonly no(关闭aof) --> appendonly yes (开启aof)
				
				# appendfsync always : 每一次操作都进行持久化
				appendfsync everysec : 每隔一秒进行一次持久化(默认)
				# appendfsync no	 : 不进行持久化

jedis

什么是jedis?

Jedis: 一款java操作redis数据库的工具.它是个Java客户端
跟Java操作MySQL一样,有连接数据库对象,有连接池。

10、jedis快速入门

	* 使用步骤:
		1. 下载jedis的jar包
		2. 使用
			1. 获取连接
    		Jedis jedis = new Jedis("ip地址",6379);
   			2. 操作
   			jedis.set("username","zhangsan");
    		3. 关闭连接
    		jedis.close();

Demo:

public class JedisTest {
    /**
     * 快速入门
     */
    @Test
    public void test1(){
        //1. 获取连接
        Jedis jedis = new Jedis("localhost",6379);
        //2. 操作
        jedis.set("username","zhangsan");

        //3. 关闭连接
        jedis.close();
    }

11、操作string

* Jedis操作各种redis中的数据结构
		1) 字符串类型 string
			set
			get
			setex
  • setex常用于激活码、验证码。过秒就自动删除数据库数据。
    Demo:
    /**
     * string 数据结构操作
     */
    @Test
    public void test2(){
        //1. 获取连接
        Jedis jedis = new Jedis();//如果使用空参构造,默认值 "localhost",6379端口
        //2. 操作
        //存储
        jedis.set("username","zhangsan");
        //获取
        String username = jedis.get("username");
        System.out.println(username);

        //可以使用setex()方法存储可以指定过期时间的 key value
        jedis.setex("activecode",20,"hehe");//将activecode:hehe键值对存入redis,并且20秒后自动删除该键值对

        //3. 关闭连接
        jedis.close();
    }

12、操作hash

2) 哈希类型 hash : map格式  
			hset
			hget
			hgetAll

Demo:


    /**
     * hash 数据结构操作
     */
    @Test
    public void test3(){
        //1. 获取连接
        Jedis jedis = new Jedis();//如果使用空参构造,默认值 "localhost",6379端口
        //2. 操作
        // 存储hash
        jedis.hset("user","name","lisi");
        jedis.hset("user","age","23");
        jedis.hset("user","gender","female");
		 // 获取hash
        String name = jedis.hget("user", "name");
        System.out.println(name);


        // 获取hash的所有map中的数据
        Map<String, String> user = jedis.hgetAll("user");
		
		//遍历集合
        // keyset
        Set<String> keySet = user.keySet();
        for (String key : keySet) {
            //获取value
            String value = user.get(key);
            System.out.println(key + ":" + value);
        }

        //3. 关闭连接
        jedis.close();
    }

13、操作list

3) 列表类型 list : linkedlist格式。支持重复元素
			lpush / rpush	
			lpop / rpop		(1个元素1个元素的弹出)
			lrange start end : 范围获取
  • 1、lpush / rpush

参数(String key,String value)
参数(String key,byte[] value)
参数(byte[] key,byte[] value)

  • 2、lpop / rpop

1个元素1个元素地弹出

  • 3、lrange start end

Irange( key,0,-1) :获取key的全部value
其中,start和end可以局部获取,指定value的索引就行了

Demo:

    /**
     * list 数据结构操作
     */
    @Test
    public void test4(){
        //1. 获取连接
        Jedis jedis = new Jedis();//如果使用空参构造,默认值 "localhost",6379端口
        //2. 操作
        // list 存储
        jedis.lpush("mylist","a","b","c");//从左边存
        jedis.rpush("mylist","a","b","c");//从右边存

        // list 范围获取
        List<String> mylist = jedis.lrange("mylist", 0, -1);
        System.out.println(mylist);//c,b,a,a,b,c
        
        // list 弹出
        String element1 = jedis.lpop("mylist");//c
        System.out.println(element1);

        String element2 = jedis.rpop("mylist");//c
        System.out.println(element2);

        // list 范围获取
        List<String> mylist2 = jedis.lrange("mylist", 0, -1);//如果像获取指定范围的话,可以指定end为某个索引
        System.out.println(mylist2);//b,a,a,b

        //3. 关闭连接
        jedis.close();
    }

14、操作set&sortedset

set

4) 集合类型 set  : 不允许重复元素
			sadd
			smembers:获取所有元素

Demo:


    /**
     * set 数据结构操作
     */
    @Test
    public void test5(){
        //1. 获取连接
        Jedis jedis = new Jedis();//如果使用空参构造,默认值 "localhost",6379端口
        //2. 操作
        // set 存储
        jedis.sadd("myset","java","php","c++");

        // set 获取
        Set<String> myset = jedis.smembers("myset");
        System.out.println(myset);

        //3. 关闭连接
        jedis.close();
    }

sortedset

5) 有序集合类型 sortedset:不允许重复元素,且元素有顺序
			zadd
			zrange

Demo:

 /**
     * sortedset 数据结构操作
     */
    @Test
    public void test6(){
        //1. 获取连接
        Jedis jedis = new Jedis();//如果使用空参构造,默认值 "localhost",6379端口
        //2. 操作
        // sortedset 存储
        jedis.zadd("mysortedset",3,"亚瑟");//key,score,value
        jedis.zadd("mysortedset",30,"后裔");
        jedis.zadd("mysortedset",55,"孙悟空");

        // sortedset 获取
        Set<String> mysortedset = jedis.zrange("mysortedset", 0, -1);

        System.out.println(mysortedset);//[亚瑟,后裔,孙悟空]

        //3. 关闭连接(连接销毁)
        jedis.close();
    }

15、jedis连接池

jedis连接池跟JDBC连接池类似,使用连接池可以获取数据库连接对象来操作redis数据库。但是区别在于,redis的连接池是内置对象,不用借助第三方jar包

* jedis连接池: JedisPool
		* 使用:
			1. 创建JedisPool连接池对象
			2. 调用方法 getResource()方法获取Jedis连接

Demo:

   /**
     * jedis连接池使用
     */
    @Test
    public void test7(){

        //0.创建一个配置对象
        JedisPoolConfig config = new JedisPoolConfig();
        config.setMaxTotal(50);//最大连接数
        config.setMaxIdle(10);//最大活动数

        //1.创建Jedis连接池对象
        JedisPool jedisPool = new JedisPool(config,"localhost",6379);

        //2.获取连接对象
        Jedis jedis = jedisPool.getResource();
        //3. 使用
        jedis.set("hehe","heihei");

        //4. 关闭连接(归还到连接池中)
        jedis.close();;
    }

  • JedisPoolConfig配置对象是配置连接池的。

下面是它的具体配置功能【jedis详细配置】:

#最大活动对象数     
redis.pool.maxTotal=1000    
#最大能够保持idel状态的对象数      
redis.pool.maxIdle=100  
#最小能够保持idel状态的对象数   
redis.pool.minIdle=50    
#当池内没有返回对象时,最大等待时间    
redis.pool.maxWaitMillis=10000    
#当调用borrow Object方法时,是否进行有效性检查    
redis.pool.testOnBorrow=true    
#当调用return Object方法时,是否进行有效性检查    
redis.pool.testOnReturn=true  
#“空闲链接”检测线程,检测的周期,毫秒数。如果为负值,表示不运行“检测线程”。默认为-1.  
redis.pool.timeBetweenEvictionRunsMillis=30000  
#向调用者输出“链接”对象时,是否检测它的空闲超时;  
redis.pool.testWhileIdle=true  
# 对于“空闲链接”检测线程而言,每次检测的链接资源的个数。默认为3.  
redis.pool.numTestsPerEvictionRun=50  
#redis服务器的IP    
redis.ip=xxxxxx  
#redis服务器的Port    
redis1.port=6379   
  • JedisPool连接池对象
  • 1、可以空参创建该对象,空参创建对象意味着设置默认配置,以及连接本地主机
  • 2、非空参:
    JedisPool jedisPool = new JedisPool(配置对象,“IP地址”,数据库端口)
    JedisPool jedisPool = new JedisPool(config,“localhost”,6379)

16、jedis工具类

注意:虽然jedis可以直接创建出来,但是没有在连接池里面获取效率高。

jedis工具类其实和JDBCTemlate工具类很像。成员变量都是连接池对象,静态代码块来加载配置文件,初始化连接池对象;成员方法是数据库连接对象。

JedisPoolUtils工具类:

/**
 * JedisPoolUtils工具类:
 *      加载配置文件,配置连接池的参数
 *      提供获取【数据库连接池】对象的方法
 */
public class JedisPoolUtils {
    private static JedisPool jedisPool;
    /**
     * 1、加载配置文件,初始化连接池对象
     * 2、获取配置文件数据
     */
    static {
        //1、加载配置文件,初始化连接池对象
            //创建Properties集合
        Properties pro = new Properties();
        try {
            //把配置文件数据以键值对方式放进集合中
            pro.load(JedisPoolUtils.class.getClassLoader().getResourceAsStream("jedis.properties"));
        } catch (IOException e) {
            e.printStackTrace();
        }
        //2、获取数据,并设置到配置对象中
            //创建配置对象
        JedisPoolConfig config = new JedisPoolConfig();
        config.setMaxTotal(Integer.parseInt(pro.getProperty("maxTotal")));  //pro.getProperty("maxTotal")获取的是String类型的value
        config.setMaxIdle(Integer.parseInt(pro.getProperty("maxIdle")));
            //初始化JedisPool连接池对象
        jedisPool = new JedisPool(config,pro.getProperty("host"),Integer.parseInt(pro.getProperty("port")));//参数:(配置对象,String ip,int 端口)
    }

    /**
     * 获取数据库连接对象的方法
     * @return
     */
    public static Jedis getJedis(){
        return jedisPool.getResource();
    }
}

测试类:

    @Test
    public void test01(){
        //通过连接池对象获取连接对象
        Jedis jedis = JedisPoolUtils.getJedis();

        //使用
        jedis.set("hello","java");
        
        String hello = jedis.get("hello");
        System.out.println(hello);
        
        //关闭(归还连接对象给连接池)
        jedis.close();
    }

要使用连接对象才能跟数据库进行交互(全部数据都是)

案例

1、分析&环境搭建

需求:

案例需求:
	1. 提供index.html页面,页面中有一个省份 下拉列表
	2. 当 页面加载完成后 发送ajax请求,加载所有省份

执行流程:
在这里插入图片描述

html页面:

    <script>
      $(function () {
        //异步请求
          $.get("provinceServlet",{},function (data) {
              //获取响应数据进行操作:[{"id":1,"name":"北京"},{"id":2,"name":"上海"},{"id":3,"name":"广州"},{"id":4,"name":"陕西"}]
                //遍历Json数组
                        //获取select对象
              var province = $("#province");
              $.each(data,function () {//参数:要遍历的对象,操作遍历元素的函数
                  //创建option元素
                  var option = "<option name='"+this.id+"'>"+this.name+"</option>";
                  //追加option元素到select元素内部
                  province.append(option);
              })

          })
      })
    </script>
</head>
<body>
<select id="province">
    <option>----请选择省份----</option>
</select>
</body>

注意:

  • var option = "<option name='"+this.id+"'>"+this.name+"</option>" 以前没见过还有这种用法

  • 原来是这样说明的:父对象1.append(子对象2),但是province.append(option)中的option,它不是JQ对象,它只是仅仅代表<option name='1'>北京</option>的意思,居然也可以。难道这里说的对象,不只是局限于JQ对象?这个算JS元素对象?

  • $.get("provinceServlet",{},function (data)中{ }代表没有发送请求参数和参数值,data参数代表服务器响应的数据,一般返回的是Json。此代码中Json返回的是数组,所以要遍历。遍历完,每个元素是{xxx},{ }又代表Json对象,所以this代表遍历的Json对象,this.key = value。

ProviceServlet:

@WebServlet("/provinceServlet")
public class provinceServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //1、调用service查询
        ProvinceService service = new ProvinceServiceImpl();
        List<Province> list = service.findAll();
        //2、将集合转换成json字符串
        ObjectMapper mapper = new ObjectMapper();
        String Json = mapper.writeValueAsString(list);
        //3、设置响应的MIME格式类型和编码格式
        response.setContentType("application/json;charset=utf-8");
        System.out.println(Json);
        //4、响应结果,发送Json
        response.getWriter().write(Json);
    }

service:

    @Override
    public List<Province> findAll() {
        return dao.findAll();
    }

dao:

private ProvinceDao dao = new ProvinceDaoImpl();
    @Override
    public List<Province> findAll() {
        String sql="select * from province";
        List<Province> list = template.query(sql, new BeanPropertyRowMapper<Province>(Province.class));
        return list;
    }

provincex的JavaBean:

 private int id;
 private String name;

Jedis连接池工具类、JDBCTemplate工具类…

2、实现:查询数据库

3、redis缓存优化

为什么要使用redis进行缓存?

因为如果客户端上的一些数据它不经常改变,那么我们可以把数据缓存到redis里面,当客户端再一次请求时,它就不需要再去找关系型数据库进行重复的CRUD了,这样浪费时间。
而,redis充当了service和数据库之间的中间件。当dao查询数据的时候,如果redis没有,redis就去寻找dao,然后把dao的数据存到进缓存;如果有,直接不需要经过查询数据库,直接返回查询结果。

使用redis要注意什么?

跟【为什么】的用法反着来。
当然,即使数据不经常改变,但它也会又改变的时候。如果数据库的数据一旦改变(数据库的表执行了CRUD操作),那么需要将redis之前缓存的数据情况掉,再一次存入redis即可。
那么,怎么存入?
在service对应的(CRUD)方法中,把【redis】get的key清除掉。因为虽然数据库变动了,但是redis之前缓存了旧的数据了,而service判断key不为空就直接用缓存的数据,而这个key是旧数据的key,所以,你要更新缓存,就得清空就缓存,再更新新的key。

原理图:
在这里插入图片描述
代码演示:(代码跟上面一样,更新了更新的地方)

ProviceServlet:

@WebServlet("/provinceServlet")
public class provinceServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        //1、调用service查询
        ProvinceService service = new ProvinceServiceImpl();
        String Json = service.findAllJson();
        //3、设置响应的MIME格式类型和编码格式
        response.setContentType("application/json;charset=utf-8");
        System.out.println(Json);
        //4、响应结果,发送Json
        response.getWriter().write(Json);
    }

}

service:

DIY:

@Override
    public String findAllJson() {
        //创建redis数据库连接对象
        Jedis jedis = JedisPoolUtils.getJedis();
        //获取Json字符串
        String Json = jedis.get("province");
        //先判断redis是否存在Json字符串数据
        if (Json ==null|| Json.length()==0){
        //没有这个数据,查询数据库数据,并缓存到redis缓存中
                //查询数据库
            List<Province> list = dao.findAll();
                //将集合转Json字符串
            ObjectMapper mapper = new ObjectMapper();
            try {
                Json = mapper.writeValueAsString(list);
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
                //缓存Json到redis
            jedis.set("province",Json);
                //归还连接对象
            jedis.close();

        }
        //有数据就返回给servlet响应
        return Json;
    }

官方


    @Override
    public List<Province> findAll() {
        return dao.findAll();
    }

    /**
        使用redis缓存
     */

    @Override
    public String findAllJson() {
        //1.先从redis中查询数据
        //1.1获取redis客户端连接
        Jedis jedis = JedisPoolUtils.getJedis();
        String province_json = jedis.get("province");

        //2判断 province_json 数据是否为null
        if(province_json == null || province_json.length() == 0){
            //redis中没有数据
            System.out.println("redis中没数据,查询数据库...");
            //2.1从数据中查询
            List<Province> ps = dao.findAll();
            //2.2将list序列化为json
            ObjectMapper mapper = new ObjectMapper();
            try {
                province_json = mapper.writeValueAsString(ps);
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }

            //2.3 将json数据存入redis
            jedis.set("province",province_json);
            //归还连接
            jedis.close();

        }else{
            System.out.println("redis中有数据,查询缓存...");
        }


        return province_json;

解析:

原本的Servlet使直接使用service层的返回list,再进行list转Json字符串;
redis缓存优化,是把Json字符串缓存到redis中,客户端第一次请求查询redis肯定没有这个数据,即,为空。然后,service去数据库查询,并把Json字符串存进redis中

发布了65 篇原创文章 · 获赞 1 · 访问量 1459

猜你喜欢

转载自blog.csdn.net/weixin_45097731/article/details/104972412