当前位置: 首页 > news >正文

MyBatis常见面试题汇总

说一下MyBatis执行流程?

MyBatis是一款优秀的基于Java的持久层框架,它内部封装了JDBC,使开发者只需要关注SQL语句本身,而不需要花费精力去处理加载驱动、创建连接等的过程,MyBatis的执行流程如下:

  1. 加载配置文件:MyBatis的执行流程从加载配置文件开始。通常,MyBatis的配置文件是一个XML文件,其中包含了了数据源配置,SQL映射配置、连接池配置
  2. 构架SqlSessionFactory:在配置文件加载后,MyBatis使用配置信息来构建SqlSessionFactory,这是MyBatis的核心工厂类。SqlSessionFactory是线程安全的,它用于创建SqlSession对象
  3. 创建SqlSession:应用程序通过SqlSessionFactory床渐渐SqlSession对象。SqlSession代表依次数据库会话,它提供了执行SQL操作的方法。通常情况下,每个线程都应该由自己的SqlSession对象
  4. 执行SQL查询:在SqlSession种,开发人员可以执行SQL查询你,这可以通过两种方式实现:
    1. 使用注解加SQL:Mybatis提供了注解加执行SQL的实现方式,MyBatis会为Mapper接口生成实现类的代理对象,实际执行SQL查询
    2. 使用XML映射文件:开发人员可以在XML映射文件中定义SQL查询语句和映射关系。然后,通过SqlSession执行这些SQL擦汗寻,将结果有映射到Java对象上
  5. SQL解析和执行:MyBatis会解析SQL查询,执行查询操作,并获取查询结果
  6. 结果映射:MyBatis使用配置的结果映射规则,将查询结果映射到Java对象上。这包括将数据库列映射到Java对象的属性上,并处理关联关系
  7. 返回结果:查询结果被返回给应用程序,开发热恩于那可以对结果进行进一步处理、展示或之旧话
  8. 关闭SqlSession:完成数据库操作后,关闭SqlSession释放资源

${}#{}有什么区别?什么情况下一定要使用${}

${}和#{}都是MyBatis种用来代替参数的特殊标识,如下:

但是二者的区别二还是很大的,主要区别如下:

  1. 含义不同:${}是直接替换(运行时已经替换成具体的执行SQL了),而#{}是预处理(运行时只设置了占位符"?",之后通过声明器(statement)来替换占位符)
  2. 使用场景不同:普通参数使用#{},如果传递的是SQL命令(如desc还是asc)或者SQL关键字的时候,需要使用${},但是使用之前一定要做好安全验证
  3. 安全性不同:使用${}存在安全性问题,#{}不存在安全性问题

也就是说,为了防止安全问题,所以大部分场景都要使用 #{} 替换参数,但是如果传递的是 SQL 关键字,例如order by xxx asc/desc 时(传递 asc 后 desc),一定要使用 $,因为它需要在执行时就被替换成关键字,而不能使用占位符替代(占位符不用应用于 SQL 关键字,否则会报错)。

在传递 SOL 关键字时,一定要使用 ${},但使用前,一定要进行过滤和安全检査,以防止 SQL 注入。

什么是SQL注入?如何防止SQL注入?

SQL注入就是指应用程序对用户输入的数据的合法性没有判断或过滤不严,攻击者可以在应用程序中事先定义好查询语句的结尾上添加额外的SQL语句,在管理员不知情的情况下实现非法操作,依次来实现欺骗数据库服务器执行非授权的热议操作,从而进一步得到相应的数据信息


也就是说所谓的 SQL 注入指的是,使用某个特殊的 SQL 语句,利用 SQL 的执行特性,绕过 SQL 的安全检查,查询到本不该查询到的结果

如以下代码:

<select id="doLogin" resultType="com.example.demo.model.User">select *from userinfo where username='${name}' and password='${pwd}
</select>

sql注入以下代码 :"'or 1='1",如下图所示:

从上述结果可以看出,以上程序在应用程序不知情的情况下实现非法操作,以此来实现欺骗数据库服务器执行非授权的敏感数据

如何防止SQL注入

