复习-JavaGuide

spring 设计模式

转载自https://github.com/Snailclimb/JavaGuide(添加小部分笔记)感谢作者!

“JDK 中用到了哪些设计模式? Spring 中用到了哪些设计模式? ”这两个问题,在面试中比较常见。

我在网上搜索了一下关于 Spring 中设计模式的讲解几乎都是千篇一律,而且大部分都年代久远。所以,花了几天时间自己总结了一下。

由于我的个人能力有限,文中如有任何错误各位都可以指出。另外,文章篇幅有限,对于设计模式以及一些源码的解读我只是一笔带过,这篇文章的主要目的是回顾一下 Spring 中的设计模式。

控制反转(IoC)和依赖注入(DI) #

IoC(Inversion of Control,控制反转) 是 Spring 中一个非常非常重要的概念,它不是什么技术,而是一种解耦的设计思想。IoC 的主要目的是借助于“第三方”(Spring 中的 IoC 容器) 实现具有依赖关系的对象之间的解耦(IOC 容器管理对象,你只管使用即可),从而降低代码之间的耦合度。

IoC 是一个原则,而不是一个模式,以下模式(但不限于)实现了 IoC 原则。

ioc-patterns

Spring IoC 容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的。 IoC 容器负责创建对象,将对象连接在一起,配置这些对象,并从创建中处理这些对象的整个生命周期,直到它们被完全销毁。

在实际项目中一个 Service 类如果有几百甚至上千个类作为它的底层,我们需要实例化这个 Service,你可能要每次都要搞清这个 Service 所有底层类的构造函数,这可能会把人逼疯。如果利用 IOC 的话,你只需要配置好,然后在需要的地方引用就行了,这大大增加了项目的可维护性且降低了开发难度。

关于 Spring IOC 的理解,推荐看这一下知乎的一个回答:https://www.zhihu.com/question/23277575/answer/169698662 ,非常不错。

控制反转怎么理解呢? 举个例子:"对象 a 依赖了对象 b,当对象 a 需要使用 对象 b 的时候必须自己去创建。但是当系统引入了 IOC 容器后, 对象 a 和对象 b 之前就失去了直接的联系。这个时候,当对象 a 需要使用 对象 b 的时候, 我们可以指定 IOC 容器去创建一个对象 b 注入到对象 a 中"。 对象 a 获得依赖对象 b 的过程,由主动行为变为了被动行为,控制权反转,这就是控制反转名字的由来。

...

Spring事务详情

转载自https://github.com/Snailclimb/JavaGuide(添加小部分笔记)感谢作者!

前段时间答应读者的 Spring 事务 分析总结终于来了。这部分内容比较重要,不论是对于工作还是面试,但是网上比较好的参考资料比较少。

什么是事务? #

事务是逻辑上的一组操作,要么都执行,要么都不执行。

相信大家应该都能背上面这句话了,下面我结合我们日常的真实开发来谈一谈。

我们系统的每个业务方法可能包括了多个原子性的数据库操作,比如下面的 savePerson() 方法中就有两个原子性的数据库操作。这些原子性的数据库操作有依赖的,它们要么都执行,要不就都不执行

	public void savePerson() {
		personDao.save(person);
		personDetailDao.save(personDetail);
	}

另外,需要格外注意的是:事务能否生效数据库引擎是否支持事务是关键。比如常用的 MySQL 数据库默认使用支持事务的 innodb引擎。但是,如果把数据库引擎变为 myisam,那么程序也就不再支持事务了!

事务最经典也经常被拿出来说例子就是转账了。假如小明要给小红转账 1000 元,这个转账会涉及到两个关键操作就是:

  1. 将小明的余额减少 1000 元。
  2. 将小红的余额增加 1000 元。

万一在这两个操作之间突然出现错误比如银行系统崩溃或者网络故障,导致小明余额减少而小红的余额没有增加,这样就不对了。事务就是保证这两个关键操作要么都成功,要么都要失败。

public class OrdersService {
	private AccountDao accountDao;

	public void setOrdersDao(AccountDao accountDao) {
		this.accountDao = accountDao;
	}

