手敲MyBatis(十一章)-支持注解配置执行SQL

news2024/11/19 12:34:09

1.前言

这一章节从题目中也看出来我们要支持注解版的增删改查,可以在Mapper层的接口类的方法上写Sql语句,如:Insert,Update,Delete,Select的这几个基础Sql,如下图,这样就不用在Xml里去写Sql了,但是这就引申出一个问题,我们怎么解析注解的Sql语句,将方法上的SQL内容取出来并按之前的既定流程去处理执行Sql呢?

 我们现在要处理以下几步才能达到目的

1.配置文件中去配置注解类,加上此在解析的时候才能知道什么情况下用XML方式,在什么情况下用注解方式

2.在配置解析时如果是注解类,直接将class值为namespce进行代理注册 

3.开始解析注解,找到Mapper类里所有的方法,每一个方法都判断下是不是加了这4种注解

   3.1.获取Sql内容,传输到以前就有的流程里得到处理过后的Sql语句,SqlSource

   3.2.通过反射获取方法的参数、返回值、参数类型、返回值类型、并将获取到的数据映射到MappedStatement以及ResultMap

4.之后的流程不变

2.Uml类图

看到类图我们知道,这一章节其实也是很简单的,涉及添加的类只有几个,涉及的修改也不多

添加了4个注解类,1个解析注解类MappernnotationBuilder

XmlConfiguration这里修改判断下如果是注解方式直接调用MapperRegistry的addMapper方法将当前的class进行代理注册,

MapperRegistry类的addMapper方法里添加调用MappernnotationBuilder解析注解数据。

MappernnotationBuilder拿到Sql原内容后调用XmlLanguageDriver进行Sql源处理,继续解析参数、返回值、调用MapperBuilderAsisstant的addMappedStatement和addResultMaps方法将所得的数据映射到MappedStatement和ResultMap类中,供后续使用,之后的就还是和之前的流程一样。

 3.代码实现

XMLConfigBuilder类:此类解析Mapper时添加从xml中获取Class,如果这个Class不为空,证明是要解析注解类,通过反射将String的class类转换为类对象,并通过configuration添加到mapper映射里。

// 处理mapper的方法,mapper里有多个sql语句,所以需要List
    private void mapperElement(Element mappers) throws Exception {
        // 得到mybatis-config-datasource.xml的mappers标签里的mapper
        List<Element> mapperList = mappers.elements("mapper");
        for (Element e : mapperList) {
            String resource = e.attributeValue("resource");
            String mapperClass = e.attributeValue("class");
            // xml解析方式
            if (resource != null && mapperClass == null) {
                InputStream inputStream = Resources.getResourceAsStream(resource);
                // 在for循环里每个mapper都重新new一个XMLMapperBuilder,来解析
                XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource);
                mapperParser.parse();
            } else if (resource == null && mapperClass != null) {
                // 注解方式
                Class<?> mapperInterface = Resources.classForName(mapperClass);
                configuration.addMapper(mapperInterface);
            }
        }
    }

 MapperRegistry类:此类里就添加了解析注解类的处理调用。

public <T> void addMapper(Class<T> type) {
        // 是接口
        if (type.isInterface()) {
            if (hasMapper(type)) {
                // 如果重复添加了报错
                throw new RuntimeException("Type " + type + " is already known to the MapperRegistry.");
            }
            // 注册映射器代理工厂
            knownMappers.put(type, new MapperProxyFactory<>(type));


            // 解析注解类语句配置
            MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
            parser.parse();
        }
    }

MapperAnnotationBuilder类:此类本节新添加的类,专门处理注解的解析,如sql,参数,返回值等操作,

1.parse方法,拿到class的所有方法,如IuserDao定义的所有方法。

