最详细的JAVA面试(208题)解析,个人理解 (含答案)(实时更新)

一、JAVA基础

1.JDK和JRE 有什么区别?

1.

JDK>JRE>JVM

JDK = JRE+编译工具 javac

JRE= JVM+ 运行类库

2.

JDK(Java Development Kit)JAVA开发工具包,是给开发人员用的编译工具。

JRE(Java Runtime Environment)JAVA运行环境,如果只是运行代码,JRE就够了。

JVM(JAVA Virtual Machine)JAVA虚拟机,它能够识别JAVA代码,将它转成操作系统可识别的操作命令,然后在转成机器码,最后由CPU控制硬件完成指令。

2.== 和 equals的区别是什么?

1.操作符与方法的区别:

==是Java中的一个二元运算符,用于比较基本数据类型或者引用数据类型  equals()是一个方法,属于java.lang.Object类,可以被任何类继承并重写,用于比较两个对象。

2.==对于基本类型与引用类型使用的区别:

对于基本类型,==直接比较其值的大小:

int a = 10;
int b = 10;
System.out.println(a == b); // 输出:true

对于引用类型,==比较其地址是否相等,即是否指向同一块区域:

String x = "string";
String y = "string";
System.out.println(x == y);// 输出:true,因为 x 和 y 指向的是同一个引用

String str1 = new String("Hello");
String str2 = new String("Hello");
System.out.println(str1 == str2); // 输出:false,因为是两个不同的对象实例

值得注意的是:

String x = "Hello";
String str1 = new String("Hello");

有没有想过为什么一个在堆创建,另一个不会?

这是由于双引号是被用来定义字符串字面量的,只要在编写Java代码时使用双引号包裹一系列字符,Java编译器就会识别出这是一个字符串字面量,并按照相应的语法规则进行处理。

所以在String str1 = new String("Hello");中,既有new关键字在堆中创建对象,又有字面量”“的识别。

但是对于字符串字面量,编译器可能会进行优化,使得相同的字面量共享同一块内存,此时使用 == 可能会得到 true。但这依赖于编译器和JVM的具体实现,并不保证总是如此。所以

3.equals() 方法的行为

对于未重写 equal()方法的类(如自定义类),equal()方法继承自 Object类,其行为等同于 ==,即比较对象引用是否指向同一内存地址,equal不支持进行基本数据类型的比较,至于为什么,可以从它继承自object类,自己体会,本身就是为了对比对象而孕育出来的。

重写行为:许多重要的Java类(如 StringIntegerDateFile 等)已经重写了 equals() 方法,使其比较对象的内容(即它们的状态或属性值)是否相等,而不是比较引用。

String str1 = new String("Hello");
String str2 = new String("Hello");
System.out.println(str1.equals(str2)); // 输出:true,因为字符串内容相同

3.两个对象的hashCode()相同,则 equals()也一定为true,对吗?

不对

hashCode()方法定义在 Java.lang.Object类中,其作用是为对象生成一个整数表示——即所谓的哈希码(hash code)。

如果两个对象通过 equals() 方法判断为不相等(即equals() 方法返回false),它们的 hashCode() 方法允许(但不鼓励)返回相同的值,这种情况称为哈希冲突。

4.final 在java中有什么作用?

final关键字是一个修饰符,它可以用于类、方法或变量。

1.final修饰的类被称为终类,不可被继承。如String类、Math类都为终类。

2.final修饰的方法不能被重写。

3.final修饰的变量为常量,常量必须初始化,初始化之后不可改变。

变量包括:基本数据类型、对象引用和数组。

对于对象引用:

final String myString = "Hello"; // myString引用不能被修改,但"Hello"字符串的内容可以被修改
(实际上在Java中字符串是不可变的)

final StringBuffer myBuffer = new StringBuffer("Hello");  
myBuffer.append(" World"); // 这是合法的,因为是在修改StringBuffer的内容而不是引用地址。  

对于数组要注意:

final int[] myArray = {1, 2, 3};  
myArray[0] = 4; // 这是合法的,因为我们在修改数组的内容,而不是改变myArray引用的对象  
System.out.println(myArray[0]); // 输出 4  

5.java 中的 Math.round(-1.5) 等于多少?

-1          (-1.5+0.5)向下取整为-1。

1.Math.round()  四舍五入     算法为: Math.floor(x+0.5)   即加上0.5再向下取整。

2.Math.floor()  向下取整        floor  地板

3.Math.ceil()    向上取整        ceil 天花板

Math.floor(-8.5) = -9.0

Math.ceil(-8.5) = -8.0

Math.round(-8.6) = Math.floor(-8.6+0.5) =Math.floor(-8.1) = -9.0

6.String 属于基础的数据类型吗?

不属于

基础数据类型有 8 种:

byte、boolean、char、

short、int、float、long、double

String属于引用类型。

7.java 中操作字符串都有哪些类?它们之间有什么区别?

操作字符串的类有:String、StringBuffer、StringBuilder。

操作字符串的类有:String、StringBuffer、StringBuilder

1.String 声明的是不可变对象,每次操作都会生成新的 String 对象,然后将指针指向新的 String 对象。

String s1 = "Hello";  
String s2 = " World";  
String s3 = s1 + s2; // 这里实际上创建了一个新的String对象,内容为"Hello World"

String s = "Hello";  
String replaced = s.replace('e', 'a'); // 创建一个新的String对象,内容为"Hallo"

String str = "abcdef";  
String sub = str.substring(2, 4); // 创建一个新的String对象,内容为"cd"

2.StringBuffer、StringBuilder 声明的是可变的对象,可以在原有对象的基础上进行操作,所以在经常改变字符串内容的情况下最好不要使用 String。

3.StringBuffer 和 StringBuilder 最大的区别在于:

StringBuffer 是线程安全的,StringBuilder 是非线程安全的,

但 StringBuilder 的性能却高于 StringBuffer,所以在单线程环境下推荐使用 StringBuilder,多线程环境下推荐使用 StringBuffer。

8.String str="i"与 String str=new String(“i”)一样吗?

功能上是等效的,因为它们都创建了一个包含字符 'i' 的字符串对象,并将引用 str 指向这个对象。