  @Transactional(propagation = Propagation.REQUIRED,
                isolation = Isolation.DEFAULT, readOnly = false, timeout = -1)
	public void accountMoney() {
    //小红账户多1000
		accountDao.addMoney(1000,xiaohong);
		//模拟突然出现的异常,比如银行中可能为突然停电等等
    //如果没有配置事务管理的话会造成,小红账户多了1000而小明账户没有少钱
		int i = 10 / 0;
		//小王账户少1000
		accountDao.reduceMoney(1000,xiaoming);
	}
}

另外,数据库事务的 ACID 四大特性是事务的基础,下面简单来了解一下。

...

Spring/SpringBoot常用注解

转载自https://github.com/Snailclimb/JavaGuide(添加小部分笔记)感谢作者!

0.前言 #

可以毫不夸张地说,这篇文章介绍的 Spring/SpringBoot 常用注解基本已经涵盖你工作中遇到的大部分常用的场景。对于每一个注解我都说了具体用法,掌握搞懂,使用 SpringBoot 来开发项目基本没啥大问题了!

为什么要写这篇文章?

最近看到网上有一篇关于 SpringBoot 常用注解的文章被转载的比较多,我看了文章内容之后属实觉得质量有点低,并且有点会误导没有太多实际使用经验的人(这些人又占据了大多数)。所以,自己索性花了大概 两天时间简单总结一下了。

因为我个人的能力和精力有限,如果有任何不对或者需要完善的地方,请帮忙指出!Guide 哥感激不尽!

1. @SpringBootApplication #

这里先单独拎出@SpringBootApplication 注解说一下,虽然我们一般不会主动去使用它。

Guide 哥:这个注解是 Spring Boot 项目的基石,创建 SpringBoot 项目之后会默认在主类加上。

@SpringBootApplication
public class SpringSecurityJwtGuideApplication {
      public static void main(java.lang.String[] args) {
        SpringApplication.run(SpringSecurityJwtGuideApplication.class, args);
    }
}

我们可以把 @SpringBootApplication看作是 @Configuration@EnableAutoConfiguration@ComponentScan 注解的集合。

package org.springframework.boot.autoconfigure;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
		@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
   ......
}

package org.springframework.boot;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {

}

根据 SpringBoot 官网,这三个注解的作用分别是:

...

spring 常见面试题总结

转载自https://github.com/Snailclimb/JavaGuide(添加小部分笔记)感谢作者!

这篇文章主要是想通过一些问题,加深大家对于 Spring 的理解,所以不会涉及太多的代码!

下面的很多问题我自己在使用 Spring 的过程中也并没有注意,自己也是临时查阅了很多资料和书籍补上的。网上也有一些很多关于 Spring 常见问题/面试题整理的文章,我感觉大部分都是互相 copy,而且很多问题也不是很好,有些回答也存在问题。所以,自己花了一周的业余时间整理了一下,希望对大家有帮助。

Spring 基础 #

什么是 Spring 框架? #

Spring 是一款开源轻量级 Java 开发框架,旨在提高开发人员的开发效率以及系统的可维护性

我们一般说 Spring 框架指的都是 Spring Framework,它是很多模块的集合,使用这些模块可以很方便地协助我们进行开发,比如说 Spring 支持 IoCInversion of Control:控制反转) 和 AOP(Aspect-Oriented Programming:面向切面编程)、可以很方便地对数据库进行访问、可以很方便地集成第三方组件电子邮件任务调度,缓存等等)、对单元测试支持比较好、支持 RESTful Java 应用程序的开发。

