【从零开发Mybatis】引入MapperConfig.xml和Mapper映射配置

引言

学习MyBatis源码之前,了解它是如何通过JDBC查询数据库数据的基础知识是非常有用的。

上一篇我们编写了一个最简单的示例,通过JDBC查询数据库数据,从本文开始,我们将正式开始Mybatis框架的开发。

通过JDBC查询数据库数据存在的问题及处理方案

问题1:数据源写死在代码中
处理方案:引入MapperConfig.xml全局配置文件,配置数据源等信息

问题2:SQL语句写死在代码中
处理方案:引入Mapper映射文件,配置SQL脚本等信息

引入MapperConfig.xml

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

<configuration>
    <dataSource >
        <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=false&amp;serverTimezone=UTC"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
    </dataSource>

    <mappers>
        <mapper resource="AuthorMapper.xml"/>
    </mappers>

</configuration>

以上XML 配置文件是一个典型的 MyBatis 配置文件的一部分,用于定义数据源(dataSource)和映射器(mapper)。这个配置文件定义了数据库连接的信息,并指定了一个映射器文件 AuthorMapper.xml。

引入Mapper映射文件

<?xml version="1.0" encoding="UTF-8" ?>
<mapper namespace="org.apache.ibatis.domain.blog.mappers.AuthorMapper">
    
    <select id="selectAuthor">
        select id, username, email from author where id = ?
    </select>

</mapper>

  • mapper 标签:定义一个 MyBatis 映射器,其中 namespace 属性指定了映射器的全限定类名。
  • namespace 属性:org.apache.ibatis.domain.blog.mappers.AuthorMapper,这是映射器的唯一标识符,用于区分不同的映射器。
  • select 标签:定义了一个 SQL 查询语句。
  • id 属性:selectAuthor,这是 SQL 语句的唯一标识符,用于在代码中引用此 SQL 语句。
  • SQL 语句:select id, username, email from author where id = ?,这是一个简单的 SQL 查询语句,用于从 author 表中选择特定记录。
  • ? 占位符:表示一个参数占位符,将在执行 SQL 语句时替换为实际值。

解析MapperConfig.xml和Mapper映射文件

我们对SqlSession类进行改造,数据源及SQL脚本通过读取MapperConfig.xml和Mapper映射文件获取,代码如下:

package org.apache.ibatis.session;

import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.sql.*;
import java.util.*;

/**
 * Sql会话
 *
 * @author crazy coder
 * @since 2024/9/27
 **/