1.String str="i" 至多创建一个对象在常量池中。

当使用双引号直接赋值时,如 String str = "i";,Java 会首先检查字符串常量池中是否已经存在该字符串。如果存在,则不会创建对象, str 会指向常量池中的那个字符串对象;如果不存在,则会在常量池中创建一个新的字符串对象,并将 str指向它,str始终是一个引用,不是对象

补充:字符串字面量"i" 在编译阶段被处理时,如果常量池中尚未存在该字面量,编译器会将其添加到字符串常量池并创建一个对应的字符串实例。此时,常量池中有一个对象被创建。

字符串字面量(String Literal)是指在源代码中直接用特定语法表示的固定、不可变的文本序列。

2.String str=new String(“i”)至多创建两个对象,堆内存和常量池各创建一个或者堆内存创建一个。

当使用 new 关键字创建字符串对象时,通过调用String类的构造函数来创建一个新的字符串对象,Java 总是在堆内存中为字符串对象分配新的内存空间,无论字符串常量池中是否已存在相同的字符串。因此,str 会指向堆中新创建的字符串对象,而不是字符串常量池中的对象。

3.由于这两种方式创建的字符串对象可能位于不同的内存区域(一个是堆内存,一个是字符串常量池),因此使用 == 运算符比较它们的引用时可能会得到不同的结果。使用equals() 方法比较它们的内容时,结果将是相同的,因为这两个字符串对象的内容都是字符 'i'

String str1 = "i";  
String str2 = "i";  
String str3 = new String("i");  
String str4 = new String("i");  
  
System.out.println(str1 == str2); // 输出 true,因为它们指向字符串常量池中的同一个对象  
System.out.println(str1 == str3); // 输出 false,因为 str1 指向常量池中的对象,而 str3 指向堆中的对象  
System.out.println(str3 == str4); // 输出 false,因为 str3 和 str4 都是堆中独立的对象  
  

System.out.println(str1.equals(str3)); // 输出 true,因为它们的内容都是 "i"  
System.out.println(str3.equals(str4)); // 输出 true,同样因为它们的内容都是 "i"

9.如何将字符串反转?

1.使用StringBuffer或StringBuilder的reverse()方法

2.获得字符串长度,通过循环截取单个字符,反向拼接。

10.String类的常用方法都有哪些?

1.subString:截取字符串

2.split():切割字符串

3.replace():替换字符串中第一个符合的字符或字符串,进行简单匹配,性能更高因为它不需要解析和匹配正则表达式。

4.replaceAll(): 替换所有符合的字符或字符串,使用正则表达式匹配,功能更强大。

5.length():获取字符串长度

6.equals():判断值是否相等

7.charAt():获取指定的字节或字符

11.抽象类必须要有抽象方法吗?

不一定,可以没有

可以从为什么要有抽象类去理解:

1.定义通用接口

抽象类可以定义一组抽象方法,这些方法构成了一个通用接口。所有继承抽象类的子类必须实现这些抽象方法,从而保证了子类具有某种共同的行为或能力。

2.为了代码的复用

例如,我要用http方法去获取上百的接口的数据,不可能创建连接的重复代码写100次把,可以在抽象类中写出,但是由于抽象方法不允许有代码块,所以可以写普通方法,子类重写的时候可以减少很多重复的代码块。

所以如果我就是想实现代码的复用,没有定义通用接口的需求,我就不需要写抽象方法了呀。

12.普通类和抽象类有哪些区别?

抽象类不能实例化,

13.抽象类能使用 final 修饰吗?

不能,抽象类就是为了让别的类继承,因为抽象类的目的就是为了实现代码复用和定义抽象接口的。如果用final修饰,还有需要用抽象类修饰符嘛?

14.接口和抽象类有什么区别?

1.接口只允许有抽象方法,抽象类可以有抽象方法,也可以有具体实现方法。

2.接口使用interface关键字定义,而抽象类使用abstract关键字定义

3.一个类只能继承一个抽象类,但可以实现多个接口

15.java 中 IO 流分为几种?

16.BIO、NIO、AIO 有什么区别?

17.Files的常用方法都有哪些?

二、容器

18.java 容器都有哪些?

19.Collection 和 Collections 有什么区别?

20.List、Set、Map 之间的区别是什么?

21.HashMap 和 Hashtable 有什么区别?

22.如何决定使用 HashMap 还是 TreeMap?

23.说一下 HashMap 的实现原理?

24.说一下 HashSet 的实现原理?

25.ArrayList 和 LinkedList 的区别是什么?

26.如何实现数组和 List 之间的转换?

27.ArrayList 和 Vector 的区别是什么?

28.Array 和 ArrayList 有何区别?

29.在 Queue 中 poll()和 remove()有什么区别?

30.哪些集合类是线程安全的?

31.迭代器 Iterator 是什么?

32.Iterator 怎么使用?有什么特点?

33.Iterator 和 ListIterator 有什么区别?

34.怎么确保一个集合不能被修改?

1.使用不可变集合

JAVA标准库提供了Collections.unmodifiableXXX()系列工厂方法,可以将可变集合转换为不可变视图。尝试对不可变集合进行添加、删除、替换等修改操作时,将会抛出UnsupportedOperationException。

List<String> mutableList = new ArrayList<>(Arrays.asList("A", "B", "C"));
List<String> unmodifiableList = Collections.unmodifiableList(mutableList);

2.封装集合,禁止直接访问

将集合封装在自定义类中,提供只读方法(如get、contains、size等)供外部访问,但不提供修改方法(如add、remove、put等)。这样,即使内部集合是可变的,外部也无法直接修改。

public class ReadOnlyCollectionWrapper<E> {
    private final Collection<E> internalCollection;

    public ReadOnlyCollectionWrapper(Collection<E> collection) {
        this.internalCollection = collection;
    }

    public boolean contains(E element) {
       return internalCollection.contains(element);
    }

}

3.使用只读接口

如果集合类本身支持只读接口,可以在方法签名或返回类型中使用这些只读接口,防止调用者通过迭代器进行修改。(例如使用ListIterator接口的相对只读的接口Iterator)

