目录
Spring Boot 整合mvc 实现增删改查
1. 导入依赖
<dependencies> <!-- 导入七牛云需要坐标 --> <dependency> <groupId>com.qiniu</groupId> <artifactId>qiniu-java-sdk</artifactId> <version>7.2.25</version> </dependency> <dependency> <groupId>com.squareup.okhttp3</groupId> <artifactId>okhttp</artifactId> <version>3.14.2</version> <scope>compile</scope> </dependency> <dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> <version>2.8.5</version> <scope>compile</scope> </dependency> <dependency> <groupId>com.qiniu</groupId> <artifactId>happy-dns-java</artifactId> <version>0.1.6</version> <scope>test</scope> </dependency> <!-- 文件上传 --> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.6</version> </dependency> <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.3.3</version> </dependency> <!-- 试图 --> <dependency> <groupId>com.jfinal</groupId> <artifactId>enjoy</artifactId> <version>5.0.3</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.28</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.3</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
2. 配置yml
spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/javaee2404?serverTimezone=GMT username: root password: 123456 mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
3. 数据库设计
4. 新建实体类
这里使用lombok插件
@Data @NoArgsConstructor @AllArgsConstructor @TableName("sporter") public class Sporter implements Serializable { @TableId(value = "s_id", type = IdType.AUTO) private Integer sId; @TableField("s_name") private String SName; @TableField("s_type") private String sType; @TableField("s_country") private String sCountry; @TableField("pic_name") private String picName; @TableField(exist = false) private MultipartFile pic; }
5.config
Configuration public class EnjoyConfig { @Bean(name = "jfinalViewResolver") public JFinalViewResolver getJFinalViewResolver() { // 创建用于整合 spring boot 的 ViewResolver 扩展对象 JFinalViewResolver jfr = new JFinalViewResolver(); // 对 spring boot 进行配置 jfr.setSuffix(".html"); jfr.setContentType("text/html;charset=UTF-8"); jfr.setOrder(0); // 设置在模板中可通过 #(session.value) 访问 session 中的数据 jfr.setSessionInView(true); // 获取 engine 对象,对 enjoy 模板引擎进行配置,配置方式与前面章节完全一样 Engine engine = JFinalViewResolver.engine; // 热加载配置能对后续配置产生影响,需要放在最前面 engine.setDevMode(true); // 使用 ClassPathSourceFactory 从 class path 与 jar 包中加载模板文件 engine.setToClassPathSourceFactory(); // 在使用 ClassPathSourceFactory 时要使用 setBaseTemplatePath // 代替 jfr.setPrefix("/view/") engine.setBaseTemplatePath("/templates/"); // 更多配置与前面章节完全一样 // engine.addDirective(...) // engine.addSharedMethod(...); return jfr; } }
6. util
public class FileUtil { //上传文件 public static String fileUpload(MultipartFile file){ //方式1.将文件upic以流的方式写入当前服务器磁盘(应用服务器) //方式2.文件服务器(七牛云) //构造一个带指定 Region 对象的配置类 Configuration cfg = new Configuration(Region.autoRegion()); //...其他参数参考类注释 UploadManager uploadManager = new UploadManager(cfg); //...生成上传凭证,然后准备上传 String accessKey = "KA0lv77wcSzEmcgBZ2Mthk7ccvVQvqi88BvTC45T"; String secretKey = "j98lF-5T1aOxWacSNJRMHT9B7AUxJ_JKIC63LtC0"; String bucket = "javaee2404"; //默认不指定key的情况下,以文件内容的hash值作为文件名 String key = null; String name = null; try { byte[] uploadBytes = file.getBytes(); Auth auth = Auth.create(accessKey, secretKey); String upToken = auth.uploadToken(bucket); try { Response response = uploadManager.put(uploadBytes, key, upToken); //解析上传成功的结果 DefaultPutRet putRet = new Gson().fromJson(response.bodyString(), DefaultPutRet.class); System.out.println(putRet.key);//获取文件名 System.out.println(putRet.hash);//获取文件hash值 name = putRet.key; } catch (QiniuException ex) { Response r = ex.response; System.err.println(r.toString()); try { System.err.println(r.bodyString()); } catch (QiniuException ex2) { //ignore } } } catch (Exception ex) { //ignore } return name; } //文件删除 public static void deleteFile(String name){ //构造一个带指定 Region 对象的配置类 Configuration cfg = new Configuration(Region.region0()); String accessKey = "KA0lv77wcSzEmcgBZ2Mthk7ccvVQvqi88BvTC45T"; String secretKey = "j98lF-5T1aOxWacSNJRMHT9B7AUxJ_JKIC63LtC0"; String bucket = "javaee2404"; String key = name; Auth auth = Auth.create(accessKey, secretKey); BucketManager bucketManager = new BucketManager(auth, cfg); try { bucketManager.delete(bucket, key); } catch (QiniuException ex) { //如果遇到异常,说明删除失败 System.err.println(ex.code()); System.err.println(ex.response.toString()); } } }
7. 三层
mapper
@Mapper public interface SporterMappper extends BaseMapper<Sporter> { }
service
public interface ISporterService extends IService<Sporter> { }
@Service public class SporterServiceImp extends ServiceImpl<SporterMappper, Sporter> implements ISporterService { }
controller
/** * @author zkt * @Version 1.0 * @since 2024/7/22 */ @Controller public class SporterController { @Autowired ISporterService service; //新增页面 @RequestMapping("/show") public String show() { return "save"; } //新增 @RequestMapping("/save") public String save(Sporter sporter) { //1.文件上传 String name = FileUtil.fileUpload(sporter.getPic()); //2.新增数据 sporter.setPicName(name); service.save(sporter); return "redirect:findAll"; } //查询 @RequestMapping("/findAll") public String findAll(HttpSession session) { List<Sporter> list = service.list(); session.setAttribute("list", list); return "list"; } //图片详情 @RequestMapping("/detail") public String detail(String picname, HttpSession session) { session.setAttribute("picname", picname); return "detail"; } //删除 @RequestMapping("/delete") public String delete(int id, String name) { service.removeById(id); FileUtil.deleteFile(name); return "redirect:findAll"; } // //之前一直修改不成功 因为 没有传value // 修改页面 @RequestMapping("/edit") public String edit(int id, HttpSession session) { Sporter sporter = service.getById(id); session.setAttribute("sporter", sporter); return "edit"; } // 报错原因 // 使用更安全的类型转换: //考虑使用Integer而不是int,因为int不能接受空值。如果sId未提供或无效,Integer可以接受null值。 // 修改 // 它有多种用途,主要用来简化从HTTP请求中提取数据的过程,以及将数据绑定到方法参数或域模型中。 @RequestMapping("/update") public String update( Sporter sporter) { // 因为不用修改上传文件 所以直接写 if (sporter.getPic() != null) { String name = FileUtil.fileUpload(sporter.getPic()); sporter.setPicName(name); } boolean b = service.updateById(sporter); System.out.println(b); return "redirect:findAll"; } }
8. 前端页面
save.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <center> <form action="/save" method="post" enctype="multipart/form-data"> <table border="1" width="500px" style="margin-top: 100px"> <tr> <td colspan="2" align="center">新增页面</td> </tr> <tr> <td>姓名</td> <td><input name="sName"/></td> </tr> <tr> <td>类型</td> <td><input name="sType"/></td> </tr> <tr> <td>国籍</td> <td><input name="sCountry"/></td> </tr> <tr> <td>图片</td> <td><input name="pic" type="file"/></td> </tr> <tr> <td colspan="2" align="center"> <input type="submit" value="新增"/> <input type="reset" value="重置"/> </td> </tr> </table> </form> </center> </body> </html>
list.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <center> <table border="1" width="500px" style="margin-top: 100px"> <tr> <td colspan="6" align="center">列表页面</td> </tr> <tr> <td>姓名</td> <td>类型</td> <td>国际</td> <td>图片</td> <td>操作</td> </tr> #for(sp:session.list) <tr> <td>#(sp.sName)</td> <td>#(sp.sType)</td> <td>#(sp.sCountry)</td> <td> <a href="/detail?picname=#(sp.picName)"><img width="40px" height="30px" src="http://sgwgn7ooo.hb-bkt.clouddn.com/#(sp.picName)"/></a> </td> <td> <a href="/delete?id=#(sp.sId)&name=#(sp.picName)">删除</a> <a href="edit?id=#(sp.sId)">修改</a> </td> </tr> #end <tr> <td colspan="6" align="center"> <a href="/show">返回新增</a> </td> </tr> </table> </center> </body> </html>
detail.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <center> <table border="1" width="500px" style="margin-top: 100px"> <tr> <td colspan="6" align="center">列表页面</td> </tr> <tr> <td>姓名</td> <td>类型</td> <td>国际</td> <td>图片</td> <td>操作</td> </tr> #for(sp:session.list) <tr> <td>#(sp.sName)</td> <td>#(sp.sType)</td> <td>#(sp.sCountry)</td> <td> <a href="/detail?picname=#(sp.picName)"><img width="40px" height="30px" src="http://sgwgn7ooo.hb-bkt.clouddn.com/#(sp.picName)"/></a> </td> <td> <a href="/delete?id=#(sp.sId)&name=#(sp.picName)">删除</a> <a href="edit?id=#(sp.sId)">修改</a> </td> </tr> #end <tr> <td colspan="6" align="center"> <a href="/show">返回新增</a> </td> </tr> </table> </center> </body> </html>
edit.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <center> <form action="/update" method="post" enctype="multipart/form-data"> <table border="1" width="500px" style="margin-top: 100px"> <tr> <td colspan="2" align="center">修改页面</td> </tr> <input type="hidden" name="sId"/> <tr> <td>姓名</td> <td><input name="sName"/></td> </tr> <tr> <td>类型</td> <td><input name="sType"/></td> </tr> <tr> <td>国籍</td> <td><input name="sCountry"/></td> </tr> <tr> <td colspan="2" align="center"> <input type="submit" value="修改"/> <input type="reset" value="重置"/> </td> </tr> </table> </form> </center> </body> </html>
这里有个问题 修改之后值 传不上去
日志显示未修改
JDBC Connection [HikariProxyConnection@992477807 wrapping com.mysql.cj.jdbc.ConnectionImpl@74d9dcc0] will not be managed by Spring ==> Preparing: UPDATE sporter SET s_name=?, s_type=?, s_country=? WHERE s_id=? ==> Parameters: 2(String), 2(String), 2(String), null <== Updates: 0
改写后的edit.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <center> <form action="/update" method="post" enctype="multipart/form-data"> <table border="1" width="500px" style="margin-top: 100px"> <tr> <td colspan="2" align="center">修改页面</td> </tr> <input type="hidden" name="sId" value="#(session.sporter.sId)" /> <tr> <td>姓名</td> <td><input name="sName" value="#(session.sporter.sName)"/></td> </tr> <tr> <td>类型</td> <td><input name="sType" value="#(session.sporter.sType)"/></td> </tr> <tr> <td>国籍</td> <td><input name="sCountry" value="#(session.sporter.sCountry)"/></td> </tr> <tr> <td colspan="2" align="center"> <input type="submit" value="修改"/> <input type="reset" value="重置"/> </td> </tr> </table> </form> </center> </body> </html>
这样就可以了
测试
一、Spring Boot中的事务管理
spring事务管理方式: 1.编程式 2.声明式 //设置事务 @Transactional(isolation = Isolation.DEFAULT) @Transactional(propagation = Propagation.REQUIRED) //开启事务 @EnableTransactionManagement
编程式之前写过 这里主要是声明式
dao层 实体类 数据库 省略
service
public interface ICustomerService { public void batchAdd(); }
@Service @Transactional public class CustomerServiceImp implements ICustomerService{ @Autowired private CustomerDao dao; @Override public void batchAdd() { dao.save(new Customer("台湾君越酒店","五星级","佩洛西1","12345678")); int a = 10/0;//模拟错误 dao.save(new Customer("台湾君越酒店","五星级","佩洛西2","12345678")); dao.save(new Customer("台湾君越酒店","五星级","佩洛西3","12345678")); } }
controller
@RestController public class CustomerControllerImp { @Autowired private ICustomerService service; @RequestMapping("/add") public String batchAdd() { service.batchAdd(); return "成功"; } }
启动类
@SpringBootApplication @EnableTransactionManagement//开启事务管理 public class Springboot01CenterTxApplication { public static void main(String[] args) { SpringApplication.run(Springboot01CenterTxApplication.class, args); } }
只需要两个注解就可以完成事务管理
二、Spring Boot异步任务
在项目开发中,绝大多数情况下都是通过同步方式处理业务逻辑的,但是比如批量处理数据,批量发送 邮件,批量发送短信等操作 容易造成阻塞的情况,之前大部分都是使用多线程来完成此类任务而在 Spring 3+之后,就已经内置了@Async注解来完美解决这个问题,从而提高效率。
//使用的注解 @EnableAsync 启动类上开启基于注解的异步任务 @Async 标识的方法会异步执行
测试
service
public interface ICustomerService { public void batchAdd(); }
@Service public class CustomerServiceImp implements ICustomerService { @Override @Async public void batchAdd() { try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("======>批量新增"); } }
controller
@RestController public class twoController { @Autowired private ICustomerService service; @RequestMapping("/add") public String batchAdd() { service.batchAdd(); return "成功"; } }
执行结果:
三、SpringBoot定时任务调度
SpringTask
在项目开发中,经常需要执行一些定时任务,比如 每月1号凌晨需要汇总上个月的数据分析报表; 每天 凌晨分析前一天的日志信息等定时操作。Spring 为我们提供了异步执行定时任务调度的方式
//使用的注解 @EnableScheduling启动类上开启基于注解的定时任务 @Scheduled标识的方法会进行定时处理 需要通过 cron 属性来指定 cron 表达式:秒 分 时 日 月 星期 年 在线生成cron表达式 http://cron.qqe2.com
@Component public class Myjob { //秒 分 时 日 月 星期 【年】 //* 20 14 22 7 ? @Scheduled(cron = "* 48 14 22 7 ?") public void show(){ System.out.println("还想继续考研~~~"); } }
在启动类加上@EnableScheduling注解
执行结果:
四、SpringBoot 整合Mail
第一步添加坐标
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-mail</artifactId> </dependency>
第二步开启配置
#配置邮箱信息 spring.mail.username= spring.mail.password= #发送邮件服务器 spring.mail.host=smtp.qq.com #ssl连接 spring.mail.properties.smtp.ssl.enable=true
我的配置文件
注意 这里的password 要在qq邮箱获取
以下是获取QQ邮箱授权码的步骤:
- 登录到你的QQ邮箱:https://mail.qq.com/
- 在页面右上角,点击你的头像或账号名称,然后选择“账户”选项。
- 滑动页面找到“账户安全”部分,点击“授权码”旁边的“查看”按钮。
- 如果这是你第一次访问授权码,系统可能会要求你进行身份验证,如回答密保问题或通过手机验证码确认。
- 完成验证后,你会看到一个“生成授权码”的按钮,点击它。
- 授权码会立即生成并显示出来,你需要立即复制并保存这个授权码,因为它只显示一次,之后将不可见。
第三步编写代码
发送简单邮件
这里我在测试类里面编写
@SpringBootTest class Springboot01CenterTxApplicationTests { @Test void contextLoads() { } @Autowired private JavaMailSenderImpl javaMailSender; //发送简单邮件 @Test void show1(){ //1.创建邮件对象 SimpleMailMessage simpleMailMessage = new SimpleMailMessage(); //2.设置信息 simpleMailMessage.setSubject("还是想考研"); simpleMailMessage.setText("2024 好难 还是想考研~~~"); simpleMailMessage.setFrom("2029146849@qq.com"); simpleMailMessage.setTo("18709208653@163.com"); //3.发送邮件 javaMailSender.send(simpleMailMessage); System.out.println("发送成功"); } }
执行结果:
发送复杂邮件
//发送复杂邮件 @Test void show2()throws Exception { //1.创建邮件对象 MimeMessage mimeMessage = javaMailSender.createMimeMessage(); //2.创建MimeMessageHelper MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage,true); //3.设置信息 mimeMessageHelper.setSubject("程序员的误解"); mimeMessageHelper.setText("程序员是个<span style='color:red'>高薪,高危</span>的职业",true); mimeMessageHelper.addAttachment("1.jpg",new File("D:\\imgs\\zkt.jpg")); mimeMessageHelper.setFrom("2029146849@qq.com"); mimeMessageHelper.setTo("18709208653@163.com"); //4.发送邮件 javaMailSender.send(mimeMessage); }
五、SpringBoot 整合redis
1. Redis 简介
一个神奇的网站
问题现象
- 海量用户
- 高并发
罪魁祸首——关系型数据库
- 性能瓶颈:磁盘IO性能低下
- 扩展瓶颈:数据关系复杂,扩展性差,不便于大规模集群
解决思路
- 降低磁盘IO次数,越低越好 —— 内存存储
- 去除数据间关系,越简单越好 —— 不存储关系,仅存储数据
Nosql解决
NOSQL
NoSQL(NoSQL = Not Only SQL),意即“不仅仅是SQL”,是一项全新的数据库理念,泛指非关系型的数 据库。随着互联网web2.0网站的兴起,传统的关系数据库在应付web2.0网站,特别是超大规模和高并 发的SNS类型,web2.0纯动态网站已经显得力不从心,暴露了很多难以克服的问题,而非关系型的数据 库则由于其本身的特点得到了非 常迅速的发展。NoSQL数据库的产生就是为了解决大规模数据集合多重数据种类带来的挑战,尤其是大数据应用难题。
NOSQL和关系型数据库比较
优点:
- 1. 成本:nosql数据库简单易部署,基本都是开源软件,不需要像使用oracle那样花费大量成本购 买使用,相比关系型数据库价格便宜。
- 2. 查询速度:nosql数据库将数据存储于缓存之中,关系型数据库将数据存储在硬盘中,自然查询 速度远不及nosql数据库。
- 3. 存储数据的格式:nosql的存储格式是key,value形式、文档形式、图片形式等等,所以可以存 储 基础类型以及对象或者是集合等各种格式,而数据库则只支持基础类型。
- 4. 扩展性:关系型数据库有类似join这样的多表查询机制的限制导致扩展很艰难。
缺点:
- 1. 维护的工具和资料有限,因为nosql是属于新的技术,不能和关系型数据库10几年的技术同日而 语。
- 2. 不提供对sql的支持,如果不支持sql这样的工业标准(不遵循SQL标准),将产生一定用户的学习 和使用成本。
- 3. 不提供关系型数据库对事务的处理。
主流的NOSQL产品
键值(Key-Value)存储数据库 相关产品:
- Tokyo Cabinet/Tyrant、 Redis、Voldemort、Berkeley DB
- 典型应用: 内容缓存,主要用于处理大量数据的高访问负载。
- 数据模型: 一系列键值对
- 优势: 快速查询
- 劣势: 存储的数据缺少结构化
列存储数据库
- 相关产品:Cassandra, HBase, Riak
- 典型应用:分布式的文件系统
- 数据模型:以列簇式存储,将同一列数据存在一起
- 优势:查找速度快,可扩展性强,更容易进行分布式扩展
- 劣势:功能相对局限
文档型数据库
- 相关产品:CouchDB、MongoDB
- 典型应用:Web应用(与Key-Value类似,Value是结构化的)
- 数据模型: 一系列键值对
- 优势:数据结构要求不严格
- 劣势: 查询性能不高,而且缺乏统一的查询语法
图形(Graph)数据库
- 相关数据库:Neo4J、InfoGrid、Infinite Graph
- 典型应用:社交网络数据模型:图结构
- 优势:利用图结构相关算法。
- 劣势:需要对整个图做计算才能得出结果,不容易做分布式的集群方案。
数据库排名: http://db-engines.com/en/ranking
解决方案(电商场景)
1. 商品基本信息
名称 价格 厂商 mysql
2. 商品附加信息
描述 详情 评论 MongoDB
3. 图片信息 分布式文件系统
4. 搜索关键字 ES solr
5. 热点信息
高频 Redis 、 memcache 、 tair
波段性
什么是Redis
概念:Redis (REmote DIctionary Server) 是用 C 语言开发的一个开源的高性能键值对(key-value)数据库
特征:
- 1. 数据间没有必然的关联关系
- 2. 内部采用单线程机制进行工作
- 3. 高性能,官方提供测试数据,50个并发执行100000 个请求,读的速度是110000 次/s,写的速度是 81000次/s。
- 4. 多数据类型支持 字符串类型 列表类型 散列类型 集合类型 有序集合类型 string list hash set sorted_set
- 5. 持久化支持。可以进行数据灾难恢复
2. Redis 的下载与安装
Linux 版 (适用于企业级开发)
- Redis 高级开始使用
- 以4.0 版本作为主版本
Windows 版本 (适合零基础学习)
- Redis 入门使用
- 以 3.2 版本作为主版本
- 下载地址: https://github.com/MSOpenTech/redis/tags
核心文件:
- redis-server.exe 服务器启动命令
- redis-cli.exe 命令行客户端
- redis.windows.conf redis核心配置文件
- redis-benchmark.exe 性能测试工具
- redis-check-aof.exe AOF文件修复工具
- redis-check-dump.exe RDB文件检查工具(快照持久化文件)
方式1:双击redis-server.exe
注意PID随机生成
方式2:指令redis-server.exe redis.windows.conf
3.Redis 数据类型(5种常用)
- 1. string String
- 2. hash HashMap
- 3. list LinkedList
- 4. set HashSet
- 5. sorted_set TreeSet
3.1 String
- 存储的数据:单个数据,最简单的数据存储类型,也是最常用的数据存储类型
- 存储数据的格式:一个存储空间保存一个数据
- 存储内容:通常使用字符串,如果字符串以整数的形式展示,可以作为数字操作使用
==========================字符串类型 string========================== 添加/修改数据 set key value 获取数据 get key 删除数据 del key 添加/修改多个数据 mset key1 value1 key2 value2 …(m代表Multiple) 获取多个数据 mget key1 key2 … 获取数据字符个数(字符串长度) strlen key 追加信息到原始信息后部(如果原始信息存在就追加,否则新建) append key value
业务场景一:
业务场景一:========================================================== 大型企业级应用中,随着数据量的递增,分表操作是基本操作,使用多张表存储同类型数据,但是对应的主键 id 必须保证统一性,不能重复。Oracle 数据库具有 sequence 设定,可以解决该问题,但是 MySQL数 据库并不具有类似的机制,那么如何解决? 设置数值数据增加指定范围的值 incr key incrby key increment incrbyfloat key increment 设置数值数据减少指定范围的值 decr key decrby key increment
业务场景二:
业务场景二:========================================================== “中国有嘻哈”启动海选投票,只能通过微信投票,每个微信号每 4 小时只能投1票。 电商商家开启热门商品推荐热门商品不能一直处于热门,每种商品热门期维持3天,3天后自动取消热门。 新闻网站会出现热点新闻,热点新闻最大的特征是时效性,如何自动控制热点新闻的时效性。 设置数据具有指定的生命周期 setex key seconds value psetex key milliseconds value 设置key的过期时间 EXPIRE key seconds 查看key的剩余时间 ttl key
业务场景三:
业务场景三:========================================================== 主页高频访问信息显示控制,例如新浪微博大V主页显示粉丝数与微博数量 关注数focuss 粉丝数fans 微博数blogs 解决方案 在redis中为大V用户设定用户信息,以用户主键和属性值作为key,后台设定定时刷新策略即可 eg: user:id:3506728370:fans → 12210947 eg: user:id:3506728370:blogs → 6164 eg: user:id:3506728370:focuss → 83 在redis中以json格式存储大V用户信息,定时刷新(也可以使用hash类型) eg: user:id:3506728370 → {"id":3506728370,"name":"春晚","fans":12210862,"blogs":6164, "focus":83} Tips 3:redis应用于各种结构型和非结构型高热度数据访问加速 key 的设置约定===数据库中的热点数据key命名惯例 表名:主键名:主键值:字段名 eg: order:id:9527:name : 王瑞豪
注意事项:
注意事项:========================================================== 数据操作不成功的反馈与数据正常操作之间的差异 ① 表示运行结果是否成功 (integer) 0 → false 失败 (integer) 1 → true 成功 ② 表示运行结果值 (integer) 3 → 3 3个 (integer) 1 → 1 1个 数据未获取到 (nil)等同于null 数据最大存储量 512MB 数值计算最大范围(java中的long的最大值) 9223372036854775807
3.2 hash
存储的困惑
对象类数据的存储如果具有较频繁的更新需求操作会显得笨重
hash哈希特点
- 新的存储需求:对一系列存储的数据进行编组,方便管理,典型应用存储对象信息
- 需要的存储结构:一个存储空间保存多个键值对数据
- hash类型:底层使用哈希表结构实现数据存储
hash存储结构优化
- 如果field数量较少,存储结构优化为类数组结构
- 如果field数量较多,存储结构使用HashMap结构
==========================hash哈希类型========================== 添加/修改数据 hset key field value 获取数据 hget key field hgetall key 删除数据 hdel key field1 [field2] 获取哈希表中字段的数量 hlen key 获取哈希表中所有的字段名或字段值 hkeys key hvals key hash 类型数据操作的注意事项 1.hash类型下的value只能存储字符串,不允许存储其他数据类型,不存在嵌套现象。如果数据未获取 到,对应的值为(nil) 2.每个 hash 可以存储 2^32^-1个键值对 3.hash类型十分贴近对象的数据存储形式,并且可以灵活添加删除对象属性。但hash设计初衷不是为了 存储大量对象而设计的,切记不可滥用,更不可以将hash作为对象列表使用 4.hgetall 操作可以获取全部属性,如果内部field过多,遍历整体数据效率就很会低,有可能成为数 据访问瓶颈
业务场景一:
业务场景一:========================================================== string存储对象(json)与hash存储对象 Stirng存对象强调整体性一般用于读 hash存对象强调灵活性一般用于写
业务场景二:
业务场景二:========================================================== 电商网站购物车设计与实现 购物车的redis存储模型 添加、浏览、更改数量、删除、清空
解决方案:
- 以客户id作为key,每位客户创建一个hash存储结构存储对应的购物车信息
- 将商品编号作为field,购买数量作为value进行存储
- 添加商品:追加全新的field与value
- 浏览:遍历hash
- 更改数量:自增/自减,设置value值
- 删除商品:删除field
- 清空:删除key
3.3 List
- 数据存储需求:存储多个数据,并对数据进入存储空间的顺序进行区分
- 需要的存储结构:一个存储空间保存多个数据,且通过数据可以体现进入顺序
- list类型:保存多个数据,底层使用双向链表存储结构实现
双向链表
==========================list========================== 添加/修改数据 lpush key value1 [value2] …… rpush key value1 [value2] …… 获取数据 lrange key start stop lindex key index llen key 获取并移除数据 lpop key rpop key 通过下标截取指定的长度,这个list已经被改变了,只剩下截取的元素 ltrim key start end lset将列表中指定下标的值替换为另外一个值,更新操作 lset key 下标 新数据 # 如果不存在列表我们去更新就会报错
业务场景一:点赞取消重新排序
list 类型数据操作注意事项 1.list中保存的数据都是string类型的,数据总容量是有限的,最多2^32^-1个元素 (4294967295) 2.list具有索引的概念,但是操作数据时通常以队列的形式进行入队出队操作,或以栈的形式进行入栈出 栈操作 3.获取全部数据操作结束索引设置为-1 4.list可以对数据进行分页操作,通常第一页的信息来自于list,第2页及更多的信息通过数据库的形式加 载
3.4 Set
- 新的存储需求:存储大量的数据,在查询方面提供更高的效率
- 需要的存储结构:能够保存大量的数据,高效的内部存储机制,便于查询
- set类型:与hash存储结构完全相同,仅存储键,不存储值(nil),并且值是不允许重复的
==========================Set========================== 添加数据 sadd key member1 [member2] 获取全部数据 smembers key 删除数据 srem key member1 [member2] 获取集合数据总量 scard key 判断集合中是否包含指定数据 sismember key member
业务场景一:
业务场景一:========================================================== 每位用户首次使用今日头条时会设置3项爱好的内容,但是后期为了增加用户的活跃度、兴趣点,必须让用户 对其他信息类别逐渐产生兴趣,增加客户留存度,如何实现? 业务分析 1.系统分析出各个分类的最新或最热点信息条目并组织成set集合 2.随机挑选其中部分信息 3.配合用户关注信息分类中的热点信息组织成展示的全信息集合 解决方案 随机获取集合中指定数量的数据 srandmember key [count] 随机获取集合中的某个数据并将该数据移出集合 spop key [count] Tips 1: redis 应用于随机推荐类信息检索,例如热点歌单推荐,热点新闻推荐,热卖旅游线路,应用APP推荐, 大V推荐等
业务场景二:
业务场景二:========================================================== 1.脉脉为了促进用户间的交流,保障业务成单率的提升,需要让每位用户拥有大量的好友,事实上职场新人不 具有更多的职场好友,如何快速为用户积累更多的好友? 2.新浪微博为了增加用户热度,提高用户留存性,需要微博用户在关注更多的人,以此获得更多的信息或热门 话题,如何提高用户关注他人的总量? 3.QQ新用户入网年龄越来越低,这些用户的朋友圈交际圈非常小,往往集中在一所学校甚至一个班级中,如何 帮助用户快速积累好友用户带来更多的活跃度? 4.美团外卖为了提升成单量,必须帮助用户挖掘美食需求,如何推荐给用户最适合自己的美食? 解决方案 求两个集合的交、并、差集 sinter key1 [key2] sunion key1 [key2] sdiff key1 [key2] 求两个集合的交、并、差集并存储到指定集合中 sinterstore destination key1 [key2] sunionstore destination key1 [key2] sdiffstore destination key1 [key2] 将指定数据从原始集合中移动到目标集合中 smove source destination member
set 类型数据操作的注意事项
- 1.set 类型不允许数据重复,如果添加的数据在 set 中已经存在,将只保留一份
- 2.set 虽然与hash的存储结构相同,但是无法启用hash中存储值的空间
3.5 sorted_set
- 新的存储需求:数据排序有利于数据的有效展示,需要提供一种可以根据自身特征进行排序的方式
- 需要的存储结构:新的存储模型,可以保存可排序的数据
- sorted_set类型:在set的存储结构基础上添加可排序字段
==========================sorted_set========================== 添加数据 zadd key score1 member1 [score2 member2] 获取全部数据 zrange key start(开始索引) stop(结束索引) [WITHSCORES] #[WITHSCORES]可以除了数据还 显示分值 zrevrange key start stop [WITHSCORES] 反转 删除数据 zrem key member [member ...] 获取集合数据总量 zcard key
4. Jedis(了解)
我们要使用 Java 来操作 Redis,什么是Jedis 是 Redis 官方推荐的 java连接开发工具! 使用Java 操作 Redis 中间件!
1.导入对应的依赖
<!--导入jedis的包--> <dependencies> <!-- https://mvnrepository.com/artifact/redis.clients/jedis --> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>3.2.0</version> </dependency> </dependencies>
2、编码测试:
import redis.clients.jedis.Jedis; public class TestPing { public static void main(String[] args) { // 1、 new Jedis 对象即可 Jedis jedis = new Jedis("127.0.0.1",6379); // jedis 所有的命令就是我们之前学习的所有指令!所以之前的指令学习很重要! System.out.println(jedis.ping()); } }