预编译语句与参数化查询:使用 PreparedStatement 可以有效防止 SQL 注入,因为它允许你先定义 SQL 语句的结构,然后将变量作为参数传入,数据库驱动程序会自动处理这些参数的安全性,确保它们不会干扰 SQL 语句的结构,如下代码所示:

String sql ="SELECT * FROM users WHERE username = ? AND password = ?";
PreparedStatement pstmt= connection.prepareStatement(sql);
pstmt.setString(1,userInputUsername);
pstmt.setstring(2,userInputPassword);
ResultSet rs=pstmt.executeQuery();

输入验证和过滤:对用户输入的信息进行验证和过滤,确保其符合预期的类型和格式 

说一下MyBaits中的二级缓存?

MyBatis二级缓存是用于提高MyBatis查询数据库的性能和减少访问数据库的机制。MyBatis的二级缓存总共由两个缓存机制:

  • 一级缓存:SqlSession级别的,MyBatis自带的缓存功能。并且无法关闭,因此当有两个SqlSession访问相同的SQL时,一级缓存也不会生效,也需要查询两次数据库。在一个service调用两个相同的mapper方法的时候,依然是查询两次,因为他会创建那两个SqlSession进行查询(为了提高查询性能)
  • 二级缓存:Mapper级别的,只要是同一个Mapper,无论是使用多少个SqlSession来操作,数据都是共享的。也就是说,一个sessionFactory下的多个session之间是共享缓存的,它的作用范围更大,生命周期更长,可以减少数据库的查询次数,提高系统性能。但是MyBatis二级缓存时默认关闭的需要手动开启

二级缓存默认是不开启的,手动开启MyBatis步骤如下:

  1. 在mapper.xml中添加<cache/>标签
  2. 在需要缓存的标签上添加usrCache="true"(新版本可以忽略,为了兼顾老版本,保留)

完整示例如下:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIc "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mybatis.demo.mapper.StudentMapper"><cache/><select id="getStudentCount" resultType="Integer" usecache="true">select count(*)from student</select>
</mapper>

编写单元测试代码:

import org.junit.jupiter.api.Test;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.boot.test.context.SpringBootTest;  @SpringBootTest  
public class StudentMapperTest {  @Autowired  private StudentMapper studentMapper;  @Test  void testGetStudentCount() {  int count = studentMapper.getStudentCount();  System.out.println("查询结果:" + count);  int count2 = studentMapper.getStudentCount();  System.out.println("查询结果2:" + count2);  }  
}

运行结果:

从以上结果可以看出,两次查询虽然使用了不同的 SqlSession,但第二次查询使用了缓存,并未查询数据库 

ORM项目中类属性名和数据库字段名不一致会导致什么问题?它的解决方案有哪些?

在ORM项目中,如果类的属性名称和数据库字段名不一致会场导致插入、修改时设置的这个不一致字段为null,查询的时候即使数据库有数据,但是查询的结果也是为null。它的常见解决方法如下:

  1. 更改程序中属性名或数据库的字段名,使其一致
  2. 使用结果映射,使用<resultMap>映射对应的字段
    1. <!-- 在 XML 配置文件中定义 resultMap -->
      <resultMap id="userResultMap" type="com.example.User"><!-- 指定 id 字段映射 --><id property="id" column="id"/><!-- 指定 username 字段映射 --><result property="username" column="username"/><!-- 指定 email 字段映射 --><result property="email" column="email"/>
      </resultMap>
      //在SQL映射文件中,可以使用这个<resultMap>来进行查询结果的映射:
      <select id="selectUser" resultMap="userResultMap">SELECT id, username, emailFROM userWHERE id = #{id}
      </select>
      
  3. 使用MyBatis Plus框架中的@TableFild注解映射二者的字段,如下图:
  4. 如果是查询操作 ,可以使用as重命名字段名,这样查询也就不会为null了

MyBatis中如何实现分页?它有几种实现分页的方式?

MyBatis中实现分页主要是一下两种实现方式:

  • 物理分页:物理上是使用SQL擦哈寻语句,在数据库引擎层面实现的,如MySQL的LIMIT语法进行分页
  • 逻辑分页:逻辑分页时在应用程序层面进行的分页,通常是先查询出所有符合条件的数据,然后在内存中对数据进行分页操作

物理分页

使用limit实现分页

