MyBatis核心机制

news2024/10/1 7:33:20

实现MyBatis核心机制环境搭建

1.核心框架示意图

img

2.模块搭建

1.创建maven项目

CleanShot 2024-08-08 at 15.29.52@2x

2.引入依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.sunxiansheng</groupId>
        <artifactId>core_mechanisms</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <artifactId>sun-mybatis</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <!-- 解析xml -->
        <dependency>
            <groupId>dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>1.6.1</version>
        </dependency>
        <!-- mysql -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.22</version>
        </dependency>
        <!-- lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.30</version>
        </dependency>
        <!-- junit -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
        </dependency>
    </dependencies>

</project>
3.连接数据库
4.数据库表设计
CREATE DATABASE `sun_mybatis`;
USE `sun_mybatis`;

CREATE TABLE `monster`
(
    `id`       INT          NOT NULL AUTO_INCREMENT,
    `age`      INT          NOT NULL,
    `birthday` DATE DEFAULT NULL,
    `email`    VARCHAR(255) NOT NULL,
    `gender`   TINYINT      NOT NULL,
    `name`     VARCHAR(255) NOT NULL,
    `salary`   DOUBLE       NOT NULL,
    PRIMARY KEY (`id`)
) CHARSET = utf8;

INSERT INTO `monster` (`age`, `birthday`, `email`, `gender`, `name`, `salary`)
VALUES (25, '1998-01-15', 'example@example.com', 1, 'John Doe', 50000.00);

3.设计图

img

读取配置文件,得到数据库连接

1.目录

CleanShot 2024-08-08 at 16.57.51@2x

2.sun-config.xml 配置文件

<?xml version="1.0" encoding="utf-8" ?>
<!-- 配置数据库连接信息 -->
<database>
    <!-- 驱动 -->
    <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
    <!-- 连接地址 -->
    <property name="url" value="jdbc:mysql://bj--grp-.sql.tencentcdb.com:24169/sun_mybatis?useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>    <!-- 用户名 -->
    <!-- 用户名 -->
    <property name="username" value=""/>
    <!-- 密码 -->
    <property name="password" value=""/>
</database>

3.SunConfiguration.java 读取配置文件获取数据库连接

package com.sunxiansheng.mybatis.sqlsession;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.List;

/**
 * Description: 读取xml配置文件
 *
 * @Author sun
 * @Create 2024/8/8 16:25
 * @Version 1.0
 */
public class SunConfiguration {

    private static final Logger log = LoggerFactory.getLogger(SunConfiguration.class);
    private static ClassLoader loader = ClassLoader.getSystemClassLoader();

    /**
     * 读取xml文件
     *
     * @param resource 这个必须是类路径下的文件路径,不用带第一个斜杠,sun-config.xml就可以
     * @return
     */
    public Connection build(String resource) {
        InputStream resourceAsStream = loader.getResourceAsStream(resource);
        // 使用dom4j解析配置文件
        SAXReader saxReader = new SAXReader();
        try {
            // 获取文档
            Document document = saxReader.read(resourceAsStream);
            // 根元素
            Element rootElement = document.getRootElement();
            // 根元素下的所有property元素
            List<Element> property = rootElement.elements("property");
            // 初始化连接信息
            String driverClassName = null;
            String url = null;
            String username = null;
            String password = null;
            // 遍历property元素获取连接信息
            for (Element element : property) {
                String name = element.attributeValue("name");
                String value = element.attributeValue("value");
                switch (name) {
                    case "driverClassName":
                        driverClassName = value;
                        log.info("加载驱动类:{}", value);
                        break;
                    case "url":
                        url = value;
                        log.info("加载url:{}", value);
                        break;
                    case "username":
                        username = value;
                        log.info("加载用户名:{}", value);
                        break;
                    case "password":
                        password = value;
                        log.info("加载密码:{}", value);
                        break;
                    default:
                        throw new RuntimeException("未知的property属性名");
                }
            }
            // 类加载驱动类
            Class.forName(driverClassName);
            return DriverManager.getConnection(url, username, password);
        } catch (DocumentException | ClassNotFoundException | SQLException e) {
            log.error("解析xml配置文件失败", e);
            throw new RuntimeException(e);
        }
    }

}

