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

客户端存储 — IndexedDB 实现分页查询

前言

相信 IndexedDB 大家都有过了解,但是不一定每个人都有过实践,并且其中涉及到事务、游标等概念,会导致在初次使用时会有些不适应,那么本文会通过 IndexedDB 实现分页查询的形式进行实践,在开始之前,可以尝试思考一下浏览器的客户端存储你都了解哪些呢?

其实客户端存储分为下面几类:

  • cookie

  • Web Storage

    • sessionStorage
    • localStorage
  • IndexDB

cookie

cookies 的特点:

  • cookie 是与特定域进行绑定的
  • 所有 cookie 都会作为请求头部由浏览器发送给服务器
  • 大多数浏览器对 1个域 下所有 cookie 的限制是不超过 4096 Byte(即 4 KB),上下可以有 1 Byte 的误差

假如 cookie 中保存大量信息,那么可能会影响特定域下浏览器请求的性能,因为保存的 cookie 越大,请求完成的时间就越长,由于以上的这些限制, cookie 只适合保存服务器必要的信息.

Web Storage

Web Storage 的目的是为了解决客户端需要使用 cookie 存储一些不需要频繁发送回服务器的数据.

sessionStorage

sessionStorage 的特点:

  • 只存储会话数据,即数据只会存储到当前设置存储的 tab 页面被关闭或者是浏览器关闭
  • 只能存储字符串类型数据,不能存储结构化数据
  • 存储的数据不受页面刷新影响,可在浏览器崩溃并重启后恢复
  • 大多数浏览器限制 1个源 只能存储 5MB

localStorage

localStorage 的特点:

  • 存储的数据会一直保留,直到通过 JavaScript 删除或者用户清除浏览器缓存
  • 只能存储字符串类型数据,不能存储结构化数据
  • 存储的数据不受页面刷新影响,也不会因关闭窗口、标签页或重新启动浏览器而丢失
  • 大多数浏览器限制 1个源 只能存储 5MB

IndexDB

IndexedDB(Indexed Database API) ,是浏览器中 存储结构化数据 的一个方案,用于代替目前已废弃的 Web SQL Database API.

其中 API 的设计基本上是 异步的,大多数操作以请求的形式 异步执行,产生成功的结果或错误都有对应的 onerroronsuccess 事件处理程序来确定输出.

IndexedDB 的特点:

  • 能够存储结构化数据
  • IndexedDB 数据库是与 页面源 绑定的,不能跨域共享
  • 大小限制
    • Chrome 限制是每个源 5MB
    • Firefox 限制是每个源 50MB
    • Firefox(移动版) 限制每个源 5MB ,若超出则请求用户许可

从以上可以看出 IndexedDB 的限制其实 Web Storage 一样.

页面源(源)= 协议 + 域 + 端口

IndexedDB

数据库

IndexedDB 数据库是类似于 MySQL 或 Web SQL Database 的数据库,它使用对象存储数据而不是使用表格,并且属于 NoSQL 的风格.

建立数据库连接 —— indexedDB.open()

indexedDB.open(name, version) 其中 name 为数据库名称,version 为数据库版本,且版本只能为整数
通过 indexedDB.open(name, version) 使用数据库:

  • 如果给定名称的数据库 已存在,则会发送一个 打开请求
  • 如果 不存在,则会发送 创建打开 这个数据库的请求
  • 异步执行,返回 IDBRequest 实例,需要监听 onerroronsuccess 事件

如下将创建一个名为 test 的数据库:

image.png

let db, request, version = 1;
request = indexedDB.open("test", version);
request.onerror = (event) => alert(`Failed to open: ${event.target.errorCode}`);
request.onsuccess = (event) => {db = event.target.result;
};

对象存储 —— request.onupgradeneeded()

数据库 版本发生变化创建新数据库时 就会触发 onupgradeneeded 事件,此时需要指定或换修改 数据库模式 —— 包括数据库中的 对象存储 和对象 存储结构

如下将在 test 数据库下创建名为 productions 的对象存储模式

image.png

// 需要存储的对象
let production = {id: "20220128",name: "产品xxx",
}// 指定对象存储的模式
request.onupgradeneeded = (event) => {const db = event.target.result;// 若存在就的存储结构则删除(可选)if (db.objectStoreNames.contains("productions")) {db.deleteObjectStore("productions");}// 创建对象存储,keyPath 表示应该用作键存储对象的属性名db.createObjectStore("productions", {keyPath: "id"});
};