物理分页是可以直接在XML中拼加SQL进行分页:

<select id="getUserList"resultType="User">select *from userlimit #{limit} offset #{offset}
</select>

使用PageHelper插件实现分页

实现代码如下:

PageHelper.startPage(1,10);
//1 表示要查询的页码为第一页,10 表示每页显示的记录数为 10 条
List<User>list=userMapper.selectIf(1);
 PageHelper实现原理分析

PageHelper底层是使用MyBatis的拦截器(Interceptor)机制,在MyBatis进行查询时,拦截并对SQL语句进行动态修改(添加limit等分页查询操作),之后查询数据库、并对查询结果进行包装,包装成分页对象(如包含数据列表、总记录数、总页数等信息的分页对象),最后再将这个分页对象返回给客户端

逻辑分页

MyBatis自带的RowBounds进行分页就是逻辑分页,它的一次性查询很多数据,然后在数据中在进行检索,实现代码如下:

RowBounds rowBounds = new RowBounds(offset, limit);
List<User> users = sqlSession.selectList("getUserList", null, rowBounds);

其中 offset 为起始行偏移量,limit 是每页数据量,虽然设置了这两个值,但在使用 RowBounds 时,它会一次性查询多条数据,然后再在内存中进行 offset 和 limit 的筛选,最后在返回符合结果的数据 

MyBaits二级缓存有几种淘汰策略?如何设置缓存淘汰策略?

  1. LRU-最近最少使用:移除最长时间不被使用的对象
  2. FIFO-先进先出:按照对象进入缓存的顺序来移除对象
  3. SOFT-软引用:基于垃圾回收器状态和软引用规则移除对象
  4. WEAK-弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象

默认的缓存淘汰策略时LRU

可以通过eviction属性来设置淘汰策略,如下:

<cache eviction="FIFO"/>

MyBatisPlus如何实现分页功能?它的底层是如何实现的?

MyBatis-Plus 是 MyBatis 的增强工具,在 MyBatis 的基础上提供了更多方便的功能,包括分页功能。在 MyBatis-Plus 中,实现分页功能非常简单,主要使用 Page 类进行分页操作。

以下是使用 MyBatis-Plus 实现分页功能的基本步骤:

  • 在 Mapper 接口中定义查询方法,并接收一个 Page 类型的参数,用于分页
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;public interface UserMapper extends BaseMapper<User> {Page<User> selectUserPage(Page<User> page);
}
  • 在 Mapper XML 文件中编写对应的 SQL 查询语句
<select id="selectUserPage" resultType="com.example.User">select * from user
</select>
  • 在 Service 层调用 Mapper 中定义的查询方法,并传入一个 Page 对象作为参数
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;@Service
public class UserServiceImpl implements UserService {@Autowiredprivate UserMapper userMapper;@Overridepublic Page<User> selectUserPage(int pageNum, int pageSize) {Page<User> page = new Page<>(pageNum, pageSize);return userMapper.selectUserPage(page);}
}

这样就完成了分页功能的实现。MyBatis-Plus会自动将查询结果封装到Page对象中,其中包含了分页的相关信息,比如当前页码、每页大小、总记录数等

MyBatis-Plus底层是通过拦截器实现分页的,它会在执行SQL语句前进行拦截,并根据传入的分页参数自动生成对应的分页SQL语句,然后执行分页查询。最后将查询结果封装到Page对象中返回给调用方。这样,开发人员不需要手动编写分页的SQL语句,简化了开发流程

