【Git】
一、 Git常用命令
git init 初始化
全局设置
git config --global user.name “smallming”
git config --global user.email “293882181@qq.com”
设置成功后会在C:\Users\smallming(此目录是Windows系统账号名)中出现.gitconfig
git add filename 添加文件到缓存区
git commit -m “备注” 提交到本地库
git remote add origin 仓库地址 设置远程仓库地址
git push -u origin master 推送到远程仓库
git clone 克隆远程仓库内容
git pull origin master 拉取内容
git rm 删除文件
git checkout -b 分支名 master 创建分支
git branch 查看本地分支
git push origin 本地分支名:远程分支名 推送分支
**git branch --set-upstream-to=origin/**分支名 关联本地和远程分支
git merge --no-ff 分支名 把分支合并到当前分支(注意先切换到当前分支)
git branch -d 分支名 删除本地分支
git push origin --delete 分支名 删除远程分支
二、 Git中常用分支及作用
**主分支(master):**主分支主要用于存放正式版本软件的。
**开发分支(Develop):**早晚提交时提交到这个分支。最后合并到主分支中。
**功能分支(feature):**一般为了开发特定功能而创建的分支。
**预发分支(release):**合并到Master之前做测试的。
修补bug分支(fixbug):软件发布后修补bug的分支。
【Linux】
三、 Linux常用命令(笔试或面试)
cd 进入目录
cd..返回上一级
pwd 打印当前目录
clear 清屏
ls/ll 显示当前目录内容
mkdir 创建新文件夹
mkdir -p 路径 : 如果路径中包含了不存在的路径 则自定创建一个
rm -r 文件夹路径 : 删除目录所有文件夹及文件夹中的子内容都需要通过输入y进行确认删除
rm -rf 文件夹路径:删除文件夹不需要确认
esc 退出编辑状态在退出编辑状态下 按两次d 表示删除当前行D+数字+回车便是删除数字行
q 退出 只有没有任何编辑操作的时候能使用
wq 保存并退出
**q!**强制退出Cat 文件路径
touch 创建新文件
cp 复制
mv
rm 删除
vi/vim 编辑
cat 查看内容内容
tail 查看文件后几行
tar 解压gzip文件
yum install 在线安装
ps aux|grep 查看进程
kill 杀掉进程
reboot重启
【JVM】
四、 请介绍一下HotSpot
在Java的官方文档中会发现,每个版本官方都提供了两个版本的文档地址。
官方文档地址: https://docs.oracle.com/javase/specs/index.html
官方对JVM提供的规范。何为规范,就是告诉我们想要开发JVM应该这样做。但是在JVM具体实现时可能有一定的偏差。
我们现在使用的都是官方对JVM规范的具体实现HotSpot。
在HotSpot实现过程中也提供一些参数,让开发者可以调试它(所谓的JVM调优其实就是调整设置这些参数)
HotSpot:https://docs.oracle.com/javase/8/docs/technotes/tools/windows/java.html
但是市面上上还有一些其他厂商对JVM规范的具体实现
JVM名称 | 说明 |
---|---|
HotSpot | Oracle官方对JVM规范的实现 |
jRocket | 曾经JVM的扛把子,号称最快的虚拟机,后被Oracle收购,和HotSpot整合 |
J9 | IBM对JVM规范的实现 |
TaobaoVM | HotSpot深度定制版 |
azul zing | 商业收费版 |
五、 JVM内存结构
JVM是负责运行Java程序的虚拟计算机,是物理计算机中的一个进程。
JVM运行过程中为了方便管理,方便GC回收,所占内存区域被划分为多个部分。
1 Java HotSpot VM 内存结构图
1.1 源文件
源文件就是我们编写Java代码的文件。文件扩展名为.java
1.2 字节码文件
字节码文件是源文件编译后的文件。字节码文件是二进制文件,需要通过特定的工具才能查看。里面存放了源文件编译后的字节码指令。
1.3 类加载器 Class Loader
Java 程序运行时会由类加载器负责把.class的字节码文件装在到内存中,供虚拟机执行。
加载 Loading
1. 启动类加载器 BootStrap Class Loader : 负责从启动类中加载类。具有最高执行优先级。即:rt.jar等。
2. 扩展类加载器 Extension Class Loader : 负责加载扩展相关类。即:jre/lib/ext 目录
3. 应用程序加载器 Application Class Loader : 加载应用程序类路径(classpath)中相关类。
4. 链接 Linking
5. 校验 Verify : 校验器会校验字节码文件是否正确。
6. 准备 Prepare : 所有静态变量初始化并赋予默认值
7. 解析 Resolve :符号引用被换成直接引用。
8. 初始化 Initialization : 所有静态变量赋予初值,静态代码块执行。
1.4 执行引擎
运行时数据区的字节码会交给执行引擎执行
1. 解释器 Interpreter : 解释器负责解释字节码文件。每次方法调用都会被重新解释。
2. JIT编译器 : JIT 即时编译器。对于多次被使用的方法会放入到本地内存中,下次就可以直接调用了。
3. 探测器 ; 负责探测多次被调用的代码。
4. 垃圾回收器 GC : 负责回收不在被使用的对象。GC是JVM中非常重要的一块,在后面我们会单独讲解一下GC。
1.5 本地库接口
在Java代码中使用native修饰的方法表示方法具体实现使用其他编程语言实现的。例如:C语言。通过本地库接口为Java程序提供调用其他语言的实现方案。
1.6 本地方法库
所有的本地方法,通过本地库接口调用。
1.7 程序计数器
程序计数器简称:PC Register
程序计数器是一块较小的内存空间。记录了当前线程执行到的字节码行号。每个线程都有自己的程序计数器,相互不影响。如果是native方法,计数器为空。
1.8 虚拟机栈
虚拟机栈跟随线程创建而创建,所以每个线程都有一个虚拟机栈。
虚拟机栈中存储的是栈帧(frames),每个栈帧对应一个方法,每个栈帧都有自己的局部变量表、操作数栈、动态链接和返回地址等。当前正在执行的方法称为当前方法,当前方法所在的帧称为当前帧。方法执行时帧就是一个入栈操作,方法执行完成之后栈帧就是一个出站操作。
1.8.1 局部变量表
局部变量表存储的8大基本数据类型和返回值以及方法参数及对象的引用。 其中long和double占用2倍长度。
局部变量表就是一个数组,数组的长度在编译器确定。通过从0开始的索引调用局部变量表的内容。
对于类方法,从索引0开始连续N个作为方法传递。对于实例方法索引0存储的都是实例化方法的实例对象的引用。
1.8.2 操作数栈
操作数栈存在于栈帧中,其大小在编译期确定。
操作数栈中存储了class文件中虚拟机指令以及准备要传递的参数和接收对方的返回结果。
运行时常量池中数据以及局部变量表中得值都可以由操作数栈进行获取。
1.8.3 动态链接
方法·把符号转换为直接引用分为两种情况。在JVM加载或第一次使用转换时称为静态链接或静态解析。而在运行期间把符号转换为直接引用时就称为动态链接。
1.8.4 方法返回地址
方法返回地址分为两种情况:
1. 正常结束执行。例如碰见return关键字。调用程序计数器的值后当前栈帧直接出栈就可以了。
2. 异常结束。可能需要恢复上层方法的局部变量表和操作数栈,然后把返回值压如到栈帧的操作数栈中,之后调用程序计数器的值后获取到下条指令。
1.9 堆
堆是所有线程共享的,存储类的实例和数组。
堆是在虚拟机启动时创建的,由GC负责回收。
堆可以是一块不连续的内存空间。
在Java 8 中,String是存在于堆中的。
堆被分为二大部分:
在Java 7时分为:新生代(Young Generation)、老年代(Old Generation)。在HotSpot中使用永久代来实现方法区的规范。且新生代、老年代和永久代是连续的。
新生代又被分为Eden区、From Survivor区、To Survivor区。官方说明默认分配比例为8:1:1。但是使用jmap工具进行测试时发现比例为6:1:1。
在Java 8时把永久代替换为元空间(MetaSpace),也就是说在Java8中使用元空间来实现方法区。且在Java8中把元空间移植到本地内存上(Native Memory),其实在Java 7 时,部分数据已经移植到本地内存上了。例如:符号引用(Symbols)
1.10 方法区
1. 方法区是线程共享的。
2. 在虚拟机启动时自动创建方法区,方法区可以是一块不连续的内存空间。
3. 方法区可以理解为编译代码存储区。在方法区中存储每个类的结构、运行时常量池、字段、方法、构造方法。
4. 在JVM规范上方法区是一个独立的区域,但是在Java SE7 的HotSpot 上方法区使用永久代作为实现,永久代和堆是一块连续空间。在Java SE8的JVM规范实现上,HotSpot使用元空间实现方法区。
1.11 运行时常量池
运行时常量池存在于方法区。存储每个类或每个接口从编译时已知的数字文字到必须在运行时解析的方法和字段引用。
1.12 本地方法栈
本地方法栈主要是为了支持native方法。每个线程都有自己独立的本地方法栈。
六、 说一下JDK6、7、8中JVM内存结构的区别
七、 请说一下你知道的JVM启动参数
在Java学习的第一课一般都是写一个HelloWorld,然后通过命令行javac编译,在通过java.exe进行执行。java.exe就是运行java程序的工具,该工具有很多参数选项进行使用。
我们平时所说的JVM启动参数其实就是java.exe的options
Java 8 中HotSpot官方文档地址
https://docs.oracle.com/javase/8/docs/technotes/tools/windows/java.html
1 总体语法
java [options] classname [args]
java [options] -jar filename [args]
javaw [options] classname [args]
javaw [options] -jar filename [args]
语法解释:
1. java和javaw为JDK里面带有的工具
2. **options**:选项。工支持6种选项。
3**. classname**:类名
4. **-jar** 参数
5. **filename** 文件名称
6. **args** 传递给主方法的参数。用空格分隔。
options分类
options分为6类,分别是:
1. Standard Options 标准选项。
2. Non-Standard Options 非标准选项
3. Advanced Runtime Options 高级运行时选项。控制Java HotSpot VM运行时行为。
4. Advanced JIT Compiler Options 高级JIT(即时编译器)编译器选项。使用JIT时优化参数,属于HotSpot专有选项,非JVM通用选项。
5. Advanced Serviceability Options 高级可维护性选项。提供了收集系统信息和执行大量调试的能力。
6. Advanced Garbage Collection Options 高级垃圾收集选项。控制HotSpot执行GC的方式。
其中3、4、5、6的参数都是以-XX:开头的
Standard Options(标准选项)
标准选项是所有JVM实现都支持的选项。
1. -help
启用帮助,显示所有支持的选项。
也可以写成:java -?
2. -version
打印版本号。
3. -ea
启用断言。默认情况下断言是不启用的。
assert expression;如果表达式为真继续运行,如果表达式为假报异常。
assert expression1:expression2;如果expression1为真继续运行。如果表达式为假expression2作为异常信息。
4. -javaagent:jarpath[=options]
加载指定的jar包。
5. -Dproperty=value
设置系统属性值。在项目部署或Spring Boot项目中会使用到。
6. -jar filename
运行指定的jar文件。
7. -verbose
-verbose:gc 打印GC信息。用于诊断GC时使用。
-verbose:class 打印载入类的相关信息。可用于诊断找不到类或类冲突时使用。
-verbose:jni 输出native方法调用情况。可用于诊断native调用相关问题。
Non-Standard Options(非标准选项)
非标准选项是HotSpot专有选项。所有内容都是以-X开头(必须是大写)。
8 . -X
显示所有选项。
里面常用的就红色区域:
-Xms<size> 设置初始化Java堆大小
-Xmx<size> 设置最大堆大小。
-Xss<size> 设置Java线程堆栈大小。等效于-
XX:ThreadStackSize
-Xmn<size> 年轻代初始化和最大大小。建议年轻代占用整个堆的1/2~1/4之间。年轻代太小GC频率太高,年轻代太大会触发Full GC。也可以使用还可以使用-XX:NewSize设置初始大小和-XX:MaxNewSize最大大小
-Xloggc:filename 设置将gc信息写入到某个日志文件中。
高级运行时选项 Advanced Runtime Options
控制Java HotSpot VM运行时行为。所有的参数都是-XX: 开头。注意大小写。
-XX:+PrintCommandLineFlags
打印命令行JVM参数。默认不开启不打印。通过此内容可以查看默认配置,例如当前锁使用的虚拟机类型。等效于jinfo -flags pid
-XX:ThreadStackSize
设置线程堆栈大小。等效于-Xss
-XX:+TraceClassLoading
启用类加载跟踪。默认不开启打印。
-XX:+TraceClassLoadingPreorder
按照引用顺序跟踪所有加载的类。默认禁用
-XX:-UseBiasedLocking
禁用偏向锁。设置后对于一些大量无竞争的同步操作会明显提升性能。但对于某些具有锁的应用性能会降低。
高级JIT编译器选项 Advanced JIT Compiler Options
使用JIT时优化参数。
-XX:+AggressiveOpts
允许积极的使用性能优化功能。预计在后续版本设置为默认开启。目前是默认关闭的。
-XX:+BackgroundCompilation
启用后台编译,默认开启。如想取消使用-XX:-
BackgroundCompilation(等效于-Xbatch).
-XX:CICompilerCount=threads
设置用于编译的编译器线程数。默认情况下,服务器 JVM 的线程数设置为 2,客户端 JVM 的线程数设置为 1
-XX:CodeCacheMinimumFreeSpace=size
设置编译器最小空间。默认500KB。当剩余空间少于最小空间时,编译器停止工作。
-XX:InitialCodeCacheSize=size
设置初始代码缓存大小
-XX:+OptimizeStringConcat
启动String连接操作优化。默认启用。可使用-XX:-OptimizeStringConcat禁用
高级可维护性选项 Advanced Serviceability Options
提供了收集系统信息和执行大量调试的能力。按照官方文档一共就只有六个参数。
-XX:+HeapDumpOnOutOfMemoryError
当出现java.lang.OutOfMemoryError把堆中内容转存到当前目录文件中。默认禁止的。
-XX:HeapDumpPath=path
当设置了-XX:+HeapDumpOnOutOfMemoryError以后,转存文件路径。例如:-XX:HeapDumpPath=C:/log/java/java_heapdump.log
-XX:LogFile=path
设置写入日志数据的路径和文件名,例如-XX:LogFile=C:/log/java/hotspot.log
-XX:+PrintClassHistogram
默认禁止。当按Ctrl+Break时打印类的直方图。等效于:jmap -histo pid 命令
-XX:+PrintConcurrentLocks
默认禁止。打印Concurrent相关的锁。等效于:jstack -l
-XX:+UnlockDiagnosticVMOptions
默认禁止。解锁诊断VM选项内容。因为在JVM中某些参数必须诊断后才能赋值。
高级垃圾回收选项 Advanced Garbage Collection Options
控制HotSpot执行GC的方式。可选参数比较多,但也比较重要。里面部分参数为JVM优化常用参数。
-XX:ActiveProcessorCount= x
设置VM操作各种线程池(例如:ForkJoinPool)的CPU数量
-XX:+AggressiveHeap
默认禁止。启用堆优化。设置后将会根据计算机配置(RAM和CPU)将各种参数设置为最合适长时间运行的值。
-XX:+CMSClassUnloadingEnabled
默认开启。在使用标记清除垃圾收集齐(CMS)时开启类卸载。可使用减号禁用。
-XX:CMSInitiatingOccupancyFraction=num
设置老年代占用比例。取值(0-100),如果为负数,表示-XX:CMSTriggerRatio进行定义比例。
-XX:ConcGCThreads=threads
设置并发GC时可用线程数。默认情况下该值取决于JVM可用的CPU数量。
-XX:+DisableExplicitGC
默认禁用的。开启后将不会处理System.gc();但JVM仍会在必要时候进行GC。
-XX:+ExplicitGCInvokesConcurrent
默认禁用。通过System.gc()启用并发GC。必须与-XX:+UseConcMarkSweepGC一起启用。
-XX:G1HeapRegionSize=size
设置使用垃圾优先 (G1,garbage-first ) 收集器时 Java 堆细分到区域大小。
-XX:+G1PrintHeapRegions
默认禁止。打印哪些堆区域进行了回收,以及G1的使用情况。
-XX:InitialHeapSize=size
初始化堆大小。必须是0或1024的倍数且大于1MB。默认是根据系统配置自动选择。
-XX:InitialSurvivorRatio=ratio
初始化Suvivor区域的比例。
-XX:InitiatingHeapOccupancyPercent=percent
初始化堆所占空间触发GC的比例。默认45%。设置0表示不间断回收
-XX:MaxGCPauseMillis=time
单位毫秒。设置堆最大暂停时间。JVM有可能无法满足这个要求。默认没有设置。
-XX:MaxHeapSize=size
堆最大大小。等效于-Xmx
-XX:MaxHeapFreeRatio=percent
设置GC后最大堆空闲比例。如果超过这个比例将会缩小。默认值75%
-XX:MaxMetaspaceSize=大小
最大元空间大小。默认是根据系统所剩内容自动判断。
-XX:MaxNewSize=size
新生代最大大小
-XX:MaxTenuringThreshold=threshold
设置GC最大大小,即GC进行回收时年龄的大小。处于Survivor区域的对象每次GC后年龄都会增加1。最大值15.parallel collection默认值15.CMS默认值6.
-XX:MetaspaceSize=size
元空间大小。超过后进行GC。默认需要看系统可以内存大小。
-XX:NewRatio=ratio
年轻代和老年代比例。默认值为2.
-XX:NewSize=size
新生代大小。等效于-Xmn
-XX:ParallelGCThreads=threads
并行GC线程数。默认值取决于CPU。
-XX:+PrintGC
打印GC消息。默认禁用。
-XX:+PrintGCApplicationStoppedTime
打印GC时应用暂停时间
-XX:+PrintGCDetails
打印GC详情。默认禁用。
-XX:+PrintGCTimeStamps
打印GC时间戳
-XX:+ScavengeBeforeFullGC
在每次FullGC之前先对年轻代进行GC。默认开启的。且Oracle不建议禁用。
-XX:SurvivorRatio=ratio
设置年轻代中Eden区和Survivor区的比例。默认值是8.但是经过测试发现是打印的是8,实际比例为6.
-XX:TargetSurvivorRatio=percent
设置年轻代GC后Survivor区域的比例。
-XX:+UseAdaptiveSizePolicy
默认启用。使用自适应大小策略。如果想关闭,可以使用减号。关闭后必须显示设置 -XX:SurvivorRatio
-XX:+UseG1GC
默认禁用,根据计算机配置和JVM类型自动选择。配置后表示启动G1垃圾收集器。适用于服务端的垃圾收集器。主要是针对内存比较大的情况。G1可以尽可能的满足GC暂停时间要求,同时保证良好的吞吐量。在使用G1收集器的时候建议堆大小至少6GB以上。
-XX:+UseGCOverheadLimit
默认启用的。JVM限制在抛出OutofMemoryError之前GC所花费的比例。如果GC花费时间超过98%,而堆恢复低于2%则抛出OutOfMemoryError
-XX:+UseParallelGC
默认禁用。配置表示启用并行GC。如果开启会自动-XX:+UseParallelOldGC。除非显示禁用了。对于64位平台,默认GC类型。
-XX:+UseParallelOldGC
默认禁用。配置表示启用并行老年代GC。如果开启会自动启用-XX:+UseParallelGC
-XX:+UseParNewGC
默认禁止。启动年轻代并行GC。在JDK8 中如果希望启用年轻代GC时推荐使用这个选项,而不是XX:+UseConcMarkSweepGC
-XX:+UseConcMarkSweepGC
默认禁止,JVM根据计算机配置自动选择合适的GC。在老年代启用CMS垃圾回收器Oracle建议在吞吐量(-XX:+UseParallelGC)垃圾收集器无法满足应用程序延迟要求时使用CMS垃圾收集器。当开启此选项后自动开启-XX:+UseParNewGC。并且在JDK8中不允许-XX:+UseConcMarkSweepGC和-XX:-UseParallelGC组合
-XX:+UseSerialGC
启用串行GC。默认禁止。但是对于小型简单应用,这种GC是较佳选择。客户端默认GC类型。
-XX:+UseStringDeduplication
G1回收器专有选项。默认禁止。配置后表示启用字符串重复删除。也就是说允许多个字符串对象指向同一个堆中字符串对象。
八、 GC是如何判断对象已经死亡(是垃圾)
引用计数(已淘汰)
引用计数算法就是看对象是否被引用。如果引用则对象计数器加一。如果释放引用计数器减一。但是引用计数算法最大的问题就是循环引用问题。当出现循环引用时对象计数器至少为1.这时候对象可能已经是垃圾了,但是无法被回收。
根可达算法(Root Searching)
达可根算法没有引用计数算法中循环引用无法被回收的问题。
其主要思路是通过一系列名为GC Roots的对象作为根,从根开始往下搜索,搜索过程经过的路径称为引用链(Reference Chain),当一个对象到达GC Root时表示当前对象还在使用,如果没有引用的或者和其他非GC Roots循环引用的内容都是垃圾。静态变量、线程变量、常量池、JNI(指针)都是GC Roots
九、 请说一下你知道的GC算法
标记清除算法(Mark-sweep)
首先标记出所有需要回收的对象。标记完成后统一回收所有标记的对象。
缺点:
效率低。碎片多。
复制算法(coping)
**目的:**主要是为了解决标记清除算法碎片问题。
**步骤:**内存按照容量分为大小相等的两块。每次只使用一块。当一块使用完成后,把存活的对象复制到另一个空间,然后把空间一次清除掉。
**缺点:**可用内存减少。
标记压缩算法(Mark-Compact)
又叫标记整理算法。
和标记清除算法有点类似。主要区别是标记完成后并不会直接清除,而是把所有不回收对象先向一端移动,然后在清除掉边界外面的对象。这样就不会产生内存碎片。
分代收集算法
把堆分为新生代和老年代。新生代采用一种算法,老年代采用采用算法。具体新生代和老年代采用的算法需要看使用哪种垃圾回收器。
十、 请说一下你知道GC的种类
1 总表说明
这些内容可以组合关系
十一、 请详细介绍下GC中各种垃圾回收器
1、 Serial、Serial Old 串行收集器
1.1 Serial 收集器
起源于JDK 1.3,单线程执行,每次回收必须STW。
应用场景:虚拟机在client模式下默认的GC。
优点:简单高效。
1.2 Serial Old收集器
老年代收集器。标记整理算法。单线程。主要应用在client模式下老年代收集。在JDK1.5之前可以与Parallel Scavenge配合使用。可作为CMS的备选。
2 ParNew收集器
Serial 收集器多线程版本,用于收集新生代。可与CMS配合使用。
ParNew可以并行执行,主要为了减少STW的时间,加快程序响应,给用户提供良好的体验。
3 Parallel Scavenge收集器
新生代收集器。采用复制算法。可以并行执行。
优点:
具备自适应调节能力。-XX:+UseAdaptiveSizePolicy
主要解决吞吐量问题。也被称为“吞吐量优先”收集器。即吞吐量 = 运行用户代码时间 /(运行用户代码时间 + 垃圾收集时间),虚拟机总共运行了100分钟,其中垃圾收集花掉1分钟,那吞吐量就是99%。高吞吐量可以高效的利用CPU时间,尽快完成运算任务。
4 Parallel Old收集器
老年代收集器。标记整理算法。多线程。JDK 1.6中出现。
5 CMS收集器(Concurrent Mark-Sweep Collector)
5.1 介绍
主要为了减少STW时间。
5.2 步骤
采用标记清除算法:
初始标记:初始标记只是标记下GC Root能够关联的对象。速度很快。需要STW
并发标记:进行GC Roots Tracing的过程
重新标记:修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录
并发清除:并发的清除对象。
5.3 优缺点
优点:
并发收集。低停顿。
缺点:
对CPU非常敏感,对于CPU大于4个时要求并发收集时使用的线程数不等于1/4。但随着CPU增加而下降。
可能产生浮动垃圾。因为CMS清理阶段程序还在运行,所以就可能产生新的垃圾,这部分垃圾只能等到下次才能被清理。所以称为浮动垃圾。
可能产生大量空间碎片。
6 G1收集器(garbage-first)
6.1 介绍
JDK8中主推的收集器。属于CMS的替代品。
G1收集器时堆中的年轻代和老年代只是逻辑上的概念,实际上把堆(一块连续内存)分为很多Region(分区)每个分区里面又被分为多个卡片(Card)。所以里面可能有很多年轻代和老年代。G1收集器里面多了一个新的概念:humongous(巨型对象)。当对象达到或超过Region一半时称为巨型对象。举行对象独占一个或多个连续的Region。
6.2 步骤:
1. 初始标记:初始标记阶段仅仅只是标记一下GC Roots能直接关联到的对象,并且修改TAMS(Next Top at Mark Start)的值。此过程需要STW,但是耗时很短。
2. 并发标记:并发标记阶段是从GC Root开始对堆中对象进行可达性分析,找出存活的对象。此过程耗时可能较长于用户操作并发执行,不需要STW。
3. 最终标记。标记因为并发标记时用户执行产生的垃圾。需要STW(也可以并行)
4. 筛选回收。对各个Region收回价值和时间进行计算,筛选出符合用户设定的预期回收时间。
十二、 G1收集器和CMS收集器的区别
1. G1是用在新生代和老年代同时使用。CMS是老年代收集器。
2. G1是Java8主推的收集器。CMS是java5出现的收集器。
3. G1的STW时间可由用户设定,在筛选回收过程“可预测”的想办法满足设定要求。CMS是尽可能的减少STW时间
4. G1使用的是标记整理算法,CMS使用的是标记清除算法(所以可能有内存碎片)。
5. G1回收的流程是初始标记、并发标记、最终标记、筛选回收。CMS的流程是:初始标记、并发标记、重新标记、并发清除。
十三、 请说一下如何进行JVM调优
想要进行JVM调优一定是程序运行过程中不能满足生产要求。否则程序运行非常流畅、非常稳定也没有必要去调优。所以JVM默认值是最好的选择。
JVM调优总体思路是:
1. 服务器硬件配置能高则高。
2. GC类型要选择好合适的类型。
3. 减少GC频率和Full GC的次数。实现较高的吞吐量或较低延迟。
服务器硬件配置能高则高
理论上讲服务器配置越高,服务器的性能越好。JVM性能自然也会更高。但是一般情况下服务器的配置都是固定的。即使不是固定的单机硬件也有瓶颈。
所以一般都是在硬件配置固定的情况下尽量实现使用更少的内存来获取高吞吐量或较低延迟。
GC类型要选择好合适的类型
GC的类型选择也是很重要的事情。
单核或客户端模式选择serial+serial Old 组合即可。
多核64位系统选择Parallel Scavenge+Parallel Old 模式。吞吐量表现非常优秀。
如果对于较低延迟较高的应用可选择G1回收器。
减少GC频率和Full GC次数
减少GC频率
新生代的大小(-Xmn或-XX:NewSize+-XX:MaxNewSize)直接影响GC频率。GC频率太高会导致JVM性能下降。
但是要注意:在堆大小固定的情况下,新生代大小越大,老年代大小越小。
减少Full GC 次数
当老年代满了以后会进行Full GC,所以增加老年代大小可以减少Full GC 的次数。
但是要注意:在堆大小固定的情况下,老年代越大,新生代越小。
所以默认情况下新生代和老年代的比例1:2(-XX:NewRatio=)是Java官方通过不断尝试,不断测试出来的最优比例,在没有特殊要求的情况下可以使用这个比例。
JVM性能明显下降了,如何进行分析
可以借助各种技术或工具进行观察。
日志
可以通过 -XX:+PrintGCDetails 和 -Xloggc:/data/jvm/gc.log 把GC日志打印出来。
堆栈错误信息
当发生OutOfMemoryError后可以使用-XX:+HeapDumpOnOutOfMemoryError和-XX:HeapDumpPath=path把堆信息转存下来。
线程快照
jstack pid 打印某个时间点的线程快照。
JDK里面带的其他工具
jconsole 可视化控制台工具,查看内存等。
jmap 堆内存查看工具。
jstat 虚拟机状态查看。
jvisualvm 和jconsole类似,功能稍微强大一点。
分析完成后如何尝试调优
JVM调优是一个经验的,并没有一个固定答案。同一个项目在不同服务器上运行可能调优方案差别就很大。
当分析后发现GC频率过高或Full GC 次数过多可以从两方面入手:
1. 尝试修改新生代,老年代,元空间大小。及新生代中Eden和survivor的比例。但是最好不能全动,需要一点一点增加或减少根据配置后的效果再次修改。
个人建议:JVM默认配置都官方经过多少次尝试给出的方案,能不动就不动。
2. 检查代码。很多时候是因为代码问题导致JVM性能下降。
(1) 避免创建大对象或大数组。因为大对象和大数组可能直接进入到老年代。增加Full GC 的次数。
(2) 避免一次做大量数据操作。可以考虑分页或分批完成。
最后的建议
JVM 并不是我们写的,我们只是使用它。
HotSpot之所以能够调优也是因为它给我们提供了一系列参数选项和工具,只要我们能够清楚JVM内存结构图、GC类型、GC算法、JVM参数就可以根据自己应用实际情况进行尝试调优。
JVM调优是无法给出固定答案的,因为一点点小变化可能导致调优方案不一样。所以只要具备调优的思想就可以应付所有的情况了。
另外优秀的代码也是调优的有段。好的代码可以大大减少调优的成本。
十四、 全盘负责委托机制
这是类加载的一种机制。即:当一个ClassLoader加载一个类时,除非明确指定,否则这个类所依赖和引用的类也由这个ClassLoader进行加载。
十五、 双亲委派机制
双亲委派机制在代码中体现可以通过java.lang.ClassLoader中的loadClass()方法进行查看。
断点查看可以总结出来下面流程,需要注意的是:
1. 委派的过程就是一层一层向上找的过程。只要当前加载器加载过,就不会重新加载。如果没有加载过,会向上寻找是否加载过。
2. 当加载到Bootstrap ClassLoader后会一层一层的向下判断是否可以进行加载,如果能加载则加载。如果不能加载向下一层寻找下个加载器是否能加载。如果到最后一层都无法加载则报ClassNotFoundException
.
好处:避免重复加载(加载一次就不加载)和避免核心类的串改(优先Bootstrap classloader)
十六、 类加载的生命周期/类加载机制
类加载的过程(类的生命周期)就是
1. 加载:加载.class文件到内存。包含自定义类的字节码文件和依赖及引用的字节码文件。
2. 链接:包含校验文件、静态变量申请内存赋初值、符号引用变成直接引用。
3. 初始化:执行静态代码块和静态变量赋值。
4. 运行。
5. 卸载。
【MySQL】
十七、 MySQL行列转置/MySQL行列转换(笔试题)
要求:
把下面数据转换
转换成下面效果
创建表及测试数据
create table student(
id int(11) primary key auto_increment,
name varchar(20),
subject varchar(20),
score double
);
insert into student values(1,'张三','语文',20);
insert into student values(2,'张三','数学',30);
insert into student values(3,'张三','英语',40);
insert into student values(4,'李四','语文',50);
insert into student values(5,'李四','数学',60);
insert into student values(6,'李四','英语',70);
可以使用if(表达式,值1,值2)函数。如果表达式成立返回值1,否则返回值2。其中值可以是列。
select
max(if(subject='语文',score,0)) 语文,
max(if(subject='数学',score,0)) 数学,
max(if(subject='英语',score,0)) 英语,
name
from student group by name;
也可以使用case 条件 when 条件取值 then 结果 … else 结果 end
select
max(case subject when '语文' then score else 0 end) 语文,
max(case subject when '数学' then score else 0 end) 数学,
max(case subject when '英语' then score else 0 end) 英语,
name
from student group by name;
十八、 请说一下MySQL支持的存储引擎?
MySQL 可以通过 show engines 查看所有支持的存储引擎。
在MySQL中默认支持的存储引擎有8个。 federated 默认是不启用的。主要用来提供对远程MySQL服务器上面的数据的访问接口。
- InnoDB
默认的存储引擎,也是所有存储引擎中唯一支持事务、XA协议、保存点的存储引擎。 - MyISAM
基于ISAM(Indexed Sequential Access Method目前已经废弃)的存储引擎,特点是查询效率较高。但不支持事务和容错性。 - MEMORY
纯内存型型存储引擎。所有数据都在内存中,硬盘只存储.frm文件。所以当MySQL宕机或非法关闭时只生效表结构。当然了,由于所有数据都在内存上,所以相对来说性能较高。 - MRG_MYISAM
以前也叫MERGE,简单理解就是对MyISAM表做了特殊的封装,对外提供单一访问入口,减少程序的复杂性。 - ARCHIVE
存储引擎主要用于通过较小的存储空间来存放过期的很少访问的历史数据。ARCHIVE表不支持索引,通过一个.frm的结构定义文件,一个.ARZ的数据压缩文件还有一个.ARM的meta信息文件。由于其所存放的数据的特殊性,ARCHIVE表不支持删除,修改操作,仅支持插入和查询操作。 - BLACKHOLE
俗称“黑洞”存储引擎。是一个非常有意思的存储引擎。所有的数据都是有去无回。 - CSV
存储引擎实际上操作的就是一个标准的CSV文件,他不支持索引。起主要用途就是大家有些时候可能会需要通过数据库中的数据导出成一份报表文件,而CSV文件是很多软件都支持的一种较为标准的格式,所以我们可以通过先在数据库中建立一张CSV表,然后将生成的报表信息插入到该表,即可得到一份CSV报表文件了。 - PERFORMANCE_SCHEMA
从MySQL 5.6新增的存储引擎。主要用于收集一些系统参数。
官方文档参考地址:https://dev.mysql.com/doc/refman/8.0/en/storage-engines.html
十九、 MySQL支持的日志类型?
MySQL 官方文档中说明MySQL共有六种日志类型
- 错误日志(Error log):记录MySQL运行过程中较为严重的警告和错误信息,以及MySQL启动和关闭的详细信息。可通过show variables like ‘%log_error%’;查看
- 通用查询日志(General query log):是记录建立的客户端连接和执行的语句。可通过通过show variables like ‘%general%’ 查看通用查询日志是否开启,默认是不开启的。
- 二进制日志(Binary log):包含所有更新数据或表结构(新增、删除、修改、改表等)SQL 信息的记录。可通过show variables like ‘%log_bin%’;查看,由于文件是二进制文件,所以是无法直接使用记事本查看的。在搭建MySQL主从时就要求开始二进制日志。
- 中继日志(Relay log):在MySQL主从复制实现中,记录从数据库数据改变。
- 慢查询日志(Slow query log):记录所有执行时间超过long_query_time秒的所有查询或不适用于索引的查询。可通过show variables like ‘%quer%’;查看。可通过log_queries_not_using_indexes 查看是否记录没有通过索引的SQL。
- DDL日志(DDL log):记录由影响表分区的数据定义语句生成的元数据操作。例如 ALTER TABLE t3 DROP PARTITION p2,我们必须确保完全删除分区并将其定义从表的分区列表中删除 t3。MySQL 使用此日志从分区元数据操作中间发生的崩溃中恢复。
官方文档参考地址: https://dev.mysql.com/doc/refman/8.0/en/server-logs.html
二十、 MySQL 的SQL语句执行流程
- **连接管理与安全验证:**MySQL有连接池(Connection Pool)管理客户端的连接。客户端连接后会验证用户名、密码、主机信息等
- 缓存(Cache&Buffer):缓存中存储了SQL命令的HASH,直接比对SQL命令的HASH和缓存中key是否对应,如果对应,直接返回结果,不再执行其他操作。由于缓存的是SQL的HASH,所以根据Hash特性SQL中空格等内容必须完全一样。缓存里面包含表缓存、记录缓存、权限缓存等。查询语句执行完成后会把查询结果缓存到缓存中。在MySQL中查询缓存默认不开启。考虑到查询缓存性能瓶颈问题,从MySQL8开始已经不支持查询缓存了。
- **解析器(Parser)**主要作用是解析SQL命令。将SQL命令分解成数据结构(解析树),后续的操作都是基于这个结构的。如果在分解过程中遇到错误,出现SQL解析错误。解析时主要检查SQL中关键字,检查关键字是否正确、SQL中关键字顺序是否正确、引号是否对应是否正确等。
- 预处理器:根据解析器的解析树,进一步检查表是否存在、列是否存在、名字和别名是否有歧义等。
- **优化器(Optimizer):**根据官网说明在执行SQL查询时会根据开销自动选择最优查询方案。采用“选择-投影-连接”的策略。先选择where中的行数。同时先选择要选择的列,而不是全部列,最后把内容合并到一起。
- **执行器:**包含执行SQL命令。获取返回结果。生成执行计划等。
- **存储引擎:**访问物理文件的媒介
MySQL中SQL执行流程文字说明
1. 客户端向服务器端发送SQL命令和连接参数
2. 服务器端连接模块连接并验证
3. 缓存模块解析SQL为Hash并与缓存中Hash表对应。如果有结果直接返回结果,如果没有对应继续向下执行。如果是MySQL 8 是没有查询缓存的。
4. 解析器解析SQL为解析树,检查关键字相关问题,如果出现错误,报SQL解析错误。如果正确,继续执行
5. 预处理器对解析树继续处理检查表、列别名等,处理成功后生成新的解析树。
6. 优化器根据开销自动选择最优执行计划,生成执行计划
7. 执行器执行执行计划,访问存储引擎接口
8. 存储引擎访问物理文件并返回结果
9. 如果开启查询缓存,缓存管理器把结果放入到查询缓存中。
10. 返回结果给客户端
二十一、 什么是执行计划?MySQL中执行计划类型有哪些?
执行计划:在MySQL中可以通过explain关键字模拟优化器执行SQL语句,从而知道MySQL是如何处理SQL语句的。
从好到坏排序。一般要求至少是range级别,最好能达到ref级别
system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL
给定表结构如下:
建表SQL语句
create table teacher(
tid int(11) primary key auto_increment,
tname varchar(20)
);
insert into teacher values(1,'老师1');
insert into teacher values(2,'老师2');
create table student(
sid int(11) primary key auto_increment,
sname varchar(20),
stid int(11),
constraint fk_t_s foreign key (stid) references teacher(tid)
);
insert into student values(1,'学生1',1);
1.1. system:逻辑表中只有一行数据,属于const的特例。如果物理表就一行数据为ALL
explain select * from (select * from student limit 0,1) t;
1.2. const:查询结果最多有一个匹配行。因为只有一行,所以可以被视为常量。const查询速度非常快,因为只读一次。一般情况下把主键或唯一索引作为唯一条件的查询都是const
explain select * from student where sid = 1
1.3. eq_ref: 查询时前表每次匹配到后表唯一一行数据(如果外键列有null时类型降低为ref),对于后表来说就是eq_ref。。但是需要注意,如果另一个表中所有行都被读取到了,就是ALL了。
explain select * from student s,teacher t where s.stid=t.tid;
ref:查询时,不是使用唯一索引或主键时的其他索引作为查询条件
explain select * from student where stid=1;
explain select * from student where stid=1 and sname='学生1';
1.4. fulltext: 只要是全文索引使用的就是fulltext类型。
1.5. ref_or_null:查询外检表时以外键列作为条件,搜索时包含null值。注意:只搜索外键列是null为ref
explain select * from student where stid=3 or stid is null;
1.6. index_merge 索引合并优化。当使用索引合并优化时的类型。要求只能合并单表的索引查询结果。
alter table student add column (age int(11));
create unique index bbb on student(age);
insert into student values (2,'学生2',2,12);
insert into student values (3,'学生3',2,13);
explain select * from student where stid=1 or age=1;
1.7. unique_subquery 当eq_ref时部分包含子查询时的情况替换为unique_subquery
explain select t.tid in (select s.sid from student s) from teacher t
1.8. index_subquery 子查询中使用了索引,但是没有使用唯一索引。
explain select t.tid in (select s.stid from student s) from teacher t
1.9. range:把这个列当作条件只检索其中一个范围。常见where从句中出现between、<、in等。主要应用在具有索引的列中
explain select * from student where sid between 1 and 5
1.10. index:Full Index Scan,index与ALL区别为index类型只遍历索引树。这通常为ALL块,因为索引文件通常比数据文件小。(Index与ALL虽然都是读全表,但index是从索引中读取,而ALL是从硬盘读取)
explain select count(*) from student;
1.11. ALL:Full Table Scan,遍历全表以找到匹配的行
explain select * from student where sname='学生';
官方文档参考地址:https://dev.mysql.com/doc/refman/8.0/en/explain-output.html
二十二、 什么是索引,索引的优缺点有哪些?
1. 索引是什么
索引类似图书的目录,一种数据结构,通过索引可以快速的找到需要查询的内容。MySQL官方文档中说明MySQL在500W~800W数据以上时查询性能可能下降,所以在大量数据时建立索引提升查询性能是非常有必要的。
索引和数据都是存储在.idb文件(InnoDB引擎),默认MySQL使用B+Tree(InnoDB引擎)结构进行存储索引数据,也可以修改为Hash索引。
B+Tree就是基于B-Tree而来的,主要是非叶子节点只存储键的信息;数据都记录在叶子节点上;所有叶子节点都有都有一个链式指针。
2. 索引的优点
为什么要创建索引?这是因为,创建索引可以大大提高系统的查询性能。
第一、通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。
第二、可以大大加快数据的检索速度,这也是创建索引的最主要的原因。
第三、可以加速表和表之间的连接,特别是在实现数据的完整性方面特别有意义。
第四、在使用分组和排序子句进行数据检索时,同样可以显著减少查询中分组和排序的时间。
第五、通过使用索引,可以在查询的过程中,使用查询优化器,提高系统的性能。
3. 索引的缺点
也许会有人要问:增加索引有如此多的优点,为什么不对表中的每一个列创建一个索引呢?这种想法固然有其合理性,然而也有其片面性。虽然,索引有许多优点, 但是,为表中的每一个列都增加索引,是非常不明智的。 这是因为,增加索引也有许多不利的一个方面:
第一、创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增加。
第二、索引需要占物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间。如果要建立聚簇索引,那么需要的空间就会更大。
第三、当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,这样就降低了数据的维护速度。
4. 什么样的字段适合创建索引
索引是建立在数据库表中的某些列的上面。因此,在创建索引的时候,应该仔细考虑在哪些列上可以创建索引,在哪些列上不能创建索引。 一般来说,应该在具备下述特性的列上创建索引:
第一、在经常需要搜索的列上,可以加快搜索的速度;
第二、在作为主键的列上,强制该列的唯一性和组织表中数据的排列结构;
第三、在经常用在连接的列上,这些列主要是一些外键,可以加快连接的速度;
第四、在经常需要根据范围进行搜索的列上创建索引,因为索引已经排序,其指定的范围是连续的;
第五、在经常需要排序的列上创建索引,因为索引已经排序,这样查询可以利用索引的排序,加快排序查询时间;
第六、在经常使用在WHERE子句中的列上面创建索引,加快条件的判断速度。
建立索引,一般按照select的where条件来建立,比如: select的条件是where f1 and f2,那么如果我们在字段f1或字段f2上建立索引是没有用的,只有在字段f1和f2上同时建立索引才有用等。
5. 什么样的字段不适合创建索引:
同样,对于有些列不应该创建索引。一般来说,不应该创建索引的这些列具有下述特点:
第一,对于那些在查询中很少使用或者参考的列不应该创建索引。这是因为,既然这些列很少使用到,因此有索引或者无索引,并不能提高查询速度。相反,由于增加了索引,反而降低了系统的维护速度和增大了空间需求。
第二,对于那些只有很少数据值的列也不应该增加索引。这是因为,由于这些列的取值很少,例如人事表的性别列,在查询的结果中,结果集的数据行占了表中数据行的很大比例,即需要在表中搜索的数据行的比例很大。增加索引,并不能明显加快检索速度。
第三,对于那些定义为text, image和bit数据类型的列不应该增加索引。这是因为,这些列的数据量要么相当大,要么取值很少。
第四,当修改性能远远大于检索性能时,不应该创建索引。这是因为,修改性能和检索性能是互相矛盾的。当增加索引时,会提高检索性能,但是会降低修改性能。当减少索引时,会提高修改性能,降低检索性能。因此,当修改性能远远大于检索性能时,不应该创建索引。
二十三、 索引分类
可以通过show index from 表名,查询表中包含的索引。
给定表结构测试索引
create table demo(
id int(11) primary key,
col1 int,
col2 int,
col3 varchar(20)
);
1. 单列索引。就是给一个列添加索引。
1.1 普通索引:不考虑过多情况,主要是为了让查询更快一些。
语法:create index 索引名 on 表名(列)
例如:create index index1 on demo(col1);
1.2 唯一索引:列中值不可以重复,可以是null
语法:create unique index 索引名 on 表名(列)
例如:create unique index index2 on demo(col2);
1.3 主键索引:列中值不可以重复,又不可以为null。简单点理解不允许为null的列添加上唯一索引就是主键索引。
2. 组合索引。
给表中大于等于两个列添加索引。但是需要满足最左前缀,创建组合索引相当于创建了多个索引,一般把最常用的放在最左边。
语法:create index 索引名 on 表名(列1,列2...)
例如:create index index3 on demo(col1,col2,col3)
此时相当于创建col1、col1-col2、col1-col2-col3、col1-col3四个索引。可以通过执行计划查询,如果执行计划类型为ref表示使用索引,如果类型为index表示没有匹配到合适索引。
3. 全文索引(full-text)
只能在char、varchar、text等字段才可以使用全文索引。全文索引是抽取一列内容的关键字,通过关键字建立索引。例如:我们都在尚学堂学习。当搜索尚学堂时就可以搜索这句话。所以全文所以适用于含有like的查询。但是也只能解决‘xxxx%’模糊查询低效的问题。
语法:create fulltext index 索引名 on 表名(列)
二十四、 请介绍下什么是全文索引
MySQL从3.23.23版开始支持全文索引和全文检索,FULLTEXT索引在MySQL5.6之前仅可用于 MyISAM 表,在MySQL5.7后InnoDB也支持,MySQL8中InnoDB也支持;他们可以从CHAR、VARCHAR或TEXT列中作为CREATE TABLE语句的一部分被创建,或是随后使用ALTER TABLE 或CREATE INDEX被添加。
对于较大的数据集,将你的资料输入一个没有FULLTEXT索引的表中,然后创建索引,其速度比把资料输入现有FULLTEXT索引的速度更为快。不过切记对于大容量的数据表,生成全文索引是一个非常消耗时间非常消耗硬盘空间的做法。
全文索引对中文支持不好,如果搜索中文就只能按照最左对照进行搜索。如果是英文就可以匹配中间
给定表结构,演示全文索引的使用
create table tb_fulltext(
id int(11) primary key auto_increment,
name varchar(100),
address varchar(200)
);
1. 创建索引
有三种方式创建全文索引。
CREATE FULLTEXT INDEX index_name ON table_name(column(length))
TABLE table_name ADD FULLTEXT index_name( column)
CREATE TABLE table_name (id int not null auto_increment,title varchar(30) ,PRIMARY KEY(id) , FULLTEXT index_name (title))
例如:create fulltext index ft on tb_fulltext(name,address);
1.2 创建后的全文索引需要配合match(列,列) against(‘内容’)使用。
1.2.1 match中列必须和创建全文索引的列一样。例如创建全文索引时(id,name),match(name)无法使用全文索引,必须单独建立name列的全文索引
insert into tb_fulltext values(1,'tony','beijing yizhuang');
insert into tb_fulltext values(2,'kevin','beijing yizhuang jingkaiqu');
insert into tb_fulltext values(3,'jordan','beijing daxing');
explain select * from tb_fulltext where match(name,address) against('yizhuang');
select * from tb_fulltext where match(name) against('yizhuang');// 执行报错
1.2.2 against中内容有三种模式
自然语言模式:IN NATURAL LANGUAGE MODE
布尔模式:IN BOOLEAN MODE
查询扩展模式:WITH QUERY EXPANSION
1.3 自然语言模式:拆分出来的关键字必须严格匹配。例如beijing只能通过beijing搜索,不能通过bei搜索。
drop index ft on tb_fulltext;
create fulltext index ft on tb_fulltext(address);
select * from tb_fulltext where match(address) against('daxing');
1.4 布尔模式:支持特殊符号。即使对没有全文索引的列也可以进行搜索,但是非常慢。查询时必须从最左开始查询,例如:北京昌平,按照昌平无法查询
+ 一定要有(不含有该关键词的数据条均被忽略)。
- 不可以有(排除指定关键词,含有该关键词的均被忽略)。
> 提高该条匹配数据的权重值。
< 降低该条匹配数据的权重值。
~ 将其相关性由正转负,表示拥有该字会降低相关性(但不像 - 将之排除),只是排在较后面权重值降低。
* 万用字,不像其他语法放在前面,这个要接在字符串后面。 因为搜索时只能满足最左前缀搜索like ‘内容%’,不能实现类似like ‘%内容%’这种。
" " 用双引号将一段句子包起来表示要完全相符,不可拆字。
# daxin* 可以搜索到内容。 daxin 不能搜索到内容
explain select * from tb_fulltext where match(address) against('daxin*' in boolean mode);
1.5 查询扩展。查询时进行扩展查询,发现和条件有关系的内容都查询出来
insert into tb_fulltext values(4,'kelvin','hebei baoding');
# 结果查询到三行,id为4的没有被查询出来。因为daxing相关的词条有beijing、所有包含beijing的都被查询出来了。
explain select * from tb_fulltext where match(address) against('daxing' with query expansion);
中文拆词器ngram
由于中文是没有空格的,MySQL 从5.7.6开始内置ngram中文分词插件。可以设置把整个中文按照指定大小进行拆词。
- 在my.ini中[mysqld]下添加参数,设置拆词长度(根据自己的需求进行完成即可)
ngram_token_size=2
- 给address创建全文索引。注意后面的with parser ngram
create fulltext index index3 on ft(address) with parser ngram;
二十五、 MySQL优化?
MySQL 优化主要从两方面入手:硬件级别(Hardware Level)和数据库级别(Database Level)
硬件级别瓶颈包含如下的几个方面
1 磁盘寻找(Disk seeks):
磁盘需要一段时间才能找到一段数据。对于现代磁盘来说,这种平均时间通常低于10ms,因此理论每秒100次寻找。这一次用新的磁盘缓慢地改进,并且对于单个表是很难优化的。优化查找时间的方法是将数据分发到一个以上的磁盘上。
2 磁盘读写(Disk reading and writing):
当磁盘处于正确位置时,我们需要读取或写入数据。使用现代磁盘,一个磁盘可以提供至少10到20Mb/s的吞吐量。这比磁盘寻找更容易优化,因为您可以从多个磁盘并行读取。
3 CPU(CPU cycles):
当数据在主存储器中时,我们必须处理它以得到结果。与内存量相比,具有大数据库量的表是最常见的限制因素。但是对于小表来说,速度通常不是问题。
4 存储带宽(Memory bandwidth):
当CPU需要比CPU缓存中更多的数据时,主存储器带宽成为瓶颈。对于大多数系统来说,这是一个不常见的瓶颈,但也是一个需要注意的点
数据库级别:导致数据库应用最高效的因素是它的基本设计
1 表结构是否正确(Are the tables structured properly?):
特别的是列是否具有正确的类型,和表中列的个数是否正确。
例如:对于执行频繁更新的应用通常设计更多表,每个表的列并不多。而对于需要进行大量分析的应用通常设计更少的表,而每个表的列更多一些。
2 正确的设置索引达到查询高效(Are the right indexes in place to make queries efficient?):
需要考虑的是什么SQL会导致索引无效,什么情况会让查询效率更高
3 对于不同情况选择不同的存储引擎(Are you using the appropriate storage engine for each table, and taking advantage of the strengths and features of each storage engine you use? )
选择不同的存储引擎对性能和可伸缩性具有较大的影响
4 每张表是否具有适当的行格式(Does each table use an appropriate row format?):
主要取决于适当的存储引擎。压缩表可以占用更低的磁盘空间和更少的I/O操作。压缩表适用于InnoDB和MyISM存储引擎。
5 应用程序是否使用适当的锁策略(Does the application use an appropriate locking strategy? ):
在具有高并发、分布式应用程序中,选择适当的锁策略以保证数据的共享性和特定的情况下独占数据。
InnoDB存储引擎在不需要我们参与下能处理大部分锁问题,允许数据库实现更好的并发性,减少代码调优量。
6 所有缓存区域使用的大小是否都正确(Are all memory areas used for caching sized correctly?):
配置的原则是缓存区域大到足以容纳所有频繁访问的数据,但又不能太大,否则导致过量占用物理内存而导致分页。
一般情况下需要配置InnoDB的缓冲池、MyISAM密钥缓存和MySQL的查询缓存
优化的哲学
注意:优化有风险,涉足需谨慎!
优化可能带来的问题
1 优化不总是对一个单纯的环境进行,还很可能是一个复杂的已投产的系统。
2 优化手段本来就有很大的风险,只不过你没能力意识到和预见到!
3 任何的技术可以解决一个问题,但必然存在带来一个问题的风险!
4 对于优化来说解决问题而带来的问题,控制在可接受的范围内才是有成果
结论:保持现状或出现更差的情况都是失败!
优化的需求
1 稳定性和业务可持续性,通常比性能更重要!
2 优化不可避免涉及到变更,变更就有风险!
3 优化使性能变好,维持和变差是等概率事件!
4 切记优化,应该是各部门协同,共同参与的工作,任何单一部门都不能对数据库进行优化!
结论:所以优化工作,是由业务需要驱使的!!!
优化由谁参与
在进行数据库优化时,应由数据库管理员、业务部门代表、应用程序架构师、应用程序设计人员、应用程序开发人员、硬件及系统管理员、存储管理员等,业务相关人员共同参与
。
优化的顺序
优化选择:
优化成本:硬件>系统配置>数据库表结构>SQL及索引
优化效果:硬件<系统配置<数据库表结构<SQL及索引
官方文档参考地址: https://dev.mysql.com/doc/refman/8.0/en/optimize-overview.html
二十六、 SQL优化
表设计方面:
1、 NULL值
在老版本中含有NULL的列无法触发索引,但是在MySQL 5.7 以后已经可以触发索引了。但是可能出现“无法预料的结果”。
所以在建立表时给列添加not null 约束或default 默认值是非常好的优化手段
2、 表格字段类型选择
尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能,并会增加存储开销。这是因为引擎在处理查询和连接时会逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了。
尽可能的使用 varchar 代替 char ,因为首先可变长度字段存储空间小,可以节省存储空间,其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。
3、 组合索引使用
在使用索引字段作为条件时,如果该索引是复合索引,那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引,否则该索引将不会被使用,并且应尽可能的让字段顺序与索引顺序相一致。
4、避免全表扫描
对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。
select 从句部分
5、 查询语法中的字段
任何地方都不要使用 select * from t ,用具体的字段列表代替“*”,不要返回用不到的任何字段。
where 从句部分
6、避免负向条件
应尽量避免在 where 子句中使用!=或<>操作符,否则引擎将放弃使用索引而进行全表扫描。
负向条件有:
!=、<>、not in、not exists、not like 等
explain select * from teacher where address !='aa';
1.1. 避免使用or逻辑
应尽量避免在 where 子句中使用 or 来连接条件,否则将导致引擎放弃使用索引而进行全表扫描,如:
select id from t where num=10 or num=20
可以这样查询:
select id from t where num=10
union all
select id from t where num=20
在MySQL5.7 开始or已经可以触发索引了,老版本不可以。
1.2. 慎用in和not in逻辑
in可以代替union all操作,虽然对CPU性能稍微增加一些,但是也可以忽略不计了。
in 和 not in 也要慎用,否则会导致全表扫描,如:
select id from t1 where num in(select id from t2 where id > 10)
此时外层查询会全表扫描,不使用索引。可以修改为:
select id from t1,(select id from t1 where id > 10)t2 where t1.id = t2.id
此时索引被使用,可以明显提升查询效率。
7、 避免查询条件中字段计算
应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描。如:
select id from t where num/2=100
应改为:
select id from t where num=100*2
8、 避免查询条件中对字段进行函数操作
应尽量避免在where子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描。如:
select id from t where substring(name,1,3)='abc'--name以abc开头的id
应改为:
select id from t where name like 'abc%'
9 、WHERE子句“=”左边注意点
不要在 where 子句中的“=”左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引。
10、 注意模糊查询
下面的查询也将导致全表扫描:
select id from t where name like '%abc%'
模糊查询如果是必要条件时,可以使用select id from t where name like 'abc%'来实现模糊查询,此时索引将被使用。
如果头匹配是必要逻辑,建议使用全文搜索引擎(Elastic search、Lucene、Solr等)。
子查询方面:
11、 exists
很多时候用 exists 代替 in 是一个好的选择:
select num from a where num in(select num from b)
用下面的语句替换:
select num from a where exists(select 1 from b where num=a.num)
其他查询方面
12、 索引也可能失效
并不是所有索引对查询都有效,SQL是根据表中数据来进行查询优化的,当索引列有大量数据重复时,SQL查询可能不会去利用索引,如一表中有字段sex,male、female几乎各一半,那么即使在sex上建了索引也对查询效率起不了作用。
13、 命令选择
不使用*、尽量不使用union,union all等关键字、尽量不使用or关键字、尽量使用等值判断。
连接方法
表连接建议不超过5个。如果超过5个,则考虑表格的设计。(互联网应用中)
表连接方式使用外联优于内联。
外连接有基础数据存在。如:A left join B,基础数据是A。
A inner join B,没有基础数据的,先使用笛卡尔积完成全连接,在根据连接条件得到内连接结果集。
大数据量级的表格做分页查询时,如果页码数量过大,则使用子查询配合完成分页逻辑。
Select * from table limit 1000000, 10
Select * from table where id in (select pk from table limit 1000000, 10)
官方文档: https://dev.mysql.com/doc/refman/8.0/en/statement-optimization.html
二十七、 索引优化
上面都在说使用索引的好处,但过多的使用索引将会造成滥用。因此索引也会有它的缺点。虽然索引大大提高了查询速度,同时却会降低更新表的速度,如对表进行INSERT、UPDATE和DELETE次数大于查询次数时,放弃索引。因为更新表时,MySQL不仅要保存数据,还要保存一下索引文件。建立索引会占用磁盘空间的索引文件。一般情况这个问题不太严重,但如果你在一个大表上创建了多种组合索引,索引文件的会膨胀很快。索引只是提高效率的一个因素,如果你的MySQL有大数据量的表,就需要花时间研究建立最优秀的索引,或优化查询语句。
1. 使用短索引(前缀索引)
对串列进行索引,如果可能应该指定一个前缀长度。例如,如果有一个CHAR(255)的列,如果在前10个或20个字符内,多数值是惟一的,那么就不要对整个列进行索引。短索引不仅可以提高查询速度而且可以节省磁盘空间和I/O操作。
CREATE INDEX index_name ON table_name (column(length))
2. 索引列排序
MySQL查询只使用一个索引,因此如果where子句中已经使用了索引的话,那么order by中的列是不会使用索引的。因此数据库默认排序可以符合要求的情况下不要使用排序操作;尽量不要包含多个列的排序,如果需要最好给这些列创建复合索引。
3. like语句操作
一般情况下不鼓励使用like操作,如果非使用不可,如何使用也是一个问题。like “%aaa%” 不会使用索引,而like “aaa%”(非前导模糊查询)可以使用索引。
使用后,优化到range级别
explain select * from teacher where address like '%oracle%';
4. 不要在列上进行运算
例如:select * from users where YEAR(adddate)<2007,将在每个行上进行运算,这将导致索引失效而进行全表扫描,因此我们可以改成:select * from users where adddate<’2007-01-01′。
应该把计算放在业务代码上完成,而不是交给数据库
5. 范围列可以使用索引
范围条件有:<、<=、>、>=、between等。
范围列可以用到索引(联合索引必须是最左前缀),但是范围列后面的列无法用到索引,索引最多用于一个范围列,如果查询条件中有两个范围列则无法全用到索引。所以where中把最主要的查询条件放在第一个。
alter table teacher add column (age int(3));
alter table teacher add column (weight int(3));
select * from teacher;
update teacher set age=10,weight=90 where id = 1;
update teacher set age=20,weight=100 where id = 2;
update teacher set age=30,weight=110 where id = 3;
create index age_index on teacher(age);
create index weight_index on teacher(weight);
explain select * from teacher where age between 10 and 20 and weight between 90 and 100;
5.类型转换会导致索引无效
当列是文本类型时,把数值类型当作列的条件会弃用索引
explain select * from teacher where name = 20;
6. 索引总结
最后总结一下,MySQL只对以下操作符才使用索引:<,<=,=,>,>=,between,in,以及某些时候的like(不以通配符%或_开头的情形)。而理论上每张表里面最多可创建16个索引,不过除非是数据量真的很多,否则过多的使用索引也不是那么好玩的。
建议:一个表的索引数最好不要超过6个,若太多则应考虑一些不常使用到的列上建的索引是否有必要。
官方文档参考地址:https://dev.mysql.com/doc/refman/8.0/en/optimization-indexes.html
二十八、 说一下MySQL中锁的分类及SQL语句
**MySQL中锁按照不同分类标准有多种分类。**
按照锁的级别分为:共享锁、排它锁。这也是最根本的分类标准。
按照锁的目标分为:行级锁、表级锁。锁影响到整个表叫做表级锁。锁影响到某一行或某几行叫做行级锁。
按照锁是否自动添加分为:自动锁、显示锁。MySQL InnoDB引擎会自动对Insert、Delete、Update添加排它锁,Select操作不会自动添加锁,但可以通过命令添加锁。
因为都是对锁的划分,所以一个锁操作可能即是共享锁,又是行级锁,有是显示锁。
共享锁又称读锁,表示在读取数据时不允许其他事务对数据进行修改。
SQL语句:
select .... lock in share mode;
排它锁又称独占锁。在一个事务操作时,其他事务不允许操作数据。
SQL:
select .... for update;
二十九、 MySQL支持的表连接类型有几种?
MySQL支持的表连接类型分为分为:内连接、左外连接、右外连接。Oracle中支持全外连接,但MySQL不支持。
给定测试表及测试数据
create table teacher(
tid int(11) primary key,
tname varchar(20)
);
insert into teacher values(1,'老师1');
insert into teacher values(2,'老师2');
insert into teacher values(3,'老师3');
create table student(
sid int(11) primary key,
sname varchar(20),
stid int(11)
);
insert into student values(1,'学生1',1);
insert into student values(2,'学生2',1);
insert into student values(3,'学生3',2);
insert into student values(4,'学生4',null);
笛卡尔积:多表连接查询出来的结果,不去除任何未关联数据。例如表A有N条数据,表B有M条数据,查询出来的结果有N*M条。
select * from student s,teacher t;
内连接使用inner join,表示查询出来两个具有关联的数据,无关联性的数据不会查询出来。也可以直接使用多表查询
select * from student s inner join teacher t on s.stid=t.tid;
select * from student s,teacher t where s.stid=t.tid;
左外连接使用left outer join,表示即使左表存在未关联数据,也被查询出来。在left outer join左侧的表叫做左表。
select * from student s left outer join teacher t on s.stid=t.tid;
select * from teacher t left outer join student s on s.stid=t.tid;
右外连接使用right outer join,表示即使右表存在未关联数据,也被查询出来。在left outer join右侧的表叫做右表。
select * from student s right outer join teacher t on s.stid=t.tid;
select * from teacher t right outer join student s on s.stid=t.tid;
三十、 请说一下count(*),count(列),count(1)的区别
对于InnoDB引擎count(*)和count(1)没有性能的区别,都是自动寻找主键列或唯一索引。统计的时候都是统计包含null的行,所以效果都是返回表中数据的行数。
官方文档参考地址: https://dev.mysql.com/doc/refman/8.0/en/aggregate-functions.html
而count(列) 统计列中包含多少行数据,不包含NULL值。
统计说明:
在InnoDB引擎中count(*)和count(1)性能没有什么差别
count(列)需要看列和count(*)优化后的列情况,如果count(列)使用了非索引列,而表中包含索引列则count(*)更快。
如果count(列)和count(*)优化后的是同一个列则性能没有什么差别。如果表中没有索引则count(列)和count(*)性能也没有什么差别。
三十一、 聚集索引和非聚集索引的区别
聚集和非聚集最主要的区别就是索引是否和数据在一起。
**聚集索引(聚簇索引)**之所以叫“聚集”是因为索引和数据一起存储。叶子节点中存储索引和数据。因为数据顺序和索引顺序是一样的,且顺序存储,所以查询速度比较快。当然了因为有顺序的所以新增需要重新排序会影响速度。简单记忆:主键索引就是聚集索引。
非聚集索引之所以叫“非聚集”是因为叶子节点中只存储主键和索引列。所以在非聚集索引查询时需要通过主键再去查询想要的数据。在叶子节点上通过主键查询数据的过程就是所谓的回表。
【Spring Security】
三十二、 Spring Security 认证流程(原理)
1. 客户端发起URL请求
2. 判断URL是否是登录URL,如果不是登录URL执行十一个Filter内置Filter功能实现。所有Filter都是通过FilterChainProxy进行协调。最终跳转到登录页面。
3. 如果是登录URL,依然执行内置Filter链,其中UsernamePasswordAuthenticationFilter负责获取到请求中的用户名和密码,并把用户名和密码封装到UsernamePasswordAuthenticationToken中。
4. 通过AuthenticationManager调用自定义登录逻辑UserDetailsService
5. 把返回结果UserDetails交给AuthenticationProvider进行处理
6. 根据处理结果调用成功处理器或失败处理器显示最终结果。
流程图
【Spring Boot】
三十三、 Spring Boot配置文件支持路径和优先级(面试题)
Spring Boot提供一个名称为application的全局配置文件,支持两种格式properties格式与YML格式。
配置文件加载顺序
1 不同格式的加载顺序
如果同一个目录下,有application.yml也有application.properties,默认先读取application.properties。
如果同一个配置属性,在多个配置文件都配置了,默认使用第1个读取到的,后面读取的不覆盖前面读取到的。
2 不同位置的加载顺序
config/application.properties
config/application.yml
application.properties
application.yml
resources/config/application.properties
resources/config/application.yml
resources/application.properties
resources/application.yml
三十四、 Spring Boot中的注解
Spring Boot项目中对Spring framework、Spring MVC、MyBatis等注解都是支持的。纯Spring Boot自己的注解常用的并不是特别多。
@SpringBootApplication 启动器注解。
@SpringBootTest 测试类注解。
下面的注解并不是特别常用,但是在自定义启动器时需要使用的注解
@EnableAutoConfiguration 启动自动化配置
@ConditionalOnBean 容器中存在指定Bean时才会去创建Bean
@ConditionalOnClass 当前项目如果能找到指定类时才会创建这个类的Bean
@ConditionalOnSingleCandidate 当容器中存在一个时或者虽然有多个但是指定首选Bean
@EnableConfigurationProperties 启动配置属性类
@AutoConfigureAfter 在加载配置类后在加载当前类
@ConfigurationProperties 定义属性前缀
@ConditionalOnMissingBean 只有当容器中不存在指定Bean时才会去实例化。
除此以外都是Spring 框架和Spring MVC框架以及MyBatis框架的注解了。
三十五、 Spring Boot 执行流程/Spring Boot 原理
Spring Boot项目执行流程,可以通过打断点的方式进行查看。
首先加载SpringApplication的构造方法
run方法中代码含义如下
执行流程文字说明:
SpringBoot项目通过SpringApplication.run()作为启动入口
1. 进入SpringApplication构造方法,从META-INF/spring.factories中读取初始化和监听器,并记录主类。
2. 创建计时器StopWatch
3. 声明上下文对象ConfigurableApplicationContext
4. 启动Headless服务器,设置系统属性
5. 创建并启动运行时监听器
6. 准备环境Environment并打印Banner
7. 上下文环境准备及刷新,此时Tomcat被启动。
8. 停止计时器,并打印启动时间。
9. 运行时监听器启动。
10. 返回上下文对象。
三十六、 Spring Boot自动化配置原理
使用Spring自家产品时,所有的配置都是spring-boot-autoconfigure中。当使用第三方产品时需要由第三方产生进行提供自动化配置。
以mybatis-spring-boot-starter举例,分析得出重要角色:
mybatis-spring-boot-autoconfigure项目包含:
MyBatisAutoConfiguration自动化配置类
MyBatisProperties 支持的配置属性
查看mybatis-spring-boot-starter依赖的自动化配置
每个启动器都会有个自动化配置的依赖
查看spring.factories文件
在@SpringBootAutoConfiguration注解中包含了
@EnableAutoConfiguration注解。在SpringBoot项目启动时会自动加载META-INF/spring.factories中EnableAutoConfiguration内容
在mybatis-spring-boot-autoconfigure中/META-INF/spring.factories编写了自动化配置类
查看MyBatisAutoConfiguration
上面配置了自动化配置条件
查看MybatisProperties
MyBatisAutoConfiguration中提供了两个Bean
SqlSessionFactory的Bean
SqlSessionTemplate的Bean
提供了两个static的内部类
AutoConfiguredMapperScannerRegistrar和MapperScannerRegistrarNotFoundConfiguration
提供了MapperScannerConfigurer扫描注解
自动装配文字说明:
1.启动类中@SpringBootApplication中包含@EnableAutoConfiguration
2.加载项目依赖的META-INF/spring.factories中EnableAutoConfiguration对应类,进行实例化。
3. 实例化自动化配置类时会同时加载属性类,从配置文件中读取。
4. 生效自动化配置,把对应的Bean放入到Spring容器中。
三十七、 Spring Boot如何自定义启动器
自定义SpringBoot启动器时一般包含两块:
xxx-spring-boot-autoconfigure:里面编写自动化配置相关内容。
xxx-spring-boot-starter:只是一个依赖,依赖xxx-spring-boot-autoconfigure
需求:
要求定义出bjsxt-spring-boot-starter
支持配置文件属性配置
bjsxt.name=www.bjsxt.com
bjsxt.age=15
新建bjsxt-spring-boot-autoconfigure项目
添加依赖
在pom.xml中添加依赖,spring-boot-configuration-processor负责帮助处理xml和properties配置的。
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.5</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
新建BjsxtProperties
新建com.bjsxt.starter.BjsxtProperties。
@ConfigurationProperties中prefix表示配置文件中配置属性前缀。
@ConfigurationProperties(prefix = "bjsxt")
public class BjsxtProperties {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
配置自动化类
在resources中下新建META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.bjsxt.starter.BjsxtAutoConfiguration
安装
对bjsxt-spring-boot-autoconfigure进行本地install
maven install
新建bjsxt-spring-boot-starter
此项目只是依赖自动化配置,让其他项目依赖当前项目,符合Spring Boot启动器规范。
<dependencies>
<dependency>
<groupId>com.bjsxt</groupId>
<artifactId>bjsxt-spring-boot-autoconfigure</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
对当前项目进行install 。
依然是选择maven --> lifecycle --> install
新建测试项目
任意新建项目,依赖bjsxt-spring-boot-starter。在单元测试中进行测试结果
@SpringBootTest
class DemoApplicationTests {
@Autowired
private BjsxtProperties bjsxtProperties;
@Test
void contextLoads() {
System.out.println(bjsxtProperties.getName());
System.out.println(bjsxtProperties.getAge());
}
}
自定义启动器的流程 文字说明:
1. 创建自动化配置项目 xxx-spring-boot-autoconfigure
2. 在项目中提供自定义属性类,添加自定义属性。在类上添加@ConfigurationProperties
3. 再创建一个自动化配置类。添加@Configuration、@EnableConfigrationProperties
4. 在类路径下添加META-INF/spring.factories设置自动化配置类的全限定路径。
5. 打包项目后新建xxx-spring-boot-starter,并且在启动器项目中添加上自动化配置项目的依赖。
三十八、 Spring Boot 支持的配置文件类型及优先级
Spring Boot支持三种格式配置文件:
.properties 格式为key=value形式
.yml 格式为key: value 采用树状结构
yaml 格式和yml 格式相同。
优先级: .properties > yml> .yaml
【Maven】
三十九、 请说一下POM模型
POM(Project Object Model)即项目对象模型,在Maven中使用pom.xml进行体现,声明式配置。
可以在pom.xml中可以配置项目与项目的关系,插件等信息。
POM模型中认为每个项目都是一个对象,项目和项目之间可以存在关系:依赖、继承、聚合。
通过坐标引用其他项目的jar。如果引用时本地仓库不存在,会从中央仓库中下载到本地仓库。
四十、 Maven中项目与项目的关系
依赖:当一个项目需要使用另一个项目的类时可以设置为依赖关系。由于Maven具有依赖传递性,有时也通过依赖替代继承来获取到另一个项目的pom.xml(Spring Cloud)。
继承:当一个项目需要继承另一个项目的pom.xml时,可以设置为继承关系。Maven中只支持单继承。
聚合:聚合是在一个窗口中通过一个父项目管理整个项目的pom.xml,方便进行版本管理、依赖管理等。
四十一、 继承和聚合的区别
继承关系是指子项目可以继承父项目的pom.xml中配置。
子项目中需要通过<parent>配置父项目的坐标,子项目的groupid和version都可以和父项目的不同。父项目中没有任何子项目的信息。
聚合项目时父项目类型为pom类型,父项目和子项目都存在一个窗口中,可认为是一个项目,但是每个子项目又可以独立运行。
在聚合项目中子项目需要通过<parent>配置父项目的坐标,子项目和父项目的groupid和version都和父项目的相同。父项目通过<modules>配置聚合的子项目。
四十二、 Maven中项目的类型
Maven项目中项目类型是在pom.xml中通过<packaging>进行配置的。可取值:
jar:默认值,表示打包为jar类型项目。
war:web项目。当需要打包为servlet容器支持时,需要配置此类型。
pom:逻辑父项目。聚合项目中父项目的类型。当设置为pom时Maven只解析pom.xml,不会编译src中内容。在idea中创建聚合项目时,自动会把父项目设置为pom类型。
四十三、 Maven常用命令
Maven是一款独立软件,也可以被IDEA或Eclipse集成。在IDEA中默认集成了Maven,可以通过Maven面板进行输入Maven命令。
mvn help:system 打印系统属性和环境变量。
mvn clean 清理target目录。
mvn validate 验证项目是否可用。
mvn compile 编译项目,生成target目录。
mvn test 执行测试。
执行测试时会自动运行src/test/java 目录下所有
**/*Test.java、**/Test*.java、**/*TestCase.java 文件,采用约定方式执行。
mvn package 打包项目。
mvn deploy 发布项目。
mvn verify 校验包是否正确。
mvn install 安装到本地仓库。
mvn install -DskipTests 跳过测试
mvn site 生成站点。但是需要添加插件才能使用。
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-project-info-reports-plugin</artifactId>
<version>2.7</version>
</plugin>
</plugins>
</build>
四十四、 Maven项目的生命周期
Maven项目生命周期是指项目运行过程中所经历的过程,每一步骤都需要等待前面步骤执行完成,整个流程严格按照顺序执行。
不同操作有不同的生命周期,例如clean,site,deploy。平时我们所说的项目周期就指的是deploy的生命周期。
可以执行整个流程,也可以执行其中某个步骤,但是无论执行哪个步骤都是从第一个步骤开始执行。
四十五、 Maven和Ant对比
Ant和Maven都是Apache的项目管理工具,也都是Java程序非常常用的编译、运行、测试、打包工具。
虽然都是项目管理工具,但是Maven的功能要比Ant更强大。
无论是IDEA还是Eclipse,默认的Java项目就是使用Ant进行项目管理的。
在老Ant项目需要自己添加build文件,但是在目前的java项目中都不需要自己进行管理,
都可以通过idea或eclipse帮助进行管理即可。
Maven项目在创建时需要明确的指定项目类型为maven项目,整个Maven项目采用约定的方式,文件等内容都有固定的位置,通过pom模型进行管理项目。同时Maven具有中央仓库和本地仓库,这点也是优于Ant的一大特点,在移植项目时只需要移植源码即可。并且Maven项目是有完整生命周期的,而Ant是没有的。
#【Spring MVC面试题】
四十六、 请介绍一下什么是MVC?
MVC是一种软件开发模式,是目前Java项目使用的最多的一种软件开发模式。因为Sun公司推荐JavaEE开发使用MVC模式。针对于MVC,Sun公司也推出了JSP Model1和JSP Model2,我们现在使用的就是JSP Model 2。
MVC适用于中大型项目,开发时由底层向上层开发,越底层复用性越高。
MVC把项目由单一大文件拆分成多个部分。包含:
M(Model)即模型。包含数据模型和业务模型。数据模式就是项目中的实体类,实现了数据封装的重用性。业务模型就是项目中的Service,业务类。
V(View)即视图。视图就是呈现给用户的具体内容,如数据表格显示,树状菜单。目前已经发展成了一个独立岗位,前后端分离开发时的前端工程师就负责的是V。
C(Controller)即控制器。在以前的项目中控制器主要的作用是根据业务模型和数据模
式控制显示的视图。目前的项目中大部分就是直接由控制器返回接口数据,例如:JSON数据,XML数据等。
四十七、 请说一下Spring MVC中你知道的组件
DispatcherServlet:前端控制器。Spring MVC的入口,也是整个流程控制中心。其他组件由DispatcherServlet统一调度,降低了组件和组件之间的耦合度。
MultipartResovler:多部分处理器。文件上传时需要使用。
LocaleResolver:解决客户端的区域和时区问题。
ThemeResolver:主题解析器。刻提供自定义布局。
HandlerMapping: 映射处理器。主要负责处理URL,并找到对应的Handler
HandlerAdapter:适配器。负责调用具体的Handler。
HandlerExceptionResovler:异常处理器。异常处理,根据不同异常返回视图。
RequestToViewNameTranslator:从请求中获取到视图名称。
ViewResovler:视图解析器,负责解析字符串视图名和物理视图文件的。
FlashMapManager:主要用于存储属性的,本质是一个Map。多用在重定向时。FlashMap在重定向之前存储,重定向之后删除。
ModelAndView:模型和视图。Spring MVC中提供的模型和视图接口。
HandlerInterceptor:拦截器。拦截控制器资源的。
其中第2个到第10个组件是在DispatcherServlet的initStrategies中进行初始化的。
四十八、 Spring MVC 运行原理
1. 客户端向服务端发起请求,当URL匹配后执行DispatcherServlet
2. DispatcherServlet根据URL类型调用HandlerMapping
3. HandlerMapping查找Handler后产生HandlerExecutionChain
4. HandlerExecutionChain中包含Handler和Interceptor返回给DispatcherServlet
5. DispatcherServlet根据Handler类型调用HandlerAdapter
6. HandlerAdapter调用Handler进行执行。
7. Handler执行完成后返回ModelAndView
8. ModelAndView返回给DispatcherServlet
9. DispatcherServlet调用ViewResovler处理视图
10. DispatcherServlet响应结果给客户端
四十九、 请说一下Spring MVC中注解
@Controller 定义控制器
@RestController 等效 @Controller+@ResponseBody
@ControllerAdvice 基于Spring框架的异常通知实现。
@RequestMapping 定义映射路径
@PutMapping 接收put请求,整体更新
@PatchMapping 属于对Put的补充,局部更新
@DeleteMapping 接收delete请求
@PostMapping 接收post请求
@GetMapping 接收get请求
@ResponseBody 把返回值转换为json并设置到响应体中
@CrossOrigin 允许跨域,在响应头添加Access-Control-Allow-Origin属性
@ExceptionHandler 异常处理
@RequestParam 处理请求参数
@RequestHeader 接收请求头
@RequestBody 把请求体数据转换为对象
@SessionAttribute 获取Session作用域的值
@PathVariable 获取restful请求参数
五十、 请说一下拦截器(Interceptor)和过滤器(Filter)的区别
1. 来源不同
拦截器是SpringMVC中的技术,过滤器是Java EE中的技术
2. 生效位置不同
拦截器是进入DispatcherServlet后才能执行,过滤器是进入到Servlet容器后就可以触发。
3. 目标不同
拦截器拦截的目标是HandlerMethod,过滤器可以过滤所有的URL。
4. 运行机制不同
拦截器是在HandlerMethod之前前后和视图处理完成后执行,分为三部分。
过滤器只能在目标资源前后执行。
5. 接口中方法类型不同
拦截器中的方法都是default方法,可以重写也可以不重写。
过滤器中的方法都是abstract方法,如果当前类不是抽象类,必须重写。
6. 上下文不同
拦截器可以获取到Spring容器中内容,但是Filter因为是被Tomcat管理,所以无法获取Spring容器内容。
【Spring面试题】
五十一、 请介绍一下什么是IoC/DI?
下图是Spring官方文档对IoC的介绍
翻译过来:
本章节涵盖了Spring框架对IoC思想的实现。IoC也被称为DI。它是一个过程,一个对象的实例中的对象属性定义仅仅通过构造参数(提供了构造方法),工厂方法的参数(提供了工厂方法),或者设置属性。当创建一个bean时由容器提供对应的依赖。这个过程基本上是bean的反转(因此称为控制反转),通过使用类的直接构造或服务定位器模式等机制来控制其依赖项的实例化或位置。
下图是官方对DI的介绍:
依赖项注入(DI)是一个过程,对象仅通过构造函数参数、工厂方法的参数或在对象实例构造或从工厂方法返回后在对象实例上设置的属性来定义其依赖项(即与之一起工作的其他对象)。然后,容器在创建bean时注入这些依赖项。这个过程基本上是bean本身的逆过程(因此称为控制反转),通过使用类的直接构造或服务定位器模式来控制其依赖项的实例化或位置。
上面官方解释虽然翻译过来后有一些解释比较官方。但是不难看出在解释时官方一直在强调这样几件事:
1. IoC和DI是一件事。IoC又称DI,DI又称IoC。它是一个过程
2. bean的创建和管理都是由容器完成,这个过程就是对象控制权的反转,也就是IoC
3. 容器创建对象的过程难免需要给对象中属性进行赋值,尤其是属性是类类型,需要使用容器中另一个Bean,
就需要从容器中获取另一个Bean注入到当前Bean。这个过程就是DI这个过程也是IoC。
4. 给对象属性设置值有二种方式:构造方法、set方法。
对应的代码:
<bean id="teacher" class="com.bjsxt.pojo.Teacher"></bean>
<bean id="student" class="com.bjsxt.pojo.Student">
<property name="teacher" ref="teacher"></property>
</bean>
所使用的技术:
XML解析(注解)+反射。
存储的Bean都放入到一个Map对象中。
五十二、 Spring注入有几种方式
在Spring 5中注入提供了二种注入方式:
Constructor-based : 构造注入
Setter-based: 设值注入
无论是p命名空间还是自动注入其本质都是构造注入和设值注入。
注:
在Spring 4中认为工厂,也是注入的一种方式,但是在Spring 5中认为工厂是实例化Bean的方式,里面包含了静态工厂方法和实例工厂方法。
五十三、 Spring中标签的scope可取值
Spring中的scope控制的是Bean的有效范围。
一共有6个可取值:
里面的singleton和prototype在Spring最基本的环境中就可以使用
但是里面的request、session、application、websocket都只有在web环境才能使用。
五十四、 BeanFactory和ApplicationContext的区别
BeanFactory是Spring中的顶级接口,接口中定了Spring容器最基本功能。是Spring IoC的最核心接口。
最常用实现类是XmlBeanFactory
FileSystemResource fsr = new FileSystemResource("D:\\ideaws\\spring1\\src\\applicationContext.xml");
XmlBeanFactory xmlBeanFactory = new XmlBeanFactory(fsr);
Object teacher = xmlBeanFactory.getBean("teacher");
System.out.println(teacher);
但是从Spring 3.1 版本开始,使用
DefaultListableBeanFactory和XMLBeanDefinitionReader替代了。
DefaultListableBeanFactory defaultListableBeanFactory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(defaultListableBeanFactory);
xmlBeanDefinitionReader.loadBeanDefinitions(new FileSystemResource("D:\\ideaws\\spring1\\src\\applicationContext.xml"));
Object teacher = defaultListableBeanFactory.getBean("teacher");
无论是使用哪个写法,都可以发现BeanFactory是在真正getBean的时候才去实例化的。
而ApplicationContext是BeanFactory的子接口。所以要比BeanFactory的功能更加强大,除了BeanFactory的功能,还包含了:
1. AOP 功能
2. 国际化(MessageSource)
3. 访问资源,如URL和文件(ResourceLoader)
4. 消息发送机制(ApplicationEventPublisher)
5. Spring集成Web时的WebApplicationContext
在使用时ApplicationContext时多使用
ClassPathXmlApplicationContext
ClassPathXmlApplicationContext applicationContext =
new ClassPathXmlApplicationContext("applicationContext.xml");
Object teacher = applicationContext.getBean("teacher");
通过结果可以看出ApplicationContext都是在加载文件后立即创建Bean。
可以通过lazy-init属性进行控制,让bean懒加载
<bean id="teacher" class="com.bjsxt.pojo.Teacher" lazy-init="true"></bean>
五十五、 Spring如何解决循环依赖(循环注入)问题
循环注入即多个类相互依赖,产生了一个闭环。
但是对于官方也说明了,可能出现循环注入的情况。当两个类都是用构造注入时,没有等当前类实例化完成就需要注入另一个类,而另一个类没有实例化完整还需要注入当前类,所以这种情况是无法解决的。会出现BeanCurrentlyInCreationException异常。
如果两个类都使用设值注入且scope为singleton的就不会出现问题,可以正常执行。因为单例默认下有三级缓存(DefaultSingletonBeanRegistry),可以暂时缓存没有被实例化完成的Bean。
但是如果两个类的scope都是prototype依然报BeanCurrentlyInCreationException。
五十六、 请说一下Spring中自动装配
自动装配是指bean中属性可以由Spring容器自动帮助注入。不需要手动指定。
Spring可以通过标签的autowire控制的自动装配方式,也可以通过根节点的default-autowire控制自动注入。可取值分别是:
no:不自动注入。
default:默认值。根据default-autowire=”"配置进行自动注入。如果autowire和default-autowire都没有配置时等效于no。
byName:通过名称自动注入。要求必须存在自定
byType:通过类型进行注入。如果存在多个相同类型的Bean,会报NoUniqueBeanDefinitionException
constructor:通过构造方法进行注入。默认按照构造方法参数byName进行注入,如果没有找到与参数同名的bean,会按照byType进行注入。如果Spring容器中存在多个相同类型的bean时,也不会报错,但是不会注入。
五十七、 请介绍一下什么是AOP
面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。
AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,
是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,
从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
主要功能
日志记录,性能统计,安全控制,事务处理,异常处理等等。
主要意图
将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,
通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,
进而改变这些行为的时候不影响业务逻辑的代码。
在实际开发中AOP主要应用在Service层。
五十八、 Spring中AOP有几种实现方式,区别是什么?
Spring中AOP有两种实现方式:
Schema-based:基于模式的。 基于接口实现的。每个通知都需要实现特定的接口类型,才能确定通知的类型。由于类已经实现了接口,所以配置起来相对比较简单。尤其是不需要在配置中指定参数和返回值类型。
① BeforeAdvice ② AfterReturningAdvice ③ MethodInterceptor ④ ThrowAdvice
<bean id="myBefore" class="com.bjsxt.aop.MyBefore"></bean>
<bean id="myafter" class="com.bjsxt.aop.MyAfter"></bean>
<bean id="myarround" class="com.bjsxt.aop.MyArround"></bean>
<bean id="mythrow" class="com.bjsxt.aop.MyThrow"></bean>
<aop:config proxy-target-class="false">
<aop:pointcut id="mingcheng" expression="execution(* com.bjsxt.aop.Demo.*(..))"/>
<aop:advisor advice-ref="myarround" pointcut-ref="mingcheng"></aop:advisor>
<aop:advisor advice-ref="myBefore" pointcut-ref="mingcheng"></aop:advisor>
<aop:advisor advice-ref="myafter" pointcut-ref="mingcheng"></aop:advisor>
<aop:advisor advice-ref="mythrow" pointcut-ref="mingcheng" ></aop:advisor>
</aop:config>
- AspectJ方式。是基于配置实现的。通过不同的配置标签告诉Spring通知的类型。AspectJ方式对于通知类写起来比较简单。但是在配置文件中参数和返回值需要特殊进行配置。
aop:config
<aop:aspect ref=“myAdvice”>
<aop:pointcut id=“mypoint” expression=“execution(* com.bjsxt.aop.Demo.test(String,int)) and args(name,age)” />
<aop:before method=“mybefore” arg-names=“name,age” pointcut-ref=“mypoint”></aop:before>
<aop:after-returning method=“myafter2” arg-names=“name,age,suiyi” pointcut-ref=“mypoint” returning=“suiyi”></aop:after-returning>
<aop:after method=“myafter” arg-names=“name,age” pointcut-ref=“mypoint”></aop:after>
<aop:around method=“myarround” arg-names=“pro,name,age” pointcut-ref=“mypoint” ></aop:around>
<aop:after-throwing method=“mythrow” arg-names=“name,age,e” throwing=“e” pointcut-ref=“mypoint”></aop:after-throwing>
</aop:aspect>
</aop:config>
因为Schame-based是运行时增强,AspectJ是编译时增强。所以当切面比较少时,性能没有太多区别。但是当切面比较多时,最好选择AspectJ方式,因为AspectJ方式要快很多。
五十九、 Spring中通知有几种类型?
前置通知、后置通知、环绕通知、异常通知。
可以使用AOP中的Schema-based或AspectJ方式实现。
其中AspectJ中后置通知又分为:after和after-returning,after无论切点是否出现异常都执行的后置通知,after-returning只有在切点正常执行完成,才会触发的通知。
六十、 什么是Spring声明式事务?
声明式事务是Spring框架提供的一种事务管理方法,释放了程序员管理事务的代码,我们只需要去声明出哪些方法需要进行哪种事务管理即可。
因为不需要编写事务管理的代码,所以可以让程序员把所有的精力都放在业务实现上了,让开发效率更高。
在以前单独使用MyBatis框架时,需要sqlSession.commit();自己提交事务,**sqlSession.rollback()**自己回滚事务,这就是编程式事务。
在Spring框架中声明时事务,只需要通过注解和XML配置声明出哪些方法需要进行事务控制即可。
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="insert*" isolation="SERIALIZABLE"/>
<tx:method name="delete*" propagation="MANDATORY"/>
<tx:method name="update*" rollback-for="java.lang.Exception" no-rollback-for="java.lang.Exception" />
<tx:method name="select*" read-only="true"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="mypoint" expression="execution(* com.bjsxt.service.impl.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="mypoint"></aop:advisor>
</aop:config>
六十一、 Spring支持的事务传播行为
Spring实际应用场景主要使用在Web项目中,事务传播行为基本都是出现在一次请求调用了多个有声明式事务管理的方法。通过事务传播行为进行控制,这些方法中的事务最终是组成同一个事务,还是以多个事务提交给数据库。
可以通过tx:method或@Transactional中propagation进行配置。
可取值分别为:
REQUIRED:
默认值。如果当前有事务则加入到事务中。如果当前没有事务则新增事务。
NEVER:
必须在非事务状态下执行,如果当前没有事务,正常执行,如果当前有事务,报错.
NESTED:
必须在事务状态下执行.如果没有事务,新建事务,如果当前有事务,创建一个嵌套事务.
REQUIRES_NEW:
必须在事务中执行,如果当前没有事务,新建事务,如果当前有事务,把当前事务挂起. 在重新建个事务。
SUPPORTS:
如果当前有事务就在事务中执行,如果当前没有事务,就在非事务状态下执行.
NOT_SUPPORTED:
必须在非事务下执行,如果当前没有事务,正常执行,如果当前有事务,把当前事务挂起.
MANDATORY:
必须在事务内部执行,如果当前有事务,就在事务中执行,如果没有事务,报错.
六十二、 Spring支持的事务隔离级别
多个事务同时操作数据库时,允许多个事务操作的方式就是事务隔离级别。事务隔离级别主要是通过添加锁操作实现的。
事务隔离级别主要是解决高并发下脏读、幻读、不可重复读问题的。
脏读:
事务A没有提交事务,事务B读取到事务A未提交的数据,这个过程称为脏读。读取到的数据叫做脏数据。
不可重复读:
当事务A读取到表中一行数据时,同时另一个事务修改这行数据,事务A读取到的数据和表中真实数据不一致。
幻读:
事务A对表做查询全部操作,事务B向表中新增一条数据。事务A查询出来的数据和表中数据不一致,称为幻读。
在tx:method或@Transactional中isolation进行配置。
其中isolation可取值分别为DEFAULT表示用数据库的隔离级别,MySQL8默认的事务隔离级别REPEATABLE_READ(通过select @@transaction_isolation)、READ_UNCOMMITTED读未提交(脏读,幻读,不可重复读)、READ_COMMITTED读已提交(幻读,不可重复读)、REPEATABLE_READ可重复读(幻读)、SERIALIZABLE串行读来通过牺牲性能解决脏读、不可重复度、幻读问题。
六十三、 说一下@Autowired和@Resource的区别和相同点
@Autowired和@Resource都是实现注入的注解。
@Autowired是Spring提供的注解,默认按照byType进行注入。当Spring容器中存在多个相同类型时按照byName进行注入,name的值就是对象的名称。也可以结合@Qualifier设置注入时的名称
@Resource是Java原生的注解。默认按照byName进行注入,如果没有找到则按照byType进行注入。也可以通过设定name和type属性进行控制使用某个方式进行注入,如果name和type同时配置,取逻辑与关系。
六十四、 Spring中你知道的注解
使用**@Component及子@Configuration**、@Service、@Repository、@Controller、进行定义Bean。
Spring 5 之后提升注解扫描性能的注解@Indexed,@Indexed主要是通过生成META-INT/spring.components以后在转换为CandidateComponentsIndex,就可以不用扫描包,直接扫描CandidateComponentsIndex。
@ComponentScan 注解扫描。在使用Spring框架XML配置时,使用的不是特别多,在Spring Boot中经常被使用。
@Scope定义Bean的作用域。
@Bean在方法上进行定义Bean常用在配置类中。
除此还使用过AOP相关的注解@Pointcut定义切点、@Aspect定义通知类、@Before定义前置通知、@After定义后置通知@AfterReturning定义后置通知、@Around定义环绕通知、@AfterThrowing定义异常通知。
还有@Value读取属性文件的值、@Transactional定义事务。@Autowired自动注入、@Qualifier定义注入Bean的名称
六十五、 Spring中Bean是否线程安全的?
Spring中Bean是否是线程安全主要看scope取值。
当scope=singleton时,表示bean是单例的,所以bean的全局属性是线程非安全的。可尽量减少全局属性,但是很难完全避免。也可以结合ThreadLocal实现保证属性的线程安全。
当scope=prototype时,bean是线程安全的。但是性能相对singleton比较低。
六十六、 Spring中Bean的生命周期
Spring中Bean的生命周期就是指Bean从初始化到销毁的过程。
Bean最简单的实现就是直接使用标签定义这个Bean
在这种情况下只会调用类的构造方法进行实例化。
可以通过标签的init-method和destory-method自定义初始化和销毁方法。
除此以外还可以让类实现各种Aware接口,例如BeanNameAware、BeanFactoryAware、ApplicationContextAware等。
也可以通过InitializingBean,DisposableBean实例化Bean和销毁Bean。
也可以通过BeanPostProcessor进行增强。但是当前类不能实现这个接口,且不能与BeanFactoryPostProcessor同时存在。
生命周期流程图:
【Spring Cloud Netfix Hystrix:断路器】
六十七、 什么是灾难性的雪崩效应
在微服务架构的项目中,尤其是中大型项目,肯定会出现一个服务调用其他的服务,其他服务又调用别的服务,服务和服务之间形成了一种链式的调用关系。
当少量请求时,对于整个服务链条是没有过多的影响的。
虽然每个服务的请求都是少量的,但是最终都访问服务T。所以对于服务T来说请求量就是比较大的。所在的服务器CPU压力比较高。
当其中某一个服务突然遇到大量请求时。整个链条上所有服务负载骤增。
导致服务U和服务T的负载过高。运行性能下降。会导致其他调用服务U和服务T的链条出现问题。从而所有的项目可能都出现的问题。
这种情况就称为灾难性的雪崩效应。
造成灾难性雪崩效应的原因,可以简单归结为下述三种:
服务提供者(Application Service)不可用。如:硬件故障、程序BUG、缓存击穿、并发请求量过大等。
重试加大流量。如:用户重试、代码重试逻辑等。
服务调用者(Application Client)不可用。如:同步请求阻塞造成的资源耗尽等。
雪崩效应最终的结果就是:服务链条中的某一个服务不可用,导致一系列的服务不可用,最终造成服务逻辑崩溃。这种问题造成的后果,往往是无法预料的。
六十八、 如何防止灾难性雪崩效应
降级
超时降级、资源不足时(线程或信号量)降级,降级后可以配合降级接口返回托底数据。实现一个fallback方法, 当请求后端服务出现异常的时候, 可以使用fallback方法返回的值.
保证:服务出现问题整个项目还可以继续运行。
熔断
当失败率(如因网络故障/超时造成的失败率高)达到阀值自动触发降级,熔断器触发的快速失败会进行快速恢复。
通俗理解:熔断就是具有特定条件的降级,当出现熔断时在设定的时间内容就不在请求Application Service了。所以在代码上熔断和降级都是一个注解
保证:服务出现问题整个项目还可以继续运行。
请求缓存
提供了请求缓存。服务A调用服务B,如果在A中添加请求缓存,第一次请求后走缓存了,就不在访问服务B了,即使出现大量请求时,也不会对B产生高负载。
请求缓存可以使用Spring Cache实现。
保证:减少对Application Service的调用。
请求合并
提供请求合并。当服务A调用服务B时,设定在5毫秒内所有请求合并到一起,对于服务B的负载就会大大减少,解决了对于服务B负载激增的问题。
保证:减少对Application Service的调用。
隔离
隔离分为线程池隔离和信号量隔离。通过判断线程池或信号量是否已满,超出容量的请求直接降级,从而达到限流的作用。
六十九、 Hystrix简介
在Spring Cloud中解决灾难性雪崩效应就是通过Spring Cloud Netflix Hystrix实现的。
Hystrix [hɪst’rɪks],中文含义是豪猪,因其背上长满棘刺,从而拥有了自我保护的能力。本文所说的Hystrix(中文:断路器)是Netflix开源的一款容错框架,同样具有自我保护能力。
通俗解释:Hystrix就是保证在高并发下即使出现问题也可以保证程序继续运行的一系列方案。作用包含两点:容错和限流。
在Spring cloud中处理服务雪崩效应,都需要依赖hystrix组件。在Application Client应用的pom文件中都需要引入下述依赖:
```java
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
降级
降级是指,当请求超时、资源不足等情况发生时进行服务降级处理,不调用真实服务逻辑,而是使用快速失败(fallback)方式直接返回一个托底数据,保证服务链条的完整,避免服务雪崩。
解决服务雪崩效应,都是避免application client请求application service时,出现服务调用错误或网络问题。处理手法都是在application client中实现。我们需要在application client相关工程中导入hystrix依赖信息。并在对应的启动类上增加新的注解@EnableCircuitBreaker,这个注解是用于开启hystrix熔断器的,简言之,就是让代码中的hystrix相关注解生效。
```java
17 新建ApplicationServiceDemo
18 配置pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.5.RELEASE</version>
</parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
19 新建配置文件
新建application.yml
spring:
application:
name: application-service-demo
eureka:
client:
service-url:
defaultZone: http://eurekaserver1:8761/eureka/
20 新建控制器
新建com.bjsxt.controller.DemoController
@Controller
public class DemoController {
@RequestMapping("/demo")
@ResponseBody
public String demo(){
return "demo-service";
}
}
21 新建启动类
新建com.bjsxt.DemoApplication
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class,args);
}
}
22 新建DemoFallback
新建项目DemoFallback作为Application Client
23 编写pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.5.RELEASE</version>
</parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
</dependencies>
24 新建配置文件
新建application.yml
注意不要端口号冲突
spring:
application:
name: fallback-demo
eureka:
client:
service-url:
defaultZone: http://eurekaserver1:8761/eureka/
server:
port: 8081
25 新建配置类
新建com.bjsxt.config.RibbonConfig
此处使用@LoadBalancer方式快捷配置负载均衡。
@Configuration
public class RibbonConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
26 新建service及实现类
新建com.bjsxt.service.DemoService及实现类。
实现类中@HystrixCommand的fallbackMethod配置的就是降级方法。
myFallback方法:
参数:和test的参数是相同的。
返回值:和test中调用的application-service-demo/demo控制器方法返回值相同
public interface DemoService {
String test();
}
@Service
public class DemoServiceImpl implements DemoService {
@Autowired
private RestTemplate restTemplate;
@HystrixCommand(fallbackMethod = "myFallback")
@Override
public String test() {
String result = restTemplate.postForObject("http://application-service-demo/demo", null, String.class);
System.out.println(result);
return result;
}
public String myFallback(){
return "托底数据";
}
}
27 新建控制器
新建com.bjsxt.controller.FallbackController
@Controller
public class FallbackController {
@Autowired
private DemoService demoService;
@RequestMapping("/demo")
@ResponseBody
public String demo(){
return demoService.test();
}
}
28 新建启动类
@SpringBootApplication
@EnableCircuitBreaker
public class ApplicationClientApplication {
public static void main(String[] args) {
SpringApplication.run(ApplicationclientdemoApplication.class, args);
}
}
29 访问
在浏览器输入http://localhost:8081/demo
30 测试降级
停止ApplicationServiceDemo项目。再次访问
七十一、 熔断
当一定时间内,异常请求比例(请求超时、网络故障、服务异常等)达到阀值时,启动熔断器,熔断器一旦启动,则会停止调用具体服务逻辑,通过fallback快速返回托底数据,保证服务链的完整。
熔断有自动恢复机制,如:当熔断器启动后,每隔5秒,尝试将新的请求发送给Application Service,如果服务可正常执行并返回结果,则关闭熔断器,服务恢复。如果仍旧调用失败,则继续返回托底数据,熔断器持续开启状态。
降级是出错了返回托底数据,而熔断是出错后如果开启了熔断将会一定时间不在访问application service
31 属性
熔断的实现是在调用远程服务的方法上增加@HystrixCommand注解。当注解配置满足则开启或关闭熔断器。
@HystrixProperty的name属性取值可以使用HystrixPropertiesManager常量,也可以直接使用字符串进行操作。
注解属性描述:
CIRCUIT_BREAKER_ENABLED
“circuitBreaker.enabled”;
是否开启熔断策略。默认值为true。
CIRCUIT_BREAKER_REQUEST_VOLUME_THRESHOLD
“circuitBreaker.requestVolumeThreshold”;
单位时间内(默认10s内),请求超时数超出则触发熔断策略。默认值为20次请求数。通俗说明:单位时间内容要判断多少次请求。
EXECUTION_ISOLATION_THREAD_TIMEOUT_IN_MILLISECONDS
execution.isolation.thread.timeoutInMilliseconds
设置单位时间,判断circuitBreaker.requestVolumeThreshold的时间单位,默认10秒。单位毫秒。
CIRCUIT_BREAKER_SLEEP_WINDOW_IN_MILLISECONDS
“circuitBreaker.sleepWindowInMilliseconds”;
当熔断策略开启后,延迟多久尝试再次请求远程服务。默认为5秒。单位毫秒。这5秒直接执行fallback方法,不在请求远程application service。
CIRCUIT_BREAKER_ERROR_THRESHOLD_PERCENTAGE
“circuitBreaker.errorThresholdPercentage”;
单位时间内,出现错误的请求百分比达到限制,则触发熔断策略。默认为50%。
CIRCUIT_BREAKER_FORCE_OPEN
“circuitBreaker.forceOpen”;
是否强制开启熔断策略。即所有请求都返回fallback托底数据。默认为false。
CIRCUIT_BREAKER_FORCE_CLOSED
“circuitBreaker.forceClosed”;
是否强制关闭熔断策略。即所有请求一定调用远程服务。默认为false。
32 代码示例
在原有降级代码上修改@HystrixCommand如下。
关闭ApplicationSeviceDemo项目,访问DemoFallback控制器,刷新5次后会发现页面加载快了,这时就开启熔断了。此时打开ApplicationServiceDemo,发现依然返回托底数据。到达30秒后再次访问才能正常访问ApplicationServiceDemo中内容。
注意:单位时间时间内容请求数必须达到5个(无论成功还是失败)才能满足条件。
@HystrixCommand(fallbackMethod = “myFallback”,commandProperties = {
// 条件一: 远程服务请求错误数量到达3个
@HystrixProperty(name= “circuitBreaker.requestVolumeThreshold”,value=“3”),
// 熔断周期时间,每10秒作为一个熔断时间单位
@HystrixProperty(name=“execution.isolation.thread.timeoutInMilliseconds”,value=“10000”),
// 条件二: 远程服务请求错误占比到达50%
@HystrixProperty(name=“circuitBreaker.errorThresholdPercentage”,value=“50”),
// 结果: 开启熔断后,30秒不在请求远程服务
@HystrixProperty(name=“circuitBreaker.sleepWindowInMilliseconds”,value = “30000”)
})
七十二、 请求缓存
Hystrix为了降低访问服务的频率,支持将一个请求与返回结果做缓存处理。如果再次请求的URL没有变化,那么Hystrix不会请求服务,而是直接从缓存中将结果返回。这样可以大大降低访问服务的压力。
Hystrix自带缓存。有两个缺点:
- 是一个本地缓存。在集群情况下缓存是不能同步的。
- 不支持第三方缓存容器。Redis,memcached不支持的。
所以可以利用spring cache。实现请求缓存。
在降级处理的代码基础上完成下面变化。
33 添加依赖
org.springframework.boot
spring-boot-starter-data-redis
34 修改配置文件
添加redis的配置,此处使用的是redis单机版。如果是redis集群使用spring.redis.cluster.nodes进行配置。
spring:
redis:
host: 192.168.192.128
35 修改启动类
在启动类上添加@EnableCaching注解
36 在HystrixService和实现类上添加
在service实现类方法上面额外在添加一个注解。
@Cacheable(key = “‘client’”,cacheNames = “com.bjsxt”)
37 编写控制器,进行测试
控制器代码没有变化,直接调用service即可。
38 检查结果
通过在redis中keys * 查看是否添加/删除key成功。
七十三、 请求合并
没有请求合并
Application Service 负载是Application Client发送请求的总数量
请求合并
把一段时间范围内的所有请求合并为一个请求。大大的降低了Application Service 负载。
什么情况下使用请求合并
在微服务架构中,我们将一个项目拆分成很多个独立的项目,这些独立的项目通过远程调用来互相配合工作,但是,在高并发情况下,通信次数的增加会导致总的通信时间增加,同时,线程池的资源也是有限的,高并发环境会导致有大量的线程处于等待状态,进而导致响应延迟,为了解决这些问题,我们需要来了解Hystrix的请求合并。
请求合并的缺点
设置请求合并之后,本来一个请求可能5ms就搞定了,但是现在必须再等10ms看看还有没有其他的请求一起的,这样一个请求的耗时就从5ms增加到15ms了,不过,如果我们要发起的命令本身就是一个高延迟的命令,那么这个时候就可以使用请求合并了,因为这个时候时间窗的时间消耗就显得微不足道了,另外高并发也是请求合并的一个非常重要的场景。
39 请求合并参数介绍
请求合并理论学习明白后,代码实现起来还是很容易的。关键就是一个@HystrixCollapser注解
40 代码实现
实现请求合并时,不仅仅需要修改Application Client代码,还需要修改Application Service的代码,因为Application Service必须支持把所有参数捆绑到一起的方式,同时还支持把多个值一起返回。
40.1 修改Application Service控制器
没有请求合并时的控制器
@RequestMapping("/service")
public String demo(String name){
return "姓名:"+name;
}
有请求合并时必须要支持一次传递过来多个参数和一次返回多个值。在额外添加一个控制器方法。
参数添加@RequestBody是因为Application Client使用请求体传递数据。
返回值也是List,List里面数据是有顺序的。
@RequestMapping("/service2")
public List<String> demo2(@RequestBody List<String> names){
System.out.println("接收到的内容:"+names);
List<String> list = new ArrayList<>();
for (String name : names){
list.add("姓名:"+name);
}
return list;
}
40.2 修改Application Client的Service
在请求缓存的代码基础上,在service及实现类中添加新方法。
@HystrixCollapser 进行请求合并
batchMethod:处理请求合并的方法
scope - 合并请求的请求作用域。可选值有global和request。
global代表所有的请求线程都可以等待可合并。 常用
request代表一个请求线程中的多次远程服务调用可合
timerDelayInMilliseconds:等待时长,默认10毫秒。
maxRequestInBatch:最大请求合并数量。
@HystrixCommand 处理请求合并的方法必须有此注解。
实现类中client(String)方法一旦被@HystrixCollapser标记,方法就不会被执行,方法体中为空即可。直接执行batchMethod对应的方法。
batchMethod方法返回值顺序和传递进来的参数顺序有关系的。
注意:
在实际测试中scope使用默认值REQUEST会出现空指针异常,请换成GLOBAL
Future<String> client(String name);
@Override
@HystrixCollapser(batchMethod = "myBatchMethod",scope = com.netflix.hystrix.HystrixCollapser.Scope.GLOBAL
,collapserProperties = {@HystrixProperty(name="timerDelayInMilliseconds",value="10"),@HystrixProperty(name="maxRequestsInBatch",value = "200")})
public Future<String> client(String name) {
System.out.println("client方法,有请求合并时将不支持这个方法");
String result = restTemplate.getForObject("http://APPLICATION-SERVICE/service?name={1}", String.class, name);
return null;
}
@HystrixCommand
public List<String> myBatchMethod(List<String> name){
System.out.println("传递过去的参数:"+name);
List<String> list = restTemplate.postForObject("http://APPLICATION-SERVICE/service2", name, List.class);
return list;
}
40.3 修改consumer中控制器方法
在一个控制器中多次调用service的方法进行模拟并发操作。
控制通过休眠模拟两次并发请求。
f1和f2的输出语句必须放到请求后面,否则无法合并。
@RequestMapping("/client")
public String client(){
try {
Future<String> f1 = clientService.client("张三");
Future<String> f2 = clientService.client("李四");
System.out.println("f1:"+f1.get());
System.out.println("f2:"+f2.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
return "ok";
}
@RequestMapping("/client")
public String client(){
try {
Future<String> f1 = clientService.client("张三");
Future<String> f2 = clientService.client("李四");
System.out.println("f1:"+f1.get());
System.out.println("f2:"+f2.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
return "ok";
}
七十四、 隔离
41 线程池隔离
42 为什么使用线程池隔离
没有线程池隔离的时候可能因为某个接口的高并发导致其他接口也可用。
当使用线程池隔离。不同接口有着自己独立的线程池
即使某个线程池都被占用,也不影响其他线程。
43 Hystrix的线程池隔离
Hystrix采用Bulkhead Partition舱壁隔离技术。
舱壁隔离指的的是讲船体内部分为多个隔舱,一旦其中某几个隔舱发生破损进水,水流不会在其他舱壁中流动,从而保证船舱依然具有足够的浮力和稳定性,降低沉船危险。
44 线程池隔离的优缺点
优点:
- 任何一个服务都会被隔离在自己的线程池内,即使自己的线程池资源填满也不会影响其他服务。
- 当依赖的服务重新恢复时,可通过清理线程池,瞬间恢复服务的调用。但是如果是tomcat线程池被填满,再恢复就会很麻烦。
- 每个都是独立线程池。一定程度上解决了高并发问题。
- 由于线程池中线程个数是有限制,所以也解决了限流问题。
缺点: - 增加了CPU开销。因为不仅仅有Tomcat的线程池,还需要有Hystrix线程池。
- 每个操作都是独立的线程,就有排队、调度和上下文切换等问题。
45 代码演示
访问thread方法和访问thread2方法时发现已经不再使用同一个线程池了。
@HystrixCommand(groupKey = "jqk",commandKey = "abc",threadPoolKey = "jqk",threadPoolProperties = {
@HystrixProperty(name="coreSize",value="8"),
@HystrixProperty(name="maxQueueSize",value="5"),
@HystrixProperty(name="keepAliveTimeMinutes",value="2"),
@HystrixProperty(name="queueSizeRejectionThreshold",value="5")
})
@Override
public String thread() {
System.out.println(Thread.currentThread().getName());
return "thread1";
}
@Override
public String thread2() {
System.out.println(Thread.currentThread().getName());
return "thread2";
}
46 参数说明
47 信号量隔离
48 信号量是什么
java.util.concurrent.Semaphore用来控制可同时并发的线程数。通过构造方法指定内部虚拟许可的数量。每次线程执行操作时先通过acquire方法获得许可,执行完毕再通过release方法释放许可。如果无可用许可,那么acquire方法将一直阻塞,直到其它线程释放许可。
如果采用信号量隔离技术,每接收一个请求,都是服务自身线程去直接调用依赖服务,信号量就相当于一道关卡,每个线程通过关卡后,信号量数量减1,当为0时不再允许线程通过,而是直接执行fallback逻辑并返回,说白了仅仅做了一个限流。
49 代码实现
@HystrixCommand(commandProperties = {
@HystrixProperty(name=HystrixPropertiesManager.EXECUTION_ISOLATION_STRATEGY,value="SEMAPHORE"),
@HystrixProperty(name=HystrixPropertiesManager.EXECUTION_ISOLATION_SEMAPHORE_MAX_CONCURRENT_REQUESTS,value="10")
},fallbackMethod = "jqk")
@Override
public String semaphore() {
System.out.println("执行了,访问service");
return "结果";
}
50 参数说明
51 信号量隔离优缺点
优点
没有线程切换,性能好。
缺点
所有请求处理共用一个线程池。所有接口相互影响更大。容易因为一个接口问题,导致其他接口同时出错。
54 线程池隔离和信号量隔离
55 限流说明
在高并发的系统中,往往需要在系统中做限流,一方面是为了防止大量的请求使服务器过载,导致服务不可用,另一方面是为了防止网络攻击。
通过Hystrix的线程池隔离和信号量隔离控制了线程数量也就实现了限流效果。
七十五、 OpenFeign的降级处理
当使用OpenFeign调用远程服务超时会出现500错误。如果不希望出现500可以使用OpenFeign自带的Hystrix进行降级处理。
56 编写配置文件
默认情况下Feign的hystrix是不开启的,需要手动开启。其他方式和之前配置是相同的。
spring:
application:
name: hystrix-provider
server:
port: 8082
eureka:
client:
service-url:
defaultZone: http://eurekaserver1:8761/eureka/,http://eurekaserver2:8761/eureka/
feign:
hystrix:
enabled: true
57 添加依赖
Feign中包含Hystrix中部分功能。所以不需要单独导入Hystrix的依赖。
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.5.RELEASE</version>
</parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
58 编写service
编写HystrixService内容。依然使用Feign进行声明式调用。
在案例中是直接使用内部类进行实现的,也可以新建一个类进行完成。
fallback属性表示降级后处理类。
@FeignClient(name = "eureka-application-provider", fallback = HystricService.HystrixServiceFallback.class)
public interface HystricService {
@PostMapping("/show1")
Map<String, Object> show(@RequestParam String content);
@Component
class HystrixServiceFallback implements HystricService {
@Override
public Map<String, Object> show(String content) {
System.out.println("执行方法,出现服务降级,返回托底数据");
Map<String, Object> map = new HashMap<>();
map.put("a", "因为Provider连接不上了,返回托底数据");
return map;
}
}
}
59 编写启动器
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class HystrixApplication {
public static void main(String[] args) {
SpringApplication.run(HystrixApplication.class,args);
}
}
七十六、 OpenFeign的熔断处理
60 配置文件
在配置文件application.yml中增加下述配置,其他代码逻辑和配置内容与降级处理完全相同。
hystrix: # hystrix 容灾配置
command: # hystrix 命令配置,就是具体的容灾方案
default: # 默认环境,相当于全局范围,后续的配置,参考HystrixPropertiesManager中的常量配置
circuitBreaker: # 熔断配置, 常用。 其他配置不常用。
enabled: true
requestVolumeThreshold: 2
sleepWindowInMilliseconds: 2000
errorThresholdPercentage: 50
【MyBatis全套面试题】
七十七、 请介绍下MyBatis框架及优缺点
官方解释:
MyBatis 是支持普通 SQL 查询,存储过程和高级映射的优秀持久层框架。MyBatis 消除了几乎所有的 JDBC 代码和参数的手工设置以及结果集的检索。MyBatis 使用简单的 XML 或注解用于配置和原始映射,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java 对象)映射成数据库中的记录。
口语化解释:
MyBatis前身是Apache的iBatis,2010年由iBatis更名为MyBatis,目前源码托管在Github
上。通过程序员自己编写SQL后交给MyBatis进行结果映射,极大的简化了JDBC冗余代码。除此还内置支持缓存、延迟加载、日志、动态SQL等功能。是目前Java项目首选的数据访问层框架。
优点:
- 灵活度高。程序员自己编写SQL,可以实现自己想要的结果。
- 内置缓存机制。通过缓存机制,大大减少了与数据库交互次数,提升程序运行性能。
- 简化代码。与传统JDBC代码相比,去掉了大量的JDBC模板代码,只需要接口的方法声明和mapper.xml配置。
- 良好的数据库兼容性。MyBatis几乎支持市场上所有主流的数据库产品。
- 支持与Spring整合。在目前Java开发中,如果一个框架不能与Spring进行良好的继承,那么这个框架很难被普及。
缺点: - 需要程序员具备良好的SQL功底。因为MyBatis中所有的SQL都是由程序员手动编写,所有如何写出一个良好的SQL语句称为了使用MyBatis框架的最大考验。
- 移植性差。SQL是依赖数据库的,不同数据库有不同的SQL语句,所以在开发时需要选定好所使用的数据库,尽量不要更换数据库。
七十八、 请说一下你理解的ORM
O/R M 全称Object/Relational Mapping,即对象/关系映射。是一种程序设计技术。主要思想是通过指定对象和关系型数据库之间的映射关系,可以使程序开发时可以使用面向对象思想操作关系型数据库。
O/R M是数据库发展速度不及编程语言的产物。
在使用ORM时只需要把对象传递给ORM框架,ORM框架会按照设定的映射关系把对象中属性的值映射到数据库的字段。自动实现数据库中数据的新增及结果的填充以及SQL语句的生成。大大的提高了程序开发效率。
七十九、 为什么说MyBatis是半自动ORM框架
标准的ORM框架是只需要把对象传递给ORM框架后,由ORM框架生成SQL语句,填充结果后返回。
但是MyBatis框架需要由程序员手动编写SQL,由MyBatis进行结果填充,所以说MyBatis是半自动化ORM框架。其中的手动编写SQL是不自动的一半,结果填充是自动的一半。
八十、 MyBatis和Hibernate的区别
MyBatis和Hibernate都是目前企业中在使用的数据访问层框架,都是对JDBC的封装,只是MyBatis市场占比要比Hibernate多很多。
- Hibernate是标准的ORM框架,在配置了映射关系后,会由Hibernate自动生成SQL并填充结果。而MyBatis是半自动化ORM框架,需要由程序员手动编写SQL。
- Hibernate移植性更强。Hibernate只需要配置方言(dialect)就可以切换为所支持的框架。而MyBatis需要修改SQL。
- Hibernate的缓存机制更强。MyBatis的缓存是基于标签的,而Hibernate的缓存是基于缓存中对象的通过OID判断是否缓存数据。并且Hibernate还会自动帮助检查脏数据。
- Hibernate具有更强的日志系统。Hibernate的日志可以记录执行SQL、SQL排版优化、缓存提示、脏数据异常等信息。而MyBatis的日志主要记录执行SQL,参数设置,执行结果缓存操作等。
- MyBatis灵活度更高。虽然Hibernate也带有SQL方式查询,但是主要使用的还是零SQL方式。MyBatis虽然需要自己写SQL,但这也是MyBatis非常重要的优点,只要具有SQL编写和优化能力,可以写出来更优秀的SQL。
- MyBatis更加简单。MyBatis的学习成本更低,框架更加方便简单。Hibernate功能强大,所以学习成本更大。
从对比上看,视乎Hibernate要比MyBatis更好,那为什么MyBatis使用的比Hibernate的更多呢?
原因很简单:mybatis非常简单方便,很容易上手。SQL现在可以说是程序员必备技能,所以Hibernate的零SQL的优势就不是很明显了。另外Hibernate太笨重,生成的SQL每次投影查询时都是全字段,而MyBatis可以根据自己的要求进行设置。
八十一、 MyBatis中是否必须有接口
MyBatis框架并不要求必须有接口。MyBatis提供了三种引入的方式:
- 只编写mapper.xml
- 编写接口和mapper.xml
- 编写接口并在接口方法上使用注解提供SQL
这三种方式对应着MyBatis全局配置文件的三种配置方式(三选一)
在平时项目中为什么非要提供一个接口?
最主要的原因就是在其他层可以通过接口快速注入接口对象,方便调用方法。
八十二、 MyBatis是如何实现接口和mapper.xml绑定的
在MyBatis中全局配置文件中指定要扫描的包
在包中提供提供同名的接口文件和mapper.xml文件
在mapper.xml中namespace指定接口的全限定路径
在标签的id属性值配置为接口中方法名称
在代码中通过SqlSession的getMapper方法获取接口的代理对象
MyBatis中通过动态代理获取到接口的代理对象
八十三、 MyBatis接口中是否支持方法重载
在Java的类中是支持方法重载的,但是在MyBatis中是不允许方法重载的。
因为在mapper.xml中每个、、、标签的最终存储时都是namespace+id作为key进行存储。如果接口中存在两个同名方法(方法重载),在mapper.xml中也会提供两个同名id。
例如:接口中存在方法重载
在mapper.xml中配置时就会出现两个同名id
当程序运行后会出现异常,异常信息如下
Caused by: java.lang.IllegalArgumentException: Mapped Statements collection already contains value for com.bjsxt.mapper.StudentMapper.selectAll. please check com/bjsxt/mapper/StudentMapper.xml and com/bjsxt/mapper/StudentMapper.xml
at org.apache.ibatis.session.Configuration$StrictMap.put(Configuration.java:1014)
at org.apache.ibatis.session.Configuration$StrictMap.put(Configuration.java:970)
at org.apache.ibatis.session.Configuration.addMappedStatement(Configuration.java:768)
at org.apache.ibatis.builder.MapperBuilderAssistant.addMappedStatement(MapperBuilderAssistant.java:297)
at org.apache.ibatis.builder.xml.XMLStatementBuilder.parseStatementNode(XMLStatementBuilder.java:113)
at org.apache.ibatis.builder.xml.XMLMapperBuilder.buildStatementFromContext(XMLMapperBuilder.java:138)
at org.apache.ibatis.builder.xml.XMLMapperBuilder.buildStatementFromContext(XMLMapperBuilder.java:131)
at org.apache.ibatis.builder.xml.XMLMapperBuilder.configurationElement(XMLMapperBuilder.java:121)
... 13 more
八十四、 MyBatis中mapper.xml除了、、、标签以外还支持哪些标签
在MyBatis的mapper.xml中除了最基本的增加、删除、修改、查看标签以外还支持结果集映射、二级缓存配置、二级缓存引用、参数映射、SQL片段、新增时回填主键、引用SQL和动态SQL的7个标签、、、、、、,还有的常用四个子标签、、、、、
八十五、 MyBatis中namespace的作用,有哪些要求
namespace是mapper.xml中标签的属性,定义出当前文件所有的SQL的命名空间,所谓的命名空间就是一个字符串类型的分组名称。
namespace属性必须存在,也不能为空。如果没有配置namespace或者取值为空时会出现BuilderException。
多个mapper.xml中允许出现同名的namespace。但是在实际项目中namespace的值都是绑定接口的全限定路径,且一个接口只会对应一个mapper.xml文件,所以在项目中是不会出现同名namespace的情况的。
八十六、 MyBatis中不同的mapper.xml中id是否允许重复
id属性存在的意义是与namespace结合作为SQL的key,每个SQL的key是不允许重复的。所以id是否允许重复和namespace有关系的。
namespace是必须配置的属性,且不同的mapper.xml的namespace是可以重复的。
- 如果namespace不同,里面标签的id属性值是可以重复的。
- 如果namespace相同,里面标签的id属性值是不允许重复的。
例如:BjsxtMapper.xml中namespace是com.bjsxt,SxtMapper.xml的namespace也是com.bjsxt,此时两个文件中都存在insert的id值两个key就都是
com.bjsxt.insert
此时就重复了。
八十七、 MyBatis的mapper.xml中如何获取方法参数
获取参数的值可以在SQL语句中使用#{}进行获取。
在MyBatis 3.5中支持三种方式在mapper.xml中获取方法的参数。且这三种只能同时使用里面的两种。
如果只有一个参数,且参数的类型是简单数据类型,可以使用#{任意内容}只要写内容就行,不可以不写。
例如:在接口中存在两个参数的方法。(没有注解)
可以在mapper.xml中通过paramN或argN-1的方式获取参数的值
paramN的示例:
argN-1的示例
也可以混合使用
如果在接口方法中配置了注解,只能使用注解配置的key和paramN的方式,无法使用argN-1的方式了。
在mapper.xml中可以通过key或paramN方式获取参数的值
如果参数是对象类型,则使用对象属性名后获取参数
八十八、 MyBatis的mapper.xml中${}和#{}的区别
#{} 解析成占位符?,防止SQL注入问题。
select id,name from people where id=#{param1}
select id,name from people where id=?
相当于字符串拼接。所以在给列设置值时不要使用 {} 相当于字符串拼接。所以在给列设置值时不要使用 相当于字符串拼接。所以在给列设置值时不要使用{}。如果希望动态设置表名或列名等时可以使用 s e l e c t i d , n a m e f r o m p e o p l e w h e r e i d = {} select id,name from people where id= selectid,namefrompeoplewhereid={param1}
select id,name from people where id=123
select id,name from ${param1} where id=123
八十九、 MyBatis中结果填充(映射)的几种方式
MyBatis中结果映射分为三种方式:
- Auto Mapping。当查询表的列名和实体类的属性名相同时,MyBatis直接按照列名填充到属性名中。
- 别名方式。如果数据库中表的列名和属性名不相同,可以在投影中通过别名的方式让别名对应上属性名。
- resultMap方式。也可以通过resultMap的方式明确配置列和属性的映射关系。
九十、 MyBatis中实体类的属性名和列名是否必须相同
在数据库的命名规范中都是多个单词使用下划线分隔。例如:stu_id。
在Java中属性名的命名规范是小驼峰。例如:stuId
命名规范的不一致就导致经常出现了属性名和字段名不一致的问题。
当名称不一致可以通过别名或进行映射。
所以在MyBatis中实体类的属性名和列名可以不相同。
九十一、 MyBatis中实体类是否必须有getter和setter方法
不是的。
MyBatis在进行结果填充时和参数获取时优先使用反射获取getter和setter。如果实体类不存在getter和setter方法,会使用反射直接访问类中属性,即使这个属性是private,也可以访问。
参数获取时调用getter方法,结果集填充时调用setter方法。
可以通过跟踪源码Configuration中
protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
继续跟踪发现在SetFieldInvoker中存在
九十二、 MyBatis如何实现关联对象查询
在MyBatis中支持两种方式实现关联对象查询。
- N +1 查询方式。
N+1查询方式,在不考虑缓存的情况下,以查询外键表信息举例,假设外键表有N条数据,则需要1条查询外键表的SQL,和N条根据主键查询主键表的SQL - 多表联合查询方式。
多表联合查询方式只需要一条SQL语句。
但是N+1查询方式支持延迟加载,而多表联合查询方式不支持。
如果关联的是一个对象,使用的进行查询。
如果关联的是一个集合,使用的进行查询。
九十三、 MyBatis中如何实现延迟加载
MyBatis中的延迟加载只能出现在N+1查询方式。默认是没有开启延迟加载的。
有两种配置办法:
- 设置全局开关。在MyBatis全局配置文件中配置两个属性,开启延迟加载
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
- 在或中fetchType控制,lazy表示延迟加载,eager表示立即加载。
当fetchType和全局开关同时配置是fetchType生效。
九十四、 MyBatis的缓存机制
MyBatis中提供了两种缓存,一级缓存和二级缓存。通过缓存可以减少对数据库的访问,提高程序执行性能。
其中一级缓存又称SqlSession缓存,默认开启的,有效范围必须是同一个SqlSession对象,且每次缓存同一个方法中相同的SQL语句。在缓存时把SQL当做Key,查询的结果当做Value进行缓存。当关闭后释放缓存中内容。
而二级缓存又称SqlSessionFactory缓存,有效范围同一个SqlSessionFactory对象。默认是禁用状态,需要在映射文件中添加标签启用二级缓存。当SqlSession提交或关闭时把一级缓存的内容放入到二级缓存中。由于在项目SqlSessionFacotry是不关闭的,所以二级缓存内容默认是一直存在,二级缓存中放经常被查询,很少被修改的内容。
每次查询时先判断二级缓存,如果没有,再判断一级缓存,如果也没有,访问数据库,访问后把结果放入一级缓存中。
九十五、 MyBatis中执行器有几种
MyBatis的执行器都实现了Executor接口。作用是SQL执行的流程。
在MyBatis中执行器共分为三个类型:SimpleExecutor、ReuseExecutor、BatchExecutor。
其中SimpleExecutor是默认的执行器类型,每次执行SQL都需要预编译、设置参数、执行。
ReuseExecutor只预编译一次,复用Statement对象,然后设置参数,并执行。
BatchExecutor先预编译,批量执行时设置参数,最后统一提交给数据库执行。
在项目可以通过factory.openSession()方法参数设置执行器类型。通过枚举类型ExecutorType进行设置。
也可以在全局配置文件中通过中defaultExecutorType 进行全局设置。
九十六、 说一下MyBatis四大核心接口及执行流程
MyBatis四大核心接口:
Executor: 执行器。负责SQL执行过程的总体控制。
ParameterHandler: 参数处理器。负责SQL语句的参数设置
StatementHandler:负责与JDBC代码的交互,包含prepare预处理、query查询、update增删改操作。
ResultSetHandler:结果集处理器。负责把查询结果映射为Java对象。
执行流程:
九十七、 MyBatis中如何定义一个插件
- 创建一个类实现Interceptor接口,重写接口方法。
intercept()方法表示拦截方法,可以获取到目标对象。能实现获取SQL等。
plugin()插件方法,产生代理对象。
setProperties() 设置属性的方法。可以获取到mybatis配置文件中配置的属性 - 在类上添加注解
@Intercepts(value=@Signature(type = StatementHandler.class,method = “prepare”,args = {Connection.class, Integer.class}))
type:监听的目标类或接口。可选值为四大核心接口或实现类
method:监听的目标中方法名称。
args: 方的参数,此属性主要是为了防止方法重载出现方法不唯一的情况。 - 配置插件
<plugins>
<plugin interceptor="com.bjsxt.plugin.PagePlugin"></plugin>
</plugins>
九十八、 MyBatis的动态SQL是什么,支持哪些动态SQL标签
动态SQL本质就是SQL语句的拼接。根据不同条件产生不同的SQL语句。
在MyBatis中动态SQL标签包含
<if>、<choose>..<when>..<otherwise>、<trim>、<where>、<set>、<foreach>、<bind>七组标签。
九十九、 MyBatis支持的日志类型
MyBatis的日志默认是没有开启的。
可以在MyBatis的全局配置文件中的logImpl进行配置。
按照官方文档说明,共支持6种日志。
一百、 MyBatis使用了哪些设计模式
代理模式(Mapper)
适配器模式(Log)
工厂模式(SqlSession)
装饰器模式(CachingExecutor)
建造者模式(SqlSessionFactoryBuilder)
策略模式(openSession可以控制ExecutorType)
模板模式(BaseExecutor)
责任链模式(Interceptor)
一百〇一、 说一下MyBatis的运行原理
MyBatis 运行过程完全可以通过断点调试的过程进行分析。
MyBatis是基于JDBC实现,提供了使用映射文件代替接口实现类的实现方案,并在里面提供了缓存的实现。
MyBatis在执行时首先需要加载MyBatis全局配置文件,可以通过Resouces类把配置文件转换为InputStream输入流对象。
然后通过MyBatis通过XMLConfigBuilder把InputStream转换为Configuration对象,然后创建SqlSessionFactory的实现类DefaultSqlSessionFactory。
然后通过工厂对象实例化SqlSession,在实例化时,MyBatis底层默认使用SimpleExecutor执行器和并创建Transaction事务对象。然后创建SqlSession实现类DefaultSqlSession。
当通过getMapper获取到接口对象时,MyBatis会使用动态代理创建接口代理对象。
当使用接口对象调用方法时会判断SQL的类型,判断接口中方法返回值类型,确定调用SqlSession的哪个方法。如果是查询,且多个返回值就调用selectList。如果查询时返回值只有一个就使用selectOne。
所有SQL执行完成后提交事务,并释放资源。
JSP
JSP页面中包含内容:
A、HTML
B、注释[java注释 HTML注释 JSP注释]
C、Java代码 [小脚本 声明式 表达式]
小脚本和声明之间区别
A、小脚本中定义java代码最后都会放到service方法中 声明定义代码最后都在方法外
B、小脚本中定义所有变量都是局部变量 声明中定义变量是成员变量
C、小脚本中不可以定义方法 声明中可以定义方法
D、小脚本中可以使用JSP内置对象 声明中不可以使用JSP内置对象
JSP注释和HTML注释区别
JSP注释更优越 JSP减少网络带宽
何为JSP的内置对象
JSP本身就给我们提供的对象 成为内置对象 我们使用内置对象时候不用创建 不用获得 ,直接使用即可,因为JSP
底层已经把这些对象创建好了。
JSP内置对象一共9个
例如:out /resuest/response
9个内置对象中 包含4个作用域 pageContext 、request、session、application
request---->HttpServletRequest;
response---->HttpServletResponse;
page-------->JspPage; 涵盖就是页面配置信息
pageContext->PageContext;
session----->HttpSession;
application->ServletContext;
config------>ServletConfig;
out--------->PrintWriter;
exception :需要在page上指定isErrorPage="true"
各种路径
<html>
<head>
<base href="<%=request.getContextPath()+"/"%>"/>
<title>Title</title>
</head>
<body>
<%--优点:任何服务器中任何资源都可以访问 缺点:路径书写比较长 比较麻烦--%>
<h3>绝对路径(协议+IP+端口+跳转地址)</h3>
<a href="http://127.0.0.1:8080/web05_war_exploded/path1/add.jsp">同一个项目中同一个目录中文件add.jsp</a><br/>
<a href="http://127.0.0.1:8080/web05_war_exploded/path1/subpath/sub.jsp">同一个项目中子级目录中文件subpath/sub.jsp</a><br/>
<a href="http://127.0.0.1:8080/web05_war_exploded/path2/update.jsp">同一个项目中同级目录中文件path2/update.jsp</a><br/>
<a href="http://127.0.0.1:8080/web05_war_exploded/index.jsp">同一个项目中父级目录中文件index.jsp</a><br/>
<a href="http://127.0.0.1:8080/web05_war_exploded/abc/PathServlet">同一个项目中Servlet如何访问</a><br/>
<a href="http://127.0.0.1:8080/web04_war_exploded/index.jsp">web04项目中index.jsp文件(同一个服务器)</a><br/>
<a href="http://www.bjsxt.com">北京尚学堂官网如何访问(不同服务器)</a><br/>
<%--优点:路径书写方式比较简单 缺点:只可以访问同一个服务器中项目--%>
<h3>根路径(必须/开始 后面紧跟【项目上下文】 +访问地址)</h3>
<a href="/web05_war_exploded/path1/add.jsp">同一个项目中同一个目录中文件add.jsp</a><br/>
<a href="/web05_war_exploded/path1/subpath/sub.jsp">同一个项目中子级目录中文件subpath/sub.jsp</a><br/>
<a href="/web05_war_exploded/path2/update.jsp">同一个项目中同级目录中文件path2/update.jsp</a><br/>
<a href="/web05_war_exploded/index.jsp">同一个项目中父级目录中文件index.jsp</a><br/>
<a href="/web05_war_exploded/abc/PathServlet">同一个项目中Servlet如何访问</a><br/>
<a href="/web04_war_exploded/index.jsp">web04项目中index.jsp文件(同一个服务器)</a><br/>
<%--<a href="http://www.bjsxt.com">北京尚学堂官网如何访问(不同服务器)</a><br/>--%>
<%--优点:路径书写方式比较简单 缺点:只可以访问同一个服务器中项目 必须找相对关系 如果path的位置改变了 这个相对路径全部不对--%>
<h3>相对路径A(直接写路径 在最前面不需要写/ 相对于本身 ./当前目录 ../返回上级)</h3>
<a href="add.jsp">同一个项目中同一个目录中文件add.jsp</a><br/>
<a href="subpath/sub.jsp">同一个项目中子级目录中文件subpath/sub.jsp</a><br/>
<a href="../path2/update.jsp">同一个项目中同级目录中文件path2/update.jsp</a><br/>
<a href="../index.jsp">同一个项目中父级目录中文件index.jsp</a><br/>
<a href="../abc/PathServlet">同一个项目中Servlet如何访问</a><br/>
<a href="../../web04_war_exploded/index.jsp">web04项目中index.jsp文件(同一个服务器)</a><br/>
<%--优点:书写最简单,并且不需要找相对关系只要顺着上面base继续写即可 缺点:只可以访问当前项目中内容--%>
<h3>相对路径B 相对于base</h3>
<a href="path1/add.jsp">同一个项目中同一个目录中文件add.jsp</a><br/>
<a href="path1/subpath/sub.jsp">同一个项目中子级目录中文件subpath/sub.jsp</a><br/>
<a href="path2/update.jsp">同一个项目中同级目录中文件path2/update.jsp</a><br/>
<a href="index.jsp">同一个项目中父级目录中文件index.jsp</a><br/>
<a href="abc/PathServlet">同一个项目中Servlet如何访问</a><br/>
<%-- <a href="/web04_war_exploded/index.jsp">web04项目中index.jsp文件(同一个服务器)</a><br/>--%>
</body>
</html>
路径规则适用于页面中 img a form …
路径使用场景
如果访问的都是当前项目文件: 推荐使用基于base的路径
如果访问是同一个服务器中其他项目文件:推荐使用根路径
如果访问是不同服务器中项目:必须使用绝对路径
MySQL的优点
MySQL之所以受到业界人士的青睐,主要是因为其具有以下几方面优点:
• 1. 开放源代码
MySQL最强大的优势之一在于它是一个开放源代码的数据库管理系统。开源的特点是给予了用户根据自己需要修改DBMS的自由。MySQL采用了General Public License,这意味着授予用户阅读、修改和优化源代码的权利,这样即使是免费版的MySQL的功能也足够强大,这也是为什么MySQL越来越受欢迎的主要原因。
• 2. 跨平台
MySQL可在不同的操作系统下运行,简单地说,MySQL可以支持Windows系统、UNIX系统、Linux系统等多种操作系统平台。这意味着在一个操作系统中实现的应用程序可很方便移植到其他操作系统下。
• 3. 轻量级
MySQL的核心程序完全采用多线程编程,这些线程都是轻量级的进程,它在灵活地为用户提供服务的同时,又不会占用过多的系统资源。因此MySQL能够更快速、高效的处理数据。
• 4. 成本低
MySQL分为社区版和企业版,社区版是完全免费的,而企业版是收费的。即使在开发中需要用到一些付费的附加功能,价格相对于昂贵的Oracle、DB2等也是有很大优势的。其实免费的社区版也支持多种数据类型和正规的SQL查询语言,能够对数据进行各种查询、增加、删除、修改等操作,所以一般情况下社区版就可以满足开发需求了,而对数据库可靠性要求比较高的企业可以选择企业版。
注意:
社区版与企业版主要的区别是:
社区版包含所有MySQL的最新功能,而企业版只包含稳定之后的功能。换句话说,社区版可以理解为是企业版的测试版。
MySQL官方的支持服务只是针对企业版,如果用户在使用社区版时出现了问题,MySQL官方是不负责任的。
总体来说,MySQL是一款开源的、免费的、轻量级的关系型数据库,其具有体积小、速度快、成本低、开放源码等优点,其发展前景是无可限量的。
MySQL中常见的数据类型
类型 | 描述 |
---|---|
int | 整型 |
double | 浮点型 |
varchar | 字符串型 |
char(M) | 0~255字符 |
date | 日期类型,格式为:yyyy-MM-dd |
datetlme | ‘YYYY-MM-DD HH:MM:SS’ |
抽象类和接口的区别
抽象类:
1. 抽象方法,只有行为的概念,没有具体的行为实现。使用abstract关键字修饰,没有方法体。子类必须重写这些抽象方法。
2. 包含抽象方法的类,一定是抽象类。
3. 抽象类只能被继承,一个类只能继承一个抽象类。
接口:
1. 全部的方法都是抽象方法,属性都是常量
2. 不能实例化,可以定义变量。
3. 接口变量可以引用具体实现类的实例
4. 接口只能被实现,一个具体类实现接口,必须实现全部的抽象方法
· 5. 接口之间可以多实现
6. 一个具体类可以实现多个接口,实现多继承现象
值传递和引用传递的区别
值传递 传的是值
引用传递 传递的是地址
理论上说,java都是引用传递,对于基本数据类型,传递是值的副本,而不是值本身。
对于对象类型,传递是对象的引用,当在一个方法操作操作参数的时候,其实操作的是引用所指向的对象。
Cookie和session的区别
1. 数据储存位置 cookie在客户端浏览器 session 在服务器
2. 安全方面 coolie相较session不安全
3. 数据大小 cookie保存的数据不能超4k 很多浏览器规定一个站点不能超20个对于session来说并没有上限,但出于对服务器端的性能考虑,session内不要存放过多的东西,并且设置session删除机制。
4. 有效期不同开发可以通过设置cookie的属性,达到使cookie长期有效的效果。session依赖于名为JSESSIONID的cookie,而cookie JSESSIONID的过期时间默认为-1,只需关闭窗口该session就会失效,因而session不能达到长期有效的效果。
5. 服务器压力不同cookie保管在客户端,不占用服务器资源。对于并发用户十分多的网站,cookie是很好的选择。 session是保管在服务器端的,每个用户都会产生一个session。假如并发访问的用户十分多,会产生十分多的session,耗费大量的内存。
6. 跨域支持上不同cookie支持跨域名访问。session不支持跨域名访问
。
==和equals的区别
==:
== 比较的是变量(栈)内存中存放的对象的(堆)内存地址,用来判断两个对象的地址是否相同,即是否是指相同一个对象。比较的是真正意义上的指针操作。
1、比较的是操作符两端的操作数是否是同一个对象。
2、两边的操作数必须是同一类型的(可以是父子类之间)才能编译通过。
3、比较的是地址,如果是具体的阿拉伯数字的比较,值相等则为true,如:
int a=10 与 long b=-10L 与 double c=10.0都是相同的(为true),因为他们都指向地址为10的堆。
equals:
equals用来比较的是两个对象的内容是否相等,由于所有的类都是继承自java.lang.Object类的,所以适用于所有对象,如果没有对该方法进行覆盖的话,调用的仍然是Object类中的方法,而Object中的
equals方法返回的却是==的判断。
总结:
所有比较是否相等时,都是用equals 并且在对常量相比较时,把常量写在前面,因为使用object的
equals object可能为null 则空指针
在阿里的代码规范中只使用equals ,阿里插件默认会识别,并可以快速修改,推荐安装阿里插件来排查老代码使用“==”,替换成equals
Jsp和Servlet的区别
1. jsp经编译后就变成了Servlet。
2. jsp更擅长表现于页面显示,servlet更擅长于逻辑控制。
3. Servlet中没有内置对象,Jsp中的内置对象都是必须通过HttpServletResponse对象以及HttpServlet对象得到。
4. 而Servlet则是个完整的Java类,这个类的Service方法用于生成对客户端的响应
5. Servlet的应用逻辑是在Java文件中,并且完全从表示层中的HTML里分离开来。
6. 而JSP的情况是Java和HTML可以组合成一个扩展名为.jsp的文件。
7. JSP侧重于视图,Servlet主要用于控制逻辑
8. Servlet更多的是类似于一个Controller,用来做控制。
JDK,JRE,JVM的区别
Jdk是工具包 包含了jre(运行环境)和jvm是虚拟机
1. JDK:java development kit:java开发工具包,是开发人员所需要安装的环境
2. JRE:java runtime environment:java运行环境,java程序运行所需要安装的环境
Get请求方式和Post请求方式的区别
* GET请求会被浏览器主动cache(缓存),而POST不会,除非手动设置。
* GET请求只能进行url编码,而POST支持多种编码方式。*
* GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留。
* GET请求在URL中传送的参数是有长度限制的,而POST则没有。对参数的数据类型GET只接受ASCII字符,从http协议的角度来说 get请求 和 post请求都没有限制, 这个长度限制是浏览器(各种浏览器) 和 服务器 提出来的 长度限制 http协议本身就是用来做数据通信用的(包含请求 & 响应) 不同的浏览器对长度的限制是不一样的: IE浏览器对长度限制为 最大2KB chrome浏览器对长度限制为 最大8kb 经过我们实测中文 1.81kb都报错 字符串1.81kb没问题,不报错 其他浏览器各自有各自的长度 需要的时候 去看一下就可以了
* POST既可是字符也可是字节。
* GET相比POST来说不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息。
String StringBuffer 和String Builder有什么区别
1、StringBuffer与StringBuilder中的方法和功能完全是等价的。
2、只是StringBuffer中的方法大都采用了 synchronized 关键字进行修饰,因此是线程安全的,而StringBuilder没有这个修饰,可以被认为是线程不安全的。
3、在单线程程序下,StringBuilder效率更快,因为它不需要加锁,不具备多线程安全而StringBuffer则每次都需要判断锁,效率相对更低
String是只读字符串,它并不是基本数据类型,而是一个对象。从底层源码来看是一个fifinal类型的字符数组,所引用的字符串不能被改变,一经定义,无法再增删改。每次对String的操作都会生成新的
String对象
每次+操作 : 隐式在堆上new了一个跟原字符串相同的StringBuilder对象,再调用append方法 拼接
+后面的字符。
StringBuffer与StringBuilder都继承了AbstractStringBulder类,而AbtractStringBuilder又实现了CharSequence接口,两个类都是用来进 行字符串操作的。
在做字符串拼接修改删除替换时,效率比string更高。
StringBuffer是线程安全的,Stringbuilder是非线程安全的。所以Stringbuilder比stringbuffer效率更高,StringBuffer的方法大多都加了
synchronized关键字
bean生命周期
1. bean的生命周期 通过构造函数(不管是有参还是无参)来实例化bean
2. 为bean的属性设置值 和对其他的bean引用(调用set方法)
3. 在初始化之前执行的方法 postProcessBeforeInitialization
4. 调用bean的初始化的方法(需要进行配置) xml文件中加上init -method
5. 在初始化之后执行的方法 postProcessAfterInitialization
6. 获取创建bean实例对象
7. 执行销毁方法
java有哪些异常
NullPointerException:空指针异常.
SQLException:mysql语句异常
IndexOutOfBoundsException:下标超出
new arrayList() 默认的长度为多少?
10
了解Map吗? HashMap底层原理
HashMap底层基于数组+链表+红黑树。HashMap存储的是键值对,
将每个键值对保存到Node对象中,然后再把Node对象存到Node数组中。
重载和重写的区别
重写**(Override)**
从字面上看,重写就是 重新写一遍的意思。其实就是在子类中把父类本身有的方法重新写一遍。子类继承了父类原有的方法,
但有时子类并不想原封不动的继承父类中的某个方法,所以在方法名,参数列表,返回类型(除过子类中方法的返回值是父类中方法
返回值的子类时)都相同的情况下, 对方法体进行修改或重写,这就是重写。但要注意子类函数的访问修饰权限不能少于父类的。
public class Father {
public static void main(String[] args) {
// TODO Auto-generated method stub
Son s = new Son();
s.sayHello();
}
public void sayHello() {
System.out.println("Hello");
}
}
class Son extends Father{
@Override
public void sayHello() {
// TODO Auto-generated method stub
System.out.println("hello by ");
}
}
原因: 在某个范围内的整型数值的个数是有限的,而浮点数却不是。
重写 总结:
1.发生在父类与子类之间
2.方法名,参数列表,返回类型(除过子类中方法的返回类型是父类中返回类型的子类)必须相同
3.访问修饰符的限制一定要大于被重写方法的访问修饰符(public>protected>default>private)
4.重写方法一定不能抛出新的检查异常或者比被重写方法申明更加宽泛的检查型异常
重载(Overload)
在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同甚至是参数顺序不同) 则视为重载。同时,重载对返回类型没有要求,
可以相同也可以不同,但不能通过返回类型是否相同来判断重载。
public class Father {
public static void main(String[] args) {
// TODO Auto-generated method stub
Father s = new Father();
s.sayHello();
s.sayHello("wintershii");
}
public void sayHello() {
System.out.println("Hello");
}
public void sayHello(String name) {
System.out.println("Hello" + " " + name);
}
}
重载总结:
1.重载Overload是一个类中多态性的一种表现
2.重载要求同名方法的参数列表不同(参数类型,参数个数甚至是参数顺序)
3.重载的时候,返回值类型可以相同也可以不相同。无法以返回型别作为重载函数的区分标准
怎么保证线程安全
1. 针对第一个因素,JDK里面提供了很多atomic类, AtomicLong, AtomicBoolean等等,
这些类本身可以通过CAS来保证操作的原子性;Java也提供了锁机制,
锁内的代码块在同一时刻只能有一个线程执行,例如synchronized关键字。
2. 针对第二个因素,可用synchronized关键字加锁保证,
将操作共享数据的语句加入synchronized关键字,
同一时刻只能有一个线程执行,直到完成。 也可用volatile关键字,可以保证修改共享数据后对其他线程可见。
3. 针对第三个因素可以使用Lock锁。Lock锁很灵活,但需要手动释放和开启。
在并发量比较高的情况下,Lock锁优于synchronized关键字。
arraylist怎么进行扩容的?什么时候扩容?
扩容的时候 会以新的容量建一个原数组的拷贝 修改原数组 指向这个新数组 原数组被抛弃
会被cg回收当数组的大小大于初始容量的时候 (比如初始值为10 当添加第11个元素的时候 )
就会进行扩容 新容量为旧容量的1.5倍
MySQL支持的事务隔离级别、每个级别解决了什么问题,默认是使用哪个级别
未提交读(Read Uncommitted)允许脏读 可能读到其他会话中未提交事务修改的数据不重复度(Read Committed)
只能读取到已经提交的数据
可重复读(Repeated Read)在同一个事务内的查询都是事务开始时刻一致的,InnoDB默认级别。
在SQL标准中,该隔离级别消除了不可重复读,但是还存在幻象读,但是innoDB解决了幻读串行读((Repeated Read))
完全串行化的读 每次读都需要获得表级共享锁 读和写都会阻塞 默认 可重读读
char和vachar区别
Char最大长度是255字符 varchar最大长度65535个字符Char是定长的 不足的地方用隐藏空格填充
varchar是不定长的Char会浪费空间 varchar不会浪费空间Char查找效率高 varchar查找效率低
BIO、NIO、AIO 有什么区别
BIO (Blocking I/O):
同步阻塞I/O模式,数据的读取写入必须阻塞在一个线程内等待其完成。这里假设
一个烧开水的场景,有一排水壶在烧开水,BIO的工作模式就是,
叫一个线程停留在一个水壶那,直到这个水壶烧开,才去处理下一个水壶。但是实际上线程在等待水壶烧开的时间段什么都没有做。
NIO (New I/O):
同时支持阻塞与非阻塞模式,但这里我们以其同步非阻塞I/O模式来说明,
那么什么叫做同步非阻塞?如果还拿烧开水来说,NIO的做法是叫一个线程不断的轮询每个水壶的状态,
看看是否有水壶的状态发生了改变,从而进行下一步的操作。
AIO ( Asynchronous I/O):
异步非阻塞I/O模型。异步非阻塞与同步非阻塞的区别在哪里?
异步非阻塞无需一个线程去轮询所有IO操作的状态改变,在相应的状态改变后,系统会通知对应的线程来处理。
对应到烧开水中就是,为每个水壶上面装了一个开关,水烧开之后,水壶会自动通知我水烧开了。
java基本数据类型?
Java 中的基本数据类型只有 8 个:
byte 、short、int、long、float、double、 char、boolean;
除了基本类型(primitive type), 剩下的都是引用类型(reference type),
Java 5 以后引入的枚举类型也算是一种比较特殊的引用类型。
如何实现序列化
序列化是将对象状态转换为可保持或传输的格式的过程。说明白点就是你可以用对象输出流输出到文件.
如果不序列化输出的话.很可能会乱!
实现方式是实现java.io.Serializable接口.这个接口不需要实现任何具体方法.
只要implements java.io.Serializable 就好了
java中的序列化机制能够将一个实例对象(只序列化对象的属性值,而不会去序列化什么所谓的方法。)的状态信息写入到一个
字节流中使其可以通过socket进行传输、或者持久化到存储数据库或文件系统中;然后在需要的时候通过字节流中的信息来
重构一个相同的对象。
一般而言,要使得一个类可以序列化,只需简单实现java.io.Serializable接口即可。
怎么保证线程安全
主要有三点:
原子性:一个或者多个操作在CPU执行的过程中被中断
可见性:一个线程对共享变量的修改,另外一个线程不能立刻看到
有序性:程序执行的顺序没有按照代码的先后顺序执行
Redis的数据类型有哪些?
概述:
Redis的键值可以使用物种数据类型:字符串,散列表,列表,集合,有序集合。本文详细介绍这五种数据类型的使用方法。
本文命令介绍部分只是列举了基本的命令,至于具体的使用示例,可以参考Redis官方文档:Redis命令大全
1.字符串类型
字符串是Redis中最基本的数据类型,它能够存储任何类型的字符串,包含二进制数据。可以用于存储邮箱,JSON化的对象,甚至是一张图片,
一个字符串允许存储的最大容量为512MB。字符串是其他四种类型的基础,与其他几种类型的区别从本质上来说只是组织字符串的方式不同而已。
基本命令
字符串操作
1、SET赋值,用法:SET key value
2、GET取值,用法:GET key
3、INCR递增数字,仅仅对数字类型的键有用,相当于Java的i++运算,用法:INCR key
4、INCRBY增加指定的数字,仅仅对数字类型的键有用,相当于Java的i+=3,用法:INCRBY key increment,意思是key自增increment,increment可以为负数,表示减少。
5、DECR递减数字,仅仅对数字类型的键有用,相当于Java的i–,用法:DECR key
6、DECRBY减少指定的数字,仅仅对数字类型的键有用,相当于Java的i-=3,用法:DECRBY key decrement,意思是key自减decrement,decrement可以为正数,表示增加。
7、INCRBYFLOAT增加指定浮点数,仅仅对数字类型的键有用,用法:INCRBYFLOAT key increment
8、APPEND向尾部追加值,相当于Java中的”hello”.append(“ world”),用法:APPEND key value
9、STRLEN获取字符串长度,用法:STRLEN key
10、MSET同时设置多个key的值,用法:MSET key1 value1 [key2 value2 ...]
11、MGET同时获取多个key的值,用法:MGET key1 [key2 ...]
位操作
1、GETBIT获取一个键值的二进制位的指定位置的值(0/1),用法:GETBIT key offset
2、SETBIT设置一个键值的二进制位的指定位置的值(0/1),用法:SETBIT key offset value
3、BITCOUNT获取一个键值的一个范围内的二进制表示的1的个数,用法:BITCOUNT key [start end]
4、BITOP该命令可以对多个字符串类型键进行位运算,并将结果存储到指定的键中,BITOP支持的运算包含:OR,AND,XOR,NOT,用法:BITOP OP desKey key1 key2
5、BITPOS获取指定键的第一个位值为0或者1的位置,用法:BITPOS key 0/1 [start, end]
2.散列类型
散列类型相当于Java中的HashMap,他的值是一个字典,保存很多key,value对,每对key,value的值个键都是字符串类型,
换句话说,散列类型不能嵌套其他数据类型。一个散列类型键最多可以包含2的32次方-1个字段。
基本命令
1、 HSET赋值,用法:HSET key field value
2、 HMSET一次赋值多个字段,用法:HMSET key field1 value1 [field2 values]
3、 HGET取值,用法:HSET key field
4、 HMGET一次取多个字段的值,用法:HMSET key field1 [field2]
5、 HGETALL一次取所有字段的值,用法:HGETALL key
6、 HEXISTS判断字段是否存在,用法:HEXISTS key field
7、 HSETNX当字段不存在时赋值,用法:HSETNX key field value
8、 HINCRBY增加数字,仅对数字类型的值有用,用法:HINCRBY key field increment
9、 HDEL删除字段,用法:HDEL key field
10、 HKEYS获取所有字段名,用法:HKEYS key
11、 HVALS获取所有字段值,用法:HVALS key
12、 HLEN获取字段数量,用法:HLEN key
3.列表类型
列表类型(list)用于存储一个有序的字符串列表,常用的操作是向队列两端添加元素或者获得列表的某一片段。
列表内部使用的是双向链表(double linked list)实现的,所以向列表两端添加元素的时间复杂度是O(1),
获取越接近列表两端的元素的速度越快。但是缺点是使用列表通过索引访问元素的效率太低(需要从端点开始遍历元素)。
所以列表的使用场景一般如:朋友圈新鲜事,只关心最新的一些内容。借助列表类型,Redis还可以作为消息队列使用。
基本命令
1、 LPUSH向列表左端添加元素,用法:LPUSH key value
2、 RPUSH向列表右端添加元素,用法:RPUSH key value
3、 LPOP从列表左端弹出元素,用法:LPOP key
4、 RPOP从列表右端弹出元素,用法:RPOP key
5、 LLEN获取列表中元素个数,用法:LLEN key
6、 LRANGE获取列表中某一片段的元素,用法:LRANGE key start stop,index从0开始,-1表示最后一个元素
7、 LREM删除列表中指定的值,用法:LREM key count value,删除列表中前count个值为value的元素,当count>0时从左边开始数,count<0时从右边开始数,count=0时会删除所有值为value的元素
8、 LINDEX获取指定索引的元素值,用法:LINDEX key index
9、 LSET设置指定索引的元素值,用法:LSET key index value
10、 LTRIM只保留列表指定片段,用法:LTRIM key start stop,包含start和stop
11、 LINSERT像列表中插入元素,用法:LINSERT key BEFORE|AFTER privot value,从左边开始寻找值为privot的第一个元素,然后根据第二个参数是BEFORE还是AFTER决定在该元素的前面还是后面插入value
12、 RPOPLPUSH将元素从一个列表转义到另一个列表,用法:RPOPLPUSH source destination
4.集合类型
集合在概念在高中课本就学过,集合中每个元素都是不同的,集合中的元素个数最多为2的32次方-1个,集合中的元素师没有顺序的。
基本命令
1. SADD添加元素,用法:SADD key value1 [value2 value3 ...]
2. SREM删除元素,用法:SREM key value2 [value2 value3 ...]
3. SMEMBERS获得集合中所有元素,用法:SMEMBERS key
4. SISMEMBER判断元素是否在集合中,用法:SISMEMBER key value
5. SDIFF对集合做差集运算,用法:SDIFF key1 key2 [key3 ...],先计算key1和key2的差集,然后再用结果与key3做差集
6. SINTER对集合做交集运算,用法:SINTER key1 key2 [key3 ...]
7. SUNION对集合做并集运算,用法:SUNION key1 key2 [key3 ...]
8. SCARD获得集合中元素的个数,用法:SCARD key
9. SDIFFSTORE对集合做差集并将结果存储,用法:SDIFFSTORE destination key1 key2 [key3 ...]
10. SINTERSTORE对集合做交集运算并将结果存储,用法:SINTERSTORE destination key1 key2 [key3 ...]
11. SUNIONSTORE对集合做并集运算并将结果存储,用法:SUNIONSTORE destination key1 key2 [key3 ...]
12. SRANDMEMBER随机获取集合中的元素,用法:SRANDMEMBER key [count],当count>0时,会随机中集合中获取count个不重复的元素,当count<0时,随机中集合中获取|count|和可能重复的元素。
13. SPOP从集合中随机弹出一个元素,用法:SPOP key
5.有序集合类型
有序集合类型与集合类型的区别就是他是有序的。有序集合是在集合的基础上为每一个元素关联一个分数,
这就让有序集合不仅支持插入,删除,判断元素是否存在等操作外,还支持获取分数最高/最低的前N个元素。
有序集合中的每个元素是不同的,但是分数却可以相同。有序集合使用散列表和跳跃表实现,
即使读取位于中间部分的数据也很快,时间复杂度为O(log(N)),有序集合比列表更费内存。
基本命令
1. ZADD添加元素,用法:ZADD key score1 value1 [score2 value2 score3 value3 ...]
2. ZSCORE获取元素的分数,用法:ZSCORE key value
3. ZRANGE获取排名在某个范围的元素,用法:ZRANGE key start stop [WITHSCORE],按照元素从小到大的顺序排序,从0开始编号,包含start和stop对应的元素,WITHSCORE选项表示是否返回元素分数
4. ZREVRANGE获取排名在某个范围的元素,用法:ZREVRANGE key start stop [WITHSCORE],和上一个命令用法一样,只是这个倒序排序的。
5. ZRANGEBYSCORE获取指定分数范围内的元素,用法:ZRANGEBYSCORE key min max,包含min和max,(min表示不包含min,(max表示不包含max,+inf表示无穷大
6. ZINCRBY增加某个元素的分数,用法:ZINCRBY key increment value
7. ZCARD获取集合中元素的个数,用法:ZCARD key
8. ZCOUNT获取指定分数范围内的元素个数,用法:ZCOUNT key min max,min和max的用法和5中的一样
9. ZREM删除一个或多个元素,用法:ZREM key value1 [value2 ...]
10. ZREMRANGEBYRANK按照排名范围删除元素,用法:ZREMRANGEBYRANK key start stop
11. ZREMRANGEBYSCORE按照分数范围删除元素,用法:ZREMRANGEBYSCORE key min max,min和max的用法和4中的一样
12. ZRANK获取正序排序的元素的排名,用法:ZRANK key value
13. ZREVRANK获取逆序排序的元素的排名,用法:ZREVRANK key value
14. ZINTERSTORE计算有序集合的交集并存储结果,用法:ZINTERSTORE destination numbers key1 key2 [key3 key4 ...]
WEIGHTS weight1 weight2 [weight3 weight4 ...] AGGREGATE SUM | MIN | MAX,numbers表示参加运算的集合个数,
weight表示权重,aggregate表示结果取值
15. ZUNIONSTORE计算有序几个的并集并存储结果,用法和14一样,不再赘述。
描述下边路缓存?
数据先找cache,cache没有数据,找数据库,在将数据库查询结果保存在cache。先写DB,再删cache,在同步cache(解决写穿透)
cache aside pattern ----缓存模式,从数据存储区加载到缓存中的数据,这种模式可以提高性能,也有助于保持在缓存中的数据之间的一致性和底层数据存储的数据
在redis中经常使用这种方式保证数据库和缓存双写的数据的一致性
在读数据时,先读缓存,缓存没有,在读数据库,然后取出来放入缓存,同时返回响应;
在更新的时候,先更新数据库,再删除缓存
为什么是删除缓存,不是更新缓存
因为在复杂的缓存场景中,缓存不单单是数据库中直接取出来的值
这个缓存到底会不会被频繁访问到?
比如可能更新了某个表的一个字段,然后其对应的缓存,是需要查询另外两个表的数据并进行运算,才能计算出缓存最新的值的。
另外更新缓存的代价有时候是很高的。是不是说,每次修改数据库的时候,都一定要将其对应的缓存更新一份?也许有的场景是这样,但是对于比较复杂的缓存数据计算的场景,就不是这样了。如果你频繁修改一个缓存涉及的多个表,缓存也频繁更新。但是问题在于,
最初级的缓存不一致问题及解决方案
问题:先更新缓存,再删除缓存,若缓存更新失败,会导致数据库是新数据,缓存中是旧数据,数据就出现不一致
解决:先删除缓存,在更新数据库,数据库更新失败,那数据库中的数据是旧数据,缓存中是空的,那么不会出现数据不一致
比较复杂的数据不一致问题及解决
数据发生了变更,先删除了缓存,然后要去修改数据库,此时还没修改。一个请求过来,去读缓存,发现缓存空了,去查询数据库,查到了修改前的旧数据,放到了缓存中。随后数据变更的程序完成了数据库的修改
解决:
。这样的话,一个数据变更的操作,先删除缓存,然后再去更新数据库,但是还没完成更新。此时如果一个读请求过来,读到了空的缓存,那么可以先将缓存更新的请求发送到队列中,此时会在队列中积压,然后同步等待缓存更新完成。
这里有一个优化点,一个队列中,其实多个更新缓存请求串在一起是没意义的,因此可以做过滤,如果发现队列中已经有一个更新缓存的请求了,那么就不用再放个更新请求操作进去了,直接等待前面的更新操作请求完成即可。
待那个队列对应的工作线程完成了上一个操作的数据库的修改之后,才会去执行下一个操作,也就是缓存更新的操作,此时会从数据库中读取最新的值,然后写入缓存中。
如果请求还在等待时间范围内,不断轮询发现可以取到值了,那么就直接返回;如果请求等待的时间超过一定时长,那么这一次直接从数据库中读取当前的旧值。
redis的持久化策略有哪些?分别有什么特点?
你知道反射吗,可以说一下什么是反射吗
反射是一种计算机处理方式。有程序可以访问、检测和修改它本身状态或行为的这种能力。
能提供封装程序集、类型的对象。(程序集包含模块,而模块包含类型,类型又包含成员。)
优点:
1、反射提高了程序的灵活性和扩展性。
2、降低耦合性,提高自适应能力。
3、它允许程序创建和控制任何类的对象,无需提前硬编码目标类。
缺点:
1、性能问题:使用反射基本上是一种解释操作,用于字段和方法接入时要远慢于直接代码。
因此反射机制主要应用在对灵活性和拓展性要求很高的系统框架上,普通程序不建议使用。
2、使用反射会模糊程序内部逻辑;程序员希望在源代码中看到程序的逻辑,反射却绕过了源代码的技术,
因而会带来维护的问题,反射代码比相应的直接代码更复杂。
AOP/OOP
区分
AOP、OOP在字面上虽然非常类似,但却是面向不同领域的两种设计思想。OOP(面向对象编程)针对业务处理过程的实体及其属性和行为进行抽象封装,
以获得更加清晰高效的逻辑单元划分。
而AOP则是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。
这两种设计思想在目标上有着本质的差异。
上面的陈述可能过于理论化,举个简单的例子,对于“雇员”这样一个业务实体进行封装,自然是OOP/OOD的任务,我们可以为其建立一个“Employee”类,
并将“雇员”相关的属性和行为封装其中。而用AOP设计思想对“雇员”进行封装将无从谈起。
同样,对于“权限检查”这一动作片断进行划分,则是AOP的目标领域。而通过OOD/OOP对一个动作进行封装,则有点不伦不类。
换而言之,OOD/OOP面向名词领域,AOP面向动词领域。
关系
很多人在初次接触 AOP 的时候可能会说,AOP 能做到的,一个定义良好的 OOP 的接口也一样能够做到,我想这个观点是值得商榷的。
AOP和定义良好的 OOP 的接口可以说都是用来解决并且实现需求中的横切问题的方法。但是对于 OOP 中的接口来说,它仍然需要我们在相应的模块中去调用
该接口中相关的方法,这是 OOP 所无法避免的,并且一旦接口不得不进行修改的时候,所有事情会变得一团糟;AOP 则不会这样,
你只需要修改相应的 Aspect,再重新编织(weave)即可。 当然,AOP 也绝对不会代替 OOP。
核心的需求仍然会由 OOP 来加以实现,而 AOP 将会和 OOP 整合起来,以此之长,补彼之短。
什么是缓存击穿?如何解决?
如果缓存中的数据在某个时刻批量过期,导致大部分用户的请求都会直接落在数据库上,这种现象就叫作缓存击穿。
造成缓存击穿的主要原因就是:我们为缓存中的数据设置了过期时间。如果在某个时刻从数据库获取了大量的数据,并设置了相同的过期时间,
这些缓存的数据就会在同一时刻失效,造成缓存击穿问题
解决:
对于比较热点的数据,我们可以在缓存中设置这些数据永不过期;也可以在访问数据的时候,在缓存中更新这些数据的过期时间;
如果是批量入库的缓存项,我们可以为这些缓存项分配比较合理的过期时间,避免同一时刻失效。
还有一种解决方案就是:使用分布式锁,保证对于每个 Key 同时只有一个线程去查询后端的服务,某个线程在查询后端服务的同时,
其他线程没有获得分布式锁的权限,需要进行等待。不过在高并发场景下,这种解决方案对于分布式锁的访问压力比较大。
什么是缓存穿透?如何解决?
缓存穿透是指用户查询数据,在数据库没有,自然在缓存中也不会有。这样就导致用户查询的时候,在缓存中找不到,每次都要去数据库再
查询一遍,然后返回空(相当于进行了两次无用的查询)。这样请求就绕过缓存直接查数据库,这也是经常提的缓存命中率问题。
解决办法;
最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从
而避免了对底层存储系统的查询压力。另外也有一个更为简单粗暴的方法,如果一个查询返回的数据为空(不管是数据不存在,还是系统故
障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。通过这个直接设置的默认值存放到缓存,这样第二次
到缓冲中获取就有值了,而不会继续访问数据库,这种办法最简单粗暴。
43.什么是缓存雪崩?如何解决?
由于原有缓存失效,新缓存未到期间(例如:我们设置缓存时采用了相同的过期时间,在同一时刻出现大面积的缓存过期),
所有原本应该访问缓存的请求都去查询数据库了,而对数据库CPU和内存造成巨大压力,严重的会造成数据库宕机。
从而形成一系列连锁反应,造成整个系统崩溃。
解决办法:
大多数系统设计者考虑用加锁( 最多的解决方案)或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写,
从而避免失效时大量的并发请求落到底层存储系统上。还有一个简单方案就时讲缓存失效时间分散开。
什么Redis的脑裂?如何解决
----- 哨兵模式下的脑裂;
------集群模式下的脑裂;
异地多活—保证高可用,
完全解决不可能,只能尽最大可能避免
-------主从迁移带来的不一致
(1)什么是redis集群的脑裂
redis的集群脑裂是指因为网络问题,导致redis master节点和redis slave节点和sentinel集群处于不同的网络分区,此时因为sentinel集群无法感知到master的存在,所以讲slave节点提升为master节点,此时存在两个不同的master节点,就像一个大脑分裂成两个
(2)redis集群的脑裂造成的问题
如果客户端还是基于原来的master节点继续写入数据,那么新的master节点吴法同步这些数据,当网络问题解决后sentinel(哨兵)集群将原来的master节点降为slave节点,此时在从新的master中同步数据,将会造成大量的数据流失
(3)解决redis脑裂问题的方案
在redis的配置文件中存在两个参数
min-slaves-to-write 3 //表示连接到master的最小slave数量
min-slaves-max-lag 10 //表示slave连接到master的最大延迟时间
若连接到master的slave数量小于第一个参数,且延迟时间小于等于第二个参数,那么master就会拒绝写请求,配置这两个参数后,
若发生集群脑裂,原来的master节点接收到客户端的写入请求会拒绝,就可以减少数据同步之后的数据丢失
Lrf和lru缓存淘汰算法的方式是什么?
LRU 全称 Least recently used,意思为淘汰掉最久未使用(即最老)的一条数据;
LFU 全称 Least-frequently used,意思为淘汰掉过去被访问次数最少的一条数据。
LRU在服务器缓存中经常有用武之地,它有非常成熟的O(1)复杂度的实现方法;LFU也有O(1)的实现方法,它的实现方法是受到LRU实现方法的启发。
LRU
首先实现对每个数据的 O(1) 访问,那么只需要将数据保存在hashmap中,利用[key, value],实现对数据的快速访问。除此之外,实现LRU算法我们需要:
在缓存已满时,再插入数据时,就需要淘汰最老的数据,怎么确定哪些数据是最老的。
在插入数据时,需要将当前插入的数据标记为最新访问
同理,在查找数据时,需要将查找的数据标记为最新访问
为了解决这3个问题,我们可以引入一条双向链表:
如果数据为第一次插入,则将其key插入在尾部
如果数据查询一次,则将其key从链表中删除,重新将key从尾部插入
这样,我们就可以确定链表的头部为最老数据、尾部为最新数据。那么第一个问题也就迎刃而解:
在插入数据时,如果当前的数据已满,需要淘汰最老的数据,即删除链表的头部节点数据。同时从hashmap中删除数据。
struct MapValue {
int value;
list<int>::iterator node_list_iter;
};
class LRU {
public:
LRU(size_t capacity) : capacity_(capacity) { }
optional<int> Get(int key) {
auto it = map_.find(key);
if(it == map_.end()) {
return {};
}
// 找到了,因为本次访问,该key对应的数据变为最新
list_.erase(it->second.node_list_iter);
it->second.node_list_iter = list_.insert(list_.end(), key);
return it->second.value;
}
void Set(int key, int value)
{
// 先查找该节点是否存在
auto it = map_.find(key);
if(it == map_.end()) { // 不存在,插入数据
// 插入数据之前,要看看数据是否已满,如果已满,需要淘汰最老数据
if(map_.size() >= capacity_) {
map_.erase(list_.front());
list_.erase(list_.begin());
}
auto&& [it2, b] = map_.insert(make_pair(key, MapValue{value}));
assert(b);
it = it2;
} else { // 存在
list_.erase(it->second.node_list_iter);
}
// 将该key对应的value设置为新的值,并将该key设置为最新(既放入链表末尾)
it->second.node_list_iter = list_.insert(list_.end(), key);
it->second.value = value;
}
private:
const size_t capacity_; // LRU最多缓存多少条数据
unordered_map<int, MapValue> map_;
list<int> list_; // 链表,存储每个节点的key。头节点为最老的节点,尾节点为最新
};
测试代码:
void test_lru()
{
LRU lru(3);
lru.set(9001, "9001");
lru.set(9002, "9002");
lru.set(9003, "9003");
lru.set(9004, "9004");
string value;
assert(lru.get(9001, value) == false);
assert(lru.get(9002, value) == true);
assert(value == "9002");
}
LFU(Least-frequently used)
LFU 是淘汰掉过去被访问次数最少的一条数据,那么我们需要已记录一个key的访问频次,并按照访问频次排序。
Spring security 认证流程
你知道的异常有哪些
1、Java中异常分为哪两种?
编译时异常
运行时异常
2、异常的处理机制有几种?
异常捕捉:try…catch…finally,
异常抛出:throws。
3、如何自定义一个异常
继承一个异常类,通常是RumtimeException或者Exception
4、try catch fifinally,try里有return,finally还执行么?
执行,并且finally的执行早于try里面的return
结论:
1、不管有木有出现异常,finally块中代码都会执行;
2、当try和catch中有return时,finally仍然会执行;
3、finally是在return后面的表达式运算后执行的(此时并没有返回运算后的值,而是先把要返回的值保存起来,
管finally中的代码怎么样,返回的值都不会改变,任然是之前保存的值),所以函数返回值是在finally执行前确定的;
4、finally中最好不要包含return,否则程序会提前退出,返回值不是try或catch中保存的返回值。
5、 Excption与Error包结构
Java可抛出(Throwable)的结构分为三种类型:被检查的异常(CheckedException),运行时异常(RuntimeException),错误(Error)。
1、运行时异常
定义:
RuntimeException及其子类都被称为运行时异常。
特点:
Java编译器不会检查它。也就是说,当程序中可能出现这类异常时,倘若既"没有通过throws声明抛出它",也"没有用try-catch语句捕获它",还是会编译通过。例如,除数为零时产生的ArithmeticException异常,数组越界时产生的IndexOutOfBoundsException异常,failfast机制产生的ConcurrentModi?cationException异常(java.util包下面的所有的集合类都是快速失败的,“快速失败”也就是fail-fast,它是Java集合的一种错误检测机制。当多个线程对集合进行结构上的改变的操作时,有可能会产生fail-fast机制。记住是有可能,而不是一定。
例如:假设存在两个线程(线程1、线程2),线程1通过Iterator在遍历集合A中的元素,在某个时候线程2修改了集合A的结构(是结构上面的修改,而不是简单的修改集合元素的内容),那么这个时候程序就会抛出ConcurrentModi?cationException 异常,从而产生fail-fast机制,这个错叫并发修改异常。Fail-safe,java.util.concurrent包下面的所有的类都是安全失败的,在遍历过程中,如果已经遍历的数组上的内容变化了,迭代器不会抛出ConcurrentModicationException异常。如果未遍历的数组上的内容发生了变化,则有可能反映到迭代过程中。这就是ConcurrentHashMap迭代器弱一致的表现。ConcurrentHashMap的弱一致性主要是为了提升效率,是一致性与效率之间的一种权衡。要成为强一致性,就得到处使用锁,甚至是全局锁,这就与Hashtable和同步的HashMap一样了。)等,都属于运行时异常。
常见的五种运行时异常:
ClassCastException(类转换异常)
IndexOutOfBoundsException(数组越界)
NullPointerException(空指针异常)
ArrayStoreException(数据存储异常,操作数组是类型不一致)
Bu?erOver?owException
2、被检查异常
定义:Exception类本身,以及Exception的子类中除了"运行时异常"之外的其它子类都属于被检查异常。
特点 : Java编译器会检查它。此类异常,要么通过throws进行声明抛出,要么通过try-catch进行捕获处理,否则不能通过编译。
例如,CloneNotSupportedException就属于被检查异常。当通过clone()接口去克隆一个对象,而该对象对应的类没有实Cloneable接口,
就会抛出CloneNotSupportedException异常。被检查异常通常都是可以恢复的。
如:
IOException
FileNotFoundException
SQLException
被检查的异常适用于那些不是因程序引起的错误情况,比如:读取文件时文件不存在引发的FileNotFoundException 。
然而,不被检查的异常通常都是由于糟糕的编程引起的,比如:在对象引用时没有确保对象非空而引起的 NullPointerException 。
3、错误
定义 : Error类及其子类。
特点 : 和运行时异常一样,编译器也不会对错误进行检查。当资源不足、约束失败、或是其它程序无法继续运行的条件发生时,就产生错误。程序本身无法修复这些错误的。例如,VirtualMachineError就属于错误。出现这种错误会导致程序终止运行。OutOfMemoryError、ThreadDeath。
Java虚拟机规范规定JVM的内存分为了好几块,比如堆,栈,程序计数器,方法区等
6、Thow与thorws区别
位置不同
throws 用在函数上,后面跟的是异常类,可以跟多个;而 throw 用在函数内,后面跟的
是异常对象。
功能不同:
1. throws 用来声明异常,让调用者只知道该功能可能出现的问题,可以给出预先的处理方式;
throw 抛出具体的问题对象,执行到 throw,功能就已经结束了,跳转到调用者,并将具体的问题对象抛给调用者。
也就是说 throw 语句独立存在时,下面不要定义其他语句,因为执行不到。
2. throws 表示出现异常的一种可能性,并不一定会发生这些异常;throw 则是抛出了异常,执行 throw 则一定抛出了某种异常对象。
3. 两者都是消极处理异常的方式,只是抛出或者可能抛出异常,但是不会由函数去处理异常,真正的处理异常由函数的上层调用处理。
7、Error与Exception区别?
Error和Exception都是java错误处理机制的一部分,都继承了Throwable类。
Exception表示的异常,异常可以通过程序来捕捉,或者优化程序来避免。
Error表示的是系统错误,不能通过程序来进行错误处理。
8、error和exception有什么区别
error 表示恢复不是不可能但很困难的情况下的一种严重问题。比如说内存溢出。不可能指望程序能处理这样的情况 exception 表示一种设
计或实现问题。也就是说,它表示如果程序运行正常,从不会发生的情况
49. 有哪些注解可以创建Bean对象
java8新特性有
- Lambda 表达式
- 函数接口
- 方法引用
- 流式编程
- Optional
线程创建的方式有哪些?
Spring boot自动化配置原理
创建索引的sql语句
创建索引可以加快对数据的查询速度,加速表和表之间的连接,使用 sql 语句创建索引的语法有:
创建表的同时指定索引语法:
create table [表名](
[属性名1] [数据类型1] [约束1],
...
[属性名n] [数据类型n] [约束n],
unique index [索引名] //唯一索引
)在这里插入代码片
alter 语句在已经存在的表上创建索引语法:
alter table [表名] add index [索引名]
使用 CREATE TABLE 语句创建索引语法:
create index [索引名] ON [表名]
注:unique 为唯一索引,通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。
有哪些技巧可以提高SQL查询的性能记录详细的需求文档
1. 记录详细的需求文档
在写SQL之前必须弄清楚需求, 具体要取什么数据, 有些什么具体的约束条件,
在数据仓库环境中还可以考虑补上这个需求具体对应哪些报表, 对应的基础表如何. 到开发环境的话,
可以考虑加上这条SQL服务于哪些业务(页面),调用频率如何.
2. 不要重新制造轮子
对于一些已经比较成熟的解决方案,有比较现成的例子的SQL,要善于利用已有SQL,已有模板.
3. 降低语句的复杂度
有些同学可能比较喜欢使用比较炫的技术,比较炫的SQL来解决问题. 但是要切记一点,
使用过于复杂过于新的技术, 如果不能在性能(以及其他方面)带来好处的话, 只会使得这条SQL难于维护, 使得其他相关人员难于理解.
4. 小心处理NULL
NULL在Oracle数据库中是一个非常特别的值, 它不等于任何值, 所以如果你的SQL返回的值数量偏少,
或者根本不对很可能就是使用NULL出现了问题..
常见的情况是:
1. 查询的时候直接使用条件 colx = xxx,而这个colx里面是有NULL值的, 这种情况下查询的返回结果是不会包含NULL值对应的记录的,
如果要查询出NULL对应的记录, 需要使用 colx is null (is not null).
2. var 为null的时候, 在plsql中给var赋值, var := var + xxx;这种情况下var的值会一直是null的, 这一点需要特别注意,
5. 自己核对数据类型
在where条件里面要仔细地核对数据类型, 由于隐形转换的问题, 在数据类型错误的时候, Oracle无法正确使用索引, 可能会导致SQL运行非常慢.
6. 小心处理重复数据
在需求明确的情况下, 如果你不在乎是否出现重复记录, 或者明确知道不会出现重复数据的情况下,
尽量使用Union All而不是Union进行查询, Union会涉及到昂贵的排序操作.
7. 避免不必要的优化操作
SQL的性能调优可能非常有趣非常带劲, 但是很多时候调优可能意义不大, 比如对于只会使用一次的查询, 你可能很少在乎是1秒钟结束还是2秒钟结束..
只查询你需要的字段, 而不要所有的查询都是用select *来进行
在通过索引来查询更合适的时候, 尽量在查询条件中指定有索引的字段来查询.
(在返回的记录条数很少的时候, 使用索引一般都能更加快速的得到查询结果.不要可以避免使用表连接. 关系数据库就是为了表连接而设计的.
8. 尽可能使用绑定变量
在开发环境使用的SQL语句尽量使用绑定变量, 这样可以大大缓解Oracle数据库解析SQL的消耗, 也可以大大提高数据库的可扩展性.
9. 使用源码控制工具
最好使用CVS/SVN一类的源码控制工具来管理你的SQL/PLSQL代码,
这对于后期的维护有非常大的帮助, 也有助于其他人更好的理解你最初写这条SQL的意图.
10. 测试,测试,测试.
在SQL写好之后, 要深入的进行测试, 以确保其正常运行
redis能支持的最大并发量
redis可以支持小数据可以达到10W的并发量。
redis是一个key-value存储系统。和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型)。这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,redis支持各种不同方式的排序。与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。