Spring和MyBatis常见面试题总结

文章目录

    • [1 Spring 基础](#1 Spring 基础)
      • [1.1 说一下你对 Spring 的理解🔥](#1.1 说一下你对 Spring 的理解🔥)
      • [1.2 Spring,Spring MVC,Spring Boot 之间什么关系?](#1.2 Spring,Spring MVC,Spring Boot 之间什么关系?)
      • [1.3 Spring 框架中用到了哪些设计模式?🔥](#1.3 Spring 框架中用到了哪些设计模式?🔥)
      • [1.4 说说自己对于 Spring MVC 了解?](#1.4 说说自己对于 Spring MVC 了解?)
      • [1.5 Spring MVC 的核心组件有哪些?](#1.5 Spring MVC 的核心组件有哪些?)
      • [1.6 SpringMVC 工作原理了解吗?🔥](#1.6 SpringMVC 工作原理了解吗?🔥)
      • [1.7 Spring 循环依赖了解吗,怎么解决?🔥](#1.7 Spring 循环依赖了解吗,怎么解决?🔥)
      • [1.8 spring 三级缓存是什么?🔥](#1.8 spring 三级缓存是什么?🔥)
      • [1.9 @Lazy 能解决多例模式下的循环依赖吗?](#1.9 @Lazy 能解决多例模式下的循环依赖吗?)
    • [2 Spring IoC 和 AOP](#2 Spring IoC 和 AOP)
      • [2.1 谈谈对于 Spring IoC 的了解🔥](#2.1 谈谈对于 Spring IoC 的了解🔥)
      • [2.2 IOC和AOP是通过什么机制来实现的?🔥](#2.2 IOC和AOP是通过什么机制来实现的?🔥)
      • [2.3 如果让你设计一个SpringIoc,你觉得会从哪些方面考虑这个设计?](#2.3 如果让你设计一个SpringIoc,你觉得会从哪些方面考虑这个设计?)
      • [2.4 谈谈自己对于 AOP 的了解🔥](#2.4 谈谈自己对于 AOP 的了解🔥)
      • [2.5 Spring AOP 和 AspectJ AOP 有什么区别?](#2.5 Spring AOP 和 AspectJ AOP 有什么区别?)
      • [2.6 AspectJ 定义的通知类型有哪些?](#2.6 AspectJ 定义的通知类型有哪些?)
      • [2.7 动态代理和静态代理的区别🔥](#2.7 动态代理和静态代理的区别🔥)
      • [2.8 什么是反射?有哪些使用场景?](#2.8 什么是反射?有哪些使用场景?)
      • [2.9 什么是 Spring Bean?将一个类声明为 Bean 的注解有哪些?](#2.9 什么是 Spring Bean?将一个类声明为 Bean 的注解有哪些?)
      • [2.10 @Component 和 @Bean 的区别是什么?](#2.10 @Component 和 @Bean 的区别是什么?)
      • [2.11 注入 Bean 的注解有哪些?](#2.11 注入 Bean 的注解有哪些?)
      • [2.12 Bean 的作用域有哪些?](#2.12 Bean 的作用域有哪些?)
      • [2.13 Bean 是线程安全的吗?🔥](#2.13 Bean 是线程安全的吗?🔥)
      • [2.14 Bean 的生命周期了解么?🔥](#2.14 Bean 的生命周期了解么?🔥)
      • [2.15 BeanFactory 和 FactoryBean 有什么区别?](#2.15 BeanFactory 和 FactoryBean 有什么区别?)
    • [3 Spring 事务](#3 Spring 事务)
      • [3.1 Spring 管理事务的方式有几种?](#3.1 Spring 管理事务的方式有几种?)
      • [3.2 Spring 中的事务是如何实现的🔥](#3.2 Spring 中的事务是如何实现的🔥)
      • [3.3 Spring 事务中哪几种事务传播行为?](#3.3 Spring 事务中哪几种事务传播行为?)
      • [3.4 @Transactional(rollbackFor = Exception.class)注解](#3.4 @Transactional(rollbackFor = Exception.class)注解)
      • [3.5 Spring 中事务失效的场景有哪些🔥](#3.5 Spring 中事务失效的场景有哪些🔥)
    • [4 SpringBoot](#4 SpringBoot)
      • [4.1 为什么使用springboot](#4.1 为什么使用springboot)
      • [4.2 Springboot 自动配置原理🔥](#4.2 Springboot 自动配置原理🔥)
      • [4.3 SpringBoot 常用注解🔥](#4.3 SpringBoot 常用注解🔥)
      • [4.4 统一异常处理怎么做?](#4.4 统一异常处理怎么做?)
      • [4.5 说几个启动器(starter)?](#4.5 说几个启动器(starter)?)
      • [4.6 写SpringBoot starter 的步骤?](#4.6 写SpringBoot starter 的步骤?)
    • [5 Spring Security](#5 Spring Security)
      • [5.1 有哪些控制请求访问权限的方法?](#5.1 有哪些控制请求访问权限的方法?)
      • [5.2 如何对密码进行加密?](#5.2 如何对密码进行加密?)
      • [5.3 如何优雅更换系统使用的加密算法?](#5.3 如何优雅更换系统使用的加密算法?)
    • [6 Mybatis](#6 Mybatis)
      • [6.1 #{} 和 \${} 的区别是什么?🔥](#{} 和 ${} 的区别是什么?🔥)
      • [6.2 与传统的 JDBC 相比,MyBatis 的优点?](#6.2 与传统的 JDBC 相比,MyBatis 的优点?)
      • [6.3 JDBC 连接数据库的步骤](#6.3 JDBC 连接数据库的步骤)
      • [6.4 项目中要用到原生的 mybatis 去查询,该怎样写?](#6.4 项目中要用到原生的 mybatis 去查询,该怎样写?)
      • [6.5 MybatisPlus 和 Mybatis 的区别?🔥](#6.5 MybatisPlus 和 Mybatis 的区别?🔥)
      • [6.6 MyBatis 运用了哪些常见的设计模式?](#6.6 MyBatis 运用了哪些常见的设计模式?)
      • [6.7 Dao 接口的工作原理是什么?](#6.7 Dao 接口的工作原理是什么?)
      • [6.8 MyBatis 都有哪些动态 sql?](#6.8 MyBatis 都有哪些动态 sql?)
      • [6.9 MyBatis 是如何将 sql 执行结果封装为目标对象并返回的?](#6.9 MyBatis 是如何将 sql 执行结果封装为目标对象并返回的?)

本文作者:夏日。主要参考:JavaGuide,同时加上网上搜索整理和个人理解总结。

1 Spring 基础

1.1 说一下你对 Spring 的理解🔥

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

  • IoC容器:Spring通过控制反转实现了对象的创建和对象间的依赖关系管理。开发者只需要定义好Bean及其依赖关系,Spring容器负责创建和组装这些对象。
  • AOP:面向切面编程,允许开发者定义横切关注点,例如事务管理、安全控制等,独立于业务逻辑的代码。通过AOP,可以将这些关注点模块化,提高代码的可维护性和可重用性。
  • 事务管理:Spring提供了一致的事务管理接口,支持声明式和编程式事务。开发者可以轻松地进行事务管理,而无需关心具体的事务API。
  • MVC框架:Spring MVC是一个基于Servlet API构建的Web框架,采用了模型-视图-控制器(MVC)架构。它支持灵活的URL到页面控制器的映射,以及多种视图技术。

1.2 Spring,Spring MVC,Spring Boot 之间什么关系?

Spring 包含了多个功能模块,其中最重要的是 Spring-Core(主要提供 IoC 依赖注入功能的支持) 模块, Spring 中的其他模块(比如 Spring MVC)的功能实现基本都需要依赖于该模块。

Spring MVC 是 Spring 中的一个很重要的模块,主要赋予 Spring 快速构建 MVC 架构的 Web 程序的能力。MVC 是模型(Model)、视图(View)、控制器(Controller)的简写,其核心思想是通过将业务逻辑、数据、显示分离来组织代码。

使用 Spring 进行开发各种配置过于麻烦比如开启某些 Spring 特性时,需要用 XML 或 Java 进行显式配置。于是,Spring Boot 诞生了!

Spring 旨在简化 J2EE 企业应用程序开发。Spring Boot 旨在简化 Spring 开发(减少配置文件,开箱即用!)。

Spring Boot 只是简化了配置,如果你需要构建 MVC 架构的 Web 程序,你还是需要使用 Spring MVC 作为 MVC 框架,只是说 Spring Boot 帮你简化了 Spring MVC 的很多配置,真正做到开箱即用!

1.3 Spring 框架中用到了哪些设计模式?🔥

  • 工厂设计模式 : Spring 使用工厂模式通过 BeanFactory 创建 bean 对象。
  • 代理设计模式 : Spring AOP 功能的实现。
  • 单例设计模式 : Spring 中的 Bean 默认都是单例的。
  • 模板方法模式 : Spring 中 jdbcTemplatehibernateTemplate 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。
  • 适配器模式 : Spring MVC 中用到了适配器模式适配 Controller
  • 包装器设计模式 : 我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。
  • 观察者模式: Spring 事件驱动模型就是观察者模式很经典的一个应用。

1.4 说说自己对于 Spring MVC 了解?

  • 视图(view): 为用户提供使用界面,与用户直接进行交互。
  • 模型(model): 代表一个存取数据的对象。模型分为两类,一类称为数据承载 Bean,一类称为业务处理 Bean。所谓数据承载 Bean 是指实体类(如:User 类),专门为用户承载业务数据的;而业务处理 Bean 则是指 Service 或 Dao 对象,专门用于处理用户提交请求的。
  • 控制器(controller): 用于将用户请求转发给相应的 Model 进行处理,并根据 Model 的计算结果向用户提供相应响应。它使视图与模型分离。

1.5 Spring MVC 的核心组件有哪些?

  • DispatcherServlet核心的中央处理器,负责接收请求、分发,并给予客户端响应。
  • HandlerMapping处理器映射器 ,根据 URL 去匹配查找能处理的 Handler(也就是我们平常说的 Controller 控制器) ,并会将请求涉及到的拦截器和 Handler 一起封装。
  • HandlerAdapter处理器适配器 ,根据 HandlerMapping 找到的 Handler ,适配执行对应的 Handler
  • Handler请求处理器,处理实际请求的处理器。
  • ViewResolver视图解析器 ,根据 Handler 返回的逻辑视图 / 视图,解析并渲染真正的视图,并传递给 DispatcherServlet 响应客户端

1.6 SpringMVC 工作原理了解吗?🔥

  1. 客户端(浏览器)发送请求, DispatcherServlet 拦截请求。
  2. DispatcherServlet 根据请求信息调用 HandlerMappingHandlerMapping 根据 URL 去匹配查找能处理的 Handler(也就是我们平常说的 Controller 控制器) 。
  3. DispatcherServlet 调用 HandlerAdapter 适配器执行 Handler
  4. Handler 完成对用户请求的处理后,会返回一个 ModelAndView 对象给 DispatcherServletView 是个逻辑上的 View。(如果是Spring Boot 返回 JSON 数据到这步就结束了)
  5. ViewResolver 会根据逻辑 View 查找实际的 View
  6. DispaterServlet 把返回的 Model 传给 View,也就是渲染视图。
  7. 最后把 View 返回给客户端

1.7 Spring 循环依赖了解吗,怎么解决?🔥

循环依赖是指两个或多个 Bean 之间相互持有对方的引用。

循环依赖问题在 Spring 中主要有三种情况:

  • 第一种:通过构造方法进行依赖注入时产生的循环依赖问题。
  • 第二种:在多例模式下产生的循环依赖问题。
  • 第三种:在单例模式下产生的循环依赖问题。

只有【第三种方式】的循环依赖问题被 Spring 解决了,其他两种方式在遇到循环依赖问题时,Spring 都会产生异常。Spring 解决单例模式下的循环依赖问题的主要方式是通过三级缓存解决循环依赖。

具体步骤如下:如果发生循环依赖的话,就去 三级缓存 中拿到三级缓存中存储的 ObjectFactory 并调用它的 getObject() 方法来获取这个循环依赖对象的早期引用,并且将这个早期引用放到二级缓存中,然后将这个早期引用注入到依赖,来解决循环依赖问题。

1.8 spring 三级缓存是什么?🔥

  1. 一级缓存(singletonObjects):存储的是已经完全初始化好的 bean,即完全准备好可以使用的 bean 实例。
  2. 二级缓存(earlySingletonObjects) :存放早期的bean引用,已经实例化但还未完全初始化,也就是三级缓存中 ObjectFactory 产生的对象,与三级缓存配合使用可以防止 AOP 的情况下,每次调用 ObjectFactory#getObject() 都会产生新的代理对象。
    1. 确保了即使在 Bean 的创建过程中有多次对早期引用的请求,也始终只返回同一个代理对象,从而避免了同一个 Bean 有多个代理对象的问题。
  3. 三级缓存(singletonFactories) :存放 ObjectFactory 对象,这些对象可以生成早期的 bean 引用。当一个 bean 正在创建过程中,如果它被其他 bean 依赖,那么这个正在创建的 bean 就会通过这个 ObjectFactory 来创建一个早期引用,从而解决循环依赖的问题。

1.9 @Lazy 能解决多例模式下的循环依赖吗?

可以解决。 @Lazy 用来标识类是否需要延迟加载,可以作用在类上、方法上、构造器上、方法参数上、成员变量中。

如果一个 Bean 被标记为延迟加载,那么它不会在 Spring IoC 容器启动时立即实例化,而是在第一次被请求时才创建。这可以帮助减少应用启动时的初始化时间,也可以用来解决循环依赖问题。

通过 @Lazy 可以解决多例模式下的循环依赖,关键点就在于对 A 中的属性 B 进行注入时,注入的是 B 的代理对象,因此不会循环依赖。之前说的发生循环依赖是因为在对 A 中的属性 B 进行注入时,注入的是 B 对象,此时又会去初始化 B 对象,发现 B 又依赖了 A,因此才导致的循环依赖。

2 Spring IoC 和 AOP

2.1 谈谈对于 Spring IoC 的了解🔥

IoC 也就是控制反转,IoC 的思想就是将原本在程序中手动创建对象的控制权,交由 Spring 框架来管理。依赖注入(DI)是实现这种思想的一种方式。传统开发过程中,我们需要通过new关键字来创建对象。使用IoC思想开发的话,我们不通过new关键字创建对象,而是通过IoC容器来获取对象。将对象之间的相互依赖关系交给 IoC 容器来管理,并由 IoC 容器完成对象的注入。通过IoC的方式,可以大大降低对象之间的耦合度。

2.2 IOC和AOP是通过什么机制来实现的?🔥

Spring IOC 实现机制

  • 反射: Spring IOC容器利用Java的反射机制动态地加载类、创建对象实例及调用对象方法,从而实现灵活的对象实例化和管理。
  • 依赖注入: IOC的核心概念是依赖注入,也就是 IOC容器负责依赖关系。Spring 提供了多种方式来进行依赖注入,比如通过构造函数注入字段注入Setter 方法注入。
  • 设计模式 - 工厂模式: IOC容器采用工厂模式来管理对象的创建和生命周期。
  • 容器实现:Spring IOC容器是实现IOC的核心,通常使用BeanFactory或ApplicationContext来管理Bean。BeanFactory是IOC容器的基本形式,提供基本的IOC功能;ApplicationContext是BeanFactory的扩展,并提供更多企业级功能。

Spring AOP 实现机制

Spring AOP的实现依赖于动态代理技术。动态代理是在运行时动态生成代理对象,而不是在编译时。它允许开发者在运行时指定要代理的接口和行为,从而实现在不修改源码的情况下增强方法的功能。

Spring AOP支持两种动态代理:

  • 基于JDK的动态代理:这种方式需要代理的类实现一个或多个接口。基于Java的反射机制和接口,代理对象在运行时实现目标类的接口,并通过反射调用目标方法。
  • 基于CGLIB的动态代理 :可以代理没有实现接口的类。CGLIB通过继承目标类并动态生成一个子类来创建代理对象。CGLIB通过使用字节码生成库(如ASM)来动态生成目标类的子类,并通过方法拦截机制来代理目标对象的方法调用。
  • 因为CGLIB通过生成字节码创建代理对象,性能通常比JDK代理稍慢,尤其是在生成代理对象的过程中会涉及较大的开销。然而,生成的代理类调用方法的速度更快。

2.3 如果让你设计一个SpringIoc,你觉得会从哪些方面考虑这个设计?

  • Bean的生命周期管理:需要设计Bean的创建、初始化、销毁等生命周期管理机制,可以考虑使用工厂模式和单例模式来实现。
  • 依赖注入:需要实现依赖注入的功能,包括属性注入、构造函数注入、Setter 方法注入等,可以考虑使用反射机制和XML配置文件来实现。
  • Bean的作用域:需要支持多种Bean作用域,比如单例、多例等,可以考虑使用Map来存储不同作用域的Bean实例。
  • AOP功能的支持:需要支持AOP功能,可以考虑使用动态代理机制和切面编程来实现。
  • 异常处理:需要考虑异常处理机制,包括Bean创建异常、依赖注入异常等,可以考虑使用try-catch机制来处理异常。
  • 配置文件加载:需要支持从不同的配置文件中加载Bean的相关信息,可以考虑使用XML、注解或者Java配置类来实现。

2.4 谈谈自己对于 AOP 的了解🔥

Spring AOP 是 Spring 框架中的一个重要模块,用于实现面向切面编程。

我们知道,Java 是一门面向对象编程的语言,在 OOP 中最小的单元就是"Class 对象",但是在 AOP 中最小的单元是"切面"。一个"切面"可以包含很多种类型和对象,对它们进行模块化管理,例如事务管理。

在面向切面编程的思想里面,把功能分为两种

  • 核心业务:登陆、注册、增、删、改、查、都叫核心业务
  • 周边功能:日志、事务管理这些次要的为周边业务

在面向切面编程中,核心业务功能和切面功能是分别独立进行开发,两者不是耦合的,然后把切面功能和核心业务功能 "编织" 在一起,这就叫 AOP。

AOP 能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来 ,便于减少系统的重复代码降低模块间的耦合度 ,并有利于未来的可拓展性和可维护性

在 AOP 中有以下几个概念:

  • AspectJ:切面,只是一个概念,没有具体的接口或类与之对应,是 Join point,Advice 和 Pointcut 的一个统称。
  • Join point:连接点,指程序执行过程中的一个点,例如方法调用、异常处理等。在 Spring AOP 中,仅支持方法级别的连接点。
  • Advice:通知,即我们定义的一个切面中的横切逻辑,有"around","before"和"after"三种类型。在很多的 AOP 实现框架中,Advice 通常作为一个拦截器,也可以包含许多个拦截器作为一条链路围绕着 Join point 进行处理。
  • Pointcut:切点,用于匹配连接点,一个 AspectJ 中包含哪些 Join point 需要由 Pointcut 进行筛选。
  • Introduction:引介,让一个切面可以声明被通知的对象实现任何他们没有真正实现的额外的接口。例如可以让一个代理对象代理两个目标类。
  • Weaving:织入,在有了连接点、切点、通知以及切面,如何将它们应用到程序中呢?没错,就是织入,在切点的引导下,将通知逻辑插入到目标方法上,使得我们的通知逻辑在方法调用时得以执行。
  • AOP proxy:AOP 代理,指在 AOP 实现框架中实现切面协议的对象。在 Spring AOP 中有两种代理,分别是 JDK 动态代理和 CGLIB 动态代理。
  • Target object:目标对象,就是被代理的对象。

Spring AOP 支持两种动态代理:

  • 基于 JDK 的动态代理:这种方式需要代理的类实现一个或多个接口。基于 Java 的反射机制和接口,代理对象在运行时实现目标类的接口,并通过反射调用目标方法。
  • 基于 CGLIB 的动态代理 :可以代理没有实现接口的类。CGLIB 通过继承目标类并动态生成一个子类来创建代理对象。CGLIB 通过使用字节码生成库(如 ASM)来动态生成目标类的子类,并通过方法拦截机制来代理目标对象的方法调用。
  • 因为 CGLIB 通过生成字节码创建代理对象,性能通常比 JDK 代理稍慢,尤其是在生成代理对象的过程中会涉及较大的开销。然而,生成的代理类调用方法的速度更快。

2.5 Spring AOP 和 AspectJ AOP 有什么区别?

Spring AOP 属于运行时增强,而 AspectJ 是编译时增强。 Spring AOP 基于代理(Proxying),而 AspectJ 基于字节码操作(Bytecode Manipulation)。Spring AOP 已经集成了 AspectJ 。

2.6 AspectJ 定义的通知类型有哪些?

  • Before(前置通知):目标对象的方法调用之前触发
  • After (后置通知):目标对象的方法调用之后触发
  • AfterReturning(返回通知):目标对象的方法调用完成,在返回结果值之后触发
  • AfterThrowing(异常通知):目标对象的方法运行中抛出 / 触发异常后触发。AfterReturning 和 AfterThrowing 两者互斥。如果方法调用成功无异常,则会有返回值;如果方法抛出了异常,则不会有返回值。
  • Around (环绕通知):编程式控制目标对象的方法调用。环绕通知是所有通知类型中可操作范围最大的一种,因为它可以直接拿到目标对象,以及要执行的方法,所以环绕通知可以任意的在目标对象的方法调用前后搞事,甚至不调用目标对象的方法

2.7 动态代理和静态代理的区别🔥

代理是一种常用的设计模式,目的是:为其他对象提供一个代理以控制对某个对象的访问,将两个类的关系解耦。代理类和委托类都要实现相同的接口,因为代理真正调用的是委托类的方法。

区别:

  • 静态代理:由程序员创建或者是由特定工具创建,在代码编译时就确定了被代理的类是一个静态代理。静态代理通常只代理一个类;
  • 动态代理:在代码运行期间,运用反射机制动态创建生成。动态代理代理的是一个接口下的多个实现类。

2.8 什么是反射?有哪些使用场景?

  1. 运行时类信息访问:反射机制允许程序在运行时获取类的完整结构信息,包括类名、包名、父类、实现的接口、构造函数、方法和字段等。
  2. 动态对象创建:可以使用反射API动态地创建对象实例,即使在编译时不知道具体的类名。这是通过Class类的newInstance()方法或Constructor对象的newInstance()方法实现的。
  3. 动态方法调用:可以在运行时动态地调用对象的方法,包括私有方法。这通过Method类的invoke()方法实现,允许你传入对象实例和参数值来执行方法。
  4. 访问和修改字段值:反射还允许程序在运行时访问和修改对象的字段值,即使是私有的。这是通过Field类的get()和set()方法完成的。
  • 控制反转 IOC:Spring 使用反射来实现其核心特性:依赖注入。通过反射找到对应的类,实例化它,并将其注入到当前类中。
  • Spring AOP:反射结合动态代理,代理对象在调用任何方法前或后都是通过反射来动态构建和执行的。

2.9 什么是 Spring Bean?将一个类声明为 Bean 的注解有哪些?

简单来说,Bean 代指的就是那些被 IoC 容器所管理的对象。

我们需要告诉 IoC 容器帮助我们管理哪些对象,可以使用 XML 文件、注解或者 Java 配置类。

  • @Controller : 对应 Spring MVC 控制层,主要用于接受用户请求并调用 Service 层返回数据给前端页面。
  • @Service : 对应服务层,主要涉及一些复杂的逻辑,需要用到 Dao 层。
  • @Repository : 对应持久层即 Dao 层,主要用于数据库相关操作。
  • @Component:通用的注解,如果一个 Bean 不知道属于哪个层,可以使用 @Component 注解标注。

2.10 @Component 和 @Bean 的区别是什么?

  • @Component 注解作用于类,而 @Bean 注解作用于方法。
  • @Component 通常是通过类路径扫描来自动装配到 Spring 容器中(可以使用 @ComponentScan 注解定义要扫描的路径)。@Bean 注解通常是我们在标有该注解的方法中定义产生这个 bean,@Bean 告诉了 Spring 这是某个类的实例,当我需要用它的时候还给我。
  • @Bean 注解比 @Component 注解的自定义性更强,而且很多地方我们只能通过 @Bean 注解来注册 bean。比如当我们引用第三方库中的类需要装配到 Spring 容器时,则只能通过 @Bean 来实现。

2.11 注入 Bean 的注解有哪些?

Spring 内置的 @Autowired 以及 JDK 内置的 @Resource@Inject 都可以用于注入 Bean。

  • @Autowired 是 Spring 提供的注解,@Resource 是 JDK 提供的注解。
  • Autowired 默认的注入方式为 byType(根据类型进行匹配),@Resource 默认注入方式为 byName(根据名称进行匹配)。
  • 当一个接口存在多个实现类的情况下,@Autowired@Resource 都需要通过名称才能正确匹配到对应的 Bean。Autowired 可以通过 @Qualifier 注解来显式指定名称,@Resource 可以通过 name 属性来显式指定名称。
  • @Autowired 支持在构造函数、方法、字段和参数上使用。@Resource 主要用于字段和方法上的注入,不支持在构造函数或参数上使用。

2.12 Bean 的作用域有哪些?

  • singleton : IoC 容器中只有唯一的 bean 实例。Spring 中的 bean 默认都是单例的,是对单例设计模式的应用。
  • prototype : 每次获取都会创建一个新的 bean 实例。也就是说,连续 getBean() 两次,得到的是不同的 Bean 实例。
  • request (仅 Web 应用可用): 每一次 HTTP 请求都会产生一个新的 bean(请求 bean),该 bean 仅在当前 HTTP request 内有效。
  • session (仅 Web 应用可用) : 每一次来自新 session 的 HTTP 请求都会产生一个新的 bean(会话 bean),该 bean 仅在当前 HTTP session 内有效。
  • application/global-session (仅 Web 应用可用):每个 Web 应用在启动时创建一个 Bean(应用 Bean),该 bean 仅在当前应用启动时间内有效。
  • websocket (仅 Web 应用可用):每一次 WebSocket 会话产生一个新的 bean。

如何配置 bean 的作用域呢?

xml 方式:

xml 复制代码
<bean id="..." class="..." scope="singleton"></bean>

注解方式:

java 复制代码
@Bean
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Person personPrototype() {
    return new Person();
}

2.13 Bean 是线程安全的吗?🔥

Spring 框架中的 Bean 是否线程安全,取决于其作用域状态

prototype 作用域下,每次获取都会创建一个新的 bean 实例,不存在资源竞争问题,所以不存在线程安全问题。

singleton 作用域下,IoC 容器中只有唯一的 bean 实例,可能会存在资源竞争问题。如果这个 bean 是有状态的话,那就存在线程安全问题(有状态 Bean 是指包含可变的成员变量的对象)。

不过,大部分 Bean 实际都是无状态(没有定义可变的成员变量)的(比如 Dao、Service),这种情况下, Bean 是线程安全的。对于单例 Bean 的线程安全问题,常见的有两种解决办法:

  1. 在 Bean 中尽量避免定义可变的成员变量。
  2. 在类中定义一个 ThreadLocal 成员变量,将需要的可变成员变量保存在 ThreadLocal 中(推荐的一种方式)。

2.14 Bean 的生命周期了解么?🔥

  1. 创建 Bean 的实例:Spring 会使用 Java 反射机制来动态创建 Bean 的实例。

  2. Bean 属性赋值 :为 Bean 设置相关属性和依赖,例如 @Autowired 注解注入的对象、@Value 注入的值。

  3. Bean 初始化

    • 如果 Bean 实现了 BeanNameAware 接口,调用 setBeanName() 方法,传入 Bean 的名字。

    • 如果 Bean 实现了 BeanClassLoaderAware 接口,调用 setBeanClassLoader() 方法,传入 ClassLoader 对象的实例。

    • 如果 Bean 实现了 BeanFactoryAware 接口,调用 setBeanFactory() 方法,传入 BeanFactory 对象的实例。

    • 与上面的类似,如果实现了其他 *.Aware 接口,就调用相应的方法设置相关依赖。

    • BeanPostProcessor 前置处理,执行前置方法。

    • 如果 Bean 实现了 InitializingBean 接口,执行 afterPropertiesSet() 方法。

    • 如果 Bean 在配置文件中的定义包含 init-method 属性,执行指定的方法。

    • BeanPostProcessor 后置处理,执行后置方法。

  4. 销毁 Bean :销毁并不是说要立马把 Bean 给销毁掉,而是把 Bean 的销毁方法先记录下来,将来需要销毁 Bean 或者销毁容器的时候,就调用这些方法去释放 Bean 所持有的资源。

    • 如果 Bean 实现了 DisposableBean 接口,执行 destroy() 方法。
    • 如果 Bean 在配置文件中的定义包含 destroy-method 属性,执行指定的 Bean 销毁方法。或者,也可以直接通过 @PreDestroy 注解标记 Bean 销毁之前执行的方法。

如何记忆呢?

  1. 整体上可以简单分为四步:实例化 ---> 属性赋值 ---> 初始化 ---> 销毁。
  2. 初始化这一步涉及到的步骤比较多,包含 Aware 接口的依赖注入、BeanPostProcessor 在初始化前后的处理以及 InitializingBeaninit-method 的初始化操作。
  3. 销毁这一步会注册相关销毁回调接口,最后通过 DisposableBeandestory-method 进行销毁。

2.15 BeanFactory 和 FactoryBean 有什么区别?

BeanFactory 是 Spring 的基础 IoC 容器接口,是整个 Spring 容器的管理者,负责管理和装配 Spring 应用中的所有 Bean。它是 Spring 的核心接口之一,定义了获取 Bean、管理 Bean 生命周期、配置 Bean 的方式。

FactoryBean 是一个更高级的接口,用于创建复杂的对象。它本质上是一个工厂模式的实现,用来生成 Bean 的实例。通过实现 FactoryBean 接口,开发者可以定制Bean的创建逻辑,而不仅仅依赖于Spring容器的默认实例化机制。

3 Spring 事务

3.1 Spring 管理事务的方式有几种?

  • 编程式事务 :在代码中硬编码(在分布式系统中推荐使用) : 通过 TransactionTemplate 或者 TransactionManager 手动管理事务,事务范围过大会出现事务未提交导致超时,因此事务要比锁的粒度更小。
  • 声明式事务 :在 XML 配置文件中配置或者直接基于注解 (单体应用或者简单业务系统推荐使用) : 实际是通过 AOP 实现(基于 @Transactional 的全注解方式使用最多)

3.2 Spring 中的事务是如何实现的🔥

spring 的事务本质就是 aop 完成的,对方法前后进行拦截,在执行方法之前开启事务,在执行完目标方法之后根据执行情况提交或者回滚事务。

3.3 Spring 事务中哪几种事务传播行为?

事务传播行为是为了解决业务层方法之间互相调用的事务问题

当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。

正确的事务传播行为可能的值如下:

  • PROPAGATION_REQUIRED:使用的最多, @Transactional 注解使用就是这个事务传播行为。如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
  • PROPAGATION_REQUIRES_NEW:不管外部方法是否开启事务都会创建一个新的事务,如果当前存在事务,则把当前事务挂起。
  • PROPAGATION_NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于 PROPAGATION_REQUIRED
  • PROPAGATION_MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。

若是错误的配置以下 3 种事务传播行为,事务将不会发生回滚:

  • PROPAGATION_SUPPORTS: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
  • PROPAGATION_NOT_SUPPORTED: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。
  • PROPAGATION_NEVER: 以非事务方式运行,如果当前存在事务,则抛出异常。

扩展:Spring 事务中的隔离级别和 MySQL 的隔离级别一样

3.4 @Transactional(rollbackFor = Exception.class)注解

@Transactional 注解默认只有在遇到 RuntimeException (运行时异常) 或者 Error 时才会回滚事务,而不会回滚 Checked Exception(受检查异常)。这是因为 Spring 认为 RuntimeException 和 Error 是不可预期的错误,而受检异常是可预期的错误,可以通过业务逻辑来处理。

如果想要修改默认的回滚策略,可以使用 @Transactional 注解的 rollbackFornoRollbackFor 属性来指定哪些异常需要回滚,哪些异常不需要回滚。

3.5 Spring 中事务失效的场景有哪些🔥

事务管理在Spring Boot中通常是通过 @Transactional 注解来实现的。事务可能会失效的一些常见情况包括:

  1. 未捕获异常: 如果事务方法中发生了未捕获的运行时异常并传播到事务边界之外,那么事务会失效,所有的数据库操作会回滚。
  2. 事务在非公开方法中失效: 如果 @Transactional 注解标注在非 public 方法上,事务也会失效。
  3. 受检查异常 : 默认情况下,@Transactional 注解默认只有在遇到 RuntimeException (运行时异常) 或者 Error 时才会回滚事务,而不会回滚 Checked Exception。@Transactional 上配置 rollbackFor 属性为 Exception 可以设置所有异常都回滚事务。
  4. 非事务方法调用事务方法 : 同一个 Service 类非事务方法内部调用另一个事务方法,事务会失效。因为Spring 中只有通过代理对象调用的方法才会使用事务。同一个类中非事务方法调用事务方法时用的是当前对象而不是代理对象。解决方法:注入当前 Service 类的代理对象,通过代理对象调用事务方法。
  5. 事务传播属性设置不当: 如果在多个事务之间存在事务嵌套,且事务传播属性配置不正确,可能导致事务失效。
  6. 多数据源的事务管理: 如果在使用多数据源时,事务管理没有正确配置或者存在多个 @Transactional 注解时,可能会导致事务失效。

4 SpringBoot

4.1 为什么使用springboot

  • 简化开发:Spring Boot通过提供一系列的开箱即用的组件和自动配置,简化了项目的配置和开发过程,开发人员可以更专注于业务逻辑的实现,而不需要花费过多时间在繁琐的配置上。
  • 快速启动:Spring Boot提供了快速的应用程序启动方式,可通过内嵌的Tomcat、Jetty或Undertow等容器快速启动应用程序,无需额外的部署步骤,方便快捷。
  • 自动化配置:Spring Boot通过自动配置功能,根据项目中的依赖关系和约定俗成的规则来配置应用程序,减少了配置的复杂性,使开发者更容易实现应用的最佳实践。

4.2 Springboot 自动配置原理🔥

在 Spring Boot 项目中的引导类上有一个注解@SpringBootApplication,这个注解是对三个注解进行了封装,分别是:

  • @SpringBootConfiguration
  • @EnableAutoConfiguration
  • @ComponentScan

其中 @EnableAutoConfiguration 是实现自动化配置的核心注解。

首先,该注解通过 @Import 注解导入对应的配置选择器 。其内部会读取 classpath 路径下META-INF/spring.factories文件中的所配置的类的全路径名。

然后,会根据条件注解所指定的条件来决定 是否需要将其导入到 Spring 容器中。一般条件判断会有像 @ConditionalOnClass 这样的注解,它是判断是否有对应的 class 文件。

最后,当条件满足时,会自动注册和配置相应的Bean,将它们添加到Spring容器中。这样,在应用程序启动时,Spring Boot就会自动完成大部分配置工作,开发者无需手动配置。

4.3 SpringBoot 常用注解🔥

  • @SpringBootApplication:用于标注主应用程序类,标识一个Spring Boot应用程序的入口点,同时启用自动配置和组件扫描。

  • @Controller:标识控制器类,处理HTTP请求。

  • @RestController:结合@Controller和@ResponseBody,返回RESTful风格的数据。

  • @Service:标识服务类,通常用于标记业务逻辑层。

  • @Repository:标识数据访问组件,通常用于标记数据访问层。

  • @Component :通用的Spring组件注解,如果一个 Bean 不知道属于哪个层,可以使用@Component 注解标注。

  • @Autowired:用于自动装配Spring Bean。

  • @Value:用于注入配置属性值。

  • @RequestMapping:用于映射HTTP请求路径到Controller的处理方法。

  • @GetMapping、@PostMapping、@PutMapping、@DeleteMapping :简化@RequestMapping的GET、POST、PUT和DELETE请求。

  • @PathVariable用于获取路径参数,@RequestParam用于获取查询参数。

  • @Transactional: 在要开启事务的方法上使用 @Transactional 注解即可

  • @Configuration:用于指定一个类为配置类,其中定义的bean会被Spring容器管理。通常与@Bean配合使用,@Bean用于声明一个Bean实例,由Spring容器进行管理。

4.4 统一异常处理怎么做?

@ControllerAdvice + @ExceptionHandler 这两个注解统一进行异常处理。

java 复制代码
@ControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(BaseException.class)
    public ResponseEntity<?> handleAppException(BaseException ex, HttpServletRequest request) {
    }
}

这种异常处理方式下,会给所有 Controller 织入异常处理的逻辑(AOP),当 Controller 中的方法抛出异常的时候,由被 @ExceptionHandler 注解修饰的方法进行处理。

getMappedMethod 方法决定了异常具体被哪个被 @ExceptionHandler 注解修饰的方法处理异常。getMappedMethod() 会首先找到可以匹配处理异常的所有方法信息,然后对其进行从小到大的排序,最后取最小的那一个匹配的方法(即匹配度最高的那个)。

4.5 说几个启动器(starter)?

  • spring-boot-starter-web:这是最常用的起步依赖之一,它包含了Spring MVC和Tomcat嵌入式服务器,用于快速构建Web应用程序。
  • spring-boot-starter-security:提供了Spring Security的基本配置,帮助开发者快速实现应用的安全性,包括认证和授权功能。
  • mybatis-spring-boot-starter: 它自动配置了 MyBatis 的相关组件,用于简化在Spring Boot应用中集成MyBatis的过程。
  • spring-boot-starter-data-jpaspring-boot-starter-jdbc: 如果使用的是Java Persistence API (JPA)进行数据库操作,那么应该使用spring-boot-starter-data-jpa。这个Starter包含了数据库连接池等必要的库,可以让你轻松地与MySQL数据库进行交互。如果倾向于直接使用JDBC而不通过JPA,那么可以使用spring-boot-starter-jdbc,它提供了基本的JDBC支持。
  • spring-boot-starter-data-redis: 用于集成Redis缓存和数据存储服务,使得在Spring Boot应用中使用Redis变得非常便捷。
  • spring-boot-starter-test:包含了单元测试和集成测试所需的库,如JUnit, Spring Test, AssertJ等,便于进行测试驱动开发(TDD)。

4.6 写SpringBoot starter 的步骤?

  • 创建Maven项目,添加必要的依赖
  • 添加自动配置的元数据
  • 创建配置属性类,绑定配置文件中的属性
  • 创建一个服务类和服务实现类,以及一个控制器来实现starter的功能
  • 将starter发布到Maven仓库
  • 在pom.xml中添加这个starter依赖就可以使用了

5 Spring Security

5.1 有哪些控制请求访问权限的方法?

  • permitAll():无条件允许任何形式访问,不管你登录还是没有登录。

  • hasRole(String) : 只允许指定的角色访问。

  • hasAuthority(String):只允许具有指定权限的用户访问

  • hasIpAddress(String) : 只允许指定 ip 的用户访问。

  • authenticated():只允许已认证的用户访问。

  • fullyAuthenticated():只允许已经登录或者通过 remember-me 登录的用户访问。

  • hasAnyRole(String) : 指定一个或者多个角色,满足其一的用户即可访问。

  • hasAnyAuthority(String):指定一个或者多个权限,满足其一的用户即可访问。

5.2 如何对密码进行加密?

Spring Security 提供了多种加密算法的实现。这些加密算法实现类的接口是 PasswordEncoderPasswordEncoder 接口一共就 3 个必须实现的方法。

java 复制代码
public interface PasswordEncoder {
    // 加密也就是对原始密码进行编码
    String encode(CharSequence var1);
    // 比对原始密码和数据库中保存的密码
    boolean matches(CharSequence var1, String var2);
    // 判断加密密码是否需要再次进行加密,默认返回 false
    default boolean upgradeEncoding(String encodedPassword) {
        return false;
    }
}

官方推荐使用基于 bcrypt 强哈希函数的加密算法实现类。

5.3 如何优雅更换系统使用的加密算法?

如果我们在开发过程中,突然发现现有的加密算法无法满足我们的需求,需要更换成另外一个加密算法,这个时候应该怎么办呢?

推荐的做法是通过 DelegatingPasswordEncoder 兼容多种不同的密码加密方案,以适应不同的业务需求。DelegatingPasswordEncoder 其实就是一个代理类,并非是一种全新的加密算法,它做的事情就是代理上面提到的加密算法实现类。在 Spring Security 5.0 之后,默认就是基于 DelegatingPasswordEncoder 进行密码加密的。

6 Mybatis

6.1 #{} 和 ${} 的区别是什么?🔥

  • Mybatis 在处理 #{} 时,会创建预编译的 SQL 语句,将 SQL 中的 #{} 替换为占位符(?),在执行 SQL 时会为预编译 SQL 中的占位符(?)赋值。预编译的 SQL 语句执行效率高,并且可以防止 SQL 注入,提供更高的安全性,适合传递参数值。
  • Mybatis 在处理 ${} 时,只是创建普通的 SQL 语句,然后在执行 SQL 语句时将参数直接拼入到 SQL 里,不能防止 SQL 注入,因为参数直接拼接到 SQL 语句中,如果参数未经过验证、过滤,可能会导致安全问题。

6.2 与传统的 JDBC 相比,MyBatis 的优点?

  • 基于 SQL 语句编程,相当灵活,不会对应用程序或者数据库的现有设计造成任何影响,SQL 写在 XML 里,解除 sql 与程序代码的耦合,便于统一管理;提供 XML 标签,支持编写动态 SQL 语句,并可重用。
  • 与 JDBC 相比,减少了 50%以上的代码量,消除了 JDBC 大量冗余的代码,不需要手动开关连接;
  • 很好的与各种数据库兼容,因为 MyBatis 使用 JDBC 来连接数据库,所以只要 JDBC 支持的数据库 MyBatis 都支持。
  • 能够与 Spring 很好的集成,开发效率高
  • 提供映射标签,支持对象与数据库的 ORM 字段关系映射;提供对象关系映射标签,支持对象关系组件维护。

6.3 JDBC 连接数据库的步骤

  1. 加载数据库驱动程序:在使用 JDBC 连接数据库之前,需要加载相应的数据库驱动程序。可以通过 Class.forName("com.mysql.jdbc.Driver") 来加载 MySQL 数据库的驱动程序。不同数据库的驱动类名会有所不同。
  2. 建立数据库连接:使用 DriverManager 类的 getConnection(url, username, password) 方法来连接数据库,其中 url 是数据库的连接字符串(包括数据库类型、主机、端口等)、username 是数据库用户名,password 是密码。
  3. 创建 Statement 对象:通过 Connection 对象的 createStatement() 方法创建一个 Statement 对象,用于执行 SQL 查询或更新操作。
  4. 执行 SQL 查询或更新操作:使用 Statement 对象的 executeQuery(sql) 方法来执行 SELECT 查询操作,或者使用 executeUpdate(sql) 方法来执行 INSERT、UPDATE 或 DELETE 操作。
  5. 处理查询结果:如果是 SELECT 查询操作,通过 ResultSet 对象来处理查询结果。可以使用 ResultSet 的 next() 方法遍历查询结果集,然后通过 getXXX() 方法获取各个字段的值。
  6. 关闭连接:在完成数据库操作后,需要逐级关闭数据库连接相关对象,即先关闭 ResultSet,再关闭 Statement,最后关闭 Connection。

6.4 项目中要用到原生的 mybatis 去查询,该怎样写?

  1. 配置 MyBatis: 在项目中配置 MyBatis 的数据源、SQL 映射文件等。
  2. 创建实体类: 创建用于映射数据库表的实体类。
  3. 编写 SQL 映射文件: 创建 XML 文件,定义 SQL 语句和映射关系。
  4. 编写 DAO 接口: 创建 DAO 接口,定义数据库操作的方法。
  5. 编写具体的 SQL 查询语句: 在 DAO 接口中定义查询方法,并在 XML 文件中编写对应的 SQL 语句。
  6. 调用查询方法: 在服务层调用 DAO 接口中的方法进行查询。

6.5 MybatisPlus 和 Mybatis 的区别?🔥

  • CRUD 操作: MybatisPlus 通过继承 BaseMapper 接口,提供了一系列内置的快捷方法,使得 CRUD 操作更加简单,无需编写重复的 SQL 语句。
  • 代码生成器: MybatisPlus 提供了代码生成器功能,可以根据数据库表结构自动生成实体类、Mapper 接口以及 XML 映射文件,减少了手动编写的工作量。
  • 通用方法封装: MybatisPlus 封装了许多常用的方法,如条件构造器、排序、分页查询等,简化了开发过程,提高了开发效率。
  • 分页插件: MybatisPlus 内置了分页插件,支持各种数据库的分页查询,开发者可以轻松实现分页功能,而在传统的 MyBatis 中,需要开发者自己手动实现分页逻辑。
  • 多租户支持: MybatisPlus 提供了多租户的支持,可以轻松实现多租户数据隔离的功能。
  • 注解支持: MybatisPlus 引入了更多的注解支持,使得开发者可以通过注解来配置实体与数据库表之间的映射关系,减少了 XML 配置文件的编写。

6.6 MyBatis 运用了哪些常见的设计模式?

  • 工厂模式,如:SqlSessionFactory、ObjectFactory、MapperProxyFactory;
  • 单例模式,例如 ErrorContext 和 LogFactory;
  • 代理模式,Mybatis 实现的核心,比如 MapperProxy、ConnectionLogger,用的 jdk 的动态代理;还有 executor.loader 包使用了 cglib 或者 javassist 达到延迟加载的效果;
  • 组合模式,例如 SqlNode 和各个子类 ChooseSqlNode 等;
  • 模板方法模式,例如 BaseExecutor 和 SimpleExecutor,还有 BaseTypeHandler 和所有的子类例如 IntegerTypeHandler;
  • 适配器模式,例如 Log 的 Mybatis 接口和它对 jdbc、log4j 等各种日志框架的适配实现;
  • 装饰者模式,例如 Cache 包中的 cache.decorators 子包中等各个装饰者的实现;
  • 迭代器模式,例如迭代器模式 PropertyTokenizer;

6.7 Dao 接口的工作原理是什么?

Dao 接口的工作原理是 JDK 动态代理,MyBatis 运行时会使用 JDK 动态代理为 Dao 接口生成代理对象,代理对象会拦截接口方法,转而执行 MappedStatement 所代表的 sql,然后将 sql 执行结果返回。

6.8 MyBatis 都有哪些动态 sql?

MyBatis 动态 sql 可以让我们在 xml 映射文件内,以标签的形式编写动态 sql,完成逻辑判断和动态拼接 sql 的功能。

  • <if>: 条件判断,决定是否包含SQL片段。

  • <where> : 自动处理WHERE子句,避免SQL语法问题。

  • <foreach> : 遍历集合或数组,生成动态SQL,常用于 IN 语句或批量操作。

  • <trim>: 灵活地拼接SQL,提供对前后缀的控制。

  • <set> : 主要用于UPDATE语句的SET子句,自动去除多余的逗号。

6.9 MyBatis 是如何将 sql 执行结果封装为目标对象并返回的?

第一种是使用 <resultMap> 标签,逐一定义列名和对象属性名之间的映射关系。

xml 复制代码
<resultMap id="userResultMap" type="User">
  <id property="id" column="user_id"/>
  <result property="username" column="user_name"/>
</resultMap>

<select id="findUserById" resultMap="userResultMap">
  SELECT user_id, user_name, user_age, address_id FROM users WHERE user_id = #{id}
</select>

第二种是使用 sql 列的别名功能,将列别名书写为对象属性名。

xml 复制代码
<select id="findUserById" resultType="User">
  SELECT user_id AS id, user_name AS username FROM users WHERE user_id = #{id}
</select>

有了列名与属性名的映射关系后,MyBatis 通过反射创建对象,同时使用反射给对象的属性逐一赋值并返回,那些找不到映射关系的属性,是无法完成赋值的。

相关推荐
小鑫记得努力5 分钟前
Java类和对象(下篇)
java
binishuaio9 分钟前
Java 第11天 (git版本控制器基础用法)
java·开发语言·git
zz.YE11 分钟前
【Java SE】StringBuffer
java·开发语言
老友@11 分钟前
aspose如何获取PPT放映页“切换”的“持续时间”值
java·powerpoint·aspose
wrx繁星点点26 分钟前
状态模式(State Pattern)详解
java·开发语言·ui·设计模式·状态模式
Upaaui29 分钟前
Aop+自定义注解实现数据字典映射
java
zzzgd81629 分钟前
easyexcel实现自定义的策略类, 最后追加错误提示列, 自适应列宽,自动合并重复单元格, 美化表头
java·excel·表格·easyexcel·导入导出
友善的鸡蛋30 分钟前
解决:使用EasyExcel导入Excel模板时出现数据导入不进去的问题
java·easyexcel·excel导入
星沁城30 分钟前
240. 搜索二维矩阵 II
java·线性代数·算法·leetcode·矩阵
NoneCoder43 分钟前
Java企业级开发系列(1)
java·开发语言·spring·团队开发·开发