MyBatis中使用了哪些设计模式?举例说明一下

  1. 工厂模式:工厂模式是Java中最常见的设计模式之一。工厂模式就是它提供一个工厂,当有客户需要调用的时候,只调用这个工厂类就可以得到自己想要的结果,从而无需关注某类的具体实现过程。工厂模式在MyBatis中典型的代表就是SqlSessionFactory。SqlSession是MyBatis中的重要而Java接口,可以通过该接口来执行SQL语句,获取映射器示例和管理事务,而SqlSessionFactory正在用来产生SqlSession对象的,所以它在MyBatis中是比较核心的接口
  2. 建造者模式:建造者模式就是将一个复杂对象的构建与它的表示分离,使得的同样的构建过程可以创建不同的表示。也就是说建造者可以通过多个模块一步一步实现对象的构建,相同的构建过程可以创建不同产品。建造者模式在MyBatis中典型代表就是SqlSessionFactoryBuilder。普通的对象都是通过new关键字直接创建的,但是如果创建对象需要构造参数很多,且不能保证每个参数都是正确的或者不能一次性得到构建所需的所有参数,那么就需要将构建逻辑从对象本身抽离出来,让对象只关注功能,把构建交给构建类,这样就可以简化对象的构建,也可以达到分布构建对象的目的
  3. 单例模式:单例模式是Java中最简单的设计模式之一,此模式保证某个类在运行期间,只有一个实例对外提供服务,而这类被称为单例类。单例模式在MyBatis中典型的代表就是ErrorContext。ErrorContext是线程级别的单例,每一个线程有一个此对象的单例,用于记录该现成的执行环境的错误信息
  4. 代理模式:代理模式是指给某个对象提供一个代理对象,并由代理对象控制原对象的调用。代理模式在MyBatis中典型的代表就是MapperProxyFactory。MapperProxyFactory的newInstance()方法就是生成一个具体的代理类来实现某个功能

相关文章:

MyBatis常见面试题汇总

说一下MyBatis执行流程&#xff1f; MyBatis是一款优秀的基于Java的持久层框架&#xff0c;它内部封装了JDBC&#xff0c;使开发者只需要关注SQL语句本身&#xff0c;而不需要花费精力去处理加载驱动、创建连接等的过程&#xff0c;MyBatis的执行流程如下&#xff1a; 加载配…...

juc并发线程学习笔记(一)

本系列会更新我在学习juc时的笔记和自己的一些思想记录。如有问题欢迎联系。 并发编程 进程与线程 1.进程和线程的概念 程序是静态的&#xff0c;进程是动态的 进程 程序由指令和数据组成&#xff0c;但这些指令要运行&#xff0c;数据要读写&#xff0c;就必须将指令加载…...

力扣热门100题刷题笔记 - 3.无重复字符的最长子串

力扣热门100题 - 3.无重复字符的最长子串 题目链接&#xff1a;3. 无重复字符的最长子串 题目描述&#xff1a; 给定一个字符串 s &#xff0c;请你找出其中不含有重复字符的 最长子串 的长度。示例&#xff1a; 输入: s "abcabcbb" 输出: 3 解释: 因为无重复字…...

达梦数据库死锁排查与解决

在达梦数据库系统中&#xff0c;死锁是指两个或多个事务相互等待对方释放资源&#xff0c;从而造成循环等待的现象&#xff0c;严重影响数据库的正常运行。以下是使用达梦数据库进行死锁排查和解决的具体步骤&#xff1a; 死锁查看 查询当前死锁信息 SELECT lc.lmode, lc.ta…...

鸿蒙(HarmonyOS)项目方舟框架(ArkUI)之TextClock组件

鸿蒙&#xff08;HarmonyOS&#xff09;项目方舟框架&#xff08;ArkUI&#xff09;之TextClock组件 一、操作环境 操作系统: Windows 10 专业版、IDE:DevEco Studio 3.1、SDK:HarmonyOS 3.1 二、TextClock组件 TextClock组件通过文本将当前系统时间显示在设备上。支持不同…...

CICD注册和使用gitlab-runner常见问题

1、现象 fatal: unable to access https://github.com/homebrew/brew/: 2、解决 git config --global --unset http.proxy git config --global --unset https.proxy 查看gitlab-runner是否成功&#xff1a; userusers-MacBook-Pro ~ % gitlab-runner -h 查看gitlab-run…...

关于Django部署

首先了解一下开发环境服务器跟生产环境服务器有何不同。 一、我们通过 python manage.py runserver 启动开发环境服务器&#xff0c;这条命令背后做了哪些事情&#xff1f; 1、首先加载Django项目的设置&#xff08;settings&#xff09; 2、检查数据库迁移&#xff0c;确保数…...

计算机网络——01什么是InterNet