[img

Spring 最核心的思想就是不重新造轮子,开箱即用,提高开发效率。

Spring 翻译过来就是春天的意思,可见其目标和使命就是为 Java 程序员带来春天啊!感动!

🤐 多提一嘴 : 语言的流行通常需要一个杀手级的应用,Spring 就是 Java 生态的一个杀手级的应用框架。

Spring 提供的核心功能主要是 IoCAOP。学习 Spring ,一定要把 IoC 和 AOP 的核心思想搞懂!

Spring 包含的模块有哪些? #

Spring4.x 版本

...

git

转载自https://github.com/Snailclimb/JavaGuide(添加小部分笔记)感谢作者!

版本控制 #

什么是版本控制 #

版本控制是一种记录一个若干文件内容变化,以便将来查阅特定版本修订情况的系统。 除了项目源代码,你还可以对任何类型的文件进行版本控制。

为什么要版本控制 #

有了它你就可以将某个文件回溯到之前的状态,甚至将整个项目都回退到过去某个时间点的状态,你可以比较文件的变化细节,查出最后是谁修改了哪个地方,从而找出导致怪异问题出现的原因,又是谁在何时报告了某个功能缺陷等等。

本地版本控制系统 #

许多人习惯用复制整个项目目录的方式来保存不同的版本,或许还会改名加上备份时间以示区别。 这么做唯一的好处就是简单,但是特别容易犯错。 有时候会混淆所在的工作目录,一不小心会写错文件或者覆盖意想外的文件。

为了解决这个问题,人们很久以前就开发了许多种本地版本控制系统,大多都是采用某种简单的数据库记录文件的历次更新差异。

image.png

集中化的版本控制系统 #

接下来人们又遇到一个问题,如何让在不同系统上的开发者协同工作? 于是,集中化的版本控制系统(Centralized Version Control Systems,简称 CVCS)应运而生。

集中化的版本控制系统都有一个单一的集中管理的服务器保存所有文件的修订版本,而协同工作的人们都通过客户端连到这台服务器,取出最新的文件或者提交更新

image.png

这么做虽然解决了本地版本控制系统无法让在不同系统上的开发者协同工作的诟病,但也还是存在下面的问题:

  • 单点故障: 中央服务器宕机,则其他人无法使用;如果中心数据库磁盘损坏又没有进行备份,你将丢失所有数据。本地版本控制系统也存在类似问题,只要整个项目的历史记录被保存在单一位置,就有丢失所有历史更新记录的风险。
  • 必须联网才能工作: 受网络状况、带宽影响。

分布式版本控制系统 #

于是分布式版本控制系统(Distributed Version Control System,简称 DVCS)面世了。 Git 就是一个典型的分布式版本控制系统。

这类系统,客户端并不只提取最新版本的文件快照,而是把代码仓库完整地镜像下来。 这么一来,任何一处协同工作用的服务器发生故障,事后都可以用任何一个镜像出来的本地仓库恢复。 因为每一次的克隆操作,实际上都是一次对代码仓库的完整备份

image.png

分布式版本控制系统可以不用联网就可以工作,因为每个人的电脑上都是完整的版本库,当你修改了某个文件后,你只需要将自己的修改推送给别人就可以了。但是,在实际使用分布式版本控制系统的时候,很少会直接进行推送修改,而是使用一台充当“中央服务器”的东西。这个服务器的作用仅仅是用来方便“交换”大家的修改,没有它大家也一样干活,只是交换修改不方便而已。

分布式版本控制系统的优势不单是不必联网这么简单,后面我们还会看到 Git 极其强大的分支管理等功能。

认识 Git #

Git 简史 #

Linux 内核项目组当时使用分布式版本控制系统 BitKeeper 来管理和维护代码。但是,后来开发 BitKeeper 的商业公司同 Linux 内核开源社区的合作关系结束,他们收回了 Linux 内核社区免费使用 BitKeeper 的权力。 Linux 开源社区(特别是 Linux 的缔造者 Linus Torvalds)基于使用 BitKeeper 时的经验教训,开发出自己的版本系统,而且对新的版本控制系统做了很多改进。

...

maven

转载自https://github.com/Snailclimb/JavaGuide(添加小部分笔记)感谢作者!

这部分内容主要根据 Maven 官方文档整理,做了对应的删减,主要保留比较重要的部分,不涉及实战,主要是一些重要概念的介绍。

Maven 介绍 #

Maven 官方文档是这样介绍的 Maven 的:

Apache Maven is a software project management and comprehension tool. Based on the concept of a project object model (POM), Maven can manage a project’s build, reporting and documentation from a central piece of information.

Apache Maven 的本质是一个软件项目管理理解工具。基于项目对象模型 (Project Object Model,POM) 的概念,Maven 可以从一条中心信息 管理项目的构建报告文档

什么是 POM? 每一个 Maven 工程都有一个 pom.xml 文件,位于根目录中,包含项目构建生命周期的详细信息。通过 pom.xml 文件,我们可以定义项目坐标项目依赖项目信息插件信息等等配置。

对于开发者来说,Maven 的主要作用主要有 3 个:

  1. 项目构建 :提供标准的、跨平台自动化项目构建方式。
  2. 依赖管理方便快捷管理项目依赖的资源jar 包),避免资源间的版本冲突问题。
  3. 统一开发结构 :提供标准的、统一项目结构