4.SunConfigurationTest.java 测试

package com.sunxiansheng.mybatis.sqlsession;

import org.junit.Test;

import java.sql.Connection;

/**
 * Description: 读取xml配置文件
 *
 * @Author sun
 * @Create 2024/8/8 16:25
 * @Version 1.0
 */
public class SunConfigurationTest {

    @Test
    public void build() {
        SunConfiguration sunConfiguration = new SunConfiguration();
        Connection build = sunConfiguration.build("sun-config.xml");
        System.out.println("build = " + build);
    }

}

使用SunExecutor来执行SQL

1.目录

CleanShot 2024-08-09 at 13.02.47@2x

2.Monster.java

package com.sunxiansheng.mybatis.entity;

import lombok.Data;

import java.io.Serializable;
import java.util.Date;

@Data
public class Monster implements Serializable {

    private Integer id;

    private Integer age;

    private Date birthday;

    private String email;

    private Byte gender;

    private String name;

    private Double salary;

    private static final long serialVersionUID = 1L;
}

3.Executor.java

package com.sunxiansheng.mybatis.sqlsession;

/**
 * Description: 执行器接口
 *
 * @Author sun
 * @Create 2024/8/9 12:37
 * @Version 1.0
 */
public interface Executor {

    public <T> T query(String sql, Object parameter);

}

4.SunExecutor.java 具体的执行器

package com.sunxiansheng.mybatis.sqlsession;

import com.sunxiansheng.mybatis.entity.Monster;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

/**
 * Description: 执行器
 *
 * @Author sun
 * @Create 2024/8/9 12:39
 * @Version 1.0
 */
public class SunExecutor implements Executor{

    public static final SunConfiguration configuration = new SunConfiguration();

    private static final Logger log = LoggerFactory.getLogger(SunExecutor.class);