2.循环遍历每一个方法并调用parseStatement(method)方法,

    2.1 获取参数类型,调用getParameterType(method);
    2.2 获取语言驱动器,目的是为了创造Sql源,调用getLanguageDriver(method)                              2.3 获取数据源,通过调用getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver)方法。

          2.3.1 通过调用getSqlAnnotationType(method)方法,获取注解类型                                                  2.3.2 根据得到的注解类型获取当前方法上的注解信息,并得到方法上的注解值。                            2.3.3 将内容Sql、参数类型、语言驱动器传递,调用buildSqlSourceFromStrings();                                   2.3.3.1 将原Sql、参数类型传递给Sql源进行处理,调用createSql();

3.生成mappedStatementId标识

4.根据注解类型处理获取Sql标签,如INSERT、DELETE,,,,,调用getSqlCommandType(method);

5.判断标签是否是查询,是去解析结果,调用parseResultMap(method);

6.所有的数据已备全,将数据添加到mappedStatement,调用助手类的addMappedStatement()方法。
  

public class MapperAnnotationBuilder {
    private final Set<Class<? extends Annotation>> sqlAnnotationTypes = new HashSet<>();
    private MapperBuilderAssistant assistant;
    private Configuration configuration;
    private Class<?> type;

    public MapperAnnotationBuilder(Configuration configuration, Class<?> type) {
        String resource = type.getName().replace(".", "/") + ".java (best guess)";
        this.assistant = new MapperBuilderAssistant(configuration, resource);
        this.configuration = configuration;
        this.type = type;
        sqlAnnotationTypes.add(Select.class);
        sqlAnnotationTypes.add(Insert.class);
        sqlAnnotationTypes.add(Update.class);
        sqlAnnotationTypes.add(Delete.class);
    }

    /**
      解析
    */
    public void parse() {
        String resource = type.toString();
        if (!configuration.isResourceLoaded(resource)) {
            assistant.setCurrentNamespace(type.getName());
            // 获取类里所有的方法
            Method[] methods = type.getMethods();
            for (Method method : methods) {
                if (!method.isBridge()) {
                    // 解析语句
                    parseStatement(method);
                }
            }

        }
    }

    /**
     * 解析语句
     */
    private void parseStatement(Method method) {
        // 通过方法获取参数类型
        Class<?> parameterTypeClass = getParameterType(method);
        // 获取默认驱动器XmlLanguageDriver
        LanguageDriver languageDriver = getLanguageDriver(method);
        // 根据注解上的Sql内容获取数据源
        SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);