关于 Maven 的基本使用这里就不介绍了,建议看看官网的 5 分钟上手 Maven 的教程: Maven in 5 Minutes

...

Atomic预备知识

Java实现CAS的原理[非javaguide] #

i是非线程安全的,因为**i不是原子操作;可以使用synchronized和CAS实现加锁**

synchronized是悲观锁,一旦获得锁,其他线程进入后就会阻塞等待锁;而CAS是乐观锁,执行时不会加锁,假设没有冲突,如果因为冲突失败了就重试,直到成功

  • 乐观锁和悲观锁

    • 这是一种分类方式
    • 悲观锁,总是认为每次访问共享资源会发生冲突,所以必须对每次数据操作加锁,以保证临界区的程序同一时间只能有一个线程在执行
    • 乐观锁,又称**“无锁”**,假设对共享资源访问没有冲突,线程可以不停的执行,无需加锁无需等待;一旦发生冲突,通常是使用一种称为CAS的技术保证线程执行安全
      • 无锁没有锁的存在,因此不可能发生死锁,即乐观锁天生免疫死锁
      • 乐观锁用于**“读多写少”的环境,避免加锁频繁影响性能;悲观锁用于“写多读少”,避免频繁失败及重试**影响性能
  • CAS概念,即CompareAndSwap ,比较和交换,CAS中,有三个值(概念上)
    V:要更新的变量(var);E:期望值(expected);N:新值(new) 判断V是否等于E,如果等于,将V的值设置为N;如果不等,说明已经有其它线程更新了V,则当前线程放弃更新,什么都不做。 一般来说,预期值E本质上指的是“旧值”(判断是否修改了)

    1. 如果有一个多个线程共享的变量i原本等于5,我现在在线程A中,想把它设置为新的值6;
    2. 我们使用CAS来做这个事情;
    3. (首先要把原来的值5在线程中保存起来)
    4. 接下来是原子操作:首先我们用(现在的i)去与5对比,发现它等于5,说明没有被其它线程改过,那我就把它设置为新的值6,此次CAS成功,i的值被设置成了6;
    5. 如果不等于5,说明i被其它线程改过了(比如现在i的值为2),那么我就什么也不做,此次CAS失败,i的值仍然为2。

其中i为V,5为E,6为N

CAS是一种原子操作,它是一种系统原语,是一条CPU原子指令,从CPU层面保证它的原子性(不可能出现说,判断了对比了i为5之后,正准备更新它的值,此时该值被其他线程改了

多个线程同时使用CAS操作一个变量时,只有一个会胜出,并成功更新其余均会失败,但失败的线程并不会被挂起,仅是被告知失败,并且允许再次尝试,当然也允许失败的线程放弃操作。

  • Java实现CAS的原理 - Unsafe类

    • 在Java中,如果一个方法是native的,那Java就不负责具体实现它,而是交给底层的JVM使用c或者c++去实现

    • Java中有一个Unsafe类,在sun.misc包中,里面有一些native方法,其中包括:

      boolean compareAndSwapObject(Object o, long offset,Object expected, Object x);
      boolean compareAndSwapInt(Object o, long offset,int expected,int x);
      boolean compareAndSwapLong(Object o, long offset,long expected,long x);
      
      
      //------>AtomicInteger.class
      
      public class AtomicInteger extends Number implements java.io.Serializable {
      private static final long serialVersionUID = 6214790243416807050L;
      
      // setup to use Unsafe.compareAndSwapInt for updates
      private static final Unsafe unsafe = Unsafe.getUnsafe();
      private static final long valueOffset;
      
      static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
      }
      
      private volatile int value;
      public final int getAndIncrement() {
      	return unsafe.getAndAddInt(this, valueOffset, 1);
      
    }
    

    }

    ...

