Iridescent-zhang

醉后不知天在水,满船清梦压星河

InternetBasicsNote

无志之人常立志,有志之人立常志

Java 后端

知识框架

Web开发:主要是Web开发相关的内容,包括HTML/CSS/js(前端页面)、 Servlet/JSP(J2EE)以及MySQL(数据库)相关的知识。它们的学习顺序应该是从前到后,因此最先学习的应该是HTML/CSS/JS(前端页面)。
  J2EE:你需要学习的是Servlet/JSP(J2EE)部分,这部分是Java后端开发必须非常精通的部分,因此这部分是这三部分中最需要花精力的。关于Servlet/Jsp部分视频的选择,业界比较认可马士兵的视频。
  最后一步,你需要学会使用数据库:mysql是个不错的入门选择,而且Java领域里主流的关系型数据库就是mysql。这部分一般在你学习Servlet/Jsp的时候,就会接触到的,其中的JDBC部分就是数据库相关的部分。
  你不仅要学会使用JDBC操作数据库,还要学会使用数据库客户端工具,比如navicat,sqlyog,二选一即可。
  开发框架:目前比较主流的是SSM框架,即spring、springmvc、mybatis。你需要学会这三个框架的搭建,并用它们做出一个简单的增删改查的Web项目。
  你可以不理解那些配置都是什么含义,以及为什么要这么做,这些留着后面你去了解。但你一定要可以快速的利用它们三个搭建出一个Web框架,你可以记录下你第一次搭建的过程,相信我,你一定会用到的。
  还要提一句的是,你在搭建SSM的过程中,可能会经常接触到一个叫maven的工具。
  这个工具也是你以后工作当中几乎是必须要使用的工具,所以你在搭建SSM的过程中,也可以顺便了解一下maven的知识。在你目前这个阶段,你只需要在网络上了解一下maven基本的使用方法即可,一些高端的用法随着你工作经验的增加,会逐渐接触到的。
  你需要去看一些JDK中的类的源码,也包括你所使用的框架的源码。这些源码能看懂的前提是,你必须对设计模式非常了解。否则的话,你看源码的过程中,永远会有这样那样的疑问,这段代码为什么要这么写?
  为什么要定义这个接口,它看起来好像很多余?由此也可以看出,这些学习的过程是环环相扣的,如果你任何一个阶段拉下来了,那么你就真的跟不上了,或者说是一步慢步步慢。而且我很负责的告诉你,我在这个阶段的时候,所学习的东西远多于这里所罗列出来的。
  总而言之,这个阶段,你需要做的是深入了解Java底层和Java类库(比如并发那本书就是Java并发包Java.concurrent的内容),也就是JVM和JDK的相关内容。而且还要更深入的去了解你所使用的框架,方式比较推荐看源码或者看官方文档。
  再总而言之,就是 学习、学习、再学习~活到老,学到老

1、面向对象的知识
java是一种面向对象的开发语言,因此熟悉面向对象对学习java很有必要,你需要了解:什么是对象,什么是类,什么是封装,什么是多态,什么是继承,什么是抽象类,什么是接口。理解以上概念后,还需要知道这些概念是如何体现的,如类和对象有什么区别?类是如何封装的?

2、java语言
Java 是一门纯粹的面向对象的编程语言,所以除了基础语法之外,必须得弄懂它的 oop 特性:封装、继承、多态。此外还有泛型、反射 的特性,很多框架的技术都依赖它,比如 Spring 核心的 Ioc 和 AOP,都用到了反射,而且 Java 自身的动态代理也是利用反射实现的。 此外还有 Java 一些标准库也是非常常见,比如集合、I/O、并发,几乎在 Web 开发中无处不在,也是面试经常会被问到的,所以在学 Java 后端之前,不妨先打好这些基础,另外还有 Java8 的一些新特性,也要重点关注,比如 Lambda 表达式、集合的 Stream 流操作、全新的 Date API 等等,关于新特性。

3、JSP和HTML
在我国的绝大多数公司,做java程序开发都少不了和JSP以及HTML打交道。所以,要熟悉java程序开发就要熟悉JSP和HTML,较好能知道JSP的几个内置对象,如Session,Request,Reponse,,以及常用的JSP标签,如include,userBean等。尽管一些工具会帮你生成HTML代码,但还是要熟悉比如title,等。如果再熟悉一下JS和CSS就更好了,那会使做出的页面更友好。

4、数据库
后端开发免不了与数据库打交道,所以掌握Java的数据库操作是一个基本要求。Java操作数据库涉及到的内容有JDBC、JNDI、RMI、DAO等内容,其中使用RMI+JDBC是构建java数据库开发的一个常见的解决方案,而JNDI则是对各种资源的定义。

5、Web Server
熟悉一种Web Server,比如:TOMCAT,RESIN等。您要熟悉如何发布你的应用,如何利用Web Server的数据库资源等。

6、Servlet
Servlet技术是Java后端的重要技术之一,作为Java Web开发的核心组件,Servlet承担了Web MVC结构中的核心作用(功能导航)。传统的Model2结构(Servlet+JavaBean+JSP)虽然在目前已经很少使用了,但是Web开发的基本结构依然没有改变。Servlet技术的应用涉及到Web容器、会话(HttpSession)、安全、同步、Web应用部署等相关内容。

7、Web主流框架
熟悉一种框架其实是java程序开发的一种可选知识,但目前开发B/S结构的应用的开发小组,都差不多会采用一种框架来构建自己的应用系统。框架都会有许多可重用的代码,良好的层次关系和业务控制逻辑,基于框架的开发使你可以省出很多的开发成本。目前比较流行的框架有Struts和Spring等。

Spring+SpringMVC+MyBatis是目前一个比较常见的后端开发方案,Spring的原理就是构建了一个“业务组件容器”,SpringMVC则是Web MVC的一个具体实现框架,而MyBatis则是一个基于DAO的实现框架。从性能的角度来说,Spring是EJB的轻量级解决方案,得到了广大Java程序员的欢迎。

网络协议是为计算机网络中进行数据交换而建立的规则、标准或约定的集合。没有网络协议就根本不可能上网,任何和互联网有关的操作都离不开网络协议。我们开发的软件网络是不可缺少的,因此计算机协议的相关知识也是不可或缺的。

尤其是要学习http协议,浏览器与服务器通过http协议交互,其实就是相互之间传递一串特定格式的字符串。get参数,post参数,url,和cookie等信息其实都包含在这字符串里面。所以说http协议是一个重要的存在,也是我们学习后端开发一个必不可少的要点

区别

J2EE,JSP,Java的区别

JSP就是用来做动态页面的,可以归属到J2EE系列中。Java 这个词的概括可能更广一些 ,因为Java包含3个领域,分别是: J2EE: 企业级开发,J2ME :嵌入式开发,J2SE :图形界面开发。

java包含三大分支:

J2SE –java standard edition–标准版本,这个是下面两个的基础!一般是位于客户端的应用; J2ME–java Micro edition —般位于嵌入式应用,例如手机游戏J2EE –java Enterprise Editon —般为服务器端程序的应用。J2EE的全称是Java 2 Platform Enterprise Edition。

java通俗的说是一个统称了, 他包括了javase(j2se) javaee(j2ee) javame(j2me)

javase是java的基础,涵盖基本语法结构,IO,集合等,反正看成是java的基石就对了

javaee是企业开发的规范,里面是一大堆的接口,但是自 己也不实现这一套规范, 他需要各大企业或者组织去实现,比如tomcatjboss等,然后开发者在javaee的规范下开发web程序,部署到web容器(tomcat等)中,就可以运行了

javaee里面最基本的就是Servlet,他接收请求,返回响应,对网页开发的应用来说,需要在Servlet里面拼接Html代码,然后使用PrintWriter进行输出

Servlet的开发难度太高也不友好后来就出现了MVC概念,让Servlet作为控制层复杂业务中转,JSP就去做前台的显示层和ASP异曲同工,因为JSP开发也可以像HTML开发-样, 所见即所得可视化开发,并组可以把html直接转换成jsp,大大提高开发效率

最后说下关系, j2ee需要用到javase才能运行, Servlet是j2ee里面的一个组成部分,负责处理请求转发,JSP其实就是Servlet,只是比Servlet的开发难度低一点,对开发者更加友好

后端和前端有什么区别

后端和前端的区别是:1、定义不同;2、展示方式不同;3、所需的技能不同;4、思考角度不同;5、入门难度不同。其中,前端统称为客户端开发,在应用程序或网站的屏幕上看到的所有内容都属于前端的工作范畴。后端称为“服务器端开发”,属于在系统“后面”所发生的事情。

1、定义不同
前端统称为”客户端开发“,在应用程序或网站的屏幕上看到的所有内容,都是由浏览器解析、处理、渲染相关HTML、CSS、JAVASCRIPT文件后呈现出来,都属于前端的工作范畴。

后端称为“服务器端开发”,属于在系统“后面”所发生的事情。在后端服务器和浏览器或应用程序之间存储网站、应用数据和中间媒介的服务器都在后端的工作范畴内。在应用程序或网站屏幕上看不到的东西基本上都是后端。

2、展示方式不同
前端的工作是制作网页,后端是结合数据库实现一些代码的功能逻辑。也就是说前端开发人员在应用程序中创建一个界面,上面有一个按钮,通过按下按钮可以获取客户的数据。

后端开发人员负责写出按钮工作的代码,通过指出从数据库中提取哪些数据并将其传回到前端(且最终显示在那个位置)。

3、所需的技能不同
前端开发需要具备的技能:对美学、艺术和设计有较好的理解、了解各种 CMS,如 WordPress,Joomla 或 Drupal、直观的用户需求、PHP 和 OOP 知识(面向对象编程)、专业的质量保证、能够使用 PhotoShop,Sketch 或 Figma 等设计工具、网络托管基础知识等。

后端开发需要具备的技能:全面深入了解第三方附加组件、关于如何调试代码的批判性理解、将客户的业务需求转换为功能代码、了解 Web 服务器配置、兼容外部系统(支付处理,社交媒体网站)、批判性思维技巧、设计用户交互系统等。

4、思考角度不同
前端主要是考虑怎么能让用户觉得用起来更舒服,考虑页面布局、交互效果、页面加载速度等,主要是偏向用户看得见的部分。

后端更多是考虑业务逻辑、数据库表结构、服务器配置、负载均衡、数据的存储、跨平台API设计等等。更多的是考虑用户看不到的部分,保证业务逻辑处理数据的谨慎,保证数据吞吐的性能。

5、入门难度不同
前端开发入门简单初期容易后期难,能看到自己做出来的展示界面会很有成就感。

后端开发入门难,想要深入则更难,后端枯燥乏味没有太大成就感,平时工作就是看一堆业务逻辑代码。

SSM

Java的SSM是什么?

Java的SSM是指使用Spring、SpringMVC和MyBatis这三个开源框架进行软件开发的一种架构模式。这三个框架分别代表了Java开发生态中的不同领域,通过它们的组合,可以实现更加高效、灵活和可维护的软件开发。

首先,让我们来看看每个框架的作用和特点。

Spring是一个轻量级的Java开发框架,提供了诸多功能和特性,如依赖注入、面向切面编程、事务管理等。它的核心思想是通过控制反转(IoC,也叫依赖注入,Dependency Injection,简称DI)和面向切面编程(AOP)来解耦和管理应用程序的各个组成部分。使用Spring,可以实现代码的模块化和解耦,提高代码的可测试性和可维护性。

SpringMVC是基于Spring的Web开发框架,它使用了MVC(Model-View-Controller)的架构模式,将应用程序的逻辑、数据和视图进行分离。SpringMVC提供了强大的请求映射、数据绑定、异常处理等功能,同时也支持RESTful风格的Web服务开发。使用SpringMVC,可以快速构建响应式、可扩展和可定制化的Web应用程序。

MyBatis是一个优秀的持久层框架,它很好地解决了Java应用程序与关系型数据库之间的交互问题。MyBatis通过提供简洁的SQL映射和灵活的SQL查询方式,将数据库操作与Java代码进行了解耦。与传统的ORM框架相比,MyBatis更加灵活,可以更好地控制SQL查询和结果映射过程,提高数据访问层的性能和可维护性。

将Spring、SpringMVC和MyBatis结合起来使用,可以构建一个完整的Java应用程序。使用SSM架构,可以实现前后端的分离、模块化的开发方式,提高团队的协作效率和开发效率。

在使用SSM进行开发时,首先需要配置好每个框架的环境和依赖关系。可以使用Maven或Gradle等构建工具来管理这些依赖。接下来,需要编写相应的配置文件,如Spring的配置文件、MyBatis的映射文件等。通过这些配置文件,可以定义应用程序的各种组件和配置项。

在开发过程中,可以使用Spring的依赖注入和AOP来实现代码的解耦和模块化。可以使用SpringMVC的注解和请求映射机制来处理用户请求并返回相应的结果。可以使用MyBatis的注解和XML配置文件来定义数据库操作和查询。

使用SSM进行开发,有很多优势。首先,它能够提高开发效率和代码的可维护性。通过使用这些成熟的框架,可以避免重复造轮子,减少开发工作量。其次,它能够提供更好的性能和扩展性。这些框架都经过了大量的实践和优化,能够提供高性能、高并发的支持。最后,它能够提供更好的开发体验和生态支持。这些框架都有庞大而活跃的社区,提供了丰富的文档、示例和解决方案。