    @Override
    public <T> T query(String sql, Object parameter) {
        // 获取DB连接
        Connection connection = configuration.build("sun-config.xml");
        log.info("获取连接:{}", connection);
        ResultSet resultSet = null;
        PreparedStatement preparedStatement = null;
        // 实体类
        Monster monster = new Monster();
        try {
            // sql预处理
            preparedStatement = connection.prepareStatement(sql);
            // 填参数
            preparedStatement.setString(1, parameter.toString());
            // 执行查询
            resultSet = preparedStatement.executeQuery();
            // 将结果封装到实体类中(写死了)
            while (resultSet.next()) {
                monster.setId(resultSet.getInt("id"));
                monster.setAge(resultSet.getInt("age"));
                monster.setBirthday(resultSet.getDate("birthday"));
                monster.setEmail(resultSet.getString("email"));
                monster.setGender(resultSet.getByte("gender"));
                monster.setName(resultSet.getString("name"));
                monster.setSalary(resultSet.getDouble("salary"));
            }
            log.info("查询结果:{}", monster);
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            // 关闭资源
            try {
                if (resultSet != null) {
                    resultSet.close();
                }
                if (preparedStatement != null) {
                    preparedStatement.close();
                }
                if (connection != null) {
                    connection.close();
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
        // 这里的T是由类型推断来指定的,也就是使用什么类型接受,就返回什么类型
        return (T) monster;
    }

}

5.SunConfigurationTest.java 测试

@Test
public void executor() {
    SunExecutor sunExecutor = new SunExecutor();
    // 这里是使用的类型推断自动转换类型
    Monster monster = sunExecutor.query("select * from monster where id = ?", 1);
}

将SqlSession封装到执行器

1.目录

CleanShot 2024-08-09 at 13.17.02@2x

2.SunSqlSession.java

package com.sunxiansheng.mybatis.sqlsession;

/**
 * Description: SqlSession
 *
 * @Author sun
 * @Create 2024/8/9 13:08
 * @Version 1.0
 */
public class SunSqlSession {

    // 执行器(操作db)
    private Executor executor = new SunExecutor();
    // 配置(用于获取连接)
    private SunConfiguration configuration = new SunConfiguration();

    public <T> T selectOne(String statement, Object parameter) {
        return executor.query(statement, parameter);
    }
}


3.测试

@Test
public void selectOne() {
    SunSqlSession sunSqlSession = new SunSqlSession();
    Monster monster = sunSqlSession.selectOne("select * from monster where id = ?", 1);
    System.out.println("monster = " + monster);
}

开发MapperBean和Function

1.目录

CleanShot 2024-08-09 at 13.39.36@2x

2.MonsterMapper.java

package com.sunxiansheng.mapper;

import com.sunxiansheng.entity.Monster;

/**
 * Description: MonsterMapper
 *
 * @Author sun
 * @Create 2024/8/9 13:21
 * @Version 1.0
 */
public interface MonsterMapper {

    public Monster getMonsterById(Integer id);

}

3.MonsterMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<mapper namespace="com.sunxiansheng.mapper.MonsterMapper">
    <select id="getMonsterById" resultType="com.sunxiansheng.entity.Monster">
        select * from monster where id = ?
    </select>
</mapper>

4.Function.java 记录对应的mapper.xml的方法信息

package com.sunxiansheng.mybatis.config;

import lombok.Data;

/**
 * Description: 记录对应的mapper.xml的方法信息
 *
 * @Author sun
 * @Create 2024/8/9 13:31
 * @Version 1.0
 */
@Data
public class Function {

    /**
     * sql类型
     */
    private String sqlType;

    /**
     * 方法名
     */
    private String funcName;

    /**
     * SQL语句
     */
    private String sql;

    /**
     * 返回类型
     */
    private Object resultType;

}

5.MapperBean.java 将mapper接口信息进行封装

package com.sunxiansheng.mybatis.config;

import java.util.List;

/**
 * Description: 将mapper接口信息进行封装
 *
 * @Author sun
 * @Create 2024/8/9 13:35
 * @Version 1.0
 */
public class MapperBean {

    /**
     * 接口全路径
     */
    private String interfaceName;

    /**
     * 方法列表
     */
    private List<Function> functions;

}

读取xml文件解析MapperBean

1.目录

CleanShot 2024-08-09 at 14.03.00@2x

2.Function.java 加链式调用注解

CleanShot 2024-08-09 at 14.03.31@2x

3.MapperBean.java 加Data注解

CleanShot 2024-08-09 at 14.03.51@2x

4. SunConfiguration.java 增加解析MapperBean的方法

/**
 * 读取mapper.xml文件,构建MapperBean
 * @param path
 * @return
 */
public MapperBean readMapper(String path) {
    // 要返回的结果
    MapperBean mapperBean = new MapperBean();
    // 使用类加载器读取配置文件
    ClassLoader loader = ClassLoader.getSystemClassLoader();
    InputStream resourceAsStream = loader.getResourceAsStream(path);
    // 解析xml文件
    SAXReader saxReader = new SAXReader();
    try {
        // 获取文档
        Document document = saxReader.read(resourceAsStream);
        // 获取根元素
        Element rootElement = document.getRootElement();
        // 获取根元素的属性:namespace
        String namespace = rootElement.attributeValue("namespace");
        // 获取子元素
        List<Element> elements = rootElement.elements();
        // 遍历子元素
        List<Function> functions = elements.stream().map(
                element -> {
                    Function function = new Function();
                    // 获取sql类型
                    String sqlType = element.getName();
                    // 获取方法名
                    String funcName = element.attributeValue("id");
                    // 获取返回类型
                    String resultType = element.attributeValue("resultType");
                    // 获取sql语句
                    String sql = element.getTextTrim();
                    // 封装到Function对象中
                    function.setSqlType(sqlType)
                            .setFuncName(funcName)
                            .setResultType(resultType)
                            .setSql(sql);
                    return function;
                }
        ).collect(Collectors.toList());
        // 构建MapperBean
        mapperBean.setInterfaceName(namespace);
        mapperBean.setFunctions(functions);
    } catch (DocumentException e) {
        throw new RuntimeException(e);
    }
    // 输出mapperBean的日志
    log.info("mapperBean:{}", mapperBean);
    return mapperBean;
}

5.SunConfigurationTest.java 测试

    @Test
    public void readMapper() {
        SunConfiguration sunConfiguration = new SunConfiguration();
        sunConfiguration.readMapper("MonsterMapper.xml");
    }

CleanShot 2024-08-09 at 14.05.29@2x

动态代理Mapper方法

1.目录

CleanShot 2024-08-09 at 14.58.13@2x

2.MapperProxyFactory.java 代理工厂,可以获取接口的代理对象

package com.sunxiansheng.mybatis.sqlsession;

import java.lang.reflect.Proxy;

/**
 * Description: 代理工厂
 *
 * @Author sun
 * @Create 2024/8/9 14:14
 * @Version 1.0
 */
public class MapperProxyFactory {

    public static <T> T getMapperProxy(Class<T> clazz, SunSqlSession sunSqlSession, SunConfiguration sunConfiguration) {
        return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, new SunMapperProxy(sunSqlSession, clazz, sunConfiguration));
    }

}

3.SunMapperProxy.java 代理逻辑

package com.sunxiansheng.mybatis.sqlsession;

import com.sunxiansheng.mybatis.config.Function;
import com.sunxiansheng.mybatis.config.MapperBean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.List;

/**
 * Description: 代理Mapper接口
 *
 * @Author sun
 * @Create 2024/8/9 14:08
 * @Version 1.0
 */
public class SunMapperProxy implements InvocationHandler {

    private static final Logger log = LoggerFactory.getLogger(SunMapperProxy.class);
    /**
     * 用于连接数据库和执行sql
     */
    private SunSqlSession sunSqlSession;

    /**
     * mapper文件名
     */
    private String mapperFile;

    /**
     * 配置类(可以根据mapper文件名来获取MapperBean)
     */
    private SunConfiguration sunConfiguration;

    public SunMapperProxy(SunSqlSession sunSqlSession, Class<?> mapperFile, SunConfiguration sunConfiguration) {
        this.sunSqlSession = sunSqlSession;
        this.mapperFile = mapperFile.getSimpleName() + ".xml";
        this.sunConfiguration = sunConfiguration;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 获取方法名
        String methodName = method.getName();
        // 获取MapperBean
        MapperBean readMapper = sunConfiguration.readMapper(mapperFile);
        // 获取Function列表,过滤出需要执行的Function
        List<Function> functions = readMapper.getFunctions();
        Function targetFunction = null;
        for (Function function : functions) {
            if (methodName.equals(function.getFuncName())) {
                targetFunction = function;
                break;
            }
        }
        // 获取Function的信息
        String sqlType = targetFunction.getSqlType();
        String sql = targetFunction.getSql();
        // 将sql的第一个问号使用参数替换,使用字符串处理
        // 根据sqlType执行sql
        if (sqlType.equals("select")) {
            Object selected = sunSqlSession.selectOne(sql, args[0]);
            log.info("invoke查询结果:{}", selected);
            return selected;
        }
        return null;
    }
}

4.测试

@Test
public void getResult() {
    MonsterMapper mapperProxy = MapperProxyFactory.getMapperProxy(MonsterMapper.class, new SunSqlSession(), new SunConfiguration());
    Monster monsterById = mapperProxy.getMonsterById(1);
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

超维机器人在工业与能源领域的具身智能探索和应用

具身智能&#xff08;Embodied AI&#xff09;是指机器人能够通过其物理形态与环境的交互&#xff0c;进行感知、学习、决策和执行&#xff0c;从而完成复杂任务的能力。具身智能强调机器人不仅要具备感知环境和分析数据的能力&#xff0c;还要能够通过身体的行为和物理互动来适…

zabbix5.0与7.0版本区别 切换建议

Zabbix5.0和Zabbix7.0的区别 1. 性能和扩展性优化 1.1 高效的数据处理和存储 优化的数据库性能&#xff1a; Zabbix 7.0 在数据库层面进行了多项优化&#xff0c;以减少查询延迟和提高数据处理速度。这包括对数据库结构的改进和索引优化&#xff0c;使得大规模数据的读取和写…

Linux云计算 |【第二阶段】SECURITY-DAY3

主要内容&#xff1a; Prometheus监控服务器、Prometheus被监控端、Grafana监控可视化 补充&#xff1a;Zabbix监控软件不自带LNMP和DB数据库&#xff0c;需要自行手动安装配置&#xff1b;Prometheus监控软件自带WEB页面和DB数据库&#xff1b;Prometheus数据库为时序数据库&…

<数据集>翻越栏杆行为识别数据集<目标检测>

数据集格式&#xff1a;VOCYOLO格式 图片数量&#xff1a;512张 标注数量(xml文件个数)&#xff1a;512 标注数量(txt文件个数)&#xff1a;512 标注类别数&#xff1a;1 标注类别名称&#xff1a;[climbing] 使用标注工具&#xff1a;labelImg 标注规则&#xff1a;对类…

十五分钟两百行代码,手写一个vue项目全局通用的弹框

前言&#xff1a; 我们在写vue项目时&#xff0c;弹框是非常常用的组件&#xff0c;并且在同一个项目中&#xff0c;弹框大多类似。所以我们可以抽离封装出一个通用的弹框&#xff1b; 因为vue3可向下兼容&#xff0c;所以作者这边会使用vue2的写法&#xff0c;vue3写法大同小…

vue3 语法糖<script setup>

在 Vue 3 中&#xff0c;<script setup>是一种新的语法糖&#xff0c;它极大地简化了组件的编写方式。 <script setup> 是在单文件组件 (SFC) 中使用组合式 API 的编译时语法糖。当同时使用 SFC 与组合式 API 时该语法是默认推荐。 基本概念 简洁的语法&#xf…

使用Qg波形快速提取模型参数的新方法以准确捕获SiC MOSFET在不同负载条件下的开关特性

来源&#xff1a;Novel Methodology for Fast Model Parameter Extracting Using Qg Waveforms to Accurately Capture Switching Characteristics of SiC MOSFET Under Various Load Conditions&#xff08;ISPSD 24年&#xff09; 摘要 本文提出了一种快速提取模型参数的方…

类和对象(高级)

类和对象&#xff08;高级&#xff09; 一、运算符重载 运算符重载是对已有的运算符 指定新功能。不能创建新运算。 运算符重载关键字operator 思路&#xff1a; 1、弄懂运算符的运算对象的个数。&#xff08;个数决定了 重载函数的参数个数&#xff09; 2、识别运算符左边的…

【css】伪元素实现跟随鼠标移动的渐变效果

主要功能是在按钮上实现鼠标跟随渐变效果。每当用户将鼠标移动到按钮上时&#xff0c;按钮会显示一个以鼠标位置为中心的渐变效果。 1. 核心部分: 监听鼠标在元素内移动 监听鼠标在元素内移动&#xff0c;并触发该事件。 const handleMouseMove (e: MouseEvent) > {if (…

C#知识|语法拾遗:数据类型转换

哈喽&#xff0c;你好啊&#xff0c;我是雷工&#xff01; 其实关于数据转换在任何语言中都会涉及&#xff0c;无论是PLC编程语言&#xff0c;还是SCADA开发中的脚本语言&#xff0c;都会涉及&#xff0c;在前边学习JavaScript的内容时&#xff0c;也看过相关内容&#xff1a; …

[RAG]喂饭教程!Neo4J可视化GraphRAG索引

GraphRAG通过结合知识图谱&#xff0c;增加RAG的全局检索能力。今天我将讲解如何使用Neo4J可视化GraphRAG索引的结果&#xff0c;以便进一步的处理、分析&#xff0c;以小说提取的实体《仙逆》为例&#xff0c;一图胜千言。本文分为4小节&#xff0c;安装neo4j、导入GraphRAG索…

如何做服务端渲染?

node server 接收客户端请求&#xff0c;得到当前的请求url 路径&#xff0c;然后在已有的路由表内查找到对应的组件&#xff0c;拿到需要请求的数据&#xff0c;将数据作为 props、context或者store 形式传入组件 然后基于 react 内置的服务端渲染方法 renderToString()把组件…

vue一键打不同环境的包

1.配置package.json 主要看的是 "build:all": "vue-cli-service build && vue-cli-service build --mode test && vue-cli-service build --mode development", "scripts": {"dev": "vue-cli-service serve"…

【SpringBoot源码】SpringBoot监听机制分析

目录 一、简介 二、SpringBoot事件监听机制 1)加载ApplicationListener监听器实现类 2)获取运行监听器EventPublishingRunListener 3)发布事件 4)Spring事件发布multicastEvent() 一、简介 接下来我们分析下SpringBoot的事件监听机制的源码。 二、SpringBoot事件监…