什么是InterNet 1.1 什么是网络 由节点和边组成的与形状大小无关的拓扑 1.2 什么是Internet 从具体构成角度来说&#xff1a; 节点&#xff1a; 主机及其上运行的应用程序路由器、交换机等网络交换设备 边&#xff1a;通信链路 接入网链路&#xff1a;主机连接到互联网的链…...

刷存在感,Excel转Protobuf/Json通用配置文件

使用场景 最近工作流中有将Excel转Protobuf作为配置文件的技术方案。具体实现是先定一个proto文件&#xff0c;再在一个对应excel表中定义对应字段&#xff0c;由策划在excel进行更改。proto文件可以生成对应语言的脚本&#xff0c;然后将excel转成对应protobuf的binary。 我…...

docker 开放tcp连接供idea等其他外部工具开放使用

docker 开放tcp连接供idea等其他外部工具开放使用 方法一&#xff1a;通过systemd工具 sudo systemctl edit docker.service 修改文件内容如下 ExecStart/usr/bin/dockerd -H fd:// -H tcp://0.0.0.0:2375 重启 systemctl 配置 sudo systemctl daemon-reload 重启docker服务 s…...

虚拟机(VMware)ubuntu16.04 直接连接网口设备 USRP 吊舱

编辑虚拟网络编辑器 点击之后 选择网卡之后&#xff0c;点击确定。 电脑配置 使用了&#xff1a;192.168.2.56 虚拟机内部配置 和PC的配置一致...

告别繁杂的状态管理:Zustand 的简洁之道

1. Zustand Zustand 是一个轻量级的状态管理库&#xff0c;用于 JavaScript 应用程序&#xff0c;特别是在 React 生态系统中。它提供了一个简单、可扩展的解决方案来中心化和管理应用程序的状态。 与其他状态管理解决方案&#xff08;如 Redux 或 MobX&#xff09;相比&…...

CentOS磁盘扩容

参考操作 检查磁盘扩容情况 lsblk 使用fdisk命令进行开垦&#xff0c;有时需要重启之后才能进行下一步 fdisk /dev/新盘mkfs.ext4 /dev/新盘使用pvcreate直接创建虚拟卷 pvcreate /dev/新盘使用vgextend添加新创建的虚拟卷 vgextend 卷名称 /dev/新盘使用lvextend进行扩容…...

【数据分享】1929-2023年全球站点的逐日降雪深度数据(Shp\Excel\免费获取)

气象数据是在各项研究中都经常使用的数据&#xff0c;气象指标包括气温、风速、降水、能见度等指标&#xff0c;说到气象数据&#xff0c;最详细的气象数据是具体到气象监测站点的数据&#xff01; 之前我们分享过1929-2023年全球气象站点的逐日平均气温数据、逐日最高气温数据…...

golang网络编程day4

golang网络编程day4 get和post的区别resful编程golang请求头golangheader内容类型和字符编码http请求头缓存和过期应用golang 请求头跨域请求应用http请求头用户代理应用golang响应头 get和post的区别 在前面的学习我只在应用场景上做了一个区别的举例,这里是进一步的学习有哪…...

为什么pgsql(内关联查询或者with字句时)会导致索引失效

1、在PostgreSQL中&#xff0c;内关联查询可能导致索引失效的原因通常与查询的过滤条件和数据分布有关。 以下是一些可能导致索引失效的情况&#xff1a; 1、使用了函数或类型转换&#xff1a;当查询条件中对索引字段使用了任何计算、函数或类型转换时&#xff0c;这可能会阻止…...

小程序 自定义组件和生命周期

文章目录 ⾃定义组件创建⾃定义组件声明组件编辑组件注册组件 声明引⼊⾃定义组件⻚⾯中使⽤⾃定义组件定义段与⽰例⽅法组件-⾃定义组件传参过程 小程序生命周期应用生命周期页面生命周期页面生命周期 ⾃定义组件 类似vue或者react中的自定义组件 ⼩程序允许我们使⽤⾃定义组件…...

asp.net 404页面配置、 asp.net MVC 配置404页面、iis 配置404页面,指定404错误页面,设置404错误页面

通过标题的三个问题 1、asp.net 404页面配置、 2、asp.net MVC 配置404页面、 3、iis 配置404页面&#xff1b; 可以看出&#xff0c;这是一篇了不得的问题&#xff0c;并进行全面讲解&#xff1b; 除了围绕以上三个核心问题外&#xff0c;我们也对以下2个核心场景也作出分析…...

