前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >京东后端实习一面,凉凉。。

京东后端实习一面,凉凉。。

作者头像
沉默王二
发布2024-03-25 10:57:08
1220
发布2024-03-25 10:57:08
举报
文章被收录于专栏:沉默王二沉默王二

大家好,我是二哥呀。

今天继续给大家带来硬核面经,这次我们以《Java 面试指南》中同学 10 的 京东后端实习一面(已挂)为例,来看看如果你在面试中遇到这些面试题的话,该如何回答?

一共 18 道题,基本上围绕着二哥一直给大家强调的 Java 后端四大件展开(除了 Redis 没问)。准备 25 届、26 届 Java 后端实习的小伙伴一定要注意,精力多放在这四大件上面,准没错,听劝(😂)

京东 24 届秋招薪资参考

京东实习面经

牛顿曾说过,“如果我比别人看得更远,那是因为我站在巨人的肩膀上”。因此,如果你也想冲京东的话,就一定要多看看前辈们的面经。

  • 三分恶面渣逆袭在线版:https://javabetter.cn/sidebar/sanfene/nixi.html
  • 三分恶面渣逆袭 PDF 版:https://t.zsxq.com/04FuZrRVf

先来看技术一面的题目大纲(围绕 Java 后端四大件展开):

  • ArrayList 和 LinkedList 的时间复杂度
  • HashSet 和 ArrayList 的区别
  • HashSet 怎么判断元素重复,重复了是否 put
  • hashcode 和 equals 方法只重写一个行不行,只重写 equals 没重写 hashcode,map put 的时候会发生什么
  • 有了解 JVM 吗
  • 堆和栈的区别是什么
  • 垃圾回收器的作用是什么
  • 什么是内存泄露
  • Java 编译时异常和运行时异常的区别
  • return 先执行还是 finally 先执行
  • 事务的四个特性,怎么理解事务一致性
  • JDBC 的执行步骤
  • 创建连接拿到的是什么对象
  • statement 和 preparedstatement 的区别
  • select 语句的执行顺序
  • Spring 事务怎么实现的
  • 事务的传播机制
  • 查询和更新都频繁的字段是否适合创建索引,为什么
  • 联合索引 abc,a=1,c=1/b=1,c=1/a=1,c=1,b=1走不走索引

内容较长,撰写硬核面经不容易,建议大家先收藏起来,面试的时候大概率会碰到,我会尽量用通俗易懂+手绘图的方式,让你能背会的同时,还能理解和掌握,总之:让天下没有难背的八股 😂

01、ArrayList 和 LinkedList 的时间复杂度

①、由于 ArrayList 是基于数组实现的,所以 get(int index) 可以直接通过数组下标获取,时间复杂度是 O(1);LinkedList 是基于链表实现的,get(int index) 需要遍历链表,时间复杂度是 O(n)。

当然,get(E element) 这种查找,两种集合都需要遍历通过 equals 比较获取元素,所以时间复杂度都是 O(n)。

②、ArrayList 如果增删的是数组的尾部,直接插入或者删除就可以了,时间复杂度是 O(1);如果 add 的时候涉及到扩容,时间复杂度会提升到 O(n)。

但如果插入的是中间的位置,就需要把插入位置后的元素向前或者向后移动,甚至还有可能触发扩容,效率就会低很多,O(n)。

LinkedList 因为是链表结构,插入和删除只需要改变前置节点、后置节点和插入节点的引用就行了,不需要移动元素。

如果是在链表的头部插入或者删除,时间复杂度是 O(1);如果是在链表的中间插入或者删除,时间复杂度是 O(n),因为需要遍历链表找到插入位置;如果是在链表的尾部插入或者删除,时间复杂度是 O(1)。

三分恶面渣逆袭:ArrayList和LinkedList中间插入

三分恶面渣逆袭:ArrayList和LinkedList中间删除