补充:(ListIterator接口是Iterator接口的子接口,专为List接口的实现类设计。它在Iterator接口的基础上增加了一些额外的功能,允许双向遍历列表,并在遍历过程中修改列表内容。)

public Iterator<String> getReadOnlyIterator() {
    return someList.iterator(); // 返回 Iterator,相对于 ListIterator 来说,限制了部分修改操作
}

三、多线程

35.并行和并发有什么区别?

36.线程和进程的区别?

37.守护线程是什么?

38.创建线程有哪几种方式?

39.说一下 runnable 和 callable 有什么区别?

40.线程有哪些状态?

41.sleep() 和 wait() 有什么区别?

1.sleep()

属于 java.lang.Thread 类的一个静态方法,用于让当前线程暂停执行指定的时间(毫秒为单位)。

它是线程的主动暂停,可以在任何地方直接调用,无需任何同步或锁定机制,主要用于线程间的简单协作或定时任务,不涉及线程间通信和同步。

2.wait()

属于  java.lang.Object 类的一个实例方法,线程调用wait()后会释放锁并进入等待状态,直到被其他线程通过notify()或notifyAll()唤醒。

是线程间的通信机制,用于线程间的协作和同步,必须在synchronized代码块或方法中调用,并且所调用的对象必须是当前代码块或方法所锁定的对象。否则会抛出IllegalMonitorStateException异常。

42.notify()和 notifyAll()有什么区别?

43.线程的 run()和 start()有什么区别?

44.创建线程池有哪几种方式?

45.线程池都有哪些状态?

46.线程池中 submit()和 execute()方法有什么区别?

47.在 java 程序中怎么保证多线程的运行安全?

48.多线程锁的升级原理是什么?

49.什么是死锁?

50.怎么防止死锁?

51.ThreadLocal 是什么?有哪些使用场景?

52.说一下 synchronized 底层实现原理?

53.synchronized 和 volatile 的区别是什么?

54.synchronized 和 Lock 有什么区别?

55.synchronized 和 ReentrantLock 区别是什么?

56.说一下 atomic 的原理?

四、反射

57.什么是反射?

Java反射是一种在运行时动态探查和操作类与对象的强大机制。

58.什么是 java 序列化?什么情况下需要序列化?

59.动态代理是什么?有哪些应用?

60.怎么实现动态代理?

五、对象拷贝

61.为什么要使用克隆?

62.如何实现对象克隆?

63.深拷贝和浅拷贝区别是什么?

六、Java Web

64.jsp 和 servlet 有什么区别?

65.jsp 有哪些内置对象?作用分别是什么?

66.说一下 jsp 的 4 种作用域?

67.session 和 cookie 有什么区别?

68.说一下 session 的工作原理?

69.如果客户端禁止 cookie 能实现 session 还能用吗?

70.spring mvc 和 struts 的区别是什么?

71.如何避免 sql 注入?

72.什么是 XSS 攻击,如何避免?

73.什么是 CSRF 攻击,如何避免?

七、异常

74.throw 和 throws 的区别?

1.throw是一个操作符,用于在方法内部主动抛出一个具体的异常实例,用于实际引发异常并中断当前方法执行。

2.throw用于在方法声明声明该方法可能会抛出的异常类型。它不是直接抛出异常,而是告诉编译器和调用者该方法有可能抛出指定类型的异常,需要调用者在使用该方法时做好异常处理。

import java.io.IOException;

// 使用 throws 声明可能会抛出 IOException 的方法
public void readFileUsingThrows(String filePath) throws IOException {
    // 假设 readFromFile 方法会抛出 IOException
    String content = readFromFile(filePath);

    System.out.println("Read file content: " + content);
}

// 使用 throw 抛出 IOException 的方法
public void readFileUsingThrow(String filePath) {
    // 模拟抛出 IOException 的条件
    if (!filePath.endsWith(".txt")) {
        throw new IllegalArgumentException("Invalid file format. Only .txt files are supported.");
    }

    // 假设 readFromFile 方法会抛出 IOException
    String content = readFromFile(filePath);

    System.out.println("Read file content: " + content);
}

// 假设的文件读取方法,抛出 IOException
private String readFromFile(String filePath) throws IOException {
    // 省略实际读取文件的逻辑,此处仅为演示
    throw new IOException("An error occurred while reading the file.");
}

public static void main(String[] args) {
    try {
        // 调用声明了 throws IOException 的方法
        readFileUsingThrows("example.txt");
    } catch (IOException e) {
        System.out.println("Caught IOException in readFileUsingThrows: " + e.getMessage());
    }

    try {
        // 调用使用 throw 抛出异常的方法
        readFileUsingThrow("example.pdf");
    } catch (IllegalArgumentException e) {
        System.out.println("Caught IllegalArgumentException in readFileUsingThrow: " + e.getMessage());
    }
}

75.final、finally、finalize 有什么区别?

76.try-catch-finally 中哪个部分可以省略?

77.try-catch-finally 中,如果 catch 中 return 了,finally 还会执行

吗?

78.常见的异常类有哪些?

八、网络

79.http 响应码 301 和 302 代表的是什么?有什么区别?

80.forward 和 redirect 的区别?

81.简述 tcp 和 udp的区别?

82.tcp 为什么要三次握手,两次不行吗?为什么?

83.说一下 tcp 粘包是怎么产生的?

84.OSI 的七层模型都有哪些?

85.get 和 post 请求有哪些区别?

86.如何实现跨域?

87.说一下 JSONP 实现原理?

九、设计模式

88.说一下你熟悉的设计模式?

89.简单工厂和抽象工厂有什么区别?

十、Spring/Spring MVC

90.为什么要使用 spring?

91.解释一下什么是 aop?

92.解释一下什么是 ioc?

93.spring 有哪些主要模块?

94.spring 常用的注入方式有哪些?

95.spring 中的 bean 是线程安全的吗?

96.spring 支持几种 bean 的作用域?

97.spring 自动装配 bean 有哪些方式?

98.spring 事务实现方式有哪些?

99.说一下 spring 的事务隔离?

