spring高级篇(五)

1、参数解析器

        前篇提到过,参数解析器是HandlerAdapters中的组件,用于解析controller层方法中加了注解的参数信息。

        有一个controller,方法的参数加上了各种注解:

public class Controller {
    
    public void test(
            @RequestParam("name1") String name1, // name1=张三
            String name2,                        // name2=李四
            @RequestParam("age") int age,        // age=18
            @RequestParam(name = "home", defaultValue = "${JAVA_HOME}") String home1, // spring 获取数据
            @RequestParam("file") MultipartFile file, // 上传文件
            @PathVariable("id") int id,               //  /test/124   /test/{id}
            @RequestHeader("Content-Type") String header,
            @CookieValue("token") String token,
            @Value("${JAVA_HOME}") String home2, // spring 获取数据  ${} #{}
            HttpServletRequest request,          // request, response, session ...
            @ModelAttribute("abc") A21.User user1,          // name=zhang&age=18
            A21.User user2,                          // name=zhang&age=18
            @RequestBody A21.User user3              // json
    ) {
    }
}

        在测试类中定义一个方法,模拟各种参数的请求信息:

   private static HttpServletRequest mockRequest() {
        MockHttpServletRequest request = new MockHttpServletRequest();
        request.setParameter("name1", "zhangsan");
        request.setParameter("name2", "lisi");
        request.addPart(new MockPart("file", "abc", "hello".getBytes(StandardCharsets.UTF_8)));
        Map<String, String> map = new AntPathMatcher().extractUriTemplateVariables("/test/{id}", "/test/123");
        System.out.println(map);
        request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, map);
        request.setContentType("application/json");
        request.setCookies(new Cookie("token", "123456"));
        request.setParameter("name", "张三");
        request.setParameter("age", "18");
        request.setContent("""
                    {
                        "name":"李四",
                        "age":20
                    }
                """.getBytes(StandardCharsets.UTF_8));
        return new StandardServletMultipartResolver().resolveMultipart(request);
    }

        测试类中获取ApplicationContext,准备测试请求:

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class);
//获取beanFactory,为了解析${} 
DefaultListableBeanFactory beanFactory = context.getDefaultListableBeanFactory();
// 准备测试 Request
HttpServletRequest request = mockRequest();

        由于我们没有使用AnnotationConfigServletWebServerApplicationContext现,不具备在初始化时收集所有 @RequestMapping 映射信息,封装为 Map(K:路径,V:HandlerMethod)的能力,

所以需要手动准备HandlerMethod:

// 要点1. 控制器方法被封装为 HandlerMethod
HandlerMethod handlerMethod = new HandlerMethod(new Controller(), Controller.class.getMethod("test", String.class, String.class, int.class, String.class, MultipartFile.class, int.class, String.class, String.class, String.class, HttpServletRequest.class, User.class, User.class, User.class));

        因为表单传递的参数类型默认都是String字符串,但是方法参数中的类型可能是其他,例如int,long等,所以还需要定义类型转换:

 // 要点2. 准备对象绑定与类型转换
ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null, null);

        还需要定义容器存储中间结果:

// 要点3. 准备 ModelAndViewContainer 用来存储中间 Model 结果
ModelAndViewContainer container = new ModelAndViewContainer();

        对参数值进行解析:

 for (MethodParameter parameter : handlerMethod.getMethodParameters()) {
            RequestParamMethodArgumentResolver resolver = new RequestParamMethodArgumentResolver(beanFactory,false);

            String annotations = Arrays.stream(parameter.getParameterAnnotations()).map(a -> a.annotationType().getSimpleName()).collect(Collectors.joining());
            String str = annotations.length() > 0 ? " @" + annotations + " " : " ";
            parameter.initParameterNameDiscovery(new DefaultParameterNameDiscoverer());

            if (resolver.supportsParameter(parameter)) {
                // 支持此参数
                Object v = resolver.resolveArgument(parameter, container, new ServletWebRequest(request), factory);
//                System.out.println(v.getClass());
                System.out.println("[" + parameter.getParameterIndex() + "] " + str + parameter.getParameterType().getSimpleName() + " " + parameter.getParameterName() + "->" + v);
            } else {
                System.out.println("[" + parameter.getParameterIndex() + "] " + str + parameter.getParameterType().getSimpleName() + " " + parameter.getParameterName());
            }

        只解析了@RequestParam注解的参数值

         但是可以看test中的第二个参数是没有解析到值的,该参数是隐式的使用了@RequestParam注解。RequestParamMethodArgumentResolver构造的第二个参数,如果填true,则可以进行识别。

RequestParamMethodArgumentResolver resolver = new RequestParamMethodArgumentResolver(beanFactory,false);

        在上面的代码中添加了针对@RequestParam注解参数解析器:

RequestParamMethodArgumentResolver resolver = new RequestParamMethodArgumentResolver(beanFactory,false);

        如果需要对剩下的注解添加解析器,可以使用组合的方式,一次性加入所有的参数解析器:

   // 多个解析器组合
            HandlerMethodArgumentResolverComposite composite = new HandlerMethodArgumentResolverComposite();
            composite.addResolvers(
                    //                                          false 表示必须有 @RequestParam
                    new RequestParamMethodArgumentResolver(beanFactory, false),
                    new PathVariableMethodArgumentResolver(),
                    new RequestHeaderMethodArgumentResolver(beanFactory),
                    new ServletCookieValueMethodArgumentResolver(beanFactory),
                    new ExpressionValueMethodArgumentResolver(beanFactory),
                    new ServletRequestMethodArgumentResolver(),
                    new ServletModelAttributeMethodProcessor(false), // 必须有 @ModelAttribute
                    new RequestResponseBodyMethodProcessor(Collections.singletonList(new MappingJackson2HttpMessageConverter())),
                    new ServletModelAttributeMethodProcessor(true), // 省略了 @ModelAttribute
                    new RequestParamMethodArgumentResolver(beanFactory, true) // 省略 @RequestParam
            );

        后续判断是否支持参数,获取对应的参数时,只需要采用组合的对象即可,此外,加上了@ModelAttribute注解的参数,还会将模型数据放入ModelAndViewContainer中。

2、参数名的获取

        在上面的案例中,能获取到参数名是因为加入了参数名解析器:

 parameter.initParameterNameDiscovery(new DefaultParameterNameDiscoverer());

        下面模拟一种无法获取参数名的情况,首先创建一个类,其中有foo(String name,int age)方法:

        手动进行编译,会发现参数名丢了:

        那么如何才能保留参数名?可以在编译时加-parameters参数:

        没有加参数时,通过javap -c -v反编译的结果,在方法上没有与参数名有关的信息:

       加上参数反编译后,多了一些信息记录方法参数名称

        也可以在编译时加入-g选项:

        这样做反编译后会生成一个本地变量表:

两者大致的区别:MethodParameters中的信息可以通过反射获取,但是LocalVariableTable可以通过ASM获取。

3、转换接口

3.1、类型转换

        在参数解析器的案例中,存在这样的情况:

@RequestParam("age") int age

        因为表单传递的参数类型默认都是String字符串,在案例中我们是定义了类型转换,在Spring中,类型转换又分为两套底层转换和一套高层实现:

        第一套底层转换:

        

  • Printer 把其它类型转为 String

  • Parser 把 String 转为其它类型

  • Formatter 是Printer 和Parser 共同实现的接口,综合 Printer 与 Parser 功能

  • Converter 可转换任意类型

  • Printer、Parser、Converter 经过适配转换成 GenericConverter 放入 Converters 集合

  • FormattingConversionService 的主要作用是将一个对象从一种表示形式转换为另一种表示形式,或者将一个对象格式化为字符串形式,以便在用户界面中显示或者从用户界面中读取。

        第二套底层转换:

  • PropertyEditor 把 String 与其它类型相互转换

  • PropertyEditorRegistry 可以注册多个 PropertyEditor 对象

  • 与第一套接口直接可以通过 FormatterPropertyEditorAdapter 来进行适配

        高层接口:

  • SimpleTypeConverter 仅做类型转换

  • BeanWrapperImpl 为 bean 的属性赋值,当需要时做类型转换,通过get()、set()方法

  • DirectFieldAccessor 为 bean 的属性赋值,当需要时做类型转换,无需get()、set()方法,直接通过字段即可

  • ServletRequestDataBinder 为 bean 的属性执行绑定(将请求参数中的信息绑定到java对象上),当需要时做类型转换,根据 directFieldAccess的布尔值选择通过get()、set()方法还是通过字段,具备校验与获取校验结果功能。

  • 上述四个接口都实现了 TypeConverter 这个高层转换接口,在转换时,会用到 TypeConverter Delegate 委派ConversionService 与 PropertyEditorRegistry 真正执行转换(Facade 门面模式)

    • 首先看是否有自定义转换器, @InitBinder 添加的即属于这种 (用了适配器模式把 Formatter 转为需要的 PropertyEditor)

    •  再看有没有 ConversionService 转换(是第一套底层FormattingConversionService 的顶级接口

    • 再利用默认的 PropertyEditor 转换(是第二套底层PropertyEditor的顶级接口

    • 最后有一些特殊处理


        SimpleTypeConverter:只有类型转换的功能

// 仅有类型转换的功能
SimpleTypeConverter typeConverter = new SimpleTypeConverter();
Integer number = typeConverter.convertIfNecessary("13", int.class);
Date date = typeConverter.convertIfNecessary("1999/03/04", Date.class);
System.out.println(number);
System.out.println(date);

        BeanWrapperImpl:为bean的属性赋值,需要bean具有get()、set()方法

 // 利用反射原理, 为 bean 的属性赋值
 MyBean target = new MyBean();
 BeanWrapperImpl wrapper = new BeanWrapperImpl(target);
 wrapper.setPropertyValue("a", "10");
 wrapper.setPropertyValue("b", "hello");
 wrapper.setPropertyValue("c", "1999/03/04");
 System.out.println(target);

        DirectFieldAccessor:为 bean 的属性赋值,bean无需get()、set()方法

// 利用反射原理, 为 bean 的属性赋值
 MyBean target = new MyBean();
 DirectFieldAccessor accessor = new DirectFieldAccessor(target);
 accessor.setPropertyValue("a", "10");
 accessor.setPropertyValue("b", "hello");
 accessor.setPropertyValue("c", "1999/03/04");
 System.out.println(target);

        ServletRequestDataBinder:在web环境下,将请求参数中的信息绑定到java对象上

  // web 环境下数据绑定
  MyBean target = new MyBean();
  ServletRequestDataBinder dataBinder = new ServletRequestDataBinder(target);
  MockHttpServletRequest request = new MockHttpServletRequest();
  request.setParameter("a", "10");
  request.setParameter("b", "hello");
  request.setParameter("c", "1999/03/04");

  dataBinder.bind(new ServletRequestParameterPropertyValues(request));

  System.out.println(target);

3.2、绑定器工厂

        假设我们现在有两个内部类:

public static class User {
//        @DateTimeFormat(pattern = "yyyy|MM|dd")
        private Date birthday;
        private Address address;

        public Address getAddress() {
            return address;
        }

        public void setAddress(Address address) {
            this.address = address;
        }

        public Date getBirthday() {
            return birthday;
        }

        public void setBirthday(Date birthday) {
            this.birthday = birthday;
        }

        @Override
        public String toString() {
            return "User{" +
                   "birthday=" + birthday +
                   ", address=" + address +
                   '}';
        }
    }

    public static class Address {
        private String name;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        @Override
        public String toString() {
            return "Address{" +
                   "name='" + name + '\'' +
                   '}';
        }
    }

        发送模拟请求:

  MockHttpServletRequest request = new MockHttpServletRequest();
  request.setParameter("birthday", "1999|01|02");
  request.setParameter("address.name", "西安");

        通过默认的ServletRequestDataBinder将请求参数中的信息绑定到java对象上

  ServletRequestDataBinder dataBinder = new ServletRequestDataBinder(target);
  dataBinder.bind(new ServletRequestParameterPropertyValues(request));

        birthday字段的值并没有被绑定上,原因在于,默认的转换器无法识别yyyy|MM|dd这样的日期格式:

         这种情况就需要自定义转换器进行扩展:

         在进行自定义扩展前,我们需要换一种ServletRequestDataBinder的实现方式,即使用ServletRequestDataBinderFactory,以便于加入各种扩展:

ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null, null);
factory.createBinder(new ServletWebRequest(request),target,"user");

        注意此时是没有任何扩展功能的,依旧无法对birthday字段的值进行绑定。

        ServletRequestDataBinderFactory的有参构造:

  • List<InvocableHandlerMethod> binderMethods:它封装了处理程序方法的相关信息,如方法本身、所属的 Controller、方法参数等。通常与PropertyEditor转换接口配合使用
  • initializer:在数据绑定过程中应用自定义的初始化逻辑。通常与ConversionService 转换接口配合使用
3.2.1、自定义转换方式一

        使用第二套底层转换接口PropertyEditor

        首先自定义一个类对转换器进行扩展,将来在执行factory.createBinder 时会回调该方法

        @InitBinder: 用于标记一个方法,该方法用于初始化 DataBinder对象,从而自定义数据绑定的行为。在控制器类中使用 @InitBinder注解标记的方法会在控制器处理请求之前被调用,可以用来注册自定义的属性编辑器、验证器等。

 static class MyController {
        @InitBinder
        public void aaa(WebDataBinder dataBinder) {
            // 扩展 dataBinder 的转换器
            dataBinder.addCustomFormatter(new MyDateFormatter("用 @InitBinder 方式扩展的"));
        }
    }

        MyDateFormatter中编写了具体扩展的逻辑,实现了Formatter接口:

public class MyDateFormatter implements Formatter<Date> {
    private static final Logger log = LoggerFactory.getLogger(MyDateFormatter.class);
    private final String desc;

    public MyDateFormatter(String desc) {
        this.desc = desc;
    }

    @Override
    public String print(Date date, Locale locale) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy|MM|dd");
        return sdf.format(date);
    }

    @Override
    public Date parse(String text, Locale locale) throws ParseException {
        log.debug(">>>>>> 进入了: {}", desc);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy|MM|dd");
        return sdf.parse(text);
    }


}

        将转换器扩展类封装成InvocableHandlerMethod对象,并且新建工厂,创建绑定器:

InvocableHandlerMethod method = new InvocableHandlerMethod(new MyController(), MyController.class.getMethod("aaa", WebDataBinder.class));
ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(Collections.singletonList(method), null);
WebDataBinder dataBinder = factory.createBinder(new ServletWebRequest(request), target, "user");
dataBinder.bind(new ServletRequestParameterPropertyValues(request));

3.2.2、自定义转换方式二

       使用第一套底层转换接口的ConversionService

FormattingConversionService service = new FormattingConversionService();
        service.addFormatter(new MyDateFormatter("用 ConversionService 方式扩展转换功能"));
 ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
initializer.setConversionService(service);
ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null, initializer);
WebDataBinder dataBinder = factory.createBinder(new ServletWebRequest(request), target, "user");
dataBinder.bind(new ServletRequestParameterPropertyValues(request));

 3.2.3、两种转换方式结合使用

        当两种转换方式结合使用时,第二套底层转换接口PropertyEditor的优先级别更高,上文也提到过:

InvocableHandlerMethod method = new InvocableHandlerMethod(new MyController(), MyController.class.getMethod("aaa", WebDataBinder.class));
FormattingConversionService service = new FormattingConversionService();
service.addFormatter(new MyDateFormatter("用 ConversionService 方式扩展转换功能"));
ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
initializer.setConversionService(service);
ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(Collections.singletonList(method), initializer); WebDataBinder dataBinder = factory.createBinder(new ServletWebRequest(request), target, "user");
dataBinder.bind(new ServletRequestParameterPropertyValues(request));

3.2.4、使用默认方式转换

        最后还可以通过默认方式进行转换:

ApplicationConversionService service = new ApplicationConversionService();
ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
initializer.setConversionService(service);
ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null, initializer);
WebDataBinder dataBinder = factory.createBinder(new ServletWebRequest(request), target, "user");
dataBinder.bind(new ServletRequestParameterPropertyValues(request));

        但是在实体类对应的字段上要加上

@DateTimeFormat(pattern = "yyyy|MM|dd")
private Date birthday;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/583250.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Python-100-Days: Day06 Functions and Modules

函数的作用 编程大师Martin Fowler先生曾经说过&#xff1a;“代码有很多种坏味道&#xff0c;重复是最坏的一种&#xff01;”&#xff0c;要写出高质量的代码首先要解决的就是重复代码的问题。可以将特定的功能封装到一个称之为“函数”的功能模块中&#xff0c;在需要的时候…