事务 —— db.transaction()

IndexedDB 是基于事务的

创建对象存储之后,剩下的所有操作都是通过事务完成的,即若要 读写数据,都要通过事务 把所有修改操作进行组织.

创建事务

通过 let transaction = db.transaction() 方法来创建事务:

  • db.transaction()不指定参数 则对数据库中所有 对象存储只读 权限
  • db.transaction("productions") — 只加载特定对象存储的事务,只读 权限
  • db.transaction(["productions","xxx"], "readwrite") —— 加载多个对象存储的事务,且权限为 读写
transaction.onerror = (event) => {// 整个事务被取消
};
transaction.oncomplete = (event) => {// 整个事务成功完成
};

获取指定对象存储 —— transaction.objectStore()

通过 let store = transaction.objectStore("productions") 获取 productions 的对象存储,得到了对应的 对象存储 后,就可以使用以下方法:

  • store.get() — 获取数据
  • store.getAll() — 获取所有数据
  • store.add() — 添加数据
  • store.put() — 更新数据
  • store.delete() — 删除数据
  • store.clear() — 清除数据

写入对象

通过对象存储的引用 store 使用 store.add()store.put() 往对象存储中写入数据,它们都只接收一个要存储的对象作为参数,前提是这个对象必须包含存储对象中的 keyPath 属性对应的属性字段.

对象存储中已存在 同名的键 时:

  • store.add() 导致错误
  • store.put() 重写该对象