100.说一下 spring mvc 运行流程?

101.spring mvc 有哪些组件?

102.@RequestMapping 的作用是什么?

103.@Autowired 的作用是什么?

十一、Spring Boot/Spring Cloud

104.什么是 spring boot?

105.为什么要用 spring boot?

106.spring boot 核心配置文件是什么?

107.spring boot 配置文件有哪几种类型?它们有什么区别?

108.spring boot 有哪些方式可以实现热部署?

109.jpa 和 hibernate 有什么区别?

110.什么是 spring cloud?

111.spring cloud 断路器的作用是什么?

112.spring cloud 的核心组件有哪些?

十二、Hibernate

113.为什么要使用 hibernate?

114.什么是 ORM 框架?

115.hibernate 中如何在控制台查看打印的 sql 语句?

116.hibernate 有几种查询方式?

117.hibernate 实体类可以被定义为 final 吗?

118.在 hibernate 中使用 Integer 和 int 做映射有什么区别?

119.hibernate 是如何工作的?

120.get()和 load()的区别?

121.说一下 hibernate 的缓存机制?

122.hibernate 对象有哪些状态?

123.在 hibernate 中 getCurrentSession 和 openSession 的区别是什么?

124.hibernate 实体类必须要有无参构造函数吗?为什么?

十三、mybatis

125.Mybatis中#{}和${}的区别?

1.处理方式和编译过程

#{} 采用的是预编译方式处理,Mybatis会将#{} 替换为数据库驱动所理解的占位符(通常是一个问号?)。

${} 采用的是字符串替换的方式处理,Mybatis直接将${} 中的内容替换为变量的原始值,不做任何额外转义或类型处理,相当于直接将变量值插入到SQL语句中。

2.安全性与sql注入防护

#{}由于使用预编译和参数绑定,能够有效防止SQL注入攻击。

${} 直接将变量值拼接到SQL语句中,没有进行预编译和参数化处理,因此无法防止SQL注入

3.自动加单引号''

#{}对于传入的字符串或类似类型的值,Mybatis会在生成的SQL语句中自动为其加上单引号。例如,#{userId}会被转化为'123',符合SQL语法要求。

${}不自动添加单引号或其他任何引号。变量值被直接插入到SQL语句中,如果该值需要被引起来(如字符串),则必须在传入时由程序员手动添加引号。

例如:

<select id="selectUser" parameterType="map" resultType="User">
    SELECT * FROM users WHERE username = #{username}
</select>

<update id="updateUser" parameterType="map">
    UPDATE users SET password = '${password}'
    WHERE username = ${username}
</update>

对于#{}

Map<String, String> params = new HashMap<>();
params.put("username", "JohnDoe");

对于#{}

Map<String, String> params = new HashMap<>();
params.put("username", "'JohnDoe'");
params.put("password", "'newPassword123'");

4.适用场景

#{}适用于大多数情况下的动态参数传递

${}适用于需要动态拼接SQL结构而非单纯参数值的场景,如动态表名、列名、ORDER BY子句中的字段名等。

126.Mybatis有几种分页方式?

1.LIMIT 分页

使用原生sql 的LIMIT语句实现分页。在查询语句后添加LIMIT#{offset},#{pageSize},其中offset是偏移量(从哪一行开始取),pagesize是每页的记录数。例如:

int currentPage = Integer.parseInt(request.getParameter("currentPage")); // 假设从请求中获取当前页码
int perPageSize = Integer.parseInt(request.getParameter("perPageSize")); // 假设从请求中获取每页记录数

int offset = (currentPage - 1) * perPageSize; // 计算偏移量
int pageSize = perPageSize; // 每页大小

List<User> users = userMapper.selectPage(offset, pageSize); // 调用Mapper方法进行分页查询

xml:

// Mapper接口方法声明
List<User> selectPage(int offset, int pageSize);

// XML映射文件中的SQL语句
<select id="selectPage" parameterType="map" resultType="User">
    SELECT * FROM users LIMIT #{offset}, #{pageSize}
</select>

这种方式简单直接,适用于大多数支持LIMIT 语句的数据库系统(如MySQL、PostgreSQL等)。它直接在数据库层面完成分页,适用于大数据量场景,减少了不必要的数据传

注意:oracle数据库不直接支持使用标准的SQL LIMIT 关键字,可以利用ROWNUM 伪列来限制查询结果的行数。

2.RowBounds分页

利用Mabatis提供的Rowbounds类进行逻辑分页。利用MyBatis核心接口SqlSession的selectList方法调用mapper,并传入一个Rowbounds实例,指定起始行(offset)和每页大小(limit),Mybatis 会在查询结果集中截取对应范围的数据返回。

RowBounds rowBounds = new RowBounds(offset, pageSize);
List<User> users = sqlSession.selectList("selectAllUsers", null, rowBounds);

注意:虽然使用方便,但Rowbounds分页通常会导致全表扫描并加载所有数据到内存中再进行分页,对于大数据量场景可能导致内存溢出,效率低下,因此在实际项目中并不推荐使用

3.分页插件

PageHelper,它可通过拦截器(Interceptor)机制自动为SQL添加分页语句。使用时只需在查询前调用PageHelper.startPage(pageNum,pageSize) ,然后执行正常的查询方法即可。插件会处理分页逻辑并在返回结果中封装分页信息。

PageHelper.startPage(pageNum, pageSize);
List<User> users = userMapper.selectAll();
PageInfo<User> pageInfo = new PageInfo<>(users);

Mybatis-Plus,内置了强大的分页功能。通过扩展Page类并传入作为参数,Mybatis-Plus会自动处理分页逻辑。同时,它还提供了便捷的链式API进行条件查询与分页。

Page<User> page = new Page<>(pageNum, pageSize);
UserQueryWrapper wrapper = new UserQueryWrapper().like("username", "John%");
userMapper.selectPage(page, wrapper);

127.RowBounds是一次性查询全部结果吗?为什么?

是,使用 RowBounds进行分页时,MyBatis 执行的SQL查询并未包含针对数据库原生分页的支持(如LIMIT、OFFSET或其他数据库特定的分页语法)。因此,数据库会返回满足查询条件的所有数据,这些数据随后被 MyBatis 在 Java 应用程序内(内存中)进行筛选,以确定最终需要返回的分页结果。具体见题126。