总之,Java的SSM是指使用Spring、SpringMVC和MyBatis这三个开源框架进行软件开发的架构模式。它的优势在于提高开发效率、提供更好的性能和扩展性,并且有庞大的社区支持。通过使用SSM,可以构建出高效、灵活和可维护的Java应用程序。

数据库基础和原理

SQL DB - 关系型数据库是如何工作的

这里关注的不是知识点,而是知识点之间的关联

B+树索引

是大多数 MySQL 存储引擎的默认索引类型。

当一次IO时,不光把当前磁盘地址的数据,而是把相邻的数据也都读取到内存缓冲区内,因为局部预读性原理告诉我们,当计算机访问一个地址的数据的时候,与其相邻的数据也会很快被访问到。每一次IO读取的数据我们称之为一页(page)。具体一页有多大数据跟操作系统有关,一般为4k或8k也就是我们读取一页内的数据时候,实际上才发生了一次IO,这个理论对于索引的数据结构设计非常有帮助。

我们现在总结一下,我们需要这种数据结构能够做些什么,其实很简单,那就是:每次查找数据时把磁盘IO次数控制在一个很小的数量级,最好是常数数量级。那么我们就想到如果一个高度可控的多路搜索树是否能满足需求呢?就这样,b+树应运而生。

B+树通常用于数据库和操作系统的文件系统中。B+树的特点是能够保持数据稳定有序, 其插入与修改拥有较稳定的对数时间复杂度。B+树元素自底向上插入。