// productions 是一个产品数组
let request, requests = [];
for (let product of productions) {request = store.add(product);request.onerror = () => {// 处理错误};request.onsuccess = () => {// 处理成功};requests.push(request);
}

查询数据

键查询 —— store.get()

键指的就是 onupgradeneeded 中指定的 keyPath 属性所对应的值,比如在上面我们指定了 { keyPath: 'id' },那么在使用键查询时,就必须要使用已存储数据对应属性的对应值.

假设需要获取如上图中的数据:

let storeName = "productions"
// 先创建事务,通过事务获取指定的存储对象
let store = db.transaction(storeName, 'readwrite').objectStore(storeName)
// 通过存储对象的 get 方法以及对应的 keyPath 键的 id 属性值获取数据
let request = store.get(1643527674720);
request.onsuccess = (event) => {console.log(event.target.result);
}
request.onerror = (event) => {console.log(`query_key error with code:${event.target.errorCode}`);
}

索引查询 —— store.index(key).get(value)

对某些数据集可能需要为对象存储指定多个键,以便于后续查询数据时可以通过不同的键去获取,需要创建存储对象时使用 store.createIndex() 去创建索引,即 onupgradeneeded 事件处理程序中,如下创建了一个名为 price 的索引,后续查询数据时就可以使用这个所以名和对应的值进行查找

创建索引

request.onupgradeneeded = (event) => {db = event.target.resultdb.createObjectStore("productions", {keyPath: "id"}).createIndex("price", "price", {unique: false});
};

使用索引

  let storeName = "productions"let store = db.transaction(storeName, 'readwrite').objectStore(storeName)let request = store.index('price').get('72.57');request.onsuccess = (event) => {console.log(event.target.result);}request.onerror = (event) => {console.log(`query_index error with code:${event.target.errorCode}`);}

游标查询 —— store.openCursor() + cursor.continue()

从上面可以知道,使用事务通过一个已知键或者索引都可以取得一条记录,但如果想取得多条数据,那就需要在事务中创建一个游标.

let cursorRequest = store.openCursor();
cursorRequest.onsuccess = (event) => {let cursor = event.target.result;// 当游标 cursor 值不存在时,代表游标查找结束或出现异常,所以需要提前判断if (!cursor) return;console.log(cursor.value);// 在本次游标指向中,通过显示调用 continue 方法就可以将游标指向下一个数据cursor.continue();
}
cursorRequest.onerror = (event) => {console.log(`query_cursor error with code:${event.target.errorCode}`);
}

范围查找 —— index(索引) + cursor(游标) + IDBKeyRange(键范围)

IDBKeyRange(键范围)

  • IDBKeyRange.only(1643607832742) 得到对应键值的记录
  • IDBKeyRange.lowerBound(1643607832742, false) 得到对应键值的下限(开始位置),第二个参数:
    • false —— 从 1643607832742 记录开始,直到最后
    • true —— 从 1643607832742 的下一条记录开始,直到最后
  • IDBKeyRange.upperBound(1643607832742, false) 得到对应键值的上限(结束位置),第二个参数:
    • false —— 从头开始,到 1643607832742 记录为止
    • true —— 从头开始,到 1643607832742 的前一条记录为止
  • IDBKeyRange.bound() —— 同时指定上下限,接收四个参数:下限键上限键可选布尔值 表示是否 跳过下限可选布尔值 是否 跳过上限

使用这样的查找方式更加理想,因为这样的方式可以实现根据指定索引在指定范围内开始游标查询,实现比较合理的范围查找.

 let request = store.index('price').openCursor(IDBKeyRange.only(1643607832742));
request.onsuccess = (event) => {let cursor = event.target.result;if (!cursor) return;console.log(cursor.value);cursor.continue();
}
request.onerror = (event) => {console.log(`query_idnex_cursor error with code:${event.target.errorCode}`);
}

实现分页查询

模拟数据

要实现查询的前提是得先有数据,这里就通过 setTimeout 模拟异步请求获取数据:

const getData = () => {setTimeout(() => {const list = []const now = Date.now()for (let index = 0; index < 100; index++) {let price = Math.random() * 100price = price < 10 ? price + 10 : pricelist.push({id: now + index,name: `产品_${index + 1}`,price: price.toFixed(2)})}data.value = list})
}

然后通过循环调用 add() 方法将数写入到数据库中

const inser = (data = []) => {let objectStore = db.transaction(storeName, 'readwrite').objectStore(storeName)data.forEach(item => objectStore.add({ ...item }))console.log('数据插入成功!')
} 

分页效果

实现效果如下

主要代码

useIndexDB.js

let db = null
let storeName = 'test_db'
let lastRecords = []
let currSize = 0
let indexArr = ["name", "price"]const getStoreByTransaction = () => db.transaction(storeName, 'readwrite').objectStore(storeName)const query = (payload) => {return new Promise((resovle, reject) => {let records = []let { name, value, type, size = 10 } = payload || {}let objectStore = getStoreByTransaction()let requestif (type) {request = type === 'next'? objectStore.openCursor(IDBKeyRange.lowerBound(lastRecords[lastRecords.length - 1].id, type)): objectStore.openCursor(IDBKeyRange.upperBound(lastRecords[0].id, true), type)} else {request = name? objectStore.index(name).openCursor(IDBKeyRange.only(value), type): objectStore.openCursor()}lastRecords = []request.onsuccess = (event) => {let cursor = event.target.resultif (!cursor || currSize >= size) {currSize = 0resovle(records)return}let lastItem = cursor.valuelastRecords.push(lastItem)records.push(lastItem)currSize++if (!name) {cursor.continue()} else {currSize = 0resovle(records)}}request.onerror = (event) => {reject(`query_idnex_cursor error with code:${event.target.errorCode}`)}})
}const inser = (data = []) => {let objectStore = db.transaction(storeName, 'readwrite').objectStore(storeName)data.forEach(item => objectStore.add({ ...item }))console.log('数据插入成功!')
}const open = (payload) => {payload = payload || { name: 'test', version: 1, storeName: "productions" }return new Promise((resovle, reject) => {let request = indexedDB.open(payload.name, payload.version)storeName = payload.storeNamerequest.onsuccess = (event) => {db = event.target.resultresovle({query,inser,getStoreByTransaction,db})console.log(`Database connection successfully established`)}request.onerror = (event) => {reject(`Failed to open: ${event.target.errorCode}`)}request.onupgradeneeded = (event) => {db = event.target.resultif (db.objectStoreNames.contains("productions")) {db.deleteObjectStore("productions")}let objectStore = db.createObjectStore("productions", {keyPath: "id"})indexArr.forEach((name) => {objectStore.createIndex(name, name, {unique: false})})}})
}export default open

App.vue

<script setup lang="ts">
import { reactive, ref, onMounted, computed } from "vue"
import openDB from "./useIndexDB.js";const data = ref([])
const queryResult = ref([])
const formItem = reactive({ price: '', name: '' })
let dbObj = nullonMounted(() => {openDB().then(({ query, inser }) => {dbObj = { query, inser }})
})const getData = () => {setTimeout(() => {const list = []const now = Date.now()for (let index = 0; index < 100; index++) {let price = Math.random() * 100price = price < 10 ? price + 10 : pricelist.push({id: now + index,name: `产品_${index + 1}`,price: price.toFixed(2)})}data.value = listconsole.log("数据获取成功!", data.value);})
}const insertData = () => {dbObj.inser(data.value)
}const payload = computed(() => {let obj = { value: formItem.name }if (formItem.name) {obj.name = 'name'obj.value = formItem.name}if (formItem.price) {obj.name = 'price'obj.value = formItem.price}return obj
})const loadPage = (type) => {dbObj.query({ type }).then((result) => {console.log(result)queryResult.value = result.sort((a,b)=> a.id - b.id)})
}const queryData = () => {dbObj.query({ ...payload.value }).then((result) => {console.log(result)queryResult.value = result.sort()})
}</script><template><div class="btn-box"><button @click="getData">获取数据</button><button @click="insertData">插入数据</button></div><div class="box"><label for="name">名称:<input id="name" v-model="formItem.name" type="text" /></label><label for="price">价格:<input id="price" v-model="formItem.price" type="text" /></label><button class="btn" @click="queryData">查询</button></div><ul class="list" v-show="queryResult.length"><li v-for="item in queryResult" :key="item.id">{{item.name}} —— {{ item.price}}</li><div><button @click="loadPage('prev')">上一页</button><button @click="loadPage('next')">下一页</button></div></ul>
</template><style>
#app {font-family: Avenir, Helvetica, Arial, sans-serif;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;text-align: center;color: #2c3e50;margin-top: 60px;transition: all 0.5s ease;
}
.box label {margin: 0 10px;
}
button {margin: 10px;
}
.list{width: 400px;margin: 10px auto;
}
</style>

