常见单例:饿汉式、懒汉式、双重检测锁、静态内部类、枚举单例
饿汉式:
优点:因为没有锁效率高
缺点:浪费内存,线程不安全
public class SingletonDemoInHunger {
// 私有实例,类初始化就加载
private static SingletonDemoInHunger instance = new SingletonDemoInHunger();
// 私有构造方法
private SingletonDemoInHunger() {}
// 公共的、静态的获取实例方法
public static SingletonDemoInHunger getInstance() {
return instance;
}
}
懒汉式:
优点:可以延迟加载,节约资源
缺点:线程不安全
public class SingletonDemoInLazy {
// 私有实例,初始化的时候不加载(延迟加载)
private static SingletonDemoInLazy instance;
// 私有构造
private SingletonDemoInLazy() {}
// 公共获取实例方法(线程不安全)
public static SingletonDemoInLazy getInstance() {
if(instance == null ) { // 使用的时候加载
instance = new SingletonDemoInLazy();
}
return instance;
}
}
双重检索:
优点:线程安全,延迟加载
缺点:
public class SingletonDemoInDoubleCheckLock {
// 私有实例,volatile关键字,禁止指令重排。
private volatile static SingletonDemoInDoubleCheckLock instance;
// 私有构造
private SingletonDemoInDoubleCheckLock() {}
// 公共获取实例方法(线程安全)
public static SingletonDemoInDoubleCheckLock getInstance() {
if(instance == null ) { // 一重检查
synchronized (SingletonDemoInDoubleCheckLock.class) {
if(instance == null) { // 二重检查
instance = new SingletonDemoInDoubleCheckLock();
}
}
}
return instance;
}
}
静态内部类:
优点:静态类部类不被加载,故而不占内存,jdk的类加载机制保证线程安全,调用获取实例方法才会实例化instance,延迟加载
public class SingletonDemoInStaticInnerClass {
// 静态内部类
private static class InnerClass{
// 初始化实例
private final static SingletonDemoInStaticInnerClass INSTANCE = new SingletonDemoInStaticInnerClass();
}
// 私有构造
private SingletonDemoInStaticInnerClass() {}
// 公关获取实例方法(线程安全,延迟加载)
public static SingletonDemoInStaticInnerClass getInstance() {
return InnerClass.INSTANCE;
}
}
枚举:
优点:最佳单例实现方法、简洁
public enum SingletonEnum {
// 枚举元素本身就是单例
INSTANCE;
// 其他要执行的方法
public void sayHello() {
System.out.println("你好");
}
}
一般情况下,不建议懒汉式,建议使用饿汉式;只有在要明确实现延迟加载效果时,才会使用静态内部类;如果涉及到反序列化创建对象时,可以尝试使用枚举;如果有其他特殊的需求,可以考虑使用双重检查锁。
排序算法:
冒泡排序:
思路:
1、比较相邻的元素。如果第一个比第二个大,就交换它们两个;
2、对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数;
3、针对所有的元素重复以上的步骤,除了最后一个;
4、重复步骤1~3,直到排序完成。
实现:
/**
* 冒泡排序
*/
public static int[] bubbleSort(int[] array){
if (array.length == 0)
return array;
for (int i = 0; i < array.length; i++){
for (int j = 0; j < array.length - 1 - i; j++){
if (array[j+1] < array[j]){
int temp = array[j+1];
array[j+1] = array[j];
array[j] = temp;
}
}
}
return array;
}
最佳情况:T(n) = O(n)
最差情况:T(n) = O(n2)
平均情况:T(n) = O(n2)
选择排序:
思路:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕
实现:
/**
* 选择排序
*/
public static int[] selectionSort(int[] array){
if (array.length == 0)
return array;
for (int i = 0; i < array.length; i++){
int minIndex = i;
for (int j = i; j < array.length; j++){
if (array[j] < array[minIndex]) //找到最小的数
minIndex = j; //将最小数的索引保存
}
int temp = array[minIndex];
array[minIndex] = array[i];
array[i] = temp;
}
return array;
}
最佳情况:T(n) = O(n2)
最差情况:T(n) = O(n2)
平均情况:T(n) = O(n2)
插入排序:
思路:通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入,因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间
/**
* 插入排序
*/
public static int[] insertionSort(int[] array){
if (array.length == 0)
return array;
int current;
for (int i = 0; i < array.length - 1; i++){
current = array[i+1];
int preIndex = i;
while (preIndex >= 0 && current < array[preIndex]){
array[preIndex + 1] = array[preIndex];
preIndex--;
}
array[preIndex + 1] = current;
}
return array;
}
最佳情况:T(n) = O(n)
最差情况:T(n) = O(n2)
平均情况:T(n) = O(n2)
归并排序:
思路:
1、把长度为n的输入序列分成两个长度为n/2的子序列;
2、对这两个子序列分别采用归并排序;
3、将两个排序好的子序列合并成一个最终的排序序列。
实现:
/**
* 归并排序
*/
public static int[] MergeSort(int[] array){
if (array.length < 2)
return array;
int mid = array.length / 2;
int[] left = Arrays.copyOfRange(array, 0, mid);
int[] right = Arrays.copyOfRange(array, mid, array.length);
return merge(MergeSort(left), MergeSort(right));
}
/**
* 归并排序——将两段排序好的数组结合成一个排序数组
*/
public static int[] merge(int[] left, int[] right){
int[] result = new int[left.length + right.length];
for (int index = 0, i = 0, j = 0; index < result.length; index++){
if (i >= left.left)
result[index] = right[j++];
else if (j >= right.length)
result[index] = left[i++];
else if (left[i] > right[j])
result[index] = right[j++];
else
result[index] = left[i++];
}
return result;
}
最佳情况:T(n) = O(n)
最差情况:T(n) = O(nlogn)
平均情况:T(n) = O(nlogn)
快速排序:
思路:快速排序使用分治法来把一个串(list)分为两个子串(sub-lists)。具体算法描述如下:
1、从数列中挑出一个元素,称为 “基准”(pivot);
2、重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
3、递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。
实现:
/**
* 快速排序
*/
public static int getIndex(int[] arr, int low, int high){
//基准数据
int temp = arr[low];
while (low < high){
//当队尾的元素大于等于基准数据时,向前挪动high指针
while (low < high && arr[high] >= temp){
high --;
}
// 如果队尾元素小于tmp了,需要将其赋值给low
arr[low] = arr[high];
// 当队首元素小于等于tmp时,向前挪动low指针
while (low <high && arr[low] <= temp){
low ++;
}
//当队首元素大于tmp时,需要将其赋值给high
arr[high] = arr[low];
}
// 跳出循环时low和high相等,此时的low或high就是tmp的正确索引位置
// 由原理部分可以很清楚的知道low位置的值并不是tmp,所以需要将tmp赋值给arr[low]
arr[low] = temp;
return low; //返回temp的正确位置
}
public static void quickSort(int[] arr, int low, int high){
if (low <high){
//找寻基准数据的正确索引
int index = getIndex(arr, low, high);
// 进行迭代对index之前和之后的数组进行相同的操作使整个数组变成有序
quickSort(arr, 0, index - 1);
quickSort(arr, index + 1, high);
}
}
最佳情况:T(n) = O(nlogn)
最差情况:T(n) = O(n2)
平均情况:T(n) = O(nlogn)
OSI七层模型:应用层(HTTP)、表示层、会话层、传输层(TCP、UDP)、网络层(IP)、数据链路层、物理层
分层优点:方便学习各层协议规范;模块化;降低复杂度,易于开发;
多线程:
什么是多线程?
指同一进程有多个线程执行。
为什么需要使用多线程?
提高CPU利用率。
多线程有哪些优缺点?
优点:提高CPU利用率,响应速度快、用户体验感好
缺点:线程越多内存占用越多,会产生竞争资源问题,产生类似读写不一致问题
如何创建一个线程?
继承 Tread类, 重写 run方法;
实现 Runnable接口,实现 run方法;
实现 Callable接口,实现 call方法,该方式可以获取线程的执行结果。
如何安全地共享数据?
使用 synchronized 关键字;
使用 volatile 关键字;
使用原子类。
什么是线程同步?
保证线程能了解其他线程任务处理结束后的处理结果而采取的保护措施即为线程同步;
优点:解决线程的安全问题;
缺点:每次都要判断锁,降低了效率;
如何避免线程死锁?
线程死锁是指两个或多个线程互相持有对方需要的锁,而导致它们都无法向前执行的状态。为了避免线程死锁,以下是几个常见的方法:
①避免循环依赖:线程之间的互相依赖可以导致死锁。确保你的线程不会形成循环依赖,如果有,可以通过重新设计程序或调整锁的顺序来解决。
② 使用定时锁:使用带有超时功能的锁可以确保死锁不会无限期地持续下去。如果一个线程尝试获取锁,但是在一定时间内没有成功,那么它就会释放所有已经获取的锁并退出。这样可以避免死锁。
③ 避免持有多个锁:如果一个线程持有多个锁,那么它就有可能在等待另一个线程释放它需要的锁时,阻塞其它线程。尽量避免一个线程同时持有多个锁。
④ 按照相同的顺序获取锁:如果多个线程都需要获取多个锁,那么它们应该按照相同的顺序获取锁。这可以避免死锁的发生。
⑤使用线程安全的数据结构:线程安全的数据结构可以避免多个线程同时访问同一数据结构而导致的死锁。
⑥使用死锁检测工具:一些工具可以检测出潜在的死锁问题,并给出相应的解决方案。使用这些工具可以帮助你及时发现并解决潜在的死锁问题。
总之,避免线程死锁需要从多个方面入手,包括设计程序的架构、锁的使用方式、以及使用合适的工具等。
什么是线程池?
是一种多线程处理的机制,它可以通过提前创建一定数量的线程并维护这些线程的状态,来避免重复创建和销毁线程,从而提高线程的利用率和性能。
Executors.newFixedThreadPool:创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待;
Executors.newCachedThreadPool:创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程;
Executors.newSingleThreadExecutor:创建单个线程数的线程池,它可以保证先进先出的执行顺序;
Executors.newScheduledThreadPool:创建一个可以执行延迟任务的线程池;
Executors.newSingleThreadScheduledExecutor:创建一个单线程的可以执行延迟任务的线程池;
Executors.newWorkStealingPool:创建一个抢占式执行的线程池(任务执行顺序不确定)【JDK 1.8 添加】。
ThreadPoolExecutor:最原始的创建线程池的方式,它包含了 7 个参数可供设置
7 个参数代表的含义如下:
参数 1:corePoolSize
核心线程数,线程池中始终存活的线程数。
参数 2:maximumPoolSize
最大线程数,线程池中允许的最大线程数,当线程池的任务队列满了之后可以创建的最大线程数。
参数 3:keepAliveTime
最大线程数可以存活的时间,当线程中没有任务执行时,最大线程就会销毁一部分,最终保持核心线程数量的线程。
参数 4:unit:
单位是和参数 3 存活时间配合使用的,合在一起用于设定线程的存活时间 ,参数 keepAliveTime 的时间单位有以下 7 种可选:
TimeUnit.DAYS:天
TimeUnit.HOURS:小时
TimeUnit.MINUTES:分
TimeUnit.SECONDS:秒
TimeUnit.MILLISECONDS:毫秒
TimeUnit.MICROSECONDS:微妙
TimeUnit.NANOSECONDS:纳秒
参数 5:workQueue
一个阻塞队列,用来存储线程池等待执行的任务,均为线程安全,它包含以下 7 种类型:
ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列。
LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列。
SynchronousQueue:一个不存储元素的阻塞队列,即直接提交给线程不保持它们。
PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列。
DelayQueue:一个使用优先级队列实现的无界阻塞队列,只有在延迟期满时才能从中提取元素。
LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。与SynchronousQueue类似,还含有非阻塞方法。
LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。
较常用的是 LinkedBlockingQueue 和 Synchronous,线程池的排队策略与 BlockingQueue 有关。
参数 6:threadFactory
线程工厂,主要用来创建线程,默认为正常优先级、非守护线程。
参数 7:handler
拒绝策略,拒绝处理任务时的策略,系统提供了 4 种可选:
AbortPolicy:拒绝并抛出异常。
CallerRunsPolicy:使用当前调用的线程来执行此任务。
DiscardOldestPolicy:抛弃队列头部(最旧)的一个任务,并执行当前任务。
DiscardPolicy:忽略并抛弃当前任务。
默认策略为 AbortPolicy。
Lambda表达式:
优点:
简化代码,易读和易于维护。
更容易地编写函数式接口的实现,并将它们作为参数传递给其他方法。
在不定义方法的情况下创建匿名函数,这大大减少了代码的冗余性。
可以使用函数式编程范式进行开发。函数式编程是一种面向函数的编程范式,它强调将计算视为数学函数的组合和应用。使用Lambda表达式可以更轻松地在Java中使用函数式编程的技巧和概念。
除此之外,在并行编程和事件驱动编程中提供更好的性能。
轻松地编写并行代码,因为它们可以在多个线程中执行而不需要显式地创建线程。
在事件驱动编程中,Lambda表达式可以作为回调函数使用,处理异步事件并简化异步编程的实现。
总之,Java中Lambda表达式的最大优势是它可以使代码更简洁、易读和易于维护,并使开发人员能够更轻松地使用函数式编程技巧和概念,以及更好地处理并行和事件驱动编程的问题。
缺点:
Lambda表达式可以使代码更紧凑,但如果不适当使用,也可能使代码难以理解。
性能问题:在某些情况下,使用Lambda表达式可能会导致性能下降,尤其是在循环中使用Lambda表达式时。这是因为Lambda表达式会在运行时创建对象和执行闭包,这可能会导致不必要的开销。此外,Lambda表达式可能会使代码变得更难调试。由于Lambda表达式是匿名的,因此在出现问题时可能需要更多的调试工作来确定问题所在。
最后,Lambda表达式不适用于所有场景。例如,当需要在代码中实现复杂的逻辑时,使用Lambda表达式可能会使代码更难以理解和维护。总之,Java中Lambda表达式的主要缺点是可能会导致代码可读性下降、性能问题、调试困难以及不适用于所有场景。然而,这些问题可以通过适当的使用和了解Lambda表达式的限制来缓解。
集合类框架:
常用的集合有ArrayList、LinkedList、HashMap。
ArrayList本质上是一个可以动态扩容数组,适合查询,不适合增加删除,默认长度是10,添加第11个元素则扩容为原数组的1.5倍;
LinkedList本质上是双向链表,不适合查询,适合增加删除;
HashMap:
jdk1.7的时候,是数组+链表结构,通过key计算hash值落到每一个桶里面,也就是数组里面,当有相同的hash值得时候,则在数组值下形成一个单向链表;
jdk1.8的时候,是数组+链表+红黑树,在原来基础上,因为单项链表查询速度是比较慢的,当单向链表的长度超过8个元素的时候开始转化为红黑树,因为变成了红黑树,所以查找效率变快。
ABA问题:
就比如t1,t2线程执行时间分别为10s,1s,这样的话t2就有可能在10s内将原来的值修改为其它值再修改回来,对于t1的话,用cas比较,值还是一样的,所以会比较交换成功,实际上值跟原来的值就不一样了,我们可以使用AtomicStampedReference(原子引用)解决,相当于加了一个版本号,比较的时候,还需要比较版本号是否一致,不一致则比较交换失败
并发读取不一致:
在Java中,当多个线程同时读取一个共享变量时,可能会发生并发读取不一致的问题。这是因为每个线程都有自己的缓存,而不是直接访问主内存中的变量。当一个线程修改了共享变量的值并将其刷新回主内存时,其他线程可能仍然使用其本地缓存中的旧值,从而导致读取不一致的问题。
为了解决这个问题,Java提供了几种机制来确保并发读取的一致性:
1. 使用volatile关键字:可以将一个变量声明为volatile,这将告诉Java虚拟机,这个变量是共享的,需要直接从主内存中读取和写入。这样可以确保读取和写入的一致性。
2. 使用synchronized关键字:使用synchronized关键字可以确保同一时刻只有一个线程可以访问共享变量,这样就避免了并发读取不一致的问题。
3. 使用原子变量:Java提供了一些原子变量,例如AtomicInteger、AtomicLong等,它们提供了一些原子操作,例如getAndAdd()、getAndSet()等,这些操作都是原子的,可以避免并发读取不一致的问题。4. 使用线程安全的集合类:Java提供了一些线程安全的集合类,例如ConcurrentHashMap、CopyOnWriteArrayList等,它们内部实现了同步机制,可以确保并发读取的一致性。总之,避免并发读取不一致的方法有很多,具体的选择取决于具体的应用场景和需求。
MySQL常见操作:
MySQL是一种关系型数据库,常用于Web应用程序中。
以下是MySQL常见的操作和常用的优化方法:
1. 创建数据库和表格使用CREATE DATABASE语句创建数据库,CREATE TABLE语句创建表格。
2. 插入数据使用INSERT语句插入数据,可以一次插入多个数据行。
3. 查询数据使用SELECT语句查询数据,可以使用WHERE子句指定查询条件,ORDER BY子句指定排序顺序。
4. 更新数据使用UPDATE语句更新数据,可以使用WHERE子句指定要更新的数据行,SET子句指定要更新的数据。
5. 删除数据使用DELETE语句删除数据,可以使用WHERE子句指定要删除的数据行。
6. 备份和恢复数据使用mysqldump命令备份MySQL数据库,使用mysql命令恢复MySQL数据库。
常用优化方法:
1. 设计优化的表格结构合理的表格结构可以提高查询和更新的效率,包括使用合适的数据类型和索引。
2. 使用索引使用索引可以加快数据查询的速度,可以创建单列或多列索引,需要根据查询和更新的需求来选择索引类型。
3. 优化查询语句查询语句可以通过使用合适的索引和优化语句来提高查询速度,例如避免使用通配符、使用JOIN替代子查询等。
4. 配置服务器参数可以通过调整服务器参数来提高MySQL的性能,例如调整缓冲池大小、调整查询缓存、调整线程池等。
5. 使用缓存可以使用缓存技术来减轻数据库服务器的压力,例如使用Memcached或Redis缓存查询结果或数据。
6. 分区表格分区表格可以将数据分成多个逻辑分区,以提高查询和更新的效率,适用于数据量较大的表格。
索引失效:
1. 数据类型不匹配:例如,在一个整数列上定义索引,但查询条件使用字符串。
2. 函数应用:如果在查询条件中使用函数(例如:LOWER,UPPER,TRIM,DATE_FORMAT等)来操作列,索引将不会被使用。因为MySQL无法使用索引来优化对列的函数操作。
3. LIKE模糊查询:如果在查询条件中使用LIKE模糊匹配,但不是以通配符开头(即使用%),那么索引将不会被使用。
4. OR操作:如果查询条件中有OR操作,且OR条件中的每个条件不是在同一个索引列上,那么索引将不会被使用。因为MySQL无法在多个索引中同时使用。
5. 不使用前缀:如果在一个前缀索引列上使用LIKE匹配,但不使用索引列的前缀,那么索引将不会被使用。
6. 非索引前缀:如果在查询条件中使用了非索引列的前缀,那么索引将不会被使用。
7. 非索引列排序:如果在ORDER BY子句中使用了非索引列排序,那么索引将不会被使用。
8. NULL值:如果索引列中有NULL值,那么MySQL将不会使用该索引。因为MySQL认为NULL值不是值,因此无法使用索引来定位它们。
9. 列类型过长:如果索引列的长度过长,那么MySQL将不会使用该索引。因为MySQL在使用索引时需要将其加载到内存中,如果列类型过长,那么内存中可以存储的记录数将会减少。
总的来说,索引失效是由于MySQL无法使用索引来优化查询所导致的。为了确保索引的有效性,需要确保查询条件中使用的列与索引列相同,且不使用函数、模糊查询、非索引列排序等操作。
Redis缓存穿透、击穿、雪崩:
Redis是非关系型数据库,通常用于缓存,它的高性能和易用性使得它成为许多应用程序的首选。然而,在使用Redis时,我们可能会遇到缓存穿透、缓存击穿和缓存雪崩等问题。
详细解释以及解决方案:
1、缓存穿透:
指请求的数据不存在于缓存中,每次请求都需要访问数据库,这样就会给数据库带来很大的负担,从而降低应用程序的性能。可能是恶意访问、也可能是正常访问缓存中不存在的数据来引发缓存穿透问题。
解决方案:
①对于缓存中不存在的请求,可以使用布隆过滤器进行过滤,从而避免向数据库发送无效的查询请求。
②可以将请求合法性校验放在缓存层之前,例如使用限流等技术。
2. 缓存击穿
指缓存中不存在但数据库中存在的数据,这样的请求也会导致数据库的访问,这会导致大量的查询请求同时访问数据库,从而影响数据库的性能。
解决方案:
①对于查询结果为空的数据,也可以使用缓存来保存空结果,这样可以避免多次查询数据库。
②可以设置热点数据的永久缓存,从而避免缓存击穿的问题。
3. 缓存雪崩
指缓存中的大量数据同时失效,这样的情况会导致大量的请求同时访问数据库,从而对数据库的性能造成极大的影响。
解决方案:
①可以设置不同的过期时间,从而避免大量的数据同时失效。
②可以使用分布式锁的方式来避免缓存雪崩的问题。
③可以增加缓存服务器的数量,从而提高缓存的可用性。
需要注意的是,以上的解决方案都有其优缺点,具体的应用场景需要根据具体情况进行选择。同时,多种技术的组合使用也可以提高缓存的可靠性和可用性。
Spring、MyBatis、Spring Boot的常见问题:
Spring:
1. 什么是Spring框架?
Spring框架是一个开源的Java应用程序框架,用于创建企业级应用程序。它提供了许多有用的功能,例如依赖注入、面向切面编程等,使得开发人员能够更加轻松地构建可维护和可扩展的应用程序。
2. 什么是Spring的依赖注入?
依赖注入是一种实现IOC(Inversion of Control,控制反转)的方式。在依赖注入中,对象不再负责管理它所依赖的对象的创建和生命周期,而是将这些任务委托给一个专门的容器。Spring容器可以自动地将需要依赖的对象注入到相应的对象中,从而实现了对象之间的解耦。
3. Spring中的Bean是什么?
在Spring中,Bean是指由Spring容器管理的对象。Bean是由Spring框架创建、组装和管理的对象。它们通常用于表示应用程序中的业务逻辑和数据。
4. Spring中的AOP是什么?
AOP(Aspect-Oriented Programming,面向切面编程)是一种编程范式,用于解决跨越多个对象的横切关注点的问题。在Spring中,AOP通过将横切关注点抽象为一个切面来实现。切面是一个类,它定义了横切关注点和它们所应用的对象。
MyBatis:
1. 什么是MyBatis?
MyBatis是一种用于Java语言的ORM框架,它允许开发人员使用普通的SQL语句来访问数据库,并将结果映射到Java对象中。它提供了许多有用的功能,例如缓存、事务处理等。
2. MyBatis中的Mapper是什么?
在MyBatis中,Mapper是指一个用于定义SQL语句和映射结果集的接口。Mapper文件通常包含一组与数据库操作相关的SQL语句。开发人员可以通过调用Mapper接口的方法来执行这些SQL语句,从而实现与数据库的交互。
3. MyBatis中的一级缓存和二级缓存是什么?
MyBatis中的一级缓存是指在同一个SqlSession中执行的多个查询可以共享同一个缓存。当SqlSession执行查询时,它会先查找缓存中是否存在相应的结果,如果存在,则直接返回缓存中的结果。MyBatis中的二级缓存是指多个Sql、Session之间可以共享同一个缓存。当一个SqlSession执行查询时,如果结果存在于缓存中,则直接返回缓存中的结果,否则将查询结果存储到缓存中以供后续的SqlSession使用。
4. MyBatis中的动态SQL是什么?
动态SQL是一种允许在运行时根据不同的条件生成不同SQL语句的技术。在MyBatis中,动态SQL可以通过使用if、choose、when、otherwise、foreach等标签来实现。这些标签可以根据不同的条件生成不同的SQL语句,从而实现灵活的查询。
Spring Boot:
1. 什么是Spring Boot?
Spring Boot是一个基于Spring框架的快速开发框架,它提供了许多有用的特性,例如自动配置、快速启动等,使得开发人员能够更加轻松地构建独立的、生产级别的应用程序。### 2. Spring Boot中的自动配置是什么?Spring Boot中的自动配置是指通过分析应用程序的类路径、配置文件和其他元数据来自动配置应用程序。这使得开发人员能够更加轻松地构建应用程序,因为它们不需要手动配置很多东西。
3. Spring Boot中的Starter是什么?
Spring Boot中的Starter是一种包含了特定的库和依赖关系的依赖项集合。开发人员可以使用这些Starter来轻松地添加特定功能到他们的应用程序中。例如,Spring Boot提供了spring-boot-starter-web Starter,它包含了用于构建Web应用程序所需的所有依赖项。
4. Spring Boot中的Actuator是什么?
Spring Boot中的Actuator是一组用于监视和管理应用程序的端点。它提供了许多有用的功能,例如健康检查、配置信息、性能监视等。开发人员可以使用这些端点来监视和管理他们的应用程序。
Spring Boot和Spring Cloud区别:
Spring Boot和Spring Cloud是两个不同的框架,它们都是基于Spring框架构建的,但是它们的目的和应用场景不同。
Spring Boot是一个用于构建独立的、生产级别的Spring应用程序的快速开发框架。它提供了很多开箱即用的特性,例如自动配置、快速启动等,使得开发人员能够更加轻松地构建独立的、生产级别的应用程序。
Spring Cloud是一个用于构建分布式系统的框架,它提供了许多有用的功能,例如服务注册与发现、配置管理、负载均衡、断路器等。它基于Spring Boot构建,并且可以与多种开源项目(例如Netflix Eureka、Zuul、Hystrix等)集成,使得开发人员能够更加轻松地构建分布式系统。
因此,可以说Spring Boot是用于构建单体应用程序的框架,而Spring Cloud是用于构建分布式系统的框架。当需要构建分布式系统时,可以使用Spring Boot和Spring Cloud一起使用。
Spring Bean的生命周期:
1. 实例化阶段:当容器启动时,Spring会根据配置文件中的信息创建Bean的实例,这个阶段会调用Bean的构造方法。
2. 属性赋值阶段:在Bean实例化后,Spring会根据配置文件或注解将Bean的属性注入到Bean中,这个阶段又称为依赖注入。
3. 初始化阶段:在Bean的所有依赖注入完成后,Spring会对Bean进行一些初始化操作,可以使用注解或实现接口的方式指定初始化方法。在这个阶段中,可以对Bean进行一些自定义的初始化操作。
4. 使用阶段:在初始化完成后,Bean可以被容器使用,它会一直存在于容器中,直到容器关闭。
5. 销毁阶段:当容器关闭时,Spring会自动调用Bean的销毁方法,可以使用注解或实现接口的方式指定销毁方法。在这个阶段中,可以对Bean进行一些自定义的销毁操作。
需要注意的是,如果Bean是通过实现DisposableBean接口或使用@PreDestroy注解指定的销毁方法进行销毁的,那么这些销毁方法会在容器关闭时自动调用。如果Bean实现了InitializingBean接口或使用@PostConstruct注解指定的初始化方法,那么这些初始化方法会在Bean的依赖注入完成后自动调用。