概述:
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中