Spring question问题小结
作者:奈川直子
Spring
一. 控制反转(IoC)
1.手动
使用了Spring的 @Configuration
和 @Bean
注解来明确指定了哪些类需要被纳入容器的管理。在 AppConfig
配置类中,通过 @Bean
注解创建了 Service
和 Controller
的实例,Spring会自动将这些实例纳入容器的管理,并处理它们之间的依赖关系。
// 定义一个Service接口 public interface Service { void doSomething(); } // 实现Service接口的具体类 public class ServiceImpl implements Service { @Override public void doSomething() { System.out.println("Service is doing something."); } } // 定义一个Controller类,它依赖于Service接口 public class Controller {d private Service service; // 通过构造函数注入依赖 public Controller(Service service) { this.service = service; } public void doAction() { service.doSomething(); } } // 在应用的入口处,使用Spring容器创建实例并进行依赖关系的管理 public class Main { public static void main(String[] args) { // 创建Spring容器 ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); // 从容器中获取Controller实例 Controller controller = context.getBean(Controller.class); // 调用Controller的方法,会自动调用Service的方法 controller.doAction(); } } // 定义一个配置类,用于告诉Spring容器如何创建和管理Bean @Configuration public class AppConfig { @Bean public Service service() { return new ServiceImpl(); } @Bean public Controller controller(Service service) { return new Controller(service); } }
2.自动
SpringApplication.run(CommunityApplication.class, args)
会扫描应用中的所有组件(包括被 @Component
、 @Service
、 @Repository
等注解标记的类),并将它们纳入Spring容器的管理。通过调用 SpringApplication.run
方法启动应用,会自动创建一个Spring应用上下文(ApplicationContext),并初始化整个应用的配置和组件。
public static void main(String[] args) { SpringApplication.run(CommunityApplication.class, args); }
@SpringBootApplication
实际上是一个组合注解,包含了 @SpringBootConfiguration
、 @EnableAutoConfiguration
和 @ComponentScan
三个注解。其中, @SpringBootConfiguration
注解表示这是一个Spring Boot的配置类, @EnableAutoConfiguration
注解启用自动配置, @ComponentScan
注解指定要扫描的包路径。使用Spring Boot的自动化机制可以方便快捷地扫描和管理所有的Bean,而手动进行依赖注入可以更加精确地控制容器中的Bean。
Q1:使用@Repository注解时,怎么在Spring的配置文件中进行相关的配置?
1、Spring
在使用@Repository注解时,可以在Spring的配置文件中进行相关的配置。以下是配置步骤:
1.在Spring的配置文件中添加 context:component-scan
标签,用于开启组件扫描功能,扫描带有 @Repository
注解的类。示例如下:
<context:component-scan base-package="com.example.repository" />
其中, base-package
属性指定了要扫描的包路径。
2.如果需要配置数据源和事务管理器,可以在Spring的配置文件中进行相关的配置。示例如下:
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource"> <!-- 数据源相关配置 --> </bean> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean>
其中, dataSource
是数据源的配置, transactionManager
是事务管理器的配置。
3.如果需要使用 @Repository
注解对应的数据访问异常转化功能,可以配置相关的异常转化器。示例如下:
<bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />
这个配置会自动为带有 @Repository
注解的类添加异常转化功能。通过以上配置,就可以在Spring的配置文件中配置 @Repository
注解相关的配置。注意,具体的配置内容根据实际需求和使用的技术选择可能会有所不同。
2、Spring Boot
在使用Spring Boot时,可以省略繁琐的配置过程,因为Spring Boot提供了自动配置的功能。对于@Repository注解的配置,可以按照以下步骤进行:
- 在Spring Boot主类上添加
@SpringBootApplication
注解,该注解包含了@EnableAutoConfiguration
注解,用于开启自动配置功能。 - 确保@Repository注解所在的包或子包在主类的扫描范围内,可以通过
@ComponentScan
注解进行配置,或者将主类放在包的顶层位置。 - 如果需要配置数据源,可以在
application.properties
或application.yml
配置文件中添加相关的配置项,如下所示:
propertiesCopy codespring.datasource.url=jdbc:mysql://localhost:3306/mydb spring.datasource.username=root spring.datasource.password=123456 spring.datasource.driver-class-name=com.mysql.jdbc.Driver
这样,Spring Boot会根据配置文件的内容自动配置数据源。
4.如果需要事务管理器,可以在需要使用事务的类或方法上添加 @Transactional
注解,Spring Boot会自动配置事务管理器。 通过以上步骤,就可以在Spring Boot中使用@Repository注解,无需进行繁琐的配置,Spring Boot会根据约定和自动配置的机制自动完成相关的配置。 Q2:@Primary有什么用
@Primary注解是Spring框架中的一个注解,用于标识主要的Bean实例。当存在多个同类型的Bean实例时,通过使用@Primary注解,可以指定其中一个Bean实例为首选的主要实例。
Q3:@Repository有什么用
@Repository注解是Spring框架中的一个注解,用于标识持久层组件(如DAO类)。
@Repository的作用主要有以下几点:
- 标识持久层组件:通过在持久层的类上添加
@Repository
注解,可以告诉Spring容器该类是一个用于数据访问的组件。Spring在进行组件扫描时,会扫描带有@Repository
注解的类,并将其实例化为Bean
。 - 提供异常转化功能:
@Repository
注解还提供了数据访问异常的转化机制。当数据访问过程中发生异常时,Spring会将底层的数据访问异常(如JDBC异常)转化为Spring的统一异常体系,使得应用程序可以更方便地处理和捕获异常。 - 具备@Component的功能:
@Repository
注解是@Component
注解的派生注解,因此@Repository
注解具备@Component
的所有功能。它可以被Spring容器自动扫描并装配,可以使用@Autowired
或@Resource
等注解进行依赖注入,也可以使用@Qualifier注解进行指定具体的实现类。
Q4:谈谈对IoC的理解
控制:创建、实例化对象的权力反转:将这些权力交给IoC容器和Spring框架
将对象之间的依赖关系交给IoC容器管理,由IoC容器完成对象的注入,简化开发,IoC容器像是一个工厂,创建一个对象时只配置好,不需要考虑怎么被创建出来的。Spring中用Xml文件配置bean,在Spring Boot中用注解配置。
Q5:@Component和@Bean的区别
- @Component注解作用于类,而@Bean注解作用于方法
- @Component通常是通过类路径扫描来自动侦察以及自动装配到Spring容器中;
- @Bean在标有该注解的方法中定义产生这个bean,@Bean告诉了Spring这是某个类的实例,当我需要的时候还给我。
- @Bean比@Component的自定义性更强
Q6:注入Bean的注解
- @Autowired
- @Resource
- @Inject
1.@Autowired
属于 Spring
内置的注解,默认的注入方式是 byType(根据类型进行匹配)
,首先根据接口类型去匹配并注入 Bean
。如果一个接口有多个实现类,注入方式就会变为 byName(根据名称匹配)
举个例子, SmsService
接口有两个实现类: SmsServiceImpl1
和 SmsServiceImpl2
,且它们都已经被 Spring
容器所管理。
// 报错,byName 和 byType 都无法匹配到 bean @Autowired private SmsService smsService; // 正确注入 SmsServiceImpl1 对象对应的 bean @Autowired private SmsService smsServiceImpl1; // 正确注入 SmsServiceImpl1 对象对应的 bean // smsServiceImpl1 就是我们上面所说的名称 @Autowired @Qualifier(value = "smsServiceImpl1") private SmsService smsService;
2.@Resource
属于 JDK 提供的注解,默认注入方式为 byName
。如果无法通过名称匹配到对应的 Bean 的话,注入方式会变为 byType
。 @Resource
有两个比较重要且日常开发常用的属性: name
(名称)、 type
(类型)。
public @interface Resource { String name() default ""; Class<?> type() default Object.class; }
如果仅指定 name
属性则注入方式为 byName
,如果仅指定 type
属性则注入方式为 byType
,如果同时指定 name
和 type
属性(不建议这么做)则注入方式为 byType
+ byName
。
// 报错,byName 和 byType 都无法匹配到 bean @Resource private SmsService smsService; // 正确注入 SmsServiceImpl1 对象对应的 bean @Resource private SmsService smsServiceImpl1; // 正确注入 SmsServiceImpl1 对象对应的 bean(比较推荐这种方式) @Resource(name = "smsServiceImpl1") private SmsService smsService;
总结一下:
@Autowired
是 Spring 提供的注解,@Resource
是 JDK 提供的注解。Autowired
默认的注入方式为byType
(根据类型进行匹配),@Resource
默认注入方式为byName
(根据名称进行匹配)。- 当一个接口存在多个实现类的情况下,
@Autowired
和@Resource
都需要通过名称才能正确匹配到对应的 Bean。Autowired
可以通过@Qualifier
注解来显式指定名称,@Resource
可以通过name
属性来显式指定名称。@Autowired
支持在构造函数、方法、字段和参数上使用。@Resource
主要用于字段和方法上的注入,不支持在构造函数或参数上使用
Q8:Bean的作用域
- Singleton:IoC容器中只有唯一的bean实例,Spring的bean默认都是单例的。在之后的每次请求或注入时,都会返回同一个实例,而不会重新创建新的实例。
- prototype:每次获取都会创建一个新的 bean 实例。与默认的单例模式不同,每次通过容器获取prototype作用域的Bean时,都会创建一个新的实例。原型模式的Bean不适合进行依赖注入,因为每次注入都会得到一个新的实例,无法保证依赖关系的一致性。
- request (仅 Web 应用可用): 每一次 HTTP 请求都会产生一个新的 bean(请求 bean),该 bean 仅在当前 HTTP request 内有效。
- session (仅 Web 应用可用) : 每一次来自新 session 的 HTTP 请求都会产生一个新的 bean(会话 bean),该 bean 仅在当前 HTTP session 内有效。
Q9:为什么在Spring boot没有配置 bean 的作用域
在Spring Boot中,为了简化配置和提高开发效率,通常不需要显式地配置Bean的作用域。Spring Boot默认使用单例模式来管理Bean,即每个Bean在容器中只会存在一个实例。 这是因为Spring Boot遵循"约定优于配置"的原则,通过自动配置和默认配置来简化开发过程。在大多数情况下,单例模式已经能够满足开发需求,因此默认使用单例模式可以减少不必要的配置。 如果需要使用其他作用域,如原型(prototype)、会话(session)、请求(request)等,可以在需要的地方使用特定的注解来标记,而不需要在配置文件中显式配置。例如,可以使用** @Scope("prototype")
**注解来将特定的Bean定义为原型模式。
二. 面向切面编程(AOP)
术语 | 含义 |
---|---|
目标(Target) | 目标对象/被通知的对象 |
代理(Proxy) | 向目标对象应用通知之后创建的代理对象 |
连接点(JoinPoint) | 目标对象的所属类中,定义的所有方法均为连接点 |
切入点(Pointcut) | 被切面拦截/增强的连接点(切入点一定是连接点,连接点不一定是织入点) |
通知(Advice) | 增强的逻辑/代码,也是拦截到目标对象的连接点后应该做的事 |
织入(Weaving) | 将通知 应用到目标对象,产生代理对象的过程动作 |
方面主键(Aspect) | Pointcut+Advice |
Q1:谈谈对AOP的理解
能够将与业务无关,却为业务模块所共同调用的逻辑和责任所封装起来,例如:事务处理、权限控制,日志管理,减少重复代码,比较好维护。
Spring AOP
基于动态代理的,如果代理的对象实现了某个接口,那么 Spring AOP
会使用 JDK Proxy
,去创建代理对象;而对于没有实现接口的对象, Spring AOP
使用 Cglib
生成一个被代理对象的子类作为代理。
Spring AOP
的工作原理:自动为目标对象生成代理,并在方法调用时织入切面逻辑。
代理对象是通过 Spring AOP
自动生成的,并且已经被注入到 ApplicationContext
中。具体来说,代理对象是在以下代码中获取的:
UserService userService = context.getBean(UserService.class);
// 主类 @SpringBootApplication public class Application { public static void main(String[] args) { ApplicationContext context = SpringApplication.run(Application.class, args); UserService userService = context.getBean(UserService.class); userService.addUser("john", "123456"); } }
在这个示例中,通过 ApplicationContext
的 getBean
方法来获取代理对象。根据 Spring AOP
的配置,如果 UserService
接口被代理,那么获取到的 userService
对象就是代理对象;如果 UserServiceImpl
类被代理,那么获取到的 userService
对象也是代理对象。
Q2:为什么要对Spring AOP进行代理,不代理会怎样?
在 Spring AOP
中,代理是实现切面功能的关键。如果不进行代理,切面逻辑将无法被织入到目标对象的方法调用中,失去了 AOP
的作用。 具体来说,代理对象在目标对象和调用方之间充当了一个中间层。当调用方调用代理对象的方法时,代理对象会在方法执行前后执行切面逻辑。这样,我们可以在切面中添加一些额外的逻辑,比如日志记录、事务管理、性能监控等。代理对象将切面逻辑织入到目标对象的方法调用中,从而实现了横切关注点的模块化。 如果不进行代理,切面逻辑将无法被自动应用到目标对象的方法调用中。这意味着我们需要在每个目标对象方法的调用处手动添加切面逻辑,这样会导致代码的重复和冗余,不利于代码的维护和扩展。而代理机制可以自动为目标对象生成代理,并在方法调用时织入切面逻辑,使得切面的应用更加便捷和灵活。 除了将切面逻辑织入到方法调用中,代理还可以实现其他功能,比如延迟加载、事务管理、缓存等。代理对象可以拦截方法调用,根据需要进行一些额外的处理,从而提供更多的功能。
Q3:那在以下代码中,谁是目标对象,谁是代理对象,谁是调用方?
public class AlphaAspect { @Pointcut("execution(* com.newcoder.community.service.*.*(..))")//所有类、所有方法 public void pointcut(){} @Before("pointcut()")//连接点开始记日志 public void before(){ System.out.println("before"); } @After("pointcut()")//连接点后记日志 public void after(){ System.out.println("after"); } @AfterReturning("pointcut()")//有返回值后记日志 public void afterReturn(){ System.out.println("afterReturn"); } @AfterThrowing("pointcut()")//抛异常后记日志 public void afterThrowing(){ System.out.println("afterThrowing"); } @Around("pointcut()")//前后都织入 执行代理对象,织入代理对象,用来代替原始对象 public Object around(ProceedingJoinPoint joinPoint) throws Throwable{//ProceedingJoinPoint为连接点,织入部位 System.out.println("around before"); Object obj = joinPoint.proceed();//proceed调目标主键 调用原始对象方法 System.out.println("around after"); return obj; }
在这个例子中,目标对象是com.newcoder.community.service包下的所有类的所有方法。 AlphaAspect类本身是一个切面类,其中包含了各种注解标记的通知方法。这些通知方法会在目标对象的方法调用前后执行相应的逻辑。因此,AlphaAspect类是代理对象。 调用方是在业务代码中调用目标对象的方法的地方,这个例子中没有给出具体的业务代码,所以无法确定调用方是什么。
所以,代理对象会在目标对象的方法调用前后执行相应的逻辑。
Q4:那为什么说向目标对象应用通知之后创建代理对象?
在AOP中,通知(Advice)是切面的核心逻辑,用于在目标对象的方法调用前后执行特定的操作。为了将通知应用到目标对象的方法调用中,需要创建代理对象来包装目标对象。代理对象负责拦截目标对象的方法调用,并在适当的时机执行通知。 代理对象的创建过程可以分为两种方式:静态代理和动态代理。
- 静态代理:在编译时就已经将切面逻辑与目标对象织入到了一起,生成了代理类的字节码文件。代理对象是在编译时生成的,不需要在运行时再去创建。
- (AspectJ)动态代理:在运行时动态地生成代理对象。动态代理需要借助于Java的反射机制来实现。通过动态创建代理对象,可以根据切面的配置在运行时将通知应用到目标对象的方法调用中。 在Spring AOP中,默认使用的是动态代理。Spring使用JDK动态代理和CGLIB动态代理两种方式来创建代理对象。如果目标对象实现了接口,则使用JDK动态代理;如果目标对象没有实现接口,则使用CGLIB动态代理。 无论是静态代理还是动态代理,代理对象的创建都是在应用通知之后进行的。通过代理对象,切面逻辑可以被织入到目标对象的方法调用中,实现对目标对象的增强。
Q5:目标对象所有的方法就是连接点吗?
是的,目标对象的所有方法都可以作为连接点。连接点是程序执行过程中的特定点,包括方法的调用、异常的抛出、字段的访问等。在AOP中,最常见的连接点是方法的调用,因为我们通常希望在方法的执行前、执行后或发生异常时插入切面逻辑。 在AOP中,可以选择在目标对象的所有方法上织入切面逻辑,也可以选择只在特定的方法上织入切面逻辑,这取决于所定义的切点(Pointcut)。切点是定义在哪些连接点上织入切面逻辑的规则,可以使用表达式或注解来定义切点。
Q6:切入点是类路径吗?
不,切入点(Pointcut)并不是类路径。切入点是在 AOP
中用于定义哪些连接点(JoinPoint)应该被织入切面逻辑的规则。 切入点可以使用表达式或注解来定义。使用表达式定义切入点时,可以使用 AspectJ
风格的切入点表达式,如 execution、within、args
等关键字,以及类、方法、参数等信息来描述需要织入切面逻辑的连接点。使用注解来定义切入点时,可以通过在目标对象的方法上添加特定的注解,然后通过切入点表达式来匹配这些注解来确定要织入切面逻辑的连接点。 切入点可以非常灵活地定义,可以选择在目标对象的所有方法上织入切面逻辑,也可以选择只在特定的方法或类上织入切面逻辑。切入点的定义是根据业务需求和设计目标来确定的,通常会根据实际情况来选择需要织入切面逻辑的连接点。
Q7:织入的过程是不是就相当于动态代理和静态代理过程?
? 是的,织入的过程可以类比为动态代理和静态代理的过程。? 在静态代理中,代理对象和目标对象实现同一个接口,代理对象在调用目标对象方法的前后插入额外的逻辑。这种方式需要在编译时期就确定代理关系,并在代码中显式地指定代理对象。? 在动态代理中,代理对象是在运行时动态生成的,无需事先编写代理类。通过Java的反射机制,动态代理可以在运行时拦截并处理目标对象的方法调用。在动态代理中,我们可以在目标对象方法的调用前后插入切面逻辑。? 类似地, AOP
的织入过程也是在运行时动态生成的。 AOP
框架会根据切入点的定义,对目标对象的方法调用进行拦截,并根据切面逻辑对其进行增强。这个过程可以看作是在运行时动态代理的过程。 不同的是,动态代理和静态代理通常是针对单个类或对象进行的,而** AOP
的织入可以同时作用于多个类和对象**,根据切入点的定义对满足条件的连接点进行增强。
三、Spring MVC
Q1:说说自己对Spring MVC的了解
MVC是模型(Model)、视图(View)、控制器(Controller)的简写,它是通过将业务逻辑、数据、显示分离组织代码。
Q2:Spring MVC的核心组件有哪些?
DispatcherServlet
:核心的中央处理器,负责接受请求、分发,并给予客户响应HandlerMapping
:处理器映射器,根据URL去匹配查找能处理的Handler
,并会将请求涉及到的拦截器和Handler
一起封装HandlerAdapter
:处理器适配器 ,根据HandlerMapping
找到的Handler
,适配执行对应的Handler
.Handler
:请求处理器,处理实际请求的处理器ViewResolver
:视图解析器,根据Handler
返回的逻辑视图,解析并渲染真正的视图,并传递给DispatcherServlet响应客户端
Q3:Spring MVC的工作原理
- 客户端发送请求,
DispatcherServlet
拦截请求DispatcherServlet
根据请求信息调用HandlerMapping
。 HandlerMapping
根据URL去匹配查找能处理的Handler
,并会将涉及到的拦截器和Handler
一起封装DispatcherServlet
调用HandlerAdapter
适配器执行Handler
Handler
完成对用户的请求的处理,返回一个ModelAndView
对象给DispatcherServlet
- ViewResolver 根据逻辑
View
查找实际的View
DispatcherServlet
把返回的Model
传给View
View
返回客户端
Q4:Handler 是什么?
Handler 是指在 Spring MVC 框架中用于处理用户请求的组件。Handler 可以是一个 Controller 类中的方法、一个 Servlet、一个 WebSocket 处理器或其他可处理请求的组件。
Q5:DispatcherServlet 为什么调用 HandlerAdapter适配器执行 Handler?
不同的 Handler 可能有不同的处理方式,例如一个 Controller 方法、一个 Servlet、一个 WebSocket 处理器等。为了统一处理不同类型的 Handler,需要使用适配器模式将不同类型的 Handler 适配为统一的处理方式。 HandlerAdapter 适配器的作用就是根据不同类型的 Handler,将请求信息进行适配,使其能够被统一调用并执行。它根据 Handler 的类型,调用相应的适配方法,将请求信息传递给 Handler 进行处理,并获取处理结果返回给 DispatcherServlet。 通过使用 HandlerAdapter 适配器,DispatcherServlet 不需要关心具体的 Handler 类型,只需要调用适配器的方法即可,实现了对不同类型 Handler 的统一调用和处理。
Q5:为什么会将请求涉及到的拦截器和 Handler 一起封装?
将请求涉及到的拦截器和Handler一起封装的目的是为了在请求处理过程中能够方便地对请求进行拦截和处理。 拦截器是用于对请求进行预处理和后处理的组件,可以在请求到达Controller之前和之后执行额外的逻辑。通过将拦截器和Handler一起封装,可以实现以下几个方面的功能:
- 统一管理拦截器:将拦截器和Handler一起封装,可以方便地统一管理拦截器的配置和使用。在配置文件或注解中指定拦截器的顺序和范围,可以灵活地控制拦截器的执行顺序和作用范围。
- 方便拦截器的调用:将拦截器和Handler一起封装后,可以在请求到达Controller之前和之后分别调用拦截器的预处理和后处理方法。这样可以方便地对请求进行拦截和处理,执行一些通用的操作,如权限验证、日志记录等。
- 提高代码的复用性:将一些通用的逻辑处理抽离出来作为拦截器,通过封装拦截器和Handler,可以在多个请求处理过程中共享这些逻辑,提高代码的复用性。例如,多个请求需要进行权限验证,可以将权限验证的逻辑抽象为一个拦截器,在多个请求处理过程中共享使用。
- 灵活控制请求处理流程:通过封装拦截器和Handler,可以灵活控制请求处理流程。在拦截器的预处理方法中,可以决定是否继续处理请求,或者进行一些重定向或错误处理操作。在拦截器的后处理方法中,可以对响应结果进行进一步的处理或修改。
四、设计模式
Q1:Spring框架中用到了哪些设计模式?
- 工厂模式:通过beanFactory、ApplicationContext创建bean对象
- 代理模式:Spring AOP功能的实现
- 单例模式:Spring的bean默认都是单例的
- 适配器模式:Spring MVC用到了适配器模式适配handler、Spring AOP的Advice用到了适配器模式适配成MethodInterceptor接口类型的对象(全都变成方法拦截器对象)
- 模板模式:以Template结尾的对数据库操作的类
- 包装器模式:项目需要连接多个数据库,不同的客户每次访问可能会访问不同的数据库。包装器模式可动态的切换不同数据源。
五、Spring事务
Q1:事务的特性(ACID)了解吗?
- 原子性:事务是最小的执行单位,不允许分割,事物的原子性确保动作要么全部完成,要么不起作用
- 一致性:执行事务前后,数据保持一致,例如转账时,无论事务是否成功,转账人和收款人的总额应该是不变的
- 隔离性:并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间的数据库是独立的
- 持久性:一个事务被提交以后,他对数据库的改变是持久的,数据库发生故障也不应该对它有任何影响
只有保证了原子性、隔离性、持久性以后,一致性才能得到保障:AID->C
Q2:什么时候才用事务的隔离性?
事务的隔离性是指多个事务并发执行时,各个事务之间的数据应该是相互隔离的,一个事务的操作不应该对其他事务的操作产生影响。在并发环境下,如果多个事务同时访问和修改共享数据,可能会导致数据不一致的问题。 举个例子来说明事务的隔离性的使用场景: 假设有一个电商平台,用户可以在平台上购买商品,而商品的库存是需要管理的。当用户购买商品时,会进行库存的扣减操作。在这个场景中,如果不考虑事务的隔离性,可能会出现以下问题:
非隔离性导致的库存错误:假设有两个用户 A 和 B 同时购买同一件商品,且库存只有一件。如果不使用事务的隔离性,A 和 B 同时发起购买请求时,在没有互斥控制的情况下,可能会导致库存扣减错误。例如,A 和 B 同时读取库存数量为 1,然后都进行库存扣减,最终库存可能会变成 -1。 为了解决这个问题,可以使用事务的隔离性。通过将库存扣减操作放在一个事务中,并使用合适的隔离级别,可以避免并发访问时的数据不一致问题。例如,使用数据库的隔离级别 SERIALIZABLE,可以确保同一时间只有一个事务能够访问和修改库存数据,从而避免库存错误问题。
Q3:MySQL怎么保证原子性?
保证原子性,就要在异常发生的时候对已经执行的操作进行回滚,在MySQL中,恢复机制是通过回滚日志实现的,所有事务进行的修改都会先记录到这个回滚日志中,然后再执行相关的操作。如果执行过程中出现异常,就利用回滚日志中的信息将数据回滚到修改之前的样子。并且,回滚日志会先把数据持久化到硬盘上,这样就能保证数据库在事务执行过程中宕机,未完成的事务会自动回滚。当再次打开数据库时,数据库会通过回滚日志来查找未完成的事务,并对其进行回滚操作,以保证数据的一致性。
到此这篇关于Spring question问题小结的文章就介绍到这了,更多相关Spring question内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!