注意,这里有个陷阱,LinkedList 更利于增删不是体现在时间复杂度上,因为二者增删的时间复杂度都是 O(n),都需要遍历列表;而是体现在增删的效率上,因为 LinkedList 的增删只需要改变引用,而 ArrayList 的增删可能需要移动元素。

02、HashSet 和 ArrayList 的区别

  • ArrayList 是基于动态数组实现的,HashSet 是基于 HashMap 实现的。
  • ArrayList 允许重复元素和 null 值,可以有多个相同的元素;HashSet 保证每个元素唯一,不允许重复元素,基于元素的 hashCode 和 equals 方法来确定元素的唯一性。
  • ArrayList 保持元素的插入顺序,可以通过索引访问元素;HashSet 不保证元素的顺序,元素的存储顺序依赖于哈希算法,并且可能随着元素的添加或删除而改变。

03、HashSet 怎么判断元素重复,重复了是否 put

HashSet 的 add 方法是通过调用 HashMap 的 put 方法实现的:

代码语言:javascript
复制
public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}

所以 HashSet 判断元素重复的逻辑底层依然是 HashMap 的底层逻辑:

三分恶面渣逆袭:HashMap插入数据流程图

HashMap 在插入元素时,通常需要三步:

第一步,通过 hash 方法计算 key 的哈希值。

代码语言:javascript
复制
static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

第二步,数组进行第一次扩容。

代码语言:javascript
复制
if ((tab = table) == null || (n = tab.length) == 0)
    n = (tab = resize()).length;

第三步,根据哈希值计算 key 在数组中的下标,如果对应下标正好没有存放数据,则直接插入。

代码语言:javascript
复制
if ((p = tab[i = (n - 1) & hash]) == null)
    tab[i] = newNode(hash, key, value, null);

如果对应下标已经有数据了,就需要判断是否为相同的 key,是则覆盖 value,否则需要判断是否为树节点,是则向树中插入节点,否则向链表中插入数据。

代码语言:javascript
复制
else {
    Node<K,V> e; K k;
    if (p.hash == hash &&
        ((k = p.key) == key || (key != null && key.equals(k))))
        e = p;
    else if (p instanceof TreeNode)
        e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
    else {
        for (int binCount = 0; ; ++binCount) {
            if ((e = p.next) == null) {
                p.next = newNode(hash, key, value, null);
                if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                    treeifyBin(tab, hash);
                break;
            }
            if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k))))
                break;
            p = e;
        }
    }
}

也就是说,HashSet 通过元素的哈希值来判断元素是否重复,如果重复了,会覆盖原来的值。

代码语言:javascript
复制
if (e != null) { // existing mapping for key
    V oldValue = e.value;
    if (!onlyIfAbsent || oldValue == null)
        e.value = value;
    afterNodeAccess(e);
    return oldValue;
}   

04、hashcode 和 equals 方法只重写一个行不行,只重写 equals 没重写 hashcode,map put 的时候会发生什么

什么是 hashCode 方法?

hashCode() 方法的作⽤是获取哈希码,它会返回⼀个 int 整数,定义在 Object 类中, 是一个本地⽅法。

代码语言:javascript
复制
public native int hashCode();
为什么要有 hashCode 方法?

hashCode 方法主要用来获取对象的哈希码,哈希码是由对象的内存地址或者对象的属性计算出来的,它是⼀个 int 类型的整数,通常是不会重复的,因此可以用来作为键值对的建,以提高查询效率。

例如 HashMap 中的 key 就是通过 hashCode 来实现的,通过调用 hashCode 方法获取键的哈希码,并将其与右移 16 位的哈希码进行异或运算。

代码语言:javascript
复制
static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
为什么重写 quals 时必须重写 hashCode ⽅法?

维护 equals()hashCode()之间的一致性是至关重要的,因为基于哈希的集合类(如 HashSet、HashMap、Hashtable 等)依赖于这一点来正确存储和检索对象。

具体地说,这些集合通过对象的哈希码将其存储在不同的“桶”中(底层数据结构是数组,哈希码用来确定下标),当查找对象时,它们使用哈希码确定在哪个桶中搜索,然后通过 equals()方法在桶中找到正确的对象。