128.Mybatis逻辑分页和物理分页的区别是什么?

1.逻辑分页:先获取全部满足条件的数据,再在内存中进行分页,对内存资源要求较高,易造成内存溢出。

RowBounds属于逻辑分页。

2.物理分页:仅从数据库获取指定分页范围内的数据,无需加载全部结果,查询效率高,实际应用中建议这种

LIMIT、分页插件(PageHelper、Mybatis-Plus)属于物理分页。

129.mybatis 是否支持延迟加载?延迟加载的原理是什么?

1.MyBatis 是支持延迟加载(Lazy Loading)的,延迟加载是一种优化策略,它允许在真正需要数据时才去加载关联的对象或集合,而不是在初次查询时就一次性加载所有相关数据。这种机制有助于减少不必要的数据库访问,提高应用程序性能,特别是在处理复杂的关联查询和数据量较大的场景中。

mybatis仅支持关联对象association和关联集合对象collection的延迟加载,association是一对一,collection是一对多查询。

在<resultMap>标签内使用fetchType="lazy"属性,或者是在MyBatis全局配置文件中使用lazyloadingEnable=true/false。

<mapper namespace="com.example.AuthorMapper">
    <resultMap id="authorResultMap" type="Author">
        <id property="id" column="author_id"/>
        <result property="name" column="author_name"/>
        
        <!-- 一对一关联映射,启用延迟加载 -->
        <association property="mainBook" javaType="Book"
                     column="author_id" select="com.example.BookMapper.selectMainBookByAuthorId"
                     fetchType="lazy"/>

        <!-- 一对多关联映射,启用延迟加载 -->
        <collection property="books" ofType="Book"
                    column="author_id" select="com.example.BookMapper.selectBooksByAuthorId"
                    fetchType="lazy"/>
    </resultMap>

    <select id="getAuthorWithBooks" resultMap="authorResultMap">
        SELECT * FROM authors WHERE id = #{id}
    </select>
</mapper>

2.延迟加载的步骤:

生成代理对象(例如CGLIB):
当使用MyBatis查询一个主对象(如author)时,如果开启了延迟加载且该对象存在关联对象(如book),MyBatis并不会立即查询关联对象的数据。相反,它会创建一个代理对象来代表实际的author对象。这个代理对象继承或实现了与author相同的接口或类,并插入了额外的逻辑来处理关联对象的延迟加载。

触发时机:
访问代理对象中的关联属性时,例如调用如author.getMainBook()或遍历author.getBook()时,由于这些方法实际上是代理对象上的方法,它们会检测到这是一个延迟加载属性的访问请求,这意味着只有在实际需要使用关联数据的时候才会执行查询。

执行查询:
当触发延迟加载时,MyBatis 会执行额外的数据库查询,从关联表中获取相应的数据。这个查询通常是在一个新的SQL语句中完成的,以避免在初始查询中加载所有数据。

填充数据:
查询完成后,MyBatis 会将查询到的数据填充到代理对象中,使得关联属性变得可用。

130.说一下 mybatis 的一级缓存和二级缓存?

一级缓存

  • Session级别,默认启用。
  • 对首次查询结果进行缓存。
  • 后续相同查询命中缓存时,避免再次访问数据库。
  • 作用范围有限:一级缓存与数据库会话(Session)紧密关联,仅对同一会话内的查询结果进行缓存。这意味着不同会话之间的查询无法共享一级缓存中的数据,当用户并发访问或应用程序中存在多个独立会话时,相同的查询可能在不同会话中重复执行,无法利用一级缓存避免数据库访问。

  • 生命周期短暂:一级缓存通常随着会话的结束而清空,例如当用户会话结束、事务提交或回滚、sqlSession关闭等情况下,一级缓存中的数据就会失效。因此,对于那些在短时间内不会改变且被多个会话频繁查询的数据,一级缓存无法提供持久的缓存效果。

二级缓存

  • SessionFactory级别,需配置开启。
  • 查询时先检查一级缓存,未命中则查找二级缓存。
  • 目的是进一步减少数据库访问,减轻系统压力。
  • 跨会话共享:二级缓存的作用范围扩大到了SessionFactory级别,它是所有会话共享的缓存区域。这意味着只要数据被加载到二级缓存中,任何会话在执行相同查询时都能受益,避免了对数据库的重复查询,尤其在多用户并发访问或分布式环境中,二级缓存能够显著降低数据库负载。

  • 较长的生命周期:二级缓存的生命周期通常比一级缓存长,它不会随着单个会话的结束而清除,而是依据配置的缓存策略(如基于时间或大小的驱逐策略)或显式清除操作来管理数据的有效期。因此,对于那些长时间内稳定不变或变化频率较低的热点数据,二级缓存能够提供更长效的缓存服务。

131.mybatis 和 hibernate 的区别有哪些?

132.mybatis 有哪些执行器(Executor)?

133.mybatis 分页插件的实现原理是什么?

134.mybatis 如何编写一个自定义插件?

十四、RabbitMQ

部分参考:RabbitMQ的学习_rabbitmq如何学习-CSDN博客

135.rabbitmq 的使用场景有哪些?

1.解耦

例如:订单服务------>库存服务

订单服务必须了解库存服务的接口细节,如接口地址、参数格式等。库存服务的任何改动(如接口调整、服务故障等)都可能直接影响订单服务。

使用RabbitMQ后变为:订单服务------>RabbitMQ------>库存服务

订单服务接收到用户的订单请求后,创建订单并保存到数据库。同时,将一条包含商品ID和所需数量的“扣减库存”消息发送到RabbitMQ的特定队列(如inventory_decrease_queue)。

库存服务作为消息消费者,订阅inventory_decrease_queue。当有新的“扣减库存”消息到达时,库存服务从队列中取出消息,执行相应的库存扣减操作,并更新数据库。

接口解耦:订单服务仅需知道如何将扣减库存请求包装成符合约定的消息格式,发送到指定的RabbitMQ队列。库存服务的接口细节对订单服务透明,库存服务内部的接口变动不影响订单服务。