B+树是应文件系统所需而出的一种B-树的变型树。一棵m阶(孩子节点数)的B+树和m阶的B-树的差异在于:

  1. 有n棵子树的节点中含有n个关键字(即每个关键字对应一棵子树);

  2. 所有叶子节点中包含了全部关键字的信息, 及指向含这些关键字记录的指针,且叶子节点本身依关键字的大小自小而大顺序链接

  1. 所有的非终端节点可以看成是索引部分,节点中仅含有其所有子树(根节点)中的最大(或最小关键字
  1. 除根节点外,其他所有节点中所含关键字的个数必须>=⌈m/2⌉(注意: B-树是除根以外的所有非终端节点(非叶子节点)至少有⌈m/2⌉棵子树)

下图是所示为一棵3阶的B+树,通常在B+树上有两个指针头, 一个指向根节点,另一个指向关键字最小的叶子节点。因此,可以对B+树进行两种查找运算: 一种是从最小关键字起顺序查找,另一种是从根节点开始,进行随机查找。

各种资料上B+树的定义各有不同,一种定义方式是关键字个数和孩子结点个数相同。这里我们采取维基百科上所定义的方式,即关键字个数比孩子结点个数小1,这种方式是和B树基本等价的。下图就是一颗阶数为4的B+树。

除此之外B+树还有以下的要求:

B+树包含2种类型的结点:内部结点(也称索引结点)和叶子结点。根结点本身即可以是内部结点,也可以是叶子结点。根结点的关键字个数最少可以只有1个。

B+树与B树最大的不同是内部结点不保存数据只用于索引所有数据(或者说记录)都保存在叶子结点中

m阶B+树表示了内部结点最多有m-1个关键字(或者说内部结点最多有m个子树),阶数m同时限制了叶子结点最多存储m-1个记录

内部结点中的key都按照从小到大的顺序排列,对于内部结点中的一个key,左树中的所有key都小于它,右子树中的key都大于等于它。叶子结点中的记录也按照key的大小排列。

每个叶子结点都存有相邻叶子结点的指针,叶子结点本身依关键字的大小自小而大顺序链接。

B+树的特性

所有关键字都出现在叶子节点的链表中(稠密索引),且链表中的关键字恰好是有序的;

非叶子节点相当于叶子节点的索引(稀疏索引),叶子节点相当于是存储(关键字)数据的数据层

B+树的查找

对B+树可以进行两种查找运算:

  1. 从最小关键字起顺序查找;

  2. 从根节点开始,进行随机查找

在查找时,若非终端节点上的关键字等于给定值,并不终止,而是继续向下直到叶子节点。因此,在B+树中,不管查找成功与否,每次查找都是走了一条从根到叶子节点的路径。其余同B-树的查找类似。

B+树的插入

插入比较复杂,只要是由底至上,归并的过程。

参考B+树详解

与B-树的比较

一棵m阶的B+树和m阶的B树的异同点在于:

所有的叶子节点中包含了全部关键字的信息,即指向含有这些关键字记录的指针,且叶子节点本身依关键字的大小自小而大的顺序链接。(而B-树的叶子节点并没有包括全部需要查找的信息)

所有的非终端节点可以看成是索引部分,节点中仅含有其子树根节点中最大(或最小)关键字。(而B-树的非终节点也包含需要查找的有效信息)

B+树用途

B+树主要适用于索引操作。为什么说B+树比B-树更适合实际应用于操作系统的文件索引和数据库索引?

B+树的磁盘读写代价更低: B+树的内部节点并没有指向关键字具体信息的指针。因此其内部节点相对B-树更小。如果把所有同一内部节点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多。一次性读入内存中的需要查找的关键字也就越多。相对来说IO读写次数也就降低了。举个例子:假设磁盘中的一个盘块容纳16bytes,而一个关键字2bytes, 一个关键字具体信息指针2bytes。一棵9阶B-树(一个节点最多8个关键字)的内部节点需要2个盘块。而B+树内部节点只需要1个盘块。当需要把内部节点读入内存的时候,B-树就比B+树多一次盘块查找时间(在磁盘中就是盘片旋转时间)

B+树的查询效率更加稳定: 由于非终节点并不是最终指向文件内容的节点,而只是叶子节点中关键字的索引。所以任何关键字的查找必须走一条从根节点到叶子节点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当。

这段话很重要

浅蓝色的块我们称之为一个磁盘块,可以看到每个磁盘块包含几个数据项(深蓝色所示)和指针(黄色所示),如磁盘块1包含数据项17和35,包含指针P1、P2、P3,P1表示小于17的磁盘块,P2表示在17和35之间的磁盘块,P3表示大于35的磁盘块真实的数据存在于叶子节点即3、5、9、10、13、15、28、29、36、60、75、79、90、99。非叶子节点不存储真实的数据,只存储指引搜索方向的数据项,如17、35并不真实存在于数据表中。

b+树的查找过程

如图所示,如果要查找数据项29,那么首先会把磁盘块1由磁盘加载到内存,此时发生一次IO,在内存中用二分查找确定29在17和35之间,锁定磁盘块1的P2指针,内存时间因为非常短(相比磁盘的IO)可以忽略不计,通过磁盘块1的P2指针的磁盘地址把磁盘块3由磁盘加载到内存,发生第二次IO,29在26和30之间,锁定磁盘块3的P2指针,通过指针加载磁盘块8到内存,发生第三次IO,同时内存中做二分查找找到29,结束查询,总计三次IO。真实的情况是,3层的b+树可以表示上百万的数据,如果上百万的数据查找只需要三次IO,性能提高将是巨大的,如果没有索引,每个数据项都要发生一次IO,那么总共需要百万次的IO,显然成本非常非常高。

b+树性质

  1. 通过上面的分析,我们知道IO次数取决于b+数的高度h,假设当前数据表的数据为N,每个磁盘块的数据项的数量是m,则有h=㏒(m+1)N,当数据量N一定的情况下,m越大,h越小;而m = 磁盘块的大小 / 数据项的大小,磁盘块的大小也就是一个数据页的大小,是固定的,如果数据项占的空间越小,数据项的数量越多,树的高度越低。这就是为什么每个数据项,即索引字段要尽量的小,比如int占4字节,要比bigint8字节少一半。这也是为什么b+树要求把真实的数据放到叶子节点而不是内层节点,一旦放到内层节点,磁盘块的数据项会大幅度下降,导致树增高。当数据项等于1时将会退化成线性表。
  2. 当b+树的数据项是复合的数据结构,比如(name,age,sex)的时候,b+数是按照从左到右的顺序来建立搜索树的,比如当(张三,20,F)这样的数据来检索的时候,b+树会优先比较name来确定下一步的所搜方向,如果name相同再依次比较age和sex,最后得到检索的数据;但当(20,F)这样的没有name的数据来的时候,b+树就不知道下一步该查哪个节点,因为建立搜索树的时候name就是第一个比较因子,必须要先根据name来搜索才能知道下一步去哪里查询。比如当(张三,F)这样的数据来检索时,b+树可以用name来指定搜索方向,但下一个字段age的缺失,所以只能把名字等于张三的数据都找到,然后再匹配性别是F的数据了, 这个是非常重要的性质,即索引的最左匹配特性

慢查询优化
关于MySQL索引原理是比较枯燥的东西,大家只需要有一个感性的认识,并不需要理解得非常透彻和深入。我们回头来看看一开始我们说的慢查询。

总结:

MySQL采用B+树原因
MySQL等数据库普遍都采用B+树,而不是B-树。主要有如下原因:

  1. B-树和B+树最重要的一个区别就是B+树只有叶子节点存放数据,其余节点用来索引。而B-树是每个索引节点都会有data域。这就决定了B+树更适合用来存储外部数据。也就是所谓的磁盘数据。

  2. 从MySQL InnoDB的角度来看,B+树是用来充当索引的,一般来说索引非常大,尤其是关系型数据库这种数据量大的索引能达到亿级别,所以为了减少内存的占用,索引也会存储在磁盘上。

  3. B+树的磁盘读写代价更低。B+树的内部节点并没有指向关键字具体信息的指针,因此其内部节点相对B-树更小。如果把所有同一内部节点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多。一次性读入内存中的需要查找的关键字也就越多。相对来说IO读写次数也就降低了。

  4. B+树的查询效率更加稳定。由于非终节点并不是最终指向文件内容的节点,而只是叶子节点中关键字的索引。所以任何关键字的查找必须走一条从根节点到叶子节点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当。

  5. B+树所有的Data域在叶子节点,一般来说都会进行一个优化,就是将所有的叶子节点用指针串起来。这样遍历叶子节点就能获得全部数据,这样就能进行区间访问了。

聚簇索引与非聚簇索引(应该就是聚集索引和非聚集索引)

在《数据库原理》里面,对聚簇索引的解释是: 聚簇索引的顺序就是数据的物理存储顺序; 而对非聚簇索引的解释是: 索引顺序与数据物理排列无关。正是因为如此,所以一个表最多只能有一个聚簇索引。直观上来说,聚簇索引的叶子节点就是数据节点; 而非聚簇索引的叶子节点仍然是索引节点,只不过是指向对应数据块的指针。不懂

Java 连接 Workbench 创建的数据库

首先需要在 MySQL Workbench 中创建一个 连接Connection,接着在这个连接里创建一个 数据库database,接着建表,复制下该 Connection 的JDBC链接

Java的数据库编程:JDBC
JDBC,即Java Database Connectivity,java数据库连接。是一种用于执行SQL语句的Java API,它是Java中的数据库连接规范。这个API由 java.sql.,javax.sql. 包中的一些类和接口组成,它为Java开发人员操作数据库提供了一个标准的API,可以为多种关系数据库提供统一访问。

JDBC优势
Java语言访问数据库操作完全面向抽象接口编程
开发数据库应用不用限定在特定数据库厂商的API
程序的可移植性大大增强

创建 Java 文件,在源文件夹下创建 lib 文件夹并在这里导入 依赖mysql-connector-j-8.3.0.jar,新建lib文件夹而不是将依赖放在 libraries中的原因是防止将文件发给别人后里面的依赖不见了。

之后配置依赖,两种方式:
第一种方式:直接将lib文件夹设置为library

第二种方式:
项目左边栏右键空白处之后选择: Open Module Settings,点击 + 号选择Jars or Directories选项并导入之前创建的 lib 文件夹即可完成配置。

数据库连接代码样例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import java.sql.*;

public class Conn { // 创建类Conn
Connection con; // 声明Connection对象
public static String user;
public static String password;
public Connection getConnection() { // 建立返回值为Connection的方法
try { // 加载数据库驱动类
Class.forName("com.mysql.cj.jdbc.Driver");
System.out.println("数据库驱动加载成功");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
user = "root";//数据库登录名
password = "123456";//密码
try { // 通过访问数据库的URL获取数据库连接对象
con = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/?user=root", user, password);
System.out.println("数据库连接成功");
} catch (SQLException e) {
e.printStackTrace();
}
return con; // 按方法要求返回一个Connection对象
}
public static void main(String[] args) throws SQLException { // 主方法,测试连接
Conn c = new Conn(); // 创建本类对象
Connection connection = c.getConnection(); // 调用连接数据库的方法
// 4.执行SQL对象Statement,执行SQL的对象
Statement statement = connection.createStatement();
// 5.执行SQL的对象去执行SQL,返回结果集
String sql = "SELECT * FROM databasejdbc.student;";
ResultSet resultSet = statement.executeQuery(sql);
while(resultSet.next()){
System.out.println("Id="+resultSet.getString("id"));
System.out.println("name="+resultSet.getString("name"));
System.out.println("score="+resultSet.getString("score"));
}
// 6.释放连接
resultSet.close();
statement.close();
connection.close();
}
}

Mysql

MySQL 5.7史上最详细下载安装配置教程

MySQL之source命令

用 zip 安装的 mysql 5.7 版本,root是用户名密码为 123465。
mysql -u root -p

真尼玛麻烦啊。

GRANT USAGE ON . TO ‘user01‘@’localhost’ IDENTIFIED BY ‘123456’ WITH GRANT OPTION;

用户:user01,密码:123456

CREATE USER ‘laowang‘@’localhost’ IDENTIFIED BY ‘123456’;
授予账户权限的方法如下:

GRANT SELECT,INSERT,UPDATE,DELETE,CREATE,DROP,ALTER ON . TO ‘laowang‘@’localhost’;
授予所有权限:

GRANT ALL PRIVILEGES ON . TO ‘laowang‘@’localhost’;
查看用户权限:

show grants for ‘laowang‘@’localhost’;

SQL

SQL 是用于访问和处理数据库的标准的计算机语言。

SQL (Structured Query Language:结构化查询语言) 是用于管理关系数据库管理系统(RDBMS)。 SQL 的范围包括数据插入、查询、更新和删除,数据库模式创建和修改,以及数据访问控制。

RDBMS 指关系型数据库管理系统,全称 Relational Database Management System。

RDBMS 是 SQL 的基础,同样也是所有现代数据库系统的基础,比如 MS SQL Server、IBM DB2、Oracle、MySQL 以及 Microsoft Access。

RDBMS 中的数据存储在被称为的数据库对象中。

表是相关的数据项的集合,它由列和行组成。

基本概念

列(column) - 表中的一个字段。所有表都是由一个或多个列组成的。
(row) - 表中的一个记录(行是一个完整的记录)。
主键(primary key) - 一列(或一组列),其值能够唯一标识表中每一行。

处理 SQL 语句时,所有空格都被忽略。SQL 语句可以写成一行,也可以分写为多行。

SQL 对大小写不敏感:SELECT 与 select 是相同的,但是数据库表名、列名和值是否区分,依赖于具体的 DBMS 以及配置。

某些数据库系统要求在每条 SQL 语句的末端使用分号

分号是在数据库系统中分隔每条 SQL 语句的标准方法,这样就可以在对服务器的相同请求中执行一条以上的 SQL 语句。

1
2
3
4
5
6
7
8
9
10
11
-- 一行 SQL 语句
UPDATE user SET username='robot', password='robot' WHERE username = 'root';

-- 多行 SQL 语句
UPDATE user
SET username='robot', password='robot'
WHERE username = 'root';

## 注释1
-- 注释2
/* 注释3 */

一些最重要的 SQL 命令

1
2
3
4
5
6
7
8
9
10
11
SELECT - 从数据库中提取数据
UPDATE - 更新数据库中的数据
DELETE - 从数据库中删除数据
INSERT INTO - 向数据库中插入新数据
CREATE DATABASE - 创建新数据库
ALTER DATABASE - 修改数据库
CREATE TABLE - 创建新表
ALTER TABLE - 变更(改变)数据库表
DROP TABLE - 删除表
CREATE INDEX - 创建索引(搜索键)
DROP INDEX - 删除索引

SQL 分类

数据定义语言(Data Definition Language,DDL)是 SQL 语言集中负责数据结构定义与数据库对象定义的语言。
DDL 的核心指令是 CREATE、ALTER、DROP

数据操纵语言(Data Manipulation Language, DML)是用于数据库操作,对数据库中的对象和数据运行访问工作的编程语句。
DML 的主要功能是 访问数据,因此其语法都是以读写数据库为主
DML 的核心指令是 INSERT、UPDATE、DELETE、SELECT。这四个指令合称 CRUD(Create, Read, Update, Delete),即增删改查

事务控制语言(Transaction Control Language, TCL)用于管理数据库中的事务。这些用于管理由 DML 语句所做的更改。它还允许将语句分组为逻辑事务。
TCL 的核心指令是 COMMIT、ROLLBACK

数据控制语言(Data Control Language, DCL)是一种可对数据访问权进行控制的指令,它可以控制特定用户账户对数据表、查看表、预存程序、用户自定义函数等数据库对象控制权
DCL 的核心指令是 GRANT、REVOKE
DCL 以控制用户的访问权限为主,因此其指令作法并不复杂,可利用 DCL 控制的权限有:CONNECT、SELECT、INSERT、UPDATE、DELETE、EXECUTE、USAGE、REFERENCES
根据不同的 DBMS 以及不同的安全性实体,其支持的权限控制也有所不同。

常用语句

上下分别为 test_table, new_table。

注意语句结束时需要分号,同时注意什么时候是语句结束。

SQL SELECT 语句

SELECT 语句用于从数据库中选取数据,获取结果集。

1
2
3
4
SELECT column1, column2, ...
FROM table_name;

SELECT * FROM table_name; // 选择全部字段

column1, column2, …:要选择的字段(列)名称,可以为多个字段。如果不指定字段名称,则会选择所有字段。
table_name:要查询的表名称。

SQL SELECT DISTINCT 语句
在表中,一个列可能会包含多个重复值,此语句用于去重。
当选择多个列时,只有所有列的值都相同才算相同。

SQL WHERE 子句

WHERE 子句用于提取那些满足指定条件的记录

注意: SELECT 的关键是影响 结果集 中有哪些列,比如显示出来哪些列,并不是说 WHERE 查询的列一定得在 SELECT 选择的列里面。

1
2
3
SELECT column1, column2, ...
FROM table_name
WHERE condition;

如:SELECT * FROM Websites WHERE name='zlc';使用SELECT * FROM Websites WHERE name='zlc';同样会只选择 name=’zlc’ 的行,但选择的结果只显示 id 。

SQL 使用单引号来环绕文本值(大部分数据库系统也接受双引号)。
在上个实例中 ‘zlc’ 文本字段使用了单引号。
如果是数值字段请不要使用引号。(MySQL Workbench 好像对数值用单引号也可以)

WHERE 子句中的运算符:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
=	等于
<> 不等于。注释:在 SQL 的一些版本中,该操作符可被写成 !=
> 大于
< 小于
>= 大于等于
<= 小于等于
and or not 逻辑运算,再加入括号组成复合表达式,如:WHERE age > 15 AND (name='zlc' OR score>90);
BETWEEN and 在某个范围内 between lowLimit and highLimit 包含上下界
IN 指定针对某个列的多个可能值 in(a,b,c..)罗列值
LIKE 搜索某种模式(模糊查询即为不全信息查询) 只有字段是文本值时才使用 LIKE,通配符 % 表示任何字符出现任意次数,_ 下划线表示任何字符出现一次,如:
M% : 正则表达式,表示的意思为模糊查询信息为 M 开头的。
%M% : 表示查询包含M的所有内容。
%M_ : 表示查询以M在倒数第二位的所有内容。
不要滥用通配符,通配符位于开头处匹配会非常慢
例如 WHERE name LIKE '%s%'; 能匹配 'sjl' 'sxr'
IS NULL 以及 IS NOT NULL 用来查找列中带有 NULL 值和不带有 Null 值的记录,
请始终使用 IS NULL 来查找 NULL 值。如 where id is null。
另外:工作中一般建表的时候一般会禁止使用 NULL 的,不利于代码维护。

SQL ORDER BY 关键字

ORDER BY 关键字用于对结果集按照一个列或者多个列进行排序,默认按照升序对记录进行排序。如果需要按照降序对记录进行排序,您可以使用 DESC 关键字。

1
2
3
4
5
6
7
SELECT column1, column2, ...
FROM table_name
ORDER BY column1, column2, ... ASC|DESC;

column1, column2, ...:要排序的字段名称,可以为多个字段。
ASC:表示按升序排序。
DESC:表示按降序排序。

选择多列的时候先按前列排序,前列相同的情况下再按后面的列排序。并且desc 或者 asc 只对它紧跟着的第一个列名有效,其他不受影响,仍然是默认的升序,如:order by A desc,B 这个时候 A 降序,B 升序排列。

LIMIT 和 OFFSET 用法

MySQL 里分页一般用 LIMIT 来实现:

限制返回的行数。可以有两个参数,第一个参数为起始行,从 0 开始;第二个参数为返回的总行数。

当 LIMIT 后面跟两个参数的时候,第一个数表示要跳过的数量,后一位表示要取的数量,例如:select* from article LIMIT 1,3 就是跳过 1 条数据,从第 2 条数据开始取,取 3 条数据,也就是取 2、3、4 三条数据。

当 LIMIT 后面跟一个参数的时候,该参数表示要取的数据的数量。例如 select* from article LIMIT 3 表示直接取前三条数据,类似 sqlserver 里的 top 语法。

当 LIMIT 和 OFFSET 组合使用的时候,LIMIT 后面只能有一个参数,表示要取的的数量,OFFSET表示要跳过的数量 。例如 select * from article LIMIT 3 OFFSET 1 表示跳过 1 条数据,从第 2 条数据开始取,取3条数据,也就是取 2、3、4 三条数据。

SQL INSERT INTO 语句

INSERT INTO 语句用于向表中插入新记录(行)。

INSERT INTO 语句可以有两种编写形式。

  1. 第一种形式无需指定要插入数据的列名,只需提供被插入的值即可:
    INSERT INTO table_name VALUES (value1,value2,value3,...);
    如:insert into test_table values(5,'sxr',18,80);
  2. 第二种形式需要指定列名及被插入的值
    INSERT INTO table_name (column1,column2,column3,...) VALUES (value1,value2,value3,...);
    insert into test_table (id,name,age,score) values(6,'myz',45,74);

table_name:需要插入新记录的表名。
column1, column2, …:需要插入的字段名
value1, value2, …:需要插入的字段值

也可以在指定的列插入数据
下面的 SQL 语句将插入一个新行,但是只在 “name”、”age” 和 “score” 列插入数据(id 字段会自动更新,前提是在 table 属性里设置 id 列自增):
insert into test_table (name,age,score) values('yyy',95,12);
需要注意的是:没有列出列名的 insert into 写法得把新插入行的每个数据都列出来。

SQL UPDATE 语句

UPDATE 语句用于更新表中已存在的记录

1
2
3
UPDATE table_name
SET column1 = value1, column2 = value2, ...
WHERE condition;

table_name:要修改的表名称。
column1, column2, …:要修改的字段名称,可以为多个字段。
value1, value2, …:要修改的值,可以为多个值。
condition:修改条件,用于指定哪些数据要修改。
WHERE 子句规定哪条记录或者哪些记录需要更新。如果您省略了 WHERE 子句,所有的记录都将被更新
WHERE 应该是需要对 关键列(主键) 进行选择(MySQL Workbench)。

Update 警告!
在更新记录时要格外小心!如果我们省略了 WHERE 子句,会更新所有数据。执行没有 WHERE 子句的 UPDATE 要慎重,再慎重。

在 MySQL 中可以通过设置 sql_safe_updates 这个自带的参数来解决,当该参数开启的情况下,你必须在update 语句后携带 where 条件,否则就会报错。

set sql_safe_updates=1; 表示开启该参数
set sql_safe_updates=0; 关闭安全模式

如果设置了 sql_safe_updates=1,那么 update 语句必须满足如下条件之一才能执行成功:

  1. 使用 where 子句, 并且 where 子句中列必须为 prefix(主键 key column) 索引列。
  2. 使用 limit。
  3. 同时使用 where 子句和 limit (此时 where 子句中列可以不是索引列)。

delete 语句必须满足如下条件之一才能执行成功。

  1. 使用 where 子句, 并且 where 子句中列必须为 prefix 索引列。
  2. 同时使用 where 子句和 limit (此时 where 子句中列可以不是索引列)。

SQL DELETE FROM 语句

DELETE 语句用于删除表中的记录(行)。

1
2
DELETE FROM table_name
WHERE condition;

table_name:要删除的表名称。
condition:删除条件,用于指定哪些数据要删除
WHERE 子句规定哪条记录或者哪些记录需要删除。如果您省略了 WHERE 子句所有的记录都将被删除

可以在不删除表的情况下,删除表中所有的行。这意味着表结构、属性、索引将保持不变
DELETE FROM table_name;

SQL关于删除的三个语句:DROP、TRUNCATE、 DELETE 的区别。

DROP:
DROP test;
删除表test,并释放空间,将test删除的一干二净没有备份表之前要慎用

TRUNCATE:
TRUNCATE test;
删除表test里的内容,表的结构(定义)还在,并释放空间,没有备份表之前要慎用。

DELETE:
1、删除指定数据
删除表test中年龄等于30的且国家为US的数据
DELETE FROM test WHERE age=30 AND country='US';

2、删除整个表
仅删除表test内的所有内容,保留表的定义,不释放空间可以回滚恢复
DELETE FROM test 或者 DELETE * FROM test;

SQL INSERT INTO SELECT 语句

INSERT INTO SELECT 语句从一个表复制数据,然后把数据插入到一个已存在的表中,目标表中任何已存在的行都不会受影响。

1
2
INSERT INTO new_table
SELECT * FROM origin__table;

或者我们可以只复制指定的列(多列)插入到另一个已存在的表中:

1
2
3
4
INSERT INTO new_table
(column_name(s))
SELECT column_name(s)
FROM origin__table;

如:INSERT INTO new_table (id,name) SELECT id,name FROM test_table; 注意格式,括号也不能多写。
可以进一步用 where 选择。

子查询

子查询是嵌套在较大查询中的 SQL 查询。子查询也称为内部查询或内部选择,而包含子查询的语句也称为外部查询或外部选择。

子查询可以嵌套在 SELECT,INSERT,UPDATE 或 DELETE 语句内或另一个子查询中。

子查询通常会在另一个 SELECT 语句的 WHERE 子句中添加。

子查询必须被圆括号 () 括起来。

内部查询首先在其父查询之前执行,以便可以将内部查询的结果传递给外部查询。

连接 JOIN

上下分别为 test_table, new_table。

连接(JOIN)

SQL join 用于把来自两个或多个表的行结合起来,基于这些表之间的共同字段

LEFT JOIN、RIGHT JOIN、INNER JOIN、OUTER JOIN 相关的 7 种用法:

最常见的 JOIN 类型:SQL INNER JOIN(简单的 JOIN)。 SQL INNER JOIN 从多个表中返回满足 JOIN 条件的所有行

INNER JOIN语法

1
2
3
SELECT column1, column2, ...
FROM table1
JOIN table2 ON condition;

column1, column2, …:要选择的字段名称,可以为多个字段。如果不指定字段名称,则会选择所有字段。
table1:要连接的第一个表(左表)。
table2:要连接的第二个表(右表)。
condition:连接条件,用于指定连接方式。

1
2
3
select test_table.id,test_table.name,new_table.height,test_table.score 
from test_table inner JOIN new_table ON
test_table.id=new_table.id;

请注意,”test_table” 表中的 “id” 列指向 “new_table” 表中的字段 “id”。上面这两个表是通过 “id” 列联系起来的。并且注意,没有要求两个字段要完全一样,比如我可以定义 “new_table” 表中是”new_id”,只要 ON 后面设置连接那就可以。

效果是将行组合起来了,注意看列的顺序也是我们在上面代码中指定的:

不同的 SQL JOIN:
INNER JOIN:如果表中有至少一个匹配,则返回行
LEFT JOIN:即使右表中没有匹配,也从左表返回所有的行
RIGHT JOIN:即使左表中没有匹配,也从右表返回所有的行
FULL JOIN:只要其中一个表中存在匹配,则返回行(MySQL中不支持)

首先,连接的结果可以在逻辑上看作是由 SELECT 语句指定的列组成的新表。

左连接与右连接左右指的是以两张表中的哪一张为基准,它们都是外连接

外连接就好像是为非基准表添加了一行全为空值的万能行,用来与基准表中找不到匹配的行进行匹配。假设两个没有空值的表进行左连接,左表是基准表,左表的所有行都出现在结果中,右表则可能因为无法与基准表匹配而出现是空值的字段。

得到的结果数
inner join <= left join, right join
full join >= left join, right join
当 inner join < left join, right join 时, full join > left join, right join

SQL LEFT JOIN 关键字从左表(table1)返回所有的行,即使右表(table2)中没有匹配。如果右表中没有匹配,则结果为 NULL

INNER JOIN语法(只要将上面代码的 INNER 改成 LEFT 就行)

1
2
3
4
SELECT column_name(s)
FROM table1
LEFT JOIN table2
ON table1.column_name=table2.column_name;

运行代码

1
2
3
4
select test_table.id,test_table.name,new_table.height,test_table.score 
from test_table left JOIN new_table ON
test_table.id=new_table.id
ORDER BY new_table.height DESC;

结果如下:

可以理解为:返回左边所有的人以及他们的身高(如果有的话)。

注释:LEFT JOIN 关键字从左表(test_table)返回所有的行,即使右表(new_table)中没有匹配。

SQL RIGHT JOIN 关键字从右表(table2)返回所有的行,即使左表(table1)中没有匹配。如果左表中没有匹配,则结果为 NULL。
注意:这里哪些显示为 NULL 是由上面 SELECT 如何选择列字段来决定的

SQL RIGHT JOIN 语法

1
2
3
4
SELECT column_name(s)
FROM table1
RIGHT JOIN table2
ON table1.column_name=table2.column_name;

运行代码

1
2
3
4
select test_table.id,test_table.name,new_table.height,test_table.score 
from test_table right JOIN new_table ON
test_table.id=new_table.id
ORDER BY new_table.height DESC;

结果如下:

这是因为 new_table 里有 test_table 里没有的 id=5,左表没有对应的数据,加上 SELECT 那里选择的是 test_table.name 等,所以只有 new_table.height 显示了,如果我 SELECT 那里选择的是 new_table.name ,那么便有:

注释:RIGHT JOIN 关键字从右表(access_log)返回所有的行,即使左表(Websites)中没有匹配。

SQL FULL OUTER JOIN 关键字只要左表(table1)和右表(table2)其中一个表中存在匹配,则返回行。

FULL OUTER JOIN 关键字结合了 LEFT JOIN 和 RIGHT JOIN 的结果,即并集。

MySQL中并没有直接支持FULL JOIN的语法,但可以使用UNION操作符和LEFT JOIN、RIGHT JOIN语法组合实现FULL JOIN的功能。 具体来说,可以将LEFT JOIN和RIGHT JOIN联合起来,使用UNION操作符将它们的结果合并,就可以实现FULL JOIN。

1
2
3
4
5
6
7
8
9
SELECT test_table.id,test_table.name,new_table.height,test_table.score
FROM test_table
LEFT JOIN new_table ON test_table.id = new_table.id

UNION

SELECT test_table.id,test_table.name,new_table.height,test_table.score
FROM test_table
RIGHT JOIN new_table ON test_table.id = new_table.id

组合 UNION

求并集

SQL UNION 操作符合并两个或多个 SELECT 语句的结果集。

请注意,UNION 内部的每个 SELECT 语句必须拥有相同数量的列。列也必须拥有相似的数据类型。同时,每个 SELECT 语句中的列的顺序必须相同

SQL UNION 语法

1
2
3
SELECT column_name(s) FROM table1
UNION
SELECT column_name(s) FROM table2;

注释:默认地,UNION 操作符选取不同的值(DISTINCT)。如果需要重复的值,请使用 UNION ALL。

如:选取所有不同的 name(只有不同的值):

1
2
3
4
SELECT id,name FROM test_table
UNION
SELECT id,name FROM new_table
order by id;

SQL 别名 AS

通过使用 SQL,可以为名称或名称指定别名。

基本上,创建别名是为了让列名称的可读性更强。

1
2
3
4
5
6
7
// 列的 SQL 别名语法
SELECT column_name AS alias_name
FROM table_name;

// 表的 SQL 别名语法
SELECT column_name(s)
FROM table_name AS alias_name;

如:

1
2
SELECT id as number, concat(name,',',age,',',score) as data
FROM runoob.test_table;

创建 number 作为 id 的别名,concat 将这三个列的数据结合在一块,并以 data 作为别名,

通过为表取别名,使用 select 查询时就要可以让 SQL 简短很多(在创建表的别名时就可以使用这个别名来选择这个表的列)

另外,在下面的情况下,使用别名很有用:
在查询中涉及超过一个表
在查询中使用了函数
列名称很长或者可读性差
需要把两个列或者多个列结合在一起

创建数据库、表

SQL CREATE DATABASE 语句用于创建数据库。

CREATE DATABASE dbname; 创建名为 “dbname” 的数据库。

SQL CREATE TABLE 语句用于创建数据库中的表。

1
2
3
4
5
6
7
CREATE TABLE table_name
(
column_name1 data_type(size),
column_name2 data_type(size),
column_name3 data_type(size),
....
);

data_type 参数规定列的数据类型(例如 varchar、integer、decimal、date 等等)。

size 参数规定表中列字段的最大长度。

SQL 约束(Constraints)

SQL 约束(Constraints)用于规定表中的数据规则

如果存在违反约束的数据行为,行为会被约束终止。

约束可以在创建表时规定(通过 CREATE TABLE 语句),或者在表创建之后规定(通过 ALTER TABLE 语句)。

SQL CREATE TABLE + CONSTRAINT 语法

1
2
3
4
5
6
7
CREATE TABLE table_name
(
column_name1 data_type(size) constraint_name,
column_name2 data_type(size) constraint_name,
column_name3 data_type(size) constraint_name,
....
);

在 SQL 中,我们有如下约束:

  • NOT NULL - 指示该列不能存储 NULL 值。
  • UNIQUE - 保证该列的每行必须有唯一的值(不能有重复值)。
  • PRIMARY KEY - NOT NULL 和 UNIQUE 的结合。确保该列(或两个列多个列的结合)有唯一标识,有助于更容易更快速地找到表中的一个特定的记录。
  • FOREIGN KEY - 保证一个表中的数据匹配另一个表中的值的参照完整性。
  • CHECK - 保证列中的值符合指定的条件。
  • DEFAULT - 规定没有给列赋值时的默认值。

在默认的情况下,表的列接受 NULL 值。
NOT NULL 约束强制字段始终包含值。这意味着,如果不向字段添加值,就无法插入新记录或者更新记录。

1
2
3
4
5
6
CREATE TABLE Persons (
ID int NOT NULL,
LastName varchar(255) NOT NULL,
FirstName varchar(255) NOT NULL,
Age int
);

在一个已创建的表添加和删除 NOT NULL 约束,注意,列名后面需要跟数据类型。

1
2
3
4
5
ALTER TABLE Persons
MODIFY Age int NOT NULL;

ALTER TABLE Persons
MODIFY Age int NULL;

UNIQUE 约束唯一标识数据库表中的每条记录。

UNIQUE 和 PRIMARY KEY 约束均为列或列集合提供了唯一性的保证。

PRIMARY KEY 约束拥有自动定义的 UNIQUE 约束。

SQL PRIMARY KEY 约束

每个表都应该有一个主键,并且每个表只能有一个主键。注意:只有有一个主键不是说只有一列是主键,可以是这个主键设置为包含了很多列(为多个列创建 PRIMARY KEY 约束)。
如:CONSTRAINT pk_PersonID PRIMARY KEY (P_Id,LastName)
注释:在上面的实例中,只有一个主键 PRIMARY KEY(pk_PersonID 主键名)。然而,pk_PersonID 的值是由两个列(P_Id 和 LastName)组成的。

Auto-increment 会在新记录插入表中时生成一个唯一的数字。

我们通常希望在每次插入新记录时,自动地创建主键字段的值。

我们可以在表中创建一个 auto-increment 字段。

SQL FOREIGN KEY 约束

一个表中的 FOREIGN KEY 指向另一个表中的 UNIQUE KEY(唯一约束的键)。

FOREIGN KEY 约束用于预防破坏表之间连接的行为

FOREIGN KEY 约束也能防止非法数据插入外键列,因为它必须是它指向的那个表中的值之一

略微复杂,之后再来吧。

SQL CREATE INDEX 语句

CREATE INDEX 语句用于在表中创建索引

不读取整个表的情况下,索引使数据库应用程序可以更快地查找数据。

通过在表中创建索引,以便更加快速高效地查询数据。

用户无法看到索引,它们只能被用来加速搜索/查询。

注释:更新一个包含索引的表需要比更新一个没有索引的表花费更多的时间,这是由于索引本身也需要更新。因此,理想的做法是仅仅在常常被搜索的列(以及表)上面创建索引。

在表上创建一个简单的索引,允许使用重复的值:

1
2
CREATE INDEX index_name
ON table_name (column_name)

SQL CREATE UNIQUE INDEX 语法
在表上创建一个唯一的索引。不允许使用重复的值:唯一的索引意味着两个行不能拥有相同的索引值。

1
2
CREATE UNIQUE INDEX index_name
ON table_name (column_name)

注释:用于创建索引的语法在不同的数据库中不一样。因此,检查您的数据库中创建索引的语法。

CREATE INDEX 实例
下面的 SQL 语句在 “Persons” 表的 “LastName” 列上创建一个名为 “PIndex” 的索引:

CREATE INDEX PIndex ON Persons (LastName)

如果您希望索引不止一个列,您可以在括号中列出这些列的名称,用逗号隔开:

CREATE INDEX PIndex ON Persons (LastName, FirstName)

DROP

DROP INDEX 语句
索引是一种优化数据库查询性能的结构,但有时候可能需要删除某个索引,例如当索引不再需要或需要替换为新的索引时。
DROP INDEX 语句用于删除表中的索引。
DROP INDEX [IF EXISTS] index_name ON TABLE_NAME;

DROP TABLE 语句
DROP TABLE 语句用于删除表。
删除表将同时删除表的结构以及存储在其中的所有数据。因此,在执行DROP TABLE语句之前,请确保您真的希望永久删除表及其所有数据,因为此操作是不可逆的。
DROP TABLE [IF EXISTS] TABLE_NAME;

DROP DATABASE 语句
DROP DATABASE 语句用于删除数据库,包括其中的所有表、视图、存储过程数据库对象
DROP DATABASE 是一个非常强大和危险的操作,因为它会永久删除整个数据库及其所有相关数据,因此在执行之前务必要慎重考虑并确保你真的希望执行此操作。
DROP DATABASE [IF EXISTS] database_name;
在执行 DROP DATABASE 之前,请确保你已经备份了数据库中的重要数据,并且你确实有权限执行这个操作,因为删除数据库通常需要管理员或超级用户的权限。此外,执行此类操作之前最好先确认没有其他用户正在使用该数据库。

TRUNCATE TABLE 语句(表示清空表的操作)
如果我们仅仅需要删除表内的数据,但并不删除表本身,那么我们该如何做呢?
在 SQL 中,TRUNCATE TABLE 语句用于快速删除表中的所有数据,但保留表的结构(列、约束等),与 DELETE 语句相比,TRUNCATE TABLE 通常更快,因为它是通过删除表中的所有行而不是逐行删除实现的。

然而,需要注意的是,TRUNCATE TABLE不会触发触发器,而且无法在事务中进行回滚。

请使用 TRUNCATE TABLE 语句:
TRUNCATE TABLE TABLE_NAME;

当使用 TRUNCATE TABLE 清除数据时,表的主键自增值将被重置为默认的起始值,通常是从 1 开始。这意味着下一次插入数据时,主键将从 1 开始递增。与之不同的是,使用 DELETE 语句删除数据并不会重置主键自增值,而是保留当前的自增值。

SQL 视图(Views)

视图是可视化的表

本章讲解如何创建、更新和删除视图。

SQL CREATE VIEW 语句(创建
在 SQL 中,视图是基于 SQL 语句的结果集的可视化的表。
视图包含行和列,就像一个真实的表。视图中的字段就是来自一个或多个数据库中的真实的表中的字段。

您可以向视图添加 SQL 函数、WHERE 以及 JOIN 语句,也可以呈现数据,就像这些数据来自于某个单一的表一样

SQL CREATE VIEW 语法

1
2
3
4
CREATE VIEW view_name AS
SELECT column1, column2, ...
FROM table_name
WHERE condition;

参数说明:
CREATE VIEW: 声明你要创建一个视图。
view_name: 指定视图的名称。
AS: 指定关键字表示视图的定义开始
SELECT column1, column2, …: 指定视图中包含的列,可以是表中的列或计算列。
FROM table_name: 指定视图从哪个表中获取数据。
WHERE condition: 可选部分,用于指定筛选条件,限制视图中的行。
注释:视图总是显示最新的数据!每当用户查询视图时,数据库引擎通过使用视图的 SQL 语句重建数据。

比如我通过如下代码创建视图:

1
2
create view highScore AS select id,name,age,score 
from test_table where score>50;

现在就可以将视图当作一个普通表一样操作,比如查询:
SELECT * FROM highScore;
这将返回所有 score 高于 50 的学生的信息,而不需要每次都编写相同的筛选条件。

值得注意的是,视图本质上是一个虚拟的表它并不存储数据,而是基于基础表的查询结果生成。因此,如果基础表的数据发生变化,视图的内容也会相应地更新。(正如前文所提到的一样)

SQL 更新视图
在 SQL 中,你不能直接使用 UPDATE 语句来更新视图,因为视图是基于查询结果生成的虚拟表,而不是实际存储数据的表

更新视图的实质是通过更新视图所基于的表中的数据(通过讲过的 update 语句),然后视图会反映这些变化,不需要且不能重新创建视图

SQL 撤销视图

在 SQL 中,撤销(或删除)视图是通过使用 DROP VIEW 语句来实现的。

DROP VIEW 语句用于从数据库中删除一个已存在的视图。语法如下:

DROP VIEW [IF EXISTS] view_name;

参数说明:
DROP VIEW: 表示你要删除一个视图。
IF EXISTS: 可选部分,用于检查视图是否存在。如果存在,则执行删除操作;如果不存在,不会发生错误。在某些数据库系统中,这是可选的。
view_name: 指定要删除的视图的名称。

请注意,这并不影响基础表中的数据,只是删除了视图的定义

在使用 DROP VIEW 语句时,请确保你真的想要删除该视图,因为一旦删除,将无法恢复视图的定义。

视图的作用

  1. 视图隐藏了底层的表结构,简化了数据访问操作,客户端不再需要知道底层表的结构及其之间的关系。
  2. 视图提供了一个统一访问数据的接口。(即可以允许用户通过视图访问数据的安全机制,而不授予用户直接访问底层表的权限)从而加强了安全性,使用户只能看到视图所显示的数据。
  3. 视图还可以被嵌套,一个视图中可以嵌套另一个视图。

MySQL 数据类型

SQL 通用数据类型
数据库表中的每个列都要求有名称和数据类型。SQL 开发人员必须创建 SQL 表时决定表中的每个列将要存储的数据的类型。数据类型是一个标签,是便于 SQL 了解每个列期望存储什么类型的数据的指南,它也标识了 SQL 如何与存储的数据进行交互。

在 MySQL 中,有三种主要的类型:Text(文本)、Number(数字)和 Date/Time(日期/时间)类型。

注意:以上的 size 代表的并不是存储在数据库中的具体的长度,如 int(4) 并不是只能存储4个长度的数字。实际上int(size)占多少存储空间和 size 大小并无任何关系。int(3)、int(4)、int(8) 在磁盘上都是占用 4 btyes 的存储空间。
区别就是在显示给用户的方式有点不同。
例如:
1、int的值为10
int(9)显示结果为000000010
int(3)显示结果为010
就是显示的长度不一样而已 都是占用四个字节的存储空间

即便 DATETIME 和 TIMESTAMP 返回相同的格式,它们的工作方式很不同。在 INSERT 或 UPDATE 查询中,TIMESTAMP 自动把自身设置为当前的日期和时间。TIMESTAMP 也接受不同的格式,比如 YYYYMMDDHHMMSS、YYMMDDHHMMSS、YYYYMMDD 或 YYMMDD。

Redis

Redis是一个使用ANSI C编写的开源、支持网络、基于内存、可选持久性(英语:Durability_(database_systems))的键值对存储数据库。

1
2
3
4
5
6
7
8
redis-server  // 启动 redis 服务器(必须先启动 redis 服务)
redis-cli // 连接本地的 redis 服务 27.0.0.1:6379
PING // 该命令用于检测 redis 服务是否启动

如果需要在远程 redis 服务上执行命令,同样使用的也是 redis-cli :
redis-cli -h host -p port -a password
如连接到主机为 127.0.0.1,端口为 6379 ,密码为 mypass 的 redis 服务
redis-cli -h 127.0.0.1 -p 6379 -a "mypass"

数据类型

Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)。

基本命令语法都是 COMMAND KEY_NAMEKEY_NAME是键的名字。

string(字符串)

和 java 中的字符串一样。

1
2
3
4
5
SET key value
设置指定 key 的值。

GET key
获取指定 key 的值。

String 类型的应用场景:缓存对象、常规计数、分布式锁、共享 session 信息等。

list(列表)

Redis列表是简单的字符串列表,每个值都是一个字符串,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边),两端都可进行 push 和 pop 操作,读取单个或多个元素,根据 值 查找和删除元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
LPUSH key value1 [value2]
将一个或多个值插入到列表头部

LPOP key
移出并获取列表的第一个元素

RPOP key
移除列表的最后一个元素,返回值为移除的元素。

RPUSH key value1 [value2]
在列表中添加一个或多个值到列表尾部

LLEN key
获取列表长度

LRANGE key start stop
获取列表指定范围内的元素

List 类型的应用场景:消息队列(但是有两个问题:1. 生产者需要自行实现全局唯一 ID;2. 不能以消费组形式消费数据)等。

set(集合)

字符串的无序集合,集合成员是唯一的,这就意味着集合中不能出现重复的数据。包含集合常见的操作方法,计算交并差集。

Redis 中集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
SADD key member1 [member2]
向集合添加一个或多个成员

SCARD key
获取集合的成员数

SISMEMBER key member
判断 member 元素是否是集合 key 的成员

SMEMBERS key
返回集合中的所有成员

SREM key member1 [member2]
移除集合中一个或多个成员

Set 类型:聚合计算(并集、交集、差集)场景,比如点赞、共同关注、抽奖活动等。

hash(哈希散列表)

包含键值对的无序散列表。

Redis hash 是一个 string 类型的 field(字段) 和 value(值) 的映射表,hash 特别适合用于存储对象。一个键 key 可以由很多个字段 field,每个字段都有相对应的 value。对 hash 的操作基本都要带上 键 key,如果操作精细到 field 就加上 field 字段。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
HSET key field value
将哈希表 key 中的字段 field 的值设为 value 。

HGET key field
获取存储在哈希表中指定字段的值。

HMSET key field1 value1 [field2 value2 ]
同时将多个 field-value (域-值)对设置到哈希表 key 中。

HMGET key field1 [field2]
获取所有给定字段的值

HEXISTS key field
查看哈希表 key 中,指定的字段是否存在。

HGETALL key
获取在哈希表中指定 key 的所有字段和值

Hash 类型:缓存对象、购物车等。

zset (sorted set:有序集合)

Redis 有序集合和集合一样也是 string 类型元素的集合,且不允许重复的成员
不同的是每个元素都会关联一个 double 类型的分数。redis 正是通过分数来为集合中的成员进行从小到大的排序。
有序集合的成员是唯一的,但分数(score)却可以重复

有序集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。

1
2
3
4
5
6
7
8
9
10
11
ZADD key score1 member1 [score2 member2]
向有序集合添加一个或多个成员,或者 更新 已存在成员的分数

ZCARD key
获取有序集合的成员数

ZRANGE key start stop [WITHSCORES]
通过 索引区间 返回有序集合指定区间内的成员 // 不是分数区间,有序集合,索引有意义

ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT]
通过分数返回有序集合指定区间内的成员