如果重写了 equals()方法而没有重写 hashCode()方法,那么被认为相等的对象可能会有不同的哈希码,从而导致无法在集合中正确处理这些对象。

为什么两个对象有相同的 hashcode 值,它们也不⼀定相等?

这主要是由于哈希码(hashCode)的本质和目的所决定的。

哈希码是通过哈希函数将对象中映射成一个整数值,其主要目的是在哈希表中快速定位对象的存储位置。

由于哈希函数将一个较大的输入域映射到一个较小的输出域,不同的输入值(即不同的对象)可能会产生相同的输出值(即相同的哈希码)。

这种情况被称为哈希冲突。当两个不相等的对象发生哈希冲突时,它们会有相同的 hashCode。

为了解决哈希冲突的问题,哈希表在处理键时,不仅会比较键对象的哈希码,还会使用 equals 方法来检查键对象是否真正相等。如果两个对象的哈希码相同,但通过 equals 方法比较结果为 false,那么这两个对象就不被视为相等。

代码语言:javascript
复制
if (p.hash == hash &&
    ((k = p.key) == key || (key != null && key.equals(k))))
    e = p;
只重写 equals 没重写 hashcode,map put 的时候会发生什么?

如果只重写 equals 方法,没有重写 hashcode 方法,那么会导致 equals 相等的两个对象,hashcode 不相等,这样的话,这两个对象会被放到不同的桶中,这样就会导致 get 的时候,找不到对应的值。

05、有了解 JVM 吗

JVM,也就是 Java 虚拟机,它是 Java 实现跨平台的基石。

Java 程序运行的时候,编译器会将 Java 源代码(.java)编译成平台无关的 Java 字节码文件(.class),接下来对应平台的 JVM 会对字节码文件进行解释,翻译成对应平台的机器指令并运行。

三分恶面渣逆袭:Java语言编译运行

同时,任何可以通过 Java 编译的语言,比如说 Groovy、Kotlin、Scala 等,都可以在 JVM 上运行。

三分恶面渣逆袭:JVM跨语言

06、堆和栈的区别是什么

JVM 的内存区域可以粗暴地划分为,当然了,按照 Java 的虚拟机规范,可以再细分为程序计数器虚拟机栈本地方法栈方法区等。

三分恶面渣逆袭:Java虚拟机运行时数据区

其中方法区是线程共享区,虚拟机栈本地方法栈程序计数器是线程私有的。

Java 虚拟机栈

Java 虚拟机栈(Java Virtual Machine Stack),通常指的就是“栈”,它的生命周期与线程相同。

Java 虚拟机栈(JVM 栈)中是一个个栈帧,每个栈帧对应一个被调用的方法。当线程执行一个方法时,会创建一个对应的栈帧,并将栈帧压入栈中。当方法执行完毕后,将栈帧从栈中移除。

三分恶面渣逆袭:Java虚拟机栈

本地方法栈

本地方法栈(Native Method Stacks)与虚拟机栈相似,区别在于虚拟机栈是为虚拟机执行 Java 方法服务的,而本地方法栈是为虚拟机使用到的本地(Native)方法服务的。

Java 堆

Java 堆(Java Heap)是虚拟机所管理的内存中最大的一块,被所有线程共享,在虚拟机启动时创建。

以前,Java 中“几乎”所有的对象都会在堆中分配,但随着 JIT 编译器的发展和逃逸技术的逐渐成熟,所有的对象都分配到堆上渐渐变得不那么“绝对”了。

从 JDK 7 开始,Java 虚拟机已经默认开启逃逸分析了,意味着如果某些方法中的对象引用没有被返回或者未被外面使用(也就是未逃逸出去),那么对象可以直接在栈上分配内存。

Java 堆是垃圾收集器管理的内存区域,因此一些资料中它也被称作“GC 堆”(Garbage Collected Heap)。