        if (sqlSource != null) {
            // mappedStatementId标识
            final String mappedStatementId = type.getCanonicalName() + "." + method.getName();
            // 获取SQL标签,INSERT、UPDATE...
            SqlCommandType sqlCommandType = getSqlCommandType(method);
            boolean isSelect = sqlCommandType == SqlCommandType.SELECT;

            String resultMapId = null;
            if (isSelect) {
                // 是查询的化解析返回值
                resultMapId = parseResultMap(method);
            }
            // 注册到mappedStatement里
            assistant.addMappedStatement(
                    mappedStatementId,
                    sqlSource,
                    sqlCommandType,
                    parameterTypeClass,
                    resultMapId,
                    getReturnType(method),
                    languageDriver
            );
        }

    }

    /**
     * 重点:DAO 方法的返回类型,如果为 List 则需要获取集合中的对象类型
     */
    private Class<?> getReturnType(Method method) {
        Class<?> returnType = method.getReturnType();
        if (Collection.class.isAssignableFrom(returnType)) {
            Type returnTypeParameter = method.getGenericReturnType();
            if (returnTypeParameter instanceof ParameterizedType) {
                Type[] actualTypeArguments = ((ParameterizedType) returnTypeParameter).getActualTypeArguments();
                if (actualTypeArguments != null && actualTypeArguments.length == 1) {
                    returnTypeParameter = actualTypeArguments[0];
                    if (returnTypeParameter instanceof Class) {
                        returnType = (Class<?>) returnTypeParameter;
                    } else if (returnTypeParameter instanceof ParameterizedType) {
                        returnType = (Class<?>) ((ParameterizedType) returnTypeParameter).getRawType();
                    } else if (returnTypeParameter instanceof GenericArrayType) {
                        Class<?> componentType = (Class<?>) ((GenericArrayType) returnTypeParameter).getGenericComponentType();
                        returnType = Array.newInstance(componentType, 0).getClass();
                    }
                }
            }
        }
        return returnType;
    }

    /** 解析返回类型处理为ResultMap */
    private String parseResultMap(Method method) {
        StringBuffer suffix = new StringBuffer();
        // 拼接参数类型
        for (Class<?> c : method.getParameterTypes()) {
            suffix.append("-");
            suffix.append(c.getSimpleName());
        }
        // 拼接空返回值
        if (suffix.length() < 1) {
            suffix.append("-void");
        }
        //df.middleware.mybatis.dao.IUserDao.queryInfoId-long-void
        String resultMapId = type.getName() + "." + method.getName() + suffix;

        // 添加ResultMap
        Class<?> returnType = getReturnType(method);
        assistant.addResultMap(resultMapId,returnType,new ArrayList<>());
        return resultMapId;
    }

    /** 根据注解类型获取Sql标签 */
    private SqlCommandType getSqlCommandType(Method method) {
        Class<? extends Annotation> type = getSqlAnnotationType(method);
        if (type == null) {
            return SqlCommandType.UNKNOWN;
        }
        return SqlCommandType.valueOf(type.getSimpleName().toUpperCase(Locale.ENGLISH));
    }

    /**
     * 根据注解的值(Sql)获取Sql源
     * */
    private SqlSource getSqlSourceFromAnnotations(Method method, Class<?> parameterType, LanguageDriver languageDriver) {
        try {
            // 获取注解类型
            Class<? extends Annotation> sqlAnnotationType = getSqlAnnotationType(method);
            if (sqlAnnotationType != null) {
                // 根据注解类型获取注解类
                Annotation sqlAnnotation = method.getAnnotation(sqlAnnotationType);
                 // 获取注解类的值-原始Sql语句
               final String[] strings = (String[]) sqlAnnotation.getClass().getMethod("value").invoke(sqlAnnotation);
                // 根据获取的注解类的值构建Sql源
               return buildSqlSourceFromStrings(strings, parameterType, languageDriver);
            }
            return null;
        } catch (Exception e) {
            throw new RuntimeException("Could not find value method on SQL annotation.  Cause: " + e);
        }

    }

    /**
     * 拼接SQL,创造数据源
     * */
    private SqlSource buildSqlSourceFromStrings(String[] strings, Class<?> parameterTypeClass, LanguageDriver languageDriver) {
        final StringBuilder sql = new StringBuilder();
        for (String fragment : strings) {
            sql.append(fragment);
            sql.append(" ");
        }
        return languageDriver.createSqlSource(configuration, sql.toString(), parameterTypeClass);
    }

    /**
     * 获取注解类型
     * */
    private Class<? extends Annotation> getSqlAnnotationType(Method method) {
        for (Class<? extends Annotation> type : sqlAnnotationTypes) {
            Annotation annotation = method.getAnnotation(type);
            if (annotation != null) return type;
        }
        return null;
    }

    /**
     * 获取语言驱动器
     * */
    private LanguageDriver getLanguageDriver(Method method) {
        Class<?> langClass = configuration.getLanguageRegistry().getDefaultDriverClass();
        return configuration.getLanguageRegistry().getDriver(langClass);
    }

    /**
     * 获取参数类型
     * */
    private Class<?> getParameterType(Method method) {
        Class<?> parameterType = null;
        // 根据方法参数获取参数类型
        Class<?>[] parameterTypes = method.getParameterTypes();
        for (Class<?> clazz : parameterTypes) {
            if (!RowBounds.class.isAssignableFrom(clazz) && !ResultHandler.class.isAssignableFrom(clazz)) {
                if (parameterType == null) {
                    parameterType = clazz;
                } else {
                    parameterType = MapperMethod.ParamMap.class;
                }
            }
        }
        return parameterType;
    }
}