相关文章:

客户端存储 — IndexedDB 实现分页查询

前言 相信 IndexedDB 大家都有过了解&#xff0c;但是不一定每个人都有过实践&#xff0c;并且其中涉及到事务、游标等概念&#xff0c;会导致在初次使用时会有些不适应&#xff0c;那么本文会通过 IndexedDB 实现分页查询的形式进行实践&#xff0c;在开始之前&#xff0c;可…...

logback 如何将日志输出到文件

如何作 将日志输出到文件需要使用 RollingFileAppender&#xff0c;该 Appender 必须定义 rollingPolicy &#xff0c;另外 rollingPollicy 下必须定义 fileNamePattern 和 encoder <appender name"fileAppender" class"ch.qos.logback.core.rolling.Rollin…...

Files.newBufferedReader和Files.readAllLines

在Java中&#xff0c;Files.newBufferedReader 和 Files.readAllLines 都是用于从文件中读取数据的工具方法&#xff0c;但它们的使用场景和功能有所不同。下面我将详细解释这两个方法的含义、用途、区别、优缺点以及各自的使用场景。 1. Files.newBufferedReader 含义和用途…...

MySQL 数据库备份与恢复全攻略

MySQL 数据库备份与恢复全攻略 引言 在现代应用中&#xff0c;数据库是核心组件之一。无论是个人项目还是企业级应用&#xff0c;数据的安全性和完整性都至关重要。为了防止数据丢失、损坏或意外删除&#xff0c;定期备份数据库是必不可少的。本文将详细介绍 MySQL 数据库的备…...

Appium中的api(一)

目录 1.基础python代码准备 1--参数的一些说明 2--python内所要编写的代码 解释 2.如何获取包名和界面名 1-api 2-完整代码 代码解释 3.如何关闭驱动连接 4.安装卸载app 1--卸载 2--安装 5.判断app是否安装 6.将应用放到后台在切换为前台的时间 7.UIAutomatorViewer的使用 1--找…...

【AI辅助设计】没错!训练FLUX LoRA就这么简单!

前言 得益于开源社区的力量&#xff0c;在各位大佬的努力下&#xff0c;现在16G VRAM的家用电脑也可以训练FLUX的LoRA了 &#x1f44f;。 今天我使用fluxgym这个方法&#xff0c;训练LoRA&#xff0c;并记录过程。 篇幅有限&#xff0c;这里就不一一展示了&#xff0c;有需要的…...

Mac 下安装FastDFS

首先我们需要下载相对应的安装包&#xff1a; libfastcommonFastDFS 下载完成后我们先将其解压到桌面。 1.安装libfastcommon 我们进入到libfastcommon-master目录中执行./make.sh和sudo ./make.sh install&#xff0c;具体代码如下&#xff1a; 2.安装FastDFS 同安装libfa…...