从回收内存的角度来看,由于垃圾收集器大部分都是基于分代收集理论设计的,所以 Java 堆中经常会出现新生代老年代Eden空间From Survivor空间To Survivor空间等名词。

三分恶面渣逆袭:Java 堆内存结构

总结来说:堆属于线程共享的内存区域,几乎所有的对象都在对上分配,生命周期不由单个方法调用所决定,可以在方法调用结束后继续存在,直到不在被任何变量引用,然后被垃圾收集器回收。

栈就是前面提到的 JVM 栈(主要存储局部变量、方法参数、对象引用等),属于线程私有,通常随着方法调用的结束而消失,也就无需进行垃圾收集。

07、垃圾回收器的作用是什么

垃圾回收器的核心作用是自动管理Java应用程序的运行时内存。它负责识别哪些内存是不再被应用程序使用的(即“垃圾”),并释放这些内存以便重新使用。

这一过程减少了程序员手动管理内存的负担,降低了内存泄漏和溢出错误的风险。

08、什么是内存泄露

推荐阅读:一次内存溢出的排查优化实战

在 Java 中,和内存相关的问题主要有两种,内存溢出和内存泄漏。

  • 内存溢出(Out Of Memory):就是申请内存时,JVM 没有足够的内存空间。通俗说法就是去蹲坑发现坑位满了。
  • 内存泄露(Memory Leak):就是申请了内存,但是没有释放,导致内存空间浪费。通俗说法就是有人占着茅坑不拉屎。

内存泄漏是内在病源,外在病症表现可能有:

  • CPU 使用率飙升,甚至到 100%
  • 应用程序抛出 OutOfMemoryError 错误

09、Java 编译时异常和运行时异常的区别

三分恶面渣逆袭:Java异常体系

Throwable 是 Java 语言中所有错误和异常的基类。它有两个主要的子类:Error 和 Exception,这两个类分别代表了 Java 异常处理体系中的两个分支。

Error 类代表那些严重的错误,这类错误通常是程序无法处理的。比如,OutOfMemoryError 表示内存不足,StackOverflowError 表示栈溢出。这些错误通常与 JVM 的运行状态有关,一旦发生,应用程序通常无法恢复。

Exception 类代表程序可以处理的异常。它分为两大类:编译时异常(Checked Exception)和运行时异常(Runtime Exception)。

①、编译时异常(Checked Exception):这类异常在编译时必须被显式处理(捕获或声明抛出)。

如果方法可能抛出某种编译时异常,但没有捕获它(try-catch)或没有在方法声明中用 throws 子句声明它,那么编译将不会通过。例如:IOException、SQLException 等。

②、运行时异常(Runtime Exception):这类异常在运行时抛出,它们都是 RuntimeException 的子类。对于运行时异常,Java 编译器不要求必须处理它们(即不需要捕获也不需要声明抛出)。

运行时异常通常是由程序逻辑错误导致的,如 NullPointerException、IndexOutOfBoundsException 等。

10、return 先执行还是 finally 先执行

这道题通常会和实际的代码结合起来看:

代码语言:javascript
复制
public class TryDemo {
    public static void main(String[] args) {
        System.out.println(test());
    }
    public static int test() {
        try {
            return 1;
        } catch (Exception e) {
            return 2;
        } finally {
            System.out.print("3");
        }
    }
}

test()方法中,首先有一个try块,接着是一个catch块(用于捕获异常),最后是一个finally块(无论是否捕获到异常,finally块总会执行)。

①、try块中包含一条return 1;语句。正常情况下,如果try块中的代码能够顺利执行,那么方法将返回数字1。在这个例子中,try块中没有任何可能抛出异常的操作,因此它会正常执行完毕,并准备返回1

②、由于try块中没有异常发生,所以catch块中的代码不会执行。

③、无论前面的代码是否发生异常,finally块总是会执行。在这个例子中,finally块包含一条System.out.print("3");语句,意味着在方法结束前,会在控制台打印出3

当执行main方法时,控制台的输出将会是:

代码语言:javascript
复制
31