Zset 类型:排序场景,比如排行榜、电话和姓名排序等。

Redis 发布订阅

Redis 发布订阅 (pub/sub) 是一种消息通信模式:发送者 (pub) 发送消息,订阅者 (sub) 接收消息。

Redis 客户端可以订阅任意数量的频道。

当有新消息通过 PUBLISH 命令发送给频道 channel 时, 这个消息就会被发送给订阅它的 所有客户端:

1
2
3
4
5
SUBSCRIBE channel [channel ...]
订阅给定的一个或多个频道的信息。

PUBLISH channel message
将信息发送到指定的频道。

Redis 事务

Redis 事务可以一次执行多个命令, 并且带有以下三个重要的保证:

批量操作在发送 EXEC 命令前被放入队列缓存
收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行
在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中。

一个事务从开始到执行会经历以下三个阶段:
开始事务。
命令入队。
执行事务。

以下是一个事务的例子, 它先以 MULTI 开始一个事务, 然后将多个命令入队到事务中, 最后由 EXEC 命令触发事务一并执行事务中的所有命令:事务就像储存命令的容器。

单个 Redis 命令的执行是原子性的,但 Redis 没有在事务上增加任何维持原子性的机制,所以 Redis 事务的执行并不是原子性的。

事务可以理解为一个打包的批量执行脚本,但批量指令并非原子化的操作,中间某条指令的失败不会导致前面已做指令的回滚,也不会造成后续的指令不做。