人工智能的未来:重塑生活与工作的变革者

随着人工智能&#xff08;AI&#xff09;技术的快速发展&#xff0c;我们正处于一个前所未有的变革时代。AI不仅在医疗、企业运营和日常生活中发挥着重要作用&#xff0c;而且正在重新定义我们的生活和工作方式。本文将探讨人工智能技术的应用前景以及它如何改变我们的生活和工…...

【微服务】Java 对接飞书多维表格使用详解

目录 一、前言 二、前置操作 2.1 开通企业飞书账户 2.2 确保账户具备多维表操作权限 2.3 创建一张测试用的多维表 2.4 获取飞书开放平台文档 2.5 获取Java SDK 三、应用App相关操作 3.1 创建应用过程 3.2 应用发布过程 3.3 应用添加操作权限 四、多维表应用授权操作…...

学习threejs,使用粒子实现下雪特效

&#x1f468;‍⚕️ 主页&#xff1a; gis分享者 &#x1f468;‍⚕️ 感谢各位大佬 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍⚕️ 收录于专栏&#xff1a;threejs gis工程师 文章目录 一、&#x1f340;前言1.1 ☘️THREE.Points简介1.11 ☘️…...

unity3d——Time

在Unity3D中&#xff0c;Time类是一个非常重要的工具类&#xff0c;它提供了一系列与时间相关的属性和方法&#xff0c;帮助开发者在游戏中实现各种时间相关的操作。以下是一些Time类常用的方法及其示例&#xff1a; 一、常用属性 Time.time 含义&#xff1a;表示从游戏开始到…...

天地图实现海量聚合marker--uniapp后端详细实现

本文章详细的讲解了前后端代码来 实现uniapp天地图功能的实现 以及 后端海量数据的聚合查询 和网格算法实现思路。 并对当数据量增加和用户频繁请求接口时可能导致服务器负载过高做了前后端优化。 前端uniapp&#xff1a; 实现了天地图的行政区划边界/地图切换/比例尺/海量数…...

Bug | 项目中数据库查询问题

问题描述 理论上&#xff0c;点击查询后&#xff0c;表头应当显示中文。而不是上面的在数据库中的表头【如上图示】 正常点击查询后&#xff0c;如果没有输入值&#xff0c;应当是查询所有的信息。 原因分析&#xff1a; 这里是直接使用SELECT * 导致的。例如&#xff1a; S…...

C++入门基础知识129—【关于C 库函数 - time()】

成长路上不孤单&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a; 【14后&#x1f60a;///C爱好者&#x1f60a;///持续分享所学&#x1f60a;///如有需要欢迎收藏转发///&#x1f60a;】 今日分享关于C 库函数 - time()的相关内容&#xff0…...

大文件秒传,分片上传,断点续传

大文件分片上传 一 功能描述 1.文件通过web端分片多线程上传到服务端&#xff0c;然后web端发起分片合并&#xff0c;完成大文件分片上传功能 2.上传过的大文件&#xff0c;实现秒传 3.上传过程中&#xff0c;服务异常退出&#xff0c;实现断点续传 二 流程图 三 代码运行…...

多生境扩增子探秘:深度溯源与多样性解析

分析微生物组数据的组成结构的一个主要挑战是确定其潜在来源。在微生物来源分析中&#xff0c;随机森林、SourceTracker和FEAST都有较广泛应用。今天&#xff0c;小编就带大家看一篇发表在《iMeta》的文章&#xff0c;使用溯源技术追踪微生物的来源与去向&#xff0c;揭示生物在…...

Selenium4自动化测试常用函数总结,各种场景操作实战

&#x1f345; 点击文末小卡片 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 seleninum作为自动化测试的工具&#xff0c;自然是提供了很多自动化操作的函数&#xff0c;下面列举下比较常用的函数&#xff0c;更多可见官方文档&#xff1a;…...

图像生成新范式:智源推出全能视觉生成模型 OmniGen

大型语言模型&#xff08;LLM&#xff09;的出现统一了语言生成任务&#xff0c;并彻底改变了人机交互。然而&#xff0c;在图像生成领域&#xff0c;能够在单一框架内处理各种任务的统一模型在很大程度上仍未得到探索。近日&#xff0c;智源推出了新的扩散模型架构 OmniGen&am…...

实现RPC接口的demo记录