MapperBuilderAssistant类:

此类添加addResultMap()方法,将ResultMap存储到Configuration中的ResultMaps变量里,并修改了setStatementResultMap()方法里补充了之前的逻辑空缺,如果resultMap不为空,则将Configuration的ResultMaps变量数据取出存储到MappedStatement里。

public class MapperBuilderAssistant{
 //id=此形式 df.middleware.mybatis.dao.IUserDao.queryInfoId-long-void
    public ResultMap addResultMap(String id, Class<?> type, List<ResultMapping> resultMappings) {
        ResultMap.Builder inlineResultMapBuilder = new ResultMap.Builder(
                configuration,
                id,
                type,
                resultMappings);
        ResultMap resultMap = inlineResultMapBuilder.build();
        // 为啥在configuration里添加,不直接用上头的方法
        configuration.addResultMap(resultMap);
        return resultMap;
    }
   
   // 封装ResultMap对象
    private void setStatementResultMap(
            String resultMap,
            Class<?> resultType,
            MappedStatement.Builder statementBuilder
    ) {
        resultMap = applyCurrentNamespace(resultMap, true);
        List<ResultMap> resultMaps = new ArrayList<>();

        if (resultMap != null) {
            String[] resultMapNames = resultMap.split(",");
            for (String resultMapName : resultMapNames) {
                resultMaps.add(configuration.getResultMap(resultMapName.trim()));
            }
        } else if (resultType != null) {
           // 省略...
        }
        statementBuilder.resultMaps(resultMaps);
    }
}

 LanguageDriver类:语言驱动器重载了createSqlSource方法,参数里script是String形式的原Sql(注解解析出来的),而原来的则是脚本文件,还需xml脚本构建器处理,进一步解析

public interface LanguageDriver {
    // 省略其他....
    SqlSource createSqlSource(Configuration configuration,String script,Class<?> parameterType);
}

XmlLanguageDriver类:

实现新添加的 createSqlSource方法,由于已经是Sql数据了,所以此处直接new RawSqlSource,这时候后续流程就都一样了,RawSqlSource只需要处理后续流程将原Sql语句的参数处理成?即可。

 /**
     * 用于处理注解配置
     * */
    @Override
    public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) {
        return new RawSqlSource(configuration, script, parameterType);
    }

Delete注解类

包:package df.middleware.mybatis.annotations

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Delete {
    String[] value();
}

INSERT注解类

​
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Insert {
    String[] value();
}

​

Select、Update注解类都是一样的,这里就不展示了,

Configuration里的修改

    // 结果映射,存在Map里-新增
    protected final Map<String, ResultMap> resultMaps = new HashMap<>();
   
    public ResultMap getResultMap(String id) {
        return resultMaps.get(id);
    }

    public void addResultMap(ResultMap resultMap) {
        resultMaps.put(resultMap.getId(), resultMap);
    }

4.测试准备

mybatis-config-datasource.xml : 数据库环境及注解或Xml mapper配置,此处修改成注解方式

<mapper class="df.middleware.mybatis.dao.IUserDao"></mapper>

<?xml version="1.0" encoding="UTF-8"?>
<configuration>

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"></transactionManager>
            <dataSource type="UNPOOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis_demo?useUnicode=true"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <!-- <mapper resource="mapper/User_Mapper.xml"/>-->
        <!-- 注解类配置 -->
        <mapper class="df.middleware.mybatis.dao.IUserDao"></mapper>
    </mappers>

</configuration>

Dao层使用注解


public interface IUserDao {

    @Select("SELECT id, userId, userName, userHead\n" +
            "FROM user\n" +
            "where id = #{id}")
    User queryUserInfoById(Long id);

    @Select("SELECT id, userId, userName, userHead\n" +
            "        FROM user\n" +
            "        where id = #{id}")
    User queryUserInfo(User req);