1
2
3
4
5
6
7
8
DISCARD
取消事务,放弃执行事务块内的所有命令。

EXEC
执行事务块内的所有命令。

MULTI
标记一个事务块的开始。

Redis 脚本

Redis 脚本使用 Lua 解释器来执行脚本。 Redis 2.6 版本通过内嵌支持 Lua 环境。执行脚本的常用命令为 EVAL。

Eval 命令的基本语法如下:

redis 127.0.0.1:6379> EVAL script numkeys key [key ...] arg [arg ...]

1
2
3
4
5
6
7
8
9
EVAL script numkeys key [key ...] arg [arg ...]
执行 **Lua** 脚本。

redis 127.0.0.1:6379> EVAL "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 first second

1) "key1"
2) "key2"
3) "first"
4) "second"

Redis GEO

Redis GEO 主要用于存储地理位置信息,并对存储的信息进行操作。

Redis GEO 操作方法有:

geoadd:添加地理位置的坐标。
geopos:获取地理位置的坐标。
geodist:计算两个位置之间的距离。
georadius:根据用户给定的经纬度坐标来获取指定范围内的地理位置集合。
georadiusbymember:根据储存在位置集合里面的某个地点获取指定范围内的地理位置集合。
geohash:返回一个或多个位置对象的 geohash 值。

geoadd
geoadd 用于存储指定的地理空间位置,可以将一个或多个经度(longitude)、纬度(latitude)、位置名称(member)添加到指定的 key 中。

geoadd 语法格式如下:
GEOADD key longitude latitude member [longitude latitude member ...]

geopos
geopos 用于从给定的 key 里返回所有指定名称(member)的位置(经度和纬度),不存在的返回 nil。

geopos 语法格式如下:
GEOPOS key member [member ...]

geodist
geodist 用于返回两个给定位置之间的距离。

geodist 语法格式如下:
GEODIST key member1 member2 [m|km|ft|mi]

member1 member2 为两个地理位置。

最后一个距离单位参数说明:
m :米,默认单位。
km :千米。
mi :英里。
ft :英尺。

georadius、georadiusbymember
georadius 以给定的经纬度为中心, 返回键包含的位置元素中与中心的距离不超过给定最大距离的所有位置元素。

georadiusbymember 和 GEORADIUS 命令一样, 都可以找出位于指定范围内的元素, 但是 georadiusbymember 的中心点是由给定的位置元素决定的, 而不是使用经度和纬度来决定中心点

georadius 与 georadiusbymember 语法格式如下:

GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]

GEORADIUSBYMEMBER key member radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]

WITHDIST: 在返回位置元素的同时, 将位置元素与中心之间的距离也一并返回。
WITHCOORD: 将位置元素的经度和纬度也一并返回。
COUNT 限定返回的记录数。
ASC: 查找结果根据距离从近到远排序。
DESC: 查找结果根据从远到近排序。

1
2
3
4
5
6
7
8
9
GEOADD Sicily 13.361389 38.115556 "Palermo" 15.087269 37.502669 "Catania" 13.583333 37.316667 "Agrigento"

GEOPOS Sicily Palermo Catania

GEODIST Sicily Palermo Catania

GEORADIUS Sicily 15 37 200 km WITHDIST WITHCOORD ASC

GEORADIUSBYMEMBER Sicily Agrigento 200 km

Redis Stream

Redis Stream 是 Redis 5.0 版本新增加的数据结构。

Redis Stream 主要用于消息队列(MQ,Message Queue),Redis 本身是有一个 Redis 发布订阅 (pub/sub) 来实现消息队列的功能,但它有个缺点就是消息无法持久化,如果出现网络断开、Redis 宕机等,消息就会被丢弃。