MySQL高性能优化规范建议总结

转载自https://github.com/Snailclimb/JavaGuide(添加小部分笔记)感谢作者!

索引优化相关

  1. in 代替 or
  2. not exist 代替 not in

数据库命名规范 #

  • 所有数据库对象名称必须使用小写字母并用下划线分割
  • 所有数据库对象名称禁止使用 MySQL 保留关键字(如果表名中包含关键字查询时,需要将其用单引号括起来)
  • 数据库对象的命名要能做到见名识意,并且最好不要超过 32 个字符
  • 临时库表必须tmp_ 为前缀并以日期为后缀,备份表必须bak_ 为前缀以日期 (时间戳) 为后缀
  • 所有存储相同数据的列名和列类型必须一致(一般作为关联列,如果查询时关联列类型不一致会自动进行数据类型隐式转换,会造成列上的索引失效,导致查询效率降低)

数据库基本设计规范 #

所有表必须使用InnoDB存储引擎 #

  • 没有特殊要求(即 InnoDB 无法满足的功能如:列存储,存储空间数据等)的情况下,所有表必须使用 InnoDB 存储引擎(MySQL5.5 之前默认使用 Myisam,5.6 以后默认的为 InnoDB)。
  • InnoDB 支持事务,支持行级锁,更好的恢复性高并发下性能更好

数据库和表的字符集统一使用UTF-8 #

兼容性更好,统一字符集可以避免由于字符集转换产生的乱码,不同的字符集进行比较前需要进行转换会造成索引失效,如果数据库中有存储 emoji 表情的需要,字符集需要采用 utf8mb4 字符集。

参考文章:

所有表和字段都需要添加注释 #

使用 comment 从句添加表列的备注,从一开始就进行数据字典的维护

尽量控制单表数据量的大小,建议控制在500万以内 #

  • 500 万并不是 MySQL 数据库的限制,过大会造成修改表结构备份恢复都会有很大的问题。

    ...

MySQL常见面试题总结

转载自https://github.com/Snailclimb/JavaGuide(添加小部分笔记)感谢作者!====

MySQL基础 #

关系型数据库介绍 #

  • 关系型数据库,建立在关系模型的基础上的数据库。表明数据库中所存储的数据之间的联系(一对一、一对多、多对多)
  • 关系型数据库中,我们的数据都被存放在各种表中(比如用户表),表中的每一行存放着一条数据(比如一个用户的信息) 关系型数据库表关系
  • 大部分关系型数据库都使用SQL来操作数据库中的数据,并且大部分关系型数据库都支持事务四大特性(ACID)

常见的关系型数据库
MySQLPostgreSQLOracleSQL ServerSQLite微信本地的聊天记录的存储就是用的 SQLite) ……

MySQL介绍 #

img

  • MySQL是一种关系型数据库,主要用于持久化存储我们系统中的一些数据比如用户信息

  • 由于 MySQL 是开源免费并且比较成熟的数据库,因此,MySQL 被大量使用在各种系统中。任何人都可以在 GPL(General Public License 通用性公开许可证) 的许可下下载并根据个性化的需要对其进行修改。MySQL 的默认端口号是3306

MySQL基础架构 #

  • MySQL的一个简要机构图,客户端的一条SQL语句在MySQL内部如何执行 ly-20241212141911246
  • MySQL主要由几部分构成
    1. 连接器身份认证权限相关(登录MySQL的时候)
    2. 查询缓存:执行查询语句的时候,会先查询缓存(MySQL8.0版本后移除,因为这个功能不太实用)
    3. 分析器没有命中缓存的话,SQL语句就会经过分析器,分析器说白了就是要先看你的SQL语句要干嘛,再检查你的SQL语句语法是否正确
    4. 优化器:按照MySQL认为最优的方案去执行
    5. 执行器执行语句,然后从存储引擎返回数据。执行语句之前会先判断是否有权限,如果没有权限,就会报错
    6. 插件式存储引擎:主要负责数据存储读取,采用的是插件式架构,支持InnoDBMyISAMMemory等多种存储引擎

