[{"content":"Maven 单元测试笔记 1. 单元测试概述 定义：针对最小的功能单元（方法），编写测试代码对其正确性进行测试。 目的：检验软件基本组成单位的正确性。 测试人员：开发人员。 测试类型：白盒测试（清楚软件内部结构、代码逻辑，用于验证代码、逻辑正确性）。 2. JUnit 单元测试框架 2.1 JUnit 简介 最流行的 Java 测试框架之一。 提供功能方便程序进行单元测试（由第三方公司提供）。 2.2 JUnit vs main 方法测试 对比项 main 方法测试 JUnit 单元测试 代码维护 测试代码与源代码未分开，难维护 测试代码与源代码分开，便于维护 测试影响 一个方法测试失败，影响后面方法 一个测试方法执行失败，不会影响其它测试方法 自动化 无法自动化测试，无法得到测试报告 可根据需要进行自动化测试，可自动生成测试报告 2.3 使用步骤 在 pom.xml 中引入 JUnit 依赖： 1\u0026lt;dependency\u0026gt; 2 \u0026lt;groupId\u0026gt;org.junit.jupiter\u0026lt;/groupId\u0026gt; 3 \u0026lt;artifactId\u0026gt;junit-jupiter\u0026lt;/artifactId\u0026gt; 4 \u0026lt;version\u0026gt;5.9.1\u0026lt;/version\u0026gt; 5\u0026lt;/dependency\u0026gt; 在 test/java 目录下创建测试类，并编写对应的测试方法。 在测试方法上声明 @Test 注解。 运行单元测试（测试通过：绿色；测试失败：红色）。 2.4 命名规范 测试类命名：XxxxxTest（规范）。 测试方法声明：必须为 public void（规定）。 3. 断言（Assertions） 3.1 断言的作用 辅助方法，用来确定被测试的方法是否按照预期效果正常工作。 单元测试方法运行不报错不代表业务方法没问题，需通过断言检测结果是否符合预期。 3.2 常见断言方法 断言方法 描述 Assertions.assertEquals(Object exp, Object act, String msg) 检查两个值是否相等，不相等就报错。 Assertions.assertNotEquals(Object unexp, Object act, String msg) 检查两个值是否不相等，相等就报错。 Assertions.assertNull(Object act, String msg) 检查对象是否为 null，不为 null 就报错。 Assertions.assertNotNull(Object act, String msg) 检查对象是否不为 null，为 null 就报错。 Assertions.assertTrue(boolean condition, String msg) 检查条件是否为 true，不为 true 就报错。 Assertions.assertFalse(boolean condition, String msg) 检查条件是否为 false，不为 false 就报错。 Assertions.assertThrows(Class expType, Executable exec, String msg) 检查程序运行抛出的异常是否符合预期。 提示：最后一个参数 msg 表示错误提示信息，可以不指定（有对应的重载方法）。\n4. JUnit 常见注解 注解 说明 备注 @Test 修饰测试方法，使其成为可执行的测试方法 单元测试 @ParameterizedTest 参数化测试注解（单个测试运行多次，每次仅参数不同） 使用后无需 @Test @ValueSource 参数化测试的参数来源，赋予测试方法参数 需配合 @ParameterizedTest @DisplayName 指定测试类或方法显示的名称（默认为类名、方法名） - @BeforeEach 修饰实例方法，在每个测试方法执行前运行一次 初始化资源（准备工作） @AfterEach 修饰实例方法，在每个测试方法执行后运行一次 释放资源（清理工作） @BeforeAll 修饰静态方法，在所有测试方法前只执行一次 初始化资源（准备工作） @AfterAll 修饰静态方法，在所有测试方法后只执行一次 释放资源（清理工作） 4.1 常见问题解答 JUnit 测试方法能否声明形参？ 可以，使用参数化测试（@ParameterizedTest + @ValueSource）。 如何在测试前做初始化操作？ 使用 @BeforeEach 或 @BeforeAll。 如何在测试后释放资源？ 使用 @AfterEach 或 @AfterAll。 是否可以在 main 目录中编写单元测试？ 可以，但不规范（应放在 test 目录）。 5. 依赖范围（Scope） 5.1 作用 默认情况下，依赖的 jar 包可在任何地方使用。 通过 \u0026lt;scope\u0026gt;...\u0026lt;/scope\u0026gt; 设置其作用范围。 5.2 常见取值 scope 值 主程序 测试程序 打包（运行） 范例 compile（默认） Y Y Y log4j test - Y - junit provided Y Y - servlet-api runtime - Y Y jdbc 驱动 5.3 示例 1\u0026lt;dependency\u0026gt; 2 \u0026lt;groupId\u0026gt;org.junit.jupiter\u0026lt;/groupId\u0026gt; 3 \u0026lt;artifactId\u0026gt;junit-jupiter\u0026lt;/artifactId\u0026gt; 4 \u0026lt;version\u0026gt;5.9.3\u0026lt;/version\u0026gt; 5 \u0026lt;scope\u0026gt;test\u0026lt;/scope\u0026gt; 6\u0026lt;/dependency\u0026gt; 提示：JUnit 通常设置为 test 范围，因为仅在测试时需要。\n","permalink":"https://houjinghao123.github.io/posts/maven/%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95/","summary":"\u003ch1 id=\"maven-单元测试笔记\"\u003eMaven 单元测试笔记\u003c/h1\u003e\n\u003ch2 id=\"1-单元测试概述\"\u003e1. 单元测试概述\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e定义\u003c/strong\u003e：针对最小的功能单元（方法），编写测试代码对其正确性进行测试。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e目的\u003c/strong\u003e：检验软件基本组成单位的正确性。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e测试人员\u003c/strong\u003e：开发人员。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e测试类型\u003c/strong\u003e：白盒测试（清楚软件内部结构、代码逻辑，用于验证代码、逻辑正确性）。\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"2-junit-单元测试框架\"\u003e2. JUnit 单元测试框架\u003c/h2\u003e\n\u003ch3 id=\"21-junit-简介\"\u003e2.1 JUnit 简介\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e最流行的 Java 测试框架之一。\u003c/li\u003e\n\u003cli\u003e提供功能方便程序进行单元测试（由第三方公司提供）。\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"22-junit-vs-main-方法测试\"\u003e2.2 JUnit vs main 方法测试\u003c/h3\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth style=\"text-align: left\"\u003e对比项\u003c/th\u003e\n          \u003cth style=\"text-align: left\"\u003emain 方法测试\u003c/th\u003e\n          \u003cth style=\"text-align: left\"\u003eJUnit 单元测试\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003e代码维护\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e测试代码与源代码未分开，难维护\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e测试代码与源代码分开，便于维护\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003e测试影响\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e一个方法测试失败，影响后面方法\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e一个测试方法执行失败，不会影响其它测试方法\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003e自动化\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e无法自动化测试，无法得到测试报告\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e可根据需要进行自动化测试，可自动生成测试报告\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003ch3 id=\"23-使用步骤\"\u003e2.3 使用步骤\u003c/h3\u003e\n\u003col\u003e\n\u003cli\u003e在 \u003ccode\u003epom.xml\u003c/code\u003e 中引入 JUnit 依赖：\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-xml\" data-lang=\"xml\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"ln\"\u003e1\u003c/span\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"ln\"\u003e2\u003c/span\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;groupId\u0026gt;\u003c/span\u003eorg.junit.jupiter\u003cspan class=\"nt\"\u003e\u0026lt;/groupId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"ln\"\u003e3\u003c/span\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;artifactId\u0026gt;\u003c/span\u003ejunit-jupiter\u003cspan class=\"nt\"\u003e\u0026lt;/artifactId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"ln\"\u003e4\u003c/span\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;version\u0026gt;\u003c/span\u003e5.9.1\u003cspan class=\"nt\"\u003e\u0026lt;/version\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"ln\"\u003e5\u003c/span\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;/dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pr","title":"单元测试"},{"content":"SpringBoot热部署 最近在写一些项目在进行调试和修改总需要手动重启很麻烦，于是就想到了对项目进行热部署。然后就在网上搜集了一些热部署相关的内容，在这里总结一下，方便自己使用。\n使用devTools来进行热部署 ###1. Spring DevTools\nSpring DevTools是Spring团队开发的一个模块，旨在提供开发时的快速迭代和调试支持。它包含以下主要功能：\n热部署： 在代码更改后，自动重新加载应用上下文，使更改立即生效。 内嵌服务器支持： 支持内嵌服务器（如Tomcat）的快速重启。 全局配置文件热加载： 允许在不重启应用的情况下更新全局配置文件。 禁用特定缓存： 可以选择性地禁用特定的缓存，以便更快地看到代码更改的效果。 2.配置Spring DevTools 2.1初始化一个maven项目 1\u0026lt;project xmlns=\u0026#34;http://maven.apache.org/POM/4.0.0\u0026#34; xmlns:xsi=\u0026#34;http://www.w3.org/2001/XMLSchema-instance\u0026#34; 2 xsi:schemaLocation=\u0026#34;http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\u0026#34;\u0026gt; 3 \u0026lt;modelVersion\u0026gt;4.0.0\u0026lt;/modelVersion\u0026gt; 4 5 \u0026lt;groupId\u0026gt;org.example\u0026lt;/groupId\u0026gt; 6 \u0026lt;artifactId\u0026gt;HotStart1\u0026lt;/artifactId\u0026gt; 7 \u0026lt;version\u0026gt;1.0-SNAPSHOT\u0026lt;/version\u0026gt; 8 \u0026lt;packaging\u0026gt;jar\u0026lt;/packaging\u0026gt; 9 10 \u0026lt;name\u0026gt;HotStart1\u0026lt;/name\u0026gt; 11 \u0026lt;url\u0026gt;http://maven.apache.org\u0026lt;/url\u0026gt; 12 13 \u0026lt;properties\u0026gt; 14 \u0026lt;project.build.sourceEncoding\u0026gt;UTF-8\u0026lt;/project.build.sourceEncoding\u0026gt; 15 \u0026lt;/properties\u0026gt; 16 17 \u0026lt;dependencies\u0026gt; 18 \u0026lt;dependency\u0026gt; 19 \u0026lt;groupId\u0026gt;junit\u0026lt;/groupId\u0026gt; 20 \u0026lt;artifactId\u0026gt;junit\u0026lt;/artifactId\u0026gt; 21 \u0026lt;version\u0026gt;3.8.1\u0026lt;/version\u0026gt; 22 \u0026lt;scope\u0026gt;test\u0026lt;/scope\u0026gt; 23 \u0026lt;/dependency\u0026gt; 24 \u0026lt;/dependencies\u0026gt; 25\u0026lt;/project\u0026gt; 2.2添加Spring DevTools相关依赖 1 \u0026lt;!--导入热部署依赖--\u0026gt; 2 \u0026lt;dependency\u0026gt; 3 \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; 4 \u0026lt;artifactId\u0026gt;spring-boot-devtools\u0026lt;/artifactId\u0026gt; 5 \u0026lt;scope\u0026gt;runtime\u0026lt;/scope\u0026gt; 6 \u0026lt;/dependency\u0026gt; 2.3在配置文件中添加相关配置信息 1#devtools配置 2spring: 3 devtools: 4 restart: 5 #开启热部署 6 enabled: true 7 #热部署重新加载java下面类文件 8 additional-paths: src/main/java 9 #排除静态文件重新部署 10 #exclude: resources/** 2.4对idea进行相关配置 打开设置进行如下配置\n再然后打开注册表(Ctrl+Shift+Alt+/)\n3.开始测试 在项目中创建一个controller包，在创建一个类\n1@RestController 2public class indexController { 3 @GetMapping(\u0026#34;/hello\u0026#34;) 4 @ResponseBody 5 public String index(){ 6 System.out.println(\u0026#34;接口请求成功\u0026#34;); 7 int a=0; 8 return \u0026#34;hell123\u0026#34;; 9 } 10} 创建一个启动类\n1@SpringBootApplication 2public class App 3{ 4 public static void main( String[] args ) 5 { 6 SpringApplication.run(App.class); 7 } 8} 然后启动\n项目正常启动，然后我们使用浏览器进行访问\n访问正常后我们可以尝试对indexController里面的方法进行修改\n当我们修改完并且切换到浏览器界面后就会发现项目已经自动加载了。\n此时在对该接口访问就会发现内容已经发生了改变。\n","permalink":"https://houjinghao123.github.io/posts/springboot%E9%85%8D%E7%BD%AE%E7%83%AD%E9%83%A8%E7%BD%B2/","summary":"\u003ch1 id=\"springboot热部署\"\u003eSpringBoot热部署\u003c/h1\u003e\n\u003cp\u003e最近在写一些项目在进行调试和修改总需要手动重启很麻烦，于是就想到了对项目进行热部署。然后就在网上搜集了一些热部署相关的内容，在这里总结一下，方便自己使用。\u003c/p\u003e","title":"SpringBoot配置热部署的几种方式"},{"content":"SpringSecurity+JWT实现登录认证和鉴权 1、准备基本环境 使用基础版的项目环境、然后我们在redis中存储用户相关信息，将用户信息存入到token中\n导入相关依赖 1 \u0026lt;!--redis依赖--\u0026gt; 2 \u0026lt;dependency\u0026gt; 3 \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; 4 \u0026lt;artifactId\u0026gt;spring-boot-starter-data-redis\u0026lt;/artifactId\u0026gt; 5 \u0026lt;/dependency\u0026gt; 6 \u0026lt;!--fastjson依赖--\u0026gt; 7 \u0026lt;dependency\u0026gt; 8 \u0026lt;groupId\u0026gt;com.alibaba\u0026lt;/groupId\u0026gt; 9 \u0026lt;artifactId\u0026gt;fastjson\u0026lt;/artifactId\u0026gt; 10 \u0026lt;version\u0026gt;1.2.33\u0026lt;/version\u0026gt; 11 \u0026lt;/dependency\u0026gt; 12 \u0026lt;!--jwt依赖--\u0026gt; 13 \u0026lt;dependency\u0026gt; 14 \u0026lt;groupId\u0026gt;io.jsonwebtoken\u0026lt;/groupId\u0026gt; 15 \u0026lt;artifactId\u0026gt;jjwt\u0026lt;/artifactId\u0026gt; 16 \u0026lt;version\u0026gt;0.9.0\u0026lt;/version\u0026gt; 17 \u0026lt;/dependency\u0026gt; 导入共通类 统一返回结果类 1 2@JsonInclude(JsonInclude.Include.NON_NULL) 3public class ResponseResult\u0026lt;T\u0026gt; { 4 /** 5 * 状态码 6 */ 7 private Integer code; 8 /** 9 * 提示信息，如果有错误时，前端可以获取该字段进行提示 10 */ 11 private String msg; 12 /** 13 * 查询到的结果数据， 14 */ 15 private T data; 16 17 public ResponseResult(Integer code, String msg) { 18 this.code = code; 19 this.msg = msg; 20 } 21 22 public ResponseResult(Integer code, T data) { 23 this.code = code; 24 this.data = data; 25 } 26 27 public Integer getCode() { 28 return code; 29 } 30 31 public void setCode(Integer code) { 32 this.code = code; 33 } 34 35 public String getMsg() { 36 return msg; 37 } 38 39 public void setMsg(String msg) { 40 this.msg = msg; 41 } 42 43 public T getData() { 44 return data; 45 } 46 47 public void setData(T data) { 48 this.data = data; 49 } 50 51 public ResponseResult(Integer code, String msg, T data) { 52 this.code = code; 53 this.msg = msg; 54 this.data = data; 55 } 56} JWT工具类 1/** 2 * JWT工具类 3 */ 4public class JwtUtil { 5 6 //有效期为 7 public static final Long JWT_TTL = 60 * 60 *1000L;// 60 * 60 *1000 一个小时 8 //设置秘钥明文 9 public static final String JWT_KEY = \u0026#34;sangeng\u0026#34;; 10 11 public static String getUUID(){ 12 String token = UUID.randomUUID().toString().replaceAll(\u0026#34;-\u0026#34;, \u0026#34;\u0026#34;); 13 return token; 14 } 15 16 /** 17 * 生成jtw 18 * @param subject token中要存放的数据（json格式） 19 * @return 20 */ 21 public static String createJWT(String subject) { 22 JwtBuilder builder = getJwtBuilder(subject, null, getUUID());// 设置过期时间 23 return builder.compact(); 24 } 25 26 /** 27 * 生成jtw 28 * @param subject token中要存放的数据（json格式） 29 * @param ttlMillis token超时时间 30 * @return 31 */ 32 public static String createJWT(String subject, Long ttlMillis) { 33 JwtBuilder builder = getJwtBuilder(subject, ttlMillis, getUUID());// 设置过期时间 34 return builder.compact(); 35 } 36 37 private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) { 38 SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; 39 SecretKey secretKey = generalKey(); 40 long nowMillis = System.currentTimeMillis(); 41 Date now = new Date(nowMillis); 42 if(ttlMillis==null){ 43 ttlMillis=JwtUtil.JWT_TTL; 44 } 45 long expMillis = nowMillis + ttlMillis; 46 Date expDate = new Date(expMillis); 47 return Jwts.builder() 48 .setId(uuid) //唯一的ID 49 .setSubject(subject) // 主题 可以是JSON数据 50 .setIssuer(\u0026#34;sg\u0026#34;) // 签发者 51 .setIssuedAt(now) // 签发时间 52 .signWith(signatureAlgorithm, secretKey) //使用HS256对称加密算法签名, 第二个参数为秘钥 53 .setExpiration(expDate); 54 } 55 56 /** 57 * 创建token 58 * @param id 59 * @param subject 60 * @param ttlMillis 61 * @return 62 */ 63 public static String createJWT(String id, String subject, Long ttlMillis) { 64 JwtBuilder builder = getJwtBuilder(subject, ttlMillis, id);// 设置过期时间 65 return builder.compact(); 66 } 67 68 public static void main(String[] args) throws Exception { 69 String token = \u0026#34;eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJjYWM2ZDVhZi1mNjVlLTQ0MDAtYjcxMi0zYWEwOGIyOTIwYjQiLCJzdWIiOiJzZyIsImlzcyI6InNnIiwiaWF0IjoxNjM4MTA2NzEyLCJleHAiOjE2MzgxMTAzMTJ9.JVsSbkP94wuczb4QryQbAke3ysBDIL5ou8fWsbt_ebg\u0026#34;; 70 Claims claims = parseJWT(token); 71 System.out.println(claims); 72 } 73 74 /** 75 * 生成加密后的秘钥 secretKey 76 * @return 77 */ 78 public static SecretKey generalKey() { 79 byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY); 80 SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, \u0026#34;AES\u0026#34;); 81 return key; 82 } 83 84 /** 85 * 解析 86 * 87 * @param jwt 88 * @return 89 * @throws Exception 90 */ 91 public static Claims parseJWT(String jwt) throws Exception { 92 SecretKey secretKey = generalKey(); 93 return Jwts.parser() 94 .setSigningKey(secretKey) 95 .parseClaimsJws(jwt) 96 .getBody(); 97 } 98 99 100} redis工具类 1/** 2 * redis 工具类 3 */ 4@SuppressWarnings(value = { \u0026#34;unchecked\u0026#34;, \u0026#34;rawtypes\u0026#34; }) 5@Component 6public class RedisCache 7{ 8 @Autowired 9 public RedisTemplate redisTemplate; 10 11 /** 12 * 缓存基本的对象，Integer、String、实体类等 13 * 14 * @param key 缓存的键值 15 * @param value 缓存的值 16 */ 17 public \u0026lt;T\u0026gt; void setCacheObject(final String key, final T value) 18 { 19 redisTemplate.opsForValue().set(key, value); 20 } 21 22 /** 23 * 缓存基本的对象，Integer、String、实体类等 24 * 25 * @param key 缓存的键值 26 * @param value 缓存的值 27 * @param timeout 时间 28 * @param timeUnit 时间颗粒度 29 */ 30 public \u0026lt;T\u0026gt; void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit) 31 { 32 redisTemplate.opsForValue().set(key, value, timeout, timeUnit); 33 } 34 35 /** 36 * 设置有效时间 37 * 38 * @param key Redis键 39 * @param timeout 超时时间 40 * @return true=设置成功；false=设置失败 41 */ 42 public boolean expire(final String key, final long timeout) 43 { 44 return expire(key, timeout, TimeUnit.SECONDS); 45 } 46 47 /** 48 * 设置有效时间 49 * 50 * @param key Redis键 51 * @param timeout 超时时间 52 * @param unit 时间单位 53 * @return true=设置成功；false=设置失败 54 */ 55 public boolean expire(final String key, final long timeout, final TimeUnit unit) 56 { 57 return redisTemplate.expire(key, timeout, unit); 58 } 59 60 /** 61 * 获得缓存的基本对象。 62 * 63 * @param key 缓存键值 64 * @return 缓存键值对应的数据 65 */ 66 public \u0026lt;T\u0026gt; T getCacheObject(final String key) 67 { 68 ValueOperations\u0026lt;String, T\u0026gt; operation = redisTemplate.opsForValue(); 69 return operation.get(key); 70 } 71 72 /** 73 * 删除单个对象 74 * 75 * @param key 76 */ 77 public boolean deleteObject(final String key) 78 { 79 return redisTemplate.delete(key); 80 } 81 82 /** 83 * 删除集合对象 84 * 85 * @param collection 多个对象 86 * @return 87 */ 88 public long deleteObject(final Collection collection) 89 { 90 return redisTemplate.delete(collection); 91 } 92 93 /** 94 * 缓存List数据 95 * 96 * @param key 缓存的键值 97 * @param dataList 待缓存的List数据 98 * @return 缓存的对象 99 */ 100 public \u0026lt;T\u0026gt; long setCacheList(final String key, final List\u0026lt;T\u0026gt; dataList) 101 { 102 Long count = redisTemplate.opsForList().rightPushAll(key, dataList); 103 return count == null ? 0 : count; 104 } 105 106 /** 107 * 获得缓存的list对象 108 * 109 * @param key 缓存的键值 110 * @return 缓存键值对应的数据 111 */ 112 public \u0026lt;T\u0026gt; List\u0026lt;T\u0026gt; getCacheList(final String key) 113 { 114 return redisTemplate.opsForList().range(key, 0, -1); 115 } 116 117 /** 118 * 缓存Set 119 * 120 * @param key 缓存键值 121 * @param dataSet 缓存的数据 122 * @return 缓存数据的对象 123 */ 124 public \u0026lt;T\u0026gt; BoundSetOperations\u0026lt;String, T\u0026gt; setCacheSet(final String key, final Set\u0026lt;T\u0026gt; dataSet) 125 { 126 BoundSetOperations\u0026lt;String, T\u0026gt; setOperation = redisTemplate.boundSetOps(key); 127 Iterator\u0026lt;T\u0026gt; it = dataSet.iterator(); 128 while (it.hasNext()) 129 { 130 setOperation.add(it.next()); 131 } 132 return setOperation; 133 } 134 135 /** 136 * 获得缓存的set 137 * 138 * @param key 139 * @return 140 */ 141 public \u0026lt;T\u0026gt; Set\u0026lt;T\u0026gt; getCacheSet(final String key) 142 { 143 return redisTemplate.opsForSet().members(key); 144 } 145 146 /** 147 * 缓存Map 148 * 149 * @param key 150 * @param dataMap 151 */ 152 public \u0026lt;T\u0026gt; void setCacheMap(final String key, final Map\u0026lt;String, T\u0026gt; dataMap) 153 { 154 if (dataMap != null) { 155 redisTemplate.opsForHash().putAll(key, dataMap); 156 } 157 } 158 159 /** 160 * 获得缓存的Map 161 * 162 * @param key 163 * @return 164 */ 165 public \u0026lt;T\u0026gt; Map\u0026lt;String, T\u0026gt; getCacheMap(final String key) 166 { 167 return redisTemplate.opsForHash().entries(key); 168 } 169 170 /** 171 * 往Hash中存入数据 172 * 173 * @param key Redis键 174 * @param hKey Hash键 175 * @param value 值 176 */ 177 public \u0026lt;T\u0026gt; void setCacheMapValue(final String key, final String hKey, final T value) 178 { 179 redisTemplate.opsForHash().put(key, hKey, value); 180 } 181 182 /** 183 * 获取Hash中的数据 184 * 185 * @param key Redis键 186 * @param hKey Hash键 187 * @return Hash中的对象 188 */ 189 public \u0026lt;T\u0026gt; T getCacheMapValue(final String key, final String hKey) 190 { 191 HashOperations\u0026lt;String, String, T\u0026gt; opsForHash = redisTemplate.opsForHash(); 192 return opsForHash.get(key, hKey); 193 } 194 195 /** 196 * 删除Hash中的数据 197 * 198 * @param key 199 * @param hkey 200 */ 201 public void delCacheMapValue(final String key, final String hkey) 202 { 203 HashOperations hashOperations = redisTemplate.opsForHash(); 204 hashOperations.delete(key, hkey); 205 } 206 207 /** 208 * 获取多个Hash中的数据 209 * 210 * @param key Redis键 211 * @param hKeys Hash键集合 212 * @return Hash对象集合 213 */ 214 public \u0026lt;T\u0026gt; List\u0026lt;T\u0026gt; getMultiCacheMapValue(final String key, final Collection\u0026lt;Object\u0026gt; hKeys) 215 { 216 return redisTemplate.opsForHash().multiGet(key, hKeys); 217 } 218 219 /** 220 * 获得缓存的基本对象列表 221 * 222 * @param pattern 字符串前缀 223 * @return 对象列表 224 */ 225 public Collection\u0026lt;String\u0026gt; keys(final String pattern) 226 { 227 return redisTemplate.keys(pattern); 228 } 229} 2、登录认证 2.1修改登录接口 我们之前的案例已经写过登录接口相关的方法，现在我们需要修改这些方法使这些方法更规范一些\n修改UserDetailsServiceImpl实现类 1 //根据用户名查询用户信息 2 LambdaQueryWrapper\u0026lt;User\u0026gt; wrapper = new LambdaQueryWrapper\u0026lt;\u0026gt;(); 3 wrapper.eq(User::getUserName,username); 4 User user = userMapper.selectOne(wrapper); 5 //如果查询不到数据就通过抛出异常来给出提示 6 if(Objects.isNull(user)){ 7 throw new RuntimeException(\u0026#34;用户名或密码错误\u0026#34;); 8 } 9 //TODO 根据用户查询权限信息 添加到LoginUser中,后面会完善这些代码 10 11 //封装成UserDetails对象返回 12 return new LoginUser(user); 还需要修改 登录接口相关的方法 LoginController\n1@RestController 2public class LoginController { 3 4 @Autowired 5 private LoginServcie loginServcie; 6 7 @PostMapping(\u0026#34;/user/login\u0026#34;) 8 public ResponseResult login(@RequestBody User user){ 9 return loginServcie.login(user); 10 } 11} LoginServiceImpl\n1@Service 2public class LoginServiceImpl implements LoginServcie { 3 4 @Autowired 5 private AuthenticationManager authenticationManager; 6 @Autowired 7 private RedisCache redisCache; 8 9 @Override 10 public ResponseResult login(User user) { 11 12 UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUserName(),user.getPassword()); 13 Authentication authenticate = authenticationManager.authenticate(authenticationToken); 14 if(Objects.isNull(authenticate)){ 15 throw new RuntimeException(\u0026#34;用户名或密码错误\u0026#34;); 16 } 17 //使用userid生成token 18 LoginUser loginUser = (LoginUser) authenticate.getPrincipal(); 19 String userId = loginUser.getUser().getId().toString(); 20 String jwt = JwtUtil.createJWT(userId); 21 //authenticate存入redis 22 redisCache.setCacheObject(\u0026#34;login:\u0026#34;+userId,loginUser); 23 //把token响应给前端 24 HashMap\u0026lt;String,String\u0026gt; map = new HashMap\u0026lt;\u0026gt;(); 25 map.put(\u0026#34;token\u0026#34;,jwt); 26 return new ResponseResult(200,\u0026#34;登陆成功\u0026#34;,map); 27 } 28} 上面这段代码就是我们通过用户名和密码登录后生成jwt并且将用户的信息以id未键的形式存入到redis中\n在SecurityConfig中添加下面的代码 1@Configuration 2public class SecurityConfig extends WebSecurityConfigurerAdapter { 3 @Bean 4 @Override 5 public AuthenticationManager authenticationManagerBean() throws Exception { 6 return super.authenticationManagerBean(); 7 } 8} 在接口中我们通过AuthenticationManager的authenticate方法来进行用户认证,所以需要在SecurityConfig中配置把AuthenticationManager注入容器。\n2.2自定义认证过滤器 现在我们已经完成用户登录接口但是对于其他的接口访问却没有做好，需要我们自己定义一个过滤器，这个过滤器需要在请求发送时查看这些请求头是否携带了token，如果携带了token可以从请求头总取出然后经过jwt工具类获取用户id然后根据用户的id从redis中获取用户信息，存入到SecurityContextHolder，这里的SecurityContextHolder时security提供的上下文工具类，能在同一线程中传递用户信息。\n自定义认证过滤器 1@Component 2public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { 3 4 @Autowired 5 private RedisCache redisCache; 6 7 @Override 8 protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { 9 //获取token 10 String token = request.getHeader(\u0026#34;token\u0026#34;); 11 if (!StringUtils.hasText(token)) { 12 //放行 13 filterChain.doFilter(request, response); 14 return; 15 } 16 //解析token 17 String userid; 18 try { 19 Claims claims = JwtUtil.parseJWT(token); 20 userid = claims.getSubject(); 21 } catch (Exception e) { 22 e.printStackTrace(); 23 throw new RuntimeException(\u0026#34;token非法\u0026#34;); 24 } 25 //从redis中获取用户信息 26 String redisKey = \u0026#34;login:\u0026#34; + userid; 27 LoginUser loginUser = redisCache.getCacheObject(redisKey); 28 if(Objects.isNull(loginUser)){ 29 throw new RuntimeException(\u0026#34;用户未登录\u0026#34;); 30 } 31 //存入SecurityContextHolder 32 //TODO 获取权限信息封装到Authentication中 33 UsernamePasswordAuthenticationToken authenticationToken = 34 new UsernamePasswordAuthenticationToken(loginUser,null,null); 35 SecurityContextHolder.getContext().setAuthentication(authenticationToken); 36 //放行 37 filterChain.doFilter(request, response); 38 } 39} 在配置文件中使用这个认证过滤器 1 @Configuration 2public class SecurityConfig extends WebSecurityConfigurerAdapter { 3 4 5 @Bean 6 public PasswordEncoder passwordEncoder(){ 7 return new BCryptPasswordEncoder(); 8 } 9 10 11 @Autowired 12 JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter; 13 14 @Override 15 protected void configure(HttpSecurity http) throws Exception { 16 http.formLogin(); 17 http 18 //关闭csrf 19 .csrf().disable() 20 //不通过Session获取SecurityContext 21 .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) 22 .and() 23 .authorizeRequests() 24 // 对于登录接口 允许匿名访问 25 .antMatchers(\u0026#34;/user/login\u0026#34;).anonymous() 26 // 除上面外的所有请求全部需要鉴权认证 27 .anyRequest().authenticated(); 28 29 //把token校验过滤器添加到过滤器链中 30 http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); 31 } 32 33 @Bean 34 @Override 35 public AuthenticationManager authenticationManagerBean() throws Exception { 36 return super.authenticationManagerBean(); 37 } 38} 2.3进行测试 查看正常登录后是否会返回token 当我们携带token访问其他接口时\n3、授权 授权我们一般使用的是数据库中的权限信息，而在数据库中对与权限的管理也有专门的模型RBAC权限模型（Role-Based Access Control）即：基于角色的权限控制。这是目前最常被开发者使用也是相对易用、通用权限模型。\n3.1导入相关数据 1CREATE DATABASE /*!32312 IF NOT EXISTS*/`sg_security` /*!40100 DEFAULT CHARACTER SET utf8mb4 */; 2 3USE `sg_security`; 4 5/*Table structure for table `sys_menu` */ 6 7DROP TABLE IF EXISTS `sys_menu`; 8 9CREATE TABLE `sys_menu` ( 10 `id` bigint(20) NOT NULL AUTO_INCREMENT, 11 `menu_name` varchar(64) NOT NULL DEFAULT \u0026#39;NULL\u0026#39; COMMENT \u0026#39;菜单名\u0026#39;, 12 `path` varchar(200) DEFAULT NULL COMMENT \u0026#39;路由地址\u0026#39;, 13 `component` varchar(255) DEFAULT NULL COMMENT \u0026#39;组件路径\u0026#39;, 14 `visible` char(1) DEFAULT \u0026#39;0\u0026#39; COMMENT \u0026#39;菜单状态（0显示 1隐藏）\u0026#39;, 15 `status` char(1) DEFAULT \u0026#39;0\u0026#39; COMMENT \u0026#39;菜单状态（0正常 1停用）\u0026#39;, 16 `perms` varchar(100) DEFAULT NULL COMMENT \u0026#39;权限标识\u0026#39;, 17 `icon` varchar(100) DEFAULT \u0026#39;#\u0026#39; COMMENT \u0026#39;菜单图标\u0026#39;, 18 `create_by` bigint(20) DEFAULT NULL, 19 `create_time` datetime DEFAULT NULL, 20 `update_by` bigint(20) DEFAULT NULL, 21 `update_time` datetime DEFAULT NULL, 22 `del_flag` int(11) DEFAULT \u0026#39;0\u0026#39; COMMENT \u0026#39;是否删除（0未删除 1已删除）\u0026#39;, 23 `remark` varchar(500) DEFAULT NULL COMMENT \u0026#39;备注\u0026#39;, 24 PRIMARY KEY (`id`) 25) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COMMENT=\u0026#39;菜单表\u0026#39;; 26 27/*Table structure for table `sys_role` */ 28 29DROP TABLE IF EXISTS `sys_role`; 30 31CREATE TABLE `sys_role` ( 32 `id` bigint(20) NOT NULL AUTO_INCREMENT, 33 `name` varchar(128) DEFAULT NULL, 34 `role_key` varchar(100) DEFAULT NULL COMMENT \u0026#39;角色权限字符串\u0026#39;, 35 `status` char(1) DEFAULT \u0026#39;0\u0026#39; COMMENT \u0026#39;角色状态（0正常 1停用）\u0026#39;, 36 `del_flag` int(1) DEFAULT \u0026#39;0\u0026#39; COMMENT \u0026#39;del_flag\u0026#39;, 37 `create_by` bigint(200) DEFAULT NULL, 38 `create_time` datetime DEFAULT NULL, 39 `update_by` bigint(200) DEFAULT NULL, 40 `update_time` datetime DEFAULT NULL, 41 `remark` varchar(500) DEFAULT NULL COMMENT \u0026#39;备注\u0026#39;, 42 PRIMARY KEY (`id`) 43) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COMMENT=\u0026#39;角色表\u0026#39;; 44 45/*Table structure for table `sys_role_menu` */ 46 47DROP TABLE IF EXISTS `sys_role_menu`; 48 49CREATE TABLE `sys_role_menu` ( 50 `role_id` bigint(200) NOT NULL AUTO_INCREMENT COMMENT \u0026#39;角色ID\u0026#39;, 51 `menu_id` bigint(200) NOT NULL DEFAULT \u0026#39;0\u0026#39; COMMENT \u0026#39;菜单id\u0026#39;, 52 PRIMARY KEY (`role_id`,`menu_id`) 53) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4; 54 55/*Table structure for table `sys_user` */ 56 57DROP TABLE IF EXISTS `sys_user`; 58 59CREATE TABLE `sys_user` ( 60 `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT \u0026#39;主键\u0026#39;, 61 `user_name` varchar(64) NOT NULL DEFAULT \u0026#39;NULL\u0026#39; COMMENT \u0026#39;用户名\u0026#39;, 62 `nick_name` varchar(64) NOT NULL DEFAULT \u0026#39;NULL\u0026#39; COMMENT \u0026#39;昵称\u0026#39;, 63 `password` varchar(64) NOT NULL DEFAULT \u0026#39;NULL\u0026#39; COMMENT \u0026#39;密码\u0026#39;, 64 `status` char(1) DEFAULT \u0026#39;0\u0026#39; COMMENT \u0026#39;账号状态（0正常 1停用）\u0026#39;, 65 `email` varchar(64) DEFAULT NULL COMMENT \u0026#39;邮箱\u0026#39;, 66 `phonenumber` varchar(32) DEFAULT NULL COMMENT \u0026#39;手机号\u0026#39;, 67 `sex` char(1) DEFAULT NULL COMMENT \u0026#39;用户性别（0男，1女，2未知）\u0026#39;, 68 `avatar` varchar(128) DEFAULT NULL COMMENT \u0026#39;头像\u0026#39;, 69 `user_type` char(1) NOT NULL DEFAULT \u0026#39;1\u0026#39; COMMENT \u0026#39;用户类型（0管理员，1普通用户）\u0026#39;, 70 `create_by` bigint(20) DEFAULT NULL COMMENT \u0026#39;创建人的用户id\u0026#39;, 71 `create_time` datetime DEFAULT NULL COMMENT \u0026#39;创建时间\u0026#39;, 72 `update_by` bigint(20) DEFAULT NULL COMMENT \u0026#39;更新人\u0026#39;, 73 `update_time` datetime DEFAULT NULL COMMENT \u0026#39;更新时间\u0026#39;, 74 `del_flag` int(11) DEFAULT \u0026#39;0\u0026#39; COMMENT \u0026#39;删除标志（0代表未删除，1代表已删除）\u0026#39;, 75 PRIMARY KEY (`id`) 76) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COMMENT=\u0026#39;用户表\u0026#39;; 77 78/*Table structure for table `sys_user_role` */ 79 80DROP TABLE IF EXISTS `sys_user_role`; 81 82CREATE TABLE `sys_user_role` ( 83 `user_id` bigint(200) NOT NULL AUTO_INCREMENT COMMENT \u0026#39;用户id\u0026#39;, 84 `role_id` bigint(200) NOT NULL DEFAULT \u0026#39;0\u0026#39; COMMENT \u0026#39;角色id\u0026#39;, 85 PRIMARY KEY (`user_id`,`role_id`) 86) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 这是根据用户id获取他的权限\n1SELECT 2\tDISTINCT sm.`perms` 3FROM 4\tsys_user_role sur 5\tLEFT JOIN `sys_role` sr ON sur.`role_id`= sr.`id` 6\tLEFT JOIN `sys_role_menu` srm ON srm.`role_id`= sr.`id` 7\tLEFT JOIN `sys_menu` sm ON srm.`menu_id`= sm.`id` 8 9WHERE 10\tsur.`user_id`=1 11\tAND sr.`status`= 0 12\tAND sm.`status`= 0 菜单表(Menu)实体类\n1* 菜单表(Menu)实体类 2 * 3 * @author makejava 4 * @since 2021-11-24 15:30:08 5 */ 6@TableName(value=\u0026#34;sys_menu\u0026#34;) 7@Data 8@AllArgsConstructor 9@NoArgsConstructor 10@JsonInclude(JsonInclude.Include.NON_NULL) 11public class Menu implements Serializable { 12 private static final long serialVersionUID = -54979041104113736L; 13 14 @TableId 15 private Long id; 16 /** 17 * 菜单名 18 */ 19 private String menuName; 20 /** 21 * 路由地址 22 */ 23 private String path; 24 /** 25 * 组件路径 26 */ 27 private String component; 28 /** 29 * 菜单状态（0显示 1隐藏） 30 */ 31 private String visible; 32 /** 33 * 菜单状态（0正常 1停用） 34 */ 35 private String status; 36 /** 37 * 权限标识 38 */ 39 private String perms; 40 /** 41 * 菜单图标 42 */ 43 private String icon; 44 45 private Long createBy; 46 47 private Date createTime; 48 49 private Long updateBy; 50 51 private Date updateTime; 52 /** 53 * 是否删除（0未删除 1已删除） 54 */ 55 private Integer delFlag; 56 /** 57 * 备注 58 */ 59 private String remark; 60} 3.2代码实现 我们只需要根据用户id去查询到其所对应的权限信息即可。\n所以我们可以创建个MenuMapper接口，其中提供一个方法可以根据userid查询权限信息。然后在对应的xml中编写查询语句，在进行这项工作之前我们先在application.yml中配置mapperXML文件的位置\n1 redis: 2 host: localhost 3 port: 6379 4mybatis-plus: 5 mapper-locations: classpath*:/mapper/**/*.xml 测试能否正常查询用户的权限,在测试之前要导入测试相关的依赖并在UserMapper文件中编写对应的方法\n1 \u0026lt;dependency\u0026gt; 2 \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; 3 \u0026lt;artifactId\u0026gt;spring-boot-starter-test\u0026lt;/artifactId\u0026gt; 4 \u0026lt;scope\u0026gt;test\u0026lt;/scope\u0026gt; 5 \u0026lt;/dependency\u0026gt; UserMapper.java\n1@Mapper 2public interface UserMapper extends BaseMapper\u0026lt;User\u0026gt; { 3 //通过用户id查询用户权限 4 List\u0026lt;String\u0026gt; selectPermsByUserId(Long id); 5} UserMapper.xml\n1\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34; ?\u0026gt; 2\u0026lt;!DOCTYPE mapper 3 PUBLIC \u0026#34;-//mybatis.org//DTD Mapper 3.0//EN\u0026#34; 4 \u0026#34;http://mybatis.org/dtd/mybatis-3-mapper.dtd\u0026#34;\u0026gt; 5\u0026lt;mapper namespace=\u0026#34;com.sg.mapper.UserMapper\u0026#34;\u0026gt; 6 \u0026lt;select id=\u0026#34;selectPermsByUserId\u0026#34; resultType=\u0026#34;String\u0026#34;\u0026gt; 7 SELECT 8 DISTINCT sm.`perms` 9 FROM 10 sys_user_role sur 11 LEFT JOIN `sys_role` sr ON sur.`role_id`= sr.`id` 12 LEFT JOIN `sys_role_menu` srm ON srm.`role_id`= sr.`id` 13 LEFT JOIN `sys_menu` sm ON srm.`menu_id`= sm.`id` 14 15 \u0026lt;where\u0026gt; 16 sur.`user_id`=#{id} 17 AND sr.`status`= 0 18 AND sm.`status`= 0 19 \u0026lt;/where\u0026gt; 20 \u0026lt;/select\u0026gt; 21\u0026lt;/mapper\u0026gt; 在测试类中编程测试代码\n1@SpringBootTest 2public class TestObject { 3 4@Autowired 5private UserMapper userMapper; 6 @Test 7 public void Test1() { 8 9 List\u0026lt;String\u0026gt; permissions =userMapper.selectPermsByUserId(1L); 10 System.out.println(permissions); 11 } 12} 出现这个说明我们的查询方法没有问题，我们现在已经查询到了用户的权限但是我们还没有把他放入到整个spring security的授权流程中，在这里我们只需要在把LoginUser中的getAuthorities这个方法修改一下。\n1package com.sg.domain; 2 3import com.alibaba.fastjson.annotation.JSONField; 4import lombok.Data; 5import lombok.NoArgsConstructor; 6import org.springframework.security.core.GrantedAuthority; 7import org.springframework.security.core.authority.SimpleGrantedAuthority; 8import org.springframework.security.core.userdetails.UserDetails; 9 10import java.util.Collection; 11import java.util.List; 12import java.util.stream.Collectors; 13 14/** 15 * ClassName: LoginUser 16 * Package: com.sg.domain 17 * Description: 18 * 19 * @Author hjh 20 * @Create 2024/10/28 8:41 21 * @Version 1.0 22 */ 23@Data 24@NoArgsConstructor 25public class LoginUser implements UserDetails { 26 private User user; 27 private List\u0026lt;String\u0026gt; permissions; 28 29 public LoginUser(User user,List\u0026lt;String\u0026gt; permissions) { 30 this.user = user; 31 this.permissions = permissions; 32 } 33 //存储SpringSecurity所需要的权限信息的集合 34 /* 35 权限GrantedAuthority集合没法序列化，每次从redis获取那个权限集合都是空，每次调用获取权限的方法，都需要重新包装对象里面的权限集合,所以使用注解不进行序列化 36 */ 37 @JSONField(serialize = false) 38 private List\u0026lt;GrantedAuthority\u0026gt; authorities; 39 40 @Override 41 public Collection\u0026lt;? extends GrantedAuthority\u0026gt; getAuthorities() { 42 if(authorities!=null){ 43 return authorities; 44 } 45 //把permissions中字符串类型的权限信息转换成GrantedAuthority对象存入authorities中 46 authorities = permissions.stream(). 47 map(SimpleGrantedAuthority::new) 48 .collect(Collectors.toList()); 49 return authorities; 50 } 51 52 @Override 53 public String getPassword() { 54 return user.getPassword(); 55 } 56 57 @Override 58 public String getUsername() { 59 return user.getUserName(); 60 } 61 62 @Override 63 public boolean isAccountNonExpired() { 64 return true; 65 } 66 67 @Override 68 public boolean isAccountNonLocked() { 69 return true; 70 } 71 72 @Override 73 public boolean isCredentialsNonExpired() { 74 return true; 75 } 76 77 @Override 78 public boolean isEnabled() { 79 return true; 80 } 81} 这里直接用构造函数将权限信息封装到当前类中，然后我们还需要去完成之前用户UserDetailsServiceImpl类中未完成的用户权限获取方法。\n1@Component 2public class UserDetailsServiceImpl implements UserDetailsService { 3 @Autowired 4 private UserMapper userMapper; 5 6 @Override 7 public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { 8 //根据用户名查询用户信息 9 LambdaQueryWrapper\u0026lt;User\u0026gt; wrapper = new LambdaQueryWrapper\u0026lt;\u0026gt;(); 10 wrapper.eq(User::getUserName, username); 11 User user = userMapper.selectOne(wrapper); 12 //如果查询不到数据就通过抛出异常来给出提示 13 if (Objects.isNull(user)) { 14 throw new RuntimeException(\u0026#34;用户名或密码错误\u0026#34;); 15 } 16 //根据用户查询权限信息 添加到LoginUser中 17 List\u0026lt;String\u0026gt; permissionKeyList = userMapper.selectPermsByUserId(user.getId()); 18 //封装成UserDetails对象返回 19 return new LoginUser(user, permissionKeyList); 20 } 21} 这里完成后在登录校验中我们还需要把用户的权限信息存入到Authentication中，在JwtAuthenticationTokenFilter中完成。\n1@Component 2public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { 3 4 @Autowired 5 private RedisCache redisCache; 6 7 @Override 8 protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { 9 //获取token 10 String token = request.getHeader(\u0026#34;token\u0026#34;); 11 if (!StringUtils.hasText(token)) { 12 //放行 13 filterChain.doFilter(request, response); 14 return; 15 } 16 //解析token 17 String userid; 18 try { 19 Claims claims = JwtUtil.parseJWT(token); 20 userid = claims.getSubject(); 21 } catch (Exception e) { 22 e.printStackTrace(); 23 throw new RuntimeException(\u0026#34;token非法\u0026#34;); 24 } 25 //从redis中获取用户信息 26 String redisKey = \u0026#34;login:\u0026#34; + userid; 27 LoginUser loginUser = redisCache.getCacheObject(redisKey); 28 if(Objects.isNull(loginUser)){ 29 throw new RuntimeException(\u0026#34;用户未登录\u0026#34;); 30 } 31 //存入SecurityContextHolder 32 //TODO 获取权限信息封装到Authentication中 33 UsernamePasswordAuthenticationToken authenticationToken = 34 new UsernamePasswordAuthenticationToken(loginUser,null,loginUser.getAuthorities()); 35 SecurityContextHolder.getContext().setAuthentication(authenticationToken); 36 //放行 37 filterChain.doFilter(request, response); 38 } 这里我们基本完成了一大半现在还剩最后的使用注解来标识，每个方法的访问权限。\n在spring security的配置文件中添加注解，开启相关配置。\n@EnableGlobalMethodSecurity(prePostEnabled = true) 在我们需要权限效验的方法上添加@PreAuthorize()来配置权限。\n1@RestController 2public class HelloController { 3 4 @RequestMapping(\u0026#34;/hello\u0026#34;) 5 @PreAuthorize(\u0026#34;hasAuthority(\u0026#39;test\u0026#39;)\u0026#34;) 6 public String hello(){ 7 return \u0026#34;hello\u0026#34;; 8 } 9} 当我们在去访问时就需要查询是否有相应权限，有才会放行没有就会报错\n","permalink":"https://houjinghao123.github.io/posts/springsecurity+jwt/","summary":"\u003ch1 id=\"springsecurityjwt实现登录认证和鉴权\"\u003eSpringSecurity+JWT实现登录认证和鉴权\u003c/h1\u003e\n\u003ch2 id=\"1准备基本环境\"\u003e1、准备基本环境\u003c/h2\u003e\n\u003cp\u003e使用基础版的项目环境、然后我们在redis中存储用户相关信息，将用户信息存入到token中\u003c/p\u003e","title":"SpringSecurity+JWT"},{"content":"SpringSecurity学习 1、什么是SpringSecurity Spring Security 是 Spring 家族中的一个安全管理框架。相比与另外一个安全框架Shiro，它提供了更丰富的功能，社区资源也比Shiro丰富。\n​\t一般来说中大型的项目都是使用SpringSecurity 来做安全框架。小项目有Shiro的比较多，因为相比与SpringSecurity，Shiro的上手更加的简单。\n​\t一般Web应用的需要进行认证和授权。\n​\t认证：验证当前访问系统的是不是本系统的用户，并且要确认具体是哪个用户\n​\t授权：经过认证后判断当前用户是否有权限进行某个操作\n​\t而认证和授权也是SpringSecurity作为安全框架的核心功能。\nSpringSecurity的完成流程 SpringSecurity的原理其实就是一个过滤器链，内部包含了提供各种功能的过滤器。 UsernamePasswordAuthenticationFilter:负责处理我们在登陆页面填写了用户名密码后的登陆请求。入门案例的认证工作主要有它负责。\nExceptionTranslationFilter：处理过滤器链中抛出的任何AccessDeniedException和AuthenticationException 。\nFilterSecurityInterceptor：负责权限校验的过滤器。\n2、SpringSecurity快速入门 2.1 准备工作 首先创建一个Maven项目并导入依赖 1 \u0026lt;parent\u0026gt; 2 \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; 3 \u0026lt;artifactId\u0026gt;spring-boot-starter-parent\u0026lt;/artifactId\u0026gt; 4 \u0026lt;version\u0026gt;2.5.0\u0026lt;/version\u0026gt; 5 \u0026lt;/parent\u0026gt; 6 \u0026lt;dependencies\u0026gt; 7 \u0026lt;dependency\u0026gt; 8 \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; 9 \u0026lt;artifactId\u0026gt;spring-boot-starter-web\u0026lt;/artifactId\u0026gt; 10 \u0026lt;/dependency\u0026gt; 11 \u0026lt;dependency\u0026gt; 12 \u0026lt;groupId\u0026gt;org.projectlombok\u0026lt;/groupId\u0026gt; 13 \u0026lt;artifactId\u0026gt;lombok\u0026lt;/artifactId\u0026gt; 14 \u0026lt;optional\u0026gt;true\u0026lt;/optional\u0026gt; 15 \u0026lt;/dependency\u0026gt; 16 \u0026lt;/dependencies\u0026gt; 然后创建SpringBoot的启动项 1@SpringBootApplication 2public class SecurityApplication { 3 4 public static void main(String[] args) { 5 SpringApplication.run(SecurityApplication.class,args); 6 } 7} 创建一个Controller类 1 2import org.springframework.web.bind.annotation.RequestMapping; 3import org.springframework.web.bind.annotation.RestController; 4 5@RestController 6public class HelloController { 7 8 @RequestMapping(\u0026#34;/hello\u0026#34;) 9 public String hello(){ 10 return \u0026#34;hello\u0026#34;; 11 } 12} 2.2引入SpringSecurity相关依赖 1 \u0026lt;dependency\u0026gt; 2 \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; 3 \u0026lt;artifactId\u0026gt;spring-boot-starter-security\u0026lt;/artifactId\u0026gt; 4 \u0026lt;/dependency\u0026gt; 在引入该依赖后，我们在访问/hello接口时会自动跳转到security自带的登录界面。 登录的用户名默认是 user，登录的密码会在控制台输出。 3、认证 3.1 简单认证流程 Authentication接口: 它的实现类，表示当前访问系统的用户，封装了用户相关信息。\nAuthenticationManager接口：定义了认证Authentication的方法\nUserDetailsService接口：加载用户特定数据的核心接口。里面定义了一个根据用户名查询用户信息的方法。\nUserDetails接口：提供核心用户信息。通过UserDetailsService根据用户名获取处理的用户信息要封装成UserDetails对象返回。然后将这些信息封装到Authentication对象中。\n3.2自定义登录用户查询 从之前的分析我们可以知道，我们可以自定义一个UserDetailsService,让SpringSecurity使用我们的UserDetailsService。我们自己的UserDetailsService可以从数据库中查询用户名和密码。\n我们先创建我们需要的用户信息表 1CREATE TABLE `sys_user` ( 2 `id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT \u0026#39;主键\u0026#39;, 3 `user_name` VARCHAR(64) NOT NULL DEFAULT \u0026#39;NULL\u0026#39; COMMENT \u0026#39;用户名\u0026#39;, 4 `nick_name` VARCHAR(64) NOT NULL DEFAULT \u0026#39;NULL\u0026#39; COMMENT \u0026#39;昵称\u0026#39;, 5 `password` VARCHAR(64) NOT NULL DEFAULT \u0026#39;NULL\u0026#39; COMMENT \u0026#39;密码\u0026#39;, 6 `status` CHAR(1) DEFAULT \u0026#39;0\u0026#39; COMMENT \u0026#39;账号状态（0正常 1停用）\u0026#39;, 7 `email` VARCHAR(64) DEFAULT NULL COMMENT \u0026#39;邮箱\u0026#39;, 8 `phonenumber` VARCHAR(32) DEFAULT NULL COMMENT \u0026#39;手机号\u0026#39;, 9 `sex` CHAR(1) DEFAULT NULL COMMENT \u0026#39;用户性别（0男，1女，2未知）\u0026#39;, 10 `avatar` VARCHAR(128) DEFAULT NULL COMMENT \u0026#39;头像\u0026#39;, 11 `user_type` CHAR(1) NOT NULL DEFAULT \u0026#39;1\u0026#39; COMMENT \u0026#39;用户类型（0管理员，1普通用户）\u0026#39;, 12 `create_by` BIGINT(20) DEFAULT NULL COMMENT \u0026#39;创建人的用户id\u0026#39;, 13 `create_time` DATETIME DEFAULT NULL COMMENT \u0026#39;创建时间\u0026#39;, 14 `update_by` BIGINT(20) DEFAULT NULL COMMENT \u0026#39;更新人\u0026#39;, 15 `update_time` DATETIME DEFAULT NULL COMMENT \u0026#39;更新时间\u0026#39;, 16 `del_flag` INT(11) DEFAULT \u0026#39;0\u0026#39; COMMENT \u0026#39;删除标志（0代表未删除，1代表已删除）\u0026#39;, 17 PRIMARY KEY (`id`) 18) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COMMENT=\u0026#39;用户表\u0026#39; 在刚才的初始项目中引入MybatisPuls和mysql驱动的依赖 1 \u0026lt;dependency\u0026gt; 2 \u0026lt;groupId\u0026gt;com.baomidou\u0026lt;/groupId\u0026gt; 3 \u0026lt;artifactId\u0026gt;mybatis-plus-boot-starter\u0026lt;/artifactId\u0026gt; 4 \u0026lt;version\u0026gt;3.4.3\u0026lt;/version\u0026gt; 5 \u0026lt;/dependency\u0026gt; 6 \u0026lt;dependency\u0026gt; 7 \u0026lt;groupId\u0026gt;mysql\u0026lt;/groupId\u0026gt; 8 \u0026lt;artifactId\u0026gt;mysql-connector-java\u0026lt;/artifactId\u0026gt; 9 \u0026lt;/dependency\u0026gt; 在资源目录下创建application.ymal文件导入下面的配置,记得修改自己的数据库密码 1spring: 2 datasource: 3 url: jdbc:mysql://localhost:3306/sg_security?characterEncoding=utf-8\u0026amp;serverTimezone=UTC 4 username: root 5 password: root 6 driver-class-name: com.mysql.cj.jdbc.Driver 定义Mapper接口 1public interface UserMapper extends BaseMapper\u0026lt;User\u0026gt; { 2} 创建用户实体类 1@Data 2@AllArgsConstructor 3@NoArgsConstructor 4@TableName(value = \u0026#34;sys_user\u0026#34;) 5public class User implements Serializable { 6 private static final long serialVersionUID = -40356785423868312L; 7 8 /** 9 * 主键 10 */ 11 @TableId 12 private Long id; 13 /** 14 * 用户名 15 */ 16 private String userName; 17 /** 18 * 昵称 19 */ 20 private String nickName; 21 /** 22 * 密码 23 */ 24 private String password; 25 /** 26 * 账号状态（0正常 1停用） 27 */ 28 private String status; 29 /** 30 * 邮箱 31 */ 32 private String email; 33 /** 34 * 手机号 35 */ 36 private String phonenumber; 37 /** 38 * 用户性别（0男，1女，2未知） 39 */ 40 private String sex; 41 /** 42 * 头像 43 */ 44 private String avatar; 45 /** 46 * 用户类型（0管理员，1普通用户） 47 */ 48 private String userType; 49 /** 50 * 创建人的用户id 51 */ 52 private Long createBy; 53 /** 54 * 创建时间 55 */ 56 private Date createTime; 57 /** 58 * 更新人 59 */ 60 private Long updateBy; 61 /** 62 * 更新时间 63 */ 64 private Date updateTime; 65 /** 66 * 删除标志（0代表未删除，1代表已删除） 67 */ 68 private Integer delFlag; 69} 在启动类中配置mapper扫描 1@SpringBootApplication 2@MapperScans({@MapperScan(\u0026#34;com.sg.mapper\u0026#34;)}) 3public class App2 4{ 5 public static void main( String[] args ) 6 { 7 SpringApplication.run(App2.class); 8 } 9} 核心代码实现 创建一个类实现UserDetailsService接口，重写其中的方法。更加用户名从数据库中查询用户信息\n1@Service 2public class UserDetailsServiceImpl implements UserDetailsService { 3 4 @Autowired 5 private UserMapper userMapper; 6 7 @Override 8 public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { 9 //根据用户名查询用户信息 10 LambdaQueryWrapper\u0026lt;User\u0026gt; wrapper = new LambdaQueryWrapper\u0026lt;\u0026gt;(); 11 wrapper.eq(User::getUserName,username); 12 User user = userMapper.selectOne(wrapper); 13 //如果查询不到数据就通过抛出异常来给出提示 14 if(Objects.isNull(user)){ 15 throw new RuntimeException(\u0026#34;用户名或密码错误\u0026#34;); 16 } 17 //TODO 根据用户查询权限信息 添加到LoginUser中 18 19 //封装成UserDetails对象返回 20 return new LoginUser(user); 21 } 22} 因为UserDetailsService方法的返回值是UserDetails类型，所以需要定义一个类，实现该接口，把用户信息封装在其中。\n1@Data 2@AllArgsConstructor 3@NoArgsConstructor 4public class LoginUser implements UserDetails { 5 private User user; 6 7 @Override 8 public Collection\u0026lt;? extends GrantedAuthority\u0026gt; getAuthorities() { 9 return null; 10 } 11 12 @Override 13 public String getPassword() { 14 return user.getPassword(); 15 } 16 17 @Override 18 public String getUsername() { 19 return user.getUserName(); 20 } 21 22 @Override 23 public boolean isAccountNonExpired() { 24 return true; 25 } 26 27 @Override 28 public boolean isAccountNonLocked() { 29 return true; 30 } 31 32 @Override 33 public boolean isCredentialsNonExpired() { 34 return true; 35 } 36 37 @Override 38 public boolean isEnabled() { 39 return true; 40 } 41} 然后我们运行项目使用数据库中的用户名和密码登录就会发现无法登录，这是因为spring security自带的有密码加密 我们想要在这次测试中不使用密码就需要在数据库中的密码前面加上{noop}这个表示不进行密码加密，\n但是在日后的开发中 我们的用户创建个人账户时肯定是需要加密的，这个加密方式的选择就需要在WebSecurityConfigurerAdapter中进行编写了。\n1@Configuration 2public class SecurityConfig extends WebSecurityConfigurerAdapter { 3 4 5 @Bean 6 public PasswordEncoder passwordEncoder(){ 7 return new BCryptPasswordEncoder(); 8 } 9 10} 这里我们使用的是BCryptPasswordEncoder方法进行的加密吗，但是需要注意这里的加密的意思是把密码加密后与数据库中已经加密的密码进行比较 因此我们需要把加密后的密码存入数据库，我们可以在测试方法中调用BCryptPasswordEncoder来获取正常密码加密后的密码。\n1@SpringBootTest 2public class AppTest { 3 @Test 4 public void Test2(){ 5 BCryptPasswordEncoder bc=new BCryptPasswordEncoder(); 6 String encode = bc.encode(\u0026#34;1234\u0026#34;); 7 System.out.println(encode); 8 System.out.println(bc.matches(\u0026#34;1234\u0026#34;, \u0026#34;$2a$10$dtsT5D5X5YjHKXYCNYH.KeEIbgp.EOpOL6ILDVHAp82SHk1oRCjla\u0026#34;)); 9 } 10 11} 在进行登录测试就可以正常登录了\n3.3自定义登录接口 我们上面完成了自定义用户信息登录的测试，但是在前后端分离的项目中我们是需要一个登录的接口，具体的登录界面是由前端实现的所以我们需要自定义登录接口 首先就是在controller中定义一个相关接口\n1@RestController 2public class LoginController { 3 4 @Autowired 5 private LoginServcie loginServcie; 6 7 @PostMapping(\u0026#34;/user/login\u0026#34;) 8 public String login(@RequestBody User user){ 9 return loginServcie.login(user); 10 } 11} 1public interface LoginServcie { 2 String login(User user); 3} 1@Component 2public class LoginServcieImpl implements LoginServcie { 3 @Override 4 public String login(User user) { 5 return \u0026#34;登录成功\u0026#34;; 6 } 7} 在定义完该接后我们还需要对securityConfig进行一些相关设置使/user/login接口不需要登录\n1 */ 2@Configuration 3public class securityConfig extends WebSecurityConfigurerAdapter { 4 @Autowired 5 private AuthenticationSuccessHandler successHandler; 6 7 //设置密码编码格式 8 @Bean 9 public PasswordEncoder passwordEncoder(){ 10 return new BCryptPasswordEncoder(); 11 } 12 13 @Override 14 protected void configure(HttpSecurity http) throws Exception { 15 //启用默认的登录表单 16 http.formLogin(); 17 http 18 //关闭csrf 19 .csrf().disable() 20 //不通过Session获取SecurityContext 21 .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) 22 .and() 23 .authorizeRequests() 24 // 对于登录接口 允许匿名访问 25 .antMatchers(\u0026#34;/user/login\u0026#34;).anonymous() 26 // 除上面外的所有请求全部需要鉴权认证 27 .anyRequest().authenticated(); 28 29 30 } 31} 我们这里定义的登录接口是使用post方法并且需要携带参数所以就使用postman进行测试\n这是我们正常请求登录时 但是请求其他接口时都需要进行登录验证\n","permalink":"https://houjinghao123.github.io/posts/springsecurity%E5%AD%A6%E4%B9%A0/","summary":"\u003ch1 id=\"springsecurity学习\"\u003eSpringSecurity学习\u003c/h1\u003e\n\u003ch2 id=\"1什么是springsecurity\"\u003e1、什么是SpringSecurity\u003c/h2\u003e\n\u003cp\u003e\u003cstrong\u003eSpring Security\u003c/strong\u003e 是 Spring 家族中的一个安全管理框架。相比与另外一个安全框架\u003cstrong\u003eShiro\u003c/strong\u003e，它提供了更丰富的功能，社区资源也比Shiro丰富。\u003c/p\u003e","title":"SpringSecurity学习"},{"content":"DockerFile的基本使用 为什么要使用 之前的方式去部署应用还是有点繁琐，因为之前使用的镜像并不能很好的符合我们自己的需求。\n使用dockerFile可以自定义镜像\n快速入门 构建一个最简单的HelloWorld镜像\n创建一个文件 1sudo mkdir images 在文件中编写相关指令 FROM ubuntu:22.04 CMD [\u0026#34;echo\u0026#34;,\u0026#34;helloworld\u0026#34;] 编译镜像 1docker build -t hello:1.0 -f HelloWorld . 容器运行测试 1docker run hello:1.0 指令学习 FROM 作用\n定义基础镜像\n用法\nFROM 镜像名:标签名\nFROM ubuntu：22.04\n作用时机\n构建镜像的时候\nCMD 作用\n用来定义容器运行时的默认命令。可以在使用docker run的时候覆盖掉CMD中定义的命令\n用法\nCMD [\u0026ldquo;命令1\u0026rdquo;，“参数1”,\u0026ldquo;参数2\u0026rdquo;]\nCMD echo $HOME\n注意\n一个DockerFile中写多个CMD的时候只有最后一个CMD会去作用\nENV 作用\n用来定义环境变量\n用法\nENV 变量名=\u0026ldquo;变量值\u0026rdquo;\n作用时机\n构建镜像的时候\n脚本\n1#第一种使用参数的方法 2FROM ubuntu:22.04 3ENV CONTENT hello 4CMD [\u0026#34;sh\u0026#34;,\u0026#34;-c\u0026#34;,\u0026#34;echo $CONTENT\u0026#34;] 5#第二种使用参数的方法 6FROM ubuntu:22.04 7ENV CONTENT=\u0026#34;helloword\u0026#34; 8CMD echo $CONTENT RUN 作用\n它是用来定义构建过程中要执行的命令的\n用法\nRUN 命令\n例如：RUN echo sg\n作用时机\n构建镜像的时候\nWORKDIR 作用\n用于设置当前工作的目录，如果该目录不存在会自动创建。\n用法\nWORKDIR 目录\nWORKDIR /root/app\n作用时机\n构建镜像的时候\n案例：\n定义一个CONTENT变量，默认值为hellodocker，在镜像的/app目录下创建一个sg目录，在其中创建一个content.txt文件，文件的内容为CONTENT变量的值。容器启动时打印content.txt的内容\n在Linux环境下执行这些命令\n1export CONTENT=hellodocker 2mkdir /app 3cd /app 4mkdir sg 5cd sg 6echo $CONTINT\u0026gt;content.txt 7cat content.txt 使用dockerfile构建响应的容器\n1FROM ubuntu:22.04 2ENV CONTINT=\u0026#34;hellodocker\u0026#34; 3WORKDIR /app/sg 4RUN echo $CONTINT\u0026gt;content.txt 5CMD [\u0026#34;cat\u0026#34;,\u0026#34;content.txt\u0026#34;] 构建 1docker build -t test:2.0 -f Test1 . 运行 1docekr run test:2.0 ADD 作用\n把构建上下文中的文件或者网络文件添加到镜像中，如果文件是一个压缩包会自动解压，如果是网络中的文件并不会自动解压\n用法\nADD 原路径 目标路径\nADD sg-blog-vue.tar.gz .\n作用时机\n构建镜像的时候\nEXPOSE 作用\n暴露需要发布的端口，让镜像使用者知道应该发布哪些端口\n用法\nEXPOSE 端口号1 端口号2 \u0026hellip;.\nEXPOSE 80 8080\n作用时机\n构建镜像的时候\nCOPY 作用\n从构建上下文中复制内容到镜像中\n用法\nCOPY 原路径 目标路径\nCOPY sg-blog-vue.tar.gz .\nENTRYPOINT 作用\n用来定义容器运行时的默认命令。docker run的时候无法覆盖ENTRYPOINT里的内容。\n作用时机\n运行容器的时候\n用法\nENTRYPOINT [\u0026ldquo;命令1\u0026rdquo;,\u0026ldquo;参数1\u0026rdquo;,\u0026ldquo;参数2\u0026rdquo;]\nENTRYPOINT [\u0026ldquo;echo\u0026rdquo;,\u0026ldquo;helloworld\u0026rdquo;]\n解释\n与CMD结合，把不希望被覆盖的命令用ENTRYPOINNT定义，其他部分用CMD定义，这样启动容器的时候只会覆盖CMD的部分\n例\nENTRYPOINT [\u0026ldquo;java\u0026rdquo;,\u0026quot;-jar\u0026quot;] CMD [\u0026ldquo;app.jar\u0026rdquo;]\n上述写法的app.jar就是可以被覆盖的，而java -jar就是不能被覆盖\n上传镜像 注册账号\n上传镜像需要有dockerhub的账号\n本地登录验证\ndocker login\n然后输入用户名和密码\n给镜像打标签\ndocker tag username/镜像名:tag #username是DockerHub的用户名\n推送镜像\ndocker push username/镜像名:tag\nDockerCompose的基本使用 概述 为什么要学习 我们在部署运行一个项目的时候可能需要运行多个容器。 还要去处理这些容器的网络，数据卷等。 如果用docker命令一个个去处理还是不方便。 DockerCompose就是去解决这个问题的。\nDockerCompose是用来定义和运行一个或多个容器(通常都是多个)运行和应用的工具\n可以使用 YAML 文件来配置应用程序的服务。然后，使用单个命令，您可以根据配置创建并启动所有服务。\n检查是否安装DockerCompose 使用docker compose version 可以查看版本\n快速入门 编写docker-compose.yaml模板文件 运行 docker compose up -d 停止 docker compose down 默认运行的文件时docker-compose，当使用docker compose up时会自动寻找docker-compose.yaml这个文件，如果更改文件名需要添加新的参数进行指定文件位置\n1docker-compose -f docker-compose.override.yml up 模板文件中的元素 command 覆盖容器启动后的默认指令 environment 指定环境变量，相当于run的-e选项 image 用来指定镜像 networks 指定网络，相当于run的\u0026ndash;network ports 用来指定要发布的端口，相当于run的-p volumes 用来指定数据卷，相当于-v restart 用来指定重启策略，相当于\u0026ndash;restart 案例\n在之前自定义镜像版本的基础上，用DockerCompose来部署\n1services: 2 # mysql 3 blog_mysql: 4 image: mysql:5.7 5 volumes: 6 - mysql_data:/var/lib/mysql 7 ports: 8 - 3306:3306 9 environment: 10 MYSQL_ROOT_PASSWORD: ******* 11 restart: always 12 networks: 13 - blog_net 14 # redis 15 blog_redis: 16 image: redis:7.0 17 volumes: 18 - redis_data:/data 19 ports: 20 - 6379:6379 21 restart: always 22 command: [\u0026#39;redis-server\u0026#39;,\u0026#39;--appendonly\u0026#39;,\u0026#39;yes\u0026#39;] 23 networks: 24 - blog_net 25 # 后端服务 26 sg_blog: 27 image: sg_blog:01 28 ports: 29 - 7777:7777 30 networks: 31 - blog_net 32 restart: always 33 # 前端服务 34 sg_blog_vue: 35 image: sg_blog_vue:01 36 ports: 37 - 80:80 38 restart: always 39 40networks: 41 blog_net: 42 43volumes: 44 mysql_data: 45 #使用已经存在的数据卷 46 external: true 47 redis_data: 48 external: true ","permalink":"https://houjinghao123.github.io/posts/dockerfile%E5%9F%BA%E6%9C%AC%E4%BD%BF%E7%94%A8/","summary":"\u003ch1 id=\"dockerfile的基本使用\"\u003eDockerFile的基本使用\u003c/h1\u003e\n\u003ch2 id=\"为什么要使用\"\u003e为什么要使用\u003c/h2\u003e\n\u003cp\u003e之前的方式去部署应用还是有点繁琐，因为之前使用的镜像并不能很好的符合我们自己的需求。\u003c/p\u003e","title":"DockerFile和DockerCompose基本使用"},{"content":"使用三更的博客项目进行部署测试 安装Mysql 安装前明确需求\n使用的镜像是mysql5.7\n后台运行 -d\n数据需要持久化存储 -v 使用别名mysql_data\n开放3306端口 -p\n设置root的密码 -e\n停止后自动重启 \u0026ndash;restart\n加入一个网络 \u0026ndash;network\n容器命名为blog_mysql\n1docker run -d \\ 2-v mysql_data:/var/lib/mysql \\ 3-p 3306:3306 \\ 4-e MYSQL_ROOT_PASSWORD=PaiTcgDK0VUZCM \\ 5--restart always \\ 6--network blog_net \\ 7--name blog_mysql \\ 8mysql:5.7 安装Redis 安装前明确需求\n版本 redis 7 后台运行 开启aof持久化 数据需要持久化存储 redis-server \u0026ndash;appendonly yes 开放端口 停止后自动重启 容器命名为blog_reids 加入网络 blog_net 1docker run -d \\ 2-v reids_data:/data \\ 3-p 6379:6379 \\ 4--restart always \\ 5--name blog_redis \\ 6--network blog_net \\ 7redis:7.0 redis-server --appendonly yes 安装OpenJDK 运行jar包 需求\nopenjdk 8u111版本 后台运行 -d 端口为7777 -p 使用数据卷用于存放jar包 -v 退出后重新运行 \u0026ndash;restart 加入blog_net 网络 \u0026ndash;network 容器名字是 sb_blog \u0026ndash;name 1docker run \\ 2-p 7777:7777 \\ 3-d \\ 4-v /usr/blog:/usr/blog \\ 5--restart always \\ 6--network blog_net \\ 7--name sg_blog \\ 8java:openjdk-8u111 java -jar /usr/blog/sangeng-blog-1.0-SNAPSHOT.jar 使用上面的方式启动会报错，因为没有设置数据库和redis的连接方式\n1docker run \\ 2-p 7777:7777 \\ 3-d \\ 4--network blog_net \\ 5-v /usr/blog:/usr/blog \\ 6--restart always \\ 7--name sg_blog \\ 8java:openjdk-8u111 java -jar /usr/blog/sangeng-blog-1.0-SNAPSHOT.jar \\ 9\u0026#34;--spring.datasource.url=jdbc:mysql://blog_mysql:3306/sg_blog?characterEncoding=utf-8\u0026amp;serverTimezone=Asia/Shanghai\u0026#34; \\ 10\u0026#34;--spring.datasource.username=root\u0026#34; \\ 11\u0026#34;--spring.datasource.password=1234\u0026#34; \\ 12\u0026#34;--spring.redis.host=blog_redis\u0026#34; \\ 使用nginx部署前端项目 分析需求\n后台运行 -d 开放端口 -p 使用数据卷的方式同步静态资源 -v 停止后自动重启 \u0026ndash;restart 容器命名sg_blog_vue 1docker run -d \\ 2-p 80:80 \\ 3-v /usr/blog/sg-blog-vue/dist:/usr/share/nginx/html \\ 4--restart always \\ 5--name sg_blog_vue \\ 6nginx:1.21.5 ","permalink":"https://houjinghao123.github.io/posts/docker%E5%9C%A8ubuntu%E4%B8%8A%E5%AE%89%E8%A3%85mysql%E5%92%8Credis/","summary":"\u003ch1 id=\"使用三更的博客项目进行部署测试\"\u003e使用三更的博客项目进行部署测试\u003c/h1\u003e\n\u003ch2 id=\"安装mysql\"\u003e安装Mysql\u003c/h2\u003e\n\u003cp\u003e安装前明确需求\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e使用的镜像是mysql5.7\u003c/p\u003e","title":"Docker在Ubuntu上部署项目案例"},{"content":"镜像 如何查看镜像的详细信息 方法\n1docker image inspect [OPTIONS] IMAGE [IMAGE...] 2docker image inspect 镜像id 容器 查看容器内进程 方法\n1docker top CONTAINER [ps OPTIONS] 2docker top nginx_test 查看容器详细信息 方法\n1docker inspect [OPTIONS] NAME|ID [NAME|ID...] 数据卷 作用 实现宿主机和容器之间文件或者文件夹的同步。一般用来解决容器数据的持久化或者是宿主机和容器间的数据共享。\n数据卷相关操作 设置数据卷 绝对路径 1docker run -v 宿主机目录:容器目录[:读写权限] 镜像名 别名 我们可以直接使用数据卷的别名来作为宿主机的目录来使用。如果这个别名的数据卷还不存在的话，Docker会自动帮我们创建对应的数据卷。\n1docker run -v 数据卷别名:容器目录[:读写权限] 镜像名 列出所有的数据卷 1docker volume ls 查看数据卷详情 1docker volume inspect 数据卷名 创建数据卷 1docker volume create 数据卷名 2# 查看数据卷在宿主机的位置 3docker volume inspect 删除数据卷 1docker volume rm 数据卷名 网络(网桥) 作用 虽然默认情况下容器和容器可以进行网络通信。但是每次创建容器都是Docker给容器分配的IP地址这让我们使用起来不太方便。\n这些情况我们都可以创建自定义网络来解决这些问题。把需要互相连通的容器加入到同一个网络，这样容器和容器之间就可以通过容器名来代替ip地址进行互相访问。\n网络相关操作 创建网络 1docker network create 网络名 2# 例 3docker network create blog_net 列出网络 1docker network ls 加入网络 创建容器时加入\n我们可以在容器创建时使用\u0026ndash;network选项让容器创建时就加入对应的网络。\n1docker run --network 网络名 镜像名 容器创建后加入 1# 如果容器已经创建了想加入网络可以使用docker network connect命令。 2docker network connect [选项] 网络名 容器名或容器id 查看网络详情 可以查看都有那些容器加入该网络\n1docker network inspect 网络名或者网络id 移除网络 1docker network rm 网络名或网络id ","permalink":"https://houjinghao123.github.io/posts/docker-%E9%95%9C%E5%83%8F_%E5%AE%B9%E5%99%A8_%E6%95%B0%E6%8D%AE%E5%8D%B7/","summary":"\u003ch1 id=\"镜像\"\u003e镜像\u003c/h1\u003e\n\u003ch2 id=\"如何查看镜像的详细信息\"\u003e如何查看镜像的详细信息\u003c/h2\u003e\n\u003cp\u003e\u003cstrong\u003e方法\u003c/strong\u003e\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"ln\"\u003e1\u003c/span\u003e\u003cspan class=\"cl\"\u003edocker image inspect \u003cspan class=\"o\"\u003e[\u003c/span\u003eOPTIONS\u003cspan class=\"o\"\u003e]\u003c/span\u003e IMAGE \u003cspan class=\"o\"\u003e[\u003c/span\u003eIMAGE...\u003cspan class=\"o\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"ln\"\u003e2\u003c/span\u003e\u003cspan class=\"cl\"\u003edocker image inspect 镜像id\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pr","title":"Docker 数据卷 网桥"},{"content":"#Run命令详解\n-p 端口对外发布（端口映射） 解释 为了使外部网络能够访问到容器\n将容器端口发布到宿主机端口，就可以通过宿主机的端口访问容器内的端口了。\n使用方法 1docker run -p 宿主机端口:容器端口 镜像名 #发布一个端口 2docker run -p 宿主机端口1:容器端口1 -p 宿主机端口2:容器端口2 镜像名 #发布多个端口 可以通过DockerHub或者DockerFile来确定不同容器发布的端口\n练习 后台运行nginx容器，让我们能通过宿主机的端口去测试容器中的nginx。\n1 docker run -d -p 80:80 nginx -v 数据卷 解释 当需要把容器中的数据持久化保存或者宿主机和容器间的数据共享。\n将宿主机目录或文件挂载到容器中，实现宿主机和容器之间的数据共享和持久化。\n用法 1docker run -v 宿主机目录:容器目录[:读写权限] 镜像名 读写权限 在使用 -v 选项时，可以添加 :ro 或 :rw 来指定容器对挂载的目录或文件是只读（read-only）还是可读写（read-write）。如果不指定默认是rw。\n如何知道挂载位置 可以通过DockerHub或者DockerFile来确定不同容器挂载的位置\n练习 后台运行nginx容器，发布一个最简单的静态页面到Nginx，让我们能够通过浏览器去访问这个静态页面 1docker run -d -p 81:80 -v /home/cp_test/index.html:/usr/share/nginx/html/index.html nginx -e 设置环境变量 解释 容器中某些变量不能直接写死，需要让使用者在创建容器的时候指定，这种情况镜像中一般是定义环境变量来使用。 例如mysql容器的root密码。 遇到这种镜像创建的容器我就可以使用-e来设置环境变量的值。\n用法 1docker run -e 变量名=变量值 镜像名 如何知道那些环境变量可以被设置 可以通过DockerHub或者DockerFile。\n练习 后台运行一个mysql5.7的容器, 要求容器中的mysql可以被外部连接 mysql容器的数据需要持久化存储，不能因为容器被删除而丢失。 mysql的root用户的密码设置为 1234 1docker run -d -p 3306:3306 -v /home/mysql5.7_data/:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=1234 mysql:5.7 尝试删除容器在重新创建容器后看数据是否存在\n1docker rm -f 9cffce295b0f 删除连接后会显示连接错误\n重新创建容器\n1docker run -d -p 3306:3306 -v /home/mysql5.7_data/:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=1234 mysql: 25.7 可以正常显示\n\u0026ndash;name 容器命名 解释 需要帮助我们更快的识别出来容器的作用。之前的容器命名都是随机命名的。当在进行删除停止容器等一些操作可以直接使用容器名字而不是容器id\n用法 1docker run --name 需要定义的容器名 镜像名 练习 后台运行nginx容器\n发布一个最简单的静态页面到Nginx，让我们能够通过浏览器去访问这个静态页面\n容器命名为 nginx-test\n1docker run -d --name nginx-test -p 82:80 -v /home/cp_test/index.html:/usr/share/nginx/html/index.html nginx \u0026ndash;restart 容器退出后的重启策略 解释 保证容器退出后也可以自动重启\n用法 1docker run --restart 重启策略 镜像名 重启策略\nno：容器退出时不会自动重启。\nalways：容器总是在退出后自动重启。\non-failure[:max-retries]：容器仅在非正常退出时重启，可以指定最大重试次数。\nunless-stopped：容器会在退出后自动重启，除非手动停止了容器。\n默认策略为no\n练习 后台运行nginx容器\n容器命名为 nginx-test\n发布一个最简单的静态页面到Nginx，让我们能够通过浏览器去访问这个静态页面\n该容器在退出后可以自动重启\n1docker run -d --name nginx-test -p 83:80 -v /home/cp_test/index.html:/usr/share/nginx/html/index.html --restart unless-stopped nginx ","permalink":"https://houjinghao123.github.io/posts/docker-run%E5%91%BD%E4%BB%A4%E8%AF%A6%E8%A7%A3/","summary":"\u003cp\u003e#Run命令详解\u003c/p\u003e\n\u003ch2 id=\"-p-端口对外发布端口映射\"\u003e-p 端口对外发布（端口映射）\u003c/h2\u003e\n\u003ch3 id=\"解释\"\u003e解释\u003c/h3\u003e\n\u003cp\u003e为了使外部网络能够访问到容器\u003c/p\u003e","title":"Docker Run命令详解"},{"content":"镜像和容器相关命令 搜索镜像 使用Docker自带的搜索命令可能会因为网络问题搜索失败 1docker search contains name:vesion 我们可以使用docker search去搜索镜像，也可以去Docker-Hub找自己需要的镜像。(推荐) 下载镜像 1docker pull [镜像仓库地址/]镜像名[:标签] 列出镜像信息 docker images [选项] 相关操作 列出镜像的镜像id docker images -q 列出所有镜像的的镜像id docker images -aq 或者 docker images -a -q 列出所有镜像名为mysql的镜像id docker images -aq \u0026ndash;filter=reference=mysql 列出容器信息 docker ps [选项]默认显示正在运行的容器信息\n相关操作\n列出当前正在运行的容器 docker ps 列出所有容器，无论是否在运行 docker ps -a 列出所有退出状态的容器 docker ps -f status=exited 列出所有退出状态的容器id docker ps -f status=exited -q 创建并运行容器 docker run [OPTIONS] IMAGE [COMMAND] [ARG\u0026hellip;] docker stop[ids] 停止容器的运行 容器的运行方式 默认的运行方式 docker run nginx:latest 后台运行 docker run -d nginx:latest 交互式运行 docker run -it nginx:latest bash 删除容器 docker rm [选项] [容器ID或容器名\u0026hellip;] 相关操作 删除hello-world的容器 docker rm 2d1ec2bba545 尝试删除一个运行的nginx容器 docker rm -f fa8075110f21 尝试删除所有容器 docker rm -f $(docker ps -aq ) 尝试删除所有非运行状态的容器 docker ps -f status=exited -q 或者 docker rm $(docker ps -f status=exited -q) 进入容器执行命令 docker exec [选项] 容器ID或容器名 命令 [参数\u0026hellip;]\n后台运行一个nginx镜像的容器，然后尝试以交互式的方式进入该容器内部执行 curl 指令测试nginx是否启动成功\ndocker exec -it 容器id bash 这个命令是进入正在运行的容器， -it参数是可交互命令行，bash是所使用的命令行方式。\n-i 以交互模式运行容器，通常与 -t 同时使用\n-t 启动容器后，为容器分配一个命令行，通常与 -i 同时使用\n查看容器日志 docker logs[选项]容器ID或容器名\n查看容器日志并且是持续输出 ​\tdocker logs -f 容器id\n查看容器的最近20条日志 ​\tdocker logs -n 20 容器id\n容器文件拷贝 我们可以使用 docker cp 命令来实现容器和宿主机之间 文件和目录的相互拷贝\n命令 ​\t把容器中的文件拷贝到宿主机中\n​\tdocker cp [OPTIONS] CONTAINER:SRC_PATH DEST_PATH\n注意开放权限\n​\t把宿主机的文件拷贝到容器中\n​\tdocker cp [OPTIONS] SRC_PATH CONTAINER:DEST_PATH\n停止容器 命令\ndocker stop [选项] [容器ID或容器名\u0026hellip;]\n运行容器 命令 ​\tdocker start [选项] 容器ID或容器名\n","permalink":"https://houjinghao123.github.io/posts/docker%E5%91%BD%E4%BB%A4/","summary":"\u003ch2 id=\"镜像和容器相关命令\"\u003e镜像和容器相关命令\u003c/h2\u003e\n\u003ch3 id=\"搜索镜像\"\u003e搜索镜像\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e使用Docker自带的搜索命令可能会因为网络问题搜索失败\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"ln\"\u003e1\u003c/span\u003e\u003cspan class=\"cl\"\u003edocker search contains name:vesion\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pr","title":"Docker命令"},{"content":"概述 什么是docker Docker是一个应用容器引擎。 一个容器可以理解成是一个轻量级的虚拟机。但是又和虚拟机有些不同。\ndocker有着比虚拟机更少的抽象层 docker利用的是宿主机的内核，而不需要加载操作系统OS内核 为什么要使用docker 解决应用部署的不便，让应用部署更加简单方便 避免环境不同导致问题 降低微服务阶段的学习成本，减少安装时间，聚焦核心 为理解实际开发中的打包发布流程打基础 Docker核心概念 镜像：相当于一个容器的模板 容器：可以理解成是一个轻量级的虚拟机 镜像仓库：存放镜像的仓库 docker的安装 准备安装环境 1#安装前先卸载操作系统默认安装的docker， 2sudo apt-get remove docker docker-engine docker.io containerd runc 3 4#安装必要支持 5sudo apt install apt-transport-https ca-certificates curl software-properties-common gnupg lsb-release 开始安装 1#添加 Docker 官方 GPG key （可能国内现在访问会存在问题） 2curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg 3 4# 阿里源（推荐使用阿里的gpg KEY） 5curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg 6 7 8 9#添加 apt 源: 10#Docker官方源 11echo \u0026#34;deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable\u0026#34; | sudo tee /etc/apt/sources.list.d/docker.list \u0026gt; /dev/null 12 13 14#阿里apt源 15echo \u0026#34;deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://mirrors.aliyun.com/docker-ce/linux/ubuntu $(lsb_release -cs) stable\u0026#34; | sudo tee /etc/apt/sources.list.d/docker.list \u0026gt; /dev/null 16 17 18#更新源 19sudo apt update 20sudo apt-get update 1#安装最新版本的Docker 2sudo apt install docker-ce docker-ce-cli containerd.io 3#等待安装完成 4 5#查看Docker版本 6sudo docker version 7 8#查看Docker运行状态 9sudo systemctl status docker 安装Docker 命令补全工具 1sudo apt-get install bash-completion 2 3sudo curl -L https://raw.githubusercontent.com/docker/docker-ce/master/components/cli/contrib/completion/bash/docker -o /etc/bash_completion.d/docker.sh 4 5source /etc/bash_completion.d/docker.sh 添加docker用户组 用户在不添加sudo命令来直接使用docker\n1sudo groupadd docker 2 3# 将当前用户添加到用户组 4sudo usermod -aG docker $USER 5 6# 使权限生效 7newgrp docker 8 9# 查看所有容器 10docker ps -a 11 12# 最后一步 更新.bashrc文件 13sudo nvim ~/.bashrc 在.bashrc文件最后一样添加\n1groupadd -f docker 如果没有此行命令每次打开新的终端都必须先执行一次 “newgrp docker” 命令\n配置镜像 在 /etc/docker/daemon.json添加可以用的镜像(10.5可用)\n1sudo mkdir -p /etc/docker 2sudo tee /etc/docker/daemon.json \u0026lt;\u0026lt;-\u0026#39;EOF\u0026#39; 3{ 4 \u0026#34;registry-mirrors\u0026#34;: [ 5 \u0026#34;https://cf-workers-docker-io-7s3.pages.dev\u0026#34;, 6 \u0026#34;https://docker.aityp.com/\u0026#34;, 7 \u0026#34;https://dockerproxy.cn\u0026#34; 8 ] 9} 10EOF 11sudo systemctl daemon-reload 12sudo systemctl restart docker 重启docker\n1sudo systemctl daemon-reload 2sudo systemctl restart docker 测试是否安装成功\n1docker run hello-world 参考文档\ndocker的安装\n配置docker镜像\nCF-Workers-docker.io：Docker仓库镜像代理工具\n","permalink":"https://houjinghao123.github.io/posts/docker%E5%AE%89%E8%A3%85/","summary":"\u003ch2 id=\"概述\"\u003e概述\u003c/h2\u003e\n\u003ch3 id=\"什么是docker\"\u003e什么是docker\u003c/h3\u003e\n\u003cp\u003eDocker是一个应用容器引擎。 一个容器可以理解成是一个轻量级的虚拟机。但是又和虚拟机有些不同。\u003c/p\u003e","title":"Ubuntu上Docker安装"},{"content":"1、逻辑存储结构 1）表空间\n表空间是InnoDB存储引擎逻辑结构的最高层， 如果用户启用了参数 innodb_file_per_table(在 8.0版本中默认开启) ，则每张表都会有一个表空间（xxx.ibd），一个mysql实例可以对应多个表空 间，用于存储记录、索引等数 据。\n2）段\n段，分为数据段（Leaf node segment）、索引段（Non-leaf node segment）、回滚段 （Rollback segment），InnoDB是索引组织表，数据段就是B+树的叶子节点， 索引段即为B+树的 非叶子节点。段用来管理多个Extent（区）。\n3） 区\n区，表空间的单元结构，每个区的大小为1M。 默认情况下， InnoDB存储引擎页大小为16K， 即一 个区中一共有64个连续的页。\n4）页\n页，是InnoDB 存储引擎磁盘管理的最小单元，每个页的大小默认为 16KB。为了保证页的连续性， InnoDB 存储引擎每次从磁盘申请 4-5 个区。\n5）行\n行，InnoDB 存储引擎数据是按行进行存放的。在行中，默认有两个隐藏字段：\nTrx_id:每行对某条数据进行改动时，都会把对应的事务id赋值给trx_id隐藏列。 Roll_pointer:每次对某条引记录进行改动时，都会把旧的版本写入到undo日志中，然后这个隐藏列就相当于一个指针，可以通过它来找到该记录修改前的信息。 2、架构 2.1概述 MySQL5.5 版本开始，默认使用InnoDB存储引擎，它擅长事务处理，具有崩溃恢复特性，在日常开发 中使用非常广泛。下面是InnoDB架构图，左侧为内存结构，右侧为磁盘结构。 2.2内存架构 在左侧的内存结构中，主要分为这么四大块儿： Buffer Pool，Change Buffer,Adaptive Hash Index,Log Buffer。\nBuffer Pool InnoDB存储引擎基于磁盘文件存储，访问物理硬盘是在内存中进行访问，速度相差很大，为了尽可能 弥补这两者之间的I/O效率的差值，就需要把经常使用的数据加载到缓冲池中，避免每次访问都进行磁 盘I/O。\n在InnoDB的缓冲池中不仅缓存了索引页和数据页，还包含了undo页、插入缓存、自适应哈希索引以及 InnoDB的锁信息等等。\n缓冲池 Buffer Pool，是主内存中的一个区域，里面可以缓存磁盘上经常操作的真实数据，在执行增 删改查操作时，先操作缓冲池中的数据（若缓冲池没有数据，则从磁盘加载并缓存），然后再以一定频 率刷新到磁盘，从而减少磁盘IO，加快处理速度。\n缓冲池以Page页为单位，底层采用链表数据结构管理Page。根据状态，将Page分为三种类型：\nfree page:空闲page，未被使用 clean page：被使用page，数据没有被修改过。 dirty page：脏页，被使用page，数据被修改过，内存中的数据与磁盘的数据产生了不一致。 在专用服务器上，通常将多达80％的物理内存分配给缓冲池 。参数设置：\n1show variables like \u0026#39;innodb_buffer_pool_size\u0026#39;; Change Buffer Change Buffer，更改缓冲区（针对于非唯一二级索引页），在执行DML语句时，如果这些数据Page 没有在Buffer Pool中，不会直接操作磁盘，而会将数据变更存在更改缓冲区 Change Buffer 中，在未来数据被读取时，再将数据合并恢复到Buffer Pool中，再将合并后的数据刷新到磁盘中。\nChange Buffer的意义是什么呢?\n与聚集索引不同，二级索引通常是非唯一的，并且以相对随机的顺序插入二级索引。同样，删除和更新 可能会影响索引树中不相邻的二级索引页，如果每一次都操作磁盘，会造成大量的磁盘IO。有了 ChangeBuffer之后，我们可以在缓冲池中进行合并处理，减少磁盘IO。\nAdaptive Hash Index 自适应hash索引，用于优化对Buffer Pool数据的查询。MySQL的innoDB引擎中虽然没有直接支持 hash索引，但是给我们提供了一个功能就是这个自适应hash索引。因为前面我们讲到过，hash索引在 进行等值匹配时，一般性能是要高于B+树的，因为hash索引一般只需要一次IO即可，而B+树，可能需 要几次匹配，所以hash索引的效率要高，但是hash索引又不适合做范围查询、模糊匹配等。 InnoDB存储引擎会监控对表上各索引页的查询，如果观察到在特定的条件下hash索引可以提升速度， 则建立hash索引，称之为自适应hash索引。\n自适应哈希索引，无需人工干预，是系统根据情况自动完成。\n参数： adaptive_hash_index\nLog Buffer Log Buffer：日志缓冲区，用来保存要写入到磁盘中的log日志数据（redo log 、undo log）， 默认大小为 16MB，日志缓冲区的日志会定期刷新到磁盘中。如果需要更新、插入或删除许多行的事 务，增加日志缓冲区的大小可以节省磁盘 I/O。\n参数：\ninnodb_log_buffer_size：缓冲区大小\ninnodb_flush_log_at_trx_commit：日志刷新到磁盘时机，取值主要包含以下三个：\n1: 日志在每次事务提交时写入并刷新到磁盘，默认值。\n0: 每秒将日志写入并刷新到磁盘一次。\n2: 日志在每次事务提交后写入，并每秒刷新到磁盘一次。\n2.3磁盘结构 接下来，再来看看InnoDB体系结构的右边部分，也就是磁盘结构：\nSystem Tablespace 系统表空间是更改缓冲区的存储区域。如果表是在系统表空间而不是每个表文件或通用表空间中创建 的，它也可能包含表和索引数据。(在MySQL5.x版本中还包含InnoDB数据字典、undolog等)\n参数：innodb_data_file_path\n系统表空间，默认的文件名叫 ibdata1。\nFile-Per-Table Tablespaces 如果开启了innodb_file_per_table开关 ，则每个表的文件表空间包含单个InnoDB表的数据和索 引 ，并存储在文件系统上的单个数据文件中。\n开关参数：innodb_file_per_table ，该参数默认开启。\nGeneral Tablespaces General Tablespaces允许用户将多个表的数据存储在一个共享的表空间文件中。这种方式有以下一些用途和优势：\n1.如果有很多小表，使用共享表空间可以帮助减少磁盘上的文件数量，使得文件管理更加简单。\n2.当需要将多个表的数据放在同一个物理设备上时，可以使用共享表空间来实现这一需求。\n3.在某些情况下，通过将频繁访问的表放在同一个高速存储设备上的共享表空间中，可以提高访问速度。 如果整个应用的多个表都需要一起备份或恢复，共享表空间可以简化这一过程。\nA. 创建表空间\n1create tablespace ts_name add datafile \u0026#39;file_name\u0026#39; enginie =engine_name; B. 创建表时指定表空间\n1CREATE TABLE xxx ... TABLESPACE ts_name; Undo Tablespaces 撤销表空间，MySQL实例在初始化时会自动创建两个默认的undo表空间（初始大小16M），用于存储 undo log日志。\nTemporary Tablespaces InnoDB 使用会话临时表空间和全局临时表空间。存储用户创建的临时表等数据。\nDoublewrite Buffer Files 双写缓冲区，innoDB引擎将数据页从Buffer Pool刷新到磁盘前，先将数据页写入双写缓冲区文件 中，便于系统异常时恢复数据。\nRedo Log 重做日志，是用来实现事务的持久性。该日志文件由两部分组成：重做日志缓冲（redo log buffer）以及重做日志文件（redo log）,前者是在内存中，后者在磁盘中。当事务提交之后会把所 有修改信息都会存到该日志中, 用于在刷新脏页到磁盘时,发生错误时, 进行数据恢复使用。\n2.4后台线程 前面我们介绍了InnoDB的内存结构，以及磁盘结构，那么内存中我们所更新的数据，又是如何到磁盘 中的呢？ 此时，就涉及到一组后台线程，接下来，就来介绍一些InnoDB中涉及到的后台线程。\n在InnoDB的后台线程中，分为4类，分别是：Master Thread 、IO Thread、Purge Thread、 Page Cleaner Thread。\nMaster Thread 核心后台线程，负责调度其他线程，还负责将缓冲池中的数据异步刷新到磁盘中, 保持数据的一致性， 还包括脏页的刷新、合并插入缓存、undo页的回收 。\nIO Thread 在InnoDB存储引擎中大量使用了AIO来处理IO请求, 这样可以极大地提高数据库的性能，而IO Thread主要负责这些IO请求的回调。\n线程类型 默认个数 职责 Read thread 4 负责读操作 Write thread 4 负责写操作 Log thread 1 负责将日志缓冲区刷新到磁盘 Insert buffer thread 1 负责将写缓冲区内容刷新到磁盘 1show engine innodb status \\G; Purge Thread 主要用于回收事务已经提交了的undo log，在事务提交之后，undo log可能不用了，就用它来回收。\nPage Cleaner Thread 协助 Master Thread 刷新脏页到磁盘的线程，它可以减轻 Master Thread 的工作压力，减少阻塞。\n3、事务原理 3.1事务基础 什么是事务 事务 是一组操作的集合，它是一个不可分割的工作单位，事务会把所有的操作作为一个整体一起向系 统提交或撤销操作请求，即这些操作要么同时成功，要么同时失败。\n特性 原子性（Atomicity）： 事务是不可分割的最小操作单元，要么全部成功，要么全部失败。 一致性（Consistency）： 事务完成时，必须使所有的数据都保持一致状态。 隔离性（Isolation）： 数据库系统提供的隔离机制，保证事务在不受外部并发操作影响的独立环 境下运行。 持久性（Durabiity）： 事务一旦提交或回滚，它对数据库中的数据的改变就是永久的。 而对于这四大特性，实际上分为两个部分。 其中的原子性、一致性、持久化，实际上是由InnoDB中的 两份日志来保证的，一份是redo log日志，一份是undo log日志。 而持久性是通过数据库的锁， 加上MVCC来保证的。 3.2redo log(发生错误时, 进行数据恢复使用) 重做日志，记录的是事务提交时数据页的物理修改，是用来实现事务的持久性。\n该日志文件由两部分组成：重做日志缓冲（redo log buffer）以及重做日志文件（redo log file）,前者是在内存中，后者在磁盘中。当事务提交之后会把所有修改信息都存到该日志文件中, 用 于在刷新脏页到磁盘,发生错误时, 进行数据恢复使用。\n**没有redo log的情况下 **\n数据先是在缓冲池中进行增删改查等操作，如果没有就会到磁盘中进行操作，如果在缓冲池中能直接找到就直接在内存中进行修改，此时缓冲池中的数据页就是脏页，这些脏页也会在特定的时机刷新到磁盘中， 假如刷新到磁盘的过程出错了，而提示给用户事务提交成功，而数据却 没有持久化下来，这就出现问题了，没有保证事务的持久性。\n在InnoDB中提供了一份日志 redo log\n有了redolog之后，当对缓冲区的数据进行增删改之后，会首先将操作的数据页的变化，记录在redo log buffer中。在事务提交时，会将redo log buffer中的数据刷新到redo log磁盘文件中。 过一段时间之后，如果刷新缓冲区的脏页到磁盘时，发生错误，此时就可以借助于redo log进行数据恢复，这样就保证了事务的持久性。 而如果脏页成功刷新到磁盘或者涉及到的数据已经落盘，此时redolog就没有作用了，就可以删除了，所以存在的两个redolog文件是循环写的。\n那为什么每一次提交事务，要刷新redo log 到磁盘中呢，而不是直接将buffer pool中的脏页刷新 到磁盘呢 ?\n因为在业务操作中，我们操作数据一般都是 随机读写磁盘的，而不是 顺序读写磁盘。 而redo log在 往磁盘文件中写入数据，由于是日志文件，所以都是顺序写的。顺序写的效率，要远大于随机写。 这 种先写日志的方式，称之为 WAL（Write-Ahead Logging）。\n3.3undo log(事务回滚时使用+MVCC) 回滚日志，用于记录数据被修改前的信息 , 作用包含两个 : 提供回滚(保证事务的原子性) 和 MVCC(多版本并发控制) 。\nUndo log销毁：undo log在事务执行时产生，事务提交时，并不会立即删除undo log，因为这些 日志可能还用于MVCC。 Undo log存储：undo log采用段的方式进行管理和记录，存放在前面介绍的 rollback segment 回滚段中，内部包含1024个undo log segment。 3.4MVCC 3.4.1什么是MVCC 全称 Multi-Version Concurrency Control，多版本并发控制。指维护一个数据的多个版本， 使得读写操作没有冲突，快照读为MySQL实现MVCC提供了一个非阻塞读功能。MVCC的具体实现，还需要依赖于数据库记录中的三个隐式字段、undo log日志、readView。\n3.4.2MVCC相关概念 当前读\n读取的是记录的最新版本，读取时还要保证其他并发事务不能修改当前记录，会对读取的记录进行加锁。使用排他锁和共享锁就是一种当前锁。\n在测试中我们可以看到，即使是在默认的RR隔离级别下，事务A中依然可以读取到事务B最新提交的内 容，因为在查询语句后面加上了 lock in share mode 共享锁，此时是当前读操作。当然，当我们 加排他锁的时候，也是当前读操作。\n快照读 简单的select（不加锁）就是快照读，快照读，读取的是记录数据的可见版本，有可能是历史数据， 不加锁，是非阻塞读。\n• Read Committed：每次select，都生成一个快照读。\n• Repeatable Read：开启事务后第一个select语句才是快照读的地方。\n• Serializable：快照读会退化为当前读。\n3.4.3MVCC的组成之隐藏字段 当我们创建了一张表时，我们在查看表结构的时候，就可以显式的看到自己定义的字段。 实际上除了自己定义的字段以外，InnoDB还会自动的给我们添加三个隐藏字段及其含义分别是：\n隐藏字段 含义 DB_TRX_ID 最近修改事务ID，记录插入这条记录或最后一次修改该记录的事务ID。 DB_ROLL_PTR 回滚指针，指向这条记录的上一个版本，用于配合undo log，指向上一个版 本。 DB_ROW_ID 隐藏主键，如果表结构没有指定主键，将会生成该隐藏字段。 而上述的前两个字段是肯定会添加的， 是否添加最后一个字段DB_ROW_ID，得看当前表有没有主键， 如果有主键，则不会添加该隐藏字段。\n3.4.4MVCC的组成之undo log 介绍 回滚日志，在insert、update、delete的时候产生的便于数据回滚的日志。 当insert的时候，产生的undo log日志只在回滚时需要，在事务提交后，可被立即删除。 而update、delete的时候，产生的undo log日志不仅在回滚时需要，在快照读时也需要，不会立即 被删除。\n版本链 有一张表原始数据为：\nDB_TRX_ID : 代表最近修改事务ID，记录插入这条记录或最后一次修改该记录的事务ID，是自增的。 DB_ROLL_PTR ： 由于这条数据是才插入的，没有被更新过，所以该字段值为null。\n然后，有四个并发事务同时在访问这张表。\n上面的四个事务总共进行了三次修改，所以在undo log就记录了三条数据。\n最终我们发现，不同事务或相同事务对同一条记录进行修改，会导致该记录的undolog生成一条 记录版本链表，链表的头部是最新的旧记录，链表尾部是最早的旧记录。\n3.4.5MVCC的组成之ReadView ReadView（读视图）是 快照读 SQL执行时MVCC提取数据的依据，记录并维护系统当前活跃的事务 （未提交的）id。\nReadView中包含了四个核心字段：\n字段 含义 m_ids 当前活跃的事务ID集合 min_trx_id 最小活跃事务ID max_trx_id 预分配事务ID，当前最大事务ID+1（因为事务ID是自增的） creator_trx_id ReadView创建者的事务ID 而在readview中就规定了 版本链数据的访问规则： trx_id 代表当前undolog版本链对应事务ID\n条件 是否可以访问 说明 trx_id == creator_trx_id 可以访问该版本 成立，说明数据是当前这个事 务更改的。 trx_id \u0026lt; min_trx_id 可以访问该版本 成立，说明数据已经提交了。 trx_id \u0026gt; max_trx_id 不可以访问该版本 成立，说明该事务是在 ReadView生成后才开启 。 min_trx_id \u0026lt;= trx_id \u0026lt;=max_trx_id 如果trx_id不在m_ids中， 是可以访问该版本的 成立，说明数据已经提交 不同的隔离级别，生成ReadView的时机不同：\nREAD COMMITTED ：在事务中每一次执行快照读时生成ReadView。 REPEATABLE READ：仅在事务中第一次执行快照读时生成ReadView，后续复用该ReadView。 ReadView不同隔离级别分析 RC隔离级别 RC隔离级别下，在事务中每一次执行快照读时生成ReadView。\n我们就来分析事务5中，两次快照读读取数据，是如何获取数据的?\n在事务5中，查询了两次id为30的记录，由于隔离级别为Read Committed，所以每一次进行快照读 都会生成一个ReadView，那么两次生成的ReadView如下。\n那么这两次快照读在获取数据时，就需要根据所生成的ReadView以及ReadView的版本链访问规则， 到undolog版本链中匹配数据，最终决定此次快照读返回的数据。\n先来看第一次快照读具体的读取过程：\n在进行匹配时，会从undo log的版本链，从上到下进行挨个匹配：\nRR隔离级别 RR隔离级别下，仅在事务中第一次执行快照读时生成ReadView，后续复用该ReadView。 而RR 是可 重复读，在一个事务中，执行两次相同的select语句，查询到的结果是一样的。\n","permalink":"https://houjinghao123.github.io/posts/innodb%E8%AF%A6%E8%A7%A3/","summary":"\u003ch2 id=\"1逻辑存储结构\"\u003e1、逻辑存储结构\u003c/h2\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"https://cdn.nlark.com/yuque/0/2024/png/49057062/1727766135694-2cc9063a-a21b-45b9-ad26-34c9691c6f26.png\" alt=\"\"  /\u003e\n\u003c/p\u003e\n\u003cp\u003e1）表空间\u003c/p\u003e\n\u003cp\u003e表空间是InnoDB存储引擎逻辑结构的最高层， 如果用户启用了参数 innodb_file_per_table(在 8.0版本中默认开启) ，则每张表都会有一个表空间（xxx.ibd），一个mysql实例可以对应多个表空 间，用于存储记录、索引等数   据。\u003c/p\u003e","title":"InnoDB详解"},{"content":"MySQL锁 1、概述 如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题，锁冲突也是影响数据库并发访问性能的一个重要因素。从这个角度来说，锁对数据库而言显得尤其重要，也更加复杂。\nMySQL中的锁，按照锁的粒度分，分为以下三类：\n全局锁：锁定数据库的所有表。 表级锁：每次操作锁住整张表。 行级锁：每次操作锁住对应的行数据。 2、全局锁 2.1介绍 全局锁就是对整个数据库实例加锁，加锁后整个实例就处于只读状态，后续的DML的写语句，DDL语句，已经更新操作的事务提交语句都将被阻塞。\n使用场景是做全库的逻辑备份，对所有的表进行锁定，从而获取一致性视图，保证数据的完整性。\n在进行全库的逻辑备份时加锁是为了防止出现数据不一致问题，发生这种问题的原因如下：\n假设在数据库中存在这样三张表: tb_stock库存表，tb_order订单表，tb_orderlog订单日志表。\n在进行数据备份时，先备份了tb_stick库存表 然后接下来，在业务系统中，执行了下单操作，扣减库存，生成订单（更新tb_stock表，插入tb_order表）。 然后在执行备份tb_order表的逻辑。 业务中执行插入订单日志操作。 最后，又备份了tb_orderlog表。 此时备份出来的数据，是存在问题的。因为备份出来的数据，tb_stock表与tb_order表的数据不一致(有最新操作的订单信息,但是库存数没减)。\n在对数据库加上全局锁后\n对数据库进行进行逻辑备份之前，先对整个数据库加上全局锁，一旦加了全局锁之后，其他的DDL、DML全部都处于阻塞状态，但是可以执行DQL语句，也就是处于只读状态，而数据备份就是查询操作。那么数据在进行逻辑备份的过程中，数据库中的数据就是不会发生变化的，这样就保证了数据的一致性和完整性。\n2.2语法 1）加全局锁\n1flush table with read lock; 2）数据备份\n1mysqldump -uroot -p**** 要备份的数据库名 \u0026gt; 要备份到的地址/名字.sql 该方式时数据库自带的备份工具是直接在命令行窗口执行的\n3）释放锁\n1unlock tables; 2.3问题 数据库中加全局锁，是一个比较重的操作，存在以下问题：\n如果在主库上备份，那么在备份期间都不能执行更新，业务基本上就得停摆。 如果在从库上备份，那么在备份期间从库不能执行主库同步过来的二进制日志（binlog），会导致主从延迟。 在InnoDB引擎中，我们可以在备份时加上参数\u0026ndash;single-transaction参数来完成不加锁的一致性数据备份。\n1mysqldump --single-transaction -uroot -p**** 要备份的数据库名 \u0026gt; 要备份到的地址/名字.sql 3表级锁 3.1介绍 表级锁，每次操作锁住整张表。锁定粒度大，发生锁冲突的概率最高，并发度最低。应用在MyISAM、InnoDB、BDB等存储引擎中。\n对于表级锁，主要分为以下三类\n表锁 元数据锁(meta data lock,MDL) 意向锁 3.2表锁 对于表锁，分为两类：\n表共享读锁（read lock） 表独占写锁（write lock） 语法：\n加锁：lock tables 表名\u0026hellip;read/write。 释放锁：unlock tables /断开客户端连接。 特点 :\n读锁 左侧为客户端一，对指定表加了读锁，不会影响右侧客户端二的读，但是会阻塞右侧客户端的写。\n写锁 左侧为客户端一，对指定表加了写锁，会阻塞右侧客户端的读和写。\n读锁不会堵塞其他客户端的读，但是会堵塞写。 写锁即会堵塞其他客户端的读，又会堵塞其他客户端换的写。\n3.3元数据锁 meta data lock,元数据锁，简写MDL。\nMDL加锁过程是系统自动控制，无需显式使用，在访问一张表的时候会自动加上。MDL锁主要作用是维护表元数据的数据一致性，在表上有活动事务的时候，不可以对元数据进行写入操作。为了避免DML与DDL冲突，保证读写的正确性。\n这里的元数据，大家可以简单理解为就是一张表的表结构。也就是说，某一张表涉及到未提交的事务时，是不能够修改这张表的表结构的。\n在MySQL5.5中引入了MDL，当对一张表进行增删改查的时候，加MDL读锁(共享)；当对表结构进行变更操作的时候，加MDL写锁(排他)。\n常见的SQL操作时，所添加的元数据锁：\n对应SQL 锁类型 说明 lock tables xxx read/write SHARED_READ_ONLY/SHARED_NO_READ_WRITE select 、select\u0026hellip;lock in share mode SHARED_READ 与SHARED_READ、SHARED_WRITE兼容，与EXCLUSIVE互斥 insert 、update、delete、select\u0026hellip; forupdate SHARED_WRITE 与SHARED_READ、SHARED_WRITE兼容，与EXCLUSIVE互斥 alter table\u0026hellip; EXCLUSIVE 与其他的MDL都互斥 当执行SELECT、INSERT、UPDATE、DELETE等语句时，添加的是元数据共享锁（SHARED_READ/SHARED_WRITE），之间是兼容的。\n当执行SELECT语句时，添加的是元数据共享锁（SHARED_READ），会阻塞元数据排他锁（EXCLUSIVE），之间是互斥的。\n我们可以通过下面的SQL,来查看数据库中的元数据锁的情况:\n1select object_type,object_schema,object_name,lock_type,lock_duration from 2performance_schema.metadata_locks ; 如果是5.7版本的数据库需要手动开启metadata_locks锁记录(临时生效)\n1update performance_schema.setup_instruments set enabled = \u0026#39;YES\u0026#39;,timed=\u0026#39;YES\u0026#39; where name=\u0026#39;wait/lock/metadata/sql/mdl\u0026#39;; 永久生效在配置文件中加入以下内容\n1# 开启监控元数据锁 2performance-schema-instrument=\u0026#39;wait/lock/metadata/sql/mdl=ON\u0026#39; 当在不同的连接窗口上时EXCLUSIVE锁是和其他锁互斥的。如果在同一连接窗口则会强制提交事务。\n3.4意向锁 1）介绍\n为了避免DML在执行时，加的行锁与表锁冲突，在InnoDB中引入了意向锁，使得表锁不用检查每行数据数据是否加锁，使用意向锁来减少表锁的检查。\n假如没有意向锁，客户端一对表加了行锁后，客户端二如何给表加表锁呢，来通过示意图简单分析一下：\n首先客户端一，开启一个事务，然后执行DML操作，在执行DML语句时，会对涉及到的行加行锁。\n当客户端二，想对这张表加表锁时，会检查当前表是否有对应的行锁，如果没有，则添加表锁，此时就会从第一行数据，检查到最后一行数据，效率较低。\n有了意向锁之后 : 客户端一，在执行DML操作时，会对涉及的行加行锁，同时也会对该表加上意向锁。\n而其他客户端，在对这张表加表锁的时候，会根据该表上所加的意向锁来判定是否可以成功加表锁，而不用逐行判断行锁情况了。\n2）分类\n意向共享锁（IS）:由语句select \u0026hellip; lock in share mode添加 。 与 表锁共享锁(read)兼容，与表锁排他锁(write)互斥。 意向排他锁 （IX）：由insert、update、delete、select\u0026hellip;for update添加。与表锁共享锁和排他锁都互斥，意向锁之间不会互斥。 一旦事务提交了，意向共享锁、意向排他锁，都会自动释放。\n可以通过以下SQL，查看意向锁及行锁的加锁情况：（在Mysql8.0中能正常使用）\n1select object_schema,object_name,index_name,lock_type,lock_mode,lock_data from performance_schema.data_locks; 四、行级锁 4.1介绍 行级锁，每次操作锁住对应的行数据。锁定粒度最小，发生锁冲突的概率最低，并发度最高。应用在InnoDB存储引擎中。\nInnoDB的数据是基于索引组织的，行锁是通过对索引上的索引项加锁来实现的，而不是对记录加的锁。对于行级锁，主要分为以下三类：\n行锁（Record Lock）: 锁定单个行记录的锁，防止其他事务对此行进行update和delete。在RC、RR隔离级别下都支持。 间隙锁（Gap Lock）：锁定索引记录间隙（不含该记录），确保索引记录间隙不变，防止其他事务在这个间隙进行insert，产生幻读。在RR隔离级别下都支持。 临键锁（Next-Key Lock）：行锁和间隙锁的组合，同时锁住数据，并锁住数据前面的间隙Gap。在RR隔离级别下支持 4.2、行锁 1）介绍\nInnoDB实现了以下两种类型的行锁：\n共享锁（S）：允许一个事务去读一行，阻止其他事务获得相同数据集的排他锁。 排他锁（X）：允许获取排他锁的事务更新数据，阻止其他事务获得相同数据集的共享锁和排他锁。 X IX S IS X 互斥 互斥 互斥 互斥 IX 互斥 兼容 互斥 兼容 S 互斥 互斥 兼容 兼容 IS 互斥 兼容 兼容 兼容 常见的SQL语句，在执行时，所加的行锁如下：\nSQL 行锁类型 说明 INSERT \u0026hellip; 排他锁 自动加锁 UPDATE \u0026hellip; 排他锁 自动加锁 DELETE \u0026hellip; 排他锁 自动加锁 SELECT（正常） 不加任何锁 SELECT \u0026hellip; LOCK IN SHARE MODE 共享锁 需要手动在SELECT之后加LOCK IN SHARE MODE SELECT \u0026hellip; FOR UPDATE 排他锁 需要手动在SELECT之后加FOR UPDATE 2）演示\n共享锁（S） 给id为1的加上共享锁后可以对数据进行查询，但是不能对数据进行更改。\n排他锁（X） 给id=3的加上排他锁后可以对其他事务进行查询但是不能更改和在对它加其他的锁。\n4.3、间隙锁 只存在于可重复读隔离级别，目的是为了解决可重复读隔离级别下幻读的现象。\n假设，表中有一个范围id为（3，5）间隙锁，那么其他事务就无法插入id=4这条记录了，这样就有效 的防止幻读现象的发生。\n间隙锁虽然存在X型间隙锁和S型间隙锁，但是并没有什么区别，间隙锁之间是兼容的，即两个事务可以 同时持有包含共同间隙范围的间隙锁，并不存在互斥关系，因为间隙锁的目的是防止插入幻影记录而提出 的。\n4.4、临键锁 Next-Key Lock 称为临键锁，是 Record Lock+ Gap Lock 的组合，锁定一个范围，并且锁定记录本身。\n假设，表中有一个范围 id 为（3，5]的 next-key lock，那么其他事务即不能插入 id= 4 记录，也不能修 改id=5 这条记录。\n所以，next-key lock 即能保护该记录，又能阻止其他事务将新纪录插入到被保护记录前面的间隙中。 next-keylock是包含间隙锁+记录锁的，如果一个事务获取了×型的next-keylock，那么另外一个事务 在获取相同范围的×型的next-keylock时，是会被阻塞的。\n参考文档\nMySQL数据库官方文档-InnoDB Locking\n","permalink":"https://houjinghao123.github.io/posts/mysql%E9%94%81/","summary":"\u003ch1 id=\"mysql锁\"\u003e\u003cstrong\u003eMySQL锁\u003c/strong\u003e\u003c/h1\u003e\n\u003ch2 id=\"1概述\"\u003e\u003cstrong\u003e1、概述\u003c/strong\u003e\u003c/h2\u003e\n\u003cp\u003e如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题，锁冲突也是影响数据库并发访问性能的一个重要因素。从这个角度来说，锁对数据库而言显得尤其重要，也更加复杂。\u003c/p\u003e","title":"MySQL锁"},{"content":"触发器 1.介绍 触发器是与表有关的数据库对象，指在insert/update/delete之前(BEFORE)或之后(AFTER)，触发并执行触发器中定义的SQL语句集合。\n使用别名OLD和NEW来引用触发器中发生变化的记录内容，这与其他的数据库是相似的。现在触发器还只支持行级触发，不支持语句级触发。触发器的这种特性可以协助应用在数据库端确保数据的完整性,日志记录,数据校验等操作 。\n触发器类型 NEW和 OLD INSERT型触发器 NEW表示将要或者已经新增的数据 UPDATE型触发器 OLD表示修改之前的数据, NEW表示将要或已经修改后的数据 DELETE型触发器 OLD表示将要或者已经删除的数据 2.语法 1）创建\n1create trigger trigger_name 2before/after insert/update/delete 3on tbl_name for each row --行级触发器 4begin 5 trigger_stmt; 6end; 2)查看\n1show triggers; 3)删除\n1drop trigger [schema_name.]trigger_name; --如果没有指定schema_name,默认为当前数据库。 案例 1-- 通过触发器记录 tb_user 表的数据变更日志，将变更日志插入到日志表user_logs中, 包含增加,修改 , 删除 ; 2 3-- 插入数据触发器 4create trigger tb_user_insert_trigger 5 after insert on tb_user for each row 6begin 7 insert into user_logs(id, operation, operate_time, operate_id, operate_params) 8 values(null,\u0026#39;insert\u0026#39;,now(),new.id, concat(\u0026#39;插入的数据内容为: 9id=\u0026#39;,new.id,\u0026#39;,name=\u0026#39;,new.name, \u0026#39;, phone=\u0026#39;, NEW.phone, \u0026#39;, email=\u0026#39;, NEW.email, \u0026#39;, 10profession=\u0026#39;, NEW.profession)); 11end; 1-- 修改数据触发器 2 3create trigger tb_user_update_trigger 4 after update on tb_user for each row 5begin 6 insert into user_logs(id, operation, operate_time, operate_id, operate_params)values(null,\u0026#39;update\u0026#39;,now(),new.id,concat(\u0026#39;更新之前的数据:id=\u0026#39;,old.id,\u0026#39;,name=\u0026#39;,old.name, \u0026#39;, phone=\u0026#39;, old.phone, \u0026#39;, email=\u0026#39;, old.email, \u0026#39;, profession=\u0026#39;, old.profession, \u0026#39; | 更新之后的数据: id=\u0026#39;,new.id,\u0026#39;,name=\u0026#39;,new.name, \u0026#39;, phone=\u0026#39;, NEW.phone, \u0026#39;, email=\u0026#39;, NEW.email, \u0026#39;, profession=\u0026#39;, NEW.profession)); 7end; 1-- 删除数据触发器 2create trigger tb_user_delete_trigger 3 after delete on tb_user for each row 4begin 5 insert into user_logs(id, operation, operate_time, operate_id, operate_params) 6 value (null,\u0026#39;delete\u0026#39;,now(),old.id, 7 concat(\u0026#39;删除之前的数据: id=\u0026#39;,old.id,\u0026#39;,name=\u0026#39;,old.name, \u0026#39;, phone=\u0026#39;,old.phone, \u0026#39;, email=\u0026#39;, old.email, \u0026#39;, profession=\u0026#39;, old.profession)); 8end; ","permalink":"https://houjinghao123.github.io/posts/mysql%E8%A7%A6%E5%8F%91%E5%99%A8/","summary":"\u003ch1 id=\"触发器\"\u003e\u003cstrong\u003e触发器\u003c/strong\u003e\u003c/h1\u003e\n\u003ch2 id=\"1介绍\"\u003e\u003cstrong\u003e1.介绍\u003c/strong\u003e\u003c/h2\u003e\n\u003cp\u003e触发器是与表有关的数据库对象，指在insert/update/delete之前(BEFORE)或之后(AFTER)，触发并执行触发器中定义的SQL语句集合。\u003c/p\u003e","title":"MySQL触发器"},{"content":"视图-存储过程 一、视图 1.1介绍 视图（View）是一种虚拟存在的表。视图中的数据并不在数据库中实际存在，行和列数据来自定义视图的查询中使用的表，并且是在使用视图时动态生成的。\n通俗的讲，视图只保存了查询的SQL逻辑，不保存查询结果。所以我们在创建视图的时候，主要的工作就落在创建这条SQL查询语句上。\n1.2基本语法 创建 1create [or replace] view 视图名称[(列表名称)] as select 语句 [with [cascaded | local] check option] 查询 1查询创建视图语句：show create view 视图名称； 2查看视图数据： select * from 视图名称 ....; 修改 1方式一：create or replace view 视图名称[(列名列表)]] as select 语句[with [cascaded | local] check option] 删除 1drop view [if exists] 视图名称 [,视图名称] ... 1.3检测选项 当使用WITH CHECK OPTION子句创建视图时，MySQL会通过视图检查正在更改的每个行，例如插入，更新，删除，以使其符合视图的定义。 MySQL允许基于另一个视图创建视图，它还会检查依赖视图中的规则以保持一致性。为了确定检查的范围，mysql提供了两个选项： CASCADED和 LOCAL，默认值为 CASCADED 。\ncascaded 级联 当使用该属性时在进行视图检查时，上一级的检查条件会向下一级传递，并对每一级进行判断。\nlocal 本地 只会检查当前级别的判定条件。\n1.4视图更新规则 要使视图可更新，视图中的行与基础表中的行之间必须存在一对一的关系。如果视图包含以下任何一项，则该视图不可更新：A.聚合函数或窗口函数（SUM()、 MIN()、 MAX()、 COUNT()等）\nB. DISTINCT\nC. GROUP BY\nD. HAVING\nE. UNION或者 UNION ALL\n1.5视图的好处 简单 视图不仅可以简化数据库使用者对数据的理解，也可以简化他们的操作。那些被经常使用的查询可以被定义为视图，从而使得使用者不必为以后的操作每次指定全部的条件。\n安全 数据库可以授权，但不能授权到数据库特定行和特定的列上。通过视图用户只能查询和修改他们所能见到的数据\n数据独立 视图可帮助用户屏蔽真实表结构变化带来的影响。\n案例 二、存储过程 2.1介绍 存储过程是事先经过编译并存储在数据库中的一段 SQL语句的集合，调用存储过程可以简化应用开发人员的很多工作，减少数据在数据库和应用服务器之间的传输，对于提高数据处理的效率是有好处的。存储过程思想上很简单，就是数据库 SQL语言层面的代码封装与重用，即SQL的函数。\n特点：\n封装，复用\u0026mdash;》可以把某一业务SQL封装在存储过程中，需要用到的时候直接调用即可。 可以接收参数，也可以返回数据 减少网络交互，提升效率 2.2基本语法 创建 1create procedure 存储过程名称 ([参数列表]) 2begin 3 -- SQL语句 4end; 调用 1call 名称([参数]); 查看 1 2SELECT * FROM INFORMATION_SCHEMA.ROUTINES WHERE ROUTINE_SCHEMA = \u0026#39;xxx\u0026#39;; -- 查询指 3定数据库的存储过程及状态信息 4 5show create procedure 存储过程名称; -- 查询某个存储过程的定义 删除 1drop procedure [if exists] 存储过程名称; 2.3变量 在MySQL中变量分为三种类型：系统变量，用户定义变量，局部变量。\n系统变量\n系统变量是MySQL服务器提供，不是用户定义的，属于服务器层面。分为全局变量（GLOBAL）、会话变量（SESSION）。\n查看系统变量 1show [session | global] variables; -- 查看所有系统变量 2SHOW [ SESSION | GLOBAL ] VARIABLES LIKE \u0026#39;......\u0026#39;; -- 可以通过LIKE模糊匹配方式查找变量 3SELECT @@[SESSION | GLOBAL] 系统变量名; -- 查看指定变量的值 设置系统变量 1set [ SESSION | GLOBAL ] 系统变量名 = 值 ; 2SET @@[SESSION | GLOBAL]系统变量名 = 值 ; 如果没有指定session/global,默认是session。\nmysql服务重新启动之后，所设置的全局参数会失效，要想不失效，可以在 /etc/my.cnf 中配置。\n用户定义变量 用户定义变量 是用户根据需要自己定义的变量，用户变量不用提前声明，在用的时候直接用 \u0026ldquo;@变量\n名\u0026rdquo; 使用就可以。其作用域为当前连接。\n赋值\n方式一：\n1set @var_name=expr [,@var_name=expr]...; 2set @var_name := expr [, @var_name := expr] ... ; 赋值时，可以使用 = ，也可以使用 := 。\n方式二:\n1select @var_name :=expr [,@var_name :=expr]...; 2select 字段名 into @var_name from 表名； 使用\n1select @var_name; 局部变量 局部变量 是根据需要定义的在局部生效的变量，访问之前，需要DECLARE声明。可用作存储过程内的\n局部变量和输入参数，局部变量的范围是在其内声明的BEGIN \u0026hellip; END块\n声明\n1declare 变量名 变量类型 [default ...]; 变量类型就是数据库字段类型：INT、BIGINT、CHAR、VARCHAR、DATE、TIME等。\n赋值\n1set 变量名 =值； 2set 变量名 :=值； 3select 字段名 into 变量名 from 表名 ...; 2.4流程控制语句 参数类型\n参数 含义 备注 IN 该类参数作为输入，也就是需要调用时传值 默认 OUT 该类参数作为输出，也就是该参数可以作为返回值 INOUT 既可以作为输入参数，也可以作为输出参数 if\n用于做条件判断，具体的语法结构为：\n1IF 条件1 THEN 2..... 3ELSEIF 条件2 THEN -- 可选 4..... 5ELSE -- 可选 6..... 7END IF; case\n1-- 含义： 当case_value的值为 when_value1时，执行statement_list1，当值为 when_value2时， 2执行statement_list2， 否则就执行 statement_list 3CASE case_value 4 5\tWHEN when_value1 THEN statement_list1 6\t[ WHEN when_value2 THEN statement_list2] ... 7\t[ ELSE statement_list ] 8END CASE; 1-- 含义： 当条件search_condition1成立时，执行statement_list1，当条件search_condition2成 2立时，执行statement_list2， 否则就执行 statement_list 3CASE 4\tWHEN search_condition1 THEN statement_list1 5\t[WHEN search_condition2 THEN statement_list2] ... 6\t[ELSE statement_list] 7END CASE; while\nwhile 循环是有条件的循环控制语句。满足条件后，再执行循环体中的SQL语句。具体语法为：\n1-- 先判定条件，如果条件为true，则执行逻辑，否则，不执行逻辑 2WHILE 条件 DO 3\tSQL逻辑... 4END WHILE; repeat\nrepeat是有条件的循环控制语句, 当满足until声明的条件的时候，则退出循环 。具体语法为：\n1-- 先执行一次逻辑，然后判定UNTIL条件是否满足，如果满足，则退出。如果不满足，则继续下一次循环 2REPEAT 3\tSQL逻辑... 4\tUNTIL 条件 5END REPEAT; loop\nLOOP 实现简单的循环，如果不在SQL逻辑中增加退出循环的条件，可以用其来实现简单的死循环。\nLOOP可以配合一下两个语句使用：\nleave:配合循环使用，退出循环。类似break。 iterate:必须用在循环中，作用是跳过当前循环剩余的语句，直接进去下一循环。类似countie。 语法：\n[begin_label:] LOOP SQL逻辑... END LOOP [end_label]; LEAVE label; -- 退出指定标记的循环体 ITERATE label; -- 直接进入下一次循环 游标\n游标（CURSOR）是用来存储查询结果集的数据类型 , 在存储过程和函数中可以使用游标对结果集进\n行循环的处理。游标的使用包括游标的声明、OPEN、FETCH 和 CLOSE，其语法分别如下\n语法 A.声明游标\n1declare 游标名称 cursor for 查询语句; B.打开游标\n1open 游标名称; C.获取游标记录\n1fetch 游标名称 into 变量 [.变量]; D.关闭游标\n1close 游标名称; 案例\n1-- 根据传入的参数uage，来查询用户表tb_user中，所有的用户年龄小于等于uage的用户姓名 2-- （name）和专业（profession），并将用户的姓名和专业插入到所创建的一张新表 3-- (id,name,profession)中。 4create procedure p11(in uage int) 5begin 6 declare uname varchar(100); 7 declare pro varchar(100); 8 declare u_cursor cursor for select name ,profession from tb_user where age\u0026lt;=uage; 9 drop table if exists tb_user_pro; 10 create table if not exists tb_user_pro( 11 id int primary key auto_increment, 12 name varchar(100), 13 profession varchar(100) 14 ); 15 open u_cursor ; 16 while true do 17 fetch u_cursor into uname,pro; 18 insert into tb_user_pro values(null,uname,pro); 19 end while; 20 close u_cursor; 21end; 2.5条件处理程序 条件处理程序（Handler）可以用来定义在流程控制结构执行过程中遇到问题时相应的处理步骤。具体\n语法为：\n1DECLARE handler_action HANDLER FOR condition_value [, condition_value] 2... statement ; 3handler_action 的取值： 4\tCONTINUE: 继续执行当前程序 5\tEXIT: 终止执行当前程序 6condition_value 的取值： 7\tSQLSTATE sqlstate_value: 状态码，如 02000 8\tSQLWARNING: 所有以01开头的SQLSTATE代码的简写 9\tNOT FOUND: 所有以02开头的SQLSTATE代码的简写 10\tSQLEXCEPTION: 所有没有被SQLWARNING 或 NOT FOUND捕获的SQLSTATE代码的简写 案例\n1create procedure p11(in uage int) 2begin 3 declare uname varchar(100); 4 declare pro varchar(100); 5 declare u_cursor cursor for select name ,profession from tb_user where age\u0026lt;=uage; 6 -- 声明条件处理程序： 当SQL语句执行抛出的状态码为02000时，将关闭游标u_cursor,并退出 7 declare exit handler for SQLSTATE \u0026#39;02000\u0026#39; close u_cursor; 8 drop table if exists tb_user_pro; 9 create table if not exists tb_user_pro( 10 id int primary key auto_increment, 11 name varchar(100), 12 profession varchar(100) 13 ); 14 open u_cursor ; 15 while true do 16 fetch u_cursor into uname,pro; 17 insert into tb_user_pro values(null,uname,pro); 18 end while; 19 close u_cursor; 20end; ","permalink":"https://houjinghao123.github.io/posts/%E8%A7%86%E5%9B%BE%E5%92%8C%E5%AD%98%E5%82%A8%E8%BF%87%E7%A8%8B/","summary":"\u003ch1 id=\"视图-存储过程\"\u003e\u003cstrong\u003e视图-存储过程\u003c/strong\u003e\u003c/h1\u003e\n\u003ch2 id=\"一视图\"\u003e\u003cstrong\u003e一、视图\u003c/strong\u003e\u003c/h2\u003e\n\u003ch3 id=\"11介绍\"\u003e\u003cstrong\u003e1.1介绍\u003c/strong\u003e\u003c/h3\u003e\n\u003cp\u003e视图（View）是一种虚拟存在的表。视图中的数据并不在数据库中实际存在，行和列数据来自定义视图的查询中使用的表，并且是在使用视图时动态生成的。\u003c/p\u003e","title":"MySQL视图和存储过程"},{"content":"SQL优化 插入数据 insert 假设我们需要插入大量数据，那么有哪些方法呢？\n批量插入数据 1INSERT INTO tb_test values(1,\u0026#39;Tom\u0026#39;),(2,\u0026#39;Cat\u0026#39;),(3,\u0026#39;Jerry\u0026#39;); 手动控制事务的提交 1start transaction; 2INSERT INTO tb_test values(1,\u0026#39;Tom\u0026#39;),(2,\u0026#39;Cat\u0026#39;),(3,\u0026#39;Jerry\u0026#39;); 3INSERT INTO tb_test values(4,\u0026#39;Tom\u0026#39;),(5,\u0026#39;Cat\u0026#39;),(6,\u0026#39;Jerry\u0026#39;); 4 5commit; 这两种方法只适用于插入数据在几千条到几万条的，当数据涉及到几十万或者几百万时MySQL也给我们提供了别的方法、\nload 指令 1-- 客户端连接服务端时，加上参数 ---local-infile 2mysql --local-infile -uroot -p 3 4-- 设置全局参数local_infile为1，开启从本地加载文件导入数据的开关 5set global local_infile =1; 6 7-- 执行load指令将准备好的数据，加载到表结构中 8load data local infile \u0026#39;d:/data/test.log\u0026#39; into table tb_test fields terminated by \u0026#39;,\u0026#39; lines terminated by \u0026#39;\\n\u0026#39;;\u0026#39;; 9 10-- fields terminatedp-每个字段之间使用什么分隔 11-- lines terminated-每条数据之间用什么分隔 主键按顺序插入性能要高于乱序插入：因为插入时需要根据主键进行排序生成索引，当使用乱序时会可能会导致用一页的数据产生分页的现象。\n主键优化 在上一小节，我们提到，主键顺序插入的性能是要高于乱序插入的。这一小节，就来介绍一下具体的原因，然后再分析一下主键又该如何设计。\n1)数据组织方式 在InnoDB存储引擎中，表数据都是根据主键顺序组织存放的，这种存储方式的表称为索引组织表(IOT)\n行数据都是存储在聚集索引的叶子节点上的，而InnoDB的逻辑结构是：表-\u0026gt;段 -\u0026gt;区 -\u0026gt;块-\u0026gt; 行\n在InnoDB引擎中。数据行是记录在逻辑结构的page页中的，每一页的大小是16k，一个页中所存储的行也是有限的，如果插入的数据行row在该页存储不小，将会存储到下一个页中，页与页之间会通过指针连接。\n页分裂 页可以为空，也可以填充一半，也可以填充100%。每个页包含了2-N行数据(如果一行数据过大，会行溢出)，根据主键排列。\nA. 主键顺序插入效果\n从磁盘中申请页，主键顺序插入 B.主键乱序插入效果\n加入1#，2#页都已经写满了，存放了如图的数据 2.此时在插入id为50的记录，会发生什么现象\n会在开启一个页，写入新的页中？\n不会。因为，索引结构的叶子节点是有顺序的。按照顺序，应该存储在47之后。\n但是在47所在的1#页中已经写满了，存储不了50对应的数据了。那么此时会开辟一个新的页 3#。\n但是不会直接将50存入3#页，而是会将1#页一半的数据移动到3#页没然后3#页，插入50。\n移动数据，并插入id为50的数据之后，那么此时，这三个页之间的数据顺序是有问题的。 1#的下一个页，应该是3#， 3#的下一个页是2#。所以，此时，需要重新设置链表指针。\n上述的这种现象，称之为\u0026quot;页分裂\u0026quot;，是比较耗费性能的操作。\n页合并 当有1#,2#,3#三个页时，假设我们删除了2#页上的50%(MERGE_THRESHOLD)的记录，那么InnoDB会开始开始寻找1#或3#(磁盘最近距离)，看看是否可以将两页合并以优化空间使用。\nMERGE_THRESHOLD:合并页的阈值，可以自己设置，并在创建表或者创建索引时指定。\n索引设计原则 满足业务需求的情况下，尽量降低主键的长度 插入数据时，尽量选择顺序插入，选择使用AUTO_INCREMENT自增主键。 业务操作时，避免对主键的修改。 尽量不要使用UUID做主键或者是其他自然主键，如身份证号。 业务操作时，避免对主键的修改。 order by 优化 MySQL的排序，有两种方式：\nUsing filesort:通过表的索引或全表扫描，读取满足条件的数据行，然后在排序缓冲区sort buffer中完成排序操作，所有不是通过索引直接返回排序结果的排序都叫 FileSort排序。 Using index:通过有序索引顺序扫描直接返回有序数据，这种情况即为 using index，不需要额外排序，操作效率高。 对于以上的两种排序方式，Using index的性能高，而Using filesort的性能低，我们在优化排序操作时，尽量要优化为 Using index。\n可以使用explain 查看extra\n为了使排序也使用索引，就需要在创建索引时添加排序。默认也会添加排序方式为asc，也可以自己指定。\n1-- 创建索引 2create index idx_user_age_phone on tb_user(age[asc | desc] ,phone[asc | desc]) 根据age, phone进行降序一个升序，一个降序\n因为创建索引时，如果未指定顺序，默认都是按照升序排序的，而查询时，一个升序，一个降序，此时就会出现Using filesort。\n解决这种问题只需要在创建索引时一个使用desc，一个使用asc。\n我们得出order by优化原则:\nA.根据排序字段建立合适的索引，多字段排序时，也遵循最左前缀法则。\nB.尽量使用覆盖索引。\nC.多字段排序,一个升序一个降序，此时需要注意联合索引在创建时的规则（ASC/DESC）。\nD.如果不可避免的出现filesort，大数据量排序时，可以适当增大排序缓冲区大小sort_buffer_size(默认256k)。\ngroup by优化 优化思路\nA.在分组操作时，可以通过索引来提高效率。\nB.分组操作时，索引的使用也是满足最左前缀法则的。\n当进行分组查询时如果使用了联合索引那就要符合最左前缀法，如果不使用就会出现Using temporary。\nlimit优化 分页查询，在查询时，越往后，分页查询效率越低。\n优化思路\n一般分页查询时，通过创建覆盖索引能够比较好地提高性能，可以通过覆盖索引加子查询形式进行优化。\n未使用索引时\n使用索引时\ncount优化 优化方案 在之前的测试中，我们发现，如果数据量很大，在执行count操作时，是非常耗时的。\n而对于count的执行方式不同的引擎也有不同的方法。\nMyISAM 引擎把一个表的总行数存在了磁盘上，因此执行 count(*)的时候会直接返回这个数，效率很高；但是如果是带条件的count，MyISAM也慢。 InnoDB 引擎是它执行count(*) 的时候，需要把数据一行一行的从引擎里面读取出来，然后积累计数。 优化思路：自己计数（可以借助redis这样的数据库进行，但是如果是带条件的count，还是麻烦）。\n不同count使用方法的性能：\ncount(字段)\u0026lt;count(主键)\u0026lt;count(1)|count(*);\nupdate优化 开启事务后，在使用没有的索引的字段机进行update的处理时会产生表锁，\n优化思路\n因为InnoDB的行锁是针对索引加的行锁，不是针对记录加的锁，并且该索引不能失效，否则行锁会升级为表锁。\n","permalink":"https://houjinghao123.github.io/posts/sql%E4%BC%98%E5%8C%96/","summary":"\u003ch1 id=\"sql优化\"\u003e\u003cstrong\u003eSQL优化\u003c/strong\u003e\u003c/h1\u003e\n\u003ch2 id=\"插入数据\"\u003e\u003cstrong\u003e插入数据\u003c/strong\u003e\u003c/h2\u003e\n\u003ch3 id=\"insert\"\u003e\u003cstrong\u003einsert\u003c/strong\u003e\u003c/h3\u003e\n\u003cp\u003e假设我们需要插入大量数据，那么有哪些方法呢？\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e批量插入数据\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-mysql\" data-lang=\"mysql\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"ln\"\u003e1\u003c/span\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eINSERT\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003eINTO\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003etb_test\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003evalues\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;Tom\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e),(\u003c/span\u003e\u003cspan class=\"mi\"\u003e2\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;Cat\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e),(\u003c/span\u003e\u003cspan class=\"mi\"\u003e3\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;Jerry\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pr","title":"SQL优化"},{"content":"复习数据结构二叉树 什么是二叉树？ 二叉树（binary tree）是一种非线性数据结构，代表“祖先”与“后代”之间的派生关系，体现了“一分为二”的分治逻辑。与链表类似，二叉树的基本单元是节点，每个节点包含值、左子节点引用和右子节点引用。每个节点都有两个引用（指针），分别指向左子节点（left-child node）和右子节点（right-child node），该节点被称为这两个子节点的父节点（parent node）。当给定一个二叉树的节点时，我们将该节点的左子节点及其以下节点形成的树称为该节点的左子树（left subtree），同理可得右子树（right subtree）。\n1/* 二叉树节点类 */ 2class TreeNode { 3 int val; // 节点值 4 TreeNode left; // 左子节点引用 5 TreeNode right; // 右子节点引用 6 TreeNode(int x) { val = x; } 7} 二叉树常见术语 根节点（root node）：位于二叉树顶层的节点，没有父节点。 叶节点（leaf node）：没有子节点的节点，其两个指针均指向 None 。 边（edge）：连接两个节点的线段，即节点引用（指针）。 节点所在的层（level）：从顶至底递增，根节点所在层为 1 。 节点的度（degree）：节点的子节点的数量。在二叉树中，度的取值范围是 0、1、2 。 二叉树的高度（height）：从根节点到最远叶节点所经过的边的数量。 节点的深度（depth）：从根节点到该节点所经过的边的数量。 节点的高度（height）：从距离该节点最远的叶节点到该节点所经过的边的数量。 ","permalink":"https://houjinghao123.github.io/posts/%E5%A4%8D%E4%B9%A0%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E6%A0%91/","summary":"\u003ch1 id=\"复习数据结构二叉树\"\u003e复习数据结构二叉树\u003c/h1\u003e\n\u003ch2 id=\"什么是二叉树\"\u003e什么是二叉树？\u003c/h2\u003e\n\u003cp\u003e二叉树（binary tree）是一种非线性数据结构，代表“祖先”与“后代”之间的派生关系，体现了“一分为二”的分治逻辑。与链表类似，二叉树的基本单元是节点，每个节点包含值、左子节点引用和右子节点引用。每个节点都有两个引用（指针），分别指向左子节点（left-child node）和右子节点（right-child node），该节点被称为这两个子节点的父节点（parent node）。当给定一个二叉树的节点时，我们将该节点的左子节点及其以下节点形成的树称为该节点的左子树（left subtree），同理可得右子树（right subtree）。\u003c/p\u003e","title":"复习数据结构二叉树"},{"content":"一、Mysql存储引擎 1.Mysql的体系结构 连接层 服务层 引擎层 存储层 2.存储引擎介绍 存储引擎就是存储数据、建立索引、更新/查询数据等技术的实现方式 。存储引擎是基于表的，而不是基于库的，所以存储引擎也可被称为表类型 。我们可以在创建表的时候，来指定选择的存储引擎，如果没有指定将自动选择默认的存储引擎(InnoDB)。\n2.1InnoDB 1).介绍\nInnoDB是一种兼顾高可靠性和高性能的通用存储引擎，在 MySQL 5.5之后，InnoDB是默认的MySQL存储引擎。\n2).特点\n●DML操作遵循ACID模型，支持事务；\n●行锁，提高并发访问性能；\n●支持外键FOREIGN KEY约束，保证数据的完整性和正确性；\n3).文件\n参数：innodb_file_per_table\n1show variables like \u0026#39;innodb_file_per_table\u0026#39;; 如果该参数开启，代表对于InnoDB引擎的表，每一张表都对应一个ibd文件。，存储该表的表结构（frm-早期的 、sdi-新版的）、数据和索引。\n1show variables like \u0026#39;%datadir%\u0026#39;; 使用上面的命令查看自己表数据存储位置\n4)*逻辑存储结构*\n表空间: InnoDB存储引擎逻辑结构的最高层，ibd文件其实就是表空间文件，在表空间中可以包含多个Segment段。 段:表空间是由各个段组成的，常见的段有数据段、索引段、回滚段等。InnoDB中对于段的管理，都是引擎自身完成，不需要人为对其控制，一个段中包含多个区。 区:区是表空间的单元结构，每个区的大小为1M。默认情况下， InnoDB存储引擎页大小为16K，即一个区中一共有64个连续的页。 页:页是组成区的最小单元，页也是InnoDB存储引擎磁盘管理的最小单元 ，每个页的大小默认为 16KB。为了保证页的连续性，InnoDB存储引擎每次从磁盘申请 4-5个区。 行: InnoDB存储引擎是面向行的，也就是说数据是按行进行存放的，在每一行中除了定义表时所指定的字段以外，还包含两个隐藏字段(后面会详细介绍)。 2.2 MyISAM 1).介绍\nMyISAM是MySQL早期的默认存储引擎。\n2).特点\n不支持事务，不支持外键\n支持表锁，不支持行锁\n访问速度快\n2.3Memory 1).介绍\nMemory引擎的表数据时存储在内存中的，由于受到硬件问题、或断电问题的影响，只能将这些表作为临时表或缓存使用。\n2).特点\n存放在内存中\nhash索引（默认）\n2.4三种索引的区别 二、索引 索引概述 索引（index）是帮助MySQL高效获取数据的数据结构(有序)。在数据之外，数据库系统还维护着满足特定查找算法的数据结构，这些数据结构以某种方式引用（指向）数据，这样就可以在这些数据结构上实现高级查找算法，这种数据结构就是索引。\n无索引和有索引的情况下 假如我们要执行的SQL语句为 ： select* from user where age= 45;\n在无索引的情况下会对全表进行扫描效率很低\n在有索引时我们可以根据这个表建立索引，一般索引都是B+tree，然后会根据age进行查找\n优势 劣势 提高数据检索的效率，降低数据库IO成本 索引列也要占用空间 通过索引列对数据进行排序，降低降低CPU的消耗 数据排序的成本，降低CPU的消耗。 索引大大提高了查询效率，同时却也降低更新表的速度如对表进行INSERT、UPDATE、DELETE时，效率降低 索引结构 索引结构 描述 B+Tree索引 最常见的索引类型，大部分引擎都支持 B+树索引 Hash索引 底层数据结构是用哈希表实现的,只有精确匹配索引列的查询才有效,不支持范围查询 R-tree(空间索引） 空间索引是MyISAM引擎的一个特殊索引类型，主要用于地理空间数据类型，通常使用较少 Full-text(全文索引) 是一种通过建立倒排索引，快速匹配文档的方式。类似于Lucene, Solr,ES 上述是MySQL中所支持的所有的索引结构，接下来，我们再来看看不同的存储引擎对于索引结构的支持情况。\n索引分类 在MySQL数据库，将索引的具体类型主要分为以下几类：主键索引、唯一索引、常规索引、全文索引。\n索引基础分类 分类 含义 特点 关键字 主键索引 针对于表中主键创建的索引 默认自动创建,只能有一个 PRIMARY 唯一索引 避免同一个表中某数据列中的值重复 可以有多个 UNIQUE 常规索引 快速定位特定数据 可以有多个 全文索引 全文索引查找的是文本中的关键词，而不是比较索引中的值 可以有多个 FULLTEXT 聚集索引\u0026amp;二级索引 而在在InnoDB存储引擎中，根据索引的存储形式，又可以分为以下两种：\n分类 含义 特点 聚集索引(ClusteredIndex) 将数据存储与索引放到了一块，索引结构的叶子节点保存了行数据 必须有,而且只有一个 二级索引(SecondaryIndex) 将数据与索引分开存储，索引结构的叶子节点关 可以存在多个 聚集索引选取规则:\n如果存在主键，主键索引就是聚集索引。 如果不存在主键，将使用第一个唯一（UNIQUE）索引作为聚集索引。 如果表没有主键，或没有合适的唯一索引，则InnoDB会自动生成一个rowid作为隐藏的聚集索引。 接下来，我们来分析一下，当我们执行如下的SQL语句时，具体的查找过程是什么样子的。\n具体过程如下:\n①.由于是根据name字段进行查询，所以先根据name=\u0026lsquo;Arm\u0026rsquo;到name字段的二级索引中进行匹配查找。但是在二级索引中只能查找到 Arm对应的主键值 10。\n②.由于查询返回的数据是*，所以此时，还需要根据主键值10，到聚集索引中查找10对应的记录，最终找到10对应的行row。\n③.最终拿到这一行的数据，直接返回即可。\n回表查询： 这种先到二级索引中查找数据，找到主键值, 然后再到聚集索引中根据主键值，获取数据的方式，就称之为回表查询\n索引语法 1）创建索引\n1CREATE [UNIQUE | FULLTEXT] INDEX index_name NO table_name(index_col_name,....); 2）查看索引\n1SHOW INDEX FROM table_name; 3)删除索引\n1DROP INDEX index_name NO table_name; 三、SQL性能分析 3.1SQL执行频率 MySQL客户端连接成功后，通过 show[ session | global ] status命令可以提供服务器状态信息。通过如下指令，可以查看当前数据库的INSERT、UPDATE、DELETE、SELECT的访问频次：\n1-- session 是查看当前会话； 2-- global 是查询全局数据； 3 SHOW GLOBAL STATUS LIKE \u0026#39;COM_______\u0026#39;; 通过上述指令，我们可以查看到当前数据库到底是以查询为主，还是以增删改为主，从而为数据库优化提供参考依据。如果是以增删改为主，我们可以考虑不对其进行索引的优化。如果是以查询为主，那么就要考虑对数据库的索引进行优化了。\n3.2慢查询日志 慢查询日志记录了所有执行时间超过指定参数（long_query_time，单位：秒，默认10秒）的所有SQL语句的日志。\nMySQL的慢查询日志默认没有开启，我们可以查看一下系统变量 slow_query_log。\n1show variables like \u0026#39;%quer%\u0026#39;; 在windos下开启慢查询 临时开启慢查询（重启MySQL后就会失效）\n1set global slow_query_log=\u0026#39;ON\u0026#39;; 2 3--设置慢查询日志存放的位置 4set global slow_query_log_file=\u0026#39;D:\\\\home\\\\mysql.log\u0026#39;; 永久开启慢查询（mysql版本5.7.30）\n找到mysql的安装目录，找到my.ini文件夹在[mysqld]处加入以下代码开启慢查询，永久有效。\n#存储位置 datadir=D:/soft/mysql-5.30/Data #开启慢查询 slow-query-log=1 #D:/soft/mysql-5.30/Data/HJH-slow.log 慢查询日志文件存储位置 slow_query_log_file=\u0026#34;HJH-slow.log\u0026#34; #慢查询判断时间 long_query_time=10 在Ubuntu中开启慢查询 //TODO\nprofile详情 show profiles能够在做SQL优化时帮助我们了解时间都耗费到哪里去了。通过have_profiling参数，能够看到当前MySQL是否支持profile操作： 1-- 查看数据库是否支持profile 2SELECT @@have_profiling; 3 4-- 开启profile 5SET [session | global]profiling = 1; 开启profiling后就能使用profiling相关命令来查询执行过的SQL语句\n执行一系列的业务SQL的操作，然后通过如下指令查看指令的执行耗时：\n1 -- 查看每一条SQL的耗时基本情况 2show profiles; 3 4-- 查看指定query_id的SQL语句各个阶段的耗时情况 5show profile for query query_id; 6 7-- 查看指定query_id的SQL语句CPU的使用情况 8show profile cpu for query query_id; explain EXPLAIN或者 DESC命令获取 MySQL如何执行 SELECT语句的信息，包括在 SELECT语句执行过程中表如何连接和连接的顺序。\n1-- 直接在select语句前加上关键字 explain/desc 2EXPLAIN SELECT 字段列表 FROM 表明 WHERE 条件; Explain执行计划中各个字段的含义:\n字段 含义 id select查询的序列号，表示查询中执行select子句或者是操作表的顺序(id相同，执行顺序从上到下；id不同，值越大，越先执行)。 select_type 表示 SELECT的类型，常见的取值有 SIMPLE（简单表，即不使用表连接或者子查询）、PRIMARY（主查询，即外层的查询）、UNION（UNION中的第二个或者后面的查询语句）、SUBQUERY（SELECT/WHERE之后包含了子查询）等 type 表示连接类型，性能由好到差的连接类型为NULL、system、const、eq_ref、ref、range、 index、all 。 possible_key 显示可能应用在这张表上的索引，一个或多个。 key 实际使用的索引，如果为NULL，则没有使用索引。 key_len 表示索引中使用的字节数， 该值为索引字段最大可能长度，并非实际使用长度，在不损失精确性的前提下，长度越短越好 。 rows MySQL认为必须要执行查询的行数，在innodb引擎的表中，是一个估计值，可能并不总是准确的。 filtered 表示返回结果的行数占需读取行数的百分比， filtered的值越大越好。 Extra Using where: Using Index 查找使用了索引，但是需要的数据都在索引列表中找到，所以不需要回表查询 。Using index condition:查找使用了索引，但是需要回表查询数据 五、索引的使用 最左前缀法则 使用联合索引时要遵守最左前缀法则。最左前缀法则是指在查询索引时从索引的最左列开始，并且不跳过索引中的列。如果跳过某一列，后面的字段索引失效。\ntb_user表\n1create table tb_user( 2\tid int primary key auto_increment comment \u0026#39;主键\u0026#39;, 3\tname varchar(50) not null comment \u0026#39;用户名\u0026#39;, 4\tphone varchar(11) not null comment \u0026#39;手机号\u0026#39;, 5\temail varchar(100) comment \u0026#39;邮箱\u0026#39;, 6\tprofession varchar(11) comment \u0026#39;专业\u0026#39;, 7\tage tinyint unsigned comment \u0026#39;年龄\u0026#39;, 8\tgender char(1) comment \u0026#39;性别 , 1: 男, 2: 女\u0026#39;, 9\tstatus char(1) comment \u0026#39;状态\u0026#39;, 10\tcreatetime datetime comment \u0026#39;创建时间\u0026#39; 11) comment \u0026#39;系统用户表\u0026#39;; 12 13 14INSERT INTO itcast.tb_user (name, phone, email, profession, age, gender, status, createtime) VALUES (\u0026#39;吕布\u0026#39;, \u0026#39;17799990000\u0026#39;, \u0026#39;lvbu666@163.com\u0026#39;, \u0026#39;软件工程\u0026#39;, 23, \u0026#39;1\u0026#39;, \u0026#39;6\u0026#39;, \u0026#39;2001-02-02 00:00:00\u0026#39;); 15INSERT INTO itcast.tb_user (name, phone, email, profession, age, gender, status, createtime) VALUES (\u0026#39;曹操\u0026#39;, \u0026#39;17799990001\u0026#39;, \u0026#39;caocao666@qq.com\u0026#39;, \u0026#39;通讯工程\u0026#39;, 33, \u0026#39;1\u0026#39;, \u0026#39;0\u0026#39;, \u0026#39;2001-03-05 00:00:00\u0026#39;); 16INSERT INTO itcast.tb_user (name, phone, email, profession, age, gender, status, createtime) VALUES (\u0026#39;赵云\u0026#39;, \u0026#39;17799990002\u0026#39;, \u0026#39;17799990@139.com\u0026#39;, \u0026#39;英语\u0026#39;, 34, \u0026#39;1\u0026#39;, \u0026#39;2\u0026#39;, \u0026#39;2002-03-02 00:00:00\u0026#39;); 17INSERT INTO itcast.tb_user (name, phone, email, profession, age, gender, status, createtime) VALUES (\u0026#39;孙悟空\u0026#39;, \u0026#39;17799990003\u0026#39;, \u0026#39;17799990@sina.com\u0026#39;, \u0026#39;工程造价\u0026#39;, 54, \u0026#39;1\u0026#39;, \u0026#39;0\u0026#39;, \u0026#39;2001-07-02 00:00:00\u0026#39;); 18INSERT INTO itcast.tb_user (name, phone, email, profession, age, gender, status, createtime) VALUES (\u0026#39;花木兰\u0026#39;, \u0026#39;17799990004\u0026#39;, \u0026#39;19980729@sina.com\u0026#39;, \u0026#39;软件工程\u0026#39;, 23, \u0026#39;2\u0026#39;, \u0026#39;1\u0026#39;, \u0026#39;2001-04-22 00:00:00\u0026#39;); 19INSERT INTO itcast.tb_user (name, phone, email, profession, age, gender, status, createtime) VALUES (\u0026#39;大乔\u0026#39;, \u0026#39;17799990005\u0026#39;, \u0026#39;daqiao666@sina.com\u0026#39;, \u0026#39;舞蹈\u0026#39;, 22, \u0026#39;2\u0026#39;, \u0026#39;0\u0026#39;, \u0026#39;2001-02-07 00:00:00\u0026#39;); 20INSERT INTO itcast.tb_user (name, phone, email, profession, age, gender, status, createtime) VALUES (\u0026#39;露娜\u0026#39;, \u0026#39;17799990006\u0026#39;, \u0026#39;luna_love@sina.com\u0026#39;, \u0026#39;应用数学\u0026#39;, 24, \u0026#39;2\u0026#39;, \u0026#39;0\u0026#39;, \u0026#39;2001-02-08 00:00:00\u0026#39;); 21INSERT INTO itcast.tb_user (name, phone, email, profession, age, gender, status, createtime) VALUES (\u0026#39;程咬金\u0026#39;, \u0026#39;17799990007\u0026#39;, \u0026#39;chengyaojin@163.com\u0026#39;, \u0026#39;化工\u0026#39;, 38, \u0026#39;1\u0026#39;, \u0026#39;5\u0026#39;, \u0026#39;2001-05-23 00:00:00\u0026#39;); 22INSERT INTO itcast.tb_user (name, phone, email, profession, age, gender, status, createtime) VALUES (\u0026#39;项羽\u0026#39;, \u0026#39;17799990008\u0026#39;, \u0026#39;xiaoyu666@qq.com\u0026#39;, \u0026#39;金属材料\u0026#39;, 43, \u0026#39;1\u0026#39;, \u0026#39;0\u0026#39;, \u0026#39;2001-09-18 00:00:00\u0026#39;); 23INSERT INTO itcast.tb_user (name, phone, email, profession, age, gender, status, createtime) VALUES (\u0026#39;白起\u0026#39;, \u0026#39;17799990009\u0026#39;, \u0026#39;baiqi666@sina.com\u0026#39;, \u0026#39;机械工程及其自动化\u0026#39;, 27, \u0026#39;1\u0026#39;, \u0026#39;2\u0026#39;, \u0026#39;2001-08-16 00:00:00\u0026#39;); 24INSERT INTO itcast.tb_user (name, phone, email, profession, age, gender, status, createtime) VALUES (\u0026#39;韩信\u0026#39;, \u0026#39;17799990010\u0026#39;, \u0026#39;hanxin520@163.com\u0026#39;, \u0026#39;无机非金属材料工程\u0026#39;, 27, \u0026#39;1\u0026#39;, \u0026#39;0\u0026#39;, \u0026#39;2001-06-12 00:00:00\u0026#39;); 25INSERT INTO itcast.tb_user (name, phone, email, profession, age, gender, status, createtime) VALUES (\u0026#39;荆轲\u0026#39;, \u0026#39;17799990011\u0026#39;, \u0026#39;jingke123@163.com\u0026#39;, \u0026#39;会计\u0026#39;, 29, \u0026#39;1\u0026#39;, \u0026#39;0\u0026#39;, \u0026#39;2001-05-11 00:00:00\u0026#39;); 26INSERT INTO itcast.tb_user (name, phone, email, profession, age, gender, status, createtime) VALUES (\u0026#39;兰陵王\u0026#39;, \u0026#39;17799990012\u0026#39;, \u0026#39;lanlinwang666@126.com\u0026#39;, \u0026#39;工程造价\u0026#39;, 44, \u0026#39;1\u0026#39;, \u0026#39;1\u0026#39;, \u0026#39;2001-04-09 00:00:00\u0026#39;); 27INSERT INTO itcast.tb_user (name, phone, email, profession, age, gender, status, createtime) VALUES (\u0026#39;狂铁\u0026#39;, \u0026#39;17799990013\u0026#39;, \u0026#39;kuangtie@sina.com\u0026#39;, \u0026#39;应用数学\u0026#39;, 43, \u0026#39;1\u0026#39;, \u0026#39;2\u0026#39;, \u0026#39;2001-04-10 00:00:00\u0026#39;); 28INSERT INTO itcast.tb_user (name, phone, email, profession, age, gender, status, createtime) VALUES (\u0026#39;貂蝉\u0026#39;, \u0026#39;17799990014\u0026#39;, \u0026#39;84958948374@qq.com\u0026#39;, \u0026#39;软件工程\u0026#39;, 40, \u0026#39;2\u0026#39;, \u0026#39;3\u0026#39;, \u0026#39;2001-02-12 00:00:00\u0026#39;); 29INSERT INTO itcast.tb_user (name, phone, email, profession, age, gender, status, createtime) VALUES (\u0026#39;妲己\u0026#39;, \u0026#39;17799990015\u0026#39;, \u0026#39;2783238293@qq.com\u0026#39;, \u0026#39;软件工程\u0026#39;, 31, \u0026#39;2\u0026#39;, \u0026#39;0\u0026#39;, \u0026#39;2001-01-30 00:00:00\u0026#39;); 30INSERT INTO itcast.tb_user (name, phone, email, profession, age, gender, status, createtime) VALUES (\u0026#39;芈月\u0026#39;, \u0026#39;17799990016\u0026#39;, \u0026#39;xiaomin2001@sina.com\u0026#39;, \u0026#39;工业经济\u0026#39;, 35, \u0026#39;2\u0026#39;, \u0026#39;0\u0026#39;, \u0026#39;2000-05-03 00:00:00\u0026#39;); 31INSERT INTO itcast.tb_user (name, phone, email, profession, age, gender, status, createtime) VALUES (\u0026#39;嬴政\u0026#39;, \u0026#39;17799990017\u0026#39;, \u0026#39;8839434342@qq.com\u0026#39;, \u0026#39;化工\u0026#39;, 38, \u0026#39;1\u0026#39;, \u0026#39;1\u0026#39;, \u0026#39;2001-08-08 00:00:00\u0026#39;); 32INSERT INTO itcast.tb_user (name, phone, email, profession, age, gender, status, createtime) VALUES (\u0026#39;狄仁杰\u0026#39;, \u0026#39;17799990018\u0026#39;, \u0026#39;jujiamlm8166@163.com\u0026#39;, \u0026#39;国际贸易\u0026#39;, 30, \u0026#39;1\u0026#39;, \u0026#39;0\u0026#39;, \u0026#39;2007-03-12 00:00:00\u0026#39;); 33INSERT INTO itcast.tb_user (name, phone, email, profession, age, gender, status, createtime) VALUES (\u0026#39;安琪拉\u0026#39;, \u0026#39;17799990019\u0026#39;, \u0026#39;jdodm1h@126.com\u0026#39;, \u0026#39;城市规划\u0026#39;, 51, \u0026#39;2\u0026#39;, \u0026#39;0\u0026#39;, \u0026#39;2001-08-15 00:00:00\u0026#39;); 34INSERT INTO itcast.tb_user (name, phone, email, profession, age, gender, status, createtime) VALUES (\u0026#39;典韦\u0026#39;, \u0026#39;17799990020\u0026#39;, \u0026#39;ycaunanjian@163.com\u0026#39;, \u0026#39;城市规划\u0026#39;, 52, \u0026#39;1\u0026#39;, \u0026#39;2\u0026#39;, \u0026#39;2000-04-12 00:00:00\u0026#39;); 35INSERT INTO itcast.tb_user (name, phone, email, profession, age, gender, status, createtime) VALUES (\u0026#39;廉颇\u0026#39;, \u0026#39;17799990021\u0026#39;, \u0026#39;lianpo321@126.com\u0026#39;, \u0026#39;土木工程\u0026#39;, 19, \u0026#39;1\u0026#39;, \u0026#39;3\u0026#39;, \u0026#39;2002-07-18 00:00:00\u0026#39;); 36INSERT INTO itcast.tb_user (name, phone, email, profession, age, gender, status, createtime) VALUES (\u0026#39;后羿\u0026#39;, \u0026#39;17799990022\u0026#39;, \u0026#39;altycj2000@139.com\u0026#39;, \u0026#39;城市园林\u0026#39;, 20, \u0026#39;1\u0026#39;, \u0026#39;0\u0026#39;, \u0026#39;2002-03-10 00:00:00\u0026#39;); 37INSERT INTO itcast.tb_user (name, phone, email, profession, age, gender, status, createtime) VALUES (\u0026#39;姜子牙\u0026#39;, \u0026#39;17799990023\u0026#39;, \u0026#39;37483844@qq.com\u0026#39;, \u0026#39;工程造价\u0026#39;, 29, \u0026#39;1\u0026#39;, \u0026#39;4\u0026#39;, \u0026#39;2003-05-26 00:00:00\u0026#39;); 在 tb_user表中，有一个联合索引，这个联合索引涉及到三个字段，顺序分别为：profession， age，status。\n对于最左前缀法则指的是，查询时，最左边的列，也就是profession必须存在，否则索引全部失效。\n从图中可以看出只要有profession存在就会使用索引，当profession不存在即使使用后面的属性也不会触发索引。\n思考题： 当执行SQL语句: explain select * from tb_user where age = 3 and status=\u0026lsquo;0’ and profession=\u0026lsquo;软件工程\u0026rsquo;;时，是否满足最左前缀法则，走不走 上述的联合索引，索引长度？\n很显然时走的，最左前缀法则中指的最最左边的列，是指在查询时，联合索引的最左边的字段（就是第一个字段）必须存在，与我们编写SQL时。条件的顺序无关。\n范围查询 联合索引中，出现范围查询(\u0026gt;,\u0026lt;)，范围查询右侧的列索引失效。\n在使用范围查询时有效字段是38，但是在不使用时有效字段是42，说明在使用范围查询时有字段失效了。\n所以在业务允许的情况下，尽可能的使用类似的\u0026gt;=或\u0026lt;=这类的范围查询。\n范围失效的情况 索引列运算 不要在索引列上进行运算操作，索引将失效。\n字符串不加引号 不要在索引列上进行运算操作，索引将失效。\n模糊查询使用头部模糊查询，索引失效 如果仅仅是尾部模糊匹配，索引不会失效。如果是头部模糊匹配，索引失效。\nor连接条件 用or分割开的条件，如果or前的条件中的列有索引，而后面的列中没有索引，那么涉及的索引都不会被用到。\n-数据分布影响。\n如果MySQL评估使用索引比全表更慢，则不使用索引。\nSQL提示 SQL提示，是优化数据库的一个重要手段，简单来说，就是在SQL语句中加入一些人为的提示来达到优化操作的目的\n1). use index ：建议MySQL使用哪一个索引完成此次查询（仅仅是建议，mysql内部还会再次进行评估）。\n1explain select * from tb_user use index(idx_user_pro) where profession=\u0026#39;软件工程\u0026#39; 2). ignore index ：忽略指定的索引。\n1explain select * from tb_user ignore index(idx_user_pro) where profession=\u0026#39;软件工程\u0026#39; 3). force index ：强制使用索引。\n1explain select * from tb_user ignore index(idx_user_pro) where profession=\u0026#39;软件工程\u0026#39; 覆盖索引 尽量使用覆盖索引，减少select*。那么什么是覆盖索引呢？覆盖索引是指查询使用了索引，并且需要返回的列，在该索引中已经全部能够找到 。\nB.执行SQL: select * from tb_user where id =2;\n根据id查询，直接走聚集索引查询一次索引扫描，直接返回数据，性能高。\nc.执行SQL: select id ,name from tb_user where name =\u0026lsquo;Arm\u0026rsquo;;\n根据name字段查询辅助索引，id和name在name的二级索引中都是可以直接获取到的，所以不需要回表查询，性能高。\nd.执行SQL:select id ,name ,gender from tb_user where name =\u0026lsquo;Arm\u0026rsquo;;\n由于在name的二级索引中，不包含gender，所以，需要两次索引扫描，也就是需要回表查询，性能相对较差一点。\n前缀索引 当字段类型为字符串（varchar，text，longtext等）时，有时候需要索引很长的字符串，这会让索引变得很大，查询时，浪费大量的磁盘IO，影响查询效率。此时可以只将字符串的一部分前缀，建立索引，这样可以大大节约索引空间，从而提高索引效率。\n1).语法\n1create index idx_xxxx on table_name(colum(n)); 示例\n在tb_user的表的email字段，建立长度未5的前缀索引。\n1create index idx_email_5 on tb_user(email(5)); 2)查询流程\n单列索引和联合索引 单列索引：即一个索引只包含单个列。\n联合索引：即一个索引包含了多个列。\n在业务场景中，如果存在多个查询条件，考虑针对于查询字段建立索引时，建议建立联合索引，而非单列索引。\n索引设计原则 1).针对于数据量较大，且查询比较频繁的表建立索引。\n2).针对于常作为查询条件（where）、排序（order by）、分组（group by）操作的字段建立索引。\n3).尽量选择区分度高的列作为索引，尽量建立唯一索引，区分度越高，使用索引的效率越高。\n4).如果是字符串类型的字段，字段的长度较长，可以针对于字段的特点，建立前缀索引。\n5).尽量使用联合索引，减少单列索引，查询时，联合索引很多时候可以覆盖索引，节省存储空间，避免回表，提高查询效率。\n6).要控制索引的数量，索引并不是多多益善，索引越多，维护索引结构的代价也就越大，会影响增删改的效率。\n7).如果索引列不能存储NULL值，请在创建表时使用NOT NULL约束它。当优化器知道每列是否包含NULL值时，它可以更好地确定哪个索引最有效地用于查询。\n","permalink":"https://houjinghao123.github.io/posts/mysql%E5%BC%95%E6%93%8E%E4%BB%8B%E7%BB%8D/","summary":"\u003ch1 id=\"一mysql存储引擎\"\u003e\u003cstrong\u003e一、Mysql存储引擎\u003c/strong\u003e\u003c/h1\u003e\n\u003ch2 id=\"1mysql的体系结构\"\u003e\u003cstrong\u003e1.Mysql的体系结构\u003c/strong\u003e\u003c/h2\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"https://i.postimg.cc/MpV3xV4W/screenshot-16.png\" alt=\"\"  /\u003e\n\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e连接层\u003c/li\u003e\n\u003cli\u003e服务层\u003c/li\u003e\n\u003cli\u003e引擎层\u003c/li\u003e\n\u003cli\u003e存储层\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch2 id=\"2存储引擎介绍\"\u003e\u003cstrong\u003e2.存储引擎介绍\u003c/strong\u003e\u003c/h2\u003e\n\u003cp\u003e存储引擎就是存储数据、建立索引、更新/查询数据等技术的实现方式 。存储引擎是基于表的，而不是基于库的，\u003ccode\u003e所以存储引擎也可被称为表类型\u003c/code\u003e 。我们可以在创建表的时候，来指定选择的存储引擎，如果没有指定将自动选择默认的存储引擎(InnoDB)。\u003c/p\u003e","title":"Mysql存储引擎"},{"content":"事务 事务 是一组操作的集合，它是一个不可分割的工作单位，事务会把所有的操作作为一个整体一起向系统提交或撤销操作请求，即这些操作要么同时成功，要么同时失败。\n使用到的关键字 1set autocommit=0; 2start transaction; 3commit; 4rollback; 5 6savepoint 断点 7commit to 断点 8rollback to 断点 事务四大特性 原子性（Atomicity）：事务是不可分割的最小操作单元，要么全部成功，要么全部失败 一致性（Consistency）：事务完成时，必须使所有的数据都保持一致状态 隔离性（Isolation）：数据库系统提供的隔离机制，保证事务在不受外部并发操作影响的独立环境下运行 持久性（Durability）：事务一旦提交或回滚，它对数据库中的数据改变就是永久的 并发事务问题 1） 脏读：一个事务读到另一个事务还没有提交的数据\n比如B读取到了A未提交的数据。\n2） 不可重复读：一个事务先后读取同一条记录，但两次读取的数据不同，称为不可重复读。\n事务A两次读取同一条记录，但是读取到的数据却是不一样的。\n3） 幻读：一个事务读取数据时，另外一个事务进行更新，导致第一个事务读取到了没有更新的数据\n不可重复读和幻读的区别\n不可重复读是读取了其他事务更改的数据 -\u0026gt;update 幻读是读取了其他事务新增的数据 -\u0026gt;insert和delete\n事务的隔离级别 为了解决并发事务所引发的问题，在数据库中引入了事务隔离级别。\n数据库事务的隔离级别有4个，由低到高依次为Read uncommitted 、Read committed 、Repeatable read 、Serializable ，这四个级别可以逐个解决脏读 、不可重复读 、幻读 这几类问题。\nMySql默认REPEATABLE_READ\n隔离级别 脏读 不可重复读 幻读 Read uncommitted(读未提交) Y Y Y Read committed(读以提交) N Y Y Repeatable Read(可重复读) N N Y Serializable N N N 查看事务隔离级别 1SELECT @@TRANSACTION_ISOLATION; 设置事务隔离级别 1SET [ SESSION | GLOBAL ] TRANSACTION ISOLATION LEVEL { READ UNCOMMITTED | 2READ COMMITTED | REPEATABLE READ | SERIALIZABLE } ","permalink":"https://houjinghao123.github.io/posts/mysql%E4%BA%8B%E5%8A%A1/","summary":"\u003ch1 id=\"事务\"\u003e事务\u003c/h1\u003e\n\u003cp\u003e事务 是一组操作的集合，它是一个不可分割的工作单位，事务会把所有的操作作为一个整体一起向系统提交或撤销操作请求，即这些操作要么同时成功，要么同时失败。\u003c/p\u003e","title":"Mysql事务"},{"content":"Mysql常用函数 聚合函数 函数 功能 count 统计数量 max 最大值 min 最小值 avg 平均值 sum 求和 字符串函数 函数 功能 CONCAT(S1,S2,\u0026hellip;Sn) 字符串拼接，将S1，S2，\u0026hellip; Sn拼接成一个字符串 LOWER(str) 将字符串str全部转为小写 UPPER(str) 将字符串str全部转为大写 LPAD(str,n,pad) 左填充，用字符串pad对str的左边进行填充，达到n个字符串长度 RPAD(str,n,pad) 右填充，用字符串pad对str的右边进行填充，达到n个字符串长度 TRIM(str) 去掉字符串头部和尾部的空格 SUBSTRING(str,start,len) 返回从字符串str从start位置起的len个长度的字符串 数值函数 函数 功能 CEIL(x) 向上取整 FLOOR(x) 向下取整 MOD(x,y) 返回x/y的模 RAND() 返回0~1内的随机数 ROUND(x,y) 求参数x的四舍五入的值，保留y位小数 例题：\n通过数据库的函数，生成一个六位数的随机验证码。\n1 select lpad(round(rand()*1000000,0),6,\u0026#39;0\u0026#39;) 日期函数 函数 功能 CURDATE() 返回当前日期 CURTIME() 返回当前时间 NOW() 返回当前日期和时间 YEAR(date) 获取指定date的年份 MONTH(date) 获取指定date的月份 DAY(date) 获取指定date的日期 DATE_ADD(date, INTERVAL exprtype) 返回一个日期/时间值加上一个时间间隔expr后的时间值 DATEDIFF(date1,date2) 返回起始时间date1 和 结束时间date2之间的天数 例题：\n查询所有员工的入职天数，并根据入职天数倒序排序\n1 select name, datediff(curdate(), entrydate) as \u0026#39;entrydays\u0026#39; from emp order by 2 entrydays desc; 流程函数 函数 功能 IF(value , t , f) 如果value为true，则返回t，否则返回f IFNULL(value1 , value2) 如果value1不为空，返回value1，否则返回value2 CASE WHEN [ val1 ] THEN [res1] \u0026hellip;ELSE [ default ] END 如果val1为true，返回res1，\u0026hellip; 否则返回default默认值 CASE [ expr ] WHEN [ val1 ] THEN[res1] \u0026hellip; ELSE [ default ] END 如果expr的值等于val1，返回res1，\u0026hellip; 否则返回default默认值 ","permalink":"https://houjinghao123.github.io/posts/mysql%E5%B8%B8%E7%94%A8%E5%87%BD%E6%95%B0/","summary":"\u003ch1 id=\"mysql常用函数\"\u003eMysql常用函数\u003c/h1\u003e\n\u003cp\u003e\u003cimg src=\"https://i.postimg.cc/cJQ332yQ/screenshot-12.png\" /\u003e\u003cbr\u003e\u003c/p\u003e\n\u003ch2 id=\"聚合函数\"\u003e\u003cstrong\u003e聚合函数\u003c/strong\u003e\u003c/h2\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth style=\"text-align: left\"\u003e函数\u003c/th\u003e\n          \u003cth style=\"text-align: left\"\u003e功能\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003ecount\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e统计数量\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003emax\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e最大值\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003emin\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e最小值\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003eavg\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e平均值\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003esum\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e求和\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003ch2 id=\"字符串函数\"\u003e\u003cstrong\u003e字符串函数\u003c/strong\u003e\u003c/h2\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth style=\"text-align: left\"\u003e函数\u003c/th\u003e\n          \u003cth style=\"text-align: left\"\u003e功能\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003eCONCAT(S1,S2,\u0026hellip;Sn)\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e字符串拼接，将S1，S2，\u0026hellip; Sn拼接成一个字符串\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003eLOWER(str)\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e将字符串str全部转为小写\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003eUPPER(str)\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e将字符串str全部转为大写\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003eLPAD(str,n,pad)\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e左填充，用字符串pad对str的左边进行填充，达到n个字符串长度\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003eRPAD(str,n,pad)\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e右填充，用字符串pad对str的右边进行填充，达到n个字符串长度\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003eTRIM(str)\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e去掉字符串头部和尾部的空格\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003eSUBSTRING(str,start,len)\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e返回从字符串str从start位置起的len个长度的字符串\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003ch2 id=\"数值函数\"\u003e\u003cstrong\u003e数值函数\u003c/strong\u003e\u003c/h2\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth style=\"text-align: left\"\u003e函数\u003c/th\u003e\n          \u003cth style=\"text-align: left\"\u003e功能\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003eCEIL(x)\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e向上取整\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003eFLOOR(x)\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e向下取整\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003eMOD(x,y)\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e返回x/y的模\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003eRAND()\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e返回0~1内的随机数\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003eROUND(x,y)\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e求参数x的四舍五入的值，保留y位小数\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003cp\u003e例题：\u003cbr\u003e\n通过数据库的函数，生成一个六位数的随机验证码。\u003c/p\u003e","title":"Mysql常用函数"},{"content":"配置硬件加速 使用SDK 管理器安装 1.选择Tools\u0026gt;SDK Manger\n2.点击 SDK Tools 标签页，然后选择 Android Emulator Hypervisor Driver\n3.点击 OK，以下载并安装 Android Emulator Hypervisor Driver。\n4.安装后，返回命令行中使用以下命令，确认驱动程序能正常运行：\n1sc query aehd 如何出现错误先尝试关闭 Hyper-V 禁用Hyper-V 在控制面板中禁用 Hyper-V 1.在dos窗口或者powershell中运行命令\n1control 2.点击程序\n3.点击启用或者关闭Windos功能\n4.展开 Hyper-V，展开 Hyper-V 平台，然后清除“Hyper-V 虚拟机监控程序”复选框。 在PowerShell 中禁用 Hyper-V 1.提升PowerShell 窗口的权限为管理员\n1 sudo 2.运行以下命令\n1Disable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V-Hypervisor 参考文献\n虚拟化应用程序无法与 Hyper-V、Device Guard 和 Credential Guard 协同工作\n","permalink":"https://houjinghao123.github.io/posts/stop-hyper-v/","summary":"\u003ch1 id=\"配置硬件加速\"\u003e配置硬件加速\u003c/h1\u003e\n\u003ch2 id=\"使用sdk-管理器安装\"\u003e使用SDK 管理器安装\u003c/h2\u003e\n\u003cp\u003e1.选择Tools\u0026gt;SDK Manger\u003cbr\u003e\n\u003cimg src=\"https://i.postimg.cc/g2zgzhDN/screenshot-9.png\" /\u003e\u003c/p\u003e","title":"Stop Hyper V"},{"content":"java集合简介 一、集合基本的关系结构 Collection 接口的接口 对象的集合（单列集合）\nList 接口：元素按进入先后有序保存，可重复 LinkedList 接口实现类， 链表， 插入删除， 没有同步， 线程不安全 ArrayList 接口实现类， 数组， 随机访问， 没有同步， 线程不安全 Vector 接口实现类 数组， 同步， 线程安全 Stack 是Vector类的实现类 Set 接口： 仅接收一次，不可重复，并做内部排序 HashSet 使用hash表（数组）存储元素 LinkedHashSet 链表维护元素的插入次序 TreeSet 底层实现为二叉树，元素排好序 Map 接口 键值对的集合 （双列集合）\nHashtable 接口实现类， 同步， 线程安全 HashMap 接口实现类 ，没有同步， 线程不安全 LinkedHashMap 双向链表和哈希表实现 WeakHashMap TreeMap 红黑树对所有的key进行排序 IdentifyHashMap 二、Collection接口及方法 2.1 添加 （1）add(E obj)：添加元素对象到当前集合中\n（2）addAll(Collection other)：添加other集合中的所有元素对象到当前集合中，即this = this ∪ other\n2.2 判断 （3）int size()：获取当前集合中实际存储的元素个数\n（4）boolean isEmpty()：判断当前集合是否为空集合\n（5）boolean contains(Object obj)：判断当前集合中是否存在一个与obj对象equals返回true的元素\n（6）boolean containsAll(Collection coll)：判断coll集合中的元素是否在当前集合中都存在。即coll集合是否是当前集合的“子集”\n（7）boolean equals(Object obj)：判断当前集合与obj是否相等\n2.3 删除 （8）void clear()：清空集合元素\n（9） boolean remove(Object obj) ：从当前集合中删除第一个找到的与obj对象equals返回true的元素。\n（10）boolean removeAll(Collection coll)：从当前集合中删除所有与coll集合中相同的元素。即this = this - this ∩ coll （11）boolean retainAll(Collection coll)：从当前集合中删除两个集合中不同的元素，使得当前集合仅保留与coll集合中的元素相同的元素，即当前集合中仅保留两个集合的交集，即this = this ∩ coll；\n2.4其他 （12）Object[] toArray()：返回包含当前集合中所有元素的数组\n（13）hashCode()：获取集合对象的哈希值\n（14）iterator()：返回迭代器对象，用于集合遍历\n三、List集合 3.1 List接口特点 List集合类中元素有序、且可重复，集合中的每个元素都有其对应的顺序索引。\nJDK API中List接口的实现类常用的有：ArrayList、LinkedList和Vector。\n3.2 List接口方法 List除了从Collection集合继承的方法外，List 集合里添加了一些根据索引来操作集合元素的方法。\n插入元素 void add(int index,Object ele); 在哦index位置插入ele元素 boolean add(int index ,Collection eles);从index位置开始将eles中的所有元素添加进来 获取元素 Object get(int inedx);获取指定index位置的元素 List subList(int fromIndex, int toIndex):返回从fromIndex到toIndex位置的子集合 获取元素索引 int indexOf(Object obj):返回obj在集合中首次出现的位置 int lastIndexOf(Object obj):返回obj在当前集合中末次出现的位置 删除和替换元素 Object remove(int index):移除指定index位置的元素，并返回此元素 Object set(int index, Object ele):设置指定index位置的元素为ele 3.3 List接口主要实现类：ArrayList ArrayList 是 List 接口的主要实现类 本质上，ArrayList是对象引用的一个”变长”数组 Arrays.asList(…) 方法返回的 List 集合，既不是 ArrayList 实例，也不是 Vector 实例。 Arrays.asList(…) 返回值是一个固定长度的 List 集合 3.4 List的实现类之二：LinkedList 对于频繁的插入或删除元素的操作，建议使用LinkedList类，效率较高。这是由底层采用链表（双向链表）结构存储数据决定的。 特有方法 void addFirst(Object obj) void addLast(Object obj) Object getFirst() Object getLast() Object removeFirst() Object removeLast() 3.5 List的实现类之三：Vector Vector 是一个古老的集合，JDK1.0就有了。大多数操作与ArrayList相同，区别之处在于Vector是线程安全的。 在各种List中，最好把ArrayList作为默认选择。当插入、删除频繁时，使用LinkedList；Vector总是比ArrayList慢，所以尽量避免使用。 四、Set集合 4.1 Set接口概述 Set接口是Collection的子接口，Set接口相较于Collection接口没有提供额外的方法 Set 集合不允许包含相同的元素，如果试把两个相同的元素加入同一个 Set 集合中，则添加操作失败。 Set集合支持的遍历方式和Collection集合一样：foreach和Iterator。 Set的常用实现类有：HashSet、TreeSet、LinkedHashSet。 4.2 Set主要实现类：HashSet HashSet 是 Set 接口的主要实现类，大多数时候使用 Set 集合时都使用这个实现类。\nHashSet 按 Hash 算法来存储集合中的元素，因此具有很好的存储、查找、删除性能。\nHashSet 具有以下特点：\n不能保证元素的排列顺序 HashSet 不是线程安全的 集合元素可以是 null HashSet 集合判断两个元素相等的标准：两个对象通过 hashCode() 方法得到的哈希值相等，并且两个对象的 equals() 方法返回值为true。\n对于存放在Set容器中的对象，对应的类一定要重写hashCode()和equals(Object obj)方法，以实现对象相等规则。即：“相等的对象必须具有相等的散列码”。\nHashSet集合中元素的无序性，不等同于随机性。这里的无序性与元素的添加位置有关。具体来说：我们在添加每一个元素到数组中时，具体的存储位置是由元素的hashCode()调用后返回的hash值决定的。导致在数组中每个元素不是依次紧密存放的，表现出一定的无序性。\n4.3 Set实现类之二：LinkedHashSet LinkedHashSet 是 HashSet 的子类，不允许集合元素重复。\nLinkedHashSet 根据元素的 hashCode 值来决定元素的存储位置，但它同时使用双向链表维护元素的次序，这使得元素看起来是以添加顺序保存的。\nLinkedHashSet插入性能略低于 HashSet，但在迭代访问 Set 里的全部元素时有很好的性能。\n4.4 Set实现类之三：TreeSet TreeSet 是 SortedSet 接口的实现类，TreeSet 可以按照添加的元素的指定的属性的大小顺序进行遍历。 TreeSet底层使用红黑树结构存储数据 五 Map接口 现实生活与开发中，我们常会看到这样的一类集合：用户ID与账户信息、学生姓名与考试成绩、IP地址与主机名等，这种一一对应的关系，就称作映射。Java提供了专门的集合框架用来存储这种映射关系的对象，即java.util.Map接口。\n5.1 Map接口概述 Map与Collection并列存在。用于保存具有映射关系的数据：key-value\nCollection集合称为单列集合，元素是孤立存在的。 Map集合称为双列集合，元素是成对存在的。 Map 中的 key 和 value 都可以是任何引用类型的数据。但常用String类作为Map的“键”。\nMap接口的常用实现类：HashMap、LinkedHashMap、TreeMap和``Properties。其中，HashMap是 Map 接口使用频率最高`的实现类。\n5.2 Map接口的常用方法 添加、修改操作： Object put(Object key,Object value)：将指定key-value添加到(或修改)当前map对象中 void putAll(Map m):将m中的所有key-value对存放到当前map中 删除操作： Object remove(Object key)：移除指定key的key-value对，并返回value void clear()：清空当前map中的所有数据 元素查询的操作： Object get(Object key)：获取指定key对应的value boolean containsKey(Object key)：是否包含指定的key boolean containsValue(Object value)：是否包含指定的value int size()：返回map中key-value对的个数 boolean isEmpty()：判断当前map是否为空 boolean equals(Object obj)：判断当前map和参数对象obj是否相等 元视图操作的方法： Set keySet()：返回所有key构成的Set集合 Collection values()：返回所有value构成的Collection集合 Set entrySet()：返回所有key-value对构成的Set集合 5.3 Map的主要实现类：HashMap 5.3.1 HashMap概述 HashMap是 Map 接口使用频率最高的实现类。 HashMap是线程不安全的。允许添加 null 键和 null 值。 存储数据采用的哈希表结构，底层使用一维数组+单向链表+红黑树进行key-value数据的存储。与HashSet一样，元素的存取顺序不能保证一致。 HashMap 判断两个key相等的标准是：两个 key 的hashCode值相等，通过 equals() 方法返回 true。 HashMap 判断两个value相等的标准是：两个 value 通过 equals() 方法返回 true。 5.4 Map实现类之二：LinkedHashMap LinkedHashMap 是 HashMap 的子类 存储数据采用的哈希表结构+链表结构，在HashMap存储结构的基础上，使用了一对双向链表来记录添加元素的先后顺序，可以保证遍历元素时，与添加的顺序一致。 通过哈希表结构可以保证键的唯一、不重复，需要键所在类重写hashCode()方法、equals()方法。 5.5 Map实现类之三：TreeMap TreeMap存储 key-value 对时，需要根据 key-value 对进行排序。TreeMap 可以保证所有的 key-value 对处于有序状态。 TreeSet底层使用红黑树结构存储数据 TreeMap 的 Key 的排序： 自然排序：TreeMap 的所有的 Key 必须实现 Comparable 接口，而且所有的 Key 应该是同一个类的对象，否则将会抛出 ClasssCastException 定制排序：创建 TreeMap 时，构造器传入一个 Comparator 对象，该对象负责对 TreeMap 中的所有 key 进行排序。此时不需要 Map 的 Key 实现 Comparable 接口 TreeMap判断两个key相等的标准：两个key通过compareTo()方法或者compare()方法返回0。 5.6 Map实现类之四：Hashtable Hashtable是Map接口的古老实现类，JDK1.0就提供了。不同于HashMap，Hashtable是线程安全的。 Hashtable实现原理和HashMap相同，功能相同。底层都使用哈希表结构（数组+单向链表），查询速度快。 与HashMap一样，Hashtable 也不能保证其中 Key-Value 对的顺序 Hashtable判断两个key相等、两个value相等的标准，与HashMap一致。 与HashMap不同，Hashtable 不允许使用 null 作为 key 或 value。 5.7 Map实现类之五：Properties Properties 类是 Hashtable 的子类，该对象用于处理属性文件\n由于属性文件里的 key、value 都是字符串类型，所以 Properties 中要求 key 和 value 都是字符串类型\n存取数据时，建议使用setProperty(String key,String value)方法和getProperty(String key)方法\n","permalink":"https://houjinghao123.github.io/posts/java%E9%9B%86%E5%90%88%E5%A4%8D%E4%B9%A0/","summary":"\u003ch1 id=\"java集合简介\"\u003ejava集合简介\u003c/h1\u003e\n\u003ch2 id=\"一集合基本的关系结构\"\u003e一、集合基本的关系结构\u003c/h2\u003e\n\u003cp\u003eCollection 接口的接口 对象的集合（单列集合）\u003c/p\u003e","title":"Java集合复习"},{"content":"MySQL的基本操作 1、查看所有的数据库\n1show datebases; 2、创建自己的数据库\n1create datebase 数据库名字; 3、使用自己的数据库\n1use 数据库名字; 说明：如果没有使用use语句，后面针对数据库的操作也没有加“数据名”的限定，那么会报“ERROR 1046 (3D000): No database selected”（没有选择数据库） 使用完use语句之后，如果接下来的SQL都是针对一个数据库操作的，那就不用重复use了，如果要针对另 一个数据库操作，那么要重新use。\n4、查看某个库的所有表格\n1show tables; #要求前面有use语句 2show tables from 数据库名; 5、创建新的表格\n1create table 表名称( 2字段名 数据类型, 3字段名 数据类型 4); 6、查看一个表的数据\n1select * from 数据库表名称; 7、添加一条记录\n1nsert into 表名称 values(值列表); 8、删除表格\n1drop table 表名称; 11、删除数据库\n1drop database 数据库名; ","permalink":"https://houjinghao123.github.io/posts/my-first-post/","summary":"\u003ch1 id=\"mysql的基本操作\"\u003eMySQL的基本操作\u003c/h1\u003e\n\u003cp\u003e1、查看所有的数据库\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-mysql\" data-lang=\"mysql\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"ln\"\u003e1\u003c/span\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eshow\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003edatebases\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e2、创建自己的数据库\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-mysql\" data-lang=\"mysql\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"ln\"\u003e1\u003c/span\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003ecreate\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003edatebase\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"err\"\u003e数据库名字\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pr","title":"MySQL的基本操作"},{"content":"","permalink":"https://houjinghao123.github.io/java/%E6%B5%8B%E8%AF%95%E7%94%A8/","summary":"","title":"测试"},{"content":"Kotlin入门之变量与基本的程序结构 变量 在Kontlin中变量的声明和基本使用是和Java不太相同的。\n变量的声明 声明的格式 val 变量名:类型 或者 var 变量名:类型\n在这里面主要是val和var的区别，val（value的简写）用来声明一个不可变的变量，这种变量在初始赋值之后就再也不能重新赋 值，对应Java中的final变量。 var（variable的简写）用来声明一个可变的变量，这种变量在初始赋值之后仍然可以再被重新 赋值，对应Java中的非final变量。\n那么为什么Kontlin这么看重于变量的可变和不可变呢？ 在Java中，除非你主动在变量前声明了final关键字，否则这个变量就是可变的，这样显然并不是一件好事，因为在后续开发中可能不知道谁就会将你定义的变量给修改了，然后就有可能在某些奇奇怪怪的地方出现问题了。因此时刻管理好自己的变量是很重要的。但是，不是每个人都能养成这种良好的编程习惯。 所以Kontlin在设计之初就提供了明确的规则使用val和var来声明变量的是否可变。这样的一种设计思想在现在的编程语言很常见，rust也有这样的设计风格。\nJava和Kotlin数据类型对照表\n程序的逻辑控制结构 条件语句if和when 1fun ifStructure(num1:Int,num2:Int):Int{ 2 var value=0 3 if (num1\u0026gt;num2){ 4 value =num1 5 } eles{ 6 value=num2 7 } 8 return value 9} 上面是Kotlin的if语句的基本使用基本和Java的if一样。\nKotlin中的if语句相比于Java有一个额外的功能，它是可以有返回值的,具体实现如下面的代码\n1fun largerNumber(num1: Int, num2: Int): Int { 2 val value = if (num1 \u0026gt; num2) { 3 num1 4 } else { 5\tnum2 6 } 7 return value 8} ","permalink":"https://houjinghao123.github.io/posts/kotlin%E5%85%A5%E9%97%A8%E4%B9%8B%E5%8F%98%E9%87%8F%E4%B8%8E%E5%87%BD%E6%95%B0/","summary":"\u003ch1 id=\"kotlin入门之变量与基本的程序结构\"\u003eKotlin入门之变量与基本的程序结构\u003c/h1\u003e\n\u003ch2 id=\"变量\"\u003e变量\u003c/h2\u003e\n\u003cp\u003e在Kontlin中变量的声明和基本使用是和Java不太相同的。\u003c/p\u003e","title":""},{"content":" docker 学习进行中\n安卓学习进行中\nsonnycalcr的博客\n","permalink":"https://houjinghao123.github.io/about/","summary":"about","title":"友链"}]