Android系统安全 — 1-OpenSSL支持的常用加解密算法介绍

常用加解密算法介绍 1. 哈希算法 常见的函数包含MD系列、SHA-1、SHA-2家族、SHA-3家族、SM3等。 1.1 MD5&#xff08;单向散列算法&#xff09; 全称是Message-Digest Algorithm 5&#xff08;信息-摘要算法&#xff09;&#xff0c;经MD2、MD3和MD4发展而来。MD5算法的使用…

线索精细化管理实践:线上推广渠道线索管理的8个要点

在如今线索获取成本越来越高的情况下&#xff0c;如何获取增量线索、经营好存量线索、实现精细化、高效率线索管理对于企业来说至关重要。获取线索是一切行动的开始&#xff0c;与其建立起稳定、持续的信任关系&#xff0c;达成合作甚至引导复购&#xff0c;是整个线索管理链路…

泛微eteams OA对接金蝶云星空写入数据

需求&#xff1a; 公司需要先在OA上对准备生产的订单进行一次量产评审&#xff0c;所有相关人员评审通过后才可以进行生产&#xff0c;导致下工单的人员每次需要把OA上的信息复制到ERP进行审批。 为什么不直接在ERP上审批呢&#xff1f; 首先该节点涉及到很多不用ERP的用户&am…