    @Select("SELECT id, userId, userName, userHead\n" +
            "FROM user")
    List<User> queryUserInfoList();

    @Update("UPDATE user\n" +
            "SET userName = #{userName}\n" +
            "WHERE id = #{id}")
    int updateUserInfo(User req);

    @Insert("INSERT INTO user\n" +
            "(userId, userName, userHead, createTime, updateTime)\n" +
            "VALUES (#{userId}, #{userName}, #{userHead}, now(), now())")
    void insertUserInfo(User req);

    @Insert("DELETE FROM user WHERE userId = #{userId}")
    int deleteUserInfoByUserId(String userId);

}

单元测试

public class ApiTest {

    private Logger logger = LoggerFactory.getLogger(ApiTest.class);

    private SqlSession sqlSession;

    @Before
    public void init() throws IOException {
        // 1. 从SqlSessionFactory中获取SqlSession
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader("mybatis-config-datasource.xml"));
        sqlSession = sqlSessionFactory.openSession();
    }

    @Test
    public void test_insertUserInfo() {
        // 1. 获取映射器对象
        IUserDao userDao = sqlSession.getMapper(IUserDao.class);

        // 2. 测试验证
        User user = new User();
        user.setUserId("10002");
        user.setUserName("小白");
        user.setUserHead("1_05");
        userDao.insertUserInfo(user);
        logger.info("测试结果:{}", "Insert OK");

        // 3. 提交事务
        sqlSession.commit();
    }

    @Test
    public void test_deleteUserInfoByUserId() {
        // 1. 获取映射器对象
        IUserDao userDao = sqlSession.getMapper(IUserDao.class);

        // 2. 测试验证
        int count = userDao.deleteUserInfoByUserId("10002");
        logger.info("测试结果:{}", count == 1);

        // 3. 提交事务
        sqlSession.commit();
    }

    @Test
    public void test_updateUserInfo() {
        // 1. 获取映射器对象
        IUserDao userDao = sqlSession.getMapper(IUserDao.class);

        // 2. 测试验证
        int count = userDao.updateUserInfo(new User(1L, "10001", "叮当猫"));
        logger.info("测试结果:{}", count);

        // 3. 提交事务
        sqlSession.commit();
    }

    @Test
    public void test_queryUserInfoById() {
        // 1. 获取映射器对象
        IUserDao userDao = sqlSession.getMapper(IUserDao.class);

        // 2. 测试验证:基本参数
        User user = userDao.queryUserInfoById(1L);
        logger.info("测试结果:{}", JSON.toJSONString(user));
    }

    @Test
    public void test_queryUserInfo() {
        // 1. 获取映射器对象
        IUserDao userDao = sqlSession.getMapper(IUserDao.class);

        // 2. 测试验证:对象参数
        User user = userDao.queryUserInfo(new User(1L));
        logger.info("测试结果:{}", JSON.toJSONString(user));
    }

    @Test
    public void test_queryUserInfoList() {
        // 1. 获取映射器对象
        IUserDao userDao = sqlSession.getMapper(IUserDao.class);

        // 2. 测试验证:对象参数
        List<User> users = userDao.queryUserInfoList();
        logger.info("测试结果:{}", JSON.toJSONString(users));
    }

}

运行新增和根据id查询测试一下,说明注解方式处理Sql成功啦,大家写完也可以测试其他的增删改查方法。

 数据库数据

 

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

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

相关文章

2023年好用的设计图制作软件推荐

说到设计图制作软件&#xff0c;设计师当然最关注核心设计功能&#xff0c;包括预加载模板、图像数据库、界面和基本编辑功能。此外&#xff0c;还要考虑设计图制作软件是否可以协同工作。 1.即时设计 即时设计是一款「专业UI设计工具」&#xff0c;不受平台限制&#xff0c;…

Mybatis之介绍及使用

