精华内容
下载资源
问答
  • 作者:llc687https://llc687.top/post/如何完成一次快速的查询哪个男孩不想完成一次快速的查询?1. MySQL查询慢是什么体验? 谢邀,利益相关。大多数互联网应...
    
    

    作者:llc687

    https://llc687.top/post/如何完成一次快速的查询

    哪个男孩不想完成一次快速的查询?

    1. MySQL查询慢是什么体验?

    谢邀,利益相关。

    大多数互联网应用场景都是读多写少,业务逻辑更多分布在写上。对读的要求大概就是要快。那么都有什么原因会导致我们完成一次出色的慢查询呢?

    1.1 索引

    在数据量不是很大时,大多慢查询可以用索引解决,大多慢查询也因为索引不合理而产生。

    MySQL 索引基于 B+ 树,这句话相信面试都背烂了,接着就可以问最左前缀索引、 B+ 树和各种树了。

    说到最左前缀,实际就是组合索引的使用规则,使用合理组合索引可以有效的提高查询速度,为什么呢?

    因为索引下推。如果查询条件包含在了组合索引中,比如存在组合索引(a,b),查询到满足 a 的记录后会直接在索引内部判断 b 是否满足,减少回表次数。同时,如果查询的列恰好包含在组合索引中,即为覆盖索引,无需回表。索引规则估计都知道,实际开发中也会创建和使用。问题可能更多的是:为什么建了索引还慢?

    1.1.1 什么原因导致索引失效

    建了索引还慢,多半是索引失效(未使用),可用 explain 分析。索引失效常见原因有 :

    1. where 中使用 != 或 <> 或 or 或表达式或函数(左侧)

    2. like 语句 % 开头

    3. 字符串未加’’

    4. 索引字段区分度过低,如性别

    5. 未匹配最左前缀

    (一张嘴就知道老面试题了) 为什么这些做法会导致失效,成熟的 MySQL 也有自己的想法。

    1.1.2 这些原因为什么导致索引失效

    如果要 MySQL 给一个理由,还是那棵 B+ 树。

    函数操作

    当在 查询 where = 左侧使用表达式或函数时,如字段 A 为字符串型且有索引, 有 where length(a) = 6查询,这时传递一个 6 到 A 的索引树,不难想象在树的第一层就迷路了。

    隐式转换

    隐式类型转换和隐式字符编码转换也会导致这个问题。

    • 隐式类型转换对于 JOOQ 这种框架来说一般倒不会出现。

    • 隐式字符编码转换在连表查询时倒可能出现,即连表字段的类型相同但字符编码不同。

    破坏了有序性

    至于 Like 语句 % 开头、字符串未加 ’’ 原因基本一致,MySQL 认为对索引字段的操作可能会破坏索引有序性就机智的优化掉了。

    不过,对于如性别这种区分度过低的字段,索引失效就不是因为这个原因。

    1.1.3 性别字段为什么不要加索引

    为什么索引区分度低的字段不要加索引。盲猜效率低,效率的确低,有时甚至会等于没加。

    对于非聚簇索引,是要回表的。假如有 100 条数据,在 sex 字段建立索引,扫描到 51 个 male,需要再回表扫描 51 行。还不如直接来一次全表扫描呢。

    所以,InnoDB 引擎对于这种场景就会放弃使用索引,至于区分度多低多少会放弃,大致是某类型的数据占到总的 30% 左右时,就会放弃使用该字段的索引,有兴趣可以试一下。

    1.1.4 有什么好用且简单的索引方法

    前面说到大多慢查询都源于索引,怎么建立并用好索引。这里有一些简单的规则。

    • 索引下推:性别字段不适合建索引,但确实存在查询场景怎么办?如果是多条件查询,可以建立联合索引利用该特性优化。

    • 覆盖索引:也是联合索引,查询需要的信息在索引里已经包含了,就不会再回表了。

    • 前缀索引:对于字符串,可以只在前 N 位添加索引,避免不必要的开支。假如的确需要如关键字查询,那交给更合适的如 ES 或许更好。

    • 不要对索引字段做函数操作

    • 对于确定的、写多读少的表或者频繁更新的字段都应该考虑索引的维护成本。

    1.1.5 如何评价 MySQL 选错了索引

    有时,建立了猛一看挺正确的索引,但事情却没按计划发展。就像“为啥 XXX 有索引,根据它查询还是慢查询”。

    此刻没准要自信点:我的代码不可能有 BUG,肯定是 MySQL 出了问题。MySQL 的确可能有点问题。

    这种情况常见于建了一大堆索引,查询条件一大堆。没使用你想让它用的那一个,而是选了个区分度低的,导致过多的扫描。造成的原因基本有两个:

    • 信息统计不准确:可以使用 analyze table x重新分析。

    • 优化器误判:可以 force index强制指定。或修改语句引导优化器,增加或删除索引绕过。

    但根据我浅薄的经验来看,更可能是因为你建了些没必要的索引导致的。不会真有人以为 MySQL 没自己机灵吧?

    除了上面这些索引原因外,还有下面这些不常见或者说不好判断的原因存在。

    1.2 等MDL锁

    在 MySQL 5.5 版本中引入了 MDL,对一个表做 CRUD 操作时,自动加 MDL 读锁;对表结构做变更时,加 MDL 写锁。读写锁、写锁间互斥。

    当某语句拿 MDL 写锁就会阻塞 MDL 读锁,可以使用show processlist命令查看处于Waiting for table metadata lock状态的语句。

    1.3 等 flush

    flush 很快,大多是因为 flush 命令被别的语句堵住,它又堵住了 select 。通过show processlist命令查看时会发现处于Waiting for table flush状态。

    1.4 等行锁

    某事物持有写锁未提交。

    1.5 当前读

    InnoDB 默认级别是可重复读。设想一个场景:事物 A 开始事务,事务 B 也开始执行大量更新。B 率先提交, A 是当前读,就要依次执行 undo log ,直到找到事务 B 开始前的值。

    1.6 大表场景

    在未二次开发的 MYSQL 中,上亿的表肯定算大表,这种情况即使在索引、查询层面做到了较好实现,面对频繁聚合操作也可能会出现 IO 或 CPU 瓶颈,即使是单纯查询,效率也会下降。

    且 Innodb 每个 B+ 树节点存储容量是 16 KB,理论上可存储 2kw 行左右,这时树高为3层。我们知道,innodb_buffer_pool 用来缓存表及索引,如果索引数据较大,缓存命中率就堪忧,同时 innodb_buffer_pool 采用 LRU 算法进行页面淘汰,如果数据量过大,对老或非热点数据的查询可能就会把热点数据给挤出去。

    所以对于大表常见优化即是分库分表和读写分离了。

    1.6.1 分库分表

    方案

    是分库还是分表呢?这要具体分析。

    • 如果磁盘或网络有 IO 瓶颈,那就要分库和垂直分表。

    • 如果是 CPU 瓶颈,即查询效率偏低,水平分表。

    水平即切分数据,分散原有数据到更多的库表中。

    垂直即按照业务对库,按字段对表切分。

    工具方面有 sharding-sphere、TDDL、Mycat。动起手来需要先评估分库、表数,制定分片规则选 key,再开发和数据迁移,还要考虑扩容问题。

    问题

    实际运行中,写问题不大,主要问题在于唯一 ID 生成、非 partition key 查询、扩容。

    • 唯一 ID 方法很多,DB 自增、Snowflake、号段、一大波GUID算法等。

    • 非 partition key 查询常用映射法解决,映射表用到覆盖索引的话还是很快的。或者可以和其他 DB 组合。

    • 扩容要根据分片时的策略确定,范围分片的话就很简单,而随机取模分片就要迁移数据了。也可以用范围 + 取模的模式分片,先取模再范围,可以避免一定程度的数据迁移。

    当然,如果分库还会面临事务一致性和跨库 join 等问题。

    1.6.2 读写分离

    为什么要读写分离

    分表针对大表解决 CPU 瓶颈,分库解决 IO 瓶颈,二者将存储压力解决了。但查询还不一定。

    如果落到 DB 的 QPS 还是很高,且读远大于写,就可以考虑读写分离,基于主从模式将读的压力分摊,避免单机负载过高,同时也保证了高可用,实现了负载均衡。

    问题

    主要问题有过期读和分配机制。

    • 过期读,也就是主从延时问题,这个对于。

    • 分配机制,是走主还是从库。可以直接代码中根据语句类型切换或者使用中间件。

    1.7 小结

    以上列举了 MySQL 常见慢查询原因和处理方法,介绍了应对较大数据场景的常用方法。

    分库分表和读写分离是针对大数据或并发场景的,同时也为了提高系统的稳定和拓展性。但也不是所有的问题都最适合这么解决。

    2. 如何评价 ElasticSearch

    前文有提到对于关键字查询可以使用 ES。那接着聊聊 ES 。

    2.1 可以干什么

    ES 是基于 Lucene 的近实时分布式搜索引擎。使用场景有全文搜索、NoSQL Json 文档数据库、监控日志、数据采集分析等。

    对非数据开发来说,常用的应该就是全文检索和日志了。ES 的使用中,常和 Logstash, Kibana 结合,也成为 ELK 。先来瞧瞧日志怎么用的。

    下面是我司日志系统某检索操作:打开 Kibana 在 Discover 页面输入格式如 “xxx” 查询。

    该操作可以在 Dev Tools 的控制台替换为:

    GET yourIndex/_search  
    {    
     "from" : 0, "size" : 10,  
     "query" : {  
            "match_phrase" : {  
                "log" : "xxx"       
            }  
        }  
    }  
    

    什么意思?Discover 中加上 “” 和 console 中的 match_phrase 都代表这是一个短语匹配,意味着只保留那些包含全部搜索词项,且位置与搜索词项相同的文档。

    2.2 ES 的结构

    在 ES 7.0 之前存储结构是 Index -> Type -> Document,按 MySQL 对比就是 database - table - id(实际这种对比不那么合理)。7.0 之后 Type 被废弃了,就暂把 index 当做 table 吧。

    在 Dev Tools 的 Console 可以通过以下命令查看一些基本信息。也可以替换为 crul 命令。

    1. GET /_cat/health?v&pretty:查看集群健康状态

    2. GET /_cat/shards?v :查看分片状态

    3. GET yourindex/_mapping   :index mapping结构

    4. GET yourindex/_settings   :index setting结构

    5. GET /_cat/indices?v   :查看当前节点所有索引信息

    重点是 mapping 和 setting ,mapping 可以理解为 MySQL 中表的结构定义,setting 负责控制如分片数量、副本数量。

    以下是截取了某日志 index 下的部分 mapping 结构,ES 对字符串类型会默认定义成 text ,同时为它定义一个叫做 keyword 的子字段。这两的区别是:text 类型会进行分词, keyword 类型不会进行分词。

    "******": {  
        "mappings": {  
          "doc": {  
            "properties": {  
              "appname": {  
                "type": "text",  
                "fields": {  
                  "keyword": {  
                    "type": "keyword",  
                    "ignore_above": 256  
                  }  
                }  
    

    2.3 ES 查询为什么快?

    分词是什么意思?看完 ES 的索引原理你就 get 了。

    ES 基于倒排索引。嘛意思?传统索引一般是以文档 ID 作索引,以内容作为记录。倒排索引相反,根据已有属性值,去找到相应的行所在的位置,也就是将单词或内容作为索引,将文档 ID 作为记录。

    下图是 ES 倒排索引的示意图,由 Term index,Team Dictionary 和 Posting List 组成。

    图片

    图中的 Ada、Sara 被称作 term,其实就是分词后的词了。如果把图中的 Term Index 去掉,是不是有点像 MySQL 了?Term Dictionary 就像二级索引,但 MySQL 是保存在磁盘上的,检索一个 term 需要若干次的 random access 磁盘操作。

    而 ES 在 Term Dictionary 基础上多了层 Term Index ,它以 FST 形式保存在内存中,保存着 term 的前缀,借此可以快速的定位到 Term dictionary 的本 term 的 offset 。而且 FST 形式和 Term dictionary 的 block 存储方式都很节省内存和磁盘空间。

    到这就知道为啥快了,就是因为有了内存中的 Term Index , 它为 term 的索引 Term Dictionary 又做了一层索引。

    不过,也不是说 ES 什么查询都比 MySQL 快。检索大致分为两类。

    2.3.1 分词后检索

    ES 的索引存储的就是分词排序后的结果。比如图中的 Ada,在 MySQL 中 %da% 就扫全表了,但对 ES 来说可以快速定位

    2.3.2 精确检索

    该情况其实相差是不大的,因为 Term Index 的优势没了,却还要借此找到在 term dictionary 中的位置。也许由于 MySQL 覆盖索引无需回表会更快一点。

    2.4 什么时候用 ES

    如前所述,对于业务中的查询场景什么时候适合使用 ES ?我觉得有两种。

    2.4.1 全文检索

    在 MySQL 中字符串类型根据关键字模糊查询就是一场灾难,对 ES 来说却是小菜一碟。具体场景,比如消息表对消息内容的模糊查询,即聊天记录查询。

    但要注意,如果需要的是类似广大搜索引擎的关键字查询而非日志的短语匹配查询,就需要对中文进行分词处理,最广泛使用的是 ik 。Ik 分词器的安装这里不再细说。

    什么意思呢?

    分词

    开头对日志的查询,键入 “我可真是个机灵鬼” 时,只会得到完全匹配的信息。

    而倘若去掉 “”,又会得到按照 “我”、“可”,“真”….分词匹配到的所有信息,这明显会返回很多信息,也是不符合中文语义的。实际期望的分词效果大概是“我”、“可”、“真是”,“机灵鬼”,之后再按照这种分词结果去匹配查询。

    这是 ES 默认的分词策略对中文的支持不友善导致的,按照英语单词字母来了,可英语单词间是带有空格的。这也是不少国外软件中文搜索效果不 nice 的原因之一。

    对于该问题,你可以在 console 使用下方命令,测试当前 index 的分词效果。

    POST yourindex/_analyze  
     {  
       "field":"yourfield",  
       "text":"我可真是个机灵鬼"   
    }  
    

    2.4.2 组合查询

    如果数据量够大,表字段又够多。把所有字段信息丢到 ES 里创建索引是不合理的。使用 MySQL 的话那就只能按前文提到的分库分表、读写分离来了。何不组合下。

    1. ES + MySQL

    将要参与查询的字段信息加上 id,放入 ES,做好分词。将全量信息放入 MySQL,通过 id 快速检索。

    2. ES + HBASE

    如果要省去分库分表什么的,或许可以抛弃 MySQL ,选择分布式数据库,比如 HBASE , 对于这种 NOSQL 来说,存储能力海量,扩容 easy ,根据 rowkey 查询也很快。

    以上思路都是经典的索引与数据存储隔离的方案了。

    当然,摊子越大越容易出事,也会面临更多的问题。使用 ES 作索引层,数据同步、时序性、mapping 设计、高可用等都需要考虑。

    毕竟和单纯做日志系统对比,日志可以等待,用户不能。

    2.5 小结

    本节简单介绍了 ES 为啥快,和这个快能用在哪。现在你可以打开 Kibana 的控制台试一试了。

    如果想在 Java 项目中接入的话,有 SpringBoot 加持,在 ES 环境 OK 的前提下,完全是开箱即用,就差一个依赖了。基本的 CRUD 支持都是完全 OK 的。

    3. HBASE

    前面有提到 HBASE , 什么是 HBASE ,鉴于篇幅这里简单说说。

    3.1 存储结构

    关系型数据库如 MySQL 是按行来的。

    姓名小学中学大学
    李某XX小学YY中学NULL

    HBASE 是按列的(实际是列族)。列式存储上表就会变成:

    姓名学校名称
    李某XX小学
    李某YY中学

    下图是一个 HBASE 实际的表模型结构。

    Row key 是主键,按照字典序排序。TimeStamp 是版本号。info 和 area 都是列簇(column Family),列簇将表进行横向切割。name、age 叫做列,属于某一个列簇,可进行动态添加。Cell 是具体的 Value 。

    3.2 OLTP 和 OLAP

    数据处理大致可分成两大类:联机事务处理OLTP(on-line transaction processing)、联机分析处理OLAP(On-Line Analytical Processing)。

    • OLTP是传统的关系型数据库的主要应用,主要是基本的、日常的事务处理。

    • OLAP是数据仓库系统的主要应用,支持复杂分析,侧重决策支持,提供直观易懂的查询结果。

    面向列的适合做 OLAP,面向行的适用于联机事务处理(OLTP)。不过 HBASE 并不是 OLAP ,他没有 transaction,实际上也是面向 CF 的。一般也没多少人用 HBASE 做 OLAP 。

    3.3 RowKey

    HBASE 表设计的好不好,就看 RowKey 设计。这是因为 HBASE 只支持三种查询方式

    1、基于 Rowkey 的单行查询 2、基于 Rowkey 的范围扫描 3、全表扫描

    可见 HBASE 并不支持复杂查询。

    3.4 使用场景

    HBASE 并非适用于实时快速查询。它更适合写密集型场景,它拥用快速写入能力,而查询对于单条或小面积查询是 OK 的,当然也只能根据 rowkey。但它的性能和可靠性非常高,不存在单点故障。

    4. 总结

    个人觉得软件开发是循序渐进的,技术服务于项目,合适比新颖复杂更重要。

    如何完成一次快速的查询?最该做的还是先找找自己的 Bug,解决了当前问题再创造新问题。

    本文列举到的部分方案对于具体实现大多一笔带过,实际无论是 MySQL 的分表还是 ES 的业务融合都会面临很多细节和困难的问题,搞工程的总要绝知此事要躬行。

    参考

    • 亿级流量系统架构之如何设计每秒十万查询的高并发架构 https://juejin.im/post/5bfe771251882509a7681b3a

    • 使用 ELK 搭建日志集中分析平台 https://wsgzao.github.io/post/elk/)https://wsgzao.github.io/post/elk/

    • MySQL和Lucene索引对比分析https://www.cnblogs.com/luxiaoxun/p/5452502.html

    • HBASE 深入浅出 https://www.ibm.com/developerworks/cn/analytics/library/ba-cn-bigdata-hbase/index.html

    
    
    
    展开全文
  • 手写一个后端接口

    2021-06-02 13:02:41
    在web应用开发环境下,要写一个前台需要的接口很简单,就是简单的controller接口;本文介绍一下在Spring Boot开发环境下怎么写一个后台可以使用的接口; 说一下我的业务场景:整合了微信公众号的情况下,要向特定的...

    在web应用开发环境下,要写一个前台需要的接口很简单,就是简单的controller接口;本文介绍一下在Spring Boot开发环境下怎么写一个后台可以使用的接口;

    说一下我的业务场景:整合了微信公众号的情况下,要向特定的用户发送模板消息,设计思路就是我的微信公众号服务提供一个后台接口,其他服务通过对用户数据的分析来决定要不要向指定用户发送模板消息,如果需要发送的话,那么调用我的公众号服务所提供的发送模板消息接口即可。

    我们知道,spring是通过容器来对对象进行管理的,因此在spring中我们无法直接调用未被容器管理的对象,如果直接调用的话spring会报无法创建bean的错误或者bean没有找到的错误。那么要写一个后台通用的接口,我们就先要让自己可以对容器来进行操作,接着再通过容器来操作对象。

    容器在这里插入图片描述

    获取容器工具类

    /**通过上下文获取容器,记得要在启动类上添加@ComponentScan注解来扫描*/
    @Component
    public class SpringContxtUtil implements ApplicationContextAware {
    
        private static ApplicationContext applicationContext = null;
    
        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            if (SpringContxtUtil.applicationContext == null){
                SpringContxtUtil.applicationContext = applicationContext;
            }
        }
    
        public static ApplicationContext getApplicationContext() {
            return applicationContext;
        }
    
    	//根据bean的名字获取
        public static Object getBean(String name){
            return getApplicationContext().getBean(name);
        }
    
        /**根据类名获取bean*/
        @SuppressWarnings("unchecked")
        public static <T> T getBeanByClass(Class<T> clazz) throws BeansException {
            try {
                char[] cs=clazz.getSimpleName().toCharArray();
                cs[0] += 32;
                return (T) applicationContext.getBean(String.valueOf(cs));
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }
    }
    
    

    使用抽象工厂模式创建一个对模板消息处理的接口:

    在这里插入图片描述
    WaningMsgTemplate、TroubleMsgTemplate、RepairMsgTemplate的具体代码:

    public interface WaningMsgTemplate {
    
        void send(SendTemplateMsgBean templateMsgBean);
    
    }
    
    public interface TroubleMsgTemplate {
    
        void send(SendTemplateMsgBean templateMsgBean);
    
        /**
         *
         * @param touser 公众号粉丝id
         * @param url 点击模板消息要跳转的链接
         * @param head 标题
         * @param content 详细文本
         * @param remark 备注
         */
        void send(@NotNull String touser, String url, String head, @NotNull String content, String remark);
    
    }
    
    public interface RepairMsgTemplate {
    
        void send(SendTemplateMsgBean templateMsgBean);
    
    }
    

    给抽象工厂TemplateMsgFactory定义实现类

    public class TemplateMsgFactoryImpl implements TemplateMsgFactory {
    
    	//运行spring应用,如果不运行的话在进行接口调用的时候spring无法获取容器
        static {
            SpringApplication.run(WeixinApplication.class);
        }
    
        @Override
        public WaningMsgTemplate waningMsg() {
        	//调用工具类来获取容器
            return SpringContxtUtil.getBeanByClass(WarningMsgTemplateImpl.class);
        }
    
        @Override
        public TroubleMsgTemplate troubleMsg() {
            return SpringContxtUtil.getBeanByClass(TroubleMsgTemplateImpl.class);
        }
    
        @Override
        public RepairMsgTemplate repairMsg() {
            return SpringContxtUtil.getBeanByClass(RepairMsgTemplateImpl.class);
        }
    
    }
    

    测试方法

     public void send(){
     		//创建一个工厂
            TemplateMsgFactory templateMsg = new TemplateMsgFactoryImpl();
            //工厂生产一个产品,故障消息
            TroubleMsgTemplate troubleMsg = templateMsg.troubleMsg();
            //粉丝id
            String touser = "ididididididididid";
            //跳转链接
            String url = null;
            //标题
            String head = "注意!注意!";
            //内容
            String content = "插播一条紧急消息!";
            //备注
            String remark = "请及时查看";
            //调用产品的方法,发送
            troubleMsg.send(touser,url,head,content,remark);
        }
    

    总结
    其实接口的性质是唯一的,不管前后台的接口本质都是通过调用某个对象中的方法来处理一段逻辑返回一个结果。因为spring的特性,在后台调用接口的话就得先获取到要调用的对象所属的容器,然后通过容器来调用对象中的方法从而处理业务。

    那么实现一个后端接口的整体流程===>
    1、创建一个工具类来获取Spring容器(本质就是通过ApplicationContext来获取容器);
    2、初始化spring容器(简单说就是在后台将项目跑起来,SpringApplication.run(启动类.class));
    3、通过第一步编写的工具类获取所需容器(本质就是创建一个类的对象,SpringContxtUtil.getBeanByClass(WarningMsgTemplateImpl.class); SpringContxtUtil是我写的获取容器的工具类,这句话的意思就是创建一个WarningMsgTemplateImpl的对象);
    4、通过容器实现逻辑(就是通过第三步中创建的对象,调用对象中的方法。)

    展开全文
  • 后台接口流程图 写在前面的话:     本篇博文是本人(初级程序员)基于企业级实战项目后做一点点的记录,一是为了总结自己的开发经历,二是为了将自己所遇到的问题和所解决的办法分享给刚刚入门的各位小伙伴。...


    写在前面的话:
        本篇博文是本人(初级程序员)基于企业级实战项目后做一点点的记录,一是为了总结自己的开发经历,二是为了将自己所遇到的问题和所解决的办法分享给刚刚入门的各位小伙伴。有实战经验的各位前辈们可以将本人写的内容看成是饭后甜点,不到之处还请批评指正哦!

    后端接口流程图

        不知道各位小伙伴拿到自己的第一个实战项目的心情是如何的?是兴奋,是紧张,还是茫然?
    在这里插入图片描述
        如上图所示,为此篇文章的核心思想的体现!这是“同事”+“同学”+“自己”在项目中的所思所学所得。
    此次的内容我将在我之前的文章SpringBoot_实现基本增删改查(前后端分离版)基础之上将自己的相关理解写得更加透彻一些,因为8月份我刚接触这个框架,很多还是混淆的状态,只知道要这样做,但是不知道为什么要这样做,这此我就来讲讲为什么要这样做。
        思想归思想,思想流程是如上图这样,但是具体的编码过程应当按以下的步骤来梳理,逻辑才会更加的清晰易懂!
    (1)建立实体类;
    (2)Service接口;
    (3)XML文件;
    (4)Mapper接口;
    (5)ServiceImpl实现类;
    (6)Controller层调用接口;
    (7)最终接口展示。

    1. 建立实体类

        实体类是连通着我们的前端接口内容,返回的响应参数都是来自于这儿,参数的数据类型和注释都是来源于这里,所以建立实体类作为我们刚开始编码的第一步毋庸置疑。建立的同时也能知道我们最终是要得到什么值返回给前端,这样我们后面的步骤就围绕着如何得到我们的实体类而行动。
        这里想说明的是,因为很多时候返回给前端是一个集合类型,存储着很多的数据。这个时候,我们一般是建立两个实体类,一个实体类用来存储List<>集合,另外一个是用来存储该集合里的内部数据。具体的代码实例如下所示:
    其中,我们每个类之前加上一个“@Data”注解,能给我们省去不少的工作量。
    @Data注解的作用有二:(1)为当前类提供读写方法,从而不用写get、set方法;(2)为当前类提供 equals()、hashCode()、toString() 方法
    @ApiModelProperty注解: 是对各个属性的说明,加上这一注解可以在Swagger中看到响应参数的相关注释内容,让前端工作者能第一时间获取到数据信息提示,提供方便。

    package com.hncr.system.domain.vo;
    
    import io.swagger.annotations.ApiModelProperty;
    import lombok.Data;
    
    import java.util.List;
    
    /**
     * @ClassName: UserVo
     * @Description:用户集合实体类
     * @Author: YuHao
     * @Date: 2021/12/15
     */
    @Data
    public class UserVo {
        @ApiModelProperty("用户集合")
        private List<UserListVo> userListVos;
    }
    
    package com.hncr.system.domain.vo;
    
    import io.swagger.annotations.ApiModelProperty;
    import lombok.Data;
    
    /**
     * @ClassName: UserListVo
     * @Description:用户类
     * @Author: YuHao
     * @Date: 2021/12/15
     */
    @Data
    public class UserListVo {
        @ApiModelProperty("姓名")
        private String name;
    
        @ApiModelProperty("年龄")
        private Integer age;
    
        @ApiModelProperty("性别:(0:男,1:女)")
        private Integer sex;
    }
    

    2. Service接口

        完成第一步以后,我们开始写第二个部分。所谓的基于“接口编程”思想大概就是基于Service接口进行的吧!返回类型是我们的UserVo实体类,因为最终要得到的值就是要返回一个集合给前端,所以这里的返回类型便是我们的实体类。(以前的疑问,在这次项目中解决了)具体的代码实例如下所示:

    package com.hncr.system.service;
    
    import com.hncr.system.domain.vo.UserVo;
    
    /**
     * @InterfaceName: IUserService
     * @Description: Service接口
     * @Author: YuHao
     * @Date: 2021/12/15
     */
    public interface IUserService {
        UserVo userSummary();
    }
    

    3. XML文件

        其实第三步和第四步是同时进行,之所以将XML文件先写,是因为我们需要将我们的sql语句写好(这个很重要),确定具体需要多少个sql语句才能解决具体的Service接口问题。具体问题具体分析,有时候写一个sql语句就搞定问题了,有时候也需要三四个才行。逻辑不清晰的时候建议去咨询一下前辈或者需求的同事,他们一般都能将你的疑问给解决掉。之前我也发几篇基础版的基于mysql写基本增删查改的sql语句,但是应付项目上的操作还是差点意思,因为牵扯到很多业务逻辑的时候,基本的几个就不够看了。(后续本人将单独增加专说sql实用性语句的博文,大约在冬季哦
        在Navicat中做一些实例操作:根据年龄进行降序排序

    SELECT
    name,
    age,
    sex
    FROM
    `user`
    ORDER BY age DESC
    

    (其实在实际项目中要查询或者改变的操作会很多,数据量也会很大,很多时候要对某些个字段做索引操作才会缩短查询的时间,特别说明一下,查询时间超过3秒或者4秒的sql,在前端看来就是一个相当慢的接口了。数据量大的数据库,我们尽量做到只访问一次就好,以避免浪费不必要的时间来影响用户的体验)
    在这里插入图片描述
    在Navicat工具中我们已经查询得到我们想要的效果了,那我们要加入到我们的XML文件中,结合我们的第四步配置namespace、id、resultType等。实例代码如下:

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.hncr.system.mapper.UserMapper">
        <select id="userSummaryMapper" resultType="java.util.Map">
            SELECT
            name,
            age,
            sex
            FROM
            `user`
            ORDER BY age DESC
        </select>
    </mapper>
    

    4. Mapper接口

        其实在第三步过程中写好我们的sql语句后,就应该跳转到第四步当中了,因为具体的命名空间等是要以Mapper接口来命名的。在上面可知用一条sql语句就搞定了想要的结果,那Mapper接口写一个即可。反正就是sql写了几个,我们对应的就得写几个Mapper接口,其实例代码如下所示:
        可以看到这里的返回类型不太一样了哦,因为我之前的文章中是写的List < User >,而在这里是写的List < Map >,区别在于XML文件中的resultType中写明的为:“java.util.Map”,而我上篇文章中对应的resultType中写明的为:“com.example.demo.entity.User”,所以要XML文件中的resultType要和Mapper接口中的一一对应上,否则是查询不到数据,接口报错。在实际的项目中,后续的这种接口方式用得较多,List、Map、List < Map >可以对其做一下了解,看一下就知道如何使用。集合很重要,项目中大多数都是使用了集合,因为太好用了,大家一定掌握好。id对应上我们的Mapper接口名字 “userSUmmaryMapper”
        记住一点就好:XML文件多少个sql,Mapper接口就写多少个;Mapper接口返回类型,接口名称要与XML文件中resultType、id等要一一对应上。遵循这一点,多练习几次也就不会有程序报错了(刚刚开始练手,难免犯错)。

    package com.hncr.system.mapper;
    
    import org.springframework.stereotype.Repository;
    
    import java.util.List;
    import java.util.Map;
    
    /**
     * @InterfaceName: UserService
     * @Description:Mapper接口
     * @Author: YuHao
     * @Date: 2021/12/15
     */
    @Repository
    public interface UserMapper {
        List<Map> userSummaryMapper();
    }
    

    5. ServiceImpl实现类

        到了第五步,这可以算是当前所写后端接口中至关重要的一步了,因为在这里要实现数据的交互,代码逻辑。这里分别对应着流程图中的三个环节 “最终值、中间值、层层计算”,如果我们通过sql语句就能得到我们想要的结果,这个结果称之为 “最终值”,如果通过sql语句只是得到我们中间环节的某些值,这些值我们称之为 “中间值”,需要通过我们在实现类中用相关的逻辑代码(业务代码)进行 “层层计算”。直接能够得到最终值的是最为简单的过程,从数据库拿到数据就可以用,这个接口做起来会非常地快,因为压根就不需要用脑子呀,拿到数据直接 “Set” 就完事了。但是如果所做的项目相对而言逻辑复杂一点的,那么很多时候从数据库拿到的都是原始数据作为我们的中间值,这些数据是要通过我们的 “通用/共用/公用方法” 层层计算才能得到想要的最终值。不管如何,最终都是要Set到我们的对象之中,直接上所对应的实例代码,这样看起来直观明了:

    package com.hncr.system.service.impl;
    
    import com.hncr.system.domain.vo.UserListVo;
    import com.hncr.system.domain.vo.UserVo;
    import com.hncr.system.mapper.UserMapper;
    import com.hncr.system.service.IUserService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Map;
    
    /**
     * @ClassName: UserServiceImpl
     * @Description:
     * @Author: YuHao
     * @Date: 2021/12/15
     */
    @Service
    public class UserServiceImpl implements IUserService {
        @Autowired
        private UserMapper userMapper;
        
        List<Map> userSummaryMapper(){
            return userMapper.userSummaryMapper();
        }
        
        public UserVo userSummary(){
            UserVo userVo = new UserVo();
            List<Map> list1 = userSummaryMapper();
            List<UserListVo> list2 = new ArrayList<>();
    
            for (int i = 0; i < list1.size(); i++) {
                UserListVo userListVo = new UserListVo();
                Map map = list1.get(i);
                userListVo.setName((String) map.get("name"));
                userListVo.setAge((Integer) map.get("age"));
                userListVo.setSex((Integer) map.get("sex"));
                list2.add(userListVo);
            }
            userVo.setUserListVos(list2);
            return userVo;
        }
    }
    

        以上的代码获取到是最终值,是直接Set对象之中的。上面的实例代码算是最简单的了,提醒刚入门的小伙伴们,这种方式都是一些较常规做法,是需要掌握的。如果这里出来的是中间值,也不需要怕什么,所谓的 “层层计算” 这些什么的我们都是在大学期间或多或少接触过,我们不管是用什么语言编程都是做过的,不外乎就是“for、if、while、简单的常规算法…”,这个时候让我想到大学为啥那时候要学习数据结构了,因为不管你是码农也好还是算法工程师也罢,把这个学好都是会有用武之地的!!!(当然了,我是学渣,我就没学好,┭┮﹏┭┮)

    6.Controller层调用接口

        写完前五步的内容,第六步算是结尾工作了,跟以前的做法类似,这里就是一个调用过程,将相关的注解写好,注入相关的接口方法,然后返回给前端,就完成了我们的一个接口,记住了,这才是仅仅的一个而已,还有更多其他类似或者复杂一丢丢的接口在等着你去写哦~~照惯例上我们的实例代码:

    package com.hncr.web.controller.system;
    
    import com.hncr.system.domain.JsonResult;
    import com.hncr.system.domain.vo.UserVo;
    import com.hncr.system.service.IUserService;
    import io.swagger.annotations.Api;
    import io.swagger.annotations.ApiOperation;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    /**
     * @ClassName: UserController
     * @Description:
     * @Author: YuHao
     * @Date: 2021/12/15
     */
    @RestController
    @Api(value = "用户信息", tags = "用户信息")
    @RequestMapping("/user")
    public class UserController {
        @Autowired
        private IUserService userService;
    
        @ApiOperation(value = "用户信息汇总", notes = "{<br/>" +
                "    \"msg\": \"操作成功\",<br/>" +
                "    \"code\": 200,<br/>" +
                "    \"data\": {<br/>" +
                "    }<br/>" +
                "}")
        @GetMapping("UserSummary")
        public JsonResult<UserVo> userSummary(){
            UserVo userVo = userService.userSummary();
            return JsonResult.success(userVo);
        }
    }
    

        以上的Controller层代码中,很多我都在以前都做过介绍了,这里我就不再累赘了,就是几个非常常用的注解,不记得就多回头看看自己或者同事以前写过的项目代码,还有直接copy使用也可以。主要想说的是,本人这有一个不错的json返回值类,我也将其贴出来,大家可以拿这个用,这是已离职的好朋友分享给我的,其实他技术挺强,但是因为学历限制了他的发展,确实可惜了。这就说明了其实企业还是比较看重学历的,有一个好一点的学历,起步工资还是高不少的!!!加油吧,小伙伴们,下面是具体的代码:

    package com.hncr.system.domain;
    
    import com.alibaba.fastjson.JSONObject;
    import com.hncr.common.constant.HttpStatus;
    import io.swagger.annotations.ApiModelProperty;
    import lombok.Data;
    
    /**
     * @ClassName: JsonResult
     * @Description: json 返回值
     * @Author: YuHao
     * @Date: 2021/11/25
     */
    @Data
    public class JsonResult<T>{
    
        @ApiModelProperty(notes = "响应码")
        private Integer code;
    
        @ApiModelProperty(notes = "响应消息")
        private String msg;
    
        @ApiModelProperty(notes = "返回数据")
        private T data;
    
        /**
         * 若没有数据返回,默认状态码为 0,提示信息为“操作成功!”
         */
        public JsonResult() {
            this.code = HttpStatus.SUCCESS;
            this.msg = "success";
        }
    
        /**
         * 若没有数据返回,可以人为指定状态码和提示信息
         * @param code
         * @param msg
         */
        public JsonResult(int code, String msg) {
            this.code = code;
            this.msg = msg;
        }
    
        /**
         * 有数据返回时,状态码为 0,默认提示信息为“操作成功!”
         * @param data
         */
        public JsonResult(T data) {
            this.data = data;
            this.code = HttpStatus.SUCCESS;
            this.msg = "success";
        }
    
        public static JsonResult success(){
            return new JsonResult<>();
        }
    
        public static <T> JsonResult success(T data){
            return new JsonResult(data);
        }
    
        public static JsonResult error(String msg){
            return new JsonResult(HttpStatus.ERROR,msg);
        }
    
        public static JsonResult error(){
            return new JsonResult(HttpStatus.ERROR,"error");
        }
    
        public static JsonResult error(int code, String msg){
            return new JsonResult(code,msg);
        }
    
        public static JsonResultBuilder builder(){
            return new JsonResultBuilder();
        }
    
        public static class JsonResultBuilder{
    
            private JSONObject buildData = new JSONObject();
    
            public JsonResultBuilder put(String key,Object value){
                buildData.put(key,value);
                return this;
            }
    
            public JsonResult get(){
                return JsonResult.success(buildData);
            }
        }
    }
    
    

    7.最终接口展示

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
        最终访问我们的本地地址+“#doc.html”,在Swagger中可以看到我们的接口已经写好了,展示的效果也是按照年龄降序排列的集合。这样的接口,前端的同事拿来就可以用,响应参数、参数数据类型都是非常的清晰。此处我是在已有的项目上做的演示实例,所以Swagger都是已经配置好的,所以此处有是瑕疵的。如果没有配置Swagger的小伙伴应该还需要先百度一下如何配置好Swagger,下个阶段我有时间也整理一下如何配置Swagger。讲实话没有Swagger看不到最终的前端调用效果确实挺难受的,所以这个环节是在我们在写接口之前就要配置好。(这里我写得有点累了,换下次再写吧,O(∩_∩)O哈哈~)

        综上所述,这便是基于SpringBoot后端接口的相关流程,有相关的思想也有编写的步骤。有些思想或者步骤我可能写得还不够细,还比较粗糙,但是大致地想法还是表达到位了。我的初衷就是想对于刚入门的小伙伴还是有一些小小的帮助的就好,自己也算是对我的第一个实战项目做大致的总结。最终项目告一段落,界面接口完成了,Bug也改了不少,新增需求也做了一些改善,后续的代码也做了一些优化。成长了不少,也收获了不少,当然也加了很多很多的班…

        不管如何,大家还是要参与到实际的项目中,才会逐步收获到一些从来没有遇到过的“惊喜”!!!

         编写不易,路过的朋友,如果博客内容对你有所帮助的话,希望能一键三连一下呀,谢谢支持哦!!!

    在这里插入图片描述

    展开全文
  • 对接后端接口时success: (res)=>中的res是什么意思? res这个参数可以理解为一个Object。 res.data指服务器返回的内容。res参数还有其他的属性,比如statusCode(指开发者服务器返回的 HTTP 状态码),还有header...

    对接后端接口时success: (res)=>中的res是什么意思?

    res这个参数可以理解为一个Object。 res.data指服务器返回的内容。res参数还有其他的属性,比如statusCode(指开发者服务器返回的 HTTP 状态码),还有header(指开发者服务器返回的 HTTP Response Header)。

    展开全文
  • RT打开你的vue项目工程目录,如下:项目目录选择config下的index.js文件,在proxy...图中的'/admin'意思是你配置的接口名称,比如请求login/login接口,,则请求的url为'/admin/login/login'如图接口中的url地址其中change...
  • 例1 update更新接口:参数有两个 ...前端调用updateUser请求后端接口的方法: 更新功能的提交按钮: 请求后端接口: 例2:getList请求接口 前端调用getList请求后端接口的方法: 请求后端接口
  • 本文主要分为两个部分: ...1、后端接口都测试什么?怎么测的? 2、 后端接口测试一遍 ,前端也测试一遍,是不是重复测试了? 于是,为了向开发解释上述问题,普及基本的测试常识,特意梳理了接口测试的相关内容以.
  • 学php第一步干什么?学php第一步就是要搭建PHP环境。对于初学者建议使用集成的服务器组件,它已经包含了 PHP、Apache、Mysql 等服务,免去了开发人员将时间花费在繁琐的配置环境过程。WampServerWindow 系统可以使用 ...
  • 本文从本人博客搬运,原文格式更加美观,可以移步原文阅读:SpringBoot后端接口请求参数映射方式详解 在SpringBoot项目中,Controller层的接口如何接收前端HTTP请求中的参数我们日常开发中经常要用到,这里针对各种...
  • 非静态文件一样走的后端路由自己做好校验就是。 3. 通过 HTTP 协议来约束没有可能,任何基于 HTTP 或 HTTPS 的网站,管你服务器还是浏览器渲染如何认证爬虫想怎么爬怎么爬,理论上没有可能从根本上区分是爬虫还是...
  • 本篇文章将对Java异步进行介绍,希望对大家有所帮助。 异步:异步与同步相对,当一个异步过程调用发出后,调用者在没有得到结果之前,就可以继续执行后续操作。... 定义回调接口异步代码执行完后,结果往往需要...
  • nginx部署vue项目并访问后端接口遇到Uncaught (in promise) Error: Request failed with status code 503 今天在一台阿里云上部署了springboot后端,并测试通过。 但是在部署vue项目到nginx后,发现无法成功访问后端...
  • 后端开发之如何写接口设计文档

    千次阅读 2021-04-15 15:07:55
    当我们说到接口时,首先要分前端和后端,前端有Android、IOS、Js,后端定义返回值、参数、请求方式、协议等。系统A调用系统B,系统B调用系统C,像是把多个系统连接起来的一座桥梁,各自遵守相同的约定,但他本身是一...
  • 什么是web前端开发和后端开发?

    千次阅读 2021-03-02 11:56:51
    1、什么是前端开发? 前端开发主要是关于网站和应用程序,用户可以从应用程序屏幕或浏览器上看到东西。简而言之,你在应用屏幕和浏览器上看到的都是前端。 网站和移动App的前端 让我们以你正在浏览的网页为例。页面...
  • 1、前端开发: 网站的“前端”是与用户直接交互的部分,包括你在浏览网页...2、后端开发: 为了让服务器、应用、数据库能够彼此交互,后端工程师需要具有用于应用构建的服务器端语言,数据相关工具,PHP框架,版本控制
  • 大致的情况是项目二组领导找他了,需要我们这边提供一个创建客户/供应商接口。然后巴拉巴拉说了一大堆的话,最后说jira已经帮你建好了编号是XXX-XXX,你打开看看里面有具体的内容。最后说了一句,今天下班完成哦,并...
  • 2.什么是后端 现在的前后端基本都是分离的,也意味着后端程序要做的,就是创建API,即应用程序接口英文Application Programming Interface 前端广义来讲不只包括网页应用的前端,一切由用户来操作的客户端都是前端,...
  • (一个项目的知识回顾) 一.请求 1.Get请求时:前端请求数据以key/value的形式,SpringMVC采用基本数据类型(String、Integer等等)接收数据。 1.1.用restful风格@PathVariable: 传输数据放在url路径中,...
  • 展开全部一、定义不同前端32313133353236313431303231363533e4b893e5b19e31333433656161是什么前端即网站前台部分,运行在PC端,移动端等浏览器上展现给用户浏览的网页。随着互联网技术的发展,HTML5,CSS3,前端...
  • 我前端先请求了后台的登录接口,提示登录成功了。然后我再请求数据,这时候接口返回来的是未登陆,查了下发现session id变了,所以后台判断还是未登陆。js代码:$(function(){$.get(...
  • JFrame是指一个计算机语言-java的GUI程序的基本思路是以JFrame为基础,它是屏幕上window的对象,能够最大化、最小化、关闭。... //创建窗口 } } 以上就是java中JFrame是什么的详细内容,更多请关注ki4网其它相关文章!
  • 一、程序内部接口 二、应用程序接口(API:Application Program Interface) 三、常见的API形式有哪些? 1、HTTP/HTTPS类型接口 2、RPC接口 3、Web Service 接口 四、API接口使用案例 1.纯请求API接口 2.开发中的API...
  • php支持哪些数据库(拥有哪些数据库接口)(推荐学习:PHP视频教程)Adabas D ,InterBase ,PostgreSQL ,dBase ,FrontBase ,SQLite ,Empress ,mSQL ,Solid ,FilePro(只读),Direct MS-SQL ,Sybase ,Hyperwave ...
  • 验证无效跨域访问得不到响应数据是被浏览器拦截,而非服务端未执行 实验环境搭建 实验后端接口如图,分为简单请求和预检请求,各组API分为三个,分别为 无任何处理、只添加Access-Control-Allow-Origin 、都添加: ...
  • thinkphp6搭建后端api接口

    千次阅读 2021-06-07 21:04:25
    所以如果有什么需要访问的图片、视频等资源,应该放在此目录下 4、解决跨域问题 在应用开发中,前后端都是分开独立开发的,而前后端通常都会自己搭建一个web服务,运行在不同的端口上,在前端访问后端接口时,会报...
  • http500什么意思

    千次阅读 2020-12-21 15:08:00
    展开全部http500指的是内部服务器错误,说明IIS服务器无法解析e68a84e8a2ad3231313335323631343130323136353331333433623161ASP代码。1、出现http500代码的情况:当访问网站时,浏览器会将请求发送到托管该网站的...
  • 后端埋点

    千次阅读 2021-01-22 17:00:38
    后端埋点 背景:客户端埋点 or 服务端埋点 埋点方式选择 埋点方式分为客户端埋点和服务端埋点两种。如何选取最合适的埋点方式,确保数据完整、准确、高效上报? 一、客户端埋点与服务端埋点结合 发挥各自优势 客户端...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 38,691
精华内容 15,476
关键字:

后端接口是什么意思