盘点八月份最好用的五款报表制作工具,涵盖各功能,你都听说过吗?

一、报表制作软件的重要性 在现代企业运营中&#xff0c;数据的处理和分析是至关重要的。如何将复杂的数据信息以直观、易懂的方式展现出来&#xff0c;是提升决策效率和准确性的关键。因此报表制作软件成为企业必备的工具之一&#xff0c;它们通过图表化的方式&#xff0c;帮…

加速网络体验,Squid缓存代理:让浏览如飞,畅享无限网络速度!

作者简介&#xff1a;我是团团儿&#xff0c;是一名专注于云计算领域的专业创作者&#xff0c;感谢大家的关注 座右铭&#xff1a; 云端筑梦&#xff0c;数据为翼&#xff0c;探索无限可能&#xff0c;引领云计算新纪元 个人主页&#xff1a;团儿.-CSDN博客 目录 前言: squ…

低代码表单引擎的核心不仅仅是拖拉拽,深入解析表单的高级功能

作为一名专业的低代码产品经理&#xff0c;我们主要以 to B 的企业级系统为配置化对象&#xff0c;我深知在构建高效、用户友好的企业应用系统时&#xff0c;表单引擎扮演着核心基础的角色。当然拖拉拽快速创建页面是最简单直接的用户体验&#xff0c;然而&#xff0c;在享受拖…