Mybatis介绍 是一款优秀的持久层框架&#xff0c;它支持自定义 SQL、存储过程以及高级映射&#xff1b; 特点 1.支持自定义SQL、存储过程 2.对原有的JDBC进行了封装&#xff0c;几乎消除了所有JDBC代码&#xff0c;让开发者只需关注SQL本身 3.支持XML和注解配置方式自定完成OR…

有哪些好用的pdf阅读软件?这样查阅不出错

PDF&#xff08;Portable Document Format&#xff0c;便携式文档格式&#xff09;是一种用于文档交换的文件格式。它可以在不同的操作系统、软件和设备上显示相同的文档&#xff0c;因此越来越多的人选择使用PDF文件格式来分享和保存文档。与传统的纸质文档相比&#xff0c;PD…

初识uni-app

初识uni-app&#xff1a;跨平台开发的神器 本文将为大家介绍uni-app&#xff0c;一款强大的跨平台App开发框架。我们将探讨其特点、优势以及如何快速上手开发一个简单的uni-app应用。 1. 什么是uni-app uni-app是由DCloud公司研发的一款基于Vue.js的开源跨平台应用开发框架。使…

客户自助服务:让您的客户自助购买

在我们这个数字优先的世界中&#xff0c;我们已经习惯了即时满足。对于品牌来说&#xff0c;这意味着在近乎即时的时间内为客户的问题提供解决方案和答案。花太长时间&#xff0c;您的客户不会满意。这就是提供客户自助服务可以对您的客户体验和满意度产生重大影响的地方。 当…

【MySQL学习】事务管理(Transaction)

文章目录 一、事务的基本认识1.1 事务的基本概念1.2 事务的基本属性1.3 支持事务的存储引擎 二、为什么要有事务三、事务的基本操作3.1 事务的提交方式3.2 事务的操作案例 四、事务的隔离级别4.1 对事务隔离性的初步理解4.2 四种隔离级别4.3 读未提交&#xff08;Read Uncommit…

我能“C”——初阶结构体

目录 结构体 1.结构体的声明 1.1结构体的基础知识 1.2结构体的声明 1.3结构成员的类型 1.4结构体变量的定义和初始化 2. 结构体成员的访问 3.结构体传参 THE END 结构体 结构体类型的声明 结构体初始化 结构体成员访问 结构体传参 1.结构体的声明 1.1结构体的基础知…

单板硬件设计:存储器SD卡( NAND FLASH)

在单板设计中&#xff0c;无论是涉及到一个简易的CPU、MCU小系统或者是复杂的单板设计&#xff0c;都离不开存储器设计&#xff1a; 1、存储器介绍 存储器的分类大致可以划分如下&#xff1a; ROM和RAM指的都是半导体存储器&#xff0c;ROM在系统停止供电的时候仍然可以保持数…

防火墙可以“阻挡”黑客的进攻吗?

"防火墙"这个词大家应该都听说过或者应用过&#xff0c;每个人的电脑、手机几乎都会安装一些的主流的防火墙软件&#xff0c;工作的企事业单位网络里都会安装硬件防火墙。那么这些防火墙能阻挡住黑客的攻击吗&#xff1f; 一、首先我们需要知道防火墙的原理或者说主…

vue项目启动出现可选链?:操作符解析失败

问题描述&#xff1a;vue项目中引入了其他npm包&#xff0c;包中使用可选链操作符?:&#xff0c;本地npm run serve启动时&#xff0c;之前都正常&#xff0c;这次报错了&#xff0c;无法启动。 解决步骤&#xff08;2步&#xff09;&#xff1a; 1. 安装&#xff1a;&#…

【算法思维】-- KMP算法

OJ须知&#xff1a; 一般而言&#xff0c;OJ在1s内能接受的算法时间复杂度&#xff1a;10e8 ~ 10e9之间&#xff08;中值5*10e8&#xff09;。在竞赛中&#xff0c;一般认为计算机1秒能执行 5*10e8 次计算。 时间复杂度取值范围o(log2n)大的离谱O(n)10e8O(nlog(n))10e6O(nsqrt(…