这是因为finally块确保了它包含的System.out.print("3");会执行并打印3,随后test()方法返回try块中的值1,最终结果就是31

11、事务的四个特性,怎么理解事务一致性

三分恶面渣逆袭:事务四大特性

原子性:

原子性子性意味着事务中的所有操作要么全部完成,要么全部不完成,它是不可分割的单位。如果事务中的任何一个操作失败了,整个事务都会回滚到事务开始之前的状态,如同这些操作从未被执行过一样。

一致性:

一致性确保事务从一个一致的状态转换到另一个一致的状态。

比如在银行转账事务中,无论发生什么,转账前后两个账户的总金额应保持不变。假如 A 账户(100 块)给 B 账户(10 块)转了 10 块钱,不管成功与否,A 和 B 的总金额都是 110 块。

隔离性:

隔离性意味着并发执行的事务是彼此隔离的,一个事务的执行不会被其他事务干扰。就是事务之间是井水不犯河水的。

隔离性主要是为了解决事务并发执行时可能出现的问题,如脏读、不可重复读、幻读等。

数据库系统通过事务隔离级别(如读未提交、读已提交、可重复读、串行化)来实现事务的隔离性。

持久性:

持久性确保事务一旦提交,它对数据库所做的更改就是永久性的,即使发生系统崩溃,数据库也能恢复到最近一次提交的状态。通常,持久性是通过数据库的恢复和日志机制来实现的,确保提交的事务更改不会丢失。

12、JDBC 的执行步骤

Java 数据库连接(JDBC)是一个用于执行 SQL 语句的 Java API,它为多种关系数据库提供了统一访问的机制。使用 JDBC 操作数据库通常涉及以下步骤:

1. 加载数据库驱动

在与数据库建立连接之前,首先需要通过Class.forName()方法加载对应的数据库驱动。这一步确保 JDBC 驱动注册到了DriverManager类中。

代码语言:javascript
复制
Class.forName("com.mysql.cj.jdbc.Driver");
2. 建立数据库连接

使用DriverManager.getConnection()方法建立到数据库的连接。这一步需要提供数据库 URL、用户名和密码作为参数。

代码语言:javascript
复制
Connection conn = DriverManager.getConnection(
    "jdbc:mysql://localhost:3306/databaseName", "username", "password");
3. 创建Statement对象

通过建立的数据库连接对象Connection创建StatementPreparedStatementCallableStatement对象,用于执行 SQL 语句。

代码语言:javascript
复制
Statement stmt = conn.createStatement();

或者创建PreparedStatement对象(预编译 SQL 语句,适用于带参数的 SQL):

代码语言:javascript
复制
PreparedStatement pstmt = conn.prepareStatement("SELECT * FROM tableName WHERE column = ?");
pstmt.setString(1, "value");
4. 执行 SQL 语句

使用StatementPreparedStatement对象执行 SQL 语句。

执行查询(SELECT)语句时,使用executeQuery()方法,它返回ResultSet对象;

执行更新(INSERT、UPDATE、DELETE)语句时,使用executeUpdate()方法,它返回一个整数表示受影响的行数。

代码语言:javascript
复制
ResultSet rs = stmt.executeQuery("SELECT * FROM tableName");

代码语言:javascript
复制
int affectedRows = stmt.executeUpdate("UPDATE tableName SET column = 'value' WHERE condition");
5. 处理结果集

如果执行的是查询操作,需要处理ResultSet对象来获取数据。

代码语言:javascript
复制
while (rs.next()) {
    String data = rs.getString("columnName");
    // 处理每一行数据
}
6. 关闭资源

最后,需要依次关闭ResultSetStatementConnection等资源,释放数据库连接等资源。

代码语言:javascript
复制
if (rs != null) rs.close();
if (stmt != null) stmt.close();
if (conn != null) conn.close();
总结

使用 JDBC 操作数据库的过程包括加载驱动、建立连接、创建执行语句、执行 SQL 语句、处理结果集和关闭资源。