MySQL存储引擎 #

MySQL核心在于存储引擎

MySQL支持哪些存储引擎?默认使用哪个? #

  • MySQL支持多种存储引擎,可以通过show engines命令来查看MySQL支持的所有存储引擎 查看 MySQL 提供的所有存储引擎

  • 默认存储引擎为InnoDB,并且,所有存储引擎中只有InnoDB是事务性存储引擎,也就是说只有InnoDB支持事务

  • 这里使用MySQL 8.x MySQL 5.5.5之前,MyISAM是MySQL的默认存储引擎;5.5.5之后,InnoDB是MySQL的默认存储引擎,可以通过select version()命令查看你的MySQL版本

    mysql> select version();
    +-----------+
    | version() |
    +-----------+
    | 8.0.27    |
    +-----------+
    1 row in set (0.00 sec) 
    

    使用show variables like %storage_engine%命令直接查看MySQL当前默认的存储引擎
    查看 MySQL 当前默认的存储引擎

    ...

MySQL中的隐式转换造成的索引失效

转载自https://github.com/Snailclimb/JavaGuide(添加小部分笔记)感谢作者!

本篇文章基于MySQL 5.7.26,原文:https://www.guitu18.com/post/2019/11/24/61.html

前言 #

  • 关于数据库优化,最常见的莫过于索引失效,数据量多的时候比较明显,处理不及时会造成雪球效应,最终导致数据库卡死甚至瘫痪
  • 这里说的是隐式转换造成的索引失效

数据准备 #

-- 创建测试数据表
DROP TABLE IF EXISTS test1;
CREATE TABLE `test1` (
    `id` int(11) NOT NULL,
    `num1` int(11) NOT NULL DEFAULT '0',
    `num2` varchar(11) NOT NULL DEFAULT '',
    `type1` int(4) NOT NULL DEFAULT '0',
    `type2` int(4) NOT NULL DEFAULT '0',
    `str1` varchar(100) NOT NULL DEFAULT '',
    `str2` varchar(100) DEFAULT NULL,
    PRIMARY KEY (`id`),
    KEY `num1` (`num1`),
    KEY `num2` (`num2`),
    KEY `type1` (`type1`),
    KEY `str1` (`str1`),
    KEY `str2` (`str2`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- 创建存储过程
DROP PROCEDURE IF EXISTS pre_test1;
DELIMITER //
CREATE PROCEDURE `pre_test1`()
BEGIN
    DECLARE i INT DEFAULT 0;
    SET autocommit = 0;
    WHILE i < 10000000 DO
        SET i = i + 1;
        SET @str1 = SUBSTRING(MD5(RAND()),1,20);
        -- 每100条数据str2产生一个null值
        IF i % 100 = 0 THEN
            SET @str2 = NULL;
        ELSE
            SET @str2 = @str1;
        END IF;
        INSERT INTO test1 (`id`, `num1`, `num2`,
        `type1`, `type2`, `str1`, `str2`)
        VALUES (CONCAT('', i), CONCAT('', i),
        CONCAT('', i), i%5, i%5, @str1, @str2);
        -- 事务优化,每一万条数据提交一次事务
        IF i % 10000 = 0 THEN
            COMMIT;
        END IF;
    END WHILE;
END;
// DELIMITER ;
-- 执行存储过程
CALL pre_test1(); 

其中,七个字段,首先使用存储过程生成 1000 万条测试数据, 测试表一共建立了 7 个字段(包括主键),num1num2保存的是和ID一样的顺序数字,其中num2是字符串类型type1type2保存的都是主键对 5 的取模,目的是模拟实际应用中常用类似 type 类型的数据,但是**type2是没有建立索引的。 str1str2都是保存了一个 20 位长度的随机字符串str1不能为NULLstr2允许为NULL,相应的生成测试数据的时候我也会str2字段生产少量NULL值**(每 100 条数据产生一个NULL值)。

...