使用mq之后,我们只需要依赖于mq,避免了各个子系统间的强依赖问题。

2.异步

例如:系统接收一个请求,需要在自己本地写库,还需要在 BCD 三个系统写库,自己本地写库要 3ms,BCD 三个系统分别写库要 300ms、450ms、200ms。最终请求总延时是 3 + 300 +
450 + 200 = 953ms,接近 1s,用户感觉搞个什么东西,慢死了慢死了。用户通过浏览器发起请
求。如果使 MQ,那么 A 系统连续发送 3 条消息到 MQ 队列中,假如耗时 5ms,A 系统从接受一
个请求到返回响应给用户,总时长是 3 + 5 = 8ms。

生产者在完成本职工作后,就能直接返回结果了。而无需等待消息消费者的返回,它们最终会独立完成所有的业务功能。

3削峰

如果发生流量突增,一时间所有的请求都到数据库,可能会导致数据库无法承受这么大的压力,响应变慢或者直接挂掉。

使用RabbitMQ后:订单系统接收到用户请求之后,将请求直接发送到mq,然后订单消费者从mq中消费消息,做写库操作。如果出现请求峰值的情况,由于消费者的消费能力有限,会按照自己的节奏来消费消息,多的请求不处理,保留在mq的队列中,不会对系统的稳定性造成影响。

136.rabbitmq 有哪些重要的角色?

1.生产者(Producer)

生产者是消息的创建者,负责生成并发送消息到RabbitMQ服务器。它们通常代表应用程序中的某个组件,该组件需要向其他服务或系统发送数据。

生产者将消息推送到指定的交换机(Exchange),并可选择性地附带一个路由键(Routing Key),以便交换机根据路由规则将消息投递到正确的队列。

1、生产者和Broker建立TCP连接。

2、生产者和Broker建立通道。

3、生产者通过通道消息发送给Broker,由Exchange将消息进行转发。

4、Exchange将消息转发到指定的Queue(队列)

2.消费者(Consumer)

消费者是消息的接收方和处理器,负责从RabbitMQ服务器接收并处理消息。它们代表应用程序中等待处理消息的组件。

消费者订阅(绑定)到特定的队列,当队列中有新消息时,消费者会接收到消息并进行相应的业务逻辑处理。处理完成后,消费者可以选择确认(acknowledge)消息,通知RabbitMQ消息已被成功处理,可以安全地从队列中移除。

1、消费者和Broker建立TCP连接

2、消费者和Broker建立通道

3、消费者监听指定的Queue(队列)

4、当有消息到达Queue时Broker默认将消息推送给消费者。

5、消费者接收到消息。

6、ack回复

3.代理(Broker)或消息代理(Message Broker)

代理即RabbitMQ服务器本身(消息队列服务进程),它扮演着消息传递的中介角色,负责接收、存储、路由和分发消息,此进程包括两个部分:Exchange与Queue

4.交换机(Exchange)

交换机是消息路由的核心组件,它接收生产者发送的消息,并根据预定义的路由规则将消息投递到一个或多个队列。

5.队列(Queue)

队列是消息的临时存储容器,它保存了待处理的消息。每个队列都有一个唯一的名称,且消息只能被消费一次(除非使用特殊特性如死信队列或消息复制)。

6.用户角色(User Roles)

在RabbitMQ的管理和运维层面,存在不同的用户角色,如超级管理员(administrator)、监控者(monitoring)、策略制定者(policymaker)、普通管理者(management)等,用于控制用户对RabbitMQ服务器的访问权限和操作能力。

137.rabbitmq 有哪些重要的组件?

1.连接管理器(ConnectionFactory)

功能:在客户端代码中创建并管理与RabbitMQ服务器建立连接的工厂类,封装了创建连接(Connection)所需的配置信息和连接过程。

行为:应用程序通过配置ConnectionFactory(如设置主机地址、端口、用户名、密码、虚拟主机等)来初始化连接,并使用它来创建实际的连接实例。

2.信道(Channel)

功能:在客户端代码中,代表与RabbitMQ服务器通信的逻辑通道,用于发布、订阅和管理消息。

行为:应用程序在一个TCP连接上可以创建多个Channel,每个Channel用于独立的AMQP操作序列。生产者和消费者通过Channel发布消息、订阅队列、声明交换机和队列、设置消息属性等。

3.RoutingKey(路由键)

功能:在客户端代码中,作为消息属性的一部分,由生产者设置,用于指示消息如何从交换机路由到队列。

行为:在发送消息时,由客户端应用程序指定,与交换机类型和绑定规则配合,决定消息应被投递到哪个(或哪些)队列。

4.BindKey(绑定键)

功能:在客户端代码中,用于声明和操作RabbitMQ服务器上的绑定关系,将交换机与队列通过特定规则连接起来。

行为:客户端应用程序通过Channel使用BindKey来创建或移除绑定,将交换机与队列关联起来,指定路由键匹配规则(如果适用),使交换机能将匹配的消息投递到对应的队列。

5.Queue(队列)

功能:在客户端代码中,用于声明和操作RabbitMQ服务器上的队列,包括创建、删除、查询队列属性等。

行为:客户端应用程序通过Channel声明(或删除)队列,设置其名称、持久性、自动删除等属性,以及获取队列的状态信息,如消息数量等。

6.Exchange(交换机)

功能:在客户端代码中,用于声明和操作RabbitMQ服务器上的交换机,定义消息路由规则。

行为:客户端应用程序通过Channel声明(或删除)交换机,设置其类型(如direct、topic、fanout等)和属性,以及进行绑定和解绑操作。

138.rabbitmq 中 vhost 的作用是什么?

vhost(Virtual Host) 是 RabbitMQ 中的一个重要概念,用于在逻辑上将消息系统划分为多个独立的、隔离的运行环境,用于实现资源隔离、权限控制和逻辑分组,每个 vhost 可以看作是一个迷你版的 RabbitMQ 服务器,它为在单一 RabbitMQ 服务器上托管多个独立、安全的消息处理环境提供了基础结构支持。