在 Java 开发中,通常会使用 JDBC 模板库(如 Spring 的 JdbcTemplate)或 ORM 框架(如 Hibernate、MyBatis、MyBatis-Plus)来简化数据库操作和资源管理。

13、创建连接拿到的是什么对象

在 JDBC 的执行步骤中,创建连接后拿到的对象是java.sql.Connection对象。这个对象是 JDBC API 中用于表示数据库连接的接口,它提供了执行 SQL 语句、管理事务等一系列操作的方法。

Connection对象代表了应用程序和数据库的一个连接会话。

通过调用DriverManager.getConnection()方法并传入数据库的 URL、用户名和密码等信息来获得这个对象。

一旦获得Connection对象,就可以使用它来创建执行 SQL 语句的StatementPreparedStatementCallableStatement对象,以及管理事务等。

14、statement 和 preparedstatement 的区别

StatementPreparedStatement都是用于执行 SQL 语句的接口,但它们之间存在几个关键的区别:

1. 预编译

①、Statement:每次执行Statement对象的executeQueryexecuteUpdate方法时,SQL 语句在数据库端都需要重新编译和执行。这适用于一次性执行的 SQL 语句。

②、PreparedStatement:代表预编译的 SQL 语句的对象。这意味着 SQL 语句在PreparedStatement对象创建时就被发送到数据库进行预编译。

之后,可以通过设置参数值来多次高效地执行这个 SQL 语句。这不仅减少了数据库编译 SQL 语句的开销,也提高了性能,尤其是对于重复执行的 SQL 操作。

2. 参数化查询
  • Statement:不支持参数化查询。如果需要在 SQL 语句中插入变量,通常需要通过字符串拼接的方式来实现,这会增加 SQL 注入攻击的风险。
  • PreparedStatement:支持参数化查询,即可以在 SQL 语句中使用问号(?)作为参数占位符。通过setXxx方法(如setStringsetInt)设置参数,可以有效防止 SQL 注入。

总的来说,PreparedStatement相比Statement有着更好的性能和更高的安全性,是执行 SQL 语句的首选方式,尤其是在处理含有用户输入的动态查询时。

15、Spring 事务怎么实现的

在 Spring 中,事务管理可以分为两大类:声明式事务管理和编程式事务管理。

三分恶面渣逆袭:Spring事务分类

编程式事务管理

编程式事务可以使用 TransactionTemplate 和 PlatformTransactionManager 来实现,需要显式执行事务。允许我们在代码中直接控制事务的边界,通过编程方式明确指定事务的开始、提交和回滚。

代码语言:javascript
复制
public class AccountService {
    private TransactionTemplate transactionTemplate;

    public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
        this.transactionTemplate = transactionTemplate;
    }

    public void transfer(final String out, final String in, final Double money) {
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus status) {
                // 转出
                accountDao.outMoney(out, money);
                // 转入
                accountDao.inMoney(in, money);
            }
        });
    }
}

在上面的代码中,我们使用了 TransactionTemplate 来实现编程式事务,通过 execute 方法来执行事务,这样就可以在方法内部实现事务的控制。

声明式事务管理

声明式事务是建立在 AOP 之上的。其本质是通过 AOP 功能,对方法前后进行拦截,将事务处理的功能编织到拦截的方法中,也就是在目标方法开始之前启动一个事务,在目标方法执行完之后根据执行情况提交或者回滚事务。

相比较编程式事务,优点是不需要在业务逻辑代码中掺杂事务管理的代码, Spring 推荐通过 @Transactional 注解的方式来实现声明式事务管理,也是日常开发中最常用的。

不足的地方是,声明式事务管理最细粒度只能作用到方法级别,无法像编程式事务那样可以作用到代码块级别。

代码语言:javascript
复制
@Service
public class AccountService {
    @Autowired
    private AccountDao accountDao;

    @Transactional
    public void transfer(String out, String in, Double money) {
        // 转出
        accountDao.outMoney(out, money);
        // 转入
        accountDao.inMoney(in, money);
    }
}