某神QQ机器人BOT搭建教程win系统

某神QQ机器人BOT搭建教程win系统 大家好我是艾西&#xff0c;今天跟大家分享的是某神qi鹅群机器人bot搭建方式以及详细的操作步骤。跟上艾西的节奏准备发车啦&#xff01; 前言&#xff1a;&#xff08;xxxx即为https&#xff09;&#xff08;zzz即为com&#xff09; qi鹅群…

geoserver发布矢量切片服务理论与实战

geoserver发布矢量数据服务前几篇文章已经分享过了&#xff0c;但是在实际业务中&#xff0c;矢量数据shp文件有时候比较大&#xff0c;包含上百万完个点&#xff0c;发布完整的服务后&#xff0c;有时候&#xff0c;前端显示还是有点慢&#xff0c;毕竟是一次加载完成&#xf…

Verilog语法概述二:何为仿真?仿真可以在几个层面上进行?

Verilog 是一种用于数字逻辑电路设计的硬件描述语言&#xff0c;可以用来进行数字电路的仿真验证、时序分析、逻辑综合。 既是一种行为级&#xff08;可用于电路的功能描述&#xff09;描述语言又是一种结构性&#xff08;可用于元器件及其之间的连接&#xff09;描述语言。 …

day34_js

今日内容 零、 复习昨日 一、JS 零、 复习昨日 一、引言 1.1 JavaScript简介 JavaScript一种解释性脚本语言&#xff0c;是一种动态类型、弱类型、基于原型继承的语言&#xff0c;内置支持类型。它的解释器被称为JavaScript引擎&#xff0c;作为浏览器的一部分&#xff0c;广泛…

数据分类分级 数据识别-excel分类分级模版文件导入、解析

前面讲了数据分类分级 数据识别-实现部分敏感数据识别,本次针对模版导入展开,excel导入采用的是easyexcel 目录 easyexcel介绍easyexcel实战添加依赖读取数据监听器的实现数据读取方法读取结果上面图片是AI创作生成!如需咒语可私戳哦! easyexcel介绍 之前的excel导入解析…

全网最可”铐“最可“刑”的fiddler抓包教程

Fiddler 下载&#xff1a; https://telerik-fiddler.s3.amazonaws.com/fiddler/FiddlerSetup.exe 浏览器f12 选择“网络”&#xff0c;点选“保留日志” Fiddler 浏览器执行“去缓存刷新”&#xff1a;shiftf5 会话 即是 包 har文件在测试当中有什么作用?&#xff1a;h…

存储卡格式化后如何找回数据?一招轻松恢复数据

存储卡内存不足、存储卡中毒、存储卡损坏这几种情况都会导致我们把存储卡格式化操作&#xff0c;存储卡格式化后所有数据都会清空&#xff0c;这是众所皆知的&#xff0c;存储卡不小心格式化了怎么办&#xff1f; 其实是有方法恢复格式化的数据&#xff0c;我们可以通过专业的数…

香橙派4和树莓派4B构建K8S集群实践之五:端口公开访问配置

1. 应用场景说明 - 我们需用k8s集群提供开放特别的端口访问服务&#xff0c;如一些微服务端口 - 在团队开发环境中&#xff0c;通常要访问公共数据库&#xff0c;集群需提供统一的接口给大伙 接下来以实践四中设置的mariadb-galera为基础&#xff0c;公开端口3306&#xff0…

银豆信息张雪灿:钻石级合作伙伴的增长秘诀

编者按&#xff1a; 杭州银豆信息技术有限公司&#xff08;简称“银豆”&#xff09;&#xff0c;是一家专注于云计算服务的高科技企业&#xff0c;目前已为2000家企业级客户提供了专业的行业解决方案, 与人民网、光大银行、长安汽车金融、vivo金融、浙江省农科院、淄博市大数…