简单来说发布订阅 (pub/sub) 可以分发消息,但无法记录历史消息。

而 Redis Stream 提供了消息的持久化和主备复制功能,可以让任何客户端访问任何时刻的数据,并且能记住每一个客户端的访问位置,还能保证消息不丢失。

Redis Stream 的结构如下所示,它有一个消息链表,将所有加入的消息都串起来,每个消息都有一个唯一的 ID 和对应的内容:

每个 Stream 都有唯一的名称,它就是 Redis 的 key,在我们首次使用 xadd 指令追加消息时自动创建。

Consumer Group :消费组,使用 XGROUP CREATE 命令创建,一个消费组有多个消费者(Consumer)。

last_delivered_id :游标,每个消费组会有个游标 last_delivered_id,任意一个消费者读取了消息都会使游标 last_delivered_id 往前(Stream direction)移动。

pending_ids :消费者(Consumer)的状态变量,作用是维护消费者的未确认的 id。 pending_ids 记录了当前已经被客户端读取的消息,但是还没有 ack (Acknowledge character:确认字符)。

消息队列相关命令:
XADD - 添加消息到末尾
XTRIM - 对流进行修剪,限制长度
XDEL - 删除消息
XLEN - 获取流包含的元素数量,即消息长度
XRANGE - 获取消息列表,会自动过滤已经删除的消息
XREVRANGE - 反向获取消息列表,ID 从大到小
XREAD - 以阻塞或非阻塞方式获取消息列表

消费者组相关命令:
XGROUP CREATE - 创建消费者组
XREADGROUP GROUP - 读取消费者组中的消息
XACK - 将消息标记为”已处理”
XGROUP SETID - 为消费者组设置新的最后递送消息ID
XGROUP DELCONSUMER - 删除消费者
XGROUP DESTROY - 删除消费者组
XPENDING - 显示待处理消息的相关信息
XCLAIM - 转移消息的归属权
XINFO - 查看流和消费者组的相关信息;

XADD
使用 XADD 向队列添加消息,如果指定的队列不存在,则创建一个队列(key),XADD 语法格式:
XADD key ID field value [field value ...]
key :队列名称,如果不存在就创建
ID :消息 id,我们使用 * 表示由 redis 帮我们生成合适的ID,可以自定义,但是要自己保证递增性。使用 * 执行命令之后返回的就是 消息 id,其他命令可能要用的这个 ID。
field value : 记录。
每次在消息队列中添加的都是一条消息,一条消息可以包含很多内容。

XTRIM
使用 XTRIM 对流进行修剪,限制长度, 语法格式:
XTRIM key MAXLEN [~] count
key :队列名称
MAXLEN :长度
count :数量

XDEL
使用 XDEL 删除消息,语法格式:
XDEL key ID [ID ...]
key:队列名称
ID :消息 ID

XRANGE
使用 XRANGE 获取消息列表,会自动过滤已经删除的消息 ,语法格式:
XRANGE key start end [COUNT count]
key :队列名
start :开始值, - 表示最小值
end :结束值, + 表示最大值
count :数量

XREVRANGE
使用 XREVRANGE 获取反向消息列表,会自动过滤已经删除的消息 ,语法格式:
XREVRANGE key end start [COUNT count]
key :队列名
end :结束值, + 表示最大值
start :开始值, - 表示最小值
count :数量

XREAD
使用 XREAD 以阻塞或非阻塞方式获取消息列表 ,语法格式:
XREAD [COUNT count] [BLOCK milliseconds] STREAMS key [key ...] id [id ...]
count :数量
milliseconds :可选,阻塞毫秒数,没有设置就是非阻塞模式
key :队列名
id :消息 ID

XGROUP CREATE
使用 XGROUP CREATE 创建消费者组,语法格式:
XGROUP [CREATE key groupname id-or-$]
key :队列名称,如果不存在就创建
groupname :组名。
$: 表示从 尾部开始消费 ,只接受新消息,当前 Stream 消息会全部忽略。
从头开始消费:
XGROUP CREATE mystream consumer-group-name 0-0

从尾部开始消费:
XGROUP CREATE mystream consumer-group-name $

XREADGROUP GROUP
使用 XREADGROUP GROUP 读取消费组中的消息,语法格式:

XREADGROUP GROUP group consumer [COUNT count] [BLOCK milliseconds] [NOACK] STREAMS key [key ...] ID [ID ...]
group :消费组名
consumer :消费者名。
count : 读取数量。
milliseconds : 阻塞毫秒数。
key : 队列名。
ID : 消息 ID。
XREADGROUP GROUP consumer-group-name consumer-name COUNT 1 STREAMS mystream >

1
2
3
4
5
6
7
8
9
10
11
12
13
XADD mystream * name Sara surname OConnor

XADD mystream * field1 value1 field2 value2 field3 value3

XLEN mystream

XRANGE mystream - +

XTRIM mystream MAXLEN 2

XGROUP CREATE mystream consumer-group-name 0-0

XREADGROUP GROUP consumer-group-name consumer-name COUNT 1 STREAMS mystream >

Redis 数据备份与恢复

Redis SAVE 命令用于创建当前数据库的备份。
该命令将在 redis 安装目录中创建dump.rdb文件。

恢复数据
如果需要恢复数据,只需将备份文件 (dump.rdb) 移动到 redis 安装目录并启动服务即可。获取 redis 目录可以使用 CONFIG GET dir 命令。

以上命令 CONFIG GET dir 输出 redis 安装目录=。

Redis 安全

我们可以通过 redis 的配置文件设置密码参数,这样客户端连接到 redis 服务就需要密码验证,这样可以让你的 redis 服务更安全。

CONFIG get requirepass查看是否设置了密码验证

默认情况下 requirepass 参数是空的,这就意味着你无需通过密码验证就可以连接到 redis 服务。

你可以通过以下命令来修改该参数:
CONFIG set requirepass "password"
设置密码后,客户端连接 redis 服务就需要密码验证,否则无法执行命令。

使用 AUTH password 命令进行密码验证。

Redis 客户端连接

Redis 通过监听一个 TCP 端口或者 Unix socket 的方式来接收来自客户端的连接,当一个连接建立后,Redis 内部会进行以下一些操作:

首先,客户端 socket 会被设置为非阻塞模式,因为 Redis 在网络事件处理上采用的是非阻塞多路复用模型。
然后为这个 socket 设置 TCP_NODELAY 属性,禁用 Nagle 算法
然后创建一个可读的文件事件用于监听这个客户端 socket 的数据发送

最大连接数 maxclients 的默认值是 10000,你也可以在 redis.conf 中对这个值进行修改。

config get maxclients 查看最大连接数。

CLIENT LIST 返回连接到 redis 服务的客户端列表。

Redis 管道技术

Redis是一种基于 客户端-服务端 模型以及 请求/响应 协议 的TCP服务。这意味着通常情况下一个请求会遵循以下步骤:

客户端向服务端发送一个查询请求,并监听Socket返回,通常是以阻塞模式,等待服务端响应。
服务端处理命令,并将结果返回给客户端。

阻塞模式
阻塞模式(Blocking Mode)是一种在编程中常见的同步模式,它指的是在某些操作无法立即完成时,线程会被阻塞(暂停执行),直到操作完成或者达到一定的条件后再继续执行。阻塞模式通常与同步机制(如锁、信号量)结合使用,用于实现线程间的协调和数据同步。

在阻塞模式下,线程会在以下情况被阻塞:

I/O 操作:当线程进行 I/O 操作时,如果操作无法立即完成(例如读取文件、网络通信等),线程会被阻塞,直到数据就绪或超时。
等待条件:当线程需要等待某个条件的满足时,如果条件尚未达到,线程会被阻塞,直到条件满足或超时。
获取锁:当线程试图获取某个锁时,如果锁已被其他线程持有,线程会被阻塞,直到锁可用或超时。
阻塞模式与非阻塞模式相对应。在非阻塞模式下,线程不会等待操作的完成或条件的满足,而是立即返回,通常会周期性地进行轮询或通过回调函数来检查操作或条件是否已完成。

在编程中,阻塞模式通常适用于那些需要等待外部事件或资源就绪后才能继续执行的场景,例如网络编程、多线程同步、事件驱动编程等。然而,过度使用阻塞模式可能会导致资源浪费和性能下降,因此需要根据具体情况综合考虑选择合适的同步模式。

回调函数
回调函数(Callback Function)是一种常见的编程模式,用于实现异步编程。它是指在某个操作完成后,通过调用预先注册的函数(回调函数),来通知调用方处理结果

回调函数通常用于以下几种情况:

事件处理:当某个事件发生时,调用预先注册的回调函数来处理事件,例如点击按钮时触发的点击事件、接收到网络请求时触发的数据到达事件等。
异步操作:当进行耗时的异步操作(如文件读取、网络请求)时,通过回调函数来处理操作完成后的结果,以避免阻塞线程。
错误处理:当发生错误或异常时,通过回调函数来处理错误信息,例如异步操作失败时的错误处理。
回调函数通常作为参数传递给异步操作的函数或方法,当操作完成后,将回调函数作为参数调用,传递操作的结果或错误信息。在编程中,回调函数可以是函数指针、匿名函数、Lambda 表达式等形式。

回调函数的优点是它能够实现非阻塞的异步编程,提高了程序的响应速度和并发性。然而,回调函数也有一些缺点,例如代码可读性较差、回调地狱(Callback Hell)等问题,因此在使用时需要注意适当的设计和组织。

Redis 管道技术
Redis 管道技术可以在服务端未响应时,客户端可以继续向服务端发送请求,并最终一次性读取所有服务端的响应

管道技术最显著的优势是提高了 redis 服务的性能。

Redis 分区

分区是分割数据到多个Redis实例的处理过程,因此每个实例只保存key的一个子集。

分区的优势
通过利用多台计算机内存的和值,允许我们构造更大的数据库。
通过多核和多台计算机,允许我们扩展计算能力;通过多台计算机和网络适配器,允许我们扩展网络带宽。

分区的不足
redis的一些特性在分区方面表现的不是很好:

  1. 涉及多个key的操作通常是不被支持的。举例来说,当两个set映射到不同的redis实例上时,你就不能对这两个set执行交集操作。
  2. 涉及多个key的redis事务不能使用。
  3. 当使用分区时,数据处理较为复杂,比如你需要处理多个rdb/aof文件,并且从多个实例和主机备份持久化文件。
  4. 增加或删除容量也比较复杂。redis集群大多数支持在运行时增加、删除节点的透明数据平衡的能力,但是类似于客户端分区、代理等其他系统则不支持这项特性。然而,一种叫做presharding的技术对此是有帮助的。

分区类型
Redis 有两种类型分区。 假设有4个Redis实例 R0,R1,R2,R3,和类似user:1,user:2这样的表示用户的多个key,对既定的key有两种不同方式来选择这个key存放在哪个实例中。也就是说,有不同的系统来映射某个key到某个Redis服务

  1. 范围分区
  2. 哈希分区
    另外一种分区方法是hash分区。这对任何key都适用,也无需是object_name:这种形式,像下面描述的一样简单:
    用一个hash函数将key转换为一个数字,比如使用crc32 hash函数。对key foobar执行crc32(foobar)会输出类似93024922的整数。
    对这个整数取模,将其转化为0-3之间的数字,就可以将这个整数映射到4个Redis实例中的一个了。93024922 % 4 = 2,就是说key foobar应该被存到R2实例中。

Java 使用 Redis (Jedis)

Jedis是Java最常用的Redis客户端,它的使用方法参数与Redis重合度非常高,这意味着对于Java开发人员来说不会增加新的学习成本,颇有一种开箱即用的味道。

jedis

首先在 Maven仓库下载 Jedis.jar 包,在源文件夹 src 下建立 lib 文件夹,项目左边栏右键空白处之后选择: Open Module Settings,点击 + 号选择Jars or Directories选项并导入lib 文件夹导入第三方库文件,再根据 Jedis 给出的 Maven 配置方式修改 Pom,成功导入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class Main {
public static void main(String[] args) {
//连接本地的 Redis 服务
Jedis jedis = new Jedis("localhost", 6379);
// 如果 Redis 服务设置了密码,需要下面这行,没有就不需要
// jedis.auth("password");
System.out.println("连接成功");
//查看服务是否运行
System.out.println("服务正在运行: "+jedis.ping());

//设置 redis 字符串数据
jedis.set("runoobkey", "www.runoob.com");
// 获取存储的数据并输出
System.out.println("redis 存储的字符串为: "+ jedis.get("runoobkey"));

//存储数据到列表中
jedis.lpush("site-list", "Runoob");
jedis.lpush("site-list", "Google");
jedis.lpush("site-list", "Taobao");
// 获取存储的数据并输出
List<String> list = jedis.lrange("site-list", 0 ,2);
for(int i=0; i<list.size(); i++) {
System.out.println("列表项"+ i + ":" + list.get(i));
}

// 获取所有的键
Set<String> keys = jedis.keys("*");
Iterator<String> it=keys.iterator() ;
while(it.hasNext()){
String key = it.next();
System.out.println(key);
}
}
}

slf4j-api、slf4j-log4j12以及log4j之间什么关系?

在Java世界中,日志记录是一项重要的功能,它帮助我们了解应用程序的运行情况,并在出现问题时提供调试信息。在这个过程中,SLF4J(Simple Logging Facade for Java)、Log4j和SLF4J-Log4j12扮演着关键的角色。