RabbitMQ 默认提供一个名为 / 的 vhost,对于简单应用场景,可以直接使用这个默认 vhost。如果需要更多的隔离和管理粒度,可以创建自定义的 vhost,并为每个 vhost 配置相应的用户和权限。

139.rabbitmq 的消息是怎么发送的?

  1. 定义RabbitMQ服务器的连接参数,包括主机地址、端口、用户名、密码。
  2. 使用ConnectionFactory创建一个连接实例。
  3. 使用Connection.createChannel()方法创建一个信道(Channel)。
  4. 声明(或创建)名为my_queue的队列,并设置其为持久化(即使RabbitMQ重启,队列也会保留)。
  5. 准备要发送的消息内容(这里是字符串Hello, RabbitMQ!)。
  6. 使用Channel.basicPublish()方法将消息发送到队列。这里直接指定队列名作为路由键,消息将直接发送到名为my_queue的队列,而不是通过交换机路由。
  7. 打印发送的消息内容,表明发送操作已完成。
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

public class RabbitMQProducer {
    private static final String QUEUE_NAME = "my_queue";

    public static void main(String[] args) throws Exception {
        // 连接参数
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");  // RabbitMQ服务器地址,这里使用本地主机
        factory.setUsername("guest");
        factory.setPassword("guest");
        factory.setPort(5672);

        // 建立连接
        try (Connection connection = factory.newConnection()) {
            // 创建一个频道(Channel)
            Channel channel = connection.createChannel();


        // 参数1:queue队列名称,不存在会自动创建
        // 参数2:durable队列是否持久化(重启RabbitMQ后队列是否还存在),
                如果开启队列会存在,但是队列中的消息仍会丢失(可在发布消息时的额外设置进行解决)
        // 参数3:exclusive是否独占队列
        // 参数4:autoDelete否在消费完成后自动删除队列
        // 参数5:arquments额外附加参数
        //要保证生产者和消费者队列的参数一致

            channel.queueDeclare(QUEUE_NAME, true, false, false, null);

            // 准备要发送的消息内容
            String messageBody = "Hello, RabbitMQ!";

            // 发送消息到队列
        /** 发布消息
         参数1:exchange交换机名称
         参数2:routingKey队列名称
         参数3:props传递消息额外设置,
         如果为MessageProperties.PERSISTENT_TEXT_PLAIN:重启RabbitMQ后消息仍会存在
         参数4:body消息的具体内容,是一个byte类型的数组
         **/
            channel.basicPublish("", QUEUE_NAME, null, messageBody.getBytes());

            System.out.println("Sent message: " + messageBody);
        }
    }
}

140.rabbitmq 怎么保证消息的稳定性?

141.rabbitmq 怎么避免消息丢失?

142.要保证消息持久化成功的条件有哪些?

143.rabbitmq 持久化有什么缺点?

144.rabbitmq 有几种广播类型?

145.rabbitmq 怎么实现延迟消息队列?

146.rabbitmq 集群有什么用?

为了提高消息系统的可用性、扩展性和容错能力而设计的部署模式。

1.普通集群

RabbitMQ代理操作所需的所有数据/状态都将跨所有节点复制。

2.镜像集群

镜像队列机制就是将队列在三个节点之间设置主从关系,消息会在三个节点之间进行自动同步,且如果其中一个节点不可用,并不会导致消息丢失或服务不可用的情况,提升MQ集群的整体高可用性。

147.rabbitmq 节点的类型有哪些?

148.rabbitmq 集群搭建需要注意哪些问题?

149.rabbitmq 每个节点是其他节点的完整拷贝吗?为什么?

150.rabbitmq 集群中唯一一个磁盘节点崩溃了会发生什么情况?

151.rabbitmq 对集群节点停止顺序有要求吗?

有的。

1.停止内存节点 (Memory Nodes)

这些节点不存储任何持久化数据,仅作为集群中的计算资源或临时消息存储使用。由于它们不持有持久化数据,因此可以首先安全地关闭。

 2.停止磁盘节点 (Disk Nodes)

在所有内存节点停止后,接着停止磁盘节点。磁盘节点存储了持久化消息、交换机、队列等重要数据。按照正确顺序关闭磁盘节点,有助于确保集群数据的一致性和完整性。

3.停止仲裁节点(Quorum Queues Arbiters)

存在仲裁节点的话最后停止仲裁节点这些节点不存储队列数据,但参与仲裁决策。在停止这类节点前,应确保相关队列的复制已经完成,且仲裁功能可以由其他存活的仲裁节点接管。

十五、Kafka

152.kafka 可以脱离 zookeeper 单独使用吗?为什么?

153.kafka 有几种数据保留的策略?

154.kafka 同时设置了 7 天和 10G 清除数据,到第五天的时候消息达到了 10G,这个时候 kafka 将如何处理?

155.什么情况会导致 kafka 运行变慢?

156.使用 kafka 集群需要注意什么?

十六、Zookeeper

157.zookeeper 是什么?

ZooKeeper 是 Apache 公司的一个开源的分布式协调服务。ZooKeeper 为分布式应用提供了高效且可靠的分布式协调服务,提供了诸如统一命名服务、配置管理和分布式锁等分布式的基础服务。

它的作用主要是用来维护和监控存储数据的状态变化。通过监控这些数据状态的变化,从而可以达到基于数据的集群管理。

158.zookeeper 都有哪些功能?

1.集群管理:ZooKeeper可以帮助管理分布式系统中的机器集群,包括机器的加入、离开以及故障检测等。它还可以用于选举主节点,例如在主从复制架构中,当主节点宕机时,ZooKeeper可以帮助选举出新的主节点。

2.领导选举:ZooKeeper提供了领导选举机制,这在分布式系统中非常有用,特别是当需要有一个节点作为领导者来协调其他节点时。ZooKeeper能够确保在任何时刻只有一个领导者存在,从而避免冲突和不一致。

3.高可用性和可靠性:ZooKeeper的设计考虑了高可用性和可靠性。它通过复制数据到多个服务器来确保数据的持久性和一致性,并且只要集群中的大部分服务器可用,ZooKeeper就能继续提供服务。