Docker存储空间清理

不知不觉服务器存储空间被Docker掏空了… 查看Docker空间占用情况 使用docker system df命令&#xff0c;可以加 -v 查看详情 清理Docker不需要的内容 使用docker system prune -a命令清理Docker 所有停止的容器所有没有被使用的networks所有没容器的镜像所有build cache …...

React16源码: React中NewContext的源码实现

NewContext 1 &#xff09;概述 新的 context API 是一个组件化的使用方式 它就跟写其他的组件一样&#xff0c;像写jsx&#xff0c;通过标签的这种方式来赋值一些props还有去给子节点去拿到这个 conntext 的属性 context的提供方和订阅方都是独立的 在什么地方想要用到这个 c…...

龙虎榜——20250610

上证指数放量收阴线&#xff0c;个股多数下跌&#xff0c;盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型&#xff0c;指数短线有调整的需求&#xff0c;大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的&#xff1a;御银股份、雄帝科技 驱动…...

云原生核心技术 (7/12): K8s 核心概念白话解读(上):Pod 和 Deployment 究竟是什么?

大家好&#xff0c;欢迎来到《云原生核心技术》系列的第七篇&#xff01; 在上一篇&#xff0c;我们成功地使用 Minikube 或 kind 在自己的电脑上搭建起了一个迷你但功能完备的 Kubernetes 集群。现在&#xff0c;我们就像一个拥有了一块崭新数字土地的农场主&#xff0c;是时…...

【kafka】Golang实现分布式Masscan任务调度系统

要求&#xff1a; 输出两个程序&#xff0c;一个命令行程序&#xff08;命令行参数用flag&#xff09;和一个服务端程序。 命令行程序支持通过命令行参数配置下发IP或IP段、端口、扫描带宽&#xff0c;然后将消息推送到kafka里面。 服务端程序&#xff1a; 从kafka消费者接收…...

k8s从入门到放弃之Ingress七层负载

k8s从入门到放弃之Ingress七层负载 在Kubernetes&#xff08;简称K8s&#xff09;中&#xff0c;Ingress是一个API对象&#xff0c;它允许你定义如何从集群外部访问集群内部的服务。Ingress可以提供负载均衡、SSL终结和基于名称的虚拟主机等功能。通过Ingress&#xff0c;你可…...

【Java学习笔记】Arrays类

Arrays 类 1. 导入包&#xff1a;import java.util.Arrays 2. 常用方法一览表 方法描述Arrays.toString()返回数组的字符串形式Arrays.sort()排序&#xff08;自然排序和定制排序&#xff09;Arrays.binarySearch()通过二分搜索法进行查找&#xff08;前提&#xff1a;数组是…...

Linux简单的操作

ls ls 查看当前目录 ll 查看详细内容 ls -a 查看所有的内容 ls --help 查看方法文档 pwd pwd 查看当前路径 cd cd 转路径 cd .. 转上一级路径 cd 名 转换路径 …...

【算法训练营Day07】字符串part1

文章目录 反转字符串反转字符串II替换数字 反转字符串 题目链接&#xff1a;344. 反转字符串 双指针法&#xff0c;两个指针的元素直接调转即可 class Solution {public void reverseString(char[] s) {int head 0;int end s.length - 1;while(head < end) {char temp …...

IoT/HCIP实验-3/LiteOS操作系统内核实验(任务、内存、信号量、CMSIS..)

文章目录 概述HelloWorld 工程C/C配置编译器主配置Makefile脚本烧录器主配置运行结果程序调用栈 任务管理实验实验结果osal 系统适配层osal_task_create 其他实验实验源码内存管理实验互斥锁实验信号量实验 CMISIS接口实验还是得JlINKCMSIS 简介LiteOS->CMSIS任务间消息交互…...

多种风格导航菜单 HTML 实现(附源码)

下面我将为您展示 6 种不同风格的导航菜单实现&#xff0c;每种都包含完整 HTML、CSS 和 JavaScript 代码。 1. 简约水平导航栏 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport&qu…...

Axure 下拉框联动

实现选省、选完省之后选对应省份下的市区...