首先,我们来看看SLF4J。SLF4J,即Simple Logging Facade for Java,是一个用于Java的简单日志记录门面。它本身并不实现日志记录功能,而是提供了一个统一的接口,让开发者能够轻松地更换日志记录框架。这意味着,如果您的项目中使用了SLF4J,您可以随时将Log4j替换为Logback或其他日志记录框架,而无需修改代码中的日志记录部分。

接下来,我们来看看Log4j。Log4j是一个功能强大的日志记录框架,它实现了SLF4J接口。这意味着,您可以将Log4j作为SLF4J的实现来使用。Log4j提供了丰富的日志记录功能,包括不同级别的日志记录、日志消息格式化、异步日志记录等。此外,Log4j还支持各种日志输出目标,如控制台、文件、数据库等。

最后,我们来看看SLF4J-Log4j12。这是一个特殊的库,它将SLF4J与Log4j 1.2版本桥接起来。由于Log4j 1.2是最早版本的Log4j,并且仍然在许多项目中广泛使用,因此SLF4J-Log4j12为这些项目提供了一个从SLF4J到Log4j 1.2的桥梁。通过引入SLF4J-Log4j12,您可以在使用Log4j 1.2的项目中享受SLF4J带来的便利,例如轻松地更换日志记录框架等。

在实际项目中,我们经常会看到SLF4J、Log4j和SLF4J-Log4j12一起使用。通常情况下,项目会首先引入SLF4J和SLF4J-Log4j12,然后通过配置文件将SLF4J的日志记录委托给Log4j 1.2。这样,项目就可以享受到Log4j 1.2的丰富功能,同时还保留了通过SLF4J更换日志记录框架的灵活性。

总之,SLF4J、Log4j和SLF4J-Log4j12是Java日志记录领域中的关键组件。它们之间的关系可以概括为:SLF4J是一个日志记录门面,Log4j是一个实现了SLF4J接口的日志记录框架,而SLF4J-Log4j12则是将SLF4J与Log4j 1.2桥接起来的特殊库。通过了解这些组件之间的关系和作用,您将能够更好地理解和应用Java日志记录技术。

使用SLF4J时的一个错误Failed to load class org.slf4j.impl.StaticLoggerBinder
也有遇到这个问题,配置依赖时 scope 的影响。

在实际使用中,为避免多线程带来的并发问题,以及反复创建销毁Redis连接带来的性能消耗,我们通常会使用池化的思想——使用Jedis连接池。(就是一个大池子,随取随用,不用就归还,对并发有好处,和线程池、常量池是一个道理,Jedis连接池应该就属于数据库连接池的一种)

什么是池,Java中的池有哪些? 这篇不错。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 配置连接池
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(20);
config.setMaxIdle(10);
config.setMinIdle(5);

// 创建连接池
JedisPool jedisPool = new JedisPool(config, "localhost", 6379);

Jedis jedis = jedisPool.getResource();

// 使用jedis进行操作
jedis.set("name", "otherNameVal");

// 用完之后,一定要手动关闭连接(归还给连接池)
jedis.close();

如何在Java中优雅的使用Redis
提到使用连接池的时候如果连接没有成功关闭,最后就会造成阻塞和死机,所以需要使用 try catch final 来解决,同时使用了更进阶的写法。

使用Jedis操作Redis 有基础用法,也有讲使用Jedis连接Redis集群。
集群搭建

SSM

Spring

组件(bean):可以复用的 Java 对象。什么叫复用呢,比如就是赋值为全局变量的对象,比如 BookServlet 多次调用 BookService 服务就不需要多次 new 一个 BookService 对象,直接用 IOC 里面保存的可以复用的对象就好了。

spring 核心容器的 两大功能
IoC 控制反转 :将对象的控制权交给 spring
DI 依赖注入 :维护容器中对象的引用或依赖关系
所以核心容器也常叫 IoC容器

所以就是用三种配置方式(XML、注解、配置类,现在主流是配置类注解配合使用)去实现 IoC、DI(DI有三种实现方式:构造函数注入、setter 方法注入、接口注入)

SpringBoot

IDEA版SpringBoot全教程 01 快速入门

这篇文章学到了不少,包括 yml 的基础语法和配置,几个注解配置等。

mybatis

一些重要知识点

Nginx 代理和负载均衡

负载均衡:负载均衡服务器通过一定的调度算法将客户端的流量分发到不同的应用服务器上面,以实现性能的水平扩展及避免单点故障出现。

正向代理是指通过代理服务器 代理 浏览器/客户端 去重定向请求访问到目标服务器。

反向代理 – 跨域
反向代理,指以代理服务器来接受Internet上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给Internet上请求连接的客户端,此时代理服务器对外就表现为一个服务器。

代理的意思可以理解为:为某人出面,成为某人某物的门面,所以代理的意义是隐藏

感觉就有点像:正向代理是 代理 浏览器/客户端,重定向它们的请求去访问目标服务器,在这个过程中,目标服务器是与代理服务器通信,所以目标服务器看不到它真正的访问源,所以正向代理是能保护 浏览器/客户端 的网络安全的。
而反向代理的 代理服务器是 代理 目标服务器,替目标服务器接收来自互联网的连接请求,然后再把请求转发给内部网络上的服务器,这个过程还能实现负载均衡,之后将得到的结果返回给请求连接的客户端。此时这个代理服务器对 客户端 而言是不是就像一个普通的服务器一样,也就是说:反向代理的话代理服务器是成为了目标服务器的门面。此时目标服务器对客户端而言是透明的,这才是代理的真正意义。

这应该就是正反代理的区别。注意:它们的共同点是都通过代理服务器来转发请求流量,都通过它来通信。


反向代理(Reverse Proxy)和负载均衡(Load Balancing)是常见的网络架构模式,用于提高网络服务的可用性、性能和安全性。

反向代理:
反向代理是指代理服务器位于服务端,它接收客户端的请求,然后将请求转发给一个或多个后端服务器,并将后端服务器的响应返回给客户端。客户端并不直接与后端服务器通信,而是通过反向代理与后端服务器交互,因此客户端对于后端服务器是透明的。
反向代理常用于隐藏真实的服务器架构、提供负载均衡、缓存静态资源、提供安全防护等。常见的反向代理软件包括 Nginx、Apache HTTP Server、HAProxy 等。
负载均衡:
负载均衡是指将网络流量分配到多个服务器上,以平衡服务器的负载,提高系统的性能和可用性。负载均衡器位于客户端和服务器之间,根据一定的算法(如轮询、加权轮询、最少连接数等)将请求分发给多个后端服务器,以达到均衡负载的目的。
负载均衡器可以是硬件设备,也可以是软件实现。常见的负载均衡软件包括 Nginx、HAProxy、F5 等。
反向代理和负载均衡通常结合使用,以提供高可用性和性能的服务架构。反向代理可以作为负载均衡器的一部分,将流量分发到多个后端服务器上,并提供一些额外的功能,如缓存(某些情况Nginx缓存访问结果可以直接返回,不需要再次访问服务器)、SSL 终止、Web 应用防火墙等。通过结合使用反向代理和负载均衡,可以构建可靠、高效、安全的网络服务架构。


nginx 反向代理的好处:

提高访问速度
因为nginx本身可以进行缓存,如果访问的同一接口,并且做了数据缓存,nginx就直接可把数据返回,不需要真正地访问服务端,从而提高访问速度。

进行负载均衡
所谓负载均衡,就是把大量的请求按照我们指定的方式均衡的分配给集群中的每台服务器。

保证后端服务安全
因为一般后台服务地址不会暴露,所以使用浏览器不能直接访问,可以把nginx作为请求访问的入口,请求到达nginx后转发到具体的服务中,从而保证后端服务的安全。

可以看苍穹外卖第一章项目介绍里面有讲一些Nginx代理。

docker

Docker 是一种开源的容器化平台,用于将应用程序和其依赖项打包到一个称为容器的可移植的、可部署的单元中。Docker 使用容器封装应用程序和所有运行时环境,包括代码、运行时、系统工具、系统库等,从而实现了轻量级、快速、一致的应用程序交付和部署。

Docker 是开源的应用容器引擎,可以让开发者打包应用及其依赖包到一个可移植的容器中,然后发布到任何流行的 Linux 或 Windows 机器上,也可以实现虚拟化。容器完全使用沙箱机制,相互之间不会有任何接口。
下面对 Docker 的作用和基本用法做个简单介绍:
Docker 的作用:
降低系统部署难度和复杂性:Docker 可以将应用和服务包装在”容器”中进行部署,所有的依赖都包含在同一容器内,避免了在部署过程中由于环境问题导致的各种冲突和错误。
提高系统扩展性:对于需要频繁扩展和缩小的服务,Docker 可以在几秒钟内启动或关闭容器,非常适合微服务架构。
提高开发效率:Docker 可以保证开发、测试、预发布和生产环境的一致性,使开发人员更加专注于功能开发。
节省资源:相比虚拟机技术,一个相同应用的 Docker 容器镜像都能够运行在同一个机器上,由于容器直接运行于宿主的内核,不需要模拟整个操作系统,因此可以节省大量的系统资源。

运维(Operations,简称Ops)是指运营和维护系统的一系列工作,旨在确保系统的稳定性、安全性和高可用性。

运维工作是保障系统稳定运行的关键,需要具备丰富的系统知识、故障排除能力和快速响应的能力。随着云计算和 DevOps 等新技术的发展,运维工作也在不断演变和发展,越来越多地倾向于自动化、自助式和云原生的方向。

权限管理

RBAC新解:基于资源的权限管理(Resource-Based Access Control)

微服务

微服务(Microservices)是一种软件架构模式,将一个大型应用程序拆分成一组更小、更灵活的服务,每个服务都围绕着特定的业务功能进行构建和部署。每个服务都是一个独立的应用,可以单独开发、部署和扩展,通过轻量级的通信机制(通常是 HTTP 或消息队列)进行交互。

微服务架构的主要特点包括:

服务拆分:将应用程序拆分成多个小服务,每个服务负责一个特定的业务功能。服务之间通过定义良好的接口进行通信。
独立部署:每个微服务都可以独立部署,不受其他服务的影响,降低了部署的风险和复杂度。
技术多样性:不同的微服务可以使用不同的技术栈和编程语言来实现,选择最适合特定任务的技术。
自动化运维:通过自动化工具和容器技术(如 Docker、Kubernetes(K8s) ),可以实现微服务的自动化部署、扩展和管理。
弹性和可伸缩性:由于每个微服务都是独立的,可以根据负载情况对特定服务进行扩展,从而提高整个系统的弹性和可伸缩性。
分布式数据管理:每个微服务都有自己的数据存储,可能会使用不同的数据库或数据存储技术,需要通过异步通信或分布式事务来实现数据一致性。
服务治理:需要实现服务注册与发现、负载均衡、断路器等机制来保证服务的可用性和稳定性。
微服务架构适用于复杂的、大型的应用系统,特别是需要频繁迭代和快速发布的场景。它可以带来更灵活的开发和部署、更高的可扩展性和可靠性,但同时也带来了更多的复杂性和管理成本。因此,在采用微服务架构时,需要根据具体情况进行合理的架构设计和管理实践。

分布式事务

分布式事务是指涉及多个参与者的分布式系统中的事务操作,保证这些事务操作的一致性和可靠性是一个复杂的问题。在传统的单体系统中,事务管理是相对简单的,因为所有的事务操作都在同一个数据库事务中执行。但在分布式系统中,涉及到多个独立的服务或数据库,事务的一致性变得更加复杂。

分布式事务的一致性保障了事务的原子性、一致性、隔离性和持久性(ACID 属性)。

以下是实现分布式事务的几种常见方法:

两阶段提交(Two-Phase Commit,2PC):
2PC 是最经典的分布式事务协议之一。它通过两个阶段来确保所有参与者都同意提交或回滚事务。
第一阶段(准备阶段):协调者询问所有参与者是否准备好提交事务。如果所有参与者都准备好,则协调者发送提交请求;否则,发送回滚请求。
第二阶段(提交/回滚阶段):根据第一阶段的结果,协调者发送提交或回滚请求给所有参与者。

补偿事务(Compensating Transaction):
补偿事务是一种基于回滚机制的分布式事务处理方法。当发生某个参与者的操作失败时,会执行相应的补偿操作来恢复到一致状态。

本地消息表(Local Message Table):
使用消息队列实现分布式事务,将事务操作和消息发送放入同一个事务中,并使用本地消息表记录消息的发送状态。如果事务提交成功但消息发送失败,则重试发送消息。

消息队列

消息队列(Message Queue)是一种常见的分布式系统架构模式,用于在应用程序之间传递消息。它允许不同的应用程序或服务之间进行异步通信,解耦了系统中不同组件的耦合度,提高了系统的可扩展性、可靠性和灵活性。

消息队列的主要组成部分包括:

消息:消息是在不同应用程序之间传递的数据单元,通常包括一些业务数据和元数据。消息队列可以传递各种类型的消息,如文本、JSON、XML 等。
消息生产者:消息生产者是产生消息并发送到消息队列中的应用程序或服务。
消息队列:消息队列是一个存储消息的中间件,负责接收、存储和分发消息。
消息消费者:消息消费者是从消息队列中接收消息并处理的应用程序或服务。