public class SqlSession {
    public String selectOne(String statement, Integer param) throws ParserConfigurationException, XPathExpressionException, IOException, SAXException {

        final String configResource = "MapperConfig.xml";
        InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream(configResource);
        Reader reader = new InputStreamReader(in);

        // 读取XML配置文件
        DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
        DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
        Document document = docBuilder.parse(new InputSource(reader));

        XPathFactory xPathFactory = XPathFactory.newInstance();
        XPath xpath = xPathFactory.newXPath();
        Node configNode = (Node) xpath.evaluate("/configuration", document, XPathConstants.NODE);

        // 解析XML配置信息 - 数据源
        // 驱动
        String driver = null;
        // 数据库连接 URL
        String url = null;
        // 数据库用户名
        String username = null;
        // 数据库密码
        String password = null;
        Node envNode = (Node) xpath.evaluate("dataSource", configNode, XPathConstants.NODE);
        NodeList nodeList = envNode.getChildNodes();
        for (int i = 0, n = nodeList.getLength(); i < n; i++) {
            Node node = nodeList.item(i);
            if (node.getNodeType() == Node.ELEMENT_NODE) {
                Properties attributes = new Properties();
                NamedNodeMap attributeNodes = node.getAttributes();
                if (attributeNodes != null) {
                    for (int j = 0; j < attributeNodes.getLength(); j++) {
                        Node attribute = attributeNodes.item(j);
                        String value = attribute.getNodeValue();
                        attributes.put(attribute.getNodeName(), value);
                    }
                }
                if ("driver".equals(attributes.get("name"))) {
                    driver = (String) attributes.get("value");
                } else if ("url".equals(attributes.get("name"))) {
                    url = (String) attributes.get("value");
                } else if ("username".equals(attributes.get("name"))) {
                    username = (String) attributes.get("value");
                } else if ("password".equals(attributes.get("name"))) {
                    password = (String) attributes.get("value");
                }
            }
        }

        // 读取Mapper配置文件
        List<String> resourceMapperList = new ArrayList<>();
        Node mappersNode = (Node) xpath.evaluate("mappers", configNode, XPathConstants.NODE);
        NodeList mapperList = mappersNode.getChildNodes();
        for (int i = 0, n = mapperList.getLength(); i < n; i++) {
            Node node = mapperList.item(i);
            if (node.getNodeType() == Node.ELEMENT_NODE) {
                NamedNodeMap attributeNodes = node.getAttributes();
                if (attributeNodes != null) {
                    for (int j = 0; j < attributeNodes.getLength(); j++) {
                        Node attribute = attributeNodes.item(j);
                        String value = attribute.getNodeValue();
                        if ("resource".equals(attribute.getNodeName())) {
                            resourceMapperList.add(value);
                        }
                    }
                }
            }
        }
        Map<String, String> statementMap = new HashMap<>();
        for (String mapperResource : resourceMapperList) {
            try (InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(mapperResource)) {
                Reader mapperReader = new InputStreamReader(inputStream);
                Document mapperDocument = docBuilder.parse(new InputSource(mapperReader));
                Node mapperNode = (Node) xpath.evaluate("/mapper", mapperDocument, XPathConstants.NODE);

                String namespace = "";
                NamedNodeMap mapperAttributeNodes = mapperNode.getAttributes();
                if (mapperAttributeNodes != null) {
                    for (int j = 0; j < mapperAttributeNodes.getLength(); j++) {
                        Node attribute = mapperAttributeNodes.item(j);
                        String value = attribute.getNodeValue();
                        if ("namespace".equals(attribute.getNodeName())) {
                            namespace = value;
                        }
                    }
                }

                Node selectNode = (Node) xpath.evaluate("select", mapperNode, XPathConstants.NODE);
                String mapperId = "";
                NamedNodeMap attributeNodes = selectNode.getAttributes();
                if (attributeNodes != null) {
                    for (int j = 0; j < attributeNodes.getLength(); j++) {
                        Node attribute = attributeNodes.item(j);
                        String value = attribute.getNodeValue();
                        if ("id".equals(attribute.getNodeName())) {
                            mapperId = value;
                        }
                    }
                }
                String sql = selectNode.getTextContent();
                statementMap.put(namespace + "." + mapperId, sql);
            }
        }


        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;

        try {
            // 加载 MySQL JDBC 驱动
            Class.forName(driver);

            // 获取数据库连接
            conn = DriverManager.getConnection(url, username, password);

            // 准备 SQL 语句
            String sql = statementMap.get(statement);

            // 创建预编译语句
            pstmt = conn.prepareStatement(sql);

            // 设置参数
            pstmt.setLong(1, param);

            // 执行 SQL 查询操作
            rs = pstmt.executeQuery();

            // 处理结果集
            StringBuilder result = new StringBuilder();
            while (rs.next()) {
                result.append("id: ").append(rs.getInt("id"))
                        .append(", username: ").append(rs.getString("username"))
                        .append(", email: ").append(rs.getString("email"));

            }
            return result.toString();

        } catch (ClassNotFoundException | SQLException e) {
            e.printStackTrace();
        } finally {
            // 关闭资源
            if (pstmt != null) {
                try {
                    pstmt.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }

            if (rs != null) {
                try {
                    rs.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }

            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }

            in.close();
        }

        return "";
    }
}

这段代码实现了通过 MyBatis 的配置文件读取数据源信息,并执行 SQL 查询的功能。下面是对这段代码的详细解析:

  • 方法定义 selectOne:
public String selectOne(String statement, Integer param)
    throws ParserConfigurationException, XPathExpressionException, IOException, SAXException {

这个方法接受两个参数:statement 和 param。statement 是一个字符串,表示 SQL 语句的唯一标识符;param 是一个整数类型的参数,用于 SQL 语句中的占位符。

  • 读取MapperConfig.xml配置文件
final String configResource = "MapperConfig.xml";
InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream(configResource);
Reader reader = new InputStreamReader(in);

// 读取XML配置文件
DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
Document document = docBuilder.parse(new InputSource(reader));

这里使用了 DOM 解析器来读取配置文件 MapperConfig.xml,并将文件内容解析成 Document 对象。

  • 解析数据源信息
XPathFactory xPathFactory = XPathFactory.newInstance();
XPath xpath = xPathFactory.newXPath();
Node configNode = (Node) xpath.evaluate("/configuration", document, XPathConstants.NODE);

// 解析XML配置信息 - 数据源
// 驱动
String driver = null;
// 数据库连接 URL
String url = null;
// 数据库用户名
String username = null;
// 数据库密码
String password = null;
Node envNode = (Node) xpath.evaluate("dataSource", configNode, XPathConstants.NODE);
NodeList nodeList = envNode.getChildNodes();
for (int i = 0, n = nodeList.getLength(); i < n; i++) {
    Node node = nodeList.item(i);
    if (node.getNodeType() == Node.ELEMENT_NODE) {
        Properties attributes = new Properties();
        NamedNodeMap attributeNodes = node.getAttributes();
        if (attributeNodes != null) {
            for (int j = 0; j < attributeNodes.getLength(); j++) {
                Node attribute = attributeNodes.item(j);
                String value = attribute.getNodeValue();
                attributes.put(attribute.getNodeName(), value);
            }
        }
        if ("driver".equals(attributes.get("name"))) {
            driver = (String) attributes.get("value");
        } else if ("url".equals(attributes.get("name"))) {
            url = (String) attributes.get("value");
        } else if ("username".equals(attributes.get("name"))) {
            username = (String) attributes.get("value");
        } else if ("password".equals(attributes.get("name"))) {
            password = (String) attributes.get("value");
        }
    }
}

这部分代码解析了配置文件中的 节点,并从中提取出数据库连接所需的各项信息:驱动、URL、用户名和密码。

  • 读取 Mapper 文件
List<String> resourceMapperList = new ArrayList<>();
Node mappersNode = (Node) xpath.evaluate("mappers", configNode, XPathConstants.NODE);
NodeList mapperList = mappersNode.getChildNodes();
for (int i = 0, n = mapperList.getLength(); i < n; i++) {
    Node node = mapperList.item(i);
    if (node.getNodeType() == Node.ELEMENT_NODE) {
        NamedNodeMap attributeNodes = node.getAttributes();
        if (attributeNodes != null) {
            for (int j = 0; j < attributeNodes.getLength(); j++) {
                Node attribute = attributeNodes.item(j);
                String value = attribute.getNodeValue();
                if ("resource".equals(attribute.getNodeName())) {
                    resourceMapperList.add(value);
                }
            }
        }
    }
}

这部分代码解析了配置文件中的 节点,并从中提取出映射器文件的路径列表。

  • 解析 SQL 语句
Map<String, String> statementMap = new HashMap<>();
for (String mapperResource : resourceMapperList) {
    try (InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(mapperResource)) {
        Reader mapperReader = new InputStreamReader(inputStream);
        Document mapperDocument = docBuilder.parse(new InputSource(mapperReader));
        Node mapperNode = (Node) xpath.evaluate("/mapper", mapperDocument, XPathConstants.NODE);

        String namespace = "";
        NamedNodeMap mapperAttributeNodes = mapperNode.getAttributes();
        if (mapperAttributeNodes != null) {
            for (int j = 0; j < mapperAttributeNodes.getLength(); j++) {
                Node attribute = mapperAttributeNodes.item(j);
                String value = attribute.getNodeValue();
                if ("namespace".equals(attribute.getNodeName())) {
                    namespace = value;
                }
            }
        }

        Node selectNode = (Node) xpath.evaluate("select", mapperNode, XPathConstants.NODE);
        String mapperId = "";
        NamedNodeMap attributeNodes = selectNode.getAttributes();
        if (attributeNodes != null) {
            for (int j = 0; j < attributeNodes.getLength(); j++) {
                Node attribute = attributeNodes.item(j);
                String value = attribute.getNodeValue();
                if ("id".equals(attribute.getNodeName())) {
                    mapperId = value;
                }
            }
        }
        String sql = selectNode.getTextContent();
        statementMap.put(namespace + "." + mapperId, sql);
    }
}

这部分代码解析了每个映射器文件中的 节点,并从中提取出 SQL 语句的 namespace 和 id,并将它们组合成键值对存储在 statementMap 中。

  • 执行 SQL 查询
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;

try {
    // 加载 MySQL JDBC 驱动
    Class.forName(driver);

    // 获取数据库连接
    conn = DriverManager.getConnection(url, username, password);

    // 准备 SQL 语句
    String sql = statementMap.get(statement);

    // 创建预编译语句
    pstmt = conn.prepareStatement(sql);

    // 设置参数
    pstmt.setLong(1, param);

    // 执行 SQL 查询操作
    rs = pstmt.executeQuery();

    // 处理结果集
    StringBuilder result = new StringBuilder();
    while (rs.next()) {
        result.append("id: ").append(rs.getInt("id"))
              .append(", username: ").append(rs.getString("username"))
              .append(", email: ").append(rs.getString("email"));
    }
    return result.toString();

} catch (ClassNotFoundException | SQLException e) {
    e.printStackTrace();
} finally {
    // 关闭资源
    if (pstmt != null) {
        try {
            pstmt.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    if (rs != null) {
        try {
            rs.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    if (conn != null) {
        try {
            conn.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    in.close();
}

return "";

这部分代码实现了通过 JDBC 连接到数据库,并执行 SQL 查询的操作。它使用预编译语句来提高安全性,并正确处理了结果集。

测试用例

package org.apache.ibatis.session;

import org.junit.jupiter.api.Test;
import org.xml.sax.SAXException;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPathExpressionException;
import java.io.IOException;

class SqlSessionTest {

    @Test
    void selectOne() throws ParserConfigurationException, SAXException, XPathExpressionException, IOException {
        SqlSession sqlSession = new SqlSession();
        String statement = "org.apache.ibatis.domain.blog.mappers.AuthorMapper.selectAuthor";
        System.out.println(sqlSession.selectOne(statement, 101));
    }
}

测试类 SqlSessionTest 包含了一个 JUnit 测试用例 selectOne,用于测试 SqlSession 类中的 selectOne 方法。

整体项目结构

在这里插入图片描述

总结

本文我们实现了以下功能:

  • 加载配置文件:从类路径中加载 MapperConfig.xml 文件。
  • 解析数据源信息:提取MapperConfig.xml 文件中的数据库连接信息(驱动、URL、用户名和密码)。
  • 读取 Mapper 文件:读取MapperConfig.xml 文件中指定的 Mapper 文件。解析 Mapper 文件中的 SQL 语句,并将 SQL 语句及其标识符存储在 Map 中。

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

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

相关文章

中国移动机器人将投入养老场景;华为与APUS共筑AI医疗多场景应用

AgeTech News 一周行业大事件 华为与APUS合作&#xff0c;共筑AI医疗多场景应用 中国移动展出人形机器人&#xff0c;预计投入养老等场景 作为科技与奥富能签约&#xff0c;共拓智能适老化改造领域 天与养老与香港科技园&#xff0c;共探智慧养老新模式 中山大学合作中国…

改变函数调用上下文:apply与call方法详解及实例

目录 改变函数调用上下文&#xff1a;apply与call方法详解及实例 一、什么是 apply 方法&#xff1f; 1、apply 语法 2、apply 示例 二、什么是 call 方法&#xff1f; 1、call 语法 2、call 示例 三、apply 和 call 的共同与差异 1、apply 和 call 的共同点 2、apply…

opencv学习:基于计算机视觉的表情识别系统

简介 基于计算机视觉的表情识别系统&#xff0c;该系统能够从视频流中实时检测人脸&#xff0c;并识别出两种基本表情&#xff1a;大笑和微笑。实验通过分析人脸关键点来计算表情特征指标&#xff0c;从而判断表情类型。 原理 基于以下原理进行&#xff1a; 人脸检测&#x…

Transformer(Vit+注意力机制)

文献基本信息&#xff1a; Encoder-Decoder&#xff1a; Transformer的结构&#xff1a; 输入编码器解码器输出 Transformer的工作流程&#xff1a; 获取输入句子的每一个单词的表示向量X&#xff0c;X由单词的embedding&#xff08;embedding是一种将高维特征映射到低维的技…

机器学习中的图像处理与计算机视觉

引言 在现代计算机科学中&#xff0c;图像处理和计算机视觉已成为最活跃的研究领域之一&#xff0c;这得益于机器学习和深度学习的发展。本文将深入探讨图像处理与计算机视觉的基础概念、常见应用、关键技术、常用工具&#xff0c;以及在这些领域中的代码示例。通过本篇文章&a…

约80%的巴西消费者热捧跨境电商平台Shopee

巴西作为南美洲最大的经济体&#xff0c;拥有庞大的消费群体和日益增长的消费需求。随着互联网的普及和电子商务的快速发展&#xff0c;巴西消费者对海外商品的兴趣日益浓厚。他们渴望获得更多元化的商品选择&#xff0c;尤其是那些在国内难以找到的特色商品或国际知名品牌。这…

Python3 接口自动化测试,HTTPS下载文件(GET方法和POST方法)

Python3 接口自动化测试,HTTPS下载文件(GET方法和POST方法) requests-pkcs12 PyPI python中如何使用requests模块下载文件并获取进度提示 1、GET方法 1.1、调用 # 下载客户端(GET)def download_client_get(self, header_all):try:url = self.host + "/xxx/v1/xxx-mod…

DBdoctor推出无Agent轻量级纳管解决方案

目录 背景 DBdoctor推出无Agent轻量级纳管解决方案 方案优势&#xff1a; 实例纳管方式&#xff1a; 无Agent纳管可体验哪些功能&#xff1f; 1.全量SQL审核功能 2.实例巡检功能 3.性能洞察功能 4.基础监控功能 总结 背景 在数字化时代&#xff0c;数据库作为信息系…

前端拦截302重定向

背景: 根据业务场景需要拦截302做后续的逻辑处理 尝试一: : axios拦截 、、、、、async created() {// 获取302请求返回的location后手动修改video的src路径let targetSrc;try {await axios.get(this.video).then((res) > {const { headers, status } res;const { locat…

unity 屏幕波动反馈打击效果(附资源下载)

unity 屏幕波动反馈打击效果 一枪打出去整个屏幕都回波动的效果反馈。 知识点&#xff1a; 1、动画事件 2、屏幕后处理 效果如图&#xff1a;&#xff08;波动速度浮动都可调整&#xff09; 附件下载

[C#][winform]基于yolov8的DMS驾驶员抽烟打电话喝水吃东西检测系统C#源码+onnx模型+评估指标曲线+精美GUI界面

【重要说明】 该系统以opencvsharp作图像处理,onnxruntime做推理引擎&#xff0c;使用CPU进行推理&#xff0c;适合有显卡或者没有显卡windows x64系统均可&#xff0c;不支持macOS和Linux系统&#xff0c;不支持x86的windows操作系统。由于采用CPU推理&#xff0c;要比GPU慢。…

EWM 库存盘点

目录 1 业务流程图 2 后台配置 & 主数据 3 业务操作 1 流程图 2 后台配置 & 主数据 仓库活动区域设置 SCM Extended Warehouse Management -> Extended Warehouse Management -> Internal Warehouse Processes -> Physical Inventory -> Physical-Inv…

k8s-pod详解

Pod生命周期 我们一般将pod对象从创建至终的这段时间范围称为pod的生命周期&#xff0c;它主要包含下面的过程 pod创建过程 运行初始化容器&#xff08;init container&#xff09;过程 运行主容器&#xff08;main container&#xff09; 容器启动后钩子&#xff08;post st…

C语言【调试】(个人笔记版)

调试 前言一、Bug二、调试工具1.DeBug2.Release 三、调试快捷键1、断点 四、调试时查看程序的当前信息1、查看临时变量2、查看内存3、查看调用堆栈、汇编、寄存器 总结 前言 这篇文章大都是我的个人笔记&#xff1a; 调试在日常程序设计中是很重要的。调试说白了就是为了解决代…

zookeeper客户端

启动单机版的zookeeper 配置Maven环境 (1) IDEA自带maven (2) 更新Maven库镜像地址&#xff1a; ① 拷贝D:\Program Files\JetBrains\IntelliJ IDEA 2018.3.5\plugins\maven\lib\maven3\conf\settings.xml [IntelliJ的安装目录]到 C:/用户/username/.m2 (如果.m2文件不存在&…

大话哈希冲突

Map是很常用的数据结构, 而哈希表是 HashMap 等集合的底层实现之一&#xff0c;它通过将键的哈希值映射到数组中的位置来存储键值对。哈希冲突 (Hash Collision) 是指在使用哈希函数将数据映射到有限大小的哈希表时&#xff0c;不同的数据项被映射到了同一个哈希表位置上。 一…

【C++】拆分详解 - stack和queue

文章目录 一、stack的介绍和使用1. 简介2. 使用3. 模拟实现 二、queue的介绍和使用1. 简介2. 使用3. 模拟实现 三、容器适配器1. 简介2. STL标准库中的使用 四、deque&#xff08;了解&#xff09;1. 简介2. 底层原理2.1 底层空间2.2 模拟访问元素2.3 迭代器2.4 STL源码片段摘要…

高清无水印推文视频素材下载网站推荐

在制作抖音短视频时&#xff0c;选择合适的视频素材至关重要。想知道哪里可以下载热门的推文视频素材吗&#xff1f;别担心&#xff0c;我为你整理了六个高品质的视频素材网站&#xff0c;让我们一起来看看吧&#xff01; 蛙学网 首先介绍的是蛙学网&#xff0c;作为国内知名的…

期权懂|股票下跌时可以使用期权止损吗?

本期让我懂 你就懂的期权懂带大家来了解&#xff0c;股票下跌时可以使用期权止损吗&#xff1f;有兴趣的朋友可以看一下。期权小懂每日分享期权知识&#xff0c;帮助期权新手及时有效地掌握即市趋势与新资讯&#xff01; 股票下跌时可以使用期权止损吗&#xff1f; 在股市中&am…

Oracle 使用位图索引 Cost降低200倍! 探讨位图索引的利与弊

一.简介 位图索引&#xff08;Bitmap Index&#xff09; 是 Oracle 数据库中一种特殊类型的索引&#xff0c;适用于低基数&#xff08;Low Cardinality&#xff09;列&#xff0c;即那些列中可选值相对较少的情况下使用。它与常规的 B-tree 索引不同&#xff0c;位图索引通过位…