Spring 的声明式事务管理是通过 AOP(面向切面编程)和代理机制实现的。

第一步,在 Bean 初始化阶段创建代理对象

Spring 容器在初始化单例 Bean 的时候,会遍历所有的 BeanPostProcessor 实现类,并执行其 postProcessAfterInitialization 方法。

在执行 postProcessAfterInitialization 方法时会遍历容器中所有的切面,查找与当前 Bean 匹配的切面,这里会获取事务的属性切面,也就是 @Transactional 注解及其属性值。

然后根据得到的切面创建一个代理对象,默认使用 JDK 动态代理创建代理,如果目标类是接口,则使用 JDK 动态代理,否则使用 Cglib。

第二步,在执行目标方法时进行事务增强操作

当通过代理对象调用 Bean 方法的时候,会触发对应的 AOP 增强拦截器,声明式事务是一种环绕增强,对应接口为MethodInterceptor,事务增强对该接口的实现为TransactionInterceptor,类图如下:

图片来源网易技术专栏

事务拦截器TransactionInterceptorinvoke方法中,通过调用父类TransactionAspectSupportinvokeWithinTransaction方法进行事务处理,包括开启事务、事务提交、异常回滚等。

16、事务的传播机制

事务的传播机制定义了在方法被另一个事务方法调用时,这个方法的事务行为应该如何。

Spring 提供了一系列事务传播行为,这些传播行为定义了事务的边界和事务上下文如何在方法调用链中传播。

三分恶面渣逆袭:6种事务传播机制

  • REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。Spring 的默认传播行为。
  • SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务方式执行。
  • MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
  • REQUIRES_NEW:总是启动一个新的事务,如果当前存在事务,则将当前事务挂起。
  • NOT_SUPPORTED:总是以非事务方式执行,如果当前存在事务,则将当前事务挂起。
  • NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前事务不存在,则行为与 REQUIRED 一样。嵌套事务是一个子事务,它依赖于父事务。父事务失败时,会回滚子事务所做的所有操作。但子事务异常不一定会导致父事务的回滚。

事务传播机制是使用 ThreadLocal 实现的,所以,如果调用的方法是在新线程中的,事务传播会失效。

Spring 默认的事务传播行为是 PROPAFATION_REQUIRED,即如果多个 ServiceX#methodX() 都工作在事务环境下,且程序中存在调用链 Service1#method1()->Service2#method2()->Service3#method3(),那么这 3 个服务类的 3 个方法都通过 Spring 的事务传播机制工作在同一个事务中。

17、查询和更新都频繁的字段是否适合创建索引,为什么

频繁更新的字段,不要作为主键或者索引。

索引能提高查询效率的根本原因在于它提供了一种快速查找数据的方式,而不需要扫描整个表。B+树索引作为数据库中最常用的索引结构之一,它通过维护数据的有序性并利用树形结构实现了快速查找,将数据访问的时间复杂度从O(n)降低到了O(log n)。

当对表进行插入、删除或更新操作时,不仅要修改表中的数据,还需要同步更新索引,以保证索引的有序性和准确性。这个过程中可能涉及到的操作包括:分裂、旋转。

频繁更新的字段,尤其是更新操作导致的插入位置随机且不可预测时,建立索引的成本可能会抵消查询性能的提升,因此不推荐为这类字段建立索引。

18、联合索引 abc,a=1,c=1/b=1,c=1/a=1,c=1,b=1走不走索引

我们通过实际的 SQL 来验证一下。

示例 1(a=1,c=1):

代码语言:javascript
复制
EXPLAIN SELECT * FROM tbn WHERE A=1 AND C=1\G

key 是 idx_abc,表明 a=1,c=1 会使用联合索引。但因为缺少了 B 字段的条件,所以 MySQL 可能无法利用索引来直接定位到精确的行,而是使用索引来缩小搜索范围。

最终,MySQL 需要检查更多的行(rows: 3)来找到满足所有条件的结果集,但总体来说,使用索引明显比全表扫描要高效得多。