消息队列的特点包括:
异步通信:消息生产者和消息消费者之间的通信是异步的,消息发送后生产者不需要等待消费者处理完毕即可继续执行。
解耦:通过消息队列,消息生产者和消费者之间的耦合度降低,它们可以独立进行开发、部署和维护。
可靠性:消息队列提供了消息持久化消息确认重试机制等功能,确保消息能够安全可靠地传递。
削峰填谷:消息队列可以作为缓冲层,平滑处理系统的峰值流量,防止系统过载。

常见的消息队列系统包括 RabbitMQ、Kafka、Redis、ActiveMQ、Amazon SQS(Simple Queue Service)等。选择合适的消息队列取决于具体的业务需求、系统架构和性能要求。

缓存击穿

缓存击穿(Cache Miss)是指在使用缓存系统时,某个热点数据缓存失效后,大量请求同时涌入,导致请求直接访问数据库或其他存储系统,使得数据库或存储系统负载剧增,甚至崩溃的情况。

缓存击穿通常发生在以下情况下:

热点数据失效:某个频繁访问的热点数据在缓存中过期或被删除,导致缓存失效。
高并发请求:大量并发请求同时涌入,请求未命中缓存,直接访问底层数据库或存储系统。
缓存击穿可能会导致以下问题:

数据库负载激增:大量请求直接访问数据库,导致数据库负载剧增,甚至引起数据库宕机。
响应时间增加:由于请求直接访问数据库,导致响应时间增加,影响系统性能。
为了避免缓存击穿,可以采取以下几种方法:

加锁或互斥机制:使用分布式锁或互斥机制,确保只有一个线程去加载数据,其他线程等待结果。
设置热点数据永不过期:对于热点数据,设置永不过期或者设置一个较长的过期时间,以保证不会因为缓存过期而导致缓存击穿。
使用互斥体:使用互斥体的方式来防止数据库穿透。当一个请求发现缓存失效时,首先获得一个互斥锁(Mutex),然后再去请求数据库,请求返回后再释放锁。
预加载:在缓存失效之前,提前预加载热点数据到缓存中,保证缓存不会空出。可以通过定时任务或者异步加载来实现。
失败重试:在缓存失效时,通过设置短暂的短期过期时间,再次尝试获取数据。如果再次失败,可以逐渐增加等待时间,直到获取到数据或达到最大等待时间。
以上方法可以有效地防止缓存击穿问题的发生,提高系统的稳定性和性能。

Project

RuoYi

最火后台管理系统 RuoYi 项目探秘

Pig

一猪三吃

开源界的卷王

遇到验证码没有图片,网页没有响应,点击F12,点击NetWork看一下,找到错误在哪里才能改。

HTTP 状态 500 是内部服务器错误,打开 Preview或者Response 看一下,发现是

反应过来是 redis server 没开,打开之后解决问题。

谷粒商城

前端学一点

shift + ! 回车得到html模板

shift + alt +f 整理代码Refractor

遇到的问题

  1. java 编译版本高于运行版本,我要用java8,所以首先需要配置项目所用的jdk和java compile 那里,然后查看springboot 和 springcloud版本,注意版本对应,就是没注意到springcloud,所以要好好看报错。

  2. lombok版本,出现很奇怪的报错的话就是lombok版本的原因,尽量选择最高版本的30,但是有一次选择了也没用,当时尝试勾选了

    use compiler from module target.. 解决了问题,但是后面再取消勾选也还是可以,所以很奇怪,这个选项可能与(使用指定的jdk或上次使用的jdk)有关。
  3. nacos本地启动然后注册服务,一直注册失败,地址什么的配置都没问题,重启nacos就行了。
    另外看了一篇文章就在地址配置那里翻车了,因为配置name那里没注意前缀,所以编码无小事。

  4. nacos不再使用ribbon,改用spring-cloud-loadbalancer依赖,所以需要导入该依赖,否则远程调用feign无法启动。

  5. nacos配置中心
    需要用 bootstrap.properties 或 bootstrap.yml 配置文件才行,这个文件优先级比 application.yml 更高,通过这个配置文件建立应用与nacos上的配置之间的联系。

注意在nacos上的配置文件的data id是有讲究的,就是前缀prefix加我们激活的application配置文件(之前学过的,可以指定active哪个文件,不指定就没有)加文件的扩展名(默认properties,用yaml要指定,注意只能是yaml不能是yml)。
${prefix}-${spring.profiles.active}.${file-extension}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
spring:
application:
name: friendmall-coupon
cloud:
nacos:
config:
server-addr: 127.0.0.1:8848
# 默认前缀本就是${spring.application.name}
prefix: ${spring.application.name}
# 默认配置文件扩展名为properties
file-extension: yaml
# 默认名空间为public
namespace: public # 用其他的要写id才行
# 默认组为DEFAULT_GROUP
group: DEFAULT_GROUP

首先需要导入 spring-cloud-starter-bootstrap 依赖,因为我无法解析bootstrap.yml 这个配置文件,控制台输出没有显示 located 到某一个配置文件。
之后定位到了 [BootstrapPropertySource {name='bootstrapProperties-friendmall-coupon.yaml,DEFAULT_GROUP'} ,注意这个是可以的。
但是依然没法动态刷新数据,搞了半天都不行,结果又是nacos的原因,重启nacos就可以了。

连续在nacos上翻两次车,如果都配置都正确了请重启nacos。

  1. 将项目的版本更新的和教程的一样
    不然老会遇到新问题,比如网关解决跨域问题时对预检请求处理不成功就是因为springboot2.4之后进行了修改,将addAllowedOrigin函数改为addAllowedOriginPattern,为了之后遇到更多问题,把版本改了吧。之后添加module的时候也要注意jdk版本,java版本都要设置,以及有时候依赖的版本也要设置。

    关于lombok的依赖,lombok在jdk21之后要设为1.18.30,common下的都是1.18.8。由于IDEA的原因,新建spring module的时候不能建jdk8的module,所以有时候只改了其他时,上图的依赖会对应于jdk21,此时就会出现lombok的那个经典报错。添加lombok的时候版本随便设也没关系,maven会调成真正需要的。

  2. nacos登录不上
    原先用nacos1.42,上面换版本后也换成nacos1.14结果nacos/nacos登录不上,原因是nacos的缓存造成的。
    解决:浏览器f12网络面板,勾选禁用缓存,然后刷新
    nacos账号密码错误

  3. NoClassDefFoundError: org/springframework/boot/context/properties/ConfigurationBeanFactoryMetadata
    nacos版本和springboot版本不兼容,在spring boot 2.4之后删掉了ConfigurationBeanFactoryMetadata,renrenfast里面是2.66的springboot,我给降了。

  4. 降版本带来的问题
    不知道是不是因为springboot版本降低,mybatis的数据库连接池连不上了,原因在于ssl认证那块,在 application.yml 的url里面加上 ?useSSL=false ,比如 url: jdbc:mysql://192.168.56.10:3306/friendmall_pms?useSSL=false,在数据库连接里面不用设置也行。
    参考这篇文章,虽然没讲为什么。idea连接mysql报错: No appropriate protocol (protocol is disabled or cipher suites are inappropriate

  5. p79里面销售属性里里面不显示快速展示按钮,应该没什么影响,弹幕4:50有人提解决方案 我没改 这俩文件在/modules/product里

  6. p83 由于前端缺少pubsub-js出现的问题
    前端的问题多看看评论区

npm install –save pubsub-js又失败了,因为node版本的问题,尝试了
npm install pubsub-js --legacy-peer-deps 成功了

  1. 端口占用
    查看被占用端口对应的 PID
    netstat -aon|findstr "8000"

查看指定 PID 对应的进程
tasklist|findstr "9088"

结束进程
强制(/F参数)杀死 pid 为 9088 的所有进程包括子进程(/T参数):
taskkill /T /F /PID 9088

酷狗音乐老是占8000端口号

  1. openfeign调用时出现问题
    如果是超时引起,加:
    1
    2
    3
    4
    # 配置 feign 默认请求时间仅几秒钟,配置请求时间长一些(毫秒)
    ribbon:
    ReadTimeout: 60000
    ConnectTimeout: 60000
    后面遇到了其他原因,业务是product调用coupon,再调用方看到了feign.FeignException: status 500 reading,方法中的异常比如除零异常等,都会通过feign.FeignException: status 500 reading…的方式给予你提示。如果检查feign的注册信息有没有写错,比如少一个斜杠什么之类的等之类发现没问题,可以去被调用方那里看看报错,我发现数据库又连接不上了,果然又是如9.所言?useSSL=false 的问题,因为coupon查自己的数据库又不行,加上就可以了

用的renrenfastvue是D/programfile下面的

  1. easyvpn占用10000端口且无法关闭
    端口占用问题,10000端口

进服务进程 services.msc 手动关

  1. 理解分页
    1
    2
    3
    4
    5
    6
    IPage<SpuInfoEntity> page = this.page(
    new Query<SpuInfoEntity>().getPage(params),
    wrapper
    );
    return new PageUtils(page)
    return R.ok().put("page", page)
    这些都是分页插件带来的方法,this.page有两个参数,第一个是IPage对象,是分页参数如page、limit等 前端传来的参数,第二个为Querywrapper对象,是查询条件,泛型是查询的实体类。最终返回实体类的分页查询结果IPage<实体类> page。

return new PageUtils(page) PageUtils(IPage<实体类> page)用PageUtils封装上一步的page生成标准的分页查询结果集PageUtils对象 page。

return R.ok().put(“page”, page) 在返回结果中将这个PageUtils对象 page 放到结果集中R.ok()中

  1. docker yum配置源

/etc/yum.repos.d是什么?

教程里的163的yum源没法使用,会报错,改用aliyun的源

vagrant用无界面启动就好,用vagrant up启动有可能用默认的网络设置覆盖我们之前的设置。
curl -o /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-7.repo

[Centos7 yum源报错] repodata/repomd.xml: [Errno 14] HTTP Error 404 - Not Found 解决方案

  1. 使用Vagrant 后发现虚拟机磁盘空间爆满,无法更新yum源

df -h 查看磁盘空间状态

这不是docker的问题,而是虚拟机的问题,虚拟机将vagrantfile所在文件夹的内容拷贝到虚拟机里面到了,导致空间爆满,血泪填坑。刚开始以为是docker的问题,网上一直说是日志什么的,清理来清理去都没用,仔细发现是虚拟机爆满了,是可以在虚拟机里看到一些windows里的文件夹,所以就是东西被拷贝进去了。
使用Vagrant 后发现虚拟机磁盘空间爆满的血泪填坑记 这篇文章讲的很好,基本就是我的心路历程。

解决Vagrant 创建的虚拟机磁盘爆满问题 有分析磁盘爆满原因

我们通过启动vagrant up命令,可以看到有这么一句:Rsyncing folder: /cygdrive/d/MyData/用户/ => /vagrant
这是要将vagrantfile所在的目录下的数据都复制到vagrant下,这就是虚拟机磁盘爆满的根本原因。
现在需要将这个目录转换成一个空目录,以后每次启动就不会同步数据到虚拟机了。

  1. docker常用命令
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
docker images 当前安装了哪些镜像(由镜像生成对应的一个个的容器就可以使用了)

docker ps -a 查看所有的容器(可能没启动)

docker ps 查看正在运行的容器

docker update redis --restart=always 设置容器开机自启动

docker restart mysql 重启容器 (这里的mysql都可以用容器Id来代替)

docker stop mysql 停掉容器

docker rm mysql 移除容器

docker logs nginx 看容器启动日志

以mysql容器为例,mysql容器其实就是一个完整的 mysql 的运行环境,也就是说可以进入容器内部,里面是一个装了mysql的linux环境。
比如进入mysql容器内部:(-it表示以交互模式 /bin/bash表示进入linux的bash控制台)
docker exec -it mysql /bin/bash
docker exec -it redis redis-cli // 进 redis client
这样就进入到了容器内部,不是在虚拟机vagrant里面了。
所以我们也就需要将端口进行映射以及一些文件挂载。

1
2
3
4
5
6
docker run -p 3306:3306 --name mysql \
-v /mydata/mysql/log:/var/log/mysql \
-v /mydata/mysql/data:/var/lib/mysql \
-v /mydata/mysql/conf:/etc/mysql \
-e MYSQL_ROOT_PASSWORD=root \
-d mysql:5.7

端口映射:比如容器的3306端口映射出来到虚拟机linux的3306端口,方便访问。
文件夹挂载:在mysql容器里面有一些文件夹里面放的是mysql的配置文件,我们把这些文件夹挂载到外部虚拟机的一些文件夹,这样以后直接改这些文件夹就能配置mysql,而不用每次都跑容器里面去配置。

exit 退出容器内部

free -m 看虚拟机内存

  1. 有时候前端数据没有响应数据库的更改

    ctrl+F5 更新浏览器缓存 联想再加Fn

这个缓存真恶心啊,有时候会怀疑自己是否哪里弄错了。
直接禁用:开发者工具 Disable cache ,有时候最好再确认下,可能失效

  1. ctrl+H 相当于hierarchy,可以看接口的实现

  2. p187 188 189 直接抄前端了

参考资料

Mysql

Mysql Workbench使用教程

Java连接mysql数据库方法

SQL

SQL 菜鸟教程

SQL 快速参考

B 树

B+树详解

什么是B-树、B树、B+树、B*树?
注:B-树是很烂的音译,会让人产生误解,实际上B-树就是B树。

树 - 红黑树(R-B Tree) 这篇还没看

Redis

Redis和同类产品的比较

Redis 教程 Runoob

gpt

COZE

COZE快速上手

COZE保姆级教程