1.Thrift RPC 接口实现 Demo Service public class DemoServiceImpl implements DemoService.Iface {private static final Logger logger LoggerFactory.getLogger(DemoServiceImpl.class);Overridepublic String sayHello(Context context, String msg) throws TException …...

Python期末题目 | 期末练习题【概念题+代码】

一、前言 Python 是一门功能强大且易于学习的编程语言&#xff0c;在高校中被广泛用作教学语言。Python 的期末考试通常会包含基础知识和编程实践&#xff0c;以考察学生的理解与应用能力。本文整理了一套 Python 期末练习题&#xff0c;包括选择题、填空题、判断题和代码题。…...

Docker 离线安装指南

参考文章 1、确认操作系统类型及内核版本 Docker依赖于Linux内核的一些特性&#xff0c;不同版本的Docker对内核版本有不同要求。例如&#xff0c;Docker 17.06及之后的版本通常需要Linux内核3.10及以上版本&#xff0c;Docker17.09及更高版本对应Linux内核4.9.x及更高版本。…...

【根据当天日期输出明天的日期(需对闰年做判定)。】2022-5-15

缘由根据当天日期输出明天的日期(需对闰年做判定)。日期类型结构体如下&#xff1a; struct data{ int year; int month; int day;};-编程语言-CSDN问答 struct mdata{ int year; int month; int day; }mdata; int 天数(int year, int month) {switch (month){case 1: case 3:…...

树莓派超全系列教程文档--(61)树莓派摄像头高级使用方法

树莓派摄像头高级使用方法 配置通过调谐文件来调整相机行为 使用多个摄像头安装 libcam 和 rpicam-apps依赖关系开发包 文章来源&#xff1a; http://raspberry.dns8844.cn/documentation 原文网址 配置 大多数用例自动工作&#xff0c;无需更改相机配置。但是&#xff0c;一…...

Leetcode 3577. Count the Number of Computer Unlocking Permutations

Leetcode 3577. Count the Number of Computer Unlocking Permutations 1. 解题思路2. 代码实现 题目链接&#xff1a;3577. Count the Number of Computer Unlocking Permutations 1. 解题思路 这一题其实就是一个脑筋急转弯&#xff0c;要想要能够将所有的电脑解锁&#x…...

pikachu靶场通关笔记22-1 SQL注入05-1-insert注入(报错法)

目录 一、SQL注入 二、insert注入 三、报错型注入 四、updatexml函数 五、源码审计 六、insert渗透实战 1、渗透准备 2、获取数据库名database 3、获取表名table 4、获取列名column 5、获取字段 本系列为通过《pikachu靶场通关笔记》的SQL注入关卡(共10关&#xff0…...

鸿蒙DevEco Studio HarmonyOS 5跑酷小游戏实现指南

1. 项目概述 本跑酷小游戏基于鸿蒙HarmonyOS 5开发&#xff0c;使用DevEco Studio作为开发工具&#xff0c;采用Java语言实现&#xff0c;包含角色控制、障碍物生成和分数计算系统。 2. 项目结构 /src/main/java/com/example/runner/├── MainAbilitySlice.java // 主界…...

Mysql中select查询语句的执行过程

目录 1、介绍 1.1、组件介绍 1.2、Sql执行顺序 2、执行流程 2.1. 连接与认证 2.2. 查询缓存 2.3. 语法解析&#xff08;Parser&#xff09; 2.4、执行sql 1. 预处理&#xff08;Preprocessor&#xff09; 2. 查询优化器&#xff08;Optimizer&#xff09; 3. 执行器…...

Unity中的transform.up

2025年6月8日&#xff0c;周日下午 在Unity中&#xff0c;transform.up是Transform组件的一个属性&#xff0c;表示游戏对象在世界空间中的“上”方向&#xff08;Y轴正方向&#xff09;&#xff0c;且会随对象旋转动态变化。以下是关键点解析&#xff1a; 基本定义 transfor…...

Axure 下拉框联动

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

数据挖掘是什么?数据挖掘技术有哪些?

目录 一、数据挖掘是什么 二、常见的数据挖掘技术 1. 关联规则挖掘 2. 分类算法 3. 聚类分析 4. 回归分析 三、数据挖掘的应用领域 1. 商业领域 2. 医疗领域 3. 金融领域 4. 其他领域 四、数据挖掘面临的挑战和未来趋势 1. 面临的挑战 2. 未来趋势 五、总结 数据…...