4.数据发布/订阅:ZooKeeper允许客户端订阅特定节点的数据变更通知。当这些节点的数据发生变化时,ZooKeeper会通知所有订阅了该节点的客户端。

5.分布式锁:ZooKeeper提供了分布式锁的实现,允许在分布式系统中协调对共享资源的访问。这可以防止多个进程或线程同时修改同一资源,从而确保数据的一致性和完整性。

6.配置管理:ZooKeeper允许你存储和管理应用程序配置信息,例如数据库连接字符串等。这些配置信息可以在ZooKeeper中动态更新,并且客户端能够监听到这些变更,从而实时加载新的配置,无需重启服务。

7.命名服务:ZooKeeper可以作为一个分布式命名服务,为分布式系统中的各种资源和服务提供唯一的命名。客户端可以通过这些名称来查找和访问相应的资源或服务。

159.zookeeper 有几种部署模式?

1.单机模式

这是ZooKeeper最简单的部署模式,主要用于开发和测试环境。在单机模式下,ZooKeeper只运行在一个服务器上,不具备数据备份和容错能力。因此,如果服务器宕机,整个ZooKeeper服务将停止工作,所以这种模式并不适合生产环境。

2.集群模式

这是ZooKeeper在生产环境中最常见的部署方式。集群模式通常包含多台服务器(一般为奇数台,如3、5、7等),其中一些作为主服务器处理客户端请求,其他服务器作为从服务器进行数据备份和提供容错能力。如果某一台主服务器出现故障,系统将会自动选举出新的主服务器继续提供服务。这种模式支持扩展和缩小服务器节点数目,以满足业务需求。

3.伪分布式模式

在这种模式下,虽然只有一台物理机器,但在这台机器上运行了多个ZooKeeper实例。伪分布式模式主要用于本地开发和测试,可以模拟集群环境来验证各种功能。

4.云服务模式

随着云计算技术的快速发展,越来越多的企业选择将ZooKeeper集成到云服务中。这种模式可以提供高可用、高性能、可扩展和安全的ZooKeeper服务,方便企业在云端进行分布式应用的开发和部署。

160.zookeeper 怎么保证主从节点的状态同步?

161.集群中为什么要有主节点?

162.集群中有 3 台服务器,其中一个节点宕机,这个时候 zookeeper 还可以使用吗?

在ZooKeeper集群中,只要超过半数的节点(称为法定人数或quorum)是活跃的,集群就可以继续提供服务。因此,在一个包含3个节点的集群中,即使有一个节点宕机,剩余的两个节点仍然可以形成一个法定人数,维持集群的正常运行。

当一个节点宕机时,ZooKeeper集群会进行以下操作:

领导者重新选举:如果宕机的节点是当前的领导者(Leader),那么集群会触发一个新的领导者选举过程。剩余的跟随者(Follower)节点会参与选举,通过投票机制选举出一个新的领导者。这个选举过程通常很快,并且不会影响客户端对ZooKeeper服务的访问。

数据同步:新的领导者被选举出来后,它会与集群中其他活跃的节点进行数据同步,确保所有节点上的数据保持一致。这个过程是ZooKeeper的容错机制的一部分,确保数据的一致性和可靠性。

163.说一下 zookeeper 的通知机制?

Zookeeper的通知机制基于事件驱动模型,当Zookeeper集群中的某个节点发生变化(例如数据被修改或添加)时,这些变化将被广播到集群中的所有其他节点。这种通知机制是通过发布/订阅模式实现的,即一个或多个订阅者可以接收这些通知。

Zookeeper使用发布者/订阅者模型来实现其通知机制,该模型基于XML格式的消息协议。消息内容包括更改数据的时间戳、变更前后的值以及相关的节点路径。通过这种机制,订阅者可以知道其他节点数据发生了变化,从而进行相应的同步操作。

十七、MySql

164.数据库的三范式是什么?

165.一张自增表里面总共有 7 条数据,删除了最后 2 条数据,重启 mysql 数据库,又插入了一条数据,此时 id 是几?

166.如何获取当前数据库版本?

167.说一下 ACID 是什么?

168.char 和 varchar 的区别是什么?

169.float 和 double 的区别是什么?

170.mysql 的内连接、左连接、右连接有什么区别?

171.mysql 索引是怎么实现的?

172.怎么验证 mysql 的索引是否满足需求?

173.说一下数据库的事务隔离?

174.说一下 mysql 常用的引擎?

175.说一下 mysql 的行锁和表锁?

176.说一下乐观锁和悲观锁?

177.mysql 问题排查都有哪些手段?

178.如何做 mysql 的性能优化?

十八、Redis

179.redis 是什么?都有哪些使用场景?

180.redis 有哪些功能?

181.redis 和 memecache 有什么区别?

182.redis 为什么是单线程的?

183.什么是缓存穿透?怎么解决?

184.redis 支持的数据类型有哪些?

185.redis 支持的 java 客户端都有哪些?

186.jedis 和 redisson 有哪些区别?

187.怎么保证缓存和数据库数据的一致性?

188.redis 持久化有几种方式?

189.redis 怎么实现分布式锁?

190.redis 分布式锁有什么缺陷?

191.redis 如何做内存优化?

192.redis 淘汰策略有哪些?

193.redis 常见的性能问题有哪些?该如何解决?

十九、JVM

194.说一下 jvm 的主要组成部分?及其作用?

195.说一下 jvm 运行时数据区?

196.说一下堆栈的区别?

197.队列和栈是什么?有什么区别?

198.什么是双亲委派模型?

199.说一下类加载的执行过程?

200.怎么判断对象是否可以被回收?

201.java 中都有哪些引用类型?

202.说一下 jvm 有哪些垃圾回收算法?

203.说一下 jvm 有哪些垃圾回收器?

204.详细介绍一下 CMS 垃圾回收器?

205.新生代垃圾回收器和老生代垃圾回收器都有哪些?有什么区别?

206.简述分代垃圾回收器是怎么工作的?

207.说一下 jvm 调优的工具?

208.常用的 jvm 调优的参数都有哪些?

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不柔情

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值