示例 2(b=1,c=1):

代码语言:javascript
复制
EXPLAIN SELECT * FROM tbn WHERE B=1 AND C=1\G

key 是 NULL,表明 b=1,c=1 不会使用联合索引。这是因为查询条件中涉及的字段 B 和 C 没有遵循之前定义的联合索引 idx_abc(A、B、C 顺序)的最左前缀原则。

在 idx_abc 索引中,A 是最左边的列,但是查询没有包含 A,因此 MySQL 无法利用这个索引。

示例 3(a=1,c=1,b=1):

代码语言:javascript
复制
EXPLAIN SELECT * FROM tbn WHERE A=1 AND C=1 AND B=1\G

key 是 idx_abc,表明 a=1,c=1,b=1 会使用联合索引。

并且 rows=1,因为查询条件包含了联合索引 idx_abc 中所有列的等值条件,并且条件的顺序与索引列的顺序相匹配,使得查询能够准确、快速地定位到目标数据。

参考链接

  • 1、星球嘉宾三分恶的面渣逆袭,可微信搜索三分恶关注他的公众号:https://javabetter.cn/sidebar/sanfene/nixi.html
  • 2、二哥的Java进阶之路:https://javabetter.cn
  • 3、PDF 版面渣逆袭:https://t.zsxq.com/04FuZrRVf
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2024-03-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 沉默王二 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 京东实习面经
    • 01、ArrayList 和 LinkedList 的时间复杂度
      • 02、HashSet 和 ArrayList 的区别
        • 03、HashSet 怎么判断元素重复,重复了是否 put
          • 04、hashcode 和 equals 方法只重写一个行不行,只重写 equals 没重写 hashcode,map put 的时候会发生什么
            • 什么是 hashCode 方法?
            • 为什么要有 hashCode 方法?
            • 为什么重写 quals 时必须重写 hashCode ⽅法?
            • 为什么两个对象有相同的 hashcode 值,它们也不⼀定相等?
            • 只重写 equals 没重写 hashcode,map put 的时候会发生什么?
          • 05、有了解 JVM 吗
            • 06、堆和栈的区别是什么
              • Java 虚拟机栈
              • 本地方法栈
              • Java 堆
            • 07、垃圾回收器的作用是什么
              • 08、什么是内存泄露
                • 09、Java 编译时异常和运行时异常的区别
                  • 10、return 先执行还是 finally 先执行
                    • 11、事务的四个特性,怎么理解事务一致性
                      • 原子性:
                      • 一致性:
                      • 隔离性:
                      • 持久性:
                    • 12、JDBC 的执行步骤
                      • 1. 加载数据库驱动
                      • 2. 建立数据库连接
                      • 3. 创建Statement对象
                      • 4. 执行 SQL 语句
                      • 5. 处理结果集
                      • 6. 关闭资源
                      • 总结
                    • 13、创建连接拿到的是什么对象
                      • 14、statement 和 preparedstatement 的区别
                        • 1. 预编译
                        • 2. 参数化查询
                      • 15、Spring 事务怎么实现的
                        • 编程式事务管理
                        • 声明式事务管理
                      • 16、事务的传播机制
                        • 17、查询和更新都频繁的字段是否适合创建索引,为什么
                          • 18、联合索引 abc,a=1,c=1/b=1,c=1/a=1,c=1,b=1走不走索引
                          • 参考链接
                          相关产品与服务
                          云数据库 MySQL
                          腾讯云数据库 MySQL(TencentDB for MySQL)为用户提供安全可靠,性能卓越、易于维护的企业级云数据库服务。其具备6大企业级特性,包括企业级定制内核、企业级高可用、企业级高可靠、企业级安全、企业级扩展以及企业级智能运维。通过使用腾讯云数据库 MySQL,可实现分钟级别的数据库部署、弹性扩展以及全自动化的运维管理,不仅经济实惠,而且稳定可靠,易于运维。
                          领券
                          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档