MyBatis(环境配置+基本CRUD)

文章目录 1.基本介绍1.为什么需要MyBatis&#xff1f;2.MyBatis介绍3.MyBatis工作示意图4.MyBatis的优势 2.快速入门文件目录1.需求分析2.数据库表设计3.父子模块环境配置1.创建maven父项目2.删除父项目的src目录3.pom.xml文件文件解释 4.创建子模块1.新建一个Module2.创建一个…

面向对象编程三大特征:封装、继承、多态

封装、继承、多态 1. 封装 1.1 介绍 封装(encapsulation)就是把抽象出的数据 [属性] 和对数据的操作 [方法] 封装在一起,数据被保护在内部,程序的其它部分只有通过被授权的操作 [方法] ,才能对数据进行操作。 1.2 封装的理解和好处 1) 隐藏实现细节:方法(连接数据库)<…

UE Snap03 启动参数设置

UE Snap03 启动参数设置 UE打包后传入自定义参数及解析。 void UGameInstance::StartGameInstance() {Super::StartGameInstance();UE_LOG(LogTemp, Warning, TEXT("--StartGameInstance--"));FString param;FParse::Value(FCommandLine::Get(), TEXT("-UserN…

Python | Leetcode Python题解之第50题Pow(x,n)

题目&#xff1a; 题解&#xff1a; class Solution:def myPow(self, x: float, n: int) -> float:def quickMul(N):ans 1.0# 贡献的初始值为 xx_contribute x# 在对 N 进行二进制拆分的同时计算答案while N > 0:if N % 2 1:# 如果 N 二进制表示的最低位为 1&#xf…

新手一文掌握 ea怎么注册?ea官网注册账号的详细教程

新手一文掌握 ea怎么注册&#xff1f;ea官网注册账号的详细教程 知名游戏平台EA平台&#xff0c;说到这个各位游戏玩家肯定不会陌生是全球知名的互动娱乐软件公司美国艺电&#xff08;Electronic Arts&#xff09;旗下的游戏平台。该平台主营电子游戏的开发、出版和销售业务&…

万兆以太网MAC设计(10)UDP协议解析以及模块设计

文章目录 前言&#xff1a;UDP报文格式一、UDP模块设计二、仿真总结&#xff1a; 前言&#xff1a;UDP报文格式 参考&#xff1a;https://sunyunqiang.com/blog/udp_protocol/ UDP (User Datagram Protocol) 是常用的传输层协议之一, 它向应用层提供无连接, 不可靠, 尽最大努力…

GitHub Copilot申请和使用

GitHub Copilot申请和使用 文章目录 前言一、申请二、使用总结 前言 之前已经成功进行了Github学生认证&#xff0c;今天邮件通知之前的学生认证已经通过。那么就去进行GitHub Copilot申请和使用。 前面准备&#xff1a;Github学生认证 一、申请 进入github的settings&#x…

上位机图像处理和嵌入式模块部署(树莓派4b开机界面程序自启动)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 前面我们学习了如何在树莓派4b上面开发qt&#xff0c;也学习了如何用/etc/rc.local启动控制台程序&#xff0c;那今天我们继续学习一下如何利用树莓…

selenium 4.x 验证码处理(python)

验证码处理 一般情况公司如果涉及web自动化测试需要对验证码进行处理的方式一般有一下几种&#xff1a; 关闭验证码功能&#xff08;开发处理&#xff09;设置万能验证码&#xff08;开发处理&#xff09;使用智能识别库进行验证 通过第三方打码平台识别验证码 1. 跳过验证功…

视频转换过程中的几个基本注意事项

1.迟滞 海康的摄像头迟滞大概会到1秒的量级&#xff0c;一般如果你自己搭个框架做转发&#xff0c;迟滞有时会达到20秒&#xff0c;这是为什么呢&#xff1f;请看例程&#xff1a; class VideoCamera(object):def __init__(self):# 打开系统默认摄像头self.cap cv2.VideoCaptu…

看看大家都在做哪些有趣的项目

最近发现两个比较有趣的项目 1.中国独立开发者项目列表 该项目旨在聚合中国独立开发者的项目&#xff0c;分享开发者们正在进行的工作&#xff0c;项目列表包括网站或 App&#xff0c;并且正在持续更新中 项目分为程序员版和主版面&#xff1a; 程序员版&#xff1a;用户是程…

docker compose安装redis

一、安装准备 在docker hub查看redis镜像版本。查看地址如下&#xff1a; Dockerhttps://hub-stage.docker.com/_/redis/tags 二、拉取docker镜像 我这里用redis:6.2.14版本&#xff0c;先拉取镜像。命令如下&#xff1a; docker pull redis:6.2.14 查看刚刚下载的镜像&am…

M2 Mac mini跑Llama3

前言 在4-19左右&#xff0c;Meta 宣布正式推出下一代开源大语言模型 Llama 3&#xff1b;共包括 80 亿和 700 亿参数两种版本&#xff0c;号称 “是 Llama 2 的重大飞跃”&#xff0c;并为这些规模的 LLM 确立了新的标准。实际上笔者早就体验过&#xff0c;只不过自己电脑没什…

nuxt3使用记录五:禁用莫名其妙的Tailwind CSS(html文件大大减小)

发现这个问题是因为&#xff0c;今天我突然很好奇&#xff0c;我发现之前构建的自动产生的200.html和404.html足足290k&#xff0c;怎么这么大呢&#xff1f;不是很占用我带宽&#xff1f; 一个啥东西都没有的静态页面&#xff0c;凭啥这么大&#xff01;所以我就想着手动把他…

matlab新手快速上手6(引力搜索算法)

本文根据一个较为简单的matlab引力搜索算法框架详细分析蚁群算法的实现过程&#xff0c;对matlab新手友好&#xff0c;源码在文末给出。 引力搜索算法简介&#xff1a; 引力搜索算法是一种启发式优化算法&#xff0c;最初于2009年由伊朗的Esmat Rashedi、Hossein Nezamabadi-p…

MyBatis(注解方式操作)

文章目录 1.注解方式操作文件目录1.快速入门&#xff08;完整步骤&#xff09;1.pom.xml&#xff08;完整&#xff09;2.resources/jdbc.properties外部配置文件&#xff08;根据实际情况修改参数&#xff09;3.在resources/mybatis-config.xml&#xff08;完整&#xff09;中配…

Linux基本指令(3)

目录 时间相关的指令&#xff1a; 1.在显示方面&#xff0c;使用者可以设定欲显示的格式&#xff0c;格式设定为一个加好后接数个标记&#xff0c;其中常用的标记列表如下&#xff1a; 2.在设定时间方面&#xff1a; 3.时间戳&#xff1a; Cal指令&#xff1a; find指令&a…

韩国云主机安装AMP环境要求科普

AMP环境&#xff0c;即Apache、MySQL和PHP的组合&#xff0c;是许多网站开发者和运维人员常用的环境配置。在韩国云主机上安装AMP环境&#xff0c;需要满足一定的要求以确保顺利运行和高效性能。下面我们将对韩国云主机安装AMP环境的要求进行科普。 首先&#xff0c;韩国云主机…

深入探索MySQL锁机制:揭秘死锁原因与RC隔离级别下的事务处理

MySQL锁的类型及死锁概述 在数据库系统中&#xff0c;为了保证事务可以正确地访问数据&#xff0c;防止数据不一致&#xff0c;通常会使用锁机制。MySQL作为广泛使用的数据库之一&#xff0c;其InnoDB存储引擎提供了多种锁类型&#xff0c;主要包括行锁&#xff08;Record Loc…