diff --git a/.babelrc b/.babelrc new file mode 100644 index 00000000..55754d07 --- /dev/null +++ b/.babelrc @@ -0,0 +1,3 @@ +{ + "compact": false +} diff --git a/.editorconfig b/.editorconfig index ee762040..d72a75ea 100644 --- a/.editorconfig +++ b/.editorconfig @@ -19,7 +19,7 @@ insert_final_newline = true [*.{bat, cmd}] end_of_line = crlf -[*.{java, gradle, groovy, kt, sh, xml}] +[*.{java, gradle, groovy, kt, sh}] indent_size = 4 [*.md] diff --git a/.gitattributes b/.gitattributes index 07962a1f..eaae227f 100644 --- a/.gitattributes +++ b/.gitattributes @@ -22,6 +22,7 @@ *.less text *.sql text *.properties text +*.md text # unix style *.sh text eol=lf diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 36b705cb..04010943 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -15,7 +15,7 @@ jobs: strategy: matrix: - node-version: [14.x] + node-version: [16.x] steps: # 使用的动作。格式:userName/repoName。作用:检出仓库,获取源码。 官方actions库:https://github.com/actions diff --git a/.gitignore b/.gitignore index 83948575..7d98dac9 100644 --- a/.gitignore +++ b/.gitignore @@ -29,7 +29,6 @@ hs_err_pid* # maven plugin temp files .flattened-pom.xml -package-lock.json # ------------------------------- javascript ------------------------------- @@ -37,10 +36,12 @@ package-lock.json node_modules # temp folders -.temp +build dist _book _jsdoc +.temp +.deploy*/ # temp files *.log @@ -48,7 +49,11 @@ npm-debug.log* yarn-debug.log* yarn-error.log* bundle*.js +.DS_Store +Thumbs.db +db.json book.pdf +package-lock.json # ------------------------------- intellij ------------------------------- diff --git a/README.md b/README.md index a509079a..5cc46533 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

- logo + logo

@@ -15,7 +15,7 @@ - commit + build @@ -31,55 +31,36 @@ > - 🔁 项目同步维护:[Github](https://github.com/dunwu/db-tutorial/) | [Gitee](https://gitee.com/turnon/db-tutorial/) > - 📖 电子书阅读:[Github Pages](https://dunwu.github.io/db-tutorial/) | [Gitee Pages](https://turnon.gitee.io/db-tutorial/) -## 分布式 - -### 分布式综合 - -- [分布式面试总结](https://dunwu.github.io/waterdrop/pages/f9209d/) - -### 分布式理论 - -- [分布式理论](https://dunwu.github.io/waterdrop/pages/286bb3/) - 关键词:`拜占庭将军`、`CAP`、`BASE`、`错误的分布式假设` -- [共识性算法 Paxos](https://dunwu.github.io/waterdrop/pages/0276bb/) - 关键词:`共识性算法` -- [共识性算法 Raft](https://dunwu.github.io/waterdrop/pages/4907dc/) - 关键词:`共识性算法` -- [分布式算法 Gossip](https://dunwu.github.io/waterdrop/pages/71539a/) - 关键词:`数据传播` - -### 分布式关键技术 - -- 集群 -- 复制 -- 分区 -- 选主 +## 数据库综合 -#### 流量调度 +### 分布式存储原理 -- [流量控制](https://dunwu.github.io/waterdrop/pages/60bb6d/) - 关键词:`限流`、`熔断`、`降级`、`计数器法`、`时间窗口法`、`令牌桶法`、`漏桶法` -- [负载均衡](https://dunwu.github.io/waterdrop/pages/98a1c1/) - 关键词:`轮询`、`随机`、`最少连接`、`源地址哈希`、`一致性哈希`、`虚拟 hash 槽` -- [服务路由](https://dunwu.github.io/waterdrop/pages/3915e8/) - 关键词:`路由`、`条件路由`、`脚本路由`、`标签路由` -- 服务网关 -- [分布式会话](https://dunwu.github.io/waterdrop/pages/95e45f/) - 关键词:`粘性 Session`、`Session 复制共享`、`基于缓存的 session 共享` +#### 分布式理论 -#### 数据调度 +- [分布式一致性](https://dunwu.github.io/blog/pages/dac0e2/) +- [深入剖析共识性算法 Paxos](https://dunwu.github.io/blog/pages/874539/) +- [深入剖析共识性算法 Raft](https://dunwu.github.io/blog/pages/e40812/) +- [分布式算法 Gossip](https://dunwu.github.io/blog/pages/d15993/) -- [数据缓存](https://dunwu.github.io/waterdrop/pages/fd0aaa/) - 关键词:`进程内缓存`、`分布式缓存`、`缓存雪崩`、`缓存穿透`、`缓存击穿`、`缓存更新`、`缓存预热`、`缓存降级` -- [读写分离](https://dunwu.github.io/waterdrop/pages/3faf18/) -- [分库分表](https://dunwu.github.io/waterdrop/pages/e1046e/) - 关键词:`分片`、`路由`、`迁移`、`扩容`、`双写`、`聚合` -- [分布式 ID](https://dunwu.github.io/waterdrop/pages/3ae455/) - 关键词:`UUID`、`自增序列`、`雪花算法`、`Leaf` -- [分布式事务](https://dunwu.github.io/waterdrop/pages/e1881c/) - 关键词:`2PC`、`3PC`、`TCC`、`本地消息表`、`MQ 消息`、`SAGA` -- [分布式锁](https://dunwu.github.io/waterdrop/pages/40ac64/) - 关键词:`数据库`、`Redis`、`ZooKeeper`、`互斥`、`可重入`、`死锁`、`容错`、`自旋尝试` +#### 分布式关键技术 -#### 资源调度 +##### 流量调度 -- 弹性伸缩 +- [流量控制](https://dunwu.github.io/blog/pages/282676/) +- [负载均衡](https://dunwu.github.io/blog/pages/98a1c1/) +- [服务路由](https://dunwu.github.io/blog/pages/d04ece/) +- [分布式会话基本原理](https://dunwu.github.io/blog/pages/3e66c2/) -#### 服务治理 +##### 数据调度 -- [服务注册和发现](https://dunwu.github.io/waterdrop/pages/1a90aa/) -- [服务容错](https://dunwu.github.io/waterdrop/pages/e32c7e/) -- 服务编排 -- 服务版本管理 +- [缓存基本原理](https://dunwu.github.io/blog/pages/471208/) +- [读写分离基本原理](https://dunwu.github.io/blog/pages/7da6ca/) +- [分库分表基本原理](https://dunwu.github.io/blog/pages/103382/) +- [分布式 ID 基本原理](https://dunwu.github.io/blog/pages/0b2e59/) +- [分布式事务基本原理](https://dunwu.github.io/blog/pages/910bad/) +- [分布式锁基本原理](https://dunwu.github.io/blog/pages/69360c/) -## 数据库综合 +### 其他 - [Nosql 技术选型](docs/12.数据库/01.数据库综合/01.Nosql技术选型.md) - [数据结构与数据库索引](docs/12.数据库/01.数据库综合/02.数据结构与数据库索引.md) @@ -94,15 +75,17 @@ > [关系型数据库](docs/12.数据库/03.关系型数据库) 整理主流关系型数据库知识点。 -### 公共知识 +### 关系型数据库综合 - [关系型数据库面试总结](docs/12.数据库/03.关系型数据库/01.综合/01.关系型数据库面试.md) 💯 -- [SQL Cheat Sheet](docs/12.数据库/03.关系型数据库/01.综合/02.SqlCheatSheet.md) 是一个 SQL 入门教程。 -- [扩展 SQL](docs/12.数据库/03.关系型数据库/01.综合/03.扩展SQL.md) 是一个 SQL 入门教程。 +- [SQL 语法基础特性](docs/12.数据库/03.关系型数据库/01.综合/02.SQL语法基础特性.md) +- [SQL 语法高级特性](docs/12.数据库/03.关系型数据库/01.综合/03.SQL语法高级特性.md) +- [扩展 SQL](docs/12.数据库/03.关系型数据库/01.综合/03.扩展SQL.md) +- [SQL Cheat Sheet](docs/12.数据库/03.关系型数据库/01.综合/99.SqlCheatSheet.md) ### Mysql -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200716103611.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200716103611.png) - [Mysql 应用指南](docs/12.数据库/03.关系型数据库/02.Mysql/01.Mysql应用指南.md) ⚡ - [Mysql 工作流](docs/12.数据库/03.关系型数据库/02.Mysql/02.MySQL工作流.md) - 关键词:`连接`、`缓存`、`语法分析`、`优化`、`执行引擎`、`redo log`、`bin log`、`两阶段提交` @@ -145,7 +128,7 @@ ### Redis -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200713105627.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200713105627.png) - [Redis 面试总结](docs/12.数据库/05.KV数据库/01.Redis/01.Redis面试总结.md) 💯 - [Redis 应用指南](docs/12.数据库/05.KV数据库/01.Redis/02.Redis应用指南.md) ⚡ - 关键词:`内存淘汰`、`事件`、`事务`、`管道`、`发布与订阅` @@ -161,12 +144,16 @@ ### HBase -> [HBase](https://dunwu.github.io/bigdata-tutorial/hbase) 📚 因为常用于大数据项目,所以将其文档和源码整理在 [bigdata-tutorial](https://dunwu.github.io/bigdata-tutorial/) 项目中。 - -- [HBase 原理](https://github.com/dunwu/bigdata-tutorial/blob/master/docs/hbase/HBase原理.md) ⚡ -- [HBase 命令](https://github.com/dunwu/bigdata-tutorial/blob/master/docs/hbase/HBase命令.md) -- [HBase 应用](https://github.com/dunwu/bigdata-tutorial/blob/master/docs/hbase/HBase应用.md) -- [HBase 运维](https://github.com/dunwu/bigdata-tutorial/blob/master/docs/hbase/HBase运维.md) +- [HBase 快速入门](docs/12.数据库/06.列式数据库/01.HBase/01.HBase快速入门.md) +- [HBase 数据模型](docs/12.数据库/06.列式数据库/01.HBase/02.HBase数据模型.md) +- [HBase Schema 设计](docs/12.数据库/06.列式数据库/01.HBase/03.HBaseSchema设计.md) +- [HBase 架构](docs/12.数据库/06.列式数据库/01.HBase/04.HBase架构.md) +- [HBase Java API 基础特性](docs/12.数据库/06.列式数据库/01.HBase/10.HBaseJavaApi基础特性.md) +- [HBase Java API 高级特性之过滤器](docs/12.数据库/06.列式数据库/01.HBase/11.HBaseJavaApi高级特性之过滤器.md) +- [HBase Java API 高级特性之协处理器](docs/12.数据库/06.列式数据库/01.HBase/12.HBaseJavaApi高级特性之协处理器.md) +- [HBase Java API 其他高级特性](docs/12.数据库/06.列式数据库/01.HBase/13.HBaseJavaApi其他高级特性.md) +- [HBase 运维](docs/12.数据库/06.列式数据库/01.HBase/21.HBase运维.md) +- [HBase 命令](docs/12.数据库/06.列式数据库/01.HBase/22.HBase命令.md) ## 搜索引擎数据库 diff --git a/codes/javadb/elasticsearch/elasticsearch6/pom.xml b/codes/javadb/elasticsearch/elasticsearch6/pom.xml new file mode 100644 index 00000000..72683b1d --- /dev/null +++ b/codes/javadb/elasticsearch/elasticsearch6/pom.xml @@ -0,0 +1,69 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 2.7.7 + + + io.github.dunwu + javadb-elasticsearch6 + 1.0.0 + jar + + + 1.8 + ${java.version} + ${java.version} + UTF-8 + UTF-8 + + + + + org.springframework.boot + spring-boot-starter-aop + + + org.springframework.boot + spring-boot-starter-web + + + org.elasticsearch.client + elasticsearch-rest-high-level-client + + + org.projectlombok + lombok + + + cn.hutool + hutool-all + 5.8.25 + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + org.elasticsearch.client + elasticsearch-rest-high-level-client + 6.4.3 + + + org.elasticsearch + elasticsearch + 6.4.3 + + + + + diff --git a/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/ElasticsearchFactory.java b/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/ElasticsearchFactory.java new file mode 100644 index 00000000..33109cb0 --- /dev/null +++ b/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/ElasticsearchFactory.java @@ -0,0 +1,173 @@ +package io.github.dunwu.javadb.elasticsearch; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.StrUtil; +import lombok.extern.slf4j.Slf4j; +import org.apache.http.HttpHost; +import org.elasticsearch.client.RestClient; +import org.elasticsearch.client.RestClientBuilder; +import org.elasticsearch.client.RestHighLevelClient; + +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Elasticsearch 客户端实例工厂 + * + * @author Zhang Peng + * @date 2024-02-07 + */ +@Slf4j +public class ElasticsearchFactory { + + public static int CONNECT_TIMEOUT_MILLIS = 1000; + + public static int SOCKET_TIMEOUT_MILLIS = 30000; + + public static int CONNECTION_REQUEST_TIMEOUT_MILLIS = 500; + + public static int MAX_CONN_TOTAL = 30; + + public static int MAX_CONN_PER_ROUTE = 10; + + public static RestClient newRestClient() { + // 从配置中心读取环境变量 + String env = "test"; + return newRestClient(env); + } + + public static RestClient newRestClient(String env) { + String hostsConfig = getDefaultEsAddress(env); + List hosts = StrUtil.split(hostsConfig, ","); + return newRestClient(hosts); + } + + public static RestClient newRestClient(Collection hosts) { + HttpHost[] httpHosts = toHttpHostList(hosts); + RestClientBuilder builder = getRestClientBuilder(httpHosts); + if (builder == null) { + return null; + } + try { + return builder.build(); + } catch (Exception e) { + log.error("【ES】connect failed.", e); + return null; + } + } + + public static RestHighLevelClient newRestHighLevelClient() { + // 从配置中心读取环境变量 + String env = "test"; + return newRestHighLevelClient(env); + } + + public static RestHighLevelClient newRestHighLevelClient(String env) { + String hostsConfig = getDefaultEsAddress(env); + List hosts = StrUtil.split(hostsConfig, ","); + return newRestHighLevelClient(hosts); + } + + public static RestHighLevelClient newRestHighLevelClient(Collection hosts) { + HttpHost[] httpHosts = toHttpHostList(hosts); + RestClientBuilder builder = getRestClientBuilder(httpHosts); + if (builder == null) { + return null; + } + try { + return new RestHighLevelClient(builder); + } catch (Exception e) { + log.error("【ES】connect failed.", e); + return null; + } + } + + public static ElasticsearchTemplate newElasticsearchTemplate() { + // 从配置中心读取环境变量 + String env = "test"; + return newElasticsearchTemplate(env); + } + + public static ElasticsearchTemplate newElasticsearchTemplate(String env) { + String hostsConfig = getDefaultEsAddress(env); + List hosts = StrUtil.split(hostsConfig, ","); + return newElasticsearchTemplate(hosts); + } + + public static ElasticsearchTemplate newElasticsearchTemplate(Collection hosts) { + RestHighLevelClient client = newRestHighLevelClient(hosts); + if (client == null) { + return null; + } + return new ElasticsearchTemplate(client); + } + + public static ElasticsearchTemplate newElasticsearchTemplate(RestHighLevelClient client) { + if (client == null) { + return null; + } + return new ElasticsearchTemplate(client); + } + + public static RestClientBuilder getRestClientBuilder(HttpHost[] httpHosts) { + if (ArrayUtil.isEmpty(httpHosts)) { + log.error("【ES】connect failed. hosts are empty."); + return null; + } + RestClientBuilder restClientBuilder = RestClient.builder(httpHosts); + restClientBuilder.setRequestConfigCallback(builder -> { + builder.setConnectTimeout(CONNECT_TIMEOUT_MILLIS); + builder.setSocketTimeout(SOCKET_TIMEOUT_MILLIS); + builder.setConnectionRequestTimeout(CONNECTION_REQUEST_TIMEOUT_MILLIS); + return builder; + }); + restClientBuilder.setHttpClientConfigCallback(builder -> { + builder.setMaxConnTotal(MAX_CONN_TOTAL); + builder.setMaxConnPerRoute(MAX_CONN_PER_ROUTE); + return builder; + }); + return restClientBuilder; + } + + private static HttpHost[] toHttpHostList(Collection hosts) { + if (CollectionUtil.isEmpty(hosts)) { + return new HttpHost[0]; + } + List list = hosts.stream().map(ElasticsearchFactory::toHttpHost).collect(Collectors.toList()); + if (CollectionUtil.isEmpty(list)) { + return new HttpHost[0]; + } + return list.toArray(new HttpHost[0]); + } + + public static HttpHost toHttpHost(String host) { + List params = StrUtil.split(host, ":"); + return new HttpHost(params.get(0), Integer.parseInt(params.get(1)), "http"); + } + + public static String getDefaultEsAddress() { + // 从配置中心读取环境变量 + String env = "test"; + return getDefaultEsAddress(env); + } + + private static String getDefaultEsAddress(String env) { + String defaultAddress; + switch (env) { + case "prd": + defaultAddress = "127.0.0.1:9200,127.0.0.2:9200,127.0.0.3:9200"; + break; + case "pre": + defaultAddress = "127.0.0.1:9200"; + break; + case "test": + default: + defaultAddress = "127.0.0.1:9200"; + break; + } + return defaultAddress; + } + +} diff --git a/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/ElasticsearchTemplate.java b/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/ElasticsearchTemplate.java new file mode 100644 index 00000000..5e627cbe --- /dev/null +++ b/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/ElasticsearchTemplate.java @@ -0,0 +1,701 @@ +package io.github.dunwu.javadb.elasticsearch; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.StrUtil; +import io.github.dunwu.javadb.elasticsearch.entity.BaseEsEntity; +import io.github.dunwu.javadb.elasticsearch.entity.common.PageData; +import io.github.dunwu.javadb.elasticsearch.entity.common.ScrollData; +import io.github.dunwu.javadb.elasticsearch.util.JsonUtil; +import lombok.extern.slf4j.Slf4j; +import org.elasticsearch.ElasticsearchException; +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.DocWriteResponse; +import org.elasticsearch.action.admin.indices.alias.Alias; +import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest; +import org.elasticsearch.action.admin.indices.alias.get.GetAliasesRequest; +import org.elasticsearch.action.admin.indices.create.CreateIndexRequest; +import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest; +import org.elasticsearch.action.admin.indices.get.GetIndexRequest; +import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest; +import org.elasticsearch.action.bulk.BackoffPolicy; +import org.elasticsearch.action.bulk.BulkProcessor; +import org.elasticsearch.action.bulk.BulkRequest; +import org.elasticsearch.action.bulk.BulkResponse; +import org.elasticsearch.action.delete.DeleteRequest; +import org.elasticsearch.action.get.GetRequest; +import org.elasticsearch.action.get.GetResponse; +import org.elasticsearch.action.get.MultiGetItemResponse; +import org.elasticsearch.action.get.MultiGetRequest; +import org.elasticsearch.action.get.MultiGetResponse; +import org.elasticsearch.action.index.IndexRequest; +import org.elasticsearch.action.index.IndexResponse; +import org.elasticsearch.action.search.ClearScrollRequest; +import org.elasticsearch.action.search.ClearScrollResponse; +import org.elasticsearch.action.search.SearchRequest; +import org.elasticsearch.action.search.SearchResponse; +import org.elasticsearch.action.search.SearchScrollRequest; +import org.elasticsearch.action.support.WriteRequest; +import org.elasticsearch.action.support.master.AcknowledgedResponse; +import org.elasticsearch.action.update.UpdateRequest; +import org.elasticsearch.action.update.UpdateResponse; +import org.elasticsearch.client.GetAliasesResponse; +import org.elasticsearch.client.RequestOptions; +import org.elasticsearch.client.RestHighLevelClient; +import org.elasticsearch.cluster.metadata.AliasMetaData; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.unit.ByteSizeUnit; +import org.elasticsearch.common.unit.ByteSizeValue; +import org.elasticsearch.common.unit.TimeValue; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentFactory; +import org.elasticsearch.index.query.BoolQueryBuilder; +import org.elasticsearch.index.query.QueryBuilder; +import org.elasticsearch.index.query.QueryBuilders; +import org.elasticsearch.rest.RestStatus; +import org.elasticsearch.search.Scroll; +import org.elasticsearch.search.SearchHits; +import org.elasticsearch.search.builder.SearchSourceBuilder; +import org.elasticsearch.search.sort.SortOrder; + +import java.io.Closeable; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.function.BiConsumer; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * ES 工具类 + * + * @author Zhang Peng + * @date 2023-06-27 + */ +@Slf4j +public class ElasticsearchTemplate implements Closeable { + + private final RestHighLevelClient client; + + public ElasticsearchTemplate(RestHighLevelClient client) { + this.client = client; + } + + public RestHighLevelClient getClient() { + return client; + } + + public BulkProcessor newAsyncBulkProcessor() { + BulkProcessor.Listener listener = new BulkProcessor.Listener() { + @Override + public void beforeBulk(long executionId, BulkRequest request) { + } + + @Override + public void afterBulk(long executionId, BulkRequest request, BulkResponse response) { + if (response.hasFailures()) { + log.error("【ES】Bulk [{}] executed with failures,response = {}", executionId, + response.buildFailureMessage()); + } + } + + @Override + public void afterBulk(long executionId, BulkRequest request, Throwable failure) { + } + }; + + int bulkTimeout = 30; + int bulkActions = 1000; + int bulkSize = 5; + int concurrentRequests = 2; + int flushInterval = 1000; + int retryInterval = 100; + int retryLimit = 3; + BiConsumer> bulkConsumer = + (request, bulkListener) -> client.bulkAsync(request, RequestOptions.DEFAULT, bulkListener); + BackoffPolicy backoffPolicy = + BackoffPolicy.constantBackoff(TimeValue.timeValueMillis(retryInterval), retryLimit); + BulkProcessor bulkProcessor = BulkProcessor.builder(bulkConsumer, listener) + // 1000条数据请求执行一次bulk + .setBulkActions(bulkActions) + // 5mb的数据刷新一次bulk + .setBulkSize(new ByteSizeValue(bulkSize, ByteSizeUnit.MB)) + // 并发请求数量, 0不并发, 1并发允许执行 + .setConcurrentRequests(concurrentRequests) + // 刷新间隔时间 + .setFlushInterval(TimeValue.timeValueMillis(flushInterval)) + // 重试次数、间隔时间 + .setBackoffPolicy(backoffPolicy).build(); + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + try { + bulkProcessor.flush(); + bulkProcessor.awaitClose(bulkTimeout, TimeUnit.SECONDS); + } catch (Exception e) { + log.error("【ES】Failed to close bulkProcessor", e); + } + log.info("【ES】bulkProcessor closed!"); + })); + return bulkProcessor; + } + + // ==================================================================== + // 索引管理操作 + // ==================================================================== + + public void createIndex(String index, String type, String alias, int shard, int replica) throws IOException { + + if (StrUtil.isBlank(index) || StrUtil.isBlank(type)) { + throw new ElasticsearchException("【ES】index、type 不能为空!"); + } + + CreateIndexRequest request = new CreateIndexRequest(index); + if (StrUtil.isNotBlank(alias)) { + request.alias(new Alias(alias)); + } + + Settings.Builder settings = + Settings.builder().put("index.number_of_shards", shard).put("index.number_of_replicas", replica); + request.settings(settings); + AcknowledgedResponse response = client.indices().create(request, RequestOptions.DEFAULT); + if (!response.isAcknowledged()) { + String msg = StrUtil.format("【ES】创建索引失败!index: {}, type: {}", index, type); + throw new ElasticsearchException(msg); + } + } + + public void deleteIndex(String index) throws IOException { + DeleteIndexRequest request = new DeleteIndexRequest(index); + AcknowledgedResponse response = client.indices().delete(request, RequestOptions.DEFAULT); + if (!response.isAcknowledged()) { + String msg = StrUtil.format("【ES】删除索引失败!index: {}", index); + throw new ElasticsearchException(msg); + } + } + + public void updateAlias(String index, String alias) throws IOException { + IndicesAliasesRequest request = new IndicesAliasesRequest(); + IndicesAliasesRequest.AliasActions aliasAction = + new IndicesAliasesRequest.AliasActions(IndicesAliasesRequest.AliasActions.Type.ADD).index(index) + .alias(alias); + request.addAliasAction(aliasAction); + AcknowledgedResponse response = client.indices().updateAliases(request, RequestOptions.DEFAULT); + if (!response.isAcknowledged()) { + String msg = StrUtil.format("【ES】更新索引别名失败!index: {}, alias: {}", index, alias); + throw new ElasticsearchException(msg); + } + } + + public boolean isIndexExists(String index) throws IOException { + GetIndexRequest request = new GetIndexRequest(); + return client.indices().exists(request.indices(index), RequestOptions.DEFAULT); + } + + public Set getIndexSet(String alias) throws IOException { + GetAliasesRequest request = new GetAliasesRequest(alias); + GetAliasesResponse response = client.indices().getAlias(request, RequestOptions.DEFAULT); + if (StrUtil.isNotBlank(response.getError())) { + String msg = StrUtil.format("【ES】获取索引失败!alias: {}, error: {}", alias, response.getError()); + throw new ElasticsearchException(msg); + } + if (response.getException() != null) { + throw response.getException(); + } + Map> aliasMap = response.getAliases(); + return aliasMap.keySet(); + } + + public void setMapping(String index, String type, Map propertiesMap) throws IOException { + + if (MapUtil.isEmpty(propertiesMap)) { + throw new ElasticsearchException("【ES】设置 mapping 的 properties 不能为空!"); + } + + PutMappingRequest request = new PutMappingRequest(index).type(type); + XContentBuilder builder = XContentFactory.jsonBuilder(); + builder.startObject(); + builder.startObject(type); + builder.startObject("properties"); + + for (Map.Entry entry : propertiesMap.entrySet()) { + + String field = entry.getKey(); + String fieldType = entry.getValue(); + if (StrUtil.isBlank(field) || StrUtil.isBlank(fieldType)) { + continue; + } + + builder.startObject(field); + { + builder.field("type", fieldType); + } + builder.endObject(); + } + + builder.endObject(); + builder.endObject(); + builder.endObject(); + request.source(builder); + AcknowledgedResponse response = client.indices().putMapping(request, RequestOptions.DEFAULT); + if (!response.isAcknowledged()) { + throw new ElasticsearchException("【ES】设置 mapping 失败!"); + } + } + + // ==================================================================== + // CRUD 操作 + // ==================================================================== + + public T save(String index, String type, T entity) throws IOException { + + if (entity == null) { + log.warn("【ES】save 实体为空!"); + return null; + } + + Map map = toMap(entity); + if (MapUtil.isEmpty(map)) { + log.warn("【ES】save 实体数据为空!"); + return null; + } + + IndexRequest request = new IndexRequest(index, type).source(map); + if (entity.getDocId() != null) { + request.id(entity.getDocId()); + } + IndexResponse response = client.index(request, RequestOptions.DEFAULT); + if (response == null) { + log.warn("【ES】save 响应结果为空!"); + return null; + } + + if (response.getResult() == DocWriteResponse.Result.CREATED + || response.getResult() == DocWriteResponse.Result.UPDATED) { + return entity; + } else { + log.warn("【ES】save 失败,result: {}!", response.getResult()); + return null; + } + } + + public boolean saveBatch(String index, String type, Collection list) + throws IOException { + + if (CollectionUtil.isEmpty(list)) { + return true; + } + + BulkRequest bulkRequest = toBulkIndexRequest(index, type, list); + BulkResponse response = client.bulk(bulkRequest, RequestOptions.DEFAULT); + if (response == null) { + log.warn("【ES】saveBatch 失败,result 为空!list: {}", JsonUtil.toString(list)); + return false; + } + if (response.hasFailures()) { + log.warn("【ES】saveBatch 失败,result: {}!", response.buildFailureMessage()); + return false; + } + return true; + } + + public void asyncSaveBatch(String index, String type, Collection list, + ActionListener listener) { + if (CollectionUtil.isEmpty(list)) { + return; + } + BulkRequest bulkRequest = toBulkIndexRequest(index, type, list); + client.bulkAsync(bulkRequest, RequestOptions.DEFAULT, listener); + } + + public T updateById(String index, String type, T entity) throws IOException { + + if (entity == null) { + log.warn("【ES】updateById 实体为空!"); + return null; + } + + if (entity.getDocId() == null) { + log.warn("【ES】updateById docId 为空!"); + return null; + } + + Map map = toMap(entity); + if (MapUtil.isEmpty(map)) { + log.warn("【ES】updateById 实体数据为空!"); + return null; + } + + UpdateRequest request = new UpdateRequest(index, type, entity.getDocId()).doc(map); + UpdateResponse response = client.update(request, RequestOptions.DEFAULT); + if (response == null) { + log.warn("【ES】updateById 响应结果为空!"); + return null; + } + + if (response.getResult() == DocWriteResponse.Result.UPDATED) { + return entity; + } else { + log.warn("【ES】updateById 响应结果无效,result: {}!", response.getResult()); + return null; + } + } + + public boolean updateBatchIds(String index, String type, Collection list) + throws IOException { + + if (CollectionUtil.isEmpty(list)) { + return true; + } + + BulkRequest bulkRequest = toBulkUpdateRequest(index, type, list); + BulkResponse response = client.bulk(bulkRequest, RequestOptions.DEFAULT); + if (response == null) { + log.warn("【ES】updateBatchIds 失败,result 为空!list: {}", JsonUtil.toString(list)); + return false; + } + if (response.hasFailures()) { + log.warn("【ES】updateBatchIds 失败,result: {}!", response.buildFailureMessage()); + return false; + } + return true; + } + + public void asyncUpdateBatchIds(String index, String type, Collection list, + ActionListener listener) { + if (CollectionUtil.isEmpty(list)) { + return; + } + BulkRequest bulkRequest = toBulkUpdateRequest(index, type, list); + client.bulkAsync(bulkRequest, RequestOptions.DEFAULT, listener); + } + + private BulkRequest toBulkIndexRequest(String index, String type, Collection list) { + BulkRequest bulkRequest = new BulkRequest(); + bulkRequest.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE); + for (T entity : list) { + if (entity == null) { + continue; + } + Map map = toMap(entity); + if (MapUtil.isEmpty(map)) { + continue; + } + IndexRequest request = new IndexRequest(index, type).source(map); + if (entity.getDocId() != null) { + request.id(entity.getDocId()); + } + bulkRequest.add(request); + } + return bulkRequest; + } + + private BulkRequest toBulkUpdateRequest(String index, String type, Collection list) { + BulkRequest bulkRequest = new BulkRequest(); + bulkRequest.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE); + for (T entity : list) { + if (entity == null || entity.getDocId() == null) { + continue; + } + Map map = toMap(entity); + if (MapUtil.isEmpty(map)) { + continue; + } + UpdateRequest request = new UpdateRequest(index, type, entity.getDocId()).doc(map); + bulkRequest.add(request); + } + return bulkRequest; + } + + public boolean deleteById(String index, String type, String id) throws IOException { + return deleteBatchIds(index, type, Collections.singleton(id)); + } + + public boolean deleteBatchIds(String index, String type, Collection ids) throws IOException { + + if (CollectionUtil.isEmpty(ids)) { + return true; + } + + BulkRequest bulkRequest = new BulkRequest(); + bulkRequest.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE); + ids.stream().filter(Objects::nonNull).forEach(id -> { + DeleteRequest deleteRequest = new DeleteRequest(index, type, id); + bulkRequest.add(deleteRequest); + }); + + BulkResponse response = client.bulk(bulkRequest, RequestOptions.DEFAULT); + if (response == null) { + log.warn("【ES】deleteBatchIds 失败,result 为空!ids: {}", JsonUtil.toString(ids)); + return false; + } + if (response.hasFailures()) { + log.warn("【ES】deleteBatchIds 失败,result: {}!", response.buildFailureMessage()); + return false; + } + return true; + } + + public void asyncDeleteBatchIds(String index, String type, Collection ids, + ActionListener listener) { + + if (CollectionUtil.isEmpty(ids)) { + return; + } + + BulkRequest bulkRequest = new BulkRequest(); + ids.forEach(id -> { + DeleteRequest deleteRequest = new DeleteRequest(index, type, id); + bulkRequest.add(deleteRequest); + }); + + client.bulkAsync(bulkRequest, RequestOptions.DEFAULT, listener); + } + + public GetResponse getById(String index, String type, String id) throws IOException { + return getById(index, type, id, null); + } + + public GetResponse getById(String index, String type, String id, Long version) throws IOException { + GetRequest getRequest = new GetRequest(index, type, id); + if (version != null) { + getRequest.version(version); + } + return client.get(getRequest, RequestOptions.DEFAULT); + } + + public T pojoById(String index, String type, String id, Class clazz) throws IOException { + return pojoById(index, type, id, null, clazz); + } + + public T pojoById(String index, String type, String id, Long version, Class clazz) throws IOException { + GetResponse response = getById(index, type, id, version); + if (response == null) { + return null; + } + return toPojo(response, clazz); + } + + public List pojoListByIds(String index, String type, Collection ids, Class clazz) + throws IOException { + + if (CollectionUtil.isEmpty(ids)) { + return new ArrayList<>(0); + } + + MultiGetRequest request = new MultiGetRequest(); + for (String id : ids) { + request.add(new MultiGetRequest.Item(index, type, id)); + } + + MultiGetResponse multiGetResponse = client.mget(request, RequestOptions.DEFAULT); + if (null == multiGetResponse + || multiGetResponse.getResponses() == null + || multiGetResponse.getResponses().length <= 0) { + return new ArrayList<>(0); + } + + List list = new ArrayList<>(); + for (MultiGetItemResponse itemResponse : multiGetResponse.getResponses()) { + if (itemResponse.isFailed()) { + log.error("通过id获取文档失败", itemResponse.getFailure().getFailure()); + } else { + T entity = toPojo(itemResponse.getResponse(), clazz); + if (entity != null) { + list.add(entity); + } + } + } + return list; + } + + public long count(String index, String type, SearchSourceBuilder builder) throws IOException { + SearchResponse response = query(index, type, builder); + if (response == null || response.status() != RestStatus.OK) { + return 0L; + } + SearchHits searchHits = response.getHits(); + return searchHits.getTotalHits(); + } + + public long count(String index, String type, QueryBuilder queryBuilder) throws IOException { + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); + searchSourceBuilder.query(queryBuilder); + return count(index, type, searchSourceBuilder); + } + + public SearchResponse query(String index, String type, SearchSourceBuilder builder) throws IOException { + SearchRequest request = new SearchRequest(index).types(type); + request.source(builder); + return client.search(request, RequestOptions.DEFAULT); + } + + public SearchResponse query(SearchRequest request) throws IOException { + return client.search(request, RequestOptions.DEFAULT); + } + + /** + * from+size 分页 + *

+ * 注:在深分页的场景下,效率很低(一般超过 1万条数据就不适用了) + */ + public PageData pojoPage(String index, String type, SearchSourceBuilder builder, Class clazz) + throws IOException { + SearchResponse response = query(index, type, builder); + if (response == null || response.status() != RestStatus.OK) { + return null; + } + + List content = toPojoList(response, clazz); + SearchHits searchHits = response.getHits(); + int from = builder.from(); + int size = builder.size(); + int page = from / size + (from % size == 0 ? 0 : 1) + 1; + return new PageData<>(page, size, searchHits.getTotalHits(), content); + } + + /** + * from+size 分页 + *

+ * 注:在深分页的场景下,效率很低(一般超过 1万条数据就不适用了) + */ + public PageData pojoPage(String index, String type, int from, int size, QueryBuilder queryBuilder, + Class clazz) throws IOException { + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); + searchSourceBuilder.query(queryBuilder); + searchSourceBuilder.from(from); + searchSourceBuilder.size(size); + return pojoPage(index, type, searchSourceBuilder, clazz); + } + + /** + * search after 分页 + */ + public ScrollData pojoPageByScrollId(String index, String type, String scrollId, + int size, + QueryBuilder queryBuilder, Class clazz) throws IOException { + + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); + searchSourceBuilder.size(size); + searchSourceBuilder.sort(BaseEsEntity.DOC_ID, SortOrder.ASC); + if (StrUtil.isNotBlank(scrollId)) { + BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery(); + boolQueryBuilder.must(queryBuilder).must(QueryBuilders.rangeQuery(BaseEsEntity.DOC_ID).gt(scrollId)); + searchSourceBuilder.query(boolQueryBuilder); + } else { + searchSourceBuilder.query(queryBuilder); + } + + SearchResponse response = query(index, type, searchSourceBuilder); + if (response == null || response.status() != RestStatus.OK) { + return null; + } + List content = toPojoList(response, clazz); + ScrollData scrollData = new ScrollData<>(); + scrollData.setSize(size); + scrollData.setTotal(response.getHits().getTotalHits()); + scrollData.setContent(content); + if (CollectionUtil.isNotEmpty(content)) { + T lastEntity = content.get(content.size() - 1); + scrollData.setScrollId(lastEntity.getDocId()); + } + return scrollData; + } + + /** + * 首次滚动查询批量查询,但是不适用与搜索,仅用于批查询 + **/ + public ScrollData pojoScrollBegin(String index, String type, SearchSourceBuilder searchBuilder, + Class clazz) throws IOException { + + int scrollTime = 10; + final Scroll scroll = new Scroll(TimeValue.timeValueSeconds(scrollTime)); + SearchRequest request = new SearchRequest(index); + request.types(type); + request.source(searchBuilder); + request.scroll(scroll); + SearchResponse response = client.search(request, RequestOptions.DEFAULT); + if (response == null || response.status() != RestStatus.OK) { + return null; + } + List content = toPojoList(response, clazz); + ScrollData scrollData = new ScrollData<>(); + scrollData.setSize(searchBuilder.size()); + scrollData.setTotal(response.getHits().getTotalHits()); + scrollData.setScrollId(response.getScrollId()); + scrollData.setContent(content); + return scrollData; + } + + /** + * 知道ScrollId之后,后续根据scrollId批量查询 + **/ + public ScrollData pojoScroll(String scrollId, SearchSourceBuilder searchBuilder, Class clazz) + throws IOException { + + int scrollTime = 10; + final Scroll scroll = new Scroll(TimeValue.timeValueSeconds(scrollTime)); + SearchScrollRequest request = new SearchScrollRequest(scrollId); + request.scroll(scroll); + SearchResponse response = client.scroll(request, RequestOptions.DEFAULT); + if (response == null || response.status() != RestStatus.OK) { + return null; + } + List content = toPojoList(response, clazz); + ScrollData scrollData = new ScrollData<>(); + scrollData.setSize(searchBuilder.size()); + scrollData.setTotal(response.getHits().getTotalHits()); + scrollData.setScrollId(response.getScrollId()); + scrollData.setContent(content); + return scrollData; + } + + public boolean pojoScrollEnd(String scrollId) throws IOException { + ClearScrollRequest request = new ClearScrollRequest(); + request.addScrollId(scrollId); + ClearScrollResponse response = client.clearScroll(request, RequestOptions.DEFAULT); + if (response != null) { + return response.isSucceeded(); + } + return false; + } + + public T toPojo(GetResponse response, Class clazz) { + if (null == response || StrUtil.isBlank(response.getSourceAsString())) { + return null; + } else { + return JsonUtil.toBean(response.getSourceAsString(), clazz); + } + } + + public List toPojoList(SearchResponse response, Class clazz) { + if (response == null || response.status() != RestStatus.OK) { + return new ArrayList<>(0); + } + if (ArrayUtil.isEmpty(response.getHits().getHits())) { + return new ArrayList<>(0); + } + return Stream.of(response.getHits().getHits()) + .map(hit -> JsonUtil.toBean(hit.getSourceAsString(), clazz)) + .collect(Collectors.toList()); + } + + public Map toMap(T entity) { + return JsonUtil.toMap(JsonUtil.toString(entity)); + } + + @Override + public synchronized void close() { + if (null == client) { + return; + } + IoUtil.close(client); + } + +} diff --git a/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/config/ElasticsearchConfig.java b/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/config/ElasticsearchConfig.java new file mode 100644 index 00000000..791bbf1c --- /dev/null +++ b/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/config/ElasticsearchConfig.java @@ -0,0 +1,44 @@ +package io.github.dunwu.javadb.elasticsearch.config; + +import cn.hutool.core.util.StrUtil; +import io.github.dunwu.javadb.elasticsearch.ElasticsearchFactory; +import io.github.dunwu.javadb.elasticsearch.ElasticsearchTemplate; +import org.elasticsearch.client.RestHighLevelClient; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +import java.util.List; + +/** + * ES 配置 + * + * @author Zhang Peng + * @date 2024-02-07 + */ +@Configuration +@ComponentScan(value = "io.github.dunwu.javadb.elasticsearch.mapper") +public class ElasticsearchConfig { + + @Value("${es.hosts:#{null}}") + private String hostsConfig; + + @Bean("restHighLevelClient") + @ConditionalOnMissingBean + public RestHighLevelClient restHighLevelClient() { + if (hostsConfig == null) { + return ElasticsearchFactory.newRestHighLevelClient(); + } else { + List hosts = StrUtil.split(hostsConfig, ","); + return ElasticsearchFactory.newRestHighLevelClient(hosts); + } + } + + @Bean("elasticsearchTemplate") + public ElasticsearchTemplate elasticsearchTemplate(RestHighLevelClient restHighLevelClient) { + return ElasticsearchFactory.newElasticsearchTemplate(restHighLevelClient); + } + +} diff --git a/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/config/EnableElasticsearch.java b/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/config/EnableElasticsearch.java new file mode 100644 index 00000000..c2c24479 --- /dev/null +++ b/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/config/EnableElasticsearch.java @@ -0,0 +1,26 @@ +package io.github.dunwu.javadb.elasticsearch.config; + +import org.springframework.context.annotation.EnableAspectJAutoProxy; +import org.springframework.context.annotation.Import; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 启动 Elasticsearch 配置注解 + * + * @author Zhang Peng + * @date 2023-06-30 + */ +@Target({ ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +@EnableAspectJAutoProxy( + proxyTargetClass = false +) +@Import({ ElasticsearchConfig.class }) +@Documented +public @interface EnableElasticsearch { +} diff --git a/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/constant/CodeMsg.java b/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/constant/CodeMsg.java new file mode 100644 index 00000000..96e46f6c --- /dev/null +++ b/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/constant/CodeMsg.java @@ -0,0 +1,15 @@ +package io.github.dunwu.javadb.elasticsearch.constant; + +/** + * 请求 / 应答状态接口 + * + * @author Zhang Peng + * @since 2019-06-06 + */ +public interface CodeMsg { + + int getCode(); + + String getMsg(); + +} diff --git a/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/constant/ResultCode.java b/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/constant/ResultCode.java new file mode 100644 index 00000000..d4822fb8 --- /dev/null +++ b/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/constant/ResultCode.java @@ -0,0 +1,97 @@ +package io.github.dunwu.javadb.elasticsearch.constant; + +import cn.hutool.core.util.StrUtil; + +import java.util.stream.Stream; + +/** + * 系统级错误码 + * + * @author Zhang Peng + * @see HTTP 状态码 + * @see 腾讯开放平台错误码 + * @see 新浪开放平台错误码 + * @see 支付宝开放平台API + * @see 微信开放平台错误码 + * @since 2019-04-11 + */ +public enum ResultCode implements CodeMsg { + + OK(0, "成功"), + + PART_OK(1, "部分成功"), + + FAIL(-1, "失败"), + + // ----------------------------------------------------- + // 系统级错误码 + // ----------------------------------------------------- + + ERROR(1000, "服务器错误"), + + PARAM_ERROR(1001, "参数错误"), + + TASK_ERROR(1001, "调度任务错误"), + + CONFIG_ERROR(1003, "配置错误"), + + REQUEST_ERROR(1004, "请求错误"), + + IO_ERROR(1005, "IO 错误"), + + // ----------------------------------------------------- + // 2000 ~ 2999 数据库错误 + // ----------------------------------------------------- + + DATA_ERROR(2000, "数据库错误"), + + // ----------------------------------------------------- + // 3000 ~ 3999 三方错误 + // ----------------------------------------------------- + + THIRD_PART_ERROR(3000, "三方错误"), + + // ----------------------------------------------------- + // 3000 ~ 3999 认证错误 + // ----------------------------------------------------- + + AUTH_ERROR(4000, "认证错误"); + + private final int code; + + private final String msg; + + ResultCode(int code, String msg) { + this.code = code; + this.msg = msg; + } + + @Override + public int getCode() { + return code; + } + + @Override + public String getMsg() { + return msg; + } + + public static String getNameByCode(int code) { + return Stream.of(ResultCode.values()).filter(item -> item.getCode() == code).findFirst() + .map(ResultCode::getMsg).orElse(null); + } + + public static ResultCode getEnumByCode(int code) { + return Stream.of(ResultCode.values()).filter(item -> item.getCode() == code).findFirst().orElse(null); + } + + public static String getTypeInfo() { + StringBuilder sb = new StringBuilder(); + ResultCode[] types = ResultCode.values(); + for (ResultCode type : types) { + sb.append(StrUtil.format("{}:{}, ", type.getCode(), type.getMsg())); + } + return sb.toString(); + } +} diff --git a/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/entity/BaseEsEntity.java b/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/entity/BaseEsEntity.java new file mode 100644 index 00000000..32206066 --- /dev/null +++ b/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/entity/BaseEsEntity.java @@ -0,0 +1,37 @@ +package io.github.dunwu.javadb.elasticsearch.entity; + +import lombok.Data; +import lombok.ToString; + +import java.io.Serializable; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * ES 实体接口 + * + * @author Zhang Peng + * @since 2023-06-28 + */ +@Data +@ToString +public abstract class BaseEsEntity implements Serializable { + + public static final String DOC_ID = "docId"; + + /** + * 获取版本 + */ + protected Long version; + + protected Float hitScore; + + public abstract String getDocId(); + + public static Map getPropertiesMap() { + Map map = new LinkedHashMap<>(1); + map.put(BaseEsEntity.DOC_ID, "keyword"); + return map; + } + +} diff --git a/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/entity/User.java b/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/entity/User.java new file mode 100644 index 00000000..b21b229c --- /dev/null +++ b/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/entity/User.java @@ -0,0 +1,44 @@ +package io.github.dunwu.javadb.elasticsearch.entity; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.io.Serializable; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * 短剧、长视频消费数据 ES 实体 + * + * @author Zhang Peng + * @date 2024-04-02 + */ +@Builder +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +public class User extends BaseEsEntity implements Serializable { + + private String id; + private String name; + private Integer age; + + @Override + public String getDocId() { + return id; + } + + public static Map getPropertiesMap() { + Map map = new LinkedHashMap<>(); + map.put(BaseEsEntity.DOC_ID, "keyword"); + map.put("id", "long"); + map.put("name", "keyword"); + map.put("age", "integer"); + return map; + } + +} diff --git a/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/entity/common/PageData.java b/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/entity/common/PageData.java new file mode 100644 index 00000000..e436f8b6 --- /dev/null +++ b/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/entity/common/PageData.java @@ -0,0 +1,37 @@ +package io.github.dunwu.javadb.elasticsearch.entity.common; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * 分页实体 + * + * @author Zhang Peng + * @date 2023-06-28 + */ +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +public class PageData implements Serializable { + + private int page; + private int size; + private long total; + private List content = new ArrayList<>(); + + public PageData(int page, int size, long total) { + this.total = total; + this.page = page; + this.size = size; + } + + private static final long serialVersionUID = 1L; + +} diff --git a/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/entity/common/ScrollData.java b/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/entity/common/ScrollData.java new file mode 100644 index 00000000..4f90cb85 --- /dev/null +++ b/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/entity/common/ScrollData.java @@ -0,0 +1,30 @@ +package io.github.dunwu.javadb.elasticsearch.entity.common; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.io.Serializable; +import java.util.Collection; + +/** + * Hbase 滚动数据实体 + * + * @author Zhang Peng + * @date 2023-11-16 + */ +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +public class ScrollData implements Serializable { + + private String scrollId; + private int size = 10; + private long total = 0L; + private Collection content; + + private static final long serialVersionUID = 1L; + +} diff --git a/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/exception/CodeMsgException.java b/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/exception/CodeMsgException.java new file mode 100644 index 00000000..98ab1995 --- /dev/null +++ b/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/exception/CodeMsgException.java @@ -0,0 +1,128 @@ +package io.github.dunwu.javadb.elasticsearch.exception; + +import cn.hutool.core.util.StrUtil; +import io.github.dunwu.javadb.elasticsearch.constant.CodeMsg; +import io.github.dunwu.javadb.elasticsearch.constant.ResultCode; + +/** + * 基础异常 + * + * @author Zhang Peng + * @since 2021-09-25 + */ +public class CodeMsgException extends RuntimeException implements CodeMsg { + + private static final long serialVersionUID = 6146660782281445735L; + + /** + * 状态码 + */ + protected int code; + + /** + * 响应信息 + */ + protected String msg; + + /** + * 提示信息 + */ + protected String toast; + + public CodeMsgException() { + this(ResultCode.FAIL); + } + + public CodeMsgException(CodeMsg codeMsg) { + this(codeMsg.getCode(), codeMsg.getMsg()); + } + + public CodeMsgException(CodeMsg codeMsg, String msg) { + this(codeMsg.getCode(), msg, null); + } + + public CodeMsgException(CodeMsg codeMsg, String msg, String toast) { + this(codeMsg.getCode(), msg, toast); + } + + public CodeMsgException(String msg) { + this(ResultCode.FAIL, msg); + } + + public CodeMsgException(int code, String msg) { + this(code, msg, msg); + } + + public CodeMsgException(int code, String msg, String toast) { + super(msg); + setCode(code); + setMsg(msg); + setToast(toast); + } + + public CodeMsgException(Throwable cause) { + this(cause, ResultCode.FAIL); + } + + public CodeMsgException(Throwable cause, String msg) { + this(cause, ResultCode.FAIL, msg); + } + + public CodeMsgException(Throwable cause, CodeMsg codeMsg) { + this(cause, codeMsg.getCode(), codeMsg.getMsg()); + } + + public CodeMsgException(Throwable cause, CodeMsg codeMsg, String msg) { + this(cause, codeMsg.getCode(), msg, null); + } + + public CodeMsgException(Throwable cause, CodeMsg codeMsg, String msg, String toast) { + this(cause, codeMsg.getCode(), msg, toast); + } + + public CodeMsgException(Throwable cause, int code, String msg) { + this(cause, code, msg, null); + } + + public CodeMsgException(Throwable cause, int code, String msg, String toast) { + super(msg, cause); + setCode(code); + setMsg(msg); + setToast(toast); + } + + @Override + public String getMessage() { + if (StrUtil.isNotBlank(msg)) { + return StrUtil.format("[{}]{}", code, msg); + } + return super.getMessage(); + } + + @Override + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + @Override + public String getMsg() { + return msg; + } + + public void setMsg(String msg) { + this.msg = msg; + } + + public String getToast() { + return toast; + } + + public void setToast(String toast) { + this.toast = toast; + } + +} diff --git a/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/exception/DefaultException.java b/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/exception/DefaultException.java new file mode 100644 index 00000000..14908e39 --- /dev/null +++ b/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/exception/DefaultException.java @@ -0,0 +1,72 @@ +package io.github.dunwu.javadb.elasticsearch.exception; + +import io.github.dunwu.javadb.elasticsearch.constant.CodeMsg; +import io.github.dunwu.javadb.elasticsearch.constant.ResultCode; + +/** + * 默认异常 + * + * @author Zhang Peng + * @since 2021-12-30 + */ +public class DefaultException extends CodeMsgException { + + private static final long serialVersionUID = -7027578114976830416L; + + public DefaultException() { + this(ResultCode.FAIL); + } + + public DefaultException(CodeMsg codeMsg) { + this(codeMsg.getCode(), codeMsg.getMsg()); + } + + public DefaultException(CodeMsg codeMsg, String msg) { + this(codeMsg.getCode(), msg, null); + } + + public DefaultException(CodeMsg codeMsg, String msg, String toast) { + this(codeMsg.getCode(), msg, toast); + } + + public DefaultException(String msg) { + this(ResultCode.FAIL, msg); + } + + public DefaultException(int code, String msg) { + this(code, msg, msg); + } + + public DefaultException(int code, String msg, String toast) { + super(code, msg, toast); + } + + public DefaultException(Throwable cause) { + this(cause, ResultCode.FAIL); + } + + public DefaultException(Throwable cause, String msg) { + this(cause, ResultCode.FAIL, msg); + } + + public DefaultException(Throwable cause, CodeMsg codeMsg) { + this(cause, codeMsg.getCode(), codeMsg.getMsg()); + } + + public DefaultException(Throwable cause, CodeMsg codeMsg, String msg) { + this(cause, codeMsg.getCode(), msg, null); + } + + public DefaultException(Throwable cause, CodeMsg codeMsg, String msg, String toast) { + this(cause, codeMsg.getCode(), msg, toast); + } + + public DefaultException(Throwable cause, int code, String msg) { + this(cause, code, msg, null); + } + + public DefaultException(Throwable cause, int code, String msg, String toast) { + super(cause, code, msg, toast); + } + +} diff --git a/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/mapper/BaseDynamicEsMapper.java b/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/mapper/BaseDynamicEsMapper.java new file mode 100644 index 00000000..c75c6cc6 --- /dev/null +++ b/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/mapper/BaseDynamicEsMapper.java @@ -0,0 +1,331 @@ +package io.github.dunwu.javadb.elasticsearch.mapper; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.DateTime; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONUtil; +import io.github.dunwu.javadb.elasticsearch.ElasticsearchTemplate; +import io.github.dunwu.javadb.elasticsearch.constant.ResultCode; +import io.github.dunwu.javadb.elasticsearch.entity.BaseEsEntity; +import io.github.dunwu.javadb.elasticsearch.entity.common.PageData; +import io.github.dunwu.javadb.elasticsearch.entity.common.ScrollData; +import io.github.dunwu.javadb.elasticsearch.exception.DefaultException; +import lombok.extern.slf4j.Slf4j; +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.bulk.BulkResponse; +import org.elasticsearch.action.get.GetResponse; +import org.elasticsearch.action.search.SearchResponse; +import org.elasticsearch.index.query.QueryBuilder; +import org.elasticsearch.search.builder.SearchSourceBuilder; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * 动态 ES Mapper 基础类(以时间为维度动态创建、删除 index),用于数据量特别大,需要按照日期分片的索引。 + *

+ * 注:使用此 Mapper 的索引、别名必须遵循命名格式:索引名 = 别名_yyyyMMdd + * + * @author Zhang Peng + * @date 2024-04-07 + */ +@Slf4j +public abstract class BaseDynamicEsMapper extends BaseEsMapper { + + public BaseDynamicEsMapper(ElasticsearchTemplate elasticsearchTemplate) { + super(elasticsearchTemplate); + } + + @Override + public boolean enableAutoCreateIndex() { + return true; + } + + // ==================================================================== + // 索引管理操作 + // ==================================================================== + + public String getIndex(String day) { + + String alias = getAlias(); + if (StrUtil.isBlank(day)) { + String msg = StrUtil.format("【ES】获取 {} 索引失败!day 不能为空!", alias); + throw new DefaultException(ResultCode.PARAM_ERROR, msg); + } + + DateTime date; + try { + date = DateUtil.parse(day, DatePattern.NORM_DATE_PATTERN); + } catch (Exception e) { + String msg = StrUtil.format("【ES】获取 {} 索引失败!day: {} 不符合日期格式 {}!", + alias, day, DatePattern.NORM_DATE_PATTERN); + throw new DefaultException(e, ResultCode.PARAM_ERROR, msg); + } + + String formatDate = DateUtil.format(date, DatePattern.PURE_DATE_FORMAT); + return alias + "_" + formatDate; + } + + public boolean isIndexExistsInDay(String day) { + if (StrUtil.isBlank(day)) { + return false; + } + String index = getIndex(day); + try { + return elasticsearchTemplate.isIndexExists(getIndex(day)); + } catch (Exception e) { + log.error("【ES】判断索引是否存在异常!index: {}", index, e); + return false; + } + } + + public String createIndexIfNotExistsInDay(String day) { + String index = getIndex(day); + String type = getType(); + String alias = getAlias(); + int shard = getShard(); + int replica = getReplica(); + return createIndex(index, type, alias, shard, replica); + } + + public void deleteIndexInDay(String day) { + String index = getIndex(day); + try { + log.info("【ES】删除索引成功!index: {}", index); + elasticsearchTemplate.deleteIndex(index); + } catch (Exception e) { + log.error("【ES】删除索引异常!index: {}", index, e); + } + } + + public void updateAliasInDay(String day) { + String index = getIndex(day); + String alias = getAlias(); + try { + log.info("【ES】更新别名成功!alias: {} -> index: {}", alias, index); + elasticsearchTemplate.updateAlias(index, alias); + } catch (IOException e) { + log.error("【ES】更新别名异常!alias: {} -> index: {}", alias, index, e); + } + } + + // ==================================================================== + // CRUD 操作 + // ==================================================================== + + public GetResponse getByIdInDay(String day, String id) { + String index = getIndex(day); + String type = getType(); + try { + return elasticsearchTemplate.getById(index, type, id, null); + } catch (IOException e) { + log.error("【ES】根据ID查询异常!index: {}, type: {}, id: {}", index, type, id, e); + return null; + } + } + + public T pojoByIdInDay(String day, String id) { + String index = getIndex(day); + String type = getType(); + try { + return elasticsearchTemplate.pojoById(index, type, id, null, getEntityClass()); + } catch (IOException e) { + log.error("【ES】根据ID查询POJO异常!index: {}, type: {}, id: {}", index, type, id, e); + return null; + } + } + + public List pojoListByIdsInDay(String day, Collection ids) { + String index = getIndex(day); + String type = getType(); + try { + return elasticsearchTemplate.pojoListByIds(index, type, ids, getEntityClass()); + } catch (IOException e) { + log.error("【ES】根据ID查询POJO列表异常!index: {}, type: {}, ids: {}", index, type, ids, e); + return new ArrayList<>(0); + } + } + + public long countInDay(String day, SearchSourceBuilder builder) { + String index = getIndex(day); + String type = getType(); + try { + return elasticsearchTemplate.count(index, type, builder); + } catch (IOException e) { + log.error("【ES】获取匹配记录数异常!index: {}, type: {}", index, type, e); + return 0L; + } + } + + public SearchResponse queryInDay(String day, SearchSourceBuilder builder) { + String index = getIndex(day); + String type = getType(); + try { + return elasticsearchTemplate.query(index, type, builder); + } catch (IOException e) { + log.error("【ES】条件查询异常!index: {}, type: {}", index, type, e); + return null; + } + } + + public PageData pojoPageInDay(String day, SearchSourceBuilder builder) { + String index = getIndex(day); + String type = getType(); + try { + return elasticsearchTemplate.pojoPage(index, type, builder, getEntityClass()); + } catch (IOException e) { + log.error("【ES】from + size 分页条件查询异常!index: {}, type: {}", index, type, e); + return null; + } + } + + public ScrollData pojoPageByLastIdInDay(String day, String scrollId, int size, QueryBuilder queryBuilder) { + String index = getIndex(day); + String type = getType(); + try { + return elasticsearchTemplate.pojoPageByScrollId(index, type, scrollId, size, queryBuilder, getEntityClass()); + } catch (IOException e) { + log.error("【ES】search after 分页条件查询异常!index: {}, type: {}", index, type, e); + return null; + } + } + + public ScrollData pojoScrollBeginInDay(String day, SearchSourceBuilder builder) { + String index = getIndex(day); + String type = getType(); + try { + return elasticsearchTemplate.pojoScrollBegin(index, type, builder, getEntityClass()); + } catch (IOException e) { + log.error("【ES】开启滚动分页条件查询异常!index: {}, type: {}", index, type, e); + return null; + } + } + + /** + * 根据日期动态选择索引并更新 + * + * @param day 日期,格式为:yyyy-MM-dd + * @param entity 待更新的数据 + * @return / + */ + public T saveInDay(String day, T entity) { + if (StrUtil.isBlank(day) || entity == null) { + return null; + } + String index = getIndex(day); + String type = getType(); + try { + checkIndex(day); + checkData(entity); + return elasticsearchTemplate.save(index, getType(), entity); + } catch (IOException e) { + log.error("【ES】添加数据异常!index: {}, type: {}, entity: {}", index, type, JSONUtil.toJsonStr(entity), e); + return null; + } + } + + /** + * 根据日期动态选择索引并批量更新 + * + * @param day 日期,格式为:yyyy-MM-dd + * @param list 待更新的数据 + * @return / + */ + public boolean saveBatchInDay(String day, Collection list) { + if (StrUtil.isBlank(day) || CollectionUtil.isEmpty(list)) { + return false; + } + String index = getIndex(day); + String type = getType(); + try { + checkIndex(day); + checkData(list); + return elasticsearchTemplate.saveBatch(index, type, list); + } catch (IOException e) { + log.error("【ES】批量添加数据异常!index: {}, type: {}, size: {}", index, type, list.size(), e); + return false; + } + } + + public void asyncSaveBatchInDay(String day, Collection list) { + asyncSaveBatchInDay(day, list, DEFAULT_BULK_LISTENER); + } + + public void asyncSaveBatchInDay(String day, Collection list, ActionListener listener) { + if (StrUtil.isBlank(day) || CollectionUtil.isEmpty(list)) { + return; + } + String index = getIndex(day); + String type = getType(); + try { + checkIndex(day); + checkData(list); + elasticsearchTemplate.asyncSaveBatch(index, type, list, listener); + } catch (Exception e) { + log.error("【ES】异步批量添加数据异常!index: {}, type: {}, size: {}", index, type, list.size(), e); + } + } + + public void asyncUpdateBatchIdsInDay(String day, Collection list) { + asyncUpdateBatchIdsInDay(day, list, DEFAULT_BULK_LISTENER); + } + + public void asyncUpdateBatchIdsInDay(String day, Collection list, ActionListener listener) { + if (StrUtil.isBlank(day) || CollectionUtil.isEmpty(list)) { + return; + } + String index = getIndex(day); + String type = getType(); + try { + checkData(list); + elasticsearchTemplate.asyncUpdateBatchIds(index, type, list, listener); + } catch (Exception e) { + log.error("【ES】异步批量更新数据异常!index: {}, type: {}, size: {}", index, type, list.size(), e); + } + } + + public boolean deleteByIdInDay(String day, String id) { + if (StrUtil.isBlank(day) || StrUtil.isBlank(id)) { + return false; + } + String index = getIndex(day); + String type = getType(); + try { + return elasticsearchTemplate.deleteById(index, type, id); + } catch (IOException e) { + log.error("【ES】根据ID删除数据异常!index: {}, type: {}, id: {}", index, type, id, e); + return false; + } + } + + public boolean deleteBatchIdsInDay(String day, Collection ids) { + if (StrUtil.isBlank(day) || CollectionUtil.isEmpty(ids)) { + return false; + } + String index = getIndex(day); + String type = getType(); + try { + return elasticsearchTemplate.deleteBatchIds(index, type, ids); + } catch (IOException e) { + log.error("【ES】根据ID批量删除数据异常!index: {}, type: {}, ids: {}", index, type, ids, e); + return false; + } + } + + protected String checkIndex(String day) { + if (!enableAutoCreateIndex()) { + return getIndex(day); + } + String index = createIndexIfNotExistsInDay(day); + if (StrUtil.isBlank(index)) { + String msg = StrUtil.format("【ES】索引 {}_{} 找不到且创建失败!", getAlias(), day); + throw new DefaultException(ResultCode.ERROR, msg); + } + return index; + } + +} diff --git a/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/mapper/BaseEsMapper.java b/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/mapper/BaseEsMapper.java new file mode 100644 index 00000000..b125bea9 --- /dev/null +++ b/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/mapper/BaseEsMapper.java @@ -0,0 +1,502 @@ +package io.github.dunwu.javadb.elasticsearch.mapper; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.ReflectUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONUtil; +import io.github.dunwu.javadb.elasticsearch.ElasticsearchTemplate; +import io.github.dunwu.javadb.elasticsearch.constant.ResultCode; +import io.github.dunwu.javadb.elasticsearch.entity.BaseEsEntity; +import io.github.dunwu.javadb.elasticsearch.entity.common.PageData; +import io.github.dunwu.javadb.elasticsearch.entity.common.ScrollData; +import io.github.dunwu.javadb.elasticsearch.exception.DefaultException; +import lombok.extern.slf4j.Slf4j; +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.bulk.BulkProcessor; +import org.elasticsearch.action.bulk.BulkResponse; +import org.elasticsearch.action.get.GetResponse; +import org.elasticsearch.action.search.SearchResponse; +import org.elasticsearch.client.RestHighLevelClient; +import org.elasticsearch.index.query.QueryBuilder; +import org.elasticsearch.search.builder.SearchSourceBuilder; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * ES Mapper 基础类 + * + * @author Zhang Peng + * @date 2023-06-27 + */ +@Slf4j +public abstract class BaseEsMapper implements EsMapper { + + protected BulkProcessor bulkProcessor; + + protected final ElasticsearchTemplate elasticsearchTemplate; + + public BaseEsMapper(ElasticsearchTemplate elasticsearchTemplate) { + this.elasticsearchTemplate = elasticsearchTemplate; + } + + public int getShard() { + return 5; + } + + public int getReplica() { + return 1; + } + + @Override + public RestHighLevelClient getClient() { + if (elasticsearchTemplate == null) { + return null; + } + return elasticsearchTemplate.getClient(); + } + + @Override + public synchronized BulkProcessor getBulkProcessor() { + if (bulkProcessor == null) { + bulkProcessor = elasticsearchTemplate.newAsyncBulkProcessor(); + } + return bulkProcessor; + } + + @SuppressWarnings("unchecked") + public Map getPropertiesMap() { + Class clazz = getEntityClass(); + Method method; + try { + method = clazz.getMethod("getPropertiesMap"); + } catch (NoSuchMethodException e) { + log.error("【ES】{} 中不存在 getPropertiesMap 方法!", clazz.getCanonicalName()); + return new HashMap<>(0); + } + + Object result = ReflectUtil.invokeStatic(method); + if (result == null) { + return new HashMap<>(0); + } + return (Map) result; + } + + // ==================================================================== + // 索引管理操作 + // ==================================================================== + + @Override + public boolean isIndexExists() { + String index = getIndex(); + try { + return elasticsearchTemplate.isIndexExists(index); + } catch (Exception e) { + log.error("【ES】判断索引是否存在异常!index: {}", index, e); + return false; + } + } + + @Override + public String createIndexIfNotExists() { + String index = getIndex(); + String type = getType(); + String alias = getAlias(); + int shard = getShard(); + int replica = getReplica(); + return createIndex(index, type, alias, shard, replica); + } + + protected String createIndex(String index, String type, String alias, int shard, int replica) { + try { + if (elasticsearchTemplate.isIndexExists(index)) { + return index; + } + elasticsearchTemplate.createIndex(index, type, alias, shard, replica); + log.info("【ES】创建索引成功!index: {}, type: {}, alias: {}, shard: {}, replica: {}", + index, type, alias, shard, replica); + Map propertiesMap = getPropertiesMap(); + if (MapUtil.isNotEmpty(propertiesMap)) { + elasticsearchTemplate.setMapping(index, type, propertiesMap); + log.error("【ES】设置索引 mapping 成功!index: {}, type: {}, propertiesMap: {}", + index, type, JSONUtil.toJsonStr(propertiesMap)); + } + return index; + } catch (Exception e) { + log.error("【ES】创建索引异常!index: {}, type: {}, alias: {}, shard: {}, replica: {}", + index, type, alias, shard, replica, e); + return null; + } + } + + @Override + public void deleteIndex() { + String index = getIndex(); + try { + log.info("【ES】删除索引成功!index: {}", index); + elasticsearchTemplate.deleteIndex(index); + } catch (Exception e) { + log.error("【ES】删除索引异常!index: {}", index, e); + } + } + + @Override + public void updateAlias() { + String index = getIndex(); + String alias = getAlias(); + try { + log.info("【ES】更新别名成功!alias: {} -> index: {}", alias, index); + elasticsearchTemplate.updateAlias(index, alias); + } catch (Exception e) { + log.error("【ES】更新别名异常!alias: {} -> index: {}", alias, index, e); + } + } + + @Override + public Set getIndexSet() { + String alias = getAlias(); + try { + return elasticsearchTemplate.getIndexSet(alias); + } catch (Exception e) { + log.error("【ES】获取别名的所有索引异常!alias: {}", alias, e); + return new HashSet<>(0); + } + } + + // ==================================================================== + // CRUD 操作 + // ==================================================================== + + @Override + public GetResponse getById(String id) { + return getById(id, null); + } + + @Override + public GetResponse getById(String id, Long version) { + String index = getIndex(); + String type = getType(); + try { + return elasticsearchTemplate.getById(index, type, id, version); + } catch (Exception e) { + log.error("【ES】根据ID查询异常!index: {}, type: {}, id: {}, version: {}", index, type, id, version, e); + return null; + } + } + + @Override + public T pojoById(String id) { + return pojoById(id, null); + } + + @Override + public T pojoById(String id, Long version) { + String index = getIndex(); + String type = getType(); + try { + return elasticsearchTemplate.pojoById(index, type, id, version, getEntityClass()); + } catch (Exception e) { + log.error("【ES】根据ID查询POJO异常!index: {}, type: {}, id: {}, version: {}", index, type, id, version, e); + return null; + } + } + + @Override + public List pojoListByIds(Collection ids) { + String index = getIndex(); + String type = getType(); + try { + return elasticsearchTemplate.pojoListByIds(index, type, ids, getEntityClass()); + } catch (Exception e) { + log.error("【ES】根据ID查询POJO列表异常!index: {}, type: {}, ids: {}", index, type, ids, e); + return new ArrayList<>(0); + } + } + + @Override + public long count(SearchSourceBuilder builder) { + String index = getIndex(); + String type = getType(); + try { + return elasticsearchTemplate.count(index, type, builder); + } catch (Exception e) { + log.error("【ES】获取匹配记录数异常!index: {}, type: {}", index, type, e); + return 0L; + } + } + + @Override + public SearchResponse query(SearchSourceBuilder builder) { + String index = getIndex(); + String type = getType(); + try { + return elasticsearchTemplate.query(index, type, builder); + } catch (Exception e) { + log.error("【ES】条件查询异常!index: {}, type: {}", index, type, e); + return null; + } + } + + @Override + public PageData pojoPage(SearchSourceBuilder builder) { + String index = getIndex(); + String type = getType(); + try { + return elasticsearchTemplate.pojoPage(index, type, builder, getEntityClass()); + } catch (Exception e) { + log.error("【ES】from + size 分页条件查询异常!index: {}, type: {}", index, type, e); + return null; + } + } + + @Override + public ScrollData pojoPageByLastId(String scrollId, int size, QueryBuilder queryBuilder) { + String index = getIndex(); + String type = getType(); + try { + return elasticsearchTemplate.pojoPageByScrollId(index, type, scrollId, size, queryBuilder, + getEntityClass()); + } catch (Exception e) { + log.error("【ES】search after 分页条件查询异常!index: {}, type: {}", index, type, e); + return null; + } + } + + @Override + public ScrollData pojoScrollBegin(SearchSourceBuilder builder) { + String index = getIndex(); + String type = getType(); + try { + return elasticsearchTemplate.pojoScrollBegin(index, type, builder, getEntityClass()); + } catch (Exception e) { + log.error("【ES】开启滚动分页条件查询异常!index: {}, type: {}", index, type, e); + return null; + } + } + + @Override + public ScrollData pojoScroll(String scrollId, SearchSourceBuilder builder) { + try { + return elasticsearchTemplate.pojoScroll(scrollId, builder, getEntityClass()); + } catch (Exception e) { + log.error("【ES】滚动分页条件查询异常!scrollId: {}", scrollId, e); + return null; + } + } + + @Override + public boolean pojoScrollEnd(String scrollId) { + try { + return elasticsearchTemplate.pojoScrollEnd(scrollId); + } catch (Exception e) { + log.error("【ES】关闭滚动分页条件查询异常!scrollId: {}", scrollId, e); + return false; + } + } + + @Override + public T save(T entity) { + if (entity == null) { + return null; + } + String index = getIndex(); + String type = getType(); + try { + checkIndex(); + checkData(entity); + return elasticsearchTemplate.save(index, type, entity); + } catch (Exception e) { + log.error("【ES】添加数据异常!index: {}, type: {}, entity: {}", index, type, JSONUtil.toJsonStr(entity), e); + return null; + } + } + + @Override + public boolean saveBatch(Collection list) { + if (CollectionUtil.isEmpty(list)) { + return false; + } + String index = getIndex(); + String type = getType(); + try { + checkIndex(); + checkData(list); + return elasticsearchTemplate.saveBatch(index, type, list); + } catch (Exception e) { + log.error("【ES】批量添加数据异常!index: {}, type: {}, size: {}", index, type, list.size(), e); + return false; + } + } + + @Override + public void asyncSaveBatch(Collection list) { + asyncSaveBatch(list, DEFAULT_BULK_LISTENER); + } + + @Override + public void asyncSaveBatch(Collection list, ActionListener listener) { + if (CollectionUtil.isEmpty(list)) { + return; + } + String index = getIndex(); + String type = getType(); + try { + checkIndex(); + checkData(list); + elasticsearchTemplate.asyncSaveBatch(index, getType(), list, listener); + } catch (Exception e) { + log.error("【ES】异步批量添加数据异常!index: {}, type: {}, size: {}", index, type, list.size(), e); + } + } + + @Override + public T updateById(T entity) { + if (entity == null) { + return null; + } + String index = getIndex(); + String type = getType(); + try { + checkData(entity); + return elasticsearchTemplate.updateById(index, type, entity); + } catch (Exception e) { + log.error("【ES】更新数据异常!index: {}, type: {}", index, type, e); + return null; + } + } + + @Override + public boolean updateBatchIds(Collection list) { + if (CollectionUtil.isEmpty(list)) { + return false; + } + String index = getIndex(); + String type = getType(); + try { + checkData(list); + return elasticsearchTemplate.updateBatchIds(index, type, list); + } catch (Exception e) { + log.error("【ES】批量更新数据异常!index: {}, type: {}, size: {}", index, type, list.size(), e); + return false; + } + } + + @Override + public void asyncUpdateBatchIds(Collection list) { + asyncUpdateBatchIds(list, DEFAULT_BULK_LISTENER); + } + + @Override + public void asyncUpdateBatchIds(Collection list, ActionListener listener) { + if (CollectionUtil.isEmpty(list)) { + return; + } + String index = getIndex(); + String type = getType(); + try { + checkData(list); + elasticsearchTemplate.asyncUpdateBatchIds(index, type, list, listener); + } catch (Exception e) { + log.error("【ES】异步批量更新数据异常!index: {}, type: {}, size: {}", index, type, list.size(), e); + } + } + + @Override + public boolean deleteById(String id) { + if (StrUtil.isBlank(id)) { + return false; + } + String index = getIndex(); + String type = getType(); + try { + return elasticsearchTemplate.deleteById(index, type, id); + } catch (Exception e) { + log.error("【ES】根据ID删除数据异常!index: {}, type: {}, id: {}", index, type, id, e); + return false; + } + } + + @Override + public boolean deleteBatchIds(Collection ids) { + if (CollectionUtil.isEmpty(ids)) { + return false; + } + String index = getIndex(); + String type = getType(); + try { + return elasticsearchTemplate.deleteBatchIds(index, type, ids); + } catch (Exception e) { + log.error("【ES】根据ID批量删除数据异常!index: {}, type: {}, ids: {}", index, type, ids, e); + return false; + } + } + + @Override + public void asyncDeleteBatchIds(Collection ids) { + asyncDeleteBatchIds(ids, DEFAULT_BULK_LISTENER); + } + + @Override + public void asyncDeleteBatchIds(Collection ids, ActionListener listener) { + if (CollectionUtil.isEmpty(ids)) { + return; + } + String index = getIndex(); + String type = getType(); + try { + elasticsearchTemplate.asyncDeleteBatchIds(index, type, ids, listener); + } catch (Exception e) { + log.error("【ES】异步根据ID批量删除数据异常!index: {}, type: {}, ids: {}", index, type, ids, e); + } + } + + protected String checkIndex() { + if (!enableAutoCreateIndex()) { + return getIndex(); + } + String index = createIndexIfNotExists(); + if (StrUtil.isBlank(index)) { + String msg = StrUtil.format("【ES】索引 {} 找不到且创建失败!", index); + throw new DefaultException(ResultCode.ERROR, msg); + } + return index; + } + + protected void checkData(Collection list) { + if (CollectionUtil.isEmpty(list)) { + String msg = StrUtil.format("【ES】写入 {} 失败!list 不能为空!", getIndex()); + throw new DefaultException(ResultCode.PARAM_ERROR, msg); + } + } + + protected void checkData(T entity) { + if (entity == null) { + String msg = StrUtil.format("【ES】写入 {} 失败!entity 不能为空!", getIndex()); + throw new DefaultException(ResultCode.PARAM_ERROR, msg); + } + } + + protected final ActionListener DEFAULT_BULK_LISTENER = new ActionListener() { + @Override + public void onResponse(BulkResponse response) { + if (response != null && !response.hasFailures()) { + log.info("【ES】异步批量写数据成功!index: {}, type: {}", getIndex(), getType()); + } else { + log.warn("【ES】异步批量写数据失败!index: {}, type: {}", getIndex(), getType()); + } + } + + @Override + public void onFailure(Exception e) { + log.error("【ES】异步批量写数据异常!index: {}, type: {}", getIndex(), getType()); + } + }; + +} diff --git a/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/mapper/EsMapper.java b/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/mapper/EsMapper.java new file mode 100644 index 00000000..5630d664 --- /dev/null +++ b/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/mapper/EsMapper.java @@ -0,0 +1,142 @@ +package io.github.dunwu.javadb.elasticsearch.mapper; + +import cn.hutool.core.collection.CollectionUtil; +import io.github.dunwu.javadb.elasticsearch.entity.BaseEsEntity; +import io.github.dunwu.javadb.elasticsearch.entity.common.PageData; +import io.github.dunwu.javadb.elasticsearch.entity.common.ScrollData; +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.bulk.BulkProcessor; +import org.elasticsearch.action.bulk.BulkResponse; +import org.elasticsearch.action.get.GetResponse; +import org.elasticsearch.action.search.SearchResponse; +import org.elasticsearch.client.RestHighLevelClient; +import org.elasticsearch.index.query.QueryBuilder; +import org.elasticsearch.search.builder.SearchSourceBuilder; + +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * ES Mapper + * + * @author Zhang Peng + * @date 2023-06-27 + */ +public interface EsMapper { + + /** + * 获取别名 + */ + String getAlias(); + + /** + * 获取索引名 + */ + String getIndex(); + + /** + * 获取索引类型 + */ + String getType(); + + /** + * 获取分片数 + */ + int getShard(); + + /** + * 获取副本数 + */ + int getReplica(); + + /** + * 获取实体类型 + */ + Class getEntityClass(); + + /** + * 如果开启,添加 ES 数据时,如果索引不存在,会自动创建索引 + */ + default boolean enableAutoCreateIndex() { + return false; + } + + RestHighLevelClient getClient(); + + BulkProcessor getBulkProcessor(); + + boolean isIndexExists(); + + String createIndexIfNotExists(); + + void deleteIndex(); + + void updateAlias(); + + Set getIndexSet(); + + GetResponse getById(String id); + + GetResponse getById(String id, Long version); + + T pojoById(String id); + + T pojoById(String id, Long version); + + List pojoListByIds(Collection ids); + + default Map pojoMapByIds(Collection ids) { + List list = pojoListByIds(ids); + if (CollectionUtil.isEmpty(list)) { + return new HashMap<>(0); + } + + Map map = new HashMap<>(list.size()); + for (T entity : list) { + map.put(entity.getDocId(), entity); + } + return map; + } + + long count(SearchSourceBuilder builder); + + SearchResponse query(SearchSourceBuilder builder); + + PageData pojoPage(SearchSourceBuilder builder); + + ScrollData pojoPageByLastId(String scrollId, int size, QueryBuilder queryBuilder); + + ScrollData pojoScrollBegin(SearchSourceBuilder builder); + + ScrollData pojoScroll(String scrollId, SearchSourceBuilder builder); + + boolean pojoScrollEnd(String scrollId); + + T save(T entity); + + boolean saveBatch(Collection list); + + void asyncSaveBatch(Collection list); + + void asyncSaveBatch(Collection list, ActionListener listener); + + T updateById(T entity); + + boolean updateBatchIds(Collection list); + + void asyncUpdateBatchIds(Collection list); + + void asyncUpdateBatchIds(Collection list, ActionListener listener); + + boolean deleteById(String id); + + boolean deleteBatchIds(Collection ids); + + void asyncDeleteBatchIds(Collection ids); + + void asyncDeleteBatchIds(Collection ids, ActionListener listener); + +} diff --git a/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/mapper/UserEsMapper.java b/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/mapper/UserEsMapper.java new file mode 100644 index 00000000..de6d1b7c --- /dev/null +++ b/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/mapper/UserEsMapper.java @@ -0,0 +1,47 @@ +package io.github.dunwu.javadb.elasticsearch.mapper; + +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.DateUtil; +import io.github.dunwu.javadb.elasticsearch.ElasticsearchTemplate; +import io.github.dunwu.javadb.elasticsearch.entity.User; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.Date; + +/** + * open_applet_consume_yyyyMMdd ES Mapper + * + * @author Zhang Peng + * @date 2023-06-27 + */ +@Slf4j +@Component +public class UserEsMapper extends BaseDynamicEsMapper { + + public UserEsMapper(ElasticsearchTemplate elasticsearchTemplate) { + super(elasticsearchTemplate); + } + + @Override + public String getAlias() { + return "user"; + } + + @Override + public String getIndex() { + String date = DateUtil.format(new Date(), DatePattern.PURE_DATE_FORMAT); + return getAlias() + "_" + date; + } + + @Override + public String getType() { + return "_doc"; + } + + @Override + public Class getEntityClass() { + return User.class; + } + +} diff --git a/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/util/JsonUtil.java b/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/util/JsonUtil.java new file mode 100644 index 00000000..dabe0df5 --- /dev/null +++ b/codes/javadb/elasticsearch/elasticsearch6/src/main/java/io/github/dunwu/javadb/elasticsearch/util/JsonUtil.java @@ -0,0 +1,99 @@ +package io.github.dunwu.javadb.elasticsearch.util; + +import cn.hutool.core.util.StrUtil; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; +import lombok.extern.slf4j.Slf4j; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * JSON 工具类 + * + * @author Zhang Peng + * @date 2023-06-29 + */ +@Slf4j +public class JsonUtil { + + private static final ObjectMapper MAPPER = + JsonMapper.builder() + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true) + .configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true) + .serializationInclusion(JsonInclude.Include.ALWAYS) + .build(); + + public static List toList(String json, Class clazz) { + if (StrUtil.isBlank(json)) { + return null; + } + JavaType javaType = MAPPER.getTypeFactory().constructParametricType(List.class, clazz); + try { + return MAPPER.readValue(json, javaType); + } catch (Exception e) { + log.error("反序列化失败!json: {}, msg: {}", json, e.getMessage()); + } + return null; + } + + public static Map toMap(String json) { + if (StrUtil.isBlank(json)) { + return new HashMap<>(0); + } + try { + return MAPPER.readValue(json, new TypeReference>() { }); + } catch (Exception e) { + log.error("反序列化失败!json: {}, msg: {}", json, e.getMessage()); + } + return Collections.emptyMap(); + } + + public static T toBean(String json, Class clazz) { + if (StrUtil.isBlank(json)) { + return null; + } + try { + return MAPPER.readValue(json, clazz); + } catch (Exception e) { + log.error("反序列化失败!json: {}, msg: {}", json, e.getMessage()); + } + return null; + } + + public static T toBean(String json, TypeReference typeReference) { + if (StrUtil.isBlank(json)) { + return null; + } + try { + return (T) MAPPER.readValue(json, typeReference); + } catch (Exception e) { + log.error("反序列化失败!json: {}, msg: {}", json, e.getMessage()); + } + return null; + } + + public static String toString(T obj) { + if (obj == null) { + return null; + } + if (obj instanceof String) { + return (String) obj; + } + try { + return MAPPER.writeValueAsString(obj); + } catch (Exception e) { + log.error("序列化失败!obj: {}, msg: {}", obj, e.getMessage()); + } + return null; + } + +} diff --git a/codes/javadb/elasticsearch/elasticsearch6/src/test/java/io/github/dunwu/javadb/elasticsearch/BaseApplicationTests.java b/codes/javadb/elasticsearch/elasticsearch6/src/test/java/io/github/dunwu/javadb/elasticsearch/BaseApplicationTests.java new file mode 100644 index 00000000..1fadeff5 --- /dev/null +++ b/codes/javadb/elasticsearch/elasticsearch6/src/test/java/io/github/dunwu/javadb/elasticsearch/BaseApplicationTests.java @@ -0,0 +1,33 @@ +package io.github.dunwu.javadb.elasticsearch; + +import io.github.dunwu.javadb.elasticsearch.config.EnableElasticsearch; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +@EnableElasticsearch +@ExtendWith(SpringExtension.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public abstract class BaseApplicationTests { + + protected MockMvc mockMvc; + + @Autowired + private WebApplicationContext context; + + @BeforeEach + public void setUp() { + mockMvc = MockMvcBuilders.webAppContextSetup(context).build(); + } + + @BeforeAll + public static void setEnvironmentInDev() { + } + +} \ No newline at end of file diff --git a/codes/javadb/elasticsearch/elasticsearch6/src/test/java/io/github/dunwu/javadb/elasticsearch/BaseElasticsearchTemplateTest.java b/codes/javadb/elasticsearch/elasticsearch6/src/test/java/io/github/dunwu/javadb/elasticsearch/BaseElasticsearchTemplateTest.java new file mode 100644 index 00000000..4ec530a1 --- /dev/null +++ b/codes/javadb/elasticsearch/elasticsearch6/src/test/java/io/github/dunwu/javadb/elasticsearch/BaseElasticsearchTemplateTest.java @@ -0,0 +1,295 @@ +package io.github.dunwu.javadb.elasticsearch; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.StrUtil; +import io.github.dunwu.javadb.elasticsearch.entity.BaseEsEntity; +import io.github.dunwu.javadb.elasticsearch.entity.common.PageData; +import io.github.dunwu.javadb.elasticsearch.entity.common.ScrollData; +import io.github.dunwu.javadb.elasticsearch.util.JsonUtil; +import lombok.extern.slf4j.Slf4j; +import org.assertj.core.api.Assertions; +import org.elasticsearch.ElasticsearchException; +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.bulk.BulkResponse; +import org.elasticsearch.action.get.GetResponse; +import org.elasticsearch.action.search.SearchResponse; +import org.elasticsearch.index.query.BoolQueryBuilder; +import org.elasticsearch.index.query.QueryBuilders; +import org.elasticsearch.search.SearchHit; +import org.elasticsearch.search.builder.SearchSourceBuilder; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +/** + * ElasticsearchTemplate 测试 + * + * @author Zhang Peng + * @date 2023-11-13 + */ +@Slf4j +public abstract class BaseElasticsearchTemplateTest { + + static final int FROM = 0; + static final int SIZE = 10; + static final String TEST_ID_01 = "1"; + static final String TEST_ID_02 = "2"; + + protected ElasticsearchTemplate TEMPLATE = ElasticsearchFactory.newElasticsearchTemplate(); + + protected abstract String getAlias(); + + protected abstract String getIndex(); + + protected abstract String getType(); + + protected abstract int getShard(); + + protected abstract int getReplica(); + + protected abstract Class getEntityClass(); + + protected abstract Map getPropertiesMap(); + + protected abstract T getOneMockData(String id); + + protected abstract List getMockList(int num); + + protected void deleteIndex() throws IOException { + try { + Set set = TEMPLATE.getIndexSet(getAlias()); + if (CollectionUtil.isNotEmpty(set)) { + for (String index : set) { + log.info("删除 alias: {}, index: {}", getAlias(), index); + TEMPLATE.deleteIndex(index); + } + } + } catch (IOException | ElasticsearchException e) { + log.error("删除索引失败!", e); + } + boolean exists = TEMPLATE.isIndexExists(getIndex()); + Assertions.assertThat(exists).isFalse(); + } + + protected void createIndex() throws IOException { + boolean exists = TEMPLATE.isIndexExists(getIndex()); + if (exists) { + return; + } + TEMPLATE.createIndex(getIndex(), getType(), getAlias(), getShard(), getReplica()); + TEMPLATE.setMapping(getIndex(), getType(), getPropertiesMap()); + exists = TEMPLATE.isIndexExists(getIndex()); + Assertions.assertThat(exists).isTrue(); + } + + public void getIndexList() throws IOException { + Set set = TEMPLATE.getIndexSet(getAlias()); + log.info("alias: {}, indexList: {}", getAlias(), set); + Assertions.assertThat(set).isNotEmpty(); + } + + protected void save() throws IOException { + String id = "1"; + T oldEntity = getOneMockData(id); + TEMPLATE.save(getIndex(), getType(), oldEntity); + T newEntity = TEMPLATE.pojoById(getIndex(), getType(), id, getEntityClass()); + log.info("记录:{}", JsonUtil.toString(newEntity)); + Assertions.assertThat(newEntity).isNotNull(); + } + + protected void saveBatch() throws IOException { + int total = 5000; + List> listGroup = CollectionUtil.split(getMockList(total), 1000); + for (List list : listGroup) { + Assertions.assertThat(TEMPLATE.saveBatch(getIndex(), getType(), list)).isTrue(); + } + long count = TEMPLATE.count(getIndex(), getType(), new SearchSourceBuilder()); + log.info("批量更新记录数: {}", count); + Assertions.assertThat(count).isEqualTo(total); + } + + protected void asyncSave() throws IOException { + String id = "10000"; + T entity = getOneMockData(id); + TEMPLATE.save(getIndex(), getType(), entity); + T newEntity = TEMPLATE.pojoById(getIndex(), getType(), id, getEntityClass()); + log.info("记录:{}", JsonUtil.toString(newEntity)); + Assertions.assertThat(newEntity).isNotNull(); + } + + protected void asyncSaveBatch() throws IOException, InterruptedException { + int total = 10000; + List> listGroup = CollectionUtil.split(getMockList(total), 1000); + for (List list : listGroup) { + TEMPLATE.asyncSaveBatch(getIndex(), getType(), list, DEFAULT_BULK_LISTENER); + } + TimeUnit.SECONDS.sleep(20); + long count = TEMPLATE.count(getIndex(), getType(), new SearchSourceBuilder()); + log.info("批量更新记录数: {}", count); + Assertions.assertThat(count).isEqualTo(total); + } + + protected void getById() throws IOException { + GetResponse response = TEMPLATE.getById(getIndex(), getType(), TEST_ID_01); + Assertions.assertThat(response).isNotNull(); + log.info("记录:{}", JsonUtil.toString(response.getSourceAsMap())); + } + + protected void pojoById() throws IOException { + T entity = TEMPLATE.pojoById(getIndex(), getType(), TEST_ID_01, getEntityClass()); + Assertions.assertThat(entity).isNotNull(); + log.info("记录:{}", JsonUtil.toString(entity)); + } + + protected void pojoListByIds() throws IOException { + List ids = Arrays.asList(TEST_ID_01, TEST_ID_02); + List list = TEMPLATE.pojoListByIds(getIndex(), getType(), ids, getEntityClass()); + Assertions.assertThat(list).isNotEmpty(); + Assertions.assertThat(list.size()).isEqualTo(2); + for (T entity : list) { + log.info("记录:{}", JsonUtil.toString(entity)); + } + } + + protected void count() throws IOException { + BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery(); + queryBuilder.must(QueryBuilders.rangeQuery("docId").lt("100")); + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); + searchSourceBuilder.query(queryBuilder); + long total = TEMPLATE.count(getIndex(), getType(), searchSourceBuilder); + Assertions.assertThat(total).isNotZero(); + log.info("符合条件的记录数:{}", total); + } + + protected void query() throws IOException { + BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery(); + queryBuilder.must(QueryBuilders.rangeQuery("docId").lt("100")); + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); + searchSourceBuilder.query(queryBuilder); + searchSourceBuilder.from(FROM); + searchSourceBuilder.size(SIZE); + SearchResponse response = TEMPLATE.query(getIndex(), getType(), searchSourceBuilder); + Assertions.assertThat(response).isNotNull(); + Assertions.assertThat(response.getHits()).isNotNull(); + for (SearchHit hit : response.getHits().getHits()) { + log.info("记录:{}", hit.getSourceAsString()); + Map map = hit.getSourceAsMap(); + Assertions.assertThat(map).isNotNull(); + Assertions.assertThat(Integer.valueOf((String) map.get("docId"))).isLessThan(100); + } + } + + protected void pojoPage() throws IOException { + BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery(); + queryBuilder.must(QueryBuilders.rangeQuery("docId").lt("100")); + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); + searchSourceBuilder.query(queryBuilder); + searchSourceBuilder.from(FROM); + searchSourceBuilder.size(SIZE); + PageData page = TEMPLATE.pojoPage(getIndex(), getType(), searchSourceBuilder, getEntityClass()); + Assertions.assertThat(page).isNotNull(); + Assertions.assertThat(page.getContent()).isNotEmpty(); + for (T entity : page.getContent()) { + log.info("记录:{}", JsonUtil.toString(entity)); + } + } + + protected void pojoPageByLastId() throws IOException { + + BoolQueryBuilder queryBuilder = new BoolQueryBuilder(); + queryBuilder.must(QueryBuilders.rangeQuery("docId").lt("100")); + + long total = TEMPLATE.count(getIndex(), getType(), queryBuilder); + ScrollData scrollData = + TEMPLATE.pojoPageByScrollId(getIndex(), getType(), null, SIZE, queryBuilder, getEntityClass()); + if (scrollData == null || scrollData.getScrollId() == null) { + return; + } + Assertions.assertThat(scrollData.getTotal()).isEqualTo(total); + + long count = 0L; + scrollData.getContent().forEach(data -> { + log.info("docId: {}", data.getDocId()); + }); + count += scrollData.getContent().size(); + + String scrollId = scrollData.getScrollId(); + while (CollectionUtil.isNotEmpty(scrollData.getContent())) { + scrollData = TEMPLATE.pojoPageByScrollId(getIndex(), getType(), scrollId, SIZE, + queryBuilder, getEntityClass()); + if (scrollData == null || CollectionUtil.isEmpty(scrollData.getContent())) { + break; + } + if (StrUtil.isNotBlank(scrollData.getScrollId())) { + scrollId = scrollData.getScrollId(); + } + scrollData.getContent().forEach(data -> { + log.info("docId: {}", data.getDocId()); + }); + count += scrollData.getContent().size(); + } + log.info("total: {}", total); + Assertions.assertThat(count).isEqualTo(total); + } + + protected void pojoScroll() throws IOException { + + BoolQueryBuilder queryBuilder = new BoolQueryBuilder(); + queryBuilder.must(QueryBuilders.rangeQuery("docId").lt("100")); + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); + searchSourceBuilder.size(SIZE).query(queryBuilder).trackScores(false); + + long total = TEMPLATE.count(getIndex(), getType(), queryBuilder); + ScrollData scrollData = + TEMPLATE.pojoScrollBegin(getIndex(), getType(), searchSourceBuilder, getEntityClass()); + if (scrollData == null || scrollData.getScrollId() == null) { + return; + } + Assertions.assertThat(scrollData.getTotal()).isEqualTo(total); + + long count = 0L; + scrollData.getContent().forEach(data -> { + log.info("docId: {}", data.getDocId()); + }); + count += scrollData.getContent().size(); + + String scrollId = scrollData.getScrollId(); + while (CollectionUtil.isNotEmpty(scrollData.getContent())) { + scrollData = TEMPLATE.pojoScroll(scrollId, searchSourceBuilder, getEntityClass()); + if (scrollData == null || CollectionUtil.isEmpty(scrollData.getContent())) { + break; + } + if (StrUtil.isNotBlank(scrollData.getScrollId())) { + scrollId = scrollData.getScrollId(); + } + scrollData.getContent().forEach(data -> { + log.info("docId: {}", data.getDocId()); + }); + count += scrollData.getContent().size(); + } + TEMPLATE.pojoScrollEnd(scrollId); + log.info("total: {}", total); + Assertions.assertThat(count).isEqualTo(total); + } + + final ActionListener DEFAULT_BULK_LISTENER = new ActionListener() { + @Override + public void onResponse(BulkResponse response) { + if (response != null && !response.hasFailures()) { + log.info("【ES】异步批量写数据成功!index: {}, type: {}", getIndex(), getType()); + } else { + log.warn("【ES】异步批量写数据失败!index: {}, type: {}", getIndex(), getType()); + } + } + + @Override + public void onFailure(Exception e) { + log.error("【ES】异步批量写数据异常!index: {}, type: {}", getIndex(), getType()); + } + }; + +} diff --git a/codes/javadb/elasticsearch/elasticsearch6/src/test/java/io/github/dunwu/javadb/elasticsearch/TestApplication.java b/codes/javadb/elasticsearch/elasticsearch6/src/test/java/io/github/dunwu/javadb/elasticsearch/TestApplication.java new file mode 100644 index 00000000..e9ae33cd --- /dev/null +++ b/codes/javadb/elasticsearch/elasticsearch6/src/test/java/io/github/dunwu/javadb/elasticsearch/TestApplication.java @@ -0,0 +1,20 @@ +package io.github.dunwu.javadb.elasticsearch; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; + +@SpringBootApplication +public class TestApplication extends SpringBootServletInitializer { + + public static void main(String[] args) { + SpringApplication.run(TestApplication.class, args); + } + + @Override + protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { + return builder.sources(TestApplication.class); + } + +} diff --git a/codes/javadb/elasticsearch/elasticsearch6/src/test/java/io/github/dunwu/javadb/elasticsearch/UserElasticsearchTemplateTest.java b/codes/javadb/elasticsearch/elasticsearch6/src/test/java/io/github/dunwu/javadb/elasticsearch/UserElasticsearchTemplateTest.java new file mode 100644 index 00000000..aa8aab5c --- /dev/null +++ b/codes/javadb/elasticsearch/elasticsearch6/src/test/java/io/github/dunwu/javadb/elasticsearch/UserElasticsearchTemplateTest.java @@ -0,0 +1,116 @@ +package io.github.dunwu.javadb.elasticsearch; + +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.RandomUtil; +import io.github.dunwu.javadb.elasticsearch.entity.User; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +/** + * 使用 ElasticsearchTemplate 对 user 索引进行测试 + * + * @author Zhang Peng + * @date 2024-04-09 + */ +@Slf4j +public class UserElasticsearchTemplateTest extends BaseElasticsearchTemplateTest { + + @Override + protected String getAlias() { + return "user"; + } + + @Override + protected String getIndex() { + String date = DateUtil.format(new Date(), DatePattern.PURE_DATE_FORMAT); + return getAlias() + "_" + date; + } + + @Override + protected String getType() { + return "_doc"; + } + + @Override + protected int getShard() { + return 5; + } + + @Override + protected int getReplica() { + return 1; + } + + @Override + protected Class getEntityClass() { + return User.class; + } + + @Override + protected Map getPropertiesMap() { + return User.getPropertiesMap(); + } + + @Override + protected User getOneMockData(String id) { + return User.builder() + .id(id) + .name("测试数据" + id) + .age(RandomUtil.randomInt(1, 100)) + .build(); + } + + @Override + protected List getMockList(int num) { + List list = new LinkedList<>(); + for (int i = 1; i <= num; i++) { + User entity = getOneMockData(String.valueOf(i)); + list.add(entity); + } + return list; + } + + @Test + @DisplayName("索引管理测试") + public void indexTest() throws IOException { + super.deleteIndex(); + super.createIndex(); + super.getIndexList(); + } + + @Test + @DisplayName("写数据测试") + protected void writeTest() throws IOException { + super.save(); + super.saveBatch(); + } + + @Test + @DisplayName("异步写数据测试") + public void asyncWriteTest() throws IOException, InterruptedException { + super.asyncSave(); + super.asyncSaveBatch(); + } + + @Test + @DisplayName("读数据测试") + public void readTest() throws IOException { + super.getById(); + super.pojoById(); + super.pojoListByIds(); + super.count(); + super.query(); + super.pojoPage(); + super.pojoPageByLastId(); + super.pojoScroll(); + } + +} diff --git a/codes/javadb/elasticsearch/elasticsearch6/src/test/java/io/github/dunwu/javadb/elasticsearch/mapper/UserEsMapperTest.java b/codes/javadb/elasticsearch/elasticsearch6/src/test/java/io/github/dunwu/javadb/elasticsearch/mapper/UserEsMapperTest.java new file mode 100644 index 00000000..a287be67 --- /dev/null +++ b/codes/javadb/elasticsearch/elasticsearch6/src/test/java/io/github/dunwu/javadb/elasticsearch/mapper/UserEsMapperTest.java @@ -0,0 +1,472 @@ +package io.github.dunwu.javadb.elasticsearch.mapper; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.RandomUtil; +import cn.hutool.core.util.StrUtil; +import io.github.dunwu.javadb.elasticsearch.BaseApplicationTests; +import io.github.dunwu.javadb.elasticsearch.entity.User; +import io.github.dunwu.javadb.elasticsearch.entity.common.PageData; +import io.github.dunwu.javadb.elasticsearch.entity.common.ScrollData; +import io.github.dunwu.javadb.elasticsearch.util.JsonUtil; +import lombok.extern.slf4j.Slf4j; +import org.assertj.core.api.Assertions; +import org.elasticsearch.action.search.SearchResponse; +import org.elasticsearch.index.query.BoolQueryBuilder; +import org.elasticsearch.index.query.QueryBuilders; +import org.elasticsearch.search.SearchHit; +import org.elasticsearch.search.builder.SearchSourceBuilder; +import org.elasticsearch.search.sort.SortOrder; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +/** + * ElasticsearchTemplate 测试 + * + * @author Zhang Peng + * @date 2023-11-13 + */ +@Slf4j +public class UserEsMapperTest extends BaseApplicationTests { + + static final int FROM = 0; + static final int SIZE = 10; + private static final String day = "2024-04-07"; + + @Autowired + private UserEsMapper mapper; + + @Nested + @DisplayName("删除索引测试") + class DeleteIndexTest { + + @Test + @DisplayName("删除当天索引") + public void deleteIndex() { + String index = mapper.getIndex(); + boolean indexExists = mapper.isIndexExists(); + if (!indexExists) { + log.info("【ES】{} 不存在!", index); + return; + } + mapper.deleteIndex(); + indexExists = mapper.isIndexExists(); + Assertions.assertThat(indexExists).isFalse(); + } + + @Test + @DisplayName("根据日期删除索引") + public void deleteIndexInDay() { + String index = mapper.getIndex(day); + boolean indexExists = mapper.isIndexExistsInDay(day); + if (!indexExists) { + log.info("【ES】{} 不存在!", index); + return; + } + mapper.deleteIndexInDay(day); + indexExists = mapper.isIndexExistsInDay(day); + Assertions.assertThat(indexExists).isFalse(); + } + + } + + @Nested + @DisplayName("创建索引测试") + class CreateIndexTest { + + @Test + @DisplayName("创建当天索引") + public void createIndex() { + + String index = mapper.getIndex(); + boolean indexExists = mapper.isIndexExists(); + if (indexExists) { + log.info("【ES】{} 已存在!", index); + return; + } + + mapper.createIndexIfNotExists(); + indexExists = mapper.isIndexExists(); + Assertions.assertThat(indexExists).isTrue(); + } + + @Test + @DisplayName("根据日期创建索引") + public void createIndexInDay() { + + String index = mapper.getIndex(day); + boolean indexExists = mapper.isIndexExistsInDay(day); + if (indexExists) { + log.info("【ES】{} 已存在!", index); + return; + } + + mapper.createIndexIfNotExistsInDay(day); + indexExists = mapper.isIndexExistsInDay(day); + Assertions.assertThat(indexExists).isTrue(); + } + + } + + @Nested + @DisplayName("写操作测试") + class WriteTest { + + @Test + @DisplayName("保存当天数据") + public void save() { + String id = "1"; + User entity = getOneMockData(id); + mapper.save(entity); + User newEntity = mapper.pojoById(id); + log.info("entity: {}", JsonUtil.toString(newEntity)); + Assertions.assertThat(newEntity).isNotNull(); + } + + @Test + @DisplayName("保存指定日期数据") + public void saveInDay() { + String id = "1"; + User entity = getOneMockData(id); + mapper.saveInDay(day, entity); + User newEntity = mapper.pojoByIdInDay(day, id); + log.info("entity: {}", JsonUtil.toString(newEntity)); + Assertions.assertThat(newEntity).isNotNull(); + } + + @Test + @DisplayName("批量保存当天数据") + public void batchSave() throws InterruptedException { + int total = 10000; + List> listGroup = CollectionUtil.split(getMockList(total), 1000); + for (List list : listGroup) { + mapper.asyncSaveBatch(list); + } + TimeUnit.SECONDS.sleep(20); + long count = mapper.count(new SearchSourceBuilder()); + log.info("count: {}", count); + Assertions.assertThat(count).isEqualTo(10 * 1000); + } + + @Test + @DisplayName("批量保存指定日期数据") + public void batchSaveInDay() throws InterruptedException { + int total = 10000; + List> listGroup = CollectionUtil.split(getMockList(total), 1000); + for (List list : listGroup) { + mapper.asyncSaveBatchInDay(day, list); + } + TimeUnit.SECONDS.sleep(20); + long count = mapper.countInDay(day, new SearchSourceBuilder()); + log.info("count: {}", count); + Assertions.assertThat(count).isEqualTo(10 * 1000); + } + + } + + @Nested + @DisplayName("读操作测试") + class ReadTest { + + @Test + @DisplayName("根据ID查找当日数据") + public void pojoById() { + String id = "1"; + User newEntity = mapper.pojoById(id); + log.info("entity: {}", JsonUtil.toString(newEntity)); + Assertions.assertThat(newEntity).isNotNull(); + } + + @Test + @DisplayName("根据ID查找指定日期数据") + public void pojoByIdInDay() { + String id = "1"; + User newEntity = mapper.pojoByIdInDay(day, id); + log.info("entity: {}", JsonUtil.toString(newEntity)); + Assertions.assertThat(newEntity).isNotNull(); + } + + @Test + @DisplayName("获取匹配条件的记录数") + public void count() { + BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery(); + queryBuilder.must(QueryBuilders.rangeQuery("docId").lt("100")); + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); + searchSourceBuilder.query(queryBuilder); + long total = mapper.count(searchSourceBuilder); + Assertions.assertThat(total).isNotZero(); + log.info("符合条件的记录数:{}", total); + } + + @Test + @DisplayName("获取匹配条件的指定日期记录数") + public void countInDay() { + BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery(); + queryBuilder.must(QueryBuilders.rangeQuery("docId").lt("100")); + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); + searchSourceBuilder.query(queryBuilder); + long total = mapper.countInDay(day, searchSourceBuilder); + Assertions.assertThat(total).isNotZero(); + log.info("符合条件的记录数:{}", total); + } + + @Test + @DisplayName("获取匹配条件的记录") + public void query() { + BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery(); + queryBuilder.must(QueryBuilders.rangeQuery("docId").lt("100")); + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); + searchSourceBuilder.query(queryBuilder); + searchSourceBuilder.from(FROM); + searchSourceBuilder.size(SIZE); + SearchResponse response = mapper.query(searchSourceBuilder); + Assertions.assertThat(response).isNotNull(); + Assertions.assertThat(response.getHits()).isNotNull(); + for (SearchHit hit : response.getHits().getHits()) { + log.info("记录:{}", hit.getSourceAsString()); + Map map = hit.getSourceAsMap(); + Assertions.assertThat(map).isNotNull(); + Assertions.assertThat(Integer.valueOf((String) map.get("docId"))).isLessThan(100); + } + } + + @Test + @DisplayName("获取匹配条件的指定日期记录") + public void queryInDay() { + BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery(); + queryBuilder.must(QueryBuilders.rangeQuery("docId").lt("100")); + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); + searchSourceBuilder.query(queryBuilder); + searchSourceBuilder.from(FROM); + searchSourceBuilder.size(SIZE); + SearchResponse response = mapper.queryInDay(day, searchSourceBuilder); + Assertions.assertThat(response).isNotNull(); + Assertions.assertThat(response.getHits()).isNotNull(); + for (SearchHit hit : response.getHits().getHits()) { + log.info("记录:{}", hit.getSourceAsString()); + Map map = hit.getSourceAsMap(); + Assertions.assertThat(map).isNotNull(); + Assertions.assertThat(Integer.valueOf((String) map.get("docId"))).isLessThan(100); + } + } + + @Test + @DisplayName("from + size 分页查询当日数据") + public void pojoPage() { + BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery(); + queryBuilder.must(QueryBuilders.rangeQuery("docId").lt("100")); + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); + searchSourceBuilder.query(queryBuilder); + searchSourceBuilder.from(FROM); + searchSourceBuilder.size(SIZE); + PageData page = mapper.pojoPage(searchSourceBuilder); + Assertions.assertThat(page).isNotNull(); + Assertions.assertThat(page.getContent()).isNotEmpty(); + for (User entity : page.getContent()) { + log.info("记录:{}", JsonUtil.toString(entity)); + } + } + + @Test + @DisplayName("from + size 分页查询指定日期数据") + public void pojoPageInDay() { + BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery(); + queryBuilder.must(QueryBuilders.rangeQuery("docId").lt("100")); + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); + searchSourceBuilder.query(queryBuilder); + searchSourceBuilder.from(FROM); + searchSourceBuilder.size(SIZE); + PageData page = mapper.pojoPageInDay(day, searchSourceBuilder); + Assertions.assertThat(page).isNotNull(); + Assertions.assertThat(page.getContent()).isNotEmpty(); + for (User entity : page.getContent()) { + log.info("记录:{}", JsonUtil.toString(entity)); + } + } + + @Test + @DisplayName("search after 分页查询当日数据") + protected void pojoPageByLastId() { + + BoolQueryBuilder queryBuilder = new BoolQueryBuilder(); + queryBuilder.must(QueryBuilders.rangeQuery("docId").lt("100")); + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); + searchSourceBuilder.query(queryBuilder); + long total = mapper.count(searchSourceBuilder); + ScrollData scrollData = mapper.pojoPageByLastId(null, SIZE, queryBuilder); + if (scrollData == null || scrollData.getScrollId() == null) { + return; + } + Assertions.assertThat(scrollData.getTotal()).isEqualTo(total); + + long count = 0L; + scrollData.getContent().forEach(data -> { + log.info("docId: {}", data.getDocId()); + }); + count += scrollData.getContent().size(); + + String scrollId = scrollData.getScrollId(); + while (CollectionUtil.isNotEmpty(scrollData.getContent())) { + scrollData = mapper.pojoPageByLastId(scrollId, SIZE, queryBuilder); + if (scrollData == null || CollectionUtil.isEmpty(scrollData.getContent())) { + break; + } + if (StrUtil.isNotBlank(scrollData.getScrollId())) { + scrollId = scrollData.getScrollId(); + } + scrollData.getContent().forEach(data -> { + log.info("docId: {}", data.getDocId()); + }); + count += scrollData.getContent().size(); + } + log.info("total: {}", total); + Assertions.assertThat(count).isEqualTo(total); + } + + @Test + @DisplayName("search after 分页查询指定日期数据") + protected void pojoPageByLastIdInDay() { + + BoolQueryBuilder queryBuilder = new BoolQueryBuilder(); + queryBuilder.must(QueryBuilders.rangeQuery("docId").lt("100")); + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); + searchSourceBuilder.query(queryBuilder); + long total = mapper.count(searchSourceBuilder); + ScrollData scrollData = mapper.pojoPageByLastIdInDay(day, null, SIZE, queryBuilder); + if (scrollData == null || scrollData.getScrollId() == null) { + return; + } + Assertions.assertThat(scrollData.getTotal()).isEqualTo(total); + + long count = 0L; + scrollData.getContent().forEach(data -> { + log.info("docId: {}", data.getDocId()); + }); + count += scrollData.getContent().size(); + + String scrollId = scrollData.getScrollId(); + while (CollectionUtil.isNotEmpty(scrollData.getContent())) { + scrollData = mapper.pojoPageByLastIdInDay(day, scrollId, SIZE, queryBuilder); + if (scrollData == null || CollectionUtil.isEmpty(scrollData.getContent())) { + break; + } + if (StrUtil.isNotBlank(scrollData.getScrollId())) { + scrollId = scrollData.getScrollId(); + } + scrollData.getContent().forEach(data -> { + log.info("docId: {}", data.getDocId()); + }); + count += scrollData.getContent().size(); + } + log.info("total: {}", total); + Assertions.assertThat(count).isEqualTo(total); + } + + @Test + @DisplayName("滚动翻页当日数据") + public void pojoScroll() { + + final int size = 100; + + BoolQueryBuilder queryBuilder = new BoolQueryBuilder(); + queryBuilder.must(QueryBuilders.rangeQuery("docId").lt("100")); + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); + searchSourceBuilder.size(size).sort("docId", SortOrder.ASC).query(queryBuilder).trackScores(false); + + long total = mapper.count(searchSourceBuilder); + log.info("total: {}", total); + + ScrollData scrollData = mapper.pojoScrollBegin(searchSourceBuilder); + if (scrollData == null || scrollData.getScrollId() == null) { + return; + } + + long count = 0L; + scrollData.getContent().forEach(data -> { + log.info("docId: {}", data.getDocId()); + }); + Assertions.assertThat(scrollData.getTotal()).isEqualTo(total); + count += scrollData.getContent().size(); + + String scrollId = scrollData.getScrollId(); + while (CollectionUtil.isNotEmpty(scrollData.getContent())) { + scrollData = mapper.pojoScroll(scrollId, searchSourceBuilder); + if (scrollData != null && StrUtil.isNotBlank(scrollData.getScrollId())) { + scrollId = scrollData.getScrollId(); + } + scrollData.getContent().forEach(data -> { + log.info("docId: {}", data.getDocId()); + }); + count += scrollData.getContent().size(); + } + mapper.pojoScrollEnd(scrollId); + Assertions.assertThat(count).isEqualTo(total); + } + + @Test + @DisplayName("滚动翻页指定日期数据") + public void pojoScrollInDay() { + + final int size = 100; + + BoolQueryBuilder queryBuilder = new BoolQueryBuilder(); + queryBuilder.must(QueryBuilders.rangeQuery("docId").lt("100")); + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); + searchSourceBuilder.size(size).sort("docId", SortOrder.ASC).query(queryBuilder).trackScores(false); + + long total = mapper.countInDay(day, searchSourceBuilder); + log.info("total: {}", total); + + ScrollData scrollData = mapper.pojoScrollBeginInDay(day, searchSourceBuilder); + if (scrollData == null || scrollData.getScrollId() == null) { + return; + } + + long count = 0L; + scrollData.getContent().forEach(data -> { + log.info("docId: {}", data.getDocId()); + }); + Assertions.assertThat(scrollData.getTotal()).isEqualTo(total); + count += scrollData.getContent().size(); + + String scrollId = scrollData.getScrollId(); + while (CollectionUtil.isNotEmpty(scrollData.getContent())) { + scrollData = mapper.pojoScroll(scrollId, searchSourceBuilder); + if (scrollData != null && StrUtil.isNotBlank(scrollData.getScrollId())) { + scrollId = scrollData.getScrollId(); + } + scrollData.getContent().forEach(data -> { + log.info("docId: {}", data.getDocId()); + }); + count += scrollData.getContent().size(); + } + mapper.pojoScrollEnd(scrollId); + Assertions.assertThat(count).isEqualTo(total); + } + + } + + public User getOneMockData(String id) { + return User.builder() + .id(id) + .name("测试数据" + id) + .age(RandomUtil.randomInt(1, 100)) + .build(); + } + + public List getMockList(int num) { + List list = new LinkedList<>(); + for (int i = 1; i <= num; i++) { + User entity = getOneMockData(String.valueOf(i)); + list.add(entity); + } + return list; + } + +} diff --git a/codes/javadb/elasticsearch/elasticsearch7/pom.xml b/codes/javadb/elasticsearch/elasticsearch7/pom.xml new file mode 100644 index 00000000..bd4e66b1 --- /dev/null +++ b/codes/javadb/elasticsearch/elasticsearch7/pom.xml @@ -0,0 +1,87 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 2.6.3 + + + io.github.dunwu + javadb-elasticsearch7 + 1.0.0 + jar + + + 7.16.3 + + + + + org.springframework.boot + spring-boot-starter-data-elasticsearch + + + org.springframework.boot + spring-boot-starter-json + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.projectlombok + lombok + + + cn.hutool + hutool-all + 5.7.20 + + + + co.elastic.clients + elasticsearch-java + 7.16.3 + + + + org.elasticsearch.client + elasticsearch-rest-client + ${elasticsearch.version} + + + org.elasticsearch.client + elasticsearch-rest-high-level-client + ${elasticsearch.version} + + + + + + + com.fasterxml.jackson.core + jackson-databind + 2.12.3 + + + com.fasterxml.jackson.core + jackson-core + 2.12.3 + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/SpringBootDataElasticsearchApplication.java b/codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/SpringBootDataElasticsearchApplication.java similarity index 100% rename from codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/SpringBootDataElasticsearchApplication.java rename to codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/SpringBootDataElasticsearchApplication.java diff --git a/codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/constant/NamingStrategy.java b/codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/constant/NamingStrategy.java similarity index 100% rename from codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/constant/NamingStrategy.java rename to codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/constant/NamingStrategy.java diff --git a/codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/constant/OrderType.java b/codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/constant/OrderType.java similarity index 100% rename from codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/constant/OrderType.java rename to codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/constant/OrderType.java diff --git a/codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/constant/QueryJudgeType.java b/codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/constant/QueryJudgeType.java similarity index 100% rename from codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/constant/QueryJudgeType.java rename to codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/constant/QueryJudgeType.java diff --git a/codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/constant/QueryLogicType.java b/codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/constant/QueryLogicType.java similarity index 100% rename from codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/constant/QueryLogicType.java rename to codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/constant/QueryLogicType.java diff --git a/codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/elasticsearch/ElasticSearchUtil.java b/codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/elasticsearch/ElasticSearchUtil.java similarity index 100% rename from codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/elasticsearch/ElasticSearchUtil.java rename to codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/elasticsearch/ElasticSearchUtil.java diff --git a/codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/elasticsearch/QueryDocument.java b/codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/elasticsearch/QueryDocument.java similarity index 100% rename from codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/elasticsearch/QueryDocument.java rename to codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/elasticsearch/QueryDocument.java diff --git a/codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/elasticsearch/QueryField.java b/codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/elasticsearch/QueryField.java similarity index 100% rename from codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/elasticsearch/QueryField.java rename to codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/elasticsearch/QueryField.java diff --git a/codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/Article.java b/codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/Article.java similarity index 100% rename from codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/Article.java rename to codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/Article.java diff --git a/codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/ArticleBuilder.java b/codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/ArticleBuilder.java similarity index 100% rename from codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/ArticleBuilder.java rename to codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/ArticleBuilder.java diff --git a/codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/Author.java b/codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/Author.java similarity index 100% rename from codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/Author.java rename to codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/Author.java diff --git a/codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/Book.java b/codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/Book.java similarity index 100% rename from codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/Book.java rename to codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/Book.java diff --git a/codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/Car.java b/codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/Car.java similarity index 100% rename from codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/Car.java rename to codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/Car.java diff --git a/codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/GirlFriend.java b/codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/GirlFriend.java similarity index 100% rename from codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/GirlFriend.java rename to codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/GirlFriend.java diff --git a/codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/Operation.java b/codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/Operation.java similarity index 100% rename from codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/Operation.java rename to codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/Operation.java diff --git a/codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/Person.java b/codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/Person.java similarity index 100% rename from codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/Person.java rename to codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/Person.java diff --git a/codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/PersonMultipleLevelNested.java b/codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/PersonMultipleLevelNested.java similarity index 100% rename from codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/PersonMultipleLevelNested.java rename to codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/PersonMultipleLevelNested.java diff --git a/codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/Product.java b/codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/Product.java similarity index 100% rename from codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/Product.java rename to codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/Product.java diff --git a/codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/Sector.java b/codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/Sector.java similarity index 100% rename from codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/Sector.java rename to codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/Sector.java diff --git a/codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/User.java b/codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/User.java similarity index 100% rename from codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/User.java rename to codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/User.java diff --git a/codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/UserQuery.java b/codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/UserQuery.java similarity index 100% rename from codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/UserQuery.java rename to codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/entities/UserQuery.java diff --git a/codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/repositories/ArticleRepository.java b/codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/repositories/ArticleRepository.java similarity index 100% rename from codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/repositories/ArticleRepository.java rename to codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/repositories/ArticleRepository.java diff --git a/codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/repositories/BookRepository.java b/codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/repositories/BookRepository.java similarity index 100% rename from codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/repositories/BookRepository.java rename to codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/repositories/BookRepository.java diff --git a/codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/repositories/OperationRepository.java b/codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/repositories/OperationRepository.java similarity index 100% rename from codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/repositories/OperationRepository.java rename to codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/repositories/OperationRepository.java diff --git a/codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/repositories/ProductRepository.java b/codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/repositories/ProductRepository.java similarity index 100% rename from codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/repositories/ProductRepository.java rename to codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/repositories/ProductRepository.java diff --git a/codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/repositories/UserRepository.java b/codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/repositories/UserRepository.java similarity index 100% rename from codes/javadb/javadb-elasticsearch/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/repositories/UserRepository.java rename to codes/javadb/elasticsearch/elasticsearch7/src/main/java/io/github/dunwu/javadb/elasticsearch/springboot/repositories/UserRepository.java diff --git a/codes/javadb/javadb-elasticsearch/src/main/resources/application.properties b/codes/javadb/elasticsearch/elasticsearch7/src/main/resources/application.properties similarity index 100% rename from codes/javadb/javadb-elasticsearch/src/main/resources/application.properties rename to codes/javadb/elasticsearch/elasticsearch7/src/main/resources/application.properties diff --git a/codes/javadb/javadb-elasticsearch/src/main/resources/banner.txt b/codes/javadb/elasticsearch/elasticsearch7/src/main/resources/banner.txt similarity index 100% rename from codes/javadb/javadb-elasticsearch/src/main/resources/banner.txt rename to codes/javadb/elasticsearch/elasticsearch7/src/main/resources/banner.txt diff --git a/codes/javadb/javadb-elasticsearch/src/main/resources/logback.xml b/codes/javadb/elasticsearch/elasticsearch7/src/main/resources/logback.xml similarity index 100% rename from codes/javadb/javadb-elasticsearch/src/main/resources/logback.xml rename to codes/javadb/elasticsearch/elasticsearch7/src/main/resources/logback.xml diff --git a/codes/javadb/javadb-elasticsearch/src/test/java/io/github/dunwu/javadb/elasticsearch/springboot/RestHighLevelClientDocumentApiTest.java b/codes/javadb/elasticsearch/elasticsearch7/src/test/java/io/github/dunwu/javadb/elasticsearch/springboot/RestHighLevelClientDocumentApiTest.java similarity index 100% rename from codes/javadb/javadb-elasticsearch/src/test/java/io/github/dunwu/javadb/elasticsearch/springboot/RestHighLevelClientDocumentApiTest.java rename to codes/javadb/elasticsearch/elasticsearch7/src/test/java/io/github/dunwu/javadb/elasticsearch/springboot/RestHighLevelClientDocumentApiTest.java diff --git a/codes/javadb/javadb-elasticsearch/src/test/java/io/github/dunwu/javadb/elasticsearch/springboot/RestHighLevelClientDocumentSearchApiTest.java b/codes/javadb/elasticsearch/elasticsearch7/src/test/java/io/github/dunwu/javadb/elasticsearch/springboot/RestHighLevelClientDocumentSearchApiTest.java similarity index 100% rename from codes/javadb/javadb-elasticsearch/src/test/java/io/github/dunwu/javadb/elasticsearch/springboot/RestHighLevelClientDocumentSearchApiTest.java rename to codes/javadb/elasticsearch/elasticsearch7/src/test/java/io/github/dunwu/javadb/elasticsearch/springboot/RestHighLevelClientDocumentSearchApiTest.java diff --git a/codes/javadb/javadb-elasticsearch/src/test/java/io/github/dunwu/javadb/elasticsearch/springboot/RestHighLevelClientIndexApiTest.java b/codes/javadb/elasticsearch/elasticsearch7/src/test/java/io/github/dunwu/javadb/elasticsearch/springboot/RestHighLevelClientIndexApiTest.java similarity index 100% rename from codes/javadb/javadb-elasticsearch/src/test/java/io/github/dunwu/javadb/elasticsearch/springboot/RestHighLevelClientIndexApiTest.java rename to codes/javadb/elasticsearch/elasticsearch7/src/test/java/io/github/dunwu/javadb/elasticsearch/springboot/RestHighLevelClientIndexApiTest.java diff --git a/codes/javadb/javadb-elasticsearch/src/test/java/io/github/dunwu/javadb/elasticsearch/springboot/RestLowLevelClientTest.java b/codes/javadb/elasticsearch/elasticsearch7/src/test/java/io/github/dunwu/javadb/elasticsearch/springboot/RestLowLevelClientTest.java similarity index 100% rename from codes/javadb/javadb-elasticsearch/src/test/java/io/github/dunwu/javadb/elasticsearch/springboot/RestLowLevelClientTest.java rename to codes/javadb/elasticsearch/elasticsearch7/src/test/java/io/github/dunwu/javadb/elasticsearch/springboot/RestLowLevelClientTest.java diff --git a/codes/javadb/javadb-elasticsearch/src/test/java/io/github/dunwu/javadb/elasticsearch/springboot/entity/ecommerce/Geoip.java b/codes/javadb/elasticsearch/elasticsearch7/src/test/java/io/github/dunwu/javadb/elasticsearch/springboot/entity/ecommerce/Geoip.java similarity index 100% rename from codes/javadb/javadb-elasticsearch/src/test/java/io/github/dunwu/javadb/elasticsearch/springboot/entity/ecommerce/Geoip.java rename to codes/javadb/elasticsearch/elasticsearch7/src/test/java/io/github/dunwu/javadb/elasticsearch/springboot/entity/ecommerce/Geoip.java diff --git a/codes/javadb/javadb-elasticsearch/src/test/java/io/github/dunwu/javadb/elasticsearch/springboot/entity/ecommerce/KibanaSampleDataEcommerceBean.java b/codes/javadb/elasticsearch/elasticsearch7/src/test/java/io/github/dunwu/javadb/elasticsearch/springboot/entity/ecommerce/KibanaSampleDataEcommerceBean.java similarity index 100% rename from codes/javadb/javadb-elasticsearch/src/test/java/io/github/dunwu/javadb/elasticsearch/springboot/entity/ecommerce/KibanaSampleDataEcommerceBean.java rename to codes/javadb/elasticsearch/elasticsearch7/src/test/java/io/github/dunwu/javadb/elasticsearch/springboot/entity/ecommerce/KibanaSampleDataEcommerceBean.java diff --git a/codes/javadb/javadb-elasticsearch/src/test/java/io/github/dunwu/javadb/elasticsearch/springboot/entity/ecommerce/Location.java b/codes/javadb/elasticsearch/elasticsearch7/src/test/java/io/github/dunwu/javadb/elasticsearch/springboot/entity/ecommerce/Location.java similarity index 100% rename from codes/javadb/javadb-elasticsearch/src/test/java/io/github/dunwu/javadb/elasticsearch/springboot/entity/ecommerce/Location.java rename to codes/javadb/elasticsearch/elasticsearch7/src/test/java/io/github/dunwu/javadb/elasticsearch/springboot/entity/ecommerce/Location.java diff --git a/codes/javadb/javadb-elasticsearch/src/test/java/io/github/dunwu/javadb/elasticsearch/springboot/entity/ecommerce/ProductsItem.java b/codes/javadb/elasticsearch/elasticsearch7/src/test/java/io/github/dunwu/javadb/elasticsearch/springboot/entity/ecommerce/ProductsItem.java similarity index 100% rename from codes/javadb/javadb-elasticsearch/src/test/java/io/github/dunwu/javadb/elasticsearch/springboot/entity/ecommerce/ProductsItem.java rename to codes/javadb/elasticsearch/elasticsearch7/src/test/java/io/github/dunwu/javadb/elasticsearch/springboot/entity/ecommerce/ProductsItem.java diff --git a/codes/javadb/elasticsearch/pom.xml b/codes/javadb/elasticsearch/pom.xml new file mode 100644 index 00000000..f7079658 --- /dev/null +++ b/codes/javadb/elasticsearch/pom.xml @@ -0,0 +1,15 @@ + + + 4.0.0 + + io.github.dunwu + javadb-elasticsearch + 1.0.0 + pom + + + elasticsearch6 + elasticsearch7 + + diff --git a/codes/javadb/h2/pom.xml b/codes/javadb/h2/pom.xml new file mode 100644 index 00000000..b7d09205 --- /dev/null +++ b/codes/javadb/h2/pom.xml @@ -0,0 +1,53 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 2.6.3 + + + io.github.dunwu + javadb-h2 + 1.0.0 + jar + + + + org.springframework.boot + spring-boot-starter-data-rest + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-test + test + + + org.projectlombok + lombok + + + + + com.h2database + h2 + 2.1.210 + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/codes/javadb/javadb-h2/src/main/java/io/github/dunwu/javadb/h2/springboot/SpringBootDataJpaApplication.java b/codes/javadb/h2/src/main/java/io/github/dunwu/javadb/h2/springboot/SpringBootDataJpaApplication.java similarity index 100% rename from codes/javadb/javadb-h2/src/main/java/io/github/dunwu/javadb/h2/springboot/SpringBootDataJpaApplication.java rename to codes/javadb/h2/src/main/java/io/github/dunwu/javadb/h2/springboot/SpringBootDataJpaApplication.java diff --git a/codes/javadb/javadb-h2/src/main/java/io/github/dunwu/javadb/h2/springboot/User.java b/codes/javadb/h2/src/main/java/io/github/dunwu/javadb/h2/springboot/User.java similarity index 100% rename from codes/javadb/javadb-h2/src/main/java/io/github/dunwu/javadb/h2/springboot/User.java rename to codes/javadb/h2/src/main/java/io/github/dunwu/javadb/h2/springboot/User.java diff --git a/codes/javadb/javadb-h2/src/main/java/io/github/dunwu/javadb/h2/springboot/UserRepository.java b/codes/javadb/h2/src/main/java/io/github/dunwu/javadb/h2/springboot/UserRepository.java similarity index 100% rename from codes/javadb/javadb-h2/src/main/java/io/github/dunwu/javadb/h2/springboot/UserRepository.java rename to codes/javadb/h2/src/main/java/io/github/dunwu/javadb/h2/springboot/UserRepository.java diff --git a/codes/javadb/javadb-h2/src/main/resources/application.properties b/codes/javadb/h2/src/main/resources/application.properties similarity index 100% rename from codes/javadb/javadb-h2/src/main/resources/application.properties rename to codes/javadb/h2/src/main/resources/application.properties diff --git a/codes/javadb/javadb-h2/src/main/resources/banner.txt b/codes/javadb/h2/src/main/resources/banner.txt similarity index 100% rename from codes/javadb/javadb-h2/src/main/resources/banner.txt rename to codes/javadb/h2/src/main/resources/banner.txt diff --git a/codes/javadb/javadb-h2/src/main/resources/logback.xml b/codes/javadb/h2/src/main/resources/logback.xml similarity index 100% rename from codes/javadb/javadb-h2/src/main/resources/logback.xml rename to codes/javadb/h2/src/main/resources/logback.xml diff --git a/codes/javadb/javadb-h2/src/main/resources/sql/data-h2.sql b/codes/javadb/h2/src/main/resources/sql/data-h2.sql similarity index 100% rename from codes/javadb/javadb-h2/src/main/resources/sql/data-h2.sql rename to codes/javadb/h2/src/main/resources/sql/data-h2.sql diff --git a/codes/javadb/javadb-h2/src/main/resources/sql/schema-h2.sql b/codes/javadb/h2/src/main/resources/sql/schema-h2.sql similarity index 100% rename from codes/javadb/javadb-h2/src/main/resources/sql/schema-h2.sql rename to codes/javadb/h2/src/main/resources/sql/schema-h2.sql diff --git a/codes/javadb/javadb-h2/src/test/java/io/github/dunwu/javadb/h2/H2JdbcTest.java b/codes/javadb/h2/src/test/java/io/github/dunwu/javadb/h2/H2JdbcTest.java similarity index 100% rename from codes/javadb/javadb-h2/src/test/java/io/github/dunwu/javadb/h2/H2JdbcTest.java rename to codes/javadb/h2/src/test/java/io/github/dunwu/javadb/h2/H2JdbcTest.java diff --git a/codes/javadb/javadb-h2/src/test/java/io/github/dunwu/javadb/h2/springboot/SpringBootJpaRestTest.java b/codes/javadb/h2/src/test/java/io/github/dunwu/javadb/h2/springboot/SpringBootJpaRestTest.java similarity index 100% rename from codes/javadb/javadb-h2/src/test/java/io/github/dunwu/javadb/h2/springboot/SpringBootJpaRestTest.java rename to codes/javadb/h2/src/test/java/io/github/dunwu/javadb/h2/springboot/SpringBootJpaRestTest.java diff --git a/codes/javadb/javadb-h2/src/test/java/io/github/dunwu/javadb/h2/springboot/SpringBootJpaTest.java b/codes/javadb/h2/src/test/java/io/github/dunwu/javadb/h2/springboot/SpringBootJpaTest.java similarity index 100% rename from codes/javadb/javadb-h2/src/test/java/io/github/dunwu/javadb/h2/springboot/SpringBootJpaTest.java rename to codes/javadb/h2/src/test/java/io/github/dunwu/javadb/h2/springboot/SpringBootJpaTest.java diff --git a/codes/javadb/hbase/pom.xml b/codes/javadb/hbase/pom.xml new file mode 100644 index 00000000..1c0e69f8 --- /dev/null +++ b/codes/javadb/hbase/pom.xml @@ -0,0 +1,60 @@ + + + 4.0.0 + + + io.github.dunwu + javadb + 1.0.0 + ../pom.xml + + + javadb-hbase + jar + + + 1.8 + ${java.version} + ${java.version} + UTF-8 + UTF-8 + + + + + org.apache.hbase + hbase-client + + + org.apache.hadoop + hadoop-auth + + + cn.hutool + hutool-all + + + com.fasterxml.jackson.core + jackson-databind + + + com.alibaba + fastjson + + + org.projectlombok + lombok + + + org.springframework + spring-context-support + + + org.springframework.boot + spring-boot-starter-test + test + + + diff --git a/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/HbaseAdmin.java b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/HbaseAdmin.java new file mode 100644 index 00000000..fd9b07a8 --- /dev/null +++ b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/HbaseAdmin.java @@ -0,0 +1,294 @@ +package io.github.dunwu.javadb.hbase; + +import cn.hutool.core.io.IoUtil; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.NamespaceDescriptor; +import org.apache.hadoop.hbase.TableName; +import org.apache.hadoop.hbase.client.Admin; +import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor; +import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder; +import org.apache.hadoop.hbase.client.Connection; +import org.apache.hadoop.hbase.client.ConnectionFactory; +import org.apache.hadoop.hbase.client.Table; +import org.apache.hadoop.hbase.client.TableDescriptor; +import org.apache.hadoop.hbase.client.TableDescriptorBuilder; +import org.apache.hadoop.hbase.security.User; +import org.apache.hadoop.security.UserGroupInformation; + +import java.io.Closeable; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * HBase 管理工具类 + * + * @author Zhang Peng + * @date 2023-03-27 + */ +public class HbaseAdmin implements Closeable { + + private final Connection connection; + private final Configuration configuration; + + protected HbaseAdmin(Configuration configuration) throws IOException { + this.configuration = configuration; + // 无需鉴权连接 + // this.connection = ConnectionFactory.createConnection(configuration); + // 鉴权连接 + this.connection = ConnectionFactory.createConnection(configuration, null, + new User.SecureHadoopUser(UserGroupInformation.createRemoteUser("test"))); + } + + protected HbaseAdmin(Connection connection) { + this.configuration = connection.getConfiguration(); + this.connection = connection; + } + + public synchronized static HbaseAdmin newInstance(Configuration configuration) throws IOException { + if (configuration == null) { + throw new IllegalArgumentException("configuration can not be null!"); + } + return new HbaseAdmin(configuration); + } + + public synchronized static HbaseAdmin newInstance(Connection connection) throws IOException { + if (connection == null) { + throw new IllegalArgumentException("connection can not be null!"); + } + return new HbaseAdmin(connection); + } + + /** + * 关闭内部持有的 HBase Connection 实例 + */ + @Override + public synchronized void close() { + if (null == connection || connection.isClosed()) { + return; + } + IoUtil.close(connection); + } + + /** + * 获取 HBase 连接实例 + * + * @return / + */ + public Connection getConnection() { + if (null == connection) { + throw new RuntimeException("HBase connection init failed..."); + } + return connection; + } + + /** + * 获取 HBase 配置 + * + * @return / + */ + public Configuration getConfiguration() { + return configuration; + } + + /** + * 创建命名空间 + * + * @param namespace 命名空间 + */ + public void createNamespace(String namespace) throws IOException { + Admin admin = null; + try { + admin = getAdmin(); + NamespaceDescriptor nd = NamespaceDescriptor.create(namespace).build(); + admin.createNamespace(nd); + } finally { + recycle(admin); + } + } + + /** + * 删除命名空间 + * + * @param namespace 命名空间 + */ + public void dropNamespace(String namespace) throws IOException { + dropNamespace(namespace, false); + } + + /** + * 删除命名空间 + * + * @param namespace 命名空间 + * @param force 是否强制删除 + */ + public void dropNamespace(String namespace, boolean force) throws IOException { + Admin admin = null; + try { + admin = getAdmin(); + if (force) { + TableName[] tableNames = admin.listTableNamesByNamespace(namespace); + for (TableName name : tableNames) { + admin.disableTable(name); + admin.deleteTable(name); + } + } + admin.deleteNamespace(namespace); + } finally { + recycle(admin); + } + } + + /** + * 获取所有命名空间 + */ + public String[] listNamespaces() throws IOException { + Admin admin = null; + try { + admin = getAdmin(); + return admin.listNamespaces(); + } finally { + recycle(admin); + } + } + + /** + * 指定表是否存在 + * + * @param tableName 表名 + */ + public boolean existsTable(TableName tableName) throws IOException { + Admin admin = getAdmin(); + boolean result = admin.tableExists(tableName); + admin.close(); + return result; + } + + /** + * 创建表 + * + * @param tableName 表名 + * @param families 列族 + */ + public void createTable(TableName tableName, String... families) throws IOException { + createTable(tableName, null, families); + } + + /** + * 创建表 + * + * @param tableName 表名 + * @param splitKeys 表初始区域的拆分关键字 + * @param families 列族 + */ + public void createTable(TableName tableName, byte[][] splitKeys, String... families) throws IOException { + + List columnFamilyDescriptorList = new ArrayList<>(); + TableDescriptorBuilder builder = TableDescriptorBuilder.newBuilder(tableName); + for (String cf : families) { + ColumnFamilyDescriptor columnFamilyDescriptor = ColumnFamilyDescriptorBuilder.of(cf); + columnFamilyDescriptorList.add(columnFamilyDescriptor); + } + builder.setColumnFamilies(columnFamilyDescriptorList); + + TableDescriptor td = builder.build(); + Admin admin = getAdmin(); + if (splitKeys != null) { + admin.createTable(td, splitKeys); + } else { + admin.createTable(td); + } + admin.close(); + } + + /** + * 删除表 + * + * @param tableName 表名 + */ + public void dropTable(TableName tableName) throws IOException { + if (existsTable(tableName)) { + Admin admin = getAdmin(); + if (admin.isTableEnabled(tableName)) { + disableTable(tableName); + } + admin.deleteTable(tableName); + admin.close(); + } + } + + /** + * 禁用表 + * + * @param tableName 表名 + */ + public void disableTable(TableName tableName) throws IOException { + Admin admin = getAdmin(); + admin.disableTable(tableName); + admin.close(); + } + + /** + * 启用表 + * + * @param tableName 表名 + */ + public void enableTable(TableName tableName) throws IOException { + Admin admin = getAdmin(); + admin.enableTable(tableName); + admin.close(); + } + + /** + * 获取所有表 + */ + public TableName[] listTableNames() throws IOException { + Admin admin = null; + try { + admin = getAdmin(); + return admin.listTableNames(); + } finally { + recycle(admin); + } + } + + /** + * 获取指定命名空间下的所有表 + */ + public TableName[] listTableNamesByNamespace(String namespace) throws IOException { + Admin admin = null; + try { + admin = getAdmin(); + return admin.listTableNamesByNamespace(namespace); + } finally { + recycle(admin); + } + } + + /** + * 获取 {@link org.apache.hadoop.hbase.client.Table} 实例 + * + * @param tableName 表名 + * @return / + */ + public Table getTable(TableName tableName) throws IOException { + return getConnection().getTable(tableName); + } + + /** + * 获取 {@link org.apache.hadoop.hbase.client.Admin} 实例 + * + * @return / + */ + public Admin getAdmin() throws IOException { + return getConnection().getAdmin(); + } + + private void recycle(Admin admin) { + if (null == admin) { + return; + } + IoUtil.close(admin); + } + +} diff --git a/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/HbaseFactory.java b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/HbaseFactory.java new file mode 100644 index 00000000..c4a66c53 --- /dev/null +++ b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/HbaseFactory.java @@ -0,0 +1,35 @@ +package io.github.dunwu.javadb.hbase; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.HBaseConfiguration; + +import java.io.IOException; + +/** + * HBase 工具实例化工厂 + * + * @author Zhang Peng + * @date 2023-07-05 + */ +public class HbaseFactory { + + public static HbaseTemplate newHbaseTemplate() throws IOException { + return HbaseTemplate.newInstance(newHbaseConfiguration()); + } + + public static HbaseAdmin newHbaseAdmin() throws IOException { + return HbaseAdmin.newInstance(newHbaseConfiguration()); + } + + public static Configuration newHbaseConfiguration() { + Configuration configuration = HBaseConfiguration.create(); + configuration.set("hbase.zookeeper.quorum", "127.0.0.1"); + configuration.set("hbase.zookeeper.property.clientPort", "2181"); + configuration.set("hbase.rootdir", "/hbase"); + configuration.set("hbase.meta.replicas.use", "true"); + configuration.set("hbase.client.retries.number", "5"); + configuration.set("hbase.rpc.timeout", "600000"); + return configuration; + } + +} diff --git a/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/HbaseTemplate.java b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/HbaseTemplate.java new file mode 100644 index 00000000..018bbddd --- /dev/null +++ b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/HbaseTemplate.java @@ -0,0 +1,1011 @@ +package io.github.dunwu.javadb.hbase; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.ReflectUtil; +import cn.hutool.core.util.StrUtil; +import io.github.dunwu.javadb.hbase.entity.BaseHbaseEntity; +import io.github.dunwu.javadb.hbase.entity.common.ColumnDo; +import io.github.dunwu.javadb.hbase.entity.common.FamilyDo; +import io.github.dunwu.javadb.hbase.entity.common.PageData; +import io.github.dunwu.javadb.hbase.entity.common.RowDo; +import io.github.dunwu.javadb.hbase.entity.common.ScrollData; +import io.github.dunwu.javadb.hbase.entity.scan.MultiFamilyScan; +import io.github.dunwu.javadb.hbase.entity.scan.SingleFamilyScan; +import io.github.dunwu.javadb.hbase.util.JsonUtil; +import lombok.extern.slf4j.Slf4j; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.Cell; +import org.apache.hadoop.hbase.CellUtil; +import org.apache.hadoop.hbase.TableName; +import org.apache.hadoop.hbase.client.Connection; +import org.apache.hadoop.hbase.client.ConnectionFactory; +import org.apache.hadoop.hbase.client.Delete; +import org.apache.hadoop.hbase.client.Durability; +import org.apache.hadoop.hbase.client.Get; +import org.apache.hadoop.hbase.client.Put; +import org.apache.hadoop.hbase.client.Result; +import org.apache.hadoop.hbase.client.ResultScanner; +import org.apache.hadoop.hbase.client.Row; +import org.apache.hadoop.hbase.client.Scan; +import org.apache.hadoop.hbase.client.Table; +import org.apache.hadoop.hbase.security.User; +import org.apache.hadoop.hbase.util.Bytes; +import org.apache.hadoop.security.UserGroupInformation; + +import java.io.Closeable; +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Date; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * HBase 客户端封装工具类 + * + * @author Zhang Peng + * @date 2023-03-27 + */ +@Slf4j +public class HbaseTemplate implements Closeable { + + private final Connection connection; + + private final Configuration configuration; + + protected HbaseTemplate(Configuration configuration) throws IOException { + this.configuration = configuration; + // 无需鉴权连接 + // this.connection = ConnectionFactory.createConnection(configuration); + // 鉴权连接 + this.connection = ConnectionFactory.createConnection(configuration, null, + new User.SecureHadoopUser(UserGroupInformation.createRemoteUser("test"))); + } + + protected HbaseTemplate(Connection connection) { + this.configuration = connection.getConfiguration(); + this.connection = connection; + } + + public static synchronized HbaseTemplate newInstance(Configuration configuration) throws IOException { + if (configuration == null) { + throw new IllegalArgumentException("configuration can not be null!"); + } + return new HbaseTemplate(configuration); + } + + public synchronized static HbaseTemplate newInstance(Connection connection) { + if (connection == null) { + throw new IllegalArgumentException("connection can not be null!"); + } + return new HbaseTemplate(connection); + } + + /** + * 关闭内部持有的 HBase Connection 实例 + */ + @Override + public synchronized void close() { + if (null == connection || connection.isClosed()) { + return; + } + IoUtil.close(connection); + } + + /** + * 获取 HBase 连接实例 + * + * @return / + */ + public Connection getConnection() { + if (null == connection) { + throw new RuntimeException("HBase connection init failed..."); + } + return connection; + } + + /** + * 获取 HBase 配置 + * + * @return / + */ + public Configuration getConfiguration() { + return configuration; + } + + /** + * 获取 {@link org.apache.hadoop.hbase.client.Table} 实例 + * + * @param tableName 表名 + * @return / + */ + public Table getTable(String tableName) throws IOException { + return getTable(TableName.valueOf(tableName)); + } + + /** + * 获取 {@link org.apache.hadoop.hbase.client.Table} 实例 + * + * @param tableName 表名 + * @return / + */ + + public synchronized Table getTable(TableName tableName) throws IOException { + return connection.getTable(tableName); + } + + // ===================================================================================== + // put 操作封装 + // ===================================================================================== + + public void put(String tableName, Put put) throws IOException { + if (StrUtil.isBlank(tableName) || put == null) { + return; + } + Table table = getTable(tableName); + try { + table.put(put); + } finally { + recycle(table); + } + } + + public void put(String tableName, String row, String family, String column, String value) + throws IOException { + Put put = newPut(row, null, family, column, value); + put(tableName, put); + } + + public void put(String tableName, String row, Long timestamp, String family, String column, String value) + throws IOException { + Put put = newPut(row, timestamp, family, column, value); + put(tableName, put); + } + + public void put(String tableName, String row, String family, Object obj) throws IOException { + put(tableName, row, null, family, obj); + } + + public void put(String tableName, String row, Long timestamp, String family, Object obj) throws IOException { + Put put = newPut(row, timestamp, family, obj); + put(tableName, put); + } + + public void put(String tableName, String row, String family, Map columnMap) + throws IOException { + Put put = newPut(row, null, family, columnMap); + put(tableName, put); + } + + public void put(String tableName, String row, Long timestamp, String family, Map columnMap) + throws IOException { + Put put = newPut(row, timestamp, family, columnMap); + put(tableName, put); + } + + public void put(String tableName, String row, Long timestamp, Map> familyMap) + throws IOException { + Put put = newPut(row, timestamp, familyMap); + put(tableName, put); + } + + public void put(String tableName, String family, T entity) throws IOException { + put(tableName, entity.getRowKey(), family, entity); + } + + public void batchPut(String tableName, Collection list) throws IOException, InterruptedException { + batch(tableName, list); + } + + public void batchPut(String tableName, String family, Collection list) + throws IOException, InterruptedException { + if (StrUtil.isBlank(tableName) || StrUtil.isBlank(family) || CollectionUtil.isEmpty(list)) { + return; + } + List puts = newPutList(family, list); + batchPut(tableName, puts); + } + + public static Put newPut(String row, Long timestamp, String family, String column, String value) { + if (StrUtil.isBlank(row) || StrUtil.isBlank(family) || StrUtil.isBlank(column) || StrUtil.isBlank(value)) { + return null; + } + Map columnMap = new LinkedHashMap<>(1); + columnMap.put(column, value); + return newPut(row, timestamp, family, columnMap); + } + + public static Put newPut(String row, Long timestamp, String family, Map columnMap) { + if (StrUtil.isBlank(row) || StrUtil.isBlank(family) || MapUtil.isEmpty(columnMap)) { + return null; + } + Map> familyMap = new LinkedHashMap<>(1); + familyMap.put(family, columnMap); + return newPut(row, timestamp, familyMap); + } + + public static Put newPut(String row, Long timestamp, String family, Object obj) { + if (obj == null) { + return null; + } + Map columnMap = JsonUtil.toMap(obj); + return newPut(row, timestamp, family, columnMap); + } + + public static Put newPut(String row, Long timestamp, Map> familyMap) { + + if (StrUtil.isBlank(row) || MapUtil.isEmpty(familyMap)) { + return null; + } + + if (timestamp == null) { + timestamp = System.currentTimeMillis(); + } + + Put put = new Put(Bytes.toBytes(row)); + for (Map.Entry> e : familyMap.entrySet()) { + String family = e.getKey(); + Map columnMap = e.getValue(); + if (MapUtil.isNotEmpty(columnMap)) { + for (Map.Entry entry : columnMap.entrySet()) { + String column = entry.getKey(); + Object value = entry.getValue(); + if (ObjectUtil.isEmpty(value)) { + continue; + } + if (value instanceof String) { + put.addColumn(Bytes.toBytes(family), Bytes.toBytes(column), timestamp, + Bytes.toBytes(value.toString())); + } else if (value instanceof Date) { + put.addColumn(Bytes.toBytes(family), Bytes.toBytes(column), timestamp, + Bytes.toBytes(DateUtil.format((Date) value, DatePattern.NORM_DATETIME_PATTERN))); + } else { + put.addColumn(Bytes.toBytes(family), Bytes.toBytes(column), + timestamp, Bytes.toBytes(JsonUtil.toString(value))); + } + } + } + } + return put; + } + + private static List newPutList(String family, Collection list) { + long timestamp = System.currentTimeMillis(); + List puts = new ArrayList<>(); + for (T entity : list) { + Put put = newPut(entity.getRowKey(), timestamp, family, entity); + puts.add(put); + } + return puts; + } + + // ===================================================================================== + // delete 操作封装 + // ===================================================================================== + + public void delete(String tableName, Delete delete) throws IOException { + if (StrUtil.isBlank(tableName) || delete == null) { + return; + } + Table table = getTable(tableName); + try { + table.delete(delete); + } finally { + recycle(table); + } + } + + public void delete(String tableName, String row) throws IOException { + Delete delete = new Delete(Bytes.toBytes(row)); + delete(tableName, delete); + } + + public void batchDelete(String tableName, String... rows) throws IOException, InterruptedException { + if (ArrayUtil.isEmpty(rows)) { + return; + } + List deletes = Stream.of(rows) + .map(row -> new Delete(Bytes.toBytes(row))) + .distinct().collect(Collectors.toList()); + batchDelete(tableName, deletes); + } + + public void batchDelete(String tableName, List deletes) throws IOException, InterruptedException { + batch(tableName, deletes); + } + + // ===================================================================================== + // get 操作封装 + // ===================================================================================== + + public Result get(String tableName, String row) throws IOException { + if (StrUtil.isBlank(tableName) || StrUtil.isBlank(row)) { + return null; + } + Get get = newGet(row); + return get(tableName, get); + } + + public Result get(String tableName, Get get) throws IOException { + if (StrUtil.isBlank(tableName) || get == null) { + return null; + } + Table table = getTable(tableName); + try { + return table.get(get); + } finally { + recycle(table); + } + } + + public Result[] batchGet(String tableName, String[] rows) throws IOException { + if (StrUtil.isBlank(tableName) || ArrayUtil.isEmpty(rows)) { + return null; + } + List gets = newGetList(rows); + return batchGet(tableName, gets); + } + + public Result[] batchGet(String tableName, List gets) throws IOException { + if (StrUtil.isBlank(tableName) || CollectionUtil.isEmpty(gets)) { + return null; + } + Table table = getTable(tableName); + try { + return table.get(gets); + } finally { + recycle(table); + } + } + + /** + * 指定行、列族,以实体 {@link T} 形式返回数据 + * + * @param tableName 表名 + * @param row 指定行 + * @param family 列族 + * @param clazz 返回实体类型 + * @param 实体类型 + * @return / + */ + public T getEntity(String tableName, String row, String family, Class clazz) throws IOException { + + if (StrUtil.isBlank(tableName) || StrUtil.isBlank(row) || StrUtil.isBlank(family) || clazz == null) { + return null; + } + Map fieldMap = ReflectUtil.getFieldMap(clazz); + String[] columns = fieldMap.keySet().toArray(new String[0]); + Map columnMap = getColumnMap(tableName, row, family, columns); + if (MapUtil.isEmpty(columnMap)) { + return null; + } + return toEntity(ColumnDo.toKvMap(columnMap), clazz); + } + + /** + * 指定多行、列族,以实体 {@link T} 列表形式返回数据 + * + * @param tableName 表名 + * @param rows 指定多行 + * @param family 列族 + * @param clazz 返回实体类型 + * @param 实体类型 + * @return / + */ + public List getEntityList(String tableName, String[] rows, String family, Class clazz) + throws IOException { + Map map = getEntityMap(tableName, rows, family, clazz); + if (MapUtil.isEmpty(map)) { + return new ArrayList<>(0); + } + return new ArrayList<>(map.values()); + } + + /** + * 指定多行、列族,以实体 {@link T} 列表形式返回数据 + * + * @param tableName 表名 + * @param rows 指定多行 + * @param family 列族 + * @param clazz 返回实体类型 + * @param 实体类型 + * @return / + */ + public List getEntityList(String tableName, Collection rows, String family, Class clazz) + throws IOException { + if (CollectionUtil.isEmpty(rows)) { + return new ArrayList<>(0); + } + return getEntityList(tableName, rows.toArray(new String[0]), family, clazz); + } + + public Map getEntityMap(String tableName, String[] rows, String family, Class clazz) + throws IOException { + + if (StrUtil.isBlank(tableName) || ArrayUtil.isEmpty(rows) || StrUtil.isBlank(family) || clazz == null) { + return null; + } + + Map fieldMap = ReflectUtil.getFieldMap(clazz); + String[] columns = fieldMap.keySet().toArray(new String[0]); + List gets = newGetList(rows, family, columns); + + Result[] results = batchGet(tableName, gets); + if (ArrayUtil.isEmpty(results)) { + return new LinkedHashMap<>(0); + } + + Map map = new LinkedHashMap<>(results.length); + for (Result result : results) { + Map columnMap = + getColumnsFromResult(result, tableName, family, CollectionUtil.newArrayList(columns)); + if (MapUtil.isNotEmpty(columnMap)) { + T entity = toEntity(ColumnDo.toKvMap(columnMap), clazz); + map.put(Bytes.toString(result.getRow()), entity); + } + } + return map; + } + + public Map getEntityMap(String tableName, Collection rows, String family, Class clazz) + throws IOException { + if (CollectionUtil.isEmpty(rows)) { + return new LinkedHashMap<>(0); + } + return getEntityMap(tableName, rows.toArray(new String[0]), family, clazz); + } + + /** + * 查询列信息 + * + * @param tableName 表名 + * @param row 指定行 + * @param family 列族 + * @param column 列 + * @return / + */ + public ColumnDo getColumn(String tableName, String row, String family, String column) throws IOException { + + if (StrUtil.isBlank(tableName) || StrUtil.isBlank(row) || StrUtil.isBlank(family) || StrUtil.isBlank(column)) { + return null; + } + + Result result = get(tableName, row); + if (result == null) { + return null; + } + + return getColumnFromResult(result, tableName, family, column); + } + + /** + * 查询多列信息 + * + * @param tableName 表名 + * @param row 指定行 + * @param family 列族 + * @param columns 指定列 + * @return / + */ + public Map getColumnMap(String tableName, String row, String family, String... columns) + throws IOException { + + if (StrUtil.isBlank(tableName) || StrUtil.isBlank(row) || StrUtil.isBlank(family)) { + return null; + } + + Get get = newGet(row, family, columns); + Result result = get(tableName, get); + if (result == null) { + return null; + } + return getColumnsFromResult(result, tableName, family, Arrays.asList(columns)); + } + + /** + * 查询列族信息 + * + * @param tableName 表名 + * @param row 指定行 + * @param family 指定列族 + * @return / + */ + public FamilyDo getFamily(String tableName, String row, String family) throws IOException { + Map columnMap = getColumnMap(tableName, row, family); + if (MapUtil.isEmpty(columnMap)) { + return null; + } + return new FamilyDo(tableName, row, family, columnMap); + } + + /** + * 查询多列族信息 + * + * @param tableName 表名 + * @param row 指定行 + * @param familyColumnMap <列族, 要查询的列> + * @return / + */ + public Map getFamilyMap(String tableName, String row, + Map> familyColumnMap) throws IOException { + + if (StrUtil.isBlank(tableName) || StrUtil.isBlank(row)) { + return new LinkedHashMap<>(0); + } + + if (MapUtil.isEmpty(familyColumnMap)) { + RowDo rowDo = getRow(tableName, row); + if (rowDo == null) { + return new LinkedHashMap<>(0); + } + return rowDo.getFamilyMap(); + } + + Get get = newGet(row); + for (Map.Entry> entry : familyColumnMap.entrySet()) { + String family = entry.getKey(); + Collection columns = entry.getValue(); + if (CollectionUtil.isNotEmpty(columns)) { + for (String column : columns) { + get.addColumn(Bytes.toBytes(family), Bytes.toBytes(column)); + } + } + } + Result result = get(tableName, get); + if (result == null) { + return null; + } + + return getFamiliesFromResult(result, tableName, familyColumnMap); + } + + /** + * 查询行信息 + * + * @param tableName 表名 + * @param row 指定行 + * @return / + */ + public RowDo getRow(String tableName, String row) throws IOException { + if (StrUtil.isBlank(tableName) || StrUtil.isBlank(row)) { + return null; + } + Result result = get(tableName, row); + if (result == null) { + return null; + } + return getRowFromResult(result, tableName); + } + + /** + * 查询多行信息 + * + * @param tableName 表名 + * @param rows 指定多行 + * @return / + */ + public Map getRowMap(String tableName, String... rows) throws IOException { + if (StrUtil.isBlank(tableName) || ArrayUtil.isEmpty(rows)) { + return null; + } + Result[] results = batchGet(tableName, rows); + if (ArrayUtil.isEmpty(results)) { + return new LinkedHashMap<>(0); + } + Map map = new LinkedHashMap<>(results.length); + for (Result result : results) { + String row = Bytes.toString(result.getRow()); + RowDo rowDo = getRowFromResult(result, tableName); + map.put(row, rowDo); + } + return map; + } + + private static Get newGet(String row) { + return new Get(Bytes.toBytes(row)); + } + + private static Get newGet(String row, String family, String... columns) { + Get get = newGet(row); + get.addFamily(Bytes.toBytes(family)); + if (ArrayUtil.isNotEmpty(columns)) { + for (String column : columns) { + get.addColumn(Bytes.toBytes(family), Bytes.toBytes(column)); + } + } + return get; + } + + private static List newGetList(String[] rows) { + if (ArrayUtil.isEmpty(rows)) { + return new ArrayList<>(); + } + return Stream.of(rows).map(HbaseTemplate::newGet).collect(Collectors.toList()); + } + + private static List newGetList(String[] rows, String family, String[] columns) { + if (ArrayUtil.isEmpty(rows)) { + return new ArrayList<>(); + } + return Stream.of(rows).map(row -> newGet(row, family, columns)).collect(Collectors.toList()); + } + + // ===================================================================================== + // scan 操作封装 + // ===================================================================================== + + /** + * 返回匹配 {@link org.apache.hadoop.hbase.client.Scan} 的所有列族的数据 + * + * @param tableName 表名 + * @param scan {@link org.apache.hadoop.hbase.client.Scan} 实体 + * @return / + */ + public Result[] scan(String tableName, Scan scan) throws IOException { + Table table = getTable(tableName); + ResultScanner scanner = null; + try { + scanner = table.getScanner(scan); + return ArrayUtil.toArray(scanner, Result.class); + } finally { + IoUtil.close(scanner); + recycle(table); + } + } + + public PageData page(SingleFamilyScan scan) throws IOException { + if (scan == null) { + return null; + } + return getPageData(scan.getTableName(), scan.getPage(), scan.getSize(), scan.toScan(), + scan.getFamilyColumnMap()); + } + + public PageData page(MultiFamilyScan scan) throws IOException { + if (scan == null) { + return null; + } + return getPageData(scan.getTableName(), scan.getPage(), scan.getSize(), scan.toScan(), + scan.getFamilyColumnMap()); + } + + public ScrollData scroll(SingleFamilyScan scan) throws IOException { + if (scan == null) { + return null; + } + return getScrollData(scan.getTableName(), scan.getSize(), scan.toScan(), scan.getFamilyColumnMap()); + } + + public ScrollData scroll(MultiFamilyScan scan) throws IOException { + if (scan == null) { + return null; + } + return getScrollData(scan.getTableName(), scan.getSize(), scan.toScan(), scan.getFamilyColumnMap()); + } + + public PageData getEntityPage(SingleFamilyScan scan, Class clazz) throws IOException { + + Map fieldMap = ReflectUtil.getFieldMap(clazz); + Set columns = fieldMap.keySet(); + scan.setColumns(columns); + + PageData data = page(scan); + if (data == null || CollectionUtil.isEmpty(data.getContent())) { + return new PageData<>(scan.getPage(), scan.getSize(), 0L, new ArrayList<>()); + } + + List list = data.getContent().stream().map(rowDo -> { + Map> familyKvMap = rowDo.getFamilyKvMap(); + Map columnKvMap = familyKvMap.get(scan.getFamily()); + return toEntity(columnKvMap, clazz); + }).collect(Collectors.toList()); + return new PageData<>(scan.getPage(), scan.getSize(), data.getTotal(), list); + } + + public ScrollData getEntityScroll(SingleFamilyScan scan, Class clazz) throws IOException { + + Map fieldMap = ReflectUtil.getFieldMap(clazz); + Set columns = fieldMap.keySet(); + scan.setColumns(columns); + + ScrollData data = scroll(scan); + if (data == null || CollectionUtil.isEmpty(data.getContent())) { + return new ScrollData<>(scan.getStartRow(), scan.getStopRow(), null, 0, new ArrayList<>()); + } + + List list = data.getContent().stream().map(rowDo -> { + Map> familyKvMap = rowDo.getFamilyKvMap(); + Map columnKvMap = familyKvMap.get(scan.getFamily()); + return toEntity(columnKvMap, clazz); + }).collect(Collectors.toList()); + return new ScrollData<>(data.getStartRow(), data.getStopRow(), data.getScrollRow(), 0, list); + } + + public ScrollData getEntityScroll(String tableName, String family, String scrollRow, int size, + Class clazz) throws IOException { + SingleFamilyScan scan = new SingleFamilyScan(); + scan.setFamily(family) + .setScrollRow(scrollRow) + .setTableName(tableName) + .setSize(size) + .setReversed(false); + return getEntityScroll(scan, clazz); + } + + private PageData getPageData(String tableName, Integer page, Integer size, Scan scan, + Map> familyColumnMap) throws IOException { + Table table = getTable(tableName); + Map rowMap = new LinkedHashMap<>(size); + try { + int pageIndex = 1; + byte[] lastRow = null; + long total = 0L; + while (true) { + if (lastRow != null) { + scan.withStartRow(lastRow, false); + } + ResultScanner rs = table.getScanner(scan); + Iterator it = rs.iterator(); + int count = 0; + while (it.hasNext()) { + Result result = it.next(); + if (page == pageIndex) { + RowDo rowDo = getRowFromResult(result, tableName, familyColumnMap); + if (rowDo != null) { + rowMap.put(rowDo.getRow(), rowDo); + } + } + lastRow = result.getRow(); + count++; + } + + pageIndex++; + rs.close(); + total += count; + if (count == 0) { + break; + } + } + return new PageData<>(page, size, total, rowMap.values()); + } finally { + recycle(table); + } + } + + private ScrollData getScrollData(String tableName, int size, Scan scan, + Map> familyColumnMap) throws IOException { + Table table = getTable(tableName); + ResultScanner scanner = null; + Map rowMap = new LinkedHashMap<>(size); + try { + scanner = table.getScanner(scan); + for (Result result : scanner) { + RowDo rowDo = getRowFromResult(result, tableName, familyColumnMap); + if (rowDo != null) { + rowMap.put(rowDo.getRow(), rowDo); + } + } + + String scrollRow = null; + if (MapUtil.isNotEmpty(rowMap)) { + List rows = rowMap.values().stream() + .map(RowDo::getRow) + .collect(Collectors.toList()); + if (scan.isReversed()) { + scrollRow = CollectionUtil.min(rows); + } else { + scrollRow = CollectionUtil.max(rows); + } + } + return new ScrollData<>(Bytes.toString(scan.getStartRow()), Bytes.toString(scan.getStopRow()), + scrollRow, size, rowMap.values()); + } finally { + IoUtil.close(scanner); + recycle(table); + } + } + + // ===================================================================================== + // 其他操作封装 + // ===================================================================================== + + public long incrementColumnValue(String tableName, String row, String family, String column, long amount) + throws IOException { + return incrementColumnValue(tableName, row, family, column, amount, Durability.SYNC_WAL); + } + + public long incrementColumnValue(String tableName, String row, String family, String column, long amount, + Durability durability) throws IOException { + if (StrUtil.isBlank(tableName) || StrUtil.isBlank(row) || StrUtil.isBlank(family) || StrUtil.isBlank(column)) { + return -1L; + } + Table table = getTable(tableName); + try { + return table.incrementColumnValue(Bytes.toBytes(row), Bytes.toBytes(family), Bytes.toBytes(column), amount, + durability); + } finally { + recycle(table); + } + } + + private void batch(String tableName, Collection list) + throws IOException, InterruptedException { + if (StrUtil.isBlank(tableName) || CollectionUtil.isEmpty(list)) { + return; + } + Object[] results = new Object[list.size()]; + Table table = getTable(tableName); + try { + table.batch(new ArrayList<>(list), results); + } finally { + recycle(table); + } + } + + private void recycle(Table table) { + if (null == table) { + return; + } + IoUtil.close(table); + } + + private static RowDo getRowFromResult(Result result, String tableName) { + + if (result == null || result.isEmpty()) { + return null; + } + + String row = Bytes.toString(result.getRow()); + Map> familyColumnMap = new LinkedHashMap<>(result.size()); + for (Cell cell : result.listCells()) { + String family = Bytes.toString(CellUtil.cloneFamily(cell)); + if (!familyColumnMap.containsKey(family)) { + familyColumnMap.put(family, new LinkedHashMap<>(0)); + } + String column = Bytes.toString(CellUtil.cloneQualifier(cell)); + ColumnDo columnDo = getColumnFromResult(result, tableName, family, column); + familyColumnMap.get(family).put(column, columnDo); + } + + Map familyMap = new LinkedHashMap<>(familyColumnMap.size()); + familyColumnMap.forEach((family, columnMap) -> { + FamilyDo familyDo = new FamilyDo(tableName, row, family, columnMap); + familyMap.put(family, familyDo); + }); + if (MapUtil.isEmpty(familyMap)) { + return null; + } + return new RowDo(tableName, row, familyMap); + } + + private static RowDo getRowFromResult(Result result, String tableName, + Map> familyColumnMap) { + if (MapUtil.isEmpty(familyColumnMap)) { + return getRowFromResult(result, tableName); + } + String row = Bytes.toString(result.getRow()); + Map familyMap = getFamiliesFromResult(result, tableName, familyColumnMap); + if (MapUtil.isEmpty(familyMap)) { + return null; + } + return new RowDo(tableName, row, familyMap); + } + + private static FamilyDo getFamilyFromResult(Result result, String tableName, String family) { + + if (result == null || result.isEmpty()) { + return null; + } + + RowDo rowDo = getRowFromResult(result, tableName); + if (rowDo == null || MapUtil.isEmpty(rowDo.getFamilyMap())) { + return null; + } + return rowDo.getFamilyMap().get(family); + } + + private static Map getFamiliesFromResult(Result result, String tableName, + Map> familyColumnMap) { + + if (result == null || StrUtil.isBlank(tableName) || MapUtil.isEmpty(familyColumnMap)) { + return new LinkedHashMap<>(0); + } + + String row = Bytes.toString(result.getRow()); + Map familyMap = new LinkedHashMap<>(familyColumnMap.size()); + familyColumnMap.forEach((family, columns) -> { + FamilyDo familyDo; + if (CollectionUtil.isNotEmpty(columns)) { + Map columnMap = new LinkedHashMap<>(columns.size()); + for (String column : columns) { + ColumnDo columnDo = getColumnFromResult(result, tableName, family, column); + columnMap.put(column, columnDo); + } + familyDo = new FamilyDo(tableName, row, family, columnMap); + } else { + familyDo = getFamilyFromResult(result, tableName, family); + } + familyMap.put(family, familyDo); + }); + return familyMap; + } + + private static ColumnDo getColumnFromResult(Result result, String tableName, String family, String column) { + + if (result == null || StrUtil.isBlank(tableName) || StrUtil.isBlank(family) || StrUtil.isBlank(column)) { + return null; + } + + Cell cell = result.getColumnLatestCell(Bytes.toBytes(family), Bytes.toBytes(column)); + if (cell == null) { + return null; + } + String row = Bytes.toString(result.getRow()); + String value = Bytes.toString(CellUtil.cloneValue(cell)); + long timestamp = cell.getTimestamp(); + return new ColumnDo(tableName, row, family, timestamp, column, value); + } + + private static Map getColumnsFromResult(Result result, String tableName, String family, + Collection columns) { + if (CollectionUtil.isEmpty(columns)) { + RowDo rowDo = getRowFromResult(result, tableName); + if (rowDo == null) { + return new LinkedHashMap<>(0); + } + return rowDo.getFamilyMap().get(family).getColumnMap(); + } + Map columnMap = new LinkedHashMap<>(columns.size()); + for (String column : columns) { + ColumnDo columnDo = getColumnFromResult(result, tableName, family, column); + if (columnDo != null) { + columnMap.put(column, columnDo); + } + } + return columnMap; + } + + private static T toEntity(Map kvMap, Class clazz) { + + if (MapUtil.isEmpty(kvMap)) { + return null; + } + + MapUtil.removeNullValue(kvMap); + T obj; + try { + Map> typeMap = new LinkedHashMap<>(); + Field[] fields = ReflectUtil.getFields(clazz); + for (Field f : fields) { + typeMap.put(f.getName(), f.getType()); + } + obj = clazz.newInstance(); + for (Map.Entry entry : kvMap.entrySet()) { + String key = entry.getKey(); + String value = entry.getValue(); + Class filedType = typeMap.get(key); + if (filedType != null) { + Object fieldObj = JsonUtil.toBean(value, filedType); + ReflectUtil.setFieldValue(obj, key, fieldObj); + } + } + return obj; + } catch (InstantiationException | IllegalAccessException e) { + throw new RuntimeException(e); + } + } + +} diff --git a/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/annotation/RowKeyRule.java b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/annotation/RowKeyRule.java new file mode 100644 index 00000000..d19e3dc2 --- /dev/null +++ b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/annotation/RowKeyRule.java @@ -0,0 +1,42 @@ +package io.github.dunwu.javadb.hbase.annotation; + +import io.github.dunwu.javadb.hbase.constant.RowType; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 表主键标识 + * + * @author Zhang Peng + * @date 2023-11-17 + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE }) +public @interface RowKeyRule { + + /** + * 唯一索引的 get 方法 + */ + String uk(); + + /** + * 主键类型 {@link RowType} + */ + RowType type() default RowType.ORIGIN_ID; + + /** + * 原 ID 长度,type 为 {@link RowType#ORIGIN_ID} 或 {@link RowType#BUCKET} 时必填 + */ + int length() default 0; + + /** + * 分桶数,type 为 {@link RowType#BUCKET} 时,才需要且必须指定 + */ + int bucket() default 0; + +} diff --git a/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/annotation/RowKeyUtil.java b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/annotation/RowKeyUtil.java new file mode 100644 index 00000000..712249c6 --- /dev/null +++ b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/annotation/RowKeyUtil.java @@ -0,0 +1,142 @@ +package io.github.dunwu.javadb.hbase.annotation; + +import cn.hutool.core.util.HashUtil; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.ReflectUtil; +import cn.hutool.core.util.StrUtil; +import io.github.dunwu.javadb.hbase.constant.RowType; +import io.github.dunwu.javadb.hbase.entity.BaseHbaseEntity; + +import java.lang.reflect.Method; + +/** + * {@link RowKeyRule} 解析器 + * + * @author Zhang Peng + * @date 2023-11-20 + */ +public class RowKeyUtil { + + /** + * 获取主键 + */ + public static String getRowKey(T entity) throws IllegalArgumentException { + + String row = null; + Class clazz = entity.getClass(); + RowKeyRule rule = getRowKeyRule(entity.getClass()); + Method method = ReflectUtil.getMethodByName(clazz, rule.uk()); + if (method == null) { + String msg = StrUtil.format("{} 实体类定义错误!@RowKeyRule 指定的 uk:{} 方法未找到!", + clazz.getCanonicalName(), rule.uk()); + throw new IllegalArgumentException(msg); + } + switch (rule.type()) { + case ORIGIN_ID: + row = getRowKeyForOriginId(entity, method, rule.length()); + break; + case TIMESTAMP: + row = getRowKeyForTimestamp(); + break; + case UUID: + row = IdUtil.fastSimpleUUID(); + break; + case BUCKET: + row = getRowKeyForBucket(entity, method, rule.length(), rule.bucket()); + default: + break; + } + + if (StrUtil.isBlank(row)) { + throw new IllegalArgumentException(StrUtil.format("实体定义错误!未定义 @RowKeyRule", entity.getClass(), + BaseHbaseEntity.class.getCanonicalName())); + } + return row; + } + + public static RowKeyRule getRowKeyRule(Class clazz) { + + RowKeyRule rule = clazz.getAnnotation(RowKeyRule.class); + + if (rule == null) { + String msg = StrUtil.format("{} 实体类定义错误!未定义 @RowKeyRule", clazz.getCanonicalName()); + throw new IllegalArgumentException(msg); + } + + if (rule.type() == RowType.ORIGIN_ID && rule.length() <= 0) { + String msg = StrUtil.format("{} 实体类定义错误!@RowKeyRule type 为 ORIGIN_ID 时,length 必须大于 0!", + clazz.getCanonicalName()); + throw new IllegalArgumentException(msg); + } + + if (rule.type() == RowType.BUCKET && (rule.length() <= 0 || rule.bucket() <= 0)) { + String msg = StrUtil.format("{} 实体类定义错误!@RowKeyRule type 为 BUCKET 时,length 和 bucket 必须大于 0!", + clazz.getCanonicalName()); + throw new IllegalArgumentException(msg); + } + return rule; + } + + public static String getRowKeyForOriginId(T entity, Method method, int length) + throws IllegalArgumentException { + String originId; + Object value = ReflectUtil.invoke(entity, method); + if (value instanceof String) { + originId = (String) value; + } else { + originId = String.valueOf(value); + } + if (length == 0) { + throw new IllegalArgumentException("length 不能为 0"); + } + return getRowKeyForOriginId(originId, length); + } + + public static String getRowKeyForOriginId(String bizId, int length) { + return StrUtil.padPre(bizId, length, "0"); + } + + public static String getRowKeyForTimestamp() { + String timestamp = String.valueOf(System.currentTimeMillis() / 1000); + return StrUtil.padPre(timestamp, 10, "0"); + } + + public static String getRowKeyForBucket(T entity, Method method, int length, int bucket) + throws IllegalArgumentException { + if (bucket == 0) { + throw new IllegalArgumentException("bucket 不能为 0"); + } + + String originId = getRowKeyForOriginId(entity, method, length); + int bucketLength = getBucketIdLength(bucket); + String bucketId = String.valueOf(HashUtil.fnvHash(originId) % bucket); + return StrUtil.padPre(bucketId, bucketLength, "0") + originId; + } + + public static String getRowKeyForBucket(String contentId, Class clazz) { + RowKeyRule rule = RowKeyUtil.getRowKeyRule(clazz); + return RowKeyUtil.getRowKeyForBucket(contentId, rule.length(), rule.bucket()); + } + + public static String getRowKeyForBucket(String bizId, int length, int bucket) throws IllegalArgumentException { + String originId = getRowKeyForOriginId(bizId, length); + int bucketLength = getBucketIdLength(bucket); + String bucketId = String.valueOf(HashUtil.fnvHash(originId) % bucket); + return StrUtil.padPre(bucketId, bucketLength, "0") + originId; + } + + private static int getBucketIdLength(int bucket) { + bucket = bucket - 1; + if (bucket <= 0) { + return 1; + } + + int length = 0; + while (bucket > 0) { + length++; + bucket = bucket / 10; + } + return length; + } + +} diff --git a/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/config/EnableHbase.java b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/config/EnableHbase.java new file mode 100644 index 00000000..466cc9c1 --- /dev/null +++ b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/config/EnableHbase.java @@ -0,0 +1,26 @@ +package io.github.dunwu.javadb.hbase.config; + +import org.springframework.context.annotation.EnableAspectJAutoProxy; +import org.springframework.context.annotation.Import; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 启动 HBase 配置注解 + * + * @author Zhang Peng + * @date 2023-06-30 + */ +@Target({ ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +@EnableAspectJAutoProxy( + proxyTargetClass = false +) +@Import({ HbaseConfiguration.class }) +@Documented +public @interface EnableHbase { +} diff --git a/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/config/HbaseConfiguration.java b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/config/HbaseConfiguration.java new file mode 100644 index 00000000..1b5cb0d2 --- /dev/null +++ b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/config/HbaseConfiguration.java @@ -0,0 +1,29 @@ +package io.github.dunwu.javadb.hbase.config; + +import io.github.dunwu.javadb.hbase.HbaseAdmin; +import io.github.dunwu.javadb.hbase.HbaseFactory; +import io.github.dunwu.javadb.hbase.HbaseTemplate; +import org.springframework.context.annotation.Bean; + +import java.io.IOException; + +/** + * HBase 启动配置 + * + * @author Zhang Peng + * @date 2023-07-04 + */ +@org.springframework.context.annotation.Configuration +public class HbaseConfiguration { + + @Bean("hbaseTemplate") + public HbaseTemplate hbaseTemplate() throws IOException { + return HbaseFactory.newHbaseTemplate(); + } + + @Bean("hbaseAdmin") + public HbaseAdmin hbaseAdmin() throws IOException { + return HbaseFactory.newHbaseAdmin(); + } + +} diff --git a/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/constant/RowType.java b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/constant/RowType.java new file mode 100644 index 00000000..d5a49ab0 --- /dev/null +++ b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/constant/RowType.java @@ -0,0 +1,43 @@ +package io.github.dunwu.javadb.hbase.constant; + +import lombok.Getter; + +/** + * 生成ID类型枚举类 + * + * @author Zhang Peng + * @date 2023-11-17 + */ +@Getter +public enum RowType { + + /** + * 原 ID + */ + ORIGIN_ID(1), + + /** + * 以 10 位的时间戳(秒级)作为 ID + *

+ * 特点:数据存储保证单调递增,适用于 scan 为主,且数据量不大(100w以内),读频率不高的业务场景。 + */ + TIMESTAMP(2), + + /** + * UUID作为主键,适合数据量较大,且以 get 为主的场景(尽量保证数据存储离散) + */ + UUID(3), + + /** + * ID = bucket(2/3) + timestamp(10) + bizId,适合数据量较大,且需要大量 scan 的场景 + *

+ * 注:如果选择此 ID 类型,必须在 @TableId 中指定分桶数 + */ + BUCKET(4); + + private final int key; + + RowType(int key) { + this.key = key; + } +} \ No newline at end of file diff --git a/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/entity/BaseHbaseContentEntity.java b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/entity/BaseHbaseContentEntity.java new file mode 100644 index 00000000..4b32fa86 --- /dev/null +++ b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/entity/BaseHbaseContentEntity.java @@ -0,0 +1,12 @@ +package io.github.dunwu.javadb.hbase.entity; + +import io.github.dunwu.javadb.hbase.mapper.UkGetter; + +/** + * HBase 基础 Content 实体 + * + * @author Zhang Peng + * @date 2023-11-24 + */ +public interface BaseHbaseContentEntity extends UkGetter, BaseHbaseEntity { +} diff --git a/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/entity/BaseHbaseEntity.java b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/entity/BaseHbaseEntity.java new file mode 100644 index 00000000..5a9cbf86 --- /dev/null +++ b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/entity/BaseHbaseEntity.java @@ -0,0 +1,22 @@ +package io.github.dunwu.javadb.hbase.entity; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import io.github.dunwu.javadb.hbase.annotation.RowKeyUtil; + +/** + * HBase 基础实体 + * + * @author Zhang Peng + * @date 2023-11-15 + */ +public interface BaseHbaseEntity { + + /** + * 获取主键 + */ + @JsonIgnore + default String getRowKey() { + return RowKeyUtil.getRowKey(this); + } + +} diff --git a/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/entity/common/ColumnDo.java b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/entity/common/ColumnDo.java new file mode 100644 index 00000000..95167743 --- /dev/null +++ b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/entity/common/ColumnDo.java @@ -0,0 +1,82 @@ +package io.github.dunwu.javadb.hbase.entity.common; + +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.StrUtil; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * HBase 列实体 + * + * @author Zhang Peng + * @date 2023-05-19 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ColumnDo { + + /** 表名 */ + private String tableName; + /** 行 */ + private String row; + /** 列族 */ + private String family; + /** 时间戳 */ + private Long timestamp; + /** 列 */ + private String column; + /** 列值 */ + private String value; + + public boolean check() { + return check(this); + } + + public static boolean check(ColumnDo columnDo) { + return columnDo != null + && StrUtil.isNotBlank(columnDo.getTableName()) + && StrUtil.isNotBlank(columnDo.getRow()) + && StrUtil.isNotBlank(columnDo.getFamily()) + && StrUtil.isNotEmpty(columnDo.getColumn()); + } + + public static Map toColumnMap(String tableName, String row, String family, + Map columnValueMap) { + if (MapUtil.isEmpty(columnValueMap)) { + return new HashMap<>(0); + } + Map map = new HashMap<>(columnValueMap.size()); + columnValueMap.forEach((column, value) -> { + ColumnDo columnDo = new ColumnDo(tableName, row, family, null, column, value); + if (columnDo.check()) { + map.put(column, columnDo); + } + }); + return map; + } + + public static Map toKvMap(Map columnMap) { + if (MapUtil.isEmpty(columnMap)) { + return new HashMap<>(0); + } + Collection columns = columnMap.values().stream() + .filter(Objects::nonNull) + .collect(Collectors.toList()); + Map map = new HashMap<>(columns.size()); + for (ColumnDo columnDo : columns) { + if (columnDo.check()) { + map.put(columnDo.getColumn(), columnDo.getValue()); + } + } + return map; + } + +} diff --git a/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/entity/common/FamilyDo.java b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/entity/common/FamilyDo.java new file mode 100644 index 00000000..8d59bd8e --- /dev/null +++ b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/entity/common/FamilyDo.java @@ -0,0 +1,73 @@ +package io.github.dunwu.javadb.hbase.entity.common; + +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.StrUtil; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.HashMap; +import java.util.Map; + +/** + * HBase 列族实体 + * + * @author Zhang Peng + * @date 2023-05-19 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class FamilyDo { + + /** 表名 */ + private String tableName; + /** 行 */ + private String row; + /** 列族 */ + private String family; + /** 列 Map(key 为 column;value 为列详细信息) */ + private Map columnMap; + + public boolean check() { + return check(this); + } + + public Map getColumnKvMap() { + return FamilyDo.getColumnKvMap(this); + } + + public static Map getColumnKvMap(FamilyDo familyDo) { + if (familyDo == null || MapUtil.isEmpty(familyDo.getColumnMap())) { + return new HashMap<>(0); + } + return ColumnDo.toKvMap(familyDo.getColumnMap()); + } + + public static boolean check(FamilyDo familyDo) { + return familyDo != null + && StrUtil.isNotBlank(familyDo.getTableName()) + && StrUtil.isNotBlank(familyDo.getRow()) + && StrUtil.isNotBlank(familyDo.getFamily()) + && MapUtil.isNotEmpty(familyDo.getColumnMap()); + } + + public static Map toFamilyMap(String tableName, String row, + Map> familyColumnValueMap) { + if (MapUtil.isEmpty(familyColumnValueMap)) { + return new HashMap<>(0); + } + + Map familyMap = new HashMap<>(familyColumnValueMap.size()); + familyColumnValueMap.forEach((family, columnMap) -> { + familyMap.put(family, toFamily(tableName, row, family, columnMap)); + }); + return familyMap; + } + + public static FamilyDo toFamily(String tableName, String row, String family, Map columnValueMap) { + Map columnMap = ColumnDo.toColumnMap(tableName, row, family, columnValueMap); + return new FamilyDo(tableName, row, family, columnMap); + } + +} diff --git a/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/entity/common/PageData.java b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/entity/common/PageData.java new file mode 100644 index 00000000..f044aab5 --- /dev/null +++ b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/entity/common/PageData.java @@ -0,0 +1,35 @@ +package io.github.dunwu.javadb.hbase.entity.common; + +import lombok.Data; + +import java.util.Collection; + +/** + * HBase 分页数据实体 + * + * @author Zhang Peng + * @date 2023-05-19 + */ +@Data +public class PageData { + + private Integer page; + private Integer size; + private Long total; + private Integer totalPages; + private Collection content; + + public PageData() { } + + public PageData(Integer page, Integer size, Long total, Collection content) { + this.page = page; + this.size = size; + this.total = total; + this.content = content; + } + + public int getTotalPages() { + return this.getSize() == 0 ? 0 : (int) Math.ceil((double) this.total / (double) this.getSize()); + } + +} diff --git a/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/entity/common/RowDo.java b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/entity/common/RowDo.java new file mode 100644 index 00000000..f9625d3f --- /dev/null +++ b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/entity/common/RowDo.java @@ -0,0 +1,87 @@ +package io.github.dunwu.javadb.hbase.entity.common; + +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.StrUtil; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * HBase 行实体 + * + * @author Zhang Peng + * @date 2023-05-19 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class RowDo { + + /** 表名 */ + private String tableName; + /** 行 */ + private String row; + /** 列族 Map(key 为 family;value 为列族详细信息) */ + private Map familyMap; + + public boolean check() { + return check(this); + } + + public Map> getFamilyKvMap() { + return RowDo.getFamilyKvMap(this); + } + + public static boolean check(RowDo rowDo) { + return rowDo != null + && StrUtil.isNotBlank(rowDo.getTableName()) + && StrUtil.isNotBlank(rowDo.getRow()) + && MapUtil.isNotEmpty(rowDo.getFamilyMap()); + } + + public static Map> getFamilyKvMap(RowDo rowDo) { + if (rowDo == null || MapUtil.isEmpty(rowDo.getFamilyMap())) { + return new HashMap<>(0); + } + Map> kvMap = new HashMap<>(rowDo.getFamilyMap().size()); + rowDo.getFamilyMap().forEach((family, familyDo) -> { + kvMap.put(family, familyDo.getColumnKvMap()); + }); + return kvMap; + } + + public static Map toRowMap(String tableName, Map>> map) { + if (MapUtil.isEmpty(map)) { + return new HashMap<>(0); + } + + Map rowMap = new HashMap<>(map.size()); + map.forEach((row, familyMap) -> { + RowDo rowDo = new RowDo(tableName, row, FamilyDo.toFamilyMap(tableName, row, familyMap)); + rowMap.put(row, rowDo); + }); + return rowMap; + } + + public static List toRowList(String tableName, Map>> map) { + Map rowMap = toRowMap(tableName, map); + if (MapUtil.isEmpty(rowMap)) { + return new ArrayList<>(); + } + return new ArrayList<>(rowMap.values()); + } + + public static RowDo toRow(String tableName, String row, Map> familyColumnMap) { + if (MapUtil.isEmpty(familyColumnMap)) { + return null; + } + Map familyMap = FamilyDo.toFamilyMap(tableName, row, familyColumnMap); + return new RowDo(tableName, row, familyMap); + } + +} diff --git a/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/entity/common/ScrollData.java b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/entity/common/ScrollData.java new file mode 100644 index 00000000..96cd4412 --- /dev/null +++ b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/entity/common/ScrollData.java @@ -0,0 +1,26 @@ +package io.github.dunwu.javadb.hbase.entity.common; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Collection; + +/** + * Hbase 滚动数据实体 + * + * @author Zhang Peng + * @date 2023-11-16 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ScrollData { + + private String startRow; + private String stopRow; + private String scrollRow; + private Integer size; + private Collection content; + +} diff --git a/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/entity/scan/BaseScan.java b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/entity/scan/BaseScan.java new file mode 100644 index 00000000..667b99ce --- /dev/null +++ b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/entity/scan/BaseScan.java @@ -0,0 +1,85 @@ +package io.github.dunwu.javadb.hbase.entity.scan; + +import cn.hutool.core.util.StrUtil; +import lombok.Data; +import lombok.experimental.Accessors; +import org.apache.hadoop.hbase.client.Scan; +import org.apache.hadoop.hbase.filter.Filter; +import org.apache.hadoop.hbase.filter.FilterList; +import org.apache.hadoop.hbase.filter.PageFilter; +import org.apache.hadoop.hbase.util.Bytes; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * HBase 基本 scan 封装请求参数 + * + * @author Zhang Peng + * @date 2023-05-19 + */ +@Data +@Accessors(chain = true) +public class BaseScan { + + /** 表名 */ + protected String tableName; + /** 起始 row */ + protected String startRow; + /** 结束 row */ + protected String stopRow; + /** 起始时间 */ + protected Long minTimeStamp; + /** 结束时间 */ + protected Long maxTimeStamp; + /** 是否降序(true: 降序;false:正序) */ + protected boolean reversed = false; + /** 页号 */ + protected Integer page; + /** 每页记录数大小 */ + protected Integer size = 100; + /** 过滤器列表 */ + protected List filters = new ArrayList<>(); + + public void addFilter(Filter filter) { + this.filters.add(filter); + } + + public Scan toScan() throws IOException { + Scan scan = new Scan(); + + // 缓存1000条数据 + scan.setCaching(1000); + scan.setCacheBlocks(false); + scan.setReversed(reversed); + if (StrUtil.isNotBlank(startRow)) { + if (reversed) { + scan.withStopRow(Bytes.toBytes(startRow), true); + } else { + scan.withStartRow(Bytes.toBytes(startRow), true); + } + } + if (StrUtil.isNotBlank(stopRow)) { + if (reversed) { + scan.withStartRow(Bytes.toBytes(stopRow), true); + } else { + scan.withStopRow(Bytes.toBytes(stopRow), true); + } + } + if (minTimeStamp != null && maxTimeStamp != null) { + scan.setTimeRange(minTimeStamp, maxTimeStamp); + } + if (size != null) { + PageFilter pageFilter = new PageFilter(size); + filters.add(pageFilter); + } + FilterList filterList = new FilterList(); + for (Filter filter : filters) { + filterList.addFilter(filter); + } + scan.setFilter(filterList); + return scan; + } + +} diff --git a/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/entity/scan/MultiFamilyScan.java b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/entity/scan/MultiFamilyScan.java new file mode 100644 index 00000000..69c58990 --- /dev/null +++ b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/entity/scan/MultiFamilyScan.java @@ -0,0 +1,67 @@ +package io.github.dunwu.javadb.hbase.entity.scan; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.StrUtil; +import org.apache.hadoop.hbase.client.Scan; +import org.apache.hadoop.hbase.util.Bytes; + +import java.io.IOException; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +/** + * HBase 多列族 scan 封装请求参数 + * + * @author Zhang Peng + * @date 2023-05-19 + */ +public class MultiFamilyScan extends BaseScan { + + /** + * 列族, 列族所包含的列(不可为空) + */ + private Map> familyColumnMap = new HashMap<>(); + private String scrollRow; + + public Map> getFamilyColumnMap() { + return familyColumnMap; + } + + public MultiFamilyScan setFamilyColumnMap( + Map> familyColumnMap) { + this.familyColumnMap = familyColumnMap; + return this; + } + + public String getScrollRow() { + return scrollRow; + } + + public MultiFamilyScan setScrollRow(String scrollRow) { + this.scrollRow = scrollRow; + return this; + } + + @Override + public Scan toScan() throws IOException { + Scan scan = super.toScan(); + if (StrUtil.isNotBlank(scrollRow)) { + scan.withStartRow(Bytes.toBytes(scrollRow), false); + } + if (MapUtil.isNotEmpty(familyColumnMap)) { + for (Map.Entry> entry : familyColumnMap.entrySet()) { + String family = entry.getKey(); + Collection columns = entry.getValue(); + if (CollectionUtil.isNotEmpty(columns)) { + for (String column : columns) { + scan.addColumn(Bytes.toBytes(family), Bytes.toBytes(column)); + } + } + } + } + return scan; + } + +} diff --git a/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/entity/scan/SingleFamilyScan.java b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/entity/scan/SingleFamilyScan.java new file mode 100644 index 00000000..614b689a --- /dev/null +++ b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/entity/scan/SingleFamilyScan.java @@ -0,0 +1,59 @@ +package io.github.dunwu.javadb.hbase.entity.scan; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.StrUtil; +import lombok.Data; +import lombok.experimental.Accessors; +import org.apache.hadoop.hbase.client.Scan; +import org.apache.hadoop.hbase.util.Bytes; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +/** + * HBase 单列族 scan 封装请求参数 + * + * @author Zhang Peng + * @date 2023-05-19 + */ +@Data +@Accessors(chain = true) +public class SingleFamilyScan extends BaseScan { + + private String family; + private Collection columns = new ArrayList<>(); + private String scrollRow; + + @Override + public Scan toScan() throws IOException { + Scan scan = super.toScan(); + if (StrUtil.isNotBlank(scrollRow)) { + scan.withStartRow(Bytes.toBytes(scrollRow), false); + } + if (CollectionUtil.isNotEmpty(this.getColumns())) { + for (String column : columns) { + scan.addColumn(Bytes.toBytes(family), Bytes.toBytes(column)); + } + } + return scan; + } + + public Map> getFamilyColumnMap() { + + if (StrUtil.isBlank(family)) { + return new HashMap<>(0); + } + + Map> familyColumnMap = new HashMap<>(1); + if (CollectionUtil.isNotEmpty(columns)) { + familyColumnMap.put(family, columns); + } else { + familyColumnMap.put(family, new ArrayList<>()); + } + return familyColumnMap; + } + +} diff --git a/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/mapper/BaseHbaseMapper.java b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/mapper/BaseHbaseMapper.java new file mode 100644 index 00000000..8f5bbbcc --- /dev/null +++ b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/mapper/BaseHbaseMapper.java @@ -0,0 +1,194 @@ +package io.github.dunwu.javadb.hbase.mapper; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.StrUtil; +import io.github.dunwu.javadb.hbase.HbaseTemplate; +import io.github.dunwu.javadb.hbase.entity.BaseHbaseEntity; +import io.github.dunwu.javadb.hbase.entity.common.ScrollData; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.hadoop.hbase.client.Connection; + +import java.io.IOException; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * HBase Mapper 基础类 + * + * @author Zhang Peng + * @date 2023-11-15 + */ +@Slf4j +@RequiredArgsConstructor +public abstract class BaseHbaseMapper implements HbaseMapper { + + protected final HbaseTemplate hbaseTemplate; + + @Override + public Connection getClient() { + return hbaseTemplate.getConnection(); + } + + @Override + public String getNamespace() { + return "default"; + } + + @Override + public String getFamily() { + return "f"; + } + + @Override + public int deleteById(Serializable id) { + + String rowKey = getIdStr(id); + if (StrUtil.isBlank(rowKey)) { + return 0; + } + + try { + hbaseTemplate.delete(getFullTableName(), rowKey); + return 1; + } catch (IOException e) { + log.error("【Hbase】deleteById 异常", e); + return 0; + } + } + + @Override + public int deleteById(T entity) { + if (entity == null) { + return 0; + } + return deleteById(entity.getRowKey()); + } + + @Override + public int deleteBatchById(Collection ids) { + + if (CollectionUtil.isEmpty(ids)) { + return 0; + } + + List rowKeys = getIdStrList(ids); + try { + hbaseTemplate.batchDelete(getFullTableName(), rowKeys.toArray(new String[0])); + return rowKeys.size(); + } catch (IOException | InterruptedException e) { + log.error("【Hbase】deleteBatchIds 异常", e); + return 0; + } + } + + @Override + public int save(T entity) { + try { + String rowKey = entity.getRowKey(); + hbaseTemplate.put(getFullTableName(), rowKey, getFamily(), entity); + return 1; + } catch (IOException e) { + log.error("【Hbase】updateById 异常", e); + return 0; + } + } + + @Override + public int batchSave(Collection list) { + + if (CollectionUtil.isEmpty(list)) { + return 0; + } + + try { + hbaseTemplate.batchPut(getFullTableName(), getFamily(), list); + return list.size(); + } catch (IOException | InterruptedException e) { + log.error("【Hbase】batchSave 异常", e); + return 0; + } + } + + @Override + public T getOneById(Serializable id) { + + String rowKey = getIdStr(id); + if (StrUtil.isBlank(rowKey)) { + return null; + } + + try { + return hbaseTemplate.getEntity(getFullTableName(), rowKey, getFamily(), getEntityClass()); + } catch (IOException e) { + log.error("【Hbase】getOneById 异常", e); + return null; + } + } + + @Override + public Map getMapByIds(Collection ids) { + + if (CollectionUtil.isEmpty(ids)) { + return new LinkedHashMap<>(0); + } + + List rowKeys = getIdStrList(ids); + try { + return hbaseTemplate.getEntityMap(getFullTableName(), rowKeys.toArray(new String[0]), getFamily(), + getEntityClass()); + } catch (IOException e) { + log.error("【Hbase】getMapByIds 异常", e); + return new LinkedHashMap<>(0); + } + } + + @Override + public List scroll(Serializable scrollId, int size) { + String scrollRowKey = getIdStr(scrollId); + try { + ScrollData scrollData = + hbaseTemplate.getEntityScroll(getFullTableName(), getFamily(), scrollRowKey, size, getEntityClass()); + if (scrollData == null || CollectionUtil.isEmpty(scrollData.getContent())) { + return new ArrayList<>(); + } + return new ArrayList<>(scrollData.getContent()); + } catch (IOException e) { + log.error("【Hbase】getEntityScroll 异常", e); + return new ArrayList<>(); + } + } + + protected String getFullTableName() { + return StrUtil.format("{}:{}", getNamespace(), getTableName()); + } + + protected String getIdStr(Serializable id) { + + if (id == null) { + return null; + } + + String rowKey; + if (id instanceof String) { + rowKey = (String) id; + } else { + rowKey = String.valueOf(id); + } + return rowKey; + } + + protected List getIdStrList(Collection ids) { + if (CollectionUtil.isEmpty(ids)) { + return new ArrayList<>(0); + } + return ids.stream().map(this::getIdStr).filter(Objects::nonNull).collect(Collectors.toList()); + } + +} diff --git a/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/mapper/BaseHbaseUkMapper.java b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/mapper/BaseHbaseUkMapper.java new file mode 100644 index 00000000..bc9dd800 --- /dev/null +++ b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/mapper/BaseHbaseUkMapper.java @@ -0,0 +1,21 @@ +package io.github.dunwu.javadb.hbase.mapper; + +import io.github.dunwu.javadb.hbase.HbaseTemplate; +import io.github.dunwu.javadb.hbase.entity.BaseHbaseContentEntity; +import lombok.extern.slf4j.Slf4j; + +/** + * HBase Mapper 基础类 + * + * @author Zhang Peng + * @date 2023-11-15 + */ +@Slf4j +public abstract class BaseHbaseUkMapper extends BaseHbaseMapper + implements HbaseUkMapper { + + public BaseHbaseUkMapper(HbaseTemplate hbaseTemplate) { + super(hbaseTemplate); + } + +} diff --git a/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/mapper/CommonMapper.java b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/mapper/CommonMapper.java new file mode 100644 index 00000000..cc31c2dd --- /dev/null +++ b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/mapper/CommonMapper.java @@ -0,0 +1,93 @@ +package io.github.dunwu.javadb.hbase.mapper; + +import cn.hutool.core.collection.CollectionUtil; + +import java.io.Serializable; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +/** + * 通用 Mapper + * + * @author Zhang Peng + * @date 2023-11-22 + */ +public interface CommonMapper { + + /** + * 插入一条记录 + * + * @param entity 实体对象 + */ + default int insert(T entity) { + return insertBatch(Collections.singleton(entity)); + } + + /** + * 批量插入记录 + * + * @param list 实体对象列表 + */ + int insertBatch(Collection list); + + /** + * 根据 ID 删除 + * + * @param id 主键ID + */ + default int deleteById(Serializable id) { + return deleteBatchById(Collections.singleton(id)); + } + + /** + * 根据实体(ID)删除 + * + * @param entity 实体对象 + */ + int deleteById(T entity); + + /** + * 删除(根据ID或实体 批量删除) + * + * @param idList 主键ID列表或实体列表(不能为 null 以及 empty) + */ + int deleteBatchById(Collection idList); + + /** + * 根据 ID 更新 + * + * @param entity 实体对象 + */ + default int updateById(T entity) { + return updateBatchById(Collections.singleton(entity)); + } + + /** + * 批量更新记录 + * + * @param list 实体对象列表 + */ + int updateBatchById(Collection list); + + /** + * 根据 ID 查询 + * + * @param id 主键ID + */ + default T getOneById(Serializable id) { + List list = getListByIds(Collections.singleton(id)); + if (CollectionUtil.isEmpty(list)) { + return null; + } + return list.get(0); + } + + /** + * 查询(根据ID 批量查询) + * + * @param idList 主键ID列表(不能为 null 以及 empty) + */ + List getListByIds(Collection idList); + +} diff --git a/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/mapper/CommonUkMapper.java b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/mapper/CommonUkMapper.java new file mode 100644 index 00000000..a185e122 --- /dev/null +++ b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/mapper/CommonUkMapper.java @@ -0,0 +1,77 @@ +package io.github.dunwu.javadb.hbase.mapper; + +import cn.hutool.core.collection.CollectionUtil; + +import java.io.Serializable; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +/** + * 基于唯一索引的通用 Mapper + * + * @author Zhang Peng + * @date 2023-11-23 + */ +public interface CommonUkMapper extends CommonMapper { + + /** + * 根据唯一索引删除 + * + * @param uk 唯一索引 + */ + default int deleteByUk(Serializable uk) { + return deleteBatchByUk(Collections.singleton(uk)); + } + + /** + * 根据唯一索引删除 + * + * @param entity 实体对象 + */ + int deleteByUk(T entity); + + /** + * 根据唯一索引批量删除 + * + * @param ukList 唯一索引列表 + */ + int deleteBatchByUk(Collection ukList); + + /** + * 根据唯一索引更新 + * + * @param entity 实体对象 + */ + default int updateByUk(T entity) { + return updateBatchByUk(Collections.singleton(entity)); + } + + /** + * 根据唯一索引批量更新 + * + * @param list 实体对象 + */ + int updateBatchByUk(Collection list); + + /** + * 根据唯一索引查询 + * + * @param uk 唯一索引 + */ + default T getOneByUk(Serializable uk) { + List list = getListByUk(Collections.singleton(uk)); + if (CollectionUtil.isEmpty(list)) { + return null; + } + return list.get(0); + } + + /** + * 根据唯一索引批量查询 + * + * @param ukList 唯一索引列表 + */ + List getListByUk(Collection ukList); + +} diff --git a/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/mapper/HbaseMapper.java b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/mapper/HbaseMapper.java new file mode 100644 index 00000000..c16cc1f1 --- /dev/null +++ b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/mapper/HbaseMapper.java @@ -0,0 +1,106 @@ +package io.github.dunwu.javadb.hbase.mapper; + +import cn.hutool.core.map.MapUtil; +import io.github.dunwu.javadb.hbase.entity.BaseHbaseEntity; +import org.apache.hadoop.hbase.client.Connection; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * Hbase Mapper + * + * @author Zhang Peng + * @date 2023-11-15 + */ +public interface HbaseMapper extends CommonMapper { + + /** + * 获取 Hbase 官方客户端实体 + */ + Connection getClient(); + + /** + * 获取命名空间 + */ + String getNamespace(); + + /** + * 获取表名 + */ + String getTableName(); + + /** + * 获取列族 + */ + String getFamily(); + + /** + * 获取实体类型 + */ + Class getEntityClass(); + + @Override + default int insert(T entity) { + return save(entity); + } + + @Override + default int updateById(T entity) { + return save(entity); + } + + @Override + default int insertBatch(Collection list) { + return batchSave(list); + } + + @Override + default int updateBatchById(Collection list) { + return batchSave(list); + } + + /** + * 保存一条记录 + * + * @param entity 实体对象 + */ + int save(T entity); + + /** + * 批量保存记录 + * + * @param list 实体对象列表 + */ + int batchSave(Collection list); + + @Override + default List getListByIds(Collection ids) { + Map map = getMapByIds(ids); + if (MapUtil.isEmpty(map)) { + return new ArrayList<>(); + } + return new ArrayList<>(map.values()); + } + + /** + * 根据 ID 列表批量查数据,以 Map 形式返回 + * + * @param ids 即 Hbase rowkey + * @return / + */ + Map getMapByIds(Collection ids); + + /** + * 根据 ID 滚动分页查询 + * + * @param scrollId 为空值时,默认查第一页 + * @param size 每页记录数 + * @return / + */ + List scroll(Serializable scrollId, int size); + +} diff --git a/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/mapper/HbaseUkMapper.java b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/mapper/HbaseUkMapper.java new file mode 100644 index 00000000..7b4f8cae --- /dev/null +++ b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/mapper/HbaseUkMapper.java @@ -0,0 +1,74 @@ +package io.github.dunwu.javadb.hbase.mapper; + +import cn.hutool.core.collection.CollectionUtil; +import io.github.dunwu.javadb.hbase.annotation.RowKeyUtil; +import io.github.dunwu.javadb.hbase.entity.BaseHbaseContentEntity; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Hbase Content Mapper + * + * @author Zhang Peng + * @date 2023-11-15 + */ +public interface HbaseUkMapper extends HbaseMapper, CommonUkMapper { + + @Override + default int deleteByUk(Serializable uk) { + String rowKey = RowKeyUtil.getRowKeyForBucket((String) uk, getEntityClass()); + return deleteById(rowKey); + } + + @Override + default int deleteByUk(T entity) { + String rowKey = RowKeyUtil.getRowKeyForBucket(entity.getUk(), getEntityClass()); + return deleteById(rowKey); + } + + @Override + default int deleteBatchByUk(Collection ukList) { + if (CollectionUtil.isEmpty(ukList)) { + return 0; + } + List rowKeys = ukList.stream() + .map(contentId -> RowKeyUtil.getRowKeyForBucket((String) contentId, + getEntityClass())) + .collect(Collectors.toList()); + return deleteBatchById(rowKeys); + } + + @Override + default int updateByUk(T entity) { + return save(entity); + } + + @Override + default int updateBatchByUk(Collection list) { + return batchSave(list); + } + + @Override + default T getOneByUk(Serializable uk) { + String rowKey = RowKeyUtil.getRowKeyForBucket((String) uk, getEntityClass()); + return getOneById(rowKey); + } + + @Override + default List getListByUk(Collection ukList) { + if (CollectionUtil.isEmpty(ukList)) { + return new ArrayList<>(); + } + + List rowKeys = ukList.stream() + .map(contentId -> RowKeyUtil.getRowKeyForBucket((String) contentId, + getEntityClass())) + .collect(Collectors.toList()); + return getListByIds(rowKeys); + } + +} diff --git a/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/mapper/UkGetter.java b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/mapper/UkGetter.java new file mode 100644 index 00000000..2e58c863 --- /dev/null +++ b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/mapper/UkGetter.java @@ -0,0 +1,16 @@ +package io.github.dunwu.javadb.hbase.mapper; + +/** + * 获取唯一索引 Get 方法接口 + * + * @author Zhang Peng + * @date 2023-11-24 + */ +public interface UkGetter { + + /** + * 获取唯一索引 + */ + String getUk(); + +} diff --git a/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/util/JsonUtil.java b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/util/JsonUtil.java new file mode 100644 index 00000000..17d57d03 --- /dev/null +++ b/codes/javadb/hbase/src/main/java/io/github/dunwu/javadb/hbase/util/JsonUtil.java @@ -0,0 +1,144 @@ +package io.github.dunwu.javadb.hbase.util; + +import cn.hutool.core.util.StrUtil; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.type.TypeFactory; +import lombok.extern.slf4j.Slf4j; + +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Slf4j +public class JsonUtil { + + private JsonUtil() { } + + private static final ObjectMapper OBJECT_MAPPER; + private static final TypeFactory TYPE_FACTORY; + + static { + OBJECT_MAPPER = new ObjectMapper(); + OBJECT_MAPPER.setSerializationInclusion(JsonInclude.Include.NON_NULL); + OBJECT_MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + TYPE_FACTORY = OBJECT_MAPPER.getTypeFactory(); + } + + public static ObjectMapper getInstance() { + return OBJECT_MAPPER; + } + + /** + * 简单对象转换 + */ + @SuppressWarnings("unchecked") + public static T toBean(String json, Class clazz) { + if (StrUtil.isBlank(json)) { + return null; + } + if (clazz == String.class) { + return (T) json; + } + try { + return OBJECT_MAPPER.readValue(json, clazz); + } catch (IOException e) { + log.error("反序列化失败!json: {}, msg: {}", json, e.getMessage()); + } + return null; + } + + /** + * 复杂对象转换 + */ + public static T toBean(String json, TypeReference typeReference) { + if (StrUtil.isBlank(json)) { + return null; + } + try { + return (T) OBJECT_MAPPER.readValue(json, typeReference); + } catch (Exception e) { + log.error("反序列化失败!json: {}, msg: {}", json, e.getMessage()); + } + return null; + } + + public static T toBean(Map map, Class clazz) { + return OBJECT_MAPPER.convertValue(toString(map), clazz); + } + + public static String toString(Object obj) { + if (obj == null) { + return null; + } + if (obj instanceof String) { + return (String) obj; + } + try { + return OBJECT_MAPPER.writeValueAsString(obj); + } catch (JsonProcessingException e) { + log.error("序列化失败!obj: {}, msg: {}", obj, e.getMessage()); + } + return null; + } + + public static Map toMap(String json) { + if (StrUtil.isBlank(json)) { + return new HashMap<>(0); + } + try { + return OBJECT_MAPPER.readValue(json, new TypeReference>() { }); + } catch (Exception e) { + log.error("反序列化失败!json: {}, msg: {}", json, e.getMessage()); + } + return Collections.emptyMap(); + } + + public static Map toMap(Object obj) { + + if (obj == null) { + return null; + } + + try { + return OBJECT_MAPPER.readValue(toString(obj), new TypeReference>() { }); + } catch (IOException e) { + log.error("反序列化失败!json: {}, msg: {}", toString(obj), e.getMessage()); + } + return null; + } + + public static List toList(String json, Class clazz) { + if (StrUtil.isBlank(json)) { + return null; + } + JavaType javaType = TYPE_FACTORY.constructParametricType(List.class, clazz); + try { + return OBJECT_MAPPER.readValue(json, javaType); + } catch (IOException e) { + log.error("反序列化失败!json: {}, msg: {}", json, e.getMessage()); + } + return null; + } + + public static List toList(String json, TypeReference typeReference) { + if (StrUtil.isBlank(json)) { + return null; + } + JavaType elementType = TYPE_FACTORY.constructType(typeReference); + JavaType javaType = TYPE_FACTORY.constructParametricType(List.class, elementType); + try { + return OBJECT_MAPPER.readValue(json, javaType); + } catch (IOException e) { + log.error("反序列化失败!json: {}, msg: {}", json, e.getMessage()); + } + return null; + } + +} \ No newline at end of file diff --git a/codes/javadb/hbase/src/test/java/io/github/dunwu/javadb/hbase/HbaseMapperTest.java b/codes/javadb/hbase/src/test/java/io/github/dunwu/javadb/hbase/HbaseMapperTest.java new file mode 100644 index 00000000..edf8d9bc --- /dev/null +++ b/codes/javadb/hbase/src/test/java/io/github/dunwu/javadb/hbase/HbaseMapperTest.java @@ -0,0 +1,76 @@ +package io.github.dunwu.javadb.hbase; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.json.JSONUtil; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.math.BigDecimal; +import java.util.Collections; +import java.util.Date; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * @author Zhang Peng + * @date 2023-11-15 + */ +public class HbaseMapperTest { + + private static final OrderMapper mapper; + + static { + HbaseTemplate hbaseTemplate = null; + try { + hbaseTemplate = HbaseFactory.newHbaseTemplate(); + mapper = new OrderMapper(hbaseTemplate); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Test + @DisplayName("批量保存、查询、删除 BaseHbaseEntity 实体") + public void batchSave() { + + Date now = new Date(); + Product product1 = new Product("1", "product1", new BigDecimal(4000.0)); + Product product2 = new Product("2", "product2", new BigDecimal(5000.0)); + List products = CollectionUtil.newArrayList(product1, product2); + User user1 = new User(1, "user1"); + Map tags = new LinkedHashMap<>(); + tags.put("type", "tool"); + tags.put("color", "red"); + + Order originOrder = Order.builder() + .id("1") + .user(user1) + .products(products) + .desc("测试订单") + .date(now) + .tags(tags) + .build(); + mapper.batchSave(Collections.singleton(originOrder)); + + List list = mapper.getListByIds(Collections.singleton(originOrder.getRowKey())); + Assertions.assertThat(list).isNotEmpty(); + Order order = list.get(0); + Assertions.assertThat(order).isNotNull(); + Assertions.assertThat(order.getDate()).isNotNull().isEqualTo(now); + Assertions.assertThat(order.getTags()).isNotNull().isEqualTo(tags); + Assertions.assertThat(order.getUser()).isNotNull().isEqualTo(user1); + Assertions.assertThat(order.getProducts()).isNotEmpty(); + Assertions.assertThat(list).isNotEmpty(); + Assertions.assertThat(list.size()).isEqualTo(1); + System.out.println(JSONUtil.toJsonStr(list)); + + mapper.deleteBatchById(Collections.singletonList(originOrder.getRowKey())); + + List list2 = mapper.getListByIds(Collections.singletonList(originOrder.getRowKey())); + Assertions.assertThat(list2).isEmpty(); + } + +} diff --git a/codes/javadb/hbase/src/test/java/io/github/dunwu/javadb/hbase/HbaseTemplateGetTest.java b/codes/javadb/hbase/src/test/java/io/github/dunwu/javadb/hbase/HbaseTemplateGetTest.java new file mode 100644 index 00000000..a0e953ce --- /dev/null +++ b/codes/javadb/hbase/src/test/java/io/github/dunwu/javadb/hbase/HbaseTemplateGetTest.java @@ -0,0 +1,308 @@ +package io.github.dunwu.javadb.hbase; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.ObjectUtil; +import io.github.dunwu.javadb.hbase.entity.BaseHbaseEntity; +import io.github.dunwu.javadb.hbase.entity.common.ColumnDo; +import io.github.dunwu.javadb.hbase.entity.common.FamilyDo; +import io.github.dunwu.javadb.hbase.entity.common.RowDo; +import io.github.dunwu.javadb.hbase.util.JsonUtil; +import org.apache.hadoop.hbase.client.Put; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.math.BigDecimal; +import java.util.Date; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * Hbase Get 测试 + * + * @author Zhang Peng + * @date 2023-11-13 + */ +public class HbaseTemplateGetTest { + + public static final String TABLE_NAME = "test:test"; + + private static final HbaseTemplate HBASE_TEMPLATE; + + static { + try { + HBASE_TEMPLATE = HbaseFactory.newHbaseTemplate(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Test + @DisplayName("put、get 单列数据") + public void test00() throws IOException { + long timestamp = System.currentTimeMillis(); + HBASE_TEMPLATE.put(TABLE_NAME, "test-key-0", "f1", "name", "user0"); + ColumnDo columnDo = HBASE_TEMPLATE.getColumn(TABLE_NAME, "test-key-0", "f1", "name"); + Assertions.assertThat(columnDo).isNotNull(); + Assertions.assertThat(columnDo.getColumn()).isEqualTo("name"); + Assertions.assertThat(columnDo.getValue()).isEqualTo("user0"); + + HBASE_TEMPLATE.put(TABLE_NAME, "test-key-0", timestamp, "f2", "姓名", "张三"); + ColumnDo columnDo2 = HBASE_TEMPLATE.getColumn(TABLE_NAME, "test-key-0", "f2", "姓名"); + Assertions.assertThat(columnDo2).isNotNull(); + Assertions.assertThat(columnDo2.getColumn()).isEqualTo("姓名"); + Assertions.assertThat(columnDo2.getValue()).isEqualTo("张三"); + Assertions.assertThat(columnDo2.getTimestamp()).isEqualTo(timestamp); + + HBASE_TEMPLATE.delete(TABLE_NAME, "test-key-0"); + columnDo = HBASE_TEMPLATE.getColumn(TABLE_NAME, "test-key-0", "f1", "name"); + Assertions.assertThat(columnDo).isNull(); + columnDo2 = HBASE_TEMPLATE.getColumn(TABLE_NAME, "test-key-0", "f2", "姓名"); + Assertions.assertThat(columnDo2).isNull(); + } + + @Test + @DisplayName("put、get 多列数据") + public void test01() throws IOException { + + String row = "test-key-1"; + long timestamp = System.currentTimeMillis(); + Map map1 = new HashMap<>(2); + map1.put("id", 1); + map1.put("name", "zhangsan"); + Map map2 = new HashMap<>(2); + map2.put("编号", 1); + map2.put("姓名", "张三"); + + HBASE_TEMPLATE.put(TABLE_NAME, row, timestamp, "f1", map1); + HBASE_TEMPLATE.put(TABLE_NAME, row, timestamp, "f2", map2); + + Map f1ColumnMap = HBASE_TEMPLATE.getColumnMap(TABLE_NAME, row, "f1", "id", "name"); + Assertions.assertThat(f1ColumnMap).isNotEmpty(); + Assertions.assertThat(f1ColumnMap.get("id")).isNotNull(); + Assertions.assertThat(f1ColumnMap.get("id").getValue()).isEqualTo(String.valueOf(1)); + Assertions.assertThat(f1ColumnMap.get("name")).isNotNull(); + Assertions.assertThat(f1ColumnMap.get("name").getValue()).isEqualTo("zhangsan"); + + Map f2ColumnMap = HBASE_TEMPLATE.getColumnMap(TABLE_NAME, row, "f2", "编号", "姓名"); + Assertions.assertThat(f2ColumnMap).isNotEmpty(); + Assertions.assertThat(f2ColumnMap.get("编号")).isNotNull(); + Assertions.assertThat(f2ColumnMap.get("编号").getValue()).isEqualTo(String.valueOf(1)); + Assertions.assertThat(f2ColumnMap.get("姓名")).isNotNull(); + Assertions.assertThat(f2ColumnMap.get("姓名").getValue()).isEqualTo("张三"); + + HBASE_TEMPLATE.delete(TABLE_NAME, row); + f1ColumnMap = HBASE_TEMPLATE.getColumnMap(TABLE_NAME, row, "f1", "id", "name"); + Assertions.assertThat(f1ColumnMap).isEmpty(); + f2ColumnMap = HBASE_TEMPLATE.getColumnMap(TABLE_NAME, row, "f2", "编号", "姓名"); + Assertions.assertThat(f2ColumnMap).isEmpty(); + } + + @Test + @DisplayName("put、get 列族数据") + public void test02() throws IOException { + + String row = "test-key-2"; + long timestamp = System.currentTimeMillis(); + Map map1 = new HashMap<>(2); + map1.put("id", 1); + map1.put("name", "zhangsan"); + Map map2 = new HashMap<>(2); + map2.put("编号", 1); + map2.put("姓名", "张三"); + + HBASE_TEMPLATE.put(TABLE_NAME, row, timestamp, "f1", map1); + HBASE_TEMPLATE.put(TABLE_NAME, row, timestamp, "f2", map2); + + FamilyDo f1 = HBASE_TEMPLATE.getFamily(TABLE_NAME, row, "f1"); + Assertions.assertThat(f1).isNotNull(); + Assertions.assertThat(f1.getColumnMap().get("id")).isNotNull(); + Assertions.assertThat(f1.getColumnMap().get("id").getValue()).isEqualTo(String.valueOf(1)); + Assertions.assertThat(f1.getColumnMap().get("name")).isNotNull(); + Assertions.assertThat(f1.getColumnMap().get("name").getValue()).isEqualTo("zhangsan"); + + FamilyDo f2 = HBASE_TEMPLATE.getFamily(TABLE_NAME, row, "f2"); + Assertions.assertThat(f2).isNotNull(); + Assertions.assertThat(f2.getColumnMap().get("编号")).isNotNull(); + Assertions.assertThat(f2.getColumnMap().get("编号").getValue()).isEqualTo(String.valueOf(1)); + Assertions.assertThat(f2.getColumnMap().get("姓名")).isNotNull(); + Assertions.assertThat(f2.getColumnMap().get("姓名").getValue()).isEqualTo("张三"); + + HBASE_TEMPLATE.delete(TABLE_NAME, row); + f1 = HBASE_TEMPLATE.getFamily(TABLE_NAME, row, "f1"); + Assertions.assertThat(f1).isNull(); + f2 = HBASE_TEMPLATE.getFamily(TABLE_NAME, row, "f2"); + Assertions.assertThat(f2).isNull(); + } + + @Test + @DisplayName("put、get 单行数据") + public void test03() throws IOException { + + String row = "test-key-3"; + long timestamp = System.currentTimeMillis(); + Map map1 = new HashMap<>(2); + map1.put("id", 1); + map1.put("name", "zhangsan"); + Map map2 = new HashMap<>(2); + map2.put("编号", 1); + map2.put("姓名", "张三"); + Map> familyMap = new HashMap<>(2); + familyMap.put("f1", map1); + familyMap.put("f2", map2); + + HBASE_TEMPLATE.put(TABLE_NAME, row, timestamp, familyMap); + + RowDo rowDo = HBASE_TEMPLATE.getRow(TABLE_NAME, row); + Assertions.assertThat(rowDo).isNotNull(); + + FamilyDo f1 = rowDo.getFamilyMap().get("f1"); + Assertions.assertThat(f1).isNotNull(); + Assertions.assertThat(f1.getColumnMap()).isNotEmpty(); + Assertions.assertThat(f1.getColumnMap().get("id")).isNotNull(); + Assertions.assertThat(f1.getColumnMap().get("id").getValue()).isEqualTo(String.valueOf(1)); + Assertions.assertThat(f1.getColumnMap().get("name")).isNotNull(); + Assertions.assertThat(f1.getColumnMap().get("name").getValue()).isEqualTo("zhangsan"); + + FamilyDo f2 = rowDo.getFamilyMap().get("f2"); + Assertions.assertThat(f2).isNotNull(); + Assertions.assertThat(f2.getColumnMap()).isNotEmpty(); + Assertions.assertThat(f2.getColumnMap().get("编号")).isNotNull(); + Assertions.assertThat(f2.getColumnMap().get("编号").getValue()).isEqualTo(String.valueOf(1)); + Assertions.assertThat(f2.getColumnMap().get("姓名")).isNotNull(); + Assertions.assertThat(f2.getColumnMap().get("姓名").getValue()).isEqualTo("张三"); + + HBASE_TEMPLATE.delete(TABLE_NAME, row); + rowDo = HBASE_TEMPLATE.getRow(TABLE_NAME, row); + Assertions.assertThat(rowDo).isNull(); + } + + @Test + @DisplayName("put get 多行数据") + public void test04() throws IOException, InterruptedException { + + long timestamp = System.currentTimeMillis(); + + Map columnMap1 = new HashMap<>(2); + columnMap1.put("id", 1); + columnMap1.put("name", "zhangsan"); + Put put = HbaseTemplate.newPut("test-key-1", timestamp, "f1", columnMap1); + + Map columnMap2 = new HashMap<>(2); + columnMap2.put("id", 2); + columnMap2.put("name", "lisi"); + Put put2 = HbaseTemplate.newPut("test-key-2", timestamp, "f1", columnMap2); + + List puts = CollectionUtil.newArrayList(put, put2); + + HBASE_TEMPLATE.batchPut(TABLE_NAME, puts); + + Map rowMap = HBASE_TEMPLATE.getRowMap(TABLE_NAME, "test-key-1", "test-key-2"); + + RowDo rowDo1 = rowMap.get("test-key-1"); + Assertions.assertThat(rowDo1).isNotNull(); + FamilyDo f1 = rowDo1.getFamilyMap().get("f1"); + Assertions.assertThat(f1).isNotNull(); + Assertions.assertThat(f1.getColumnMap()).isNotEmpty(); + Assertions.assertThat(f1.getColumnMap().get("id")).isNotNull(); + Assertions.assertThat(f1.getColumnMap().get("id").getValue()).isEqualTo(String.valueOf(1)); + Assertions.assertThat(f1.getColumnMap().get("name")).isNotNull(); + Assertions.assertThat(f1.getColumnMap().get("name").getValue()).isEqualTo("zhangsan"); + + RowDo rowDo2 = rowMap.get("test-key-2"); + FamilyDo f2 = rowDo2.getFamilyMap().get("f1"); + Assertions.assertThat(f2).isNotNull(); + Assertions.assertThat(f2.getColumnMap()).isNotEmpty(); + Assertions.assertThat(f2.getColumnMap().get("id")).isNotNull(); + Assertions.assertThat(f2.getColumnMap().get("id").getValue()).isEqualTo(String.valueOf(2)); + Assertions.assertThat(f2.getColumnMap().get("name")).isNotNull(); + Assertions.assertThat(f2.getColumnMap().get("name").getValue()).isEqualTo("lisi"); + + HBASE_TEMPLATE.batchDelete(TABLE_NAME, "test-key-1", "test-key-2"); + rowDo1 = HBASE_TEMPLATE.getRow(TABLE_NAME, "test-key-1"); + Assertions.assertThat(rowDo1).isNull(); + rowDo2 = HBASE_TEMPLATE.getRow(TABLE_NAME, "test-key-2"); + Assertions.assertThat(rowDo2).isNull(); + } + + @Test + @DisplayName("put get 简单 Java 实体数据") + public void test05() throws IOException, InterruptedException { + + User originUser1 = new User(1, "user1"); + HBASE_TEMPLATE.put(TABLE_NAME, "test-key-1", "f1", originUser1); + User user1 = HBASE_TEMPLATE.getEntity(TABLE_NAME, "test-key-1", "f1", User.class); + Assertions.assertThat(user1).isNotNull(); + Assertions.assertThat(ObjectUtil.equals(originUser1, user1)).isTrue(); + + HBASE_TEMPLATE.batchDelete(TABLE_NAME, "test-key-1", "test-key-2"); + user1 = HBASE_TEMPLATE.getEntity(TABLE_NAME, "test-key-1", "f1", User.class); + Assertions.assertThat(user1).isNull(); + } + + @Test + @DisplayName("put get 实现 BaseHbaseEntity 的简单 Java 实体数据") + public void test06() throws IOException, InterruptedException { + + Product product1 = new Product("1", "product1", new BigDecimal(4000.0)); + Product product2 = new Product("2", "product2", new BigDecimal(5000.0)); + List products = CollectionUtil.newArrayList(product1, product2); + HBASE_TEMPLATE.batchPut(TABLE_NAME, "f1", products); + + List rows = products.stream().map(BaseHbaseEntity::getRowKey).collect(Collectors.toList()); + List list = HBASE_TEMPLATE.getEntityList(TABLE_NAME, rows, "f1", Product.class); + Assertions.assertThat(list).isNotEmpty(); + Assertions.assertThat(list.size()).isEqualTo(rows.size()); + + HBASE_TEMPLATE.batchDelete(TABLE_NAME, rows.toArray(new String[0])); + product1 = HBASE_TEMPLATE.getEntity(TABLE_NAME, "test-key-1", "f1", Product.class); + Assertions.assertThat(product1).isNull(); + product2 = HBASE_TEMPLATE.getEntity(TABLE_NAME, "test-key-2", "f1", Product.class); + Assertions.assertThat(product2).isNull(); + list = HBASE_TEMPLATE.getEntityList(TABLE_NAME, rows, "f1", Product.class); + Assertions.assertThat(list).isEmpty(); + } + + @Test + @DisplayName("put get 实现 BaseHbaseEntity 的复杂 Java 实体数据") + public void test07() throws IOException { + + Date now = new Date(); + Product product1 = new Product("1", "product1", new BigDecimal(4000.0)); + Product product2 = new Product("2", "product2", new BigDecimal(5000.0)); + List products = CollectionUtil.newArrayList(product1, product2); + User user1 = new User(1, "user1"); + Map tags = new LinkedHashMap<>(); + tags.put("type", "tool"); + tags.put("color", "red"); + + Order originOrder = Order.builder() + .id("1") + .user(user1) + .products(products) + .desc("测试订单") + .date(now) + .tags(tags) + .build(); + + HBASE_TEMPLATE.put(TABLE_NAME, "f1", originOrder); + + Order order = HBASE_TEMPLATE.getEntity(TABLE_NAME, originOrder.getRowKey(), "f1", Order.class); + Assertions.assertThat(order).isNotNull(); + Assertions.assertThat(order.getDate()).isNotNull().isEqualTo(now); + Assertions.assertThat(order.getTags()).isNotNull().isEqualTo(tags); + Assertions.assertThat(order.getUser()).isNotNull().isEqualTo(user1); + Assertions.assertThat(order.getProducts()).isNotEmpty(); + + System.out.println("order: " + JsonUtil.toString(order)); + + HBASE_TEMPLATE.delete(TABLE_NAME, originOrder.getRowKey()); + order = HBASE_TEMPLATE.getEntity(TABLE_NAME, order.getRowKey(), "f1", Order.class); + Assertions.assertThat(order).isNull(); + } + +} diff --git a/codes/javadb/hbase/src/test/java/io/github/dunwu/javadb/hbase/HbaseTemplateScanTest.java b/codes/javadb/hbase/src/test/java/io/github/dunwu/javadb/hbase/HbaseTemplateScanTest.java new file mode 100644 index 00000000..4496fc08 --- /dev/null +++ b/codes/javadb/hbase/src/test/java/io/github/dunwu/javadb/hbase/HbaseTemplateScanTest.java @@ -0,0 +1,242 @@ +package io.github.dunwu.javadb.hbase; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.RandomUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONUtil; +import io.github.dunwu.javadb.hbase.entity.common.PageData; +import io.github.dunwu.javadb.hbase.entity.common.RowDo; +import io.github.dunwu.javadb.hbase.entity.common.ScrollData; +import io.github.dunwu.javadb.hbase.entity.scan.MultiFamilyScan; +import io.github.dunwu.javadb.hbase.entity.scan.SingleFamilyScan; +import org.apache.hadoop.hbase.client.Put; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Get 测试集 + *

+ * 测试前,先完整执行 {@link HbaseTemplateGetTest} + * + * @author Zhang Peng + * @date 2023-11-13 + */ +public class HbaseTemplateScanTest { + + public static final String TABLE_NAME = "test:test"; + + private static final HbaseTemplate HBASE_TEMPLATE; + + static { + try { + HBASE_TEMPLATE = HbaseFactory.newHbaseTemplate(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Test + @DisplayName("批量初始化") + public void init() throws IOException, InterruptedException { + List products = new ArrayList<>(); + List userPuts = new ArrayList<>(); + for (int i = 1; i <= 100; i++) { + Product product = new Product(String.valueOf(i), "product" + i, + new BigDecimal(RandomUtil.randomDouble(9999.0))); + products.add(product); + + User user = new User(i, "user" + i); + Put put = HbaseTemplate.newPut(product.getRowKey(), null, "f2", user); + userPuts.add(put); + } + HBASE_TEMPLATE.batchPut(TABLE_NAME, "f1", products); + HBASE_TEMPLATE.batchPut(TABLE_NAME, userPuts); + } + + @Test + @DisplayName("单列族分页查询") + public void test01() throws IOException { + SingleFamilyScan scan = new SingleFamilyScan(); + scan.setFamily("f1") + .setTableName(TABLE_NAME) + .setPage(1) + .setSize(10) + .setReversed(true); + PageData firstPage = HBASE_TEMPLATE.page(scan); + System.out.println(StrUtil.format("第 {} 页数据: {}", 1, JSONUtil.toJsonStr(firstPage))); + + int totalPages = firstPage.getTotalPages(); + for (int page = 2; page <= totalPages; page++) { + scan.setPage(page); + PageData nextPage = HBASE_TEMPLATE.page(scan); + System.out.println(StrUtil.format("第 {} 页数据: {}", page, JSONUtil.toJsonStr(nextPage))); + Assertions.assertThat(nextPage).isNotNull(); + } + } + + @Test + @DisplayName("多列族分页查询") + public void test02() throws IOException { + Map> familyColumnMap = new HashMap<>(); + familyColumnMap.put("f1", CollectionUtil.newArrayList("id", "name", "price")); + familyColumnMap.put("f2", CollectionUtil.newArrayList("id", "name")); + + MultiFamilyScan scan = new MultiFamilyScan(); + scan.setFamilyColumnMap(familyColumnMap) + .setTableName(TABLE_NAME) + .setPage(1) + .setSize(10) + .setReversed(true); + PageData firstPage = HBASE_TEMPLATE.page(scan); + System.out.println(StrUtil.format("第 {} 页数据: {}", 1, JSONUtil.toJsonStr(firstPage))); + + int totalPages = firstPage.getTotalPages(); + for (int page = 1; page <= totalPages; page++) { + scan.setPage(page); + PageData nextPage = HBASE_TEMPLATE.page(scan); + System.out.println(StrUtil.format("查询实体: {}", JSONUtil.toJsonStr(nextPage))); + Assertions.assertThat(nextPage).isNotNull(); + } + } + + @Test + @DisplayName("实体分页查询") + public void test03() throws IOException { + + SingleFamilyScan scan = new SingleFamilyScan(); + scan.setFamily("f2") + .setTableName(TABLE_NAME) + .setPage(1) + .setSize(10) + .setReversed(true); + PageData firstPage = HBASE_TEMPLATE.getEntityPage(scan, User.class); + System.out.println(StrUtil.format("第 {} 页数据: {}", 1, JSONUtil.toJsonStr(firstPage))); + + int totalPages = firstPage.getTotalPages(); + for (int page = 2; page <= totalPages; page++) { + scan.setPage(page); + PageData nextPage = HBASE_TEMPLATE.getEntityPage(scan, User.class); + System.out.println(StrUtil.format("第 {} 页数据: {}", page, JSONUtil.toJsonStr(nextPage))); + Assertions.assertThat(nextPage).isNotNull(); + } + } + + @Test + @DisplayName("单列族滚动查询") + public void test04() throws IOException { + + SingleFamilyScan scan = new SingleFamilyScan(); + scan.setFamily("f1") + .setTableName(TABLE_NAME) + .setSize(10) + .setReversed(false); + + int page = 1; + ScrollData first = HBASE_TEMPLATE.scroll(scan); + System.out.println(StrUtil.format("第 {} 页数据: {}", page, JSONUtil.toJsonPrettyStr(first))); + Assertions.assertThat(first).isNotNull(); + scan.setScrollRow(first.getScrollRow()); + + while (true) { + page++; + ScrollData next = HBASE_TEMPLATE.scroll(scan); + if (next == null || CollectionUtil.isEmpty(next.getContent())) { + break; + } + System.out.println(StrUtil.format("第 {} 页数据: {}", page, JSONUtil.toJsonPrettyStr(first))); + scan.setScrollRow(next.getScrollRow()); + } + } + + @Test + @DisplayName("多列族滚动查询") + public void test05() throws IOException { + Map> familyColumnMap = new HashMap<>(); + familyColumnMap.put("f1", CollectionUtil.newArrayList("id", "name", "price")); + familyColumnMap.put("f2", CollectionUtil.newArrayList("id", "name")); + + MultiFamilyScan scan = new MultiFamilyScan(); + scan.setFamilyColumnMap(familyColumnMap) + .setTableName(TABLE_NAME) + .setSize(10) + .setReversed(true); + + ScrollData first = HBASE_TEMPLATE.scroll(scan); + System.out.println(StrUtil.format("查询实体: {}", JSONUtil.toJsonPrettyStr(first))); + Assertions.assertThat(first).isNotNull(); + scan.setScrollRow(first.getScrollRow()); + + while (true) { + ScrollData next = HBASE_TEMPLATE.scroll(scan); + if (next == null || CollectionUtil.isEmpty(next.getContent())) { + break; + } + System.out.println(StrUtil.format("查询实体: {}", JSONUtil.toJsonPrettyStr(next))); + scan.setScrollRow(next.getScrollRow()); + } + } + + @Test + @DisplayName("滚动查询实体") + public void test06() throws IOException { + + SingleFamilyScan scan = new SingleFamilyScan(); + scan.setFamily("f1") + .setTableName(TABLE_NAME) + .setSize(10) + .setReversed(false); + + ScrollData first = HBASE_TEMPLATE.getEntityScroll(scan, Product.class); + System.out.println(StrUtil.format("查询实体: {}", JSONUtil.toJsonPrettyStr(first))); + Assertions.assertThat(first).isNotNull(); + scan.setScrollRow(first.getScrollRow()); + + while (true) { + ScrollData next = HBASE_TEMPLATE.getEntityScroll(scan, Product.class); + if (next == null || CollectionUtil.isEmpty(next.getContent())) { + break; + } + System.out.println(StrUtil.format("查询实体: {}", JSONUtil.toJsonPrettyStr(next))); + scan.setScrollRow(next.getScrollRow()); + } + } + + @Test + @DisplayName("滚动删除全部记录") + public void clear() throws IOException, InterruptedException { + + SingleFamilyScan scan = new SingleFamilyScan(); + scan.setFamily("f1") + .setTableName(TABLE_NAME) + .setSize(100) + .setReversed(false); + + ScrollData first = HBASE_TEMPLATE.scroll(scan); + System.out.println(StrUtil.format("查询实体: {}", JSONUtil.toJsonPrettyStr(first))); + Assertions.assertThat(first).isNotNull(); + scan.setScrollRow(first.getScrollRow()); + HBASE_TEMPLATE.batchDelete(TABLE_NAME, + first.getContent().stream().map(RowDo::getRow).distinct().toArray(String[]::new)); + + while (true) { + ScrollData next = HBASE_TEMPLATE.scroll(scan); + if (next == null || CollectionUtil.isEmpty(next.getContent())) { + break; + } + System.out.println(StrUtil.format("查询实体: {}", JSONUtil.toJsonPrettyStr(next))); + scan.setScrollRow(next.getScrollRow()); + HBASE_TEMPLATE.batchDelete(TABLE_NAME, + next.getContent().stream().map(RowDo::getRow).distinct().toArray(String[]::new)); + } + } + +} diff --git a/codes/javadb/hbase/src/test/java/io/github/dunwu/javadb/hbase/Order.java b/codes/javadb/hbase/src/test/java/io/github/dunwu/javadb/hbase/Order.java new file mode 100644 index 00000000..acef0afd --- /dev/null +++ b/codes/javadb/hbase/src/test/java/io/github/dunwu/javadb/hbase/Order.java @@ -0,0 +1,34 @@ +package io.github.dunwu.javadb.hbase; + +import io.github.dunwu.javadb.hbase.annotation.RowKeyRule; +import io.github.dunwu.javadb.hbase.entity.BaseHbaseEntity; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Date; +import java.util.List; +import java.util.Map; + +/** + * 较为复杂的 Java 实体 + * + * @author Zhang Peng + * @date 2023-11-20 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@RowKeyRule(uk = "getId", length = 20) +public class Order implements BaseHbaseEntity { + + private String id; + private User user; + private List products; + private String desc; + private Date date; + private Map tags; + +} diff --git a/codes/javadb/hbase/src/test/java/io/github/dunwu/javadb/hbase/OrderMapper.java b/codes/javadb/hbase/src/test/java/io/github/dunwu/javadb/hbase/OrderMapper.java new file mode 100644 index 00000000..f6639380 --- /dev/null +++ b/codes/javadb/hbase/src/test/java/io/github/dunwu/javadb/hbase/OrderMapper.java @@ -0,0 +1,30 @@ +package io.github.dunwu.javadb.hbase; + +import io.github.dunwu.javadb.hbase.mapper.BaseHbaseMapper; + +/** + * @author Zhang Peng + * @date 2023-11-15 + */ +public class OrderMapper extends BaseHbaseMapper { + + public OrderMapper(HbaseTemplate hbaseTemplate) { + super(hbaseTemplate); + } + + @Override + public String getTableName() { + return "test"; + } + + @Override + public String getFamily() { + return "f1"; + } + + @Override + public Class getEntityClass() { + return Order.class; + } + +} diff --git a/codes/javadb/hbase/src/test/java/io/github/dunwu/javadb/hbase/Product.java b/codes/javadb/hbase/src/test/java/io/github/dunwu/javadb/hbase/Product.java new file mode 100644 index 00000000..1a486365 --- /dev/null +++ b/codes/javadb/hbase/src/test/java/io/github/dunwu/javadb/hbase/Product.java @@ -0,0 +1,29 @@ +package io.github.dunwu.javadb.hbase; + +import io.github.dunwu.javadb.hbase.annotation.RowKeyRule; +import io.github.dunwu.javadb.hbase.entity.BaseHbaseEntity; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.math.BigDecimal; + +/** + * 产品实体 + * + * @author Zhang Peng + * @date 2023-11-15 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@RowKeyRule(uk = "getId", length = 10) +public class Product implements BaseHbaseEntity { + + private String id; + private String name; + private BigDecimal price; + + private static final long serialVersionUID = -2596114168690429555L; + +} diff --git a/codes/javadb/hbase/src/test/java/io/github/dunwu/javadb/hbase/User.java b/codes/javadb/hbase/src/test/java/io/github/dunwu/javadb/hbase/User.java new file mode 100644 index 00000000..63cc86c8 --- /dev/null +++ b/codes/javadb/hbase/src/test/java/io/github/dunwu/javadb/hbase/User.java @@ -0,0 +1,16 @@ +package io.github.dunwu.javadb.hbase; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class User { + + private int id; + + private String name; + +} \ No newline at end of file diff --git a/codes/javadb/javadb-elasticsearch/pom.xml b/codes/javadb/javadb-elasticsearch/pom.xml deleted file mode 100644 index ab483b6d..00000000 --- a/codes/javadb/javadb-elasticsearch/pom.xml +++ /dev/null @@ -1,87 +0,0 @@ - - - 4.0.0 - - - org.springframework.boot - spring-boot-starter-parent - 2.6.3 - - - io.github.dunwu - javadb-elasticsearch - 1.0.0 - jar - - - 7.16.3 - - - - - org.springframework.boot - spring-boot-starter-data-elasticsearch - - - org.springframework.boot - spring-boot-starter-json - - - org.springframework.boot - spring-boot-starter-test - test - - - - org.projectlombok - lombok - - - cn.hutool - hutool-all - 5.7.20 - - - - co.elastic.clients - elasticsearch-java - 7.16.3 - - - - org.elasticsearch.client - elasticsearch-rest-client - ${elasticsearch.version} - - - org.elasticsearch.client - elasticsearch-rest-high-level-client - ${elasticsearch.version} - - - - - - - com.fasterxml.jackson.core - jackson-databind - 2.12.3 - - - com.fasterxml.jackson.core - jackson-core - 2.12.3 - - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - diff --git a/codes/javadb/javadb-h2/pom.xml b/codes/javadb/javadb-h2/pom.xml deleted file mode 100644 index 5660fec6..00000000 --- a/codes/javadb/javadb-h2/pom.xml +++ /dev/null @@ -1,53 +0,0 @@ - - - 4.0.0 - - - org.springframework.boot - spring-boot-starter-parent - 2.6.3 - - - io.github.dunwu - javadb-h2 - 1.0.0 - jar - - - - org.springframework.boot - spring-boot-starter-data-rest - - - org.springframework.boot - spring-boot-starter-data-jpa - - - org.springframework.boot - spring-boot-starter-test - test - - - org.projectlombok - lombok - - - - - com.h2database - h2 - 2.1.210 - - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - diff --git a/codes/javadb/javadb-hbase/pom.xml b/codes/javadb/javadb-hbase/pom.xml deleted file mode 100644 index 76d12d07..00000000 --- a/codes/javadb/javadb-hbase/pom.xml +++ /dev/null @@ -1,63 +0,0 @@ - - - 4.0.0 - io.github.dunwu - javadb-hbase - 1.0.0 - jar - - - UTF-8 - 1.8 - ${java.version} - ${java.version} - - 1.3.1 - 4.13.1 - 0.5.7 - - - - - org.apache.hbase - hbase-client - - - io.github.dunwu - dunwu-tool-core - - - - - junit - junit - - - - - - - - org.apache.hbase - hbase-client - ${hbase.version} - - - io.github.dunwu - dunwu-tool-core - ${dunwu.version} - - - - - junit - junit - ${junit.version} - test - - - - - diff --git a/codes/javadb/javadb-hbase/src/main/java/io/github/dunwu/javadb/hbase/HBaseConstant.java b/codes/javadb/javadb-hbase/src/main/java/io/github/dunwu/javadb/hbase/HBaseConstant.java deleted file mode 100644 index 63ca6e9d..00000000 --- a/codes/javadb/javadb-hbase/src/main/java/io/github/dunwu/javadb/hbase/HBaseConstant.java +++ /dev/null @@ -1,25 +0,0 @@ -package io.github.dunwu.javadb.hbase; - -public enum HBaseConstant { - - HBASE_ZOOKEEPER_QUORUM("hbase.zookeeper.quorum"), - HBASE_ENABLE("hbase.enable"), - HBASE_MASTER("hbase.master"), - HBASE_ZOOKEEPER_PROPERTY_CLIENTPORT("hbase.zookeeper.property.clientPort"), - HBASE_HCONNECTION_THREADS_MAX("hbase.hconnection.threads.max"), - HBASE_HCONNECTION_THREADS_CORE("hbase.hconnection.threads.core"), - ZOOKEEPER_ZNODE_PARENT("zookeeper.znode.parent"), - HBASE_COLUMN_FAMILY("hbase.column.family"), - HBASE_EXECUTOR_NUM("hbase.executor.num"), - HBASE_IPC_POOL_SIZE("hbase.client.ipc.pool.size"); - - private String key; - - HBaseConstant(String key) { - this.key = key; - } - - public String key() { - return key; - } -} diff --git a/codes/javadb/javadb-hbase/src/main/java/io/github/dunwu/javadb/hbase/HbaseCellEntity.java b/codes/javadb/javadb-hbase/src/main/java/io/github/dunwu/javadb/hbase/HbaseCellEntity.java deleted file mode 100644 index 5b228f41..00000000 --- a/codes/javadb/javadb-hbase/src/main/java/io/github/dunwu/javadb/hbase/HbaseCellEntity.java +++ /dev/null @@ -1,84 +0,0 @@ -package io.github.dunwu.javadb.hbase; - -/** - * HBase Cell 实体 - * @author Zhang Peng - * @since 2019-03-04 - */ -public class HbaseCellEntity { - - private String table; - - private String row; - - private String colFamily; - - private String col; - - private String val; - - public HbaseCellEntity() { - } - - public HbaseCellEntity(String row, String colFamily, String col, String val) { - this.row = row; - this.colFamily = colFamily; - this.col = col; - this.val = val; - } - - public HbaseCellEntity(String table, String row, String colFamily, String col, String val) { - this.table = table; - this.row = row; - this.colFamily = colFamily; - this.col = col; - this.val = val; - } - - public String getTable() { - return table; - } - - public void setTable(String table) { - this.table = table; - } - - public String getRow() { - return row; - } - - public void setRow(String row) { - this.row = row; - } - - public String getColFamily() { - return colFamily; - } - - public void setColFamily(String colFamily) { - this.colFamily = colFamily; - } - - public String getCol() { - return col; - } - - public void setCol(String col) { - this.col = col; - } - - public String getVal() { - return val; - } - - public void setVal(String val) { - this.val = val; - } - - @Override - public String toString() { - return "HbaseCellEntity{" + "table='" + table + '\'' + ", row='" + row + '\'' + ", colFamily='" + colFamily - + '\'' + ", col='" + col + '\'' + ", val='" + val + '\'' + '}'; - } - -} diff --git a/codes/javadb/javadb-hbase/src/main/java/io/github/dunwu/javadb/hbase/HbaseHelper.java b/codes/javadb/javadb-hbase/src/main/java/io/github/dunwu/javadb/hbase/HbaseHelper.java deleted file mode 100644 index c4f86684..00000000 --- a/codes/javadb/javadb-hbase/src/main/java/io/github/dunwu/javadb/hbase/HbaseHelper.java +++ /dev/null @@ -1,369 +0,0 @@ -package io.github.dunwu.javadb.hbase; - -import io.github.dunwu.tool.util.PropertiesUtil; -import org.apache.commons.lang.StringUtils; -import org.apache.hadoop.conf.Configuration; -import org.apache.hadoop.hbase.*; -import org.apache.hadoop.hbase.client.*; -import org.apache.hadoop.hbase.util.Bytes; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.Properties; - -/** - * HBase 服务实现类 - * @author Zhang Peng - * @since 2019-03-01 - */ -public class HbaseHelper { - - private static final String FIRST_CONFIG = "classpath://config//hbase.properties"; - - private static final String SECOND_CONFIG = "classpath://application.properties"; - - private HbaseProperties hbaseProperties; - - private Connection connection; - - public HbaseHelper() throws Exception { - // 初始化参数 - Properties properties = loadConfigFile(); - if (properties == null) { - throw new Exception("读取 Hbase 配置失败,无法建立连接"); - } - Boolean enable = PropertiesUtil.getBoolean(properties, HBaseConstant.HBASE_ENABLE.key(), true); - if (!enable) { - return; - } - String quorum = PropertiesUtil.getString(properties, HBaseConstant.HBASE_ZOOKEEPER_QUORUM.key(), ""); - String hbaseMaster = PropertiesUtil.getString(properties, HBaseConstant.HBASE_MASTER.key(), ""); - String clientPort = - PropertiesUtil.getString(properties, HBaseConstant.HBASE_ZOOKEEPER_PROPERTY_CLIENTPORT.key(), ""); - String znodeParent = PropertiesUtil.getString(properties, HBaseConstant.ZOOKEEPER_ZNODE_PARENT.key(), ""); - String maxThreads = PropertiesUtil.getString(properties, HBaseConstant.HBASE_HCONNECTION_THREADS_MAX.key(), ""); - String coreThreads = - PropertiesUtil.getString(properties, HBaseConstant.HBASE_HCONNECTION_THREADS_CORE.key(), ""); - String columnFamily = PropertiesUtil.getString(properties, HBaseConstant.HBASE_COLUMN_FAMILY.key(), ""); - String hbaseExecutorsNum = PropertiesUtil.getString(properties, HBaseConstant.HBASE_EXECUTOR_NUM.key(), "10"); - String ipcPoolSize = PropertiesUtil.getString(properties, HBaseConstant.HBASE_IPC_POOL_SIZE.key(), "1"); - - hbaseProperties = - new HbaseProperties(hbaseMaster, quorum, clientPort, znodeParent, maxThreads, coreThreads, columnFamily, - hbaseExecutorsNum, ipcPoolSize); - init(hbaseProperties); - } - - private Properties loadConfigFile() { - Properties properties = null; - try { - properties = PropertiesUtil.loadFromFile(FIRST_CONFIG); - } catch (Exception e) { - e.printStackTrace(); - } - - if (properties == null) { - try { - properties = PropertiesUtil.loadFromFile(SECOND_CONFIG); - } catch (Exception e) { - e.printStackTrace(); - return null; - } - } - return properties; - } - - private void init(HbaseProperties hbaseProperties) throws Exception { - try { - // @formatter:off - Configuration configuration = HBaseConfiguration.create(); - configuration.set(HBaseConstant.HBASE_ZOOKEEPER_QUORUM.key(), hbaseProperties.getQuorum()); - configuration.set(HBaseConstant.HBASE_MASTER.key(), hbaseProperties.getHbaseMaster()); - configuration.set(HBaseConstant.HBASE_ZOOKEEPER_PROPERTY_CLIENTPORT.key(), - hbaseProperties.getClientPort()); - configuration.set(HBaseConstant.HBASE_HCONNECTION_THREADS_MAX.key(), - hbaseProperties.getMaxThreads()); - configuration.set(HBaseConstant.HBASE_HCONNECTION_THREADS_CORE.key(), - hbaseProperties.getCoreThreads()); - configuration.set(HBaseConstant.ZOOKEEPER_ZNODE_PARENT.key(), hbaseProperties.getZnodeParent()); - configuration.set(HBaseConstant.HBASE_COLUMN_FAMILY.key(), hbaseProperties.getColumnFamily()); - configuration.set(HBaseConstant.HBASE_IPC_POOL_SIZE.key(), hbaseProperties.getIpcPoolSize()); - // @formatter:on - connection = ConnectionFactory.createConnection(configuration); - } catch (Exception e) { - throw new Exception("hbase链接未创建", e); - } - } - - public HbaseHelper(HbaseProperties hbaseProperties) throws Exception { - this.hbaseProperties = hbaseProperties; - init(hbaseProperties); - } - - public void destory() { - if (connection != null) { - try { - connection.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - } - - public HTableDescriptor[] listTables() throws Exception { - return listTables(null); - } - - public HTableDescriptor[] listTables(String tableName) throws Exception { - if (connection == null) { - throw new Exception("hbase链接未创建"); - } - - HTableDescriptor[] hTableDescriptors = new HTableDescriptor[0]; - try { - if (StringUtils.isEmpty(tableName)) { - hTableDescriptors = connection.getAdmin().listTables(); - } else { - hTableDescriptors = connection.getAdmin().listTables(tableName); - } - } catch (IOException e) { - throw new Exception("执行失败", e); - } - return hTableDescriptors; - } - - /** - * 创建表 - *

- * 等价于: - *

    - *
  • create 'tablename','family1','family2','family3'...
  • - *
- */ - public void createTable(String tableName) throws Exception { - createTable(tableName, new String[] {hbaseProperties.getColumnFamily()}); - } - - /** - * 创建表 - *

- * 等价于: - *

    - *
  • create 'tablename','family1','family2','family3'...
  • - *
- */ - public void createTable(String tableName, String[] colFamilies) throws Exception { - if (connection == null) { - throw new Exception("hbase链接未创建"); - } - - try { - TableName tablename = TableName.valueOf(tableName); - // 如果表存在,先删除 - if (connection.getAdmin().isTableAvailable(tablename)) { - dropTable(tableName); - } - HTableDescriptor tableDescriptor = new HTableDescriptor(tablename); - for (String famliy : colFamilies) { - tableDescriptor.addFamily(new HColumnDescriptor(famliy)); - } - - connection.getAdmin().createTable(tableDescriptor); - } catch (IOException e) { - e.printStackTrace(); - } - } - - /** - * 删除表 - *

- * 等价于: - *

    - *
  • disable 'tablename'
  • - *
  • drop 't1'
  • - *
- * @param name - */ - public void dropTable(String name) throws Exception { - if (connection == null) { - throw new Exception("hbase链接未创建"); - } - - Admin admin = null; - try { - admin = connection.getAdmin(); - TableName tableName = TableName.valueOf(name); - // 如果表存在,先删除 - if (admin.isTableAvailable(tableName)) { - admin.disableTable(tableName); - admin.deleteTable(tableName); - } - } catch (IOException e) { - e.printStackTrace(); - } - } - - private Put toPut(HbaseCellEntity hBaseTableDTO) throws Exception { - if (connection == null) { - throw new Exception("hbase链接未创建"); - } - - Put put = new Put(Bytes.toBytes(hBaseTableDTO.getRow())); - put.addColumn(Bytes.toBytes(hBaseTableDTO.getColFamily()), Bytes.toBytes(hBaseTableDTO.getCol()), - Bytes.toBytes(hBaseTableDTO.getVal())); - return put; - } - - public void delete(String tableName, String rowKey) throws Exception { - if (connection == null) { - throw new Exception("hbase链接未创建"); - } - - Table table = null; - try { - table = connection.getTable(TableName.valueOf(tableName)); - Delete delete = new Delete(Bytes.toBytes(rowKey)); - table.delete(delete); - } catch (IOException e) { - e.printStackTrace(); - throw new Exception("delete失败"); - } - } - - public String resultToString(Result result) { - if (result == null) { - return null; - } - Cell[] cells = result.rawCells(); - StringBuilder sb = new StringBuilder(); - for (Cell cell : cells) { - sb.append("{ "); - sb.append("RowName -> ").append(new String(CellUtil.cloneRow(cell))); - sb.append(", Timetamp -> ").append(cell.getTimestamp()); - sb.append(", Column Family -> ").append(new String(CellUtil.cloneFamily(cell))); - sb.append(", Row Name -> ").append(new String(CellUtil.cloneQualifier(cell))); - sb.append(", value -> ").append(new String(CellUtil.cloneValue(cell))); - sb.append(" }\n"); - } - return sb.toString(); - } - - public Result get(String tableName, String rowKey) throws Exception { - return get(tableName, rowKey, null, null); - } - - public Result get(String tableName, String rowKey, String colFamily, String qualifier) throws Exception { - if (connection == null) { - throw new Exception("hbase链接未创建"); - } - - if (connection.isClosed()) { - throw new Exception("hbase 连接已关闭"); - } - - if (StringUtils.isEmpty(tableName) || StringUtils.isEmpty(rowKey)) { - return null; - } - - Result result = null; - try { - Table table = connection.getTable(TableName.valueOf(tableName)); - Get get = new Get(Bytes.toBytes(rowKey)); - if (StringUtils.isNotEmpty(colFamily)) { - if (StringUtils.isNotEmpty(qualifier)) { - get.addColumn(Bytes.toBytes(colFamily), Bytes.toBytes(qualifier)); - } else { - get.addFamily(Bytes.toBytes(colFamily)); - } - } - result = table.get(get); - } catch (IOException e) { - throw new Exception("查询时发生异常"); - } - return result; - } - - public Result get(String tableName, String rowKey, String colFamily) throws Exception { - return get(tableName, rowKey, colFamily, null); - } - - public Result[] scan(String tableName) throws Exception { - return scan(tableName, null, null, null, null); - } - - public Result[] scan(String tableName, String colFamily, String qualifier, String startRow, String stopRow) - throws Exception { - if (connection == null) { - throw new Exception("hbase链接未创建"); - } - - if (StringUtils.isEmpty(tableName)) { - return null; - } - - ResultScanner resultScanner = null; - List list = new ArrayList<>(); - try { - Table table = connection.getTable(TableName.valueOf(tableName)); - Scan scan = new Scan(); - if (StringUtils.isNotEmpty(colFamily)) { - if (StringUtils.isNotEmpty(qualifier)) { - scan.addColumn(Bytes.toBytes(colFamily), Bytes.toBytes(qualifier)); - } - scan.addFamily(Bytes.toBytes(colFamily)); - } - if (StringUtils.isNotEmpty(startRow)) { - scan.setStartRow(Bytes.toBytes(startRow)); - } - if (StringUtils.isNotEmpty(stopRow)) { - scan.setStopRow(Bytes.toBytes(stopRow)); - } - resultScanner = table.getScanner(scan); - Result result = resultScanner.next(); - while (result != null) { - list.add(result); - result = resultScanner.next(); - } - } catch (IOException e) { - e.printStackTrace(); - } finally { - if (resultScanner != null) { - resultScanner.close(); - } - } - return list.toArray(new Result[0]); - } - - public Result[] scan(String tableName, String colFamily) throws Exception { - return scan(tableName, colFamily, null, null, null); - } - - public Result[] scan(String tableName, String colFamily, String qualifier) throws Exception { - return scan(tableName, colFamily, qualifier, null, null); - } - - private List resultScannerToResults(ResultScanner resultScanner) { - if (resultScanner == null) { - return null; - } - - List list = new ArrayList<>(); - Result result = null; - try { - result = resultScanner.next(); - while (result != null) { - list.add(result); - result = resultScanner.next(); - } - } catch (IOException e) { - e.printStackTrace(); - } - return list; - } - - public HbaseProperties getHbaseProperties() { - return hbaseProperties; - } - -} diff --git a/codes/javadb/javadb-hbase/src/main/java/io/github/dunwu/javadb/hbase/HbaseProperties.java b/codes/javadb/javadb-hbase/src/main/java/io/github/dunwu/javadb/hbase/HbaseProperties.java deleted file mode 100644 index f52b69a3..00000000 --- a/codes/javadb/javadb-hbase/src/main/java/io/github/dunwu/javadb/hbase/HbaseProperties.java +++ /dev/null @@ -1,127 +0,0 @@ -package io.github.dunwu.javadb.hbase; - -import java.io.Serializable; - -/** - * Hbase 配置参数管理对象 - * @author Zhang Peng - */ -public class HbaseProperties implements Serializable { - - private static final long serialVersionUID = 2930639554689310736L; - - private String hbaseMaster; - - private String quorum; - - private String clientPort; - - private String znodeParent; - - private String maxThreads; - - private String coreThreads; - - private String columnFamily; - - private String hbaseExecutorsNum = "10"; - - private String ipcPoolSize; - - public HbaseProperties() { - } - - public HbaseProperties(String hbaseMaster, String quorum, String clientPort, String znodeParent, String maxThreads, - String coreThreads, String columnFamily, String hbaseExecutorsNum, String ipcPoolSize) { - this.hbaseMaster = hbaseMaster; - this.quorum = quorum; - this.clientPort = clientPort; - this.znodeParent = znodeParent; - this.maxThreads = maxThreads; - this.coreThreads = coreThreads; - this.columnFamily = columnFamily; - this.hbaseExecutorsNum = hbaseExecutorsNum; - this.ipcPoolSize = ipcPoolSize; - } - - public String getHbaseMaster() { - return hbaseMaster; - } - - public void setHbaseMaster(String hbaseMaster) { - this.hbaseMaster = hbaseMaster; - } - - public String getQuorum() { - return quorum; - } - - public void setQuorum(String quorum) { - this.quorum = quorum; - } - - public String getClientPort() { - return clientPort; - } - - public void setClientPort(String clientPort) { - this.clientPort = clientPort; - } - - public String getZnodeParent() { - return znodeParent; - } - - public void setZnodeParent(String znodeParent) { - this.znodeParent = znodeParent; - } - - public String getMaxThreads() { - return maxThreads; - } - - public void setMaxThreads(String maxThreads) { - this.maxThreads = maxThreads; - } - - public String getCoreThreads() { - return coreThreads; - } - - public void setCoreThreads(String coreThreads) { - this.coreThreads = coreThreads; - } - - public String getColumnFamily() { - return columnFamily; - } - - public void setColumnFamily(String columnFamily) { - this.columnFamily = columnFamily; - } - - public String getHbaseExecutorsNum() { - return hbaseExecutorsNum; - } - - public void setHbaseExecutorsNum(String hbaseExecutorsNum) { - this.hbaseExecutorsNum = hbaseExecutorsNum; - } - - public String getIpcPoolSize() { - return ipcPoolSize; - } - - public void setIpcPoolSize(String ipcPoolSize) { - this.ipcPoolSize = ipcPoolSize; - } - - @Override - public String toString() { - return "HbaseProperties{" + "quorum='" + quorum + '\'' + ", clientPort='" + clientPort + '\'' - + ", znodeParent='" + znodeParent + '\'' + ", maxThreads='" + maxThreads + '\'' + ", coreThreads='" - + coreThreads + '\'' + ", columnFamily='" + columnFamily + '\'' + ", hbaseExecutorsNum='" - + hbaseExecutorsNum + '\'' + '}'; - } - -} diff --git a/codes/javadb/javadb-hbase/src/test/java/io/github/dunwu/javadb/hbase/HbaseHelperTest.java b/codes/javadb/javadb-hbase/src/test/java/io/github/dunwu/javadb/hbase/HbaseHelperTest.java deleted file mode 100644 index c79aaa54..00000000 --- a/codes/javadb/javadb-hbase/src/test/java/io/github/dunwu/javadb/hbase/HbaseHelperTest.java +++ /dev/null @@ -1,110 +0,0 @@ -package io.github.dunwu.javadb.hbase; - -import io.github.dunwu.javadb.hbase.HbaseHelper; -import org.apache.hadoop.hbase.HTableDescriptor; -import org.apache.hadoop.hbase.client.Result; -import org.junit.Assert; -import org.junit.BeforeClass; -import org.junit.Test; - -/** - * @author Zhang Peng - * @since 2019-03-29 - */ -public class HbaseHelperTest { - - private static HbaseHelper hbaseHelper; - - @BeforeClass - public static void BeforeClass() { - try { - hbaseHelper = new HbaseHelper(); - } catch (Exception e) { - e.printStackTrace(); - } - } - - @Test - public void listTable() throws Exception { - HTableDescriptor[] hTableDescriptors = hbaseHelper.listTables(); - if (hTableDescriptors == null || hTableDescriptors.length <= 0) { - Assert.fail(); - } - - System.out.println("Tables:"); - for (HTableDescriptor item : hTableDescriptors) { - System.out.println(item.getTableName()); - } - } - - @Test - public void createTable() throws Exception { - hbaseHelper.createTable("table1", new String[] {"columnFamliy1", "columnFamliy2"}); - HTableDescriptor[] table1s = hbaseHelper.listTables("table1"); - if (table1s == null || table1s.length <= 0) { - Assert.fail(); - } - - hbaseHelper.createTable("table2", new String[] {"columnFamliy1", "columnFamliy2"}); - table1s = hbaseHelper.listTables("table2"); - if (table1s == null || table1s.length <= 0) { - Assert.fail(); - } - } - - @Test - public void dropTable() throws Exception { - hbaseHelper.dropTable("table1"); - HTableDescriptor[] table1s = hbaseHelper.listTables("table1"); - if (table1s != null && table1s.length > 0) { - Assert.fail(); - } - } - - @Test - public void get() throws Exception { - Result result = hbaseHelper.get("table1", "row1"); - System.out.println(hbaseHelper.resultToString(result)); - - result = hbaseHelper.get("table1", "row2", "columnFamliy1"); - System.out.println(hbaseHelper.resultToString(result)); - } - - @Test - public void scan() throws Exception { - Result[] results = hbaseHelper.scan("table1"); - System.out.println("HbaseUtil.scan(\"table1\") result: "); - if (results.length > 0) { - for (Result r : results) { - System.out.println(hbaseHelper.resultToString(r)); - } - } - - results = hbaseHelper.scan("table1", "columnFamliy1"); - System.out.println("HbaseUtil.scan(\"table1\", \"columnFamliy1\" result: "); - if (results.length > 0) { - for (Result r : results) { - System.out.println(hbaseHelper.resultToString(r)); - } - } - - results = hbaseHelper.scan("table1", "columnFamliy1", "a"); - System.out.println("HbaseUtil.scan(\"table1\", \"columnFamliy1\", \"a\") result: "); - if (results.length > 0) { - for (Result r : results) { - System.out.println(hbaseHelper.resultToString(r)); - } - } - } - - @Test - public void delete() throws Exception { - Result result = hbaseHelper.get("table1", "row1"); - System.out.println(result.toString()); - - hbaseHelper.delete("table1", "row1"); - result = hbaseHelper.get("table1", "row1"); - System.out.println(result.toString()); - } - -} diff --git a/codes/javadb/javadb-hbase/src/test/resources/config/hbase.properties b/codes/javadb/javadb-hbase/src/test/resources/config/hbase.properties deleted file mode 100644 index 4ba3ffd3..00000000 --- a/codes/javadb/javadb-hbase/src/test/resources/config/hbase.properties +++ /dev/null @@ -1,7 +0,0 @@ -hbase.enable = true -hbase.zookeeper.quorum = localhost,xxxx,xxxx -hbase.zookeeper.property.clientPort = 2181 -zookeeper.znode.parent = /hbase -hbase.hconnection.threads.max = 256 -hbase.hconnection.threads.core = 32 -hbase.column.family = F diff --git a/codes/javadb/javadb-mongodb/pom.xml b/codes/javadb/javadb-mongodb/pom.xml deleted file mode 100644 index 864c6dcf..00000000 --- a/codes/javadb/javadb-mongodb/pom.xml +++ /dev/null @@ -1,73 +0,0 @@ - - - 4.0.0 - - - org.springframework.boot - spring-boot-starter-parent - 2.6.3 - - - io.github.dunwu - javadb-mongodb - 1.0.0 - jar - - - - org.springframework.boot - spring-boot-starter-data-mongodb - - - org.springframework.boot - spring-boot-starter-json - - - org.springframework.boot - spring-boot-starter-test - test - - - - org.projectlombok - lombok - - - cn.hutool - hutool-all - 5.7.20 - - - - com.querydsl - querydsl-mongodb - - - org.mongodb - mongo-java-drver - - - - - io.projectreactor - reactor-core - 3.4.14 - - - - junit - junit - test - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - diff --git a/codes/javadb/javadb-mysql/pom.xml b/codes/javadb/javadb-mysql/pom.xml deleted file mode 100644 index 808030e6..00000000 --- a/codes/javadb/javadb-mysql/pom.xml +++ /dev/null @@ -1,45 +0,0 @@ - - - 4.0.0 - - - org.springframework.boot - spring-boot-starter-parent - 2.6.3 - - - io.github.dunwu - javadb-mysql - 1.0.0 - jar - - - - org.springframework.boot - spring-boot-starter-jdbc - - - org.springframework.boot - spring-boot-starter-test - test - - - mysql - mysql-connector-java - - - org.projectlombok - lombok - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - diff --git a/codes/javadb/javadb-redis/pom.xml b/codes/javadb/javadb-redis/pom.xml deleted file mode 100644 index b58fc4a3..00000000 --- a/codes/javadb/javadb-redis/pom.xml +++ /dev/null @@ -1,83 +0,0 @@ - - - 4.0.0 - - - org.springframework.boot - spring-boot-starter-parent - 2.6.3 - - - io.github.dunwu - javadb-redis - 1.0.0 - jar - - - 3.7.2 - - - - - org.springframework.boot - spring-boot-starter-data-redis - - - org.springframework.boot - spring-boot-starter-json - - - org.springframework.boot - spring-boot-starter-test - test - - - - cn.hutool - hutool-all - 5.5.9 - - - org.projectlombok - lombok - - - - - redis.clients - jedis - - - org.redisson - redisson - 3.16.8 - - - - - junit - junit - test - - - - - - - org.redisson - redisson - ${redisson.version} - - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - diff --git a/codes/javadb/javadb-redis/src/test/java/io/github/dunwu/javadb/redis/RedissonStandaloneTest.java b/codes/javadb/javadb-redis/src/test/java/io/github/dunwu/javadb/redis/RedissonStandaloneTest.java deleted file mode 100644 index cf04e72f..00000000 --- a/codes/javadb/javadb-redis/src/test/java/io/github/dunwu/javadb/redis/RedissonStandaloneTest.java +++ /dev/null @@ -1,26 +0,0 @@ -package io.github.dunwu.javadb.redis; - -import org.junit.jupiter.api.Test; -import org.redisson.api.RBucket; -import org.redisson.api.RedissonClient; -import org.springframework.context.ApplicationContext; -import org.springframework.context.support.ClassPathXmlApplicationContext; - -/** - * @author Zhang Peng - * @since 2018/6/19 - */ -public class RedissonStandaloneTest { - - @Test - public void testRedissonConnect() { - ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:redisson-standalone.xml"); - RedissonClient redisson = (RedissonClient) applicationContext.getBean("standalone"); - // 首先获取redis中的key-value对象,key不存在没关系 - RBucket keyObject = redisson.getBucket("key"); - // 如果key存在,就设置key的值为新值value - // 如果key不存在,就设置key的值为value - keyObject.set("value"); - } - -} diff --git a/codes/javadb/javadb-sqlite/pom.xml b/codes/javadb/javadb-sqlite/pom.xml deleted file mode 100644 index c335c5dc..00000000 --- a/codes/javadb/javadb-sqlite/pom.xml +++ /dev/null @@ -1,50 +0,0 @@ - - - 4.0.0 - - - org.springframework.boot - spring-boot-starter-parent - 2.6.3 - - - io.github.dunwu - javadb-sqlite - 1.0.0 - jar - - - - org.springframework.boot - spring-boot-starter-jdbc - - - org.springframework.boot - spring-boot-starter-test - test - - - - org.projectlombok - lombok - - - - - org.xerial - sqlite-jdbc - 3.36.0.2 - - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - diff --git a/codes/javadb/mongodb/pom.xml b/codes/javadb/mongodb/pom.xml new file mode 100644 index 00000000..1c6c2c27 --- /dev/null +++ b/codes/javadb/mongodb/pom.xml @@ -0,0 +1,73 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 2.6.3 + + + io.github.dunwu + javadb-mongodb + 1.0.0 + jar + + + + org.springframework.boot + spring-boot-starter-data-mongodb + + + org.springframework.boot + spring-boot-starter-json + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.projectlombok + lombok + + + cn.hutool + hutool-all + 5.7.20 + + + + com.querydsl + querydsl-mongodb + + + org.mongodb + mongo-java-drver + + + + + io.projectreactor + reactor-core + 3.4.14 + + + + junit + junit + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/SpringBootDataMongodbApplication.java b/codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/SpringBootDataMongodbApplication.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/SpringBootDataMongodbApplication.java rename to codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/SpringBootDataMongodbApplication.java diff --git a/codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/advanced/AdvancedRepository.java b/codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/advanced/AdvancedRepository.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/advanced/AdvancedRepository.java rename to codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/advanced/AdvancedRepository.java diff --git a/codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/advanced/ApplicationConfiguration.java b/codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/advanced/ApplicationConfiguration.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/advanced/ApplicationConfiguration.java rename to codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/advanced/ApplicationConfiguration.java diff --git a/codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/ApplicationConfiguration.java b/codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/ApplicationConfiguration.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/ApplicationConfiguration.java rename to codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/ApplicationConfiguration.java diff --git a/codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/Invoice.java b/codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/Invoice.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/Invoice.java rename to codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/Invoice.java diff --git a/codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/LineItem.java b/codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/LineItem.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/LineItem.java rename to codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/LineItem.java diff --git a/codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/Order.java b/codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/Order.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/Order.java rename to codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/Order.java diff --git a/codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/OrderRepository.java b/codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/OrderRepository.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/OrderRepository.java rename to codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/OrderRepository.java diff --git a/codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/OrderRepositoryCustom.java b/codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/OrderRepositoryCustom.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/OrderRepositoryCustom.java rename to codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/OrderRepositoryCustom.java diff --git a/codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/OrderRepositoryImpl.java b/codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/OrderRepositoryImpl.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/OrderRepositoryImpl.java rename to codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/OrderRepositoryImpl.java diff --git a/codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/OrdersPerCustomer.java b/codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/OrdersPerCustomer.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/OrdersPerCustomer.java rename to codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/OrdersPerCustomer.java diff --git a/codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/customer/Address.java b/codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/customer/Address.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/customer/Address.java rename to codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/customer/Address.java diff --git a/codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/customer/ApplicationConfiguration.java b/codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/customer/ApplicationConfiguration.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/customer/ApplicationConfiguration.java rename to codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/customer/ApplicationConfiguration.java diff --git a/codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/customer/Customer.java b/codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/customer/Customer.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/customer/Customer.java rename to codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/customer/Customer.java diff --git a/codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/customer/CustomerRepository.java b/codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/customer/CustomerRepository.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/customer/CustomerRepository.java rename to codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/customer/CustomerRepository.java diff --git a/codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/immutable/ApplicationConfiguration.java b/codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/immutable/ApplicationConfiguration.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/immutable/ApplicationConfiguration.java rename to codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/immutable/ApplicationConfiguration.java diff --git a/codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/immutable/ImmutablePerson.java b/codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/immutable/ImmutablePerson.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/immutable/ImmutablePerson.java rename to codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/immutable/ImmutablePerson.java diff --git a/codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/projections/Customer.java b/codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/projections/Customer.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/projections/Customer.java rename to codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/projections/Customer.java diff --git a/codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/projections/CustomerDto.java b/codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/projections/CustomerDto.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/projections/CustomerDto.java rename to codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/projections/CustomerDto.java diff --git a/codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/projections/CustomerProjection.java b/codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/projections/CustomerProjection.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/projections/CustomerProjection.java rename to codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/projections/CustomerProjection.java diff --git a/codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/projections/CustomerRepository.java b/codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/projections/CustomerRepository.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/projections/CustomerRepository.java rename to codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/projections/CustomerRepository.java diff --git a/codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/projections/CustomerSummary.java b/codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/projections/CustomerSummary.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/projections/CustomerSummary.java rename to codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/projections/CustomerSummary.java diff --git a/codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/ApplicationConfiguration.java b/codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/ApplicationConfiguration.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/ApplicationConfiguration.java rename to codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/ApplicationConfiguration.java diff --git a/codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/Contact.java b/codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/Contact.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/Contact.java rename to codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/Contact.java diff --git a/codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/ContactRepository.java b/codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/ContactRepository.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/ContactRepository.java rename to codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/ContactRepository.java diff --git a/codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/Person.java b/codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/Person.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/Person.java rename to codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/Person.java diff --git a/codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/Relative.java b/codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/Relative.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/Relative.java rename to codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/Relative.java diff --git a/codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/RelativeRepository.java b/codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/RelativeRepository.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/RelativeRepository.java rename to codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/RelativeRepository.java diff --git a/codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/UserRepository.java b/codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/UserRepository.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/UserRepository.java rename to codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/UserRepository.java diff --git a/codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/package-info.java b/codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/package-info.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/package-info.java rename to codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/package-info.java diff --git a/codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/textsearch/BlogPost.java b/codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/textsearch/BlogPost.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/textsearch/BlogPost.java rename to codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/textsearch/BlogPost.java diff --git a/codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/textsearch/BlogPostRepository.java b/codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/textsearch/BlogPostRepository.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/textsearch/BlogPostRepository.java rename to codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/textsearch/BlogPostRepository.java diff --git a/codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/textsearch/MongoTestConfiguration.java b/codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/textsearch/MongoTestConfiguration.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/textsearch/MongoTestConfiguration.java rename to codes/javadb/mongodb/src/main/java/io/github/dunwu/javadb/mongodb/springboot/textsearch/MongoTestConfiguration.java diff --git a/codes/javadb/javadb-mongodb/src/main/resources/application.properties b/codes/javadb/mongodb/src/main/resources/application.properties similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/resources/application.properties rename to codes/javadb/mongodb/src/main/resources/application.properties diff --git a/codes/javadb/javadb-mongodb/src/main/resources/banner.txt b/codes/javadb/mongodb/src/main/resources/banner.txt similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/resources/banner.txt rename to codes/javadb/mongodb/src/main/resources/banner.txt diff --git a/codes/javadb/javadb-mongodb/src/main/resources/db/books.json b/codes/javadb/mongodb/src/main/resources/db/books.json similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/resources/db/books.json rename to codes/javadb/mongodb/src/main/resources/db/books.json diff --git a/codes/javadb/javadb-mongodb/src/main/resources/db/products.json b/codes/javadb/mongodb/src/main/resources/db/products.json similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/resources/db/products.json rename to codes/javadb/mongodb/src/main/resources/db/products.json diff --git a/codes/javadb/javadb-mongodb/src/main/resources/db/students.json b/codes/javadb/mongodb/src/main/resources/db/students.json similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/resources/db/students.json rename to codes/javadb/mongodb/src/main/resources/db/students.json diff --git a/codes/javadb/javadb-mongodb/src/main/resources/logback.xml b/codes/javadb/mongodb/src/main/resources/logback.xml similarity index 100% rename from codes/javadb/javadb-mongodb/src/main/resources/logback.xml rename to codes/javadb/mongodb/src/main/resources/logback.xml diff --git a/codes/javadb/javadb-mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/advanced/AdvancedIntegrationTests.java b/codes/javadb/mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/advanced/AdvancedIntegrationTests.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/advanced/AdvancedIntegrationTests.java rename to codes/javadb/mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/advanced/AdvancedIntegrationTests.java diff --git a/codes/javadb/javadb-mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/advanced/package-info.java b/codes/javadb/mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/advanced/package-info.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/advanced/package-info.java rename to codes/javadb/mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/advanced/package-info.java diff --git a/codes/javadb/javadb-mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/OrderRepositoryIntegrationTests.java b/codes/javadb/mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/OrderRepositoryIntegrationTests.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/OrderRepositoryIntegrationTests.java rename to codes/javadb/mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/OrderRepositoryIntegrationTests.java diff --git a/codes/javadb/javadb-mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/SpringBooksIntegrationTests.java b/codes/javadb/mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/SpringBooksIntegrationTests.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/SpringBooksIntegrationTests.java rename to codes/javadb/mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/aggregation/SpringBooksIntegrationTests.java diff --git a/codes/javadb/javadb-mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/customer/CustomerRepositoryIntegrationTest.java b/codes/javadb/mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/customer/CustomerRepositoryIntegrationTest.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/customer/CustomerRepositoryIntegrationTest.java rename to codes/javadb/mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/customer/CustomerRepositoryIntegrationTest.java diff --git a/codes/javadb/javadb-mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/customer/package-info.java b/codes/javadb/mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/customer/package-info.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/customer/package-info.java rename to codes/javadb/mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/customer/package-info.java diff --git a/codes/javadb/javadb-mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/immutable/ImmutableEntityIntegrationTest.java b/codes/javadb/mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/immutable/ImmutableEntityIntegrationTest.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/immutable/ImmutableEntityIntegrationTest.java rename to codes/javadb/mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/immutable/ImmutableEntityIntegrationTest.java diff --git a/codes/javadb/javadb-mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/projections/CustomerRepositoryIntegrationTest.java b/codes/javadb/mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/projections/CustomerRepositoryIntegrationTest.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/projections/CustomerRepositoryIntegrationTest.java rename to codes/javadb/mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/projections/CustomerRepositoryIntegrationTest.java diff --git a/codes/javadb/javadb-mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/ContactRepositoryIntegrationTests.java b/codes/javadb/mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/ContactRepositoryIntegrationTests.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/ContactRepositoryIntegrationTests.java rename to codes/javadb/mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/ContactRepositoryIntegrationTests.java diff --git a/codes/javadb/javadb-mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/MongoOperationsIntegrationTests.java b/codes/javadb/mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/MongoOperationsIntegrationTests.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/MongoOperationsIntegrationTests.java rename to codes/javadb/mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/MongoOperationsIntegrationTests.java diff --git a/codes/javadb/javadb-mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/UserRepositoryIntegrationTests.java b/codes/javadb/mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/UserRepositoryIntegrationTests.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/UserRepositoryIntegrationTests.java rename to codes/javadb/mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/querybyexample/UserRepositoryIntegrationTests.java diff --git a/codes/javadb/javadb-mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/textsearch/TextSearchRepositoryTests.java b/codes/javadb/mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/textsearch/TextSearchRepositoryTests.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/textsearch/TextSearchRepositoryTests.java rename to codes/javadb/mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/textsearch/TextSearchRepositoryTests.java diff --git a/codes/javadb/javadb-mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/textsearch/TextSearchTemplateTests.java b/codes/javadb/mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/textsearch/TextSearchTemplateTests.java similarity index 96% rename from codes/javadb/javadb-mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/textsearch/TextSearchTemplateTests.java rename to codes/javadb/mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/textsearch/TextSearchTemplateTests.java index 6f50b0e0..5b6cc98b 100644 --- a/codes/javadb/javadb-mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/textsearch/TextSearchTemplateTests.java +++ b/codes/javadb/mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/textsearch/TextSearchTemplateTests.java @@ -15,7 +15,6 @@ */ package io.github.dunwu.javadb.mongodb.springboot.textsearch; -import io.github.dunwu.javadb.mongodb.springboot.SpringBootDataMongodbApplication; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; diff --git a/codes/javadb/javadb-mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/textsearch/util/BlogPostInitializer.java b/codes/javadb/mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/textsearch/util/BlogPostInitializer.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/textsearch/util/BlogPostInitializer.java rename to codes/javadb/mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/textsearch/util/BlogPostInitializer.java diff --git a/codes/javadb/javadb-mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/textsearch/util/ConsoleResultPrinter.java b/codes/javadb/mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/textsearch/util/ConsoleResultPrinter.java similarity index 100% rename from codes/javadb/javadb-mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/textsearch/util/ConsoleResultPrinter.java rename to codes/javadb/mongodb/src/test/java/io/github/dunwu/javadb/mongodb/springboot/textsearch/util/ConsoleResultPrinter.java diff --git a/codes/javadb/javadb-mongodb/src/test/resources/spring-blog.atom.json b/codes/javadb/mongodb/src/test/resources/spring-blog.atom.json similarity index 100% rename from codes/javadb/javadb-mongodb/src/test/resources/spring-blog.atom.json rename to codes/javadb/mongodb/src/test/resources/spring-blog.atom.json diff --git a/codes/javadb/mysql/pom.xml b/codes/javadb/mysql/pom.xml new file mode 100644 index 00000000..c314b060 --- /dev/null +++ b/codes/javadb/mysql/pom.xml @@ -0,0 +1,45 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 2.6.3 + + + io.github.dunwu + javadb-mysql + 1.0.0 + jar + + + + org.springframework.boot + spring-boot-starter-jdbc + + + org.springframework.boot + spring-boot-starter-test + test + + + mysql + mysql-connector-java + + + org.projectlombok + lombok + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/codes/javadb/javadb-mysql/src/main/java/io/github/dunwu/javadb/mysql/springboot/SpringBootDataJdbcApplication.java b/codes/javadb/mysql/src/main/java/io/github/dunwu/javadb/mysql/springboot/SpringBootDataJdbcApplication.java similarity index 100% rename from codes/javadb/javadb-mysql/src/main/java/io/github/dunwu/javadb/mysql/springboot/SpringBootDataJdbcApplication.java rename to codes/javadb/mysql/src/main/java/io/github/dunwu/javadb/mysql/springboot/SpringBootDataJdbcApplication.java diff --git a/codes/javadb/javadb-mysql/src/main/java/io/github/dunwu/javadb/mysql/springboot/User.java b/codes/javadb/mysql/src/main/java/io/github/dunwu/javadb/mysql/springboot/User.java similarity index 100% rename from codes/javadb/javadb-mysql/src/main/java/io/github/dunwu/javadb/mysql/springboot/User.java rename to codes/javadb/mysql/src/main/java/io/github/dunwu/javadb/mysql/springboot/User.java diff --git a/codes/javadb/javadb-mysql/src/main/java/io/github/dunwu/javadb/mysql/springboot/UserDao.java b/codes/javadb/mysql/src/main/java/io/github/dunwu/javadb/mysql/springboot/UserDao.java similarity index 100% rename from codes/javadb/javadb-mysql/src/main/java/io/github/dunwu/javadb/mysql/springboot/UserDao.java rename to codes/javadb/mysql/src/main/java/io/github/dunwu/javadb/mysql/springboot/UserDao.java diff --git a/codes/javadb/javadb-mysql/src/main/java/io/github/dunwu/javadb/mysql/springboot/UserDaoExecutor.java b/codes/javadb/mysql/src/main/java/io/github/dunwu/javadb/mysql/springboot/UserDaoExecutor.java similarity index 100% rename from codes/javadb/javadb-mysql/src/main/java/io/github/dunwu/javadb/mysql/springboot/UserDaoExecutor.java rename to codes/javadb/mysql/src/main/java/io/github/dunwu/javadb/mysql/springboot/UserDaoExecutor.java diff --git a/codes/javadb/javadb-mysql/src/main/java/io/github/dunwu/javadb/mysql/springboot/UserDaoImpl.java b/codes/javadb/mysql/src/main/java/io/github/dunwu/javadb/mysql/springboot/UserDaoImpl.java similarity index 100% rename from codes/javadb/javadb-mysql/src/main/java/io/github/dunwu/javadb/mysql/springboot/UserDaoImpl.java rename to codes/javadb/mysql/src/main/java/io/github/dunwu/javadb/mysql/springboot/UserDaoImpl.java diff --git a/codes/javadb/javadb-mysql/src/main/resources/application.properties b/codes/javadb/mysql/src/main/resources/application.properties similarity index 100% rename from codes/javadb/javadb-mysql/src/main/resources/application.properties rename to codes/javadb/mysql/src/main/resources/application.properties diff --git a/codes/javadb/javadb-mysql/src/main/resources/banner.txt b/codes/javadb/mysql/src/main/resources/banner.txt similarity index 100% rename from codes/javadb/javadb-mysql/src/main/resources/banner.txt rename to codes/javadb/mysql/src/main/resources/banner.txt diff --git a/codes/javadb/javadb-mysql/src/main/resources/logback.xml b/codes/javadb/mysql/src/main/resources/logback.xml similarity index 100% rename from codes/javadb/javadb-mysql/src/main/resources/logback.xml rename to codes/javadb/mysql/src/main/resources/logback.xml diff --git a/codes/javadb/javadb-mysql/src/main/resources/sql/data.sql b/codes/javadb/mysql/src/main/resources/sql/data.sql similarity index 100% rename from codes/javadb/javadb-mysql/src/main/resources/sql/data.sql rename to codes/javadb/mysql/src/main/resources/sql/data.sql diff --git a/codes/javadb/javadb-mysql/src/main/resources/sql/schema.sql b/codes/javadb/mysql/src/main/resources/sql/schema.sql similarity index 100% rename from codes/javadb/javadb-mysql/src/main/resources/sql/schema.sql rename to codes/javadb/mysql/src/main/resources/sql/schema.sql diff --git a/codes/javadb/javadb-mysql/src/test/java/io/github/dunwu/javadb/mysql/springboot/MysqlDemoTest.java b/codes/javadb/mysql/src/test/java/io/github/dunwu/javadb/mysql/springboot/MysqlDemoTest.java similarity index 91% rename from codes/javadb/javadb-mysql/src/test/java/io/github/dunwu/javadb/mysql/springboot/MysqlDemoTest.java rename to codes/javadb/mysql/src/test/java/io/github/dunwu/javadb/mysql/springboot/MysqlDemoTest.java index c8a2236d..6b0f2dbf 100644 --- a/codes/javadb/javadb-mysql/src/test/java/io/github/dunwu/javadb/mysql/springboot/MysqlDemoTest.java +++ b/codes/javadb/mysql/src/test/java/io/github/dunwu/javadb/mysql/springboot/MysqlDemoTest.java @@ -6,11 +6,15 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.sql.*; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; /** * Mysql 测试例 - * @author Zhang Peng + * @author Zhang Peng * @see https://dev.mysql.com/doc/connector-j/5.1/en/ */ public class MysqlDemoTest { diff --git a/codes/javadb/javadb-mysql/src/test/java/io/github/dunwu/javadb/mysql/springboot/SpringBootDataJdbcTest.java b/codes/javadb/mysql/src/test/java/io/github/dunwu/javadb/mysql/springboot/SpringBootDataJdbcTest.java similarity index 100% rename from codes/javadb/javadb-mysql/src/test/java/io/github/dunwu/javadb/mysql/springboot/SpringBootDataJdbcTest.java rename to codes/javadb/mysql/src/test/java/io/github/dunwu/javadb/mysql/springboot/SpringBootDataJdbcTest.java diff --git a/codes/javadb/pom.xml b/codes/javadb/pom.xml index f5b53999..7e45ab8c 100644 --- a/codes/javadb/pom.xml +++ b/codes/javadb/pom.xml @@ -1,6 +1,6 @@ + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 io.github.dunwu @@ -8,13 +8,718 @@ 1.0.0 pom + + 1.8 + ${java.version} + ${java.version} + UTF-8 + UTF-8 + 3.5.4 + + + 2.2.2.RELEASE + 3.3.6 + + + 1.6.2 + 2.3.3 + 1.2 + + + 4.2.0 + 1.1.24 + 1.26.6 + 3.5.6 + 1.3.5 + 3.4.2 + 2.0.2 + 4.0.4 + 5.1.8 + 6.2.5.Final + 3.8.7 + 2.4.15 + + + 6.4.1 + 1.2.70 + 2.56 + 1.18.16 + 3.16.1 + 0.10.2 + + + 1.12 + 4.3 + 1.20 + 2.7 + 1.6 + 1.4 + 2.7 + 3.9 + 3.6.1 + 2.8.0 + 1.10.0 + 31.1-jre + 5.8.9 + + + 4.1.2 + 3.4.2 + 7.11.0.Final + 0.11.1 + 1.14.2 + 5.6.0 + 2.4.7.Final + 1.7.1 + 2.9.2 + 1.5.22 + 1.9.0.RELEASE + 0.4.19 + 3.4.0 + 3.1.1 + 3.24.0 + + + + 3.0.0 + 3.2.0 + 3.1.1 + 3.1.0 + 3.8.0 + 3.1.2 + 2.8.2 + 3.0.0-M2 + 2.22.2 + 3.2.0 + 2.5.2 + 3.2.0 + 3.2.0 + 3.2.0 + 3.12.0 + 3.1.0 + 3.2.3 + 3.9.0 + 3.2.1 + 2.22.2 + 3.2.3 + + + 3.0.0 + 1.6.0 + 1.2.2 + 2.7 + 1.0.2 + + + 1.6 + 1.6.8 + + + 1.4.0 + + - javadb-h2 - javadb-hbase - javadb-mysql - javadb-redis - javadb-sqlite - javadb-mongodb - javadb-elasticsearch + h2 + hbase + mysql + redis + sqlite + mongodb + elasticsearch + + + + + + org.springframework.boot + spring-boot-dependencies + ${spring-boot.version} + pom + import + + + + + + javax.servlet.jsp + javax.servlet.jsp-api + ${javax.servlet.jsp-api.version} + + + javax.servlet.jsp.jstl + jstl + ${javax.servlet.jsp.jstl.version} + + + com.sun.mail + javax.mail + ${javax.mail.version} + + + + + + org.apache.curator + curator-framework + ${curator.version} + + + org.apache.curator + curator-test + ${curator.version} + + + com.alibaba + druid-spring-boot-starter + ${druid.version} + + + com.alibaba + druid + ${druid.version} + + + com.github.tobato + fastdfs-client + ${fastdfs.version} + + + org.mybatis + mybatis + ${mybatis.version} + + + org.mybatis.generator + mybatis-generator-core + ${mybatis-generator.version} + + + org.mybatis + mybatis-spring + ${mybatis-spring.version} + + + com.baomidou + mybatis-plus + ${mybatis-plus.version} + + + com.baomidou + mybatis-plus-boot-starter + ${mybatis-plus.version} + + + com.baomidou + mybatis-plus-core + ${mybatis-plus.version} + + + com.baomidou + mybatis-plus-extension + ${mybatis-plus.version} + + + com.baomidou + mybatis-plus-generator + ${mybatis-plus.version} + + + tk.mybatis + mapper + ${mybatis.mapper.version} + + + com.github.pagehelper + pagehelper + ${mybatis.pagehelper.version} + + + p6spy + p6spy + ${p6spy.version} + + + org.hibernate.validator + hibernate-validator + ${hibernate-validator.version} + + + org.apache.hbase + hbase-client + ${hbase.version} + + + + + + com.github.dozermapper + dozer-core + ${dozer.version} + + + com.github.dozermapper + dozer-spring4 + ${dozer.version} + + + com.github.dozermapper + dozer-spring-boot-starter + ${dozer.version} + + + + org.reflections + reflections + ${reflections.version} + + + com.alibaba + fastjson + ${fastjson.version} + + + org.projectlombok + lombok + ${lombok.version} + + + com.google.protobuf + protobuf-java + ${protobuf-java.version} + + + de.ruedigermoeller + fst + ${fst.version} + + + + + commons-codec + commons-codec + ${commons-codec.version} + + + org.apache.commons + commons-configuration2 + ${commons-configuration2.version} + + + org.apache.commons + commons-collections4 + ${commons-collections4.version} + + + org.apache.commons + commons-compress + ${commons-compress.version} + + + org.apache.commons + commons-csv + ${commons-csv.version} + + + commons-fileupload + commons-fileupload + ${commons-fileupload.version} + + + commons-io + commons-io + ${commons-io.version} + + + org.apache.commons + commons-lang3 + ${commons-lang3.version} + + + org.apache.commons + commons-math3 + ${commons-math3.version} + + + org.apache.commons + commons-pool2 + ${commons-pool2.version} + + + org.apache.commons + commons-text + ${commons-text.version} + + + com.google.guava + guava + ${guava.version} + + + cn.hutool + hutool-all + ${hutool.version} + + + + + org.apache.poi + poi + ${poi.version} + + + org.apache.poi + poi-ooxml + ${poi.version} + + + xml-apis + xml-apis + 1.4.01 + + + org.apache.xmlbeans + xmlbeans + 3.1.0 + + + com.lmax + disruptor + ${disruptor.version} + + + org.drools + drools-core + ${drools.version} + + + org.drools + drools-compiler + ${drools.version} + + + org.drools + drools-decisiontables + ${drools.version} + + + org.drools + drools-templates + ${drools.version} + + + org.kie + kie-api + ${drools.version} + + + io.jsonwebtoken + jjwt-api + ${jjwt.version} + + + io.jsonwebtoken + jjwt-impl + ${jjwt.version} + + + io.jsonwebtoken + jjwt-jackson + ${jjwt.version} + + + org.jsoup + jsoup + ${jsoup.version} + + + org.mvel + mvel2 + ${mvel.version} + + + org.apache.shiro + shiro-spring + ${shiro.version} + + + io.springfox + springfox-swagger2 + ${swagger.ui} + + + io.springfox + springfox-swagger-ui + ${swagger.ui} + + + io.swagger + swagger-annotations + ${swagger-annotations.version} + + + com.spring4all + swagger-spring-boot-starter + ${swagger-spring-boot-starter.version} + + + net.coobird + thumbnailator + ${thumbnailator.version} + + + com.google.zxing + core + ${zxing.version} + + + com.alibaba + easyexcel + ${easyexcel.version} + + + + com.github.javaparser + javaparser-symbol-solver-core + ${javaparser.version} + + + + org.junit.jupiter + junit-jupiter + ${junit-jupiter.version} + + + + + org.apache.hadoop + hadoop-hdfs + ${hadoop.version} + + + org.apache.hadoop + hadoop-auth + ${hadoop.version} + + + org.apache.hadoop + hadoop-common + ${hadoop.version} + + + org.apache.hadoop + hadoop-client + ${hadoop.version} + + + commons-logging + commons-logging + + + jsr305 + com.google.code.findbugs + + + nimbus-jose-jwt + com.nimbusds + + + curator-client + org.apache.curator + + + + + + + + + + + + org.apache.maven.plugins + maven-antrun-plugin + ${maven-antrun-plugin.version} + + + org.apache.maven.plugins + maven-assembly-plugin + ${maven-assembly-plugin.version} + + + org.apache.maven.plugins + maven-checkstyle-plugin + ${maven-checkstyle-plugin.version} + + + org.apache.maven.plugins + maven-clean-plugin + ${maven-clean-plugin.version} + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler-plugin.version} + + + org.apache.maven.plugins + maven-dependency-plugin + ${maven-dependency-plugin.version} + + + org.apache.maven.plugins + maven-deploy-plugin + ${maven-deploy-plugin.version} + + + org.apache.maven.plugins + maven-enforcer-plugin + ${maven-enforcer-plugin.version} + + + org.apache.maven.plugins + maven-failsafe-plugin + ${maven-failsafe-plugin.version} + + + org.apache.maven.plugins + maven-gpg-plugin + ${maven-gpg-plugin.version} + + + org.apache.maven.plugins + maven-help-plugin + ${maven-help-plugin.version} + + + org.apache.maven.plugins + maven-install-plugin + ${maven-install-plugin.version} + + + org.apache.maven.plugins + maven-invoker-plugin + ${maven-invoker-plugin.version} + + + org.apache.maven.plugins + maven-jar-plugin + ${maven-jar-plugin.version} + + + org.apache.maven.plugins + maven-javadoc-plugin + ${maven-javadoc-plugin.version} + + + org.apache.maven.plugins + maven-pmd-plugin + ${maven-pmd-plugin.version} + + + org.apache.maven.plugins + maven-resources-plugin + ${maven-resources-plugin.version} + + + org.apache.maven.plugins + maven-shade-plugin + ${maven-shade-plugin.version} + + + org.apache.maven.plugins + maven-site-plugin + ${maven-site-plugin.version} + + + org.apache.maven.plugins + maven-source-plugin + ${maven-source-plugin.version} + + + org.apache.maven.plugins + maven-surefire-plugin + ${maven-surefire-plugin.version} + + + org.apache.maven.plugins + maven-war-plugin + ${maven-war-plugin.version} + + + + + org.codehaus.mojo + build-helper-maven-plugin + ${build-helper-maven-plugin.version} + + + org.codehaus.mojo + exec-maven-plugin + ${exec-maven-plugin.version} + + + org.codehaus.mojo + flatten-maven-plugin + ${flatten-maven-plugin.version} + + + org.codehaus.mojo + versions-maven-plugin + ${versions-maven-plugin.version} + + + org.codehaus.mojo + xml-maven-plugin + ${xml-maven-plugin.version} + + + + + org.basepom.maven + duplicate-finder-maven-plugin + ${duplicate-finder-maven-plugin.version} + + + + + diff --git a/codes/javadb/redis/pom.xml b/codes/javadb/redis/pom.xml new file mode 100644 index 00000000..3150ffba --- /dev/null +++ b/codes/javadb/redis/pom.xml @@ -0,0 +1,67 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 2.7.18 + + + io.github.dunwu + javadb-redis + 1.0.0 + jar + + + 3.7.2 + + + + + org.springframework.boot + spring-boot-starter-data-redis + + + org.springframework.boot + spring-boot-starter-json + + + org.springframework.boot + spring-boot-starter-test + test + + + + cn.hutool + hutool-all + 5.8.27 + + + org.projectlombok + lombok + + + + + redis.clients + jedis + + + org.redisson + redisson + 3.29.0 + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/codes/javadb/javadb-redis/src/main/java/io/github/dunwu/javadb/redis/springboot/RedisAutoConfiguration.java b/codes/javadb/redis/src/main/java/io/github/dunwu/javadb/redis/springboot/RedisAutoConfiguration.java similarity index 77% rename from codes/javadb/javadb-redis/src/main/java/io/github/dunwu/javadb/redis/springboot/RedisAutoConfiguration.java rename to codes/javadb/redis/src/main/java/io/github/dunwu/javadb/redis/springboot/RedisAutoConfiguration.java index 6ea387bf..eb7c40e1 100644 --- a/codes/javadb/javadb-redis/src/main/java/io/github/dunwu/javadb/redis/springboot/RedisAutoConfiguration.java +++ b/codes/javadb/redis/src/main/java/io/github/dunwu/javadb/redis/springboot/RedisAutoConfiguration.java @@ -1,14 +1,24 @@ package io.github.dunwu.javadb.redis.springboot; +import cn.hutool.core.util.StrUtil; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; +import org.redisson.Redisson; +import org.redisson.api.RedissonClient; +import org.redisson.config.Config; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.core.*; +import org.springframework.data.redis.core.HashOperations; +import org.springframework.data.redis.core.ListOperations; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.SetOperations; +import org.springframework.data.redis.core.ValueOperations; +import org.springframework.data.redis.core.ZSetOperations; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; @@ -22,6 +32,19 @@ public class RedisAutoConfiguration { @Autowired private ObjectMapper objectMapper; + @Value("${spring.redis.host:localhost}") + private String host; + + @Value("${spring.redis.port:6379}") + private String port; + + @Bean + public RedissonClient redissonClient() { + Config config = new Config(); + config.useSingleServer().setAddress(StrUtil.format("redis://{}:{}", host, port)); + return Redisson.create(config); + } + @Bean public HashOperations hashOperations(RedisTemplate redisTemplate) { return redisTemplate.opsForHash(); @@ -44,7 +67,6 @@ public RedisTemplate redisTemplate(RedisConnectionFactory factor // 使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式) Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class); serializer.setObjectMapper(objectMapper); - RedisTemplate template = new RedisTemplate<>(); // 配置连接工厂 template.setConnectionFactory(factory); diff --git a/codes/javadb/javadb-redis/src/main/java/io/github/dunwu/javadb/redis/springboot/SpringBootDataRedisApplication.java b/codes/javadb/redis/src/main/java/io/github/dunwu/javadb/redis/springboot/SpringBootDataRedisApplication.java similarity index 100% rename from codes/javadb/javadb-redis/src/main/java/io/github/dunwu/javadb/redis/springboot/SpringBootDataRedisApplication.java rename to codes/javadb/redis/src/main/java/io/github/dunwu/javadb/redis/springboot/SpringBootDataRedisApplication.java diff --git a/codes/javadb/javadb-redis/src/main/java/io/github/dunwu/javadb/redis/springboot/data/User.java b/codes/javadb/redis/src/main/java/io/github/dunwu/javadb/redis/springboot/data/User.java similarity index 100% rename from codes/javadb/javadb-redis/src/main/java/io/github/dunwu/javadb/redis/springboot/data/User.java rename to codes/javadb/redis/src/main/java/io/github/dunwu/javadb/redis/springboot/data/User.java diff --git a/codes/javadb/javadb-redis/src/main/java/io/github/dunwu/javadb/redis/springboot/data/UserService.java b/codes/javadb/redis/src/main/java/io/github/dunwu/javadb/redis/springboot/data/UserService.java similarity index 100% rename from codes/javadb/javadb-redis/src/main/java/io/github/dunwu/javadb/redis/springboot/data/UserService.java rename to codes/javadb/redis/src/main/java/io/github/dunwu/javadb/redis/springboot/data/UserService.java diff --git a/codes/javadb/javadb-redis/src/main/java/io/github/dunwu/javadb/redis/springboot/data/UserServiceImpl.java b/codes/javadb/redis/src/main/java/io/github/dunwu/javadb/redis/springboot/data/UserServiceImpl.java similarity index 100% rename from codes/javadb/javadb-redis/src/main/java/io/github/dunwu/javadb/redis/springboot/data/UserServiceImpl.java rename to codes/javadb/redis/src/main/java/io/github/dunwu/javadb/redis/springboot/data/UserServiceImpl.java diff --git a/codes/javadb/javadb-redis/src/main/resources/application.properties b/codes/javadb/redis/src/main/resources/application.properties similarity index 100% rename from codes/javadb/javadb-redis/src/main/resources/application.properties rename to codes/javadb/redis/src/main/resources/application.properties diff --git a/codes/javadb/javadb-redis/src/main/resources/banner.txt b/codes/javadb/redis/src/main/resources/banner.txt similarity index 100% rename from codes/javadb/javadb-redis/src/main/resources/banner.txt rename to codes/javadb/redis/src/main/resources/banner.txt diff --git a/codes/javadb/javadb-redis/src/main/resources/logback.xml b/codes/javadb/redis/src/main/resources/logback.xml similarity index 100% rename from codes/javadb/javadb-redis/src/main/resources/logback.xml rename to codes/javadb/redis/src/main/resources/logback.xml diff --git a/codes/javadb/redis/src/test/java/io/github/dunwu/javadb/redis/RedissonStandaloneTest.java b/codes/javadb/redis/src/test/java/io/github/dunwu/javadb/redis/RedissonStandaloneTest.java new file mode 100644 index 00000000..ed3c13a3 --- /dev/null +++ b/codes/javadb/redis/src/test/java/io/github/dunwu/javadb/redis/RedissonStandaloneTest.java @@ -0,0 +1,104 @@ +package io.github.dunwu.javadb.redis; + +import cn.hutool.core.thread.ThreadUtil; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.redisson.api.RBucket; +import org.redisson.api.RLock; +import org.redisson.api.RedissonClient; +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.TimeUnit; + +/** + * @author Zhang Peng + * @since 2018/6/19 + */ +@Slf4j +public class RedissonStandaloneTest { + + private static RedissonClient redissonClient; + + static { + ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:redisson-standalone.xml"); + redissonClient = (RedissonClient) applicationContext.getBean("standalone"); + } + + @Test + @DisplayName("测试连接") + public void testRedissonConnect() { + // 首先获取redis中的key-value对象,key不存在没关系 + RBucket keyObject = redissonClient.getBucket("key"); + // 如果key存在,就设置key的值为新值value + // 如果key不存在,就设置key的值为value + keyObject.set("value"); + String value = keyObject.get(); + System.out.println("value=" + value); + } + + @Test + @DisplayName("分布式锁测试") + public void testLock() { + // 两个线程任务都是不断再尝试获取或,直到成功获取锁后才推出任务 + // 第一个线程获取到锁后,第二个线程需要等待 5 秒超时后才能获取到锁 + CountDownLatch latch = new CountDownLatch(2); + ExecutorService executorService = ThreadUtil.newFixedExecutor(2, "获取锁", true); + executorService.submit(new Task(latch)); + executorService.submit(new Task(latch)); + + try { + latch.await(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + // 输出: + // 17:59:25.896 [获取锁1] [INFO ] i.g.d.j.redis.RedissonStandaloneTest.run - + // 获取分布式锁成功 + // 17:59:26.888 [获取锁0] [WARN ] i.g.d.j.redis.RedissonStandaloneTest.run - + // 获取分布式锁失败 + // 17:59:27.889 [获取锁0] [WARN ] i.g.d.j.redis.RedissonStandaloneTest.run - + // 获取分布式锁失败 + // 17:59:28.891 [获取锁0] [WARN ] i.g.d.j.redis.RedissonStandaloneTest.run - + // 获取分布式锁失败 + // 17:59:29.892 [获取锁0] [WARN ] i.g.d.j.redis.RedissonStandaloneTest.run - + // 获取分布式锁失败 + // 17:59:30.895 [获取锁0] [WARN ] i.g.d.j.redis.RedissonStandaloneTest.run - + // 获取分布式锁失败 + // 17:59:30.896 [获取锁0] [INFO ] i.g.d.j.redis.RedissonStandaloneTest.run - + // 获取分布式锁成功 + + static class Task implements Runnable { + + private CountDownLatch latch; + + public Task(CountDownLatch latch) { + this.latch = latch; + } + + @Override + public void run() { + while (true) { + RLock lock = redissonClient.getLock("test_lock"); + try { + boolean isLock = lock.tryLock(1, 5, TimeUnit.SECONDS); + if (isLock) { + log.info("获取分布式锁成功"); + break; + } else { + log.warn("获取分布式锁失败"); + } + } catch (Exception e) { + log.error("获取分布式锁异常", e); + } + } + latch.countDown(); + } + + } + +} diff --git a/codes/javadb/javadb-redis/src/test/java/io/github/dunwu/javadb/redis/jedis/JedisDemoTest.java b/codes/javadb/redis/src/test/java/io/github/dunwu/javadb/redis/jedis/JedisDemoTest.java similarity index 98% rename from codes/javadb/javadb-redis/src/test/java/io/github/dunwu/javadb/redis/jedis/JedisDemoTest.java rename to codes/javadb/redis/src/test/java/io/github/dunwu/javadb/redis/jedis/JedisDemoTest.java index 18126676..a48d1ea2 100644 --- a/codes/javadb/javadb-redis/src/test/java/io/github/dunwu/javadb/redis/jedis/JedisDemoTest.java +++ b/codes/javadb/redis/src/test/java/io/github/dunwu/javadb/redis/jedis/JedisDemoTest.java @@ -15,7 +15,7 @@ /** * Jedis 测试例 * - * @author Zhang Peng + * @author Zhang Peng * @see https://github.com/xetorthio/jedis */ @Slf4j diff --git a/codes/javadb/javadb-redis/src/test/java/io/github/dunwu/javadb/redis/jedis/JedisPoolDemoTest.java b/codes/javadb/redis/src/test/java/io/github/dunwu/javadb/redis/jedis/JedisPoolDemoTest.java similarity index 90% rename from codes/javadb/javadb-redis/src/test/java/io/github/dunwu/javadb/redis/jedis/JedisPoolDemoTest.java rename to codes/javadb/redis/src/test/java/io/github/dunwu/javadb/redis/jedis/JedisPoolDemoTest.java index d39ea123..e52d65ba 100644 --- a/codes/javadb/javadb-redis/src/test/java/io/github/dunwu/javadb/redis/jedis/JedisPoolDemoTest.java +++ b/codes/javadb/redis/src/test/java/io/github/dunwu/javadb/redis/jedis/JedisPoolDemoTest.java @@ -1,13 +1,11 @@ package io.github.dunwu.javadb.redis.jedis; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; @@ -16,9 +14,8 @@ import java.util.Set; /** - * @author Zhang Peng + * @author Zhang Peng */ -@RunWith(SpringJUnit4ClassRunner.class) @ActiveProfiles("dev") @ContextConfiguration(locations = {"classpath:/applicationContext.xml"}) public class JedisPoolDemoTest { diff --git a/codes/javadb/javadb-redis/src/test/java/io/github/dunwu/javadb/redis/jedis/rank/RankDemo.java b/codes/javadb/redis/src/test/java/io/github/dunwu/javadb/redis/jedis/rank/RankDemo.java similarity index 100% rename from codes/javadb/javadb-redis/src/test/java/io/github/dunwu/javadb/redis/jedis/rank/RankDemo.java rename to codes/javadb/redis/src/test/java/io/github/dunwu/javadb/redis/jedis/rank/RankDemo.java diff --git a/codes/javadb/javadb-redis/src/test/java/io/github/dunwu/javadb/redis/jedis/rank/RankDemoTests.java b/codes/javadb/redis/src/test/java/io/github/dunwu/javadb/redis/jedis/rank/RankDemoTests.java similarity index 100% rename from codes/javadb/javadb-redis/src/test/java/io/github/dunwu/javadb/redis/jedis/rank/RankDemoTests.java rename to codes/javadb/redis/src/test/java/io/github/dunwu/javadb/redis/jedis/rank/RankDemoTests.java diff --git a/codes/javadb/javadb-redis/src/test/java/io/github/dunwu/javadb/redis/jedis/rank/RankElement.java b/codes/javadb/redis/src/test/java/io/github/dunwu/javadb/redis/jedis/rank/RankElement.java similarity index 100% rename from codes/javadb/javadb-redis/src/test/java/io/github/dunwu/javadb/redis/jedis/rank/RankElement.java rename to codes/javadb/redis/src/test/java/io/github/dunwu/javadb/redis/jedis/rank/RankElement.java diff --git a/codes/javadb/javadb-redis/src/test/java/io/github/dunwu/javadb/redis/jedis/rank/RankRegion.java b/codes/javadb/redis/src/test/java/io/github/dunwu/javadb/redis/jedis/rank/RankRegion.java similarity index 100% rename from codes/javadb/javadb-redis/src/test/java/io/github/dunwu/javadb/redis/jedis/rank/RankRegion.java rename to codes/javadb/redis/src/test/java/io/github/dunwu/javadb/redis/jedis/rank/RankRegion.java diff --git a/codes/javadb/javadb-redis/src/test/java/io/github/dunwu/javadb/redis/jedis/rank/RankRegionElement.java b/codes/javadb/redis/src/test/java/io/github/dunwu/javadb/redis/jedis/rank/RankRegionElement.java similarity index 100% rename from codes/javadb/javadb-redis/src/test/java/io/github/dunwu/javadb/redis/jedis/rank/RankRegionElement.java rename to codes/javadb/redis/src/test/java/io/github/dunwu/javadb/redis/jedis/rank/RankRegionElement.java diff --git a/codes/javadb/javadb-redis/src/test/resources/applicationContext.xml b/codes/javadb/redis/src/test/resources/applicationContext.xml similarity index 100% rename from codes/javadb/javadb-redis/src/test/resources/applicationContext.xml rename to codes/javadb/redis/src/test/resources/applicationContext.xml diff --git a/codes/javadb/javadb-redis/src/test/resources/config.xml b/codes/javadb/redis/src/test/resources/config.xml similarity index 100% rename from codes/javadb/javadb-redis/src/test/resources/config.xml rename to codes/javadb/redis/src/test/resources/config.xml diff --git a/codes/javadb/javadb-redis/src/test/resources/properties/application-dev.properties b/codes/javadb/redis/src/test/resources/properties/application-dev.properties similarity index 100% rename from codes/javadb/javadb-redis/src/test/resources/properties/application-dev.properties rename to codes/javadb/redis/src/test/resources/properties/application-dev.properties diff --git a/codes/javadb/javadb-redis/src/test/resources/properties/application-test.properties b/codes/javadb/redis/src/test/resources/properties/application-test.properties similarity index 100% rename from codes/javadb/javadb-redis/src/test/resources/properties/application-test.properties rename to codes/javadb/redis/src/test/resources/properties/application-test.properties diff --git a/codes/javadb/javadb-redis/src/test/resources/properties/application.properties b/codes/javadb/redis/src/test/resources/properties/application.properties similarity index 100% rename from codes/javadb/javadb-redis/src/test/resources/properties/application.properties rename to codes/javadb/redis/src/test/resources/properties/application.properties diff --git a/codes/javadb/javadb-redis/src/test/resources/redis.xml b/codes/javadb/redis/src/test/resources/redis.xml similarity index 100% rename from codes/javadb/javadb-redis/src/test/resources/redis.xml rename to codes/javadb/redis/src/test/resources/redis.xml diff --git a/codes/javadb/javadb-redis/src/test/resources/redisson-standalone.xml b/codes/javadb/redis/src/test/resources/redisson-standalone.xml similarity index 89% rename from codes/javadb/javadb-redis/src/test/resources/redisson-standalone.xml rename to codes/javadb/redis/src/test/resources/redisson-standalone.xml index 462abd6c..47607374 100644 --- a/codes/javadb/javadb-redis/src/test/resources/redisson-standalone.xml +++ b/codes/javadb/redis/src/test/resources/redisson-standalone.xml @@ -14,8 +14,6 @@ idle-connection-timeout="10000" connect-timeout="10000" timeout="3000" - ping-timeout="30000" - reconnection-timeout="30000" database="0"/> diff --git a/codes/javadb/sqlite/pom.xml b/codes/javadb/sqlite/pom.xml new file mode 100644 index 00000000..0fe9bdc2 --- /dev/null +++ b/codes/javadb/sqlite/pom.xml @@ -0,0 +1,50 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 2.6.3 + + + io.github.dunwu + javadb-sqlite + 1.0.0 + jar + + + + org.springframework.boot + spring-boot-starter-jdbc + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.projectlombok + lombok + + + + + org.xerial + sqlite-jdbc + 3.36.0.2 + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/codes/javadb/javadb-sqlite/src/main/java/io/github/dunwu/javadb/sqlite/springboot/SpringBootDataSqliteApplication.java b/codes/javadb/sqlite/src/main/java/io/github/dunwu/javadb/sqlite/springboot/SpringBootDataSqliteApplication.java similarity index 91% rename from codes/javadb/javadb-sqlite/src/main/java/io/github/dunwu/javadb/sqlite/springboot/SpringBootDataSqliteApplication.java rename to codes/javadb/sqlite/src/main/java/io/github/dunwu/javadb/sqlite/springboot/SpringBootDataSqliteApplication.java index e7fad56e..3870e9b9 100644 --- a/codes/javadb/javadb-sqlite/src/main/java/io/github/dunwu/javadb/sqlite/springboot/SpringBootDataSqliteApplication.java +++ b/codes/javadb/sqlite/src/main/java/io/github/dunwu/javadb/sqlite/springboot/SpringBootDataSqliteApplication.java @@ -6,7 +6,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; /** - * @author Zhang Peng + * @author Zhang Peng * @since 2019-03-05 */ @Slf4j diff --git a/codes/javadb/javadb-sqlite/src/main/java/io/github/dunwu/javadb/sqlite/springboot/SqliteDemo.java b/codes/javadb/sqlite/src/main/java/io/github/dunwu/javadb/sqlite/springboot/SqliteDemo.java similarity index 99% rename from codes/javadb/javadb-sqlite/src/main/java/io/github/dunwu/javadb/sqlite/springboot/SqliteDemo.java rename to codes/javadb/sqlite/src/main/java/io/github/dunwu/javadb/sqlite/springboot/SqliteDemo.java index b2ac44f7..fac342d3 100644 --- a/codes/javadb/javadb-sqlite/src/main/java/io/github/dunwu/javadb/sqlite/springboot/SqliteDemo.java +++ b/codes/javadb/sqlite/src/main/java/io/github/dunwu/javadb/sqlite/springboot/SqliteDemo.java @@ -6,7 +6,7 @@ import java.sql.Statement; /** - * @author Zhang Peng + * @author Zhang Peng * @since 2019-03-05 */ public class SqliteDemo { diff --git a/codes/javadb/javadb-sqlite/src/main/resources/application.properties b/codes/javadb/sqlite/src/main/resources/application.properties similarity index 100% rename from codes/javadb/javadb-sqlite/src/main/resources/application.properties rename to codes/javadb/sqlite/src/main/resources/application.properties diff --git a/codes/javadb/javadb-sqlite/src/main/resources/banner.txt b/codes/javadb/sqlite/src/main/resources/banner.txt similarity index 100% rename from codes/javadb/javadb-sqlite/src/main/resources/banner.txt rename to codes/javadb/sqlite/src/main/resources/banner.txt diff --git a/codes/javadb/javadb-sqlite/src/main/resources/logback.xml b/codes/javadb/sqlite/src/main/resources/logback.xml similarity index 100% rename from codes/javadb/javadb-sqlite/src/main/resources/logback.xml rename to codes/javadb/sqlite/src/main/resources/logback.xml diff --git "a/codes/mysql/Leetcode\344\271\213SQL\351\242\230/easy/\346\237\245\346\211\276\351\207\215\345\244\215\347\232\204\347\224\265\345\255\220\351\202\256\347\256\261.sql" "b/codes/mysql/Leetcode\344\271\213SQL\351\242\230/easy/\346\237\245\346\211\276\351\207\215\345\244\215\347\232\204\347\224\265\345\255\220\351\202\256\347\256\261.sql" index 0567c469..6b59150a 100644 --- "a/codes/mysql/Leetcode\344\271\213SQL\351\242\230/easy/\346\237\245\346\211\276\351\207\215\345\244\215\347\232\204\347\224\265\345\255\220\351\202\256\347\256\261.sql" +++ "b/codes/mysql/Leetcode\344\271\213SQL\351\242\230/easy/\346\237\245\346\211\276\351\207\215\345\244\215\347\232\204\347\224\265\345\255\220\351\202\256\347\256\261.sql" @@ -1,7 +1,7 @@ -- -------------------------------------------------------------------------------------- -- 查找重复的电子邮箱 -- @link https://leetcode-cn.com/problems/duplicate-emails/ --- @author Zhang Peng +-- @author Zhang Peng -- @date 2020/02/29 -- ---------------------------------------------------------------------------------------- diff --git "a/codes/mysql/SQL\345\277\205\347\237\245\345\277\205\344\274\232\347\244\272\344\276\213/README.md" "b/codes/mysql/SQL\345\277\205\347\237\245\345\277\205\344\274\232\347\244\272\344\276\213/README.md" index 50ade328..4ec36034 100644 --- "a/codes/mysql/SQL\345\277\205\347\237\245\345\277\205\344\274\232\347\244\272\344\276\213/README.md" +++ "b/codes/mysql/SQL\345\277\205\347\237\245\345\277\205\344\274\232\347\244\272\344\276\213/README.md" @@ -1,3 +1,3 @@ # 源码说明 -> 本目录代码为 [《SQL 必知必会》](https://item.jd.com/11232698.html) 部分示例源码 +> 本目录代码为 [《SQL 必知必会》](https://book.douban.com/subject/35167240/) 部分示例源码 diff --git "a/codes/mysql/SQL\345\277\205\347\237\245\345\277\205\344\274\232\347\244\272\344\276\213/create.sql" "b/codes/mysql/SQL\345\277\205\347\237\245\345\277\205\344\274\232\347\244\272\344\276\213/create.sql" index b3928a23..b15e1c3f 100644 --- "a/codes/mysql/SQL\345\277\205\347\237\245\345\277\205\344\274\232\347\244\272\344\276\213/create.sql" +++ "b/codes/mysql/SQL\345\277\205\347\237\245\345\277\205\344\274\232\347\244\272\344\276\213/create.sql" @@ -72,25 +72,25 @@ CREATE TABLE vendors ( -- Define primary keys -- ------------------- ALTER TABLE customers - ADD PRIMARY KEY (cust_id); +ADD PRIMARY KEY (cust_id); ALTER TABLE orderitems - ADD PRIMARY KEY (order_num, order_item); +ADD PRIMARY KEY (order_num, order_item); ALTER TABLE orders - ADD PRIMARY KEY (order_num); +ADD PRIMARY KEY (order_num); ALTER TABLE products - ADD PRIMARY KEY (prod_id); +ADD PRIMARY KEY (prod_id); ALTER TABLE vendors - ADD PRIMARY KEY (vend_id); +ADD PRIMARY KEY (vend_id); -- ------------------- -- Define foreign keys -- ------------------- ALTER TABLE orderitems - ADD CONSTRAINT fk_orderitems_orders FOREIGN KEY (order_num) REFERENCES orders(order_num); +ADD CONSTRAINT fk_orderitems_orders FOREIGN KEY (order_num) REFERENCES orders(order_num); ALTER TABLE orderitems - ADD CONSTRAINT fk_orderitems_products FOREIGN KEY (prod_id) REFERENCES products(prod_id); +ADD CONSTRAINT fk_orderitems_products FOREIGN KEY (prod_id) REFERENCES products(prod_id); ALTER TABLE orders - ADD CONSTRAINT fk_orders_customers FOREIGN KEY (cust_id) REFERENCES customers(cust_id); +ADD CONSTRAINT fk_orders_customers FOREIGN KEY (cust_id) REFERENCES customers(cust_id); ALTER TABLE products - ADD CONSTRAINT fk_products_vendors FOREIGN KEY (vend_id) REFERENCES vendors(vend_id); +ADD CONSTRAINT fk_products_vendors FOREIGN KEY (vend_id) REFERENCES vendors(vend_id); diff --git "a/codes/mysql/SQL\345\277\205\347\237\245\345\277\205\344\274\232\347\244\272\344\276\213/select.sql" "b/codes/mysql/SQL\345\277\205\347\237\245\345\277\205\344\274\232\347\244\272\344\276\213/select.sql" index 79dd3d46..6ec69d84 100644 --- "a/codes/mysql/SQL\345\277\205\347\237\245\345\277\205\344\274\232\347\244\272\344\276\213/select.sql" +++ "b/codes/mysql/SQL\345\277\205\347\237\245\345\277\205\344\274\232\347\244\272\344\276\213/select.sql" @@ -1,8 +1,8 @@ -/** - * Mysql 查询示例 - * @author Zhang Peng - * @date 2018/5/5 - */ +-- -------------------------------------------------------------------------------------- +-- Mysql 查询示例 +-- @author Zhang Peng +-- @date 2018/5/5 +-- ---------------------------------------------------------------------------------------- -- ------------------------------------------- -- 查询数据 @@ -24,7 +24,7 @@ FROM products; SELECT DISTINCT vend_id FROM products; --- 限制结果 +-- 限制查询数量 -- 返回前 5 行(1) SELECT * FROM products diff --git "a/codes/mysql/SQL\346\200\247\350\203\275\344\274\230\345\214\226/SQL\345\207\275\346\225\260\345\275\261\345\223\215\347\264\242\345\274\225.sql" "b/codes/mysql/SQL\346\200\247\350\203\275\344\274\230\345\214\226/SQL\345\207\275\346\225\260\345\275\261\345\223\215\347\264\242\345\274\225.sql" new file mode 100644 index 00000000..577ec893 --- /dev/null +++ "b/codes/mysql/SQL\346\200\247\350\203\275\344\274\230\345\214\226/SQL\345\207\275\346\225\260\345\275\261\345\223\215\347\264\242\345\274\225.sql" @@ -0,0 +1,119 @@ +-- -------------------------------------------------------------------------------------- +-- 函数操作影响索引效率示例 +-- @author Zhang Peng +-- ---------------------------------------------------------------------------------------- + +-- 步骤 1、建表 +CREATE TABLE tradelog ( + id INT(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'Id', + tradeid VARCHAR(32) DEFAULT NULL, + operator INT(11) DEFAULT NULL, + t_modified DATETIME DEFAULT NULL, + PRIMARY KEY (id), + KEY tradeid(tradeid), + KEY t_modified(t_modified) +) + ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4; + +CREATE TABLE trade_detail ( + id INT(11) NOT NULL, + tradeid VARCHAR(32) DEFAULT NULL, + trade_step INT(11) DEFAULT NULL, /* 操作步骤 */ + step_info VARCHAR(32) DEFAULT NULL, /* 步骤信息 */ + PRIMARY KEY (id), + KEY tradeid(tradeid) +) + ENGINE = InnoDB + DEFAULT CHARSET = utf8; + +-- 步骤 2、存储过程初始化数据 + +INSERT INTO trade_detail +VALUES (1, 'aaaaaaaa', 1, 'add'); +INSERT INTO trade_detail +VALUES (2, 'aaaaaaaa', 2, 'update'); +INSERT INTO trade_detail +VALUES (3, 'aaaaaaaa', 3, 'commit'); +INSERT INTO trade_detail +VALUES (4, 'aaaaaaab', 1, 'add'); +INSERT INTO trade_detail +VALUES (5, 'aaaaaaab', 2, 'update'); +INSERT INTO trade_detail +VALUES (6, 'aaaaaaab', 3, 'update again'); +INSERT INTO trade_detail +VALUES (7, 'aaaaaaab', 4, 'commit'); +INSERT INTO trade_detail +VALUES (8, 'aaaaaaac', 1, 'add'); +INSERT INTO trade_detail +VALUES (9, 'aaaaaaac', 2, 'update'); +INSERT INTO trade_detail +VALUES (10, 'aaaaaaac', 3, 'update again'); +INSERT INTO trade_detail +VALUES (11, 'aaaaaaac', 4, 'commit'); + +INSERT INTO tradelog +VALUES (1, 'aaaaaaaa', 1000, now()); +INSERT INTO tradelog +VALUES (2, 'aaaaaaab', 1000, now()); +INSERT INTO tradelog +VALUES (3, 'aaaaaaac', 1000, now()); + +DELIMITER ;; +DROP PROCEDURE IF EXISTS init; +CREATE PROCEDURE init() +BEGIN + DECLARE i INT; + SET i = 3; + WHILE i < 10000 + DO + INSERT INTO tradelog(tradeid, operator, t_modified) + VALUES (concat(char(97 + (i DIV 1000)), char(97 + (i % 1000 DIV 100)), char(97 + (i % 100 DIV 10)), + char(97 + (i % 10))), i, now()); + SET i = i + 1; + END WHILE; +END;; +DELIMITER ; +CALL init(); + +-- 步骤 3、执行计划查看SQL效率 +-- 3.1.1 此 SQL 对索引字段做函数操作,优化器会放弃走树搜索功能,改为全表扫描 +EXPLAIN +SELECT count(*) +FROM tradelog +WHERE month(t_modified) = 7; + +-- 3.1.2 SQL 优化 +EXPLAIN +SELECT count(*) +FROM tradelog +WHERE (t_modified >= '2016-7-1' AND t_modified < '2016-8-1') OR + (t_modified >= '2017-7-1' AND t_modified < '2017-8-1') OR + (t_modified >= '2018-7-1' AND t_modified < '2018-8-1'); + +-- 3.2.1 此 SQL 对索引字段隐式的使用了转换函数操作,优化器会放弃走树搜索功能,改为全表扫描 +-- 相当于 select * from tradelog where CAST(tradid AS signed int) = 110717; +EXPLAIN +SELECT * +FROM tradelog +WHERE tradeid = 110717; + +-- 3.3.1 下面两条 SQL 的扫描行数不同 +-- 原因是:字符集 utf8mb4 是 utf8 的超集,所以当这两个类型的字符串在做比较的时候, +-- MySQL 内部的操作是,先把 utf8 字符串转成 utf8mb4 字符集,再做比较。 +# 需要做字符编码转换 +EXPLAIN +SELECT d.* +FROM tradelog l, trade_detail d +WHERE d.tradeid = l.tradeid AND l.id = 2; + +# 上面的 SQL 等价于这条注掉的 SQL +# SELECT * +# FROM trade_detail +# WHERE CONVERT(traideid USING utf8mb4) = $l2.tradeid.value; + +# 不需要做字符编码转换 +EXPLAIN +SELECT l.operator +FROM tradelog l, trade_detail d +WHERE d.tradeid = l.tradeid AND d.id = 2; diff --git "a/codes/mysql/SQL\346\200\247\350\203\275\344\274\230\345\214\226/\347\255\211MDL\351\224\201.sql" "b/codes/mysql/SQL\346\200\247\350\203\275\344\274\230\345\214\226/\347\255\211MDL\351\224\201.sql" new file mode 100644 index 00000000..67acf42c --- /dev/null +++ "b/codes/mysql/SQL\346\200\247\350\203\275\344\274\230\345\214\226/\347\255\211MDL\351\224\201.sql" @@ -0,0 +1,27 @@ +-- -------------------------------------------------------------------------------------- +-- 函数操作影响索引效率示例 +-- @author Zhang Peng +-- ---------------------------------------------------------------------------------------- + +CREATE TABLE t ( + id INT(11) NOT NULL AUTO_INCREMENT COMMENT 'Id', + c INT(11) DEFAULT NULL, + PRIMARY KEY (id) +) + ENGINE = InnoDB; + +DELIMITER ;; +DROP PROCEDURE IF EXISTS init; +CREATE PROCEDURE init() +BEGIN + DECLARE i INT; + SET i = 1; + WHILE(i <= 100000) + DO + INSERT INTO t VALUES (i, i); + SET i = i + 1; + END WHILE; +END;; +DELIMITER ; + +CALL init(); diff --git "a/codes/mysql/\344\272\213\345\212\241\347\244\272\344\276\213.sql" "b/codes/mysql/\344\272\213\345\212\241/\344\272\213\345\212\241\347\244\272\344\276\213.sql" similarity index 95% rename from "codes/mysql/\344\272\213\345\212\241\347\244\272\344\276\213.sql" rename to "codes/mysql/\344\272\213\345\212\241/\344\272\213\345\212\241\347\244\272\344\276\213.sql" index 10f3fdac..5dfde103 100644 --- "a/codes/mysql/\344\272\213\345\212\241\347\244\272\344\276\213.sql" +++ "b/codes/mysql/\344\272\213\345\212\241/\344\272\213\345\212\241\347\244\272\344\276\213.sql" @@ -1,6 +1,6 @@ -- -------------------------------------------------------------------------------------- -- Mysql 事务示例 --- @author Zhang Peng +-- @author Zhang Peng -- @date 2020/02/29 -- ---------------------------------------------------------------------------------------- diff --git "a/codes/mysql/\344\272\213\345\212\241/\345\271\273\350\257\273\347\244\272\344\276\2131.sql" "b/codes/mysql/\344\272\213\345\212\241/\345\271\273\350\257\273\347\244\272\344\276\2131.sql" new file mode 100644 index 00000000..55467a1e --- /dev/null +++ "b/codes/mysql/\344\272\213\345\212\241/\345\271\273\350\257\273\347\244\272\344\276\2131.sql" @@ -0,0 +1,55 @@ +-- -------------------------------------------------------------------------------------- +-- 幻读示例 +-- 实验说明:以下 SQL 脚本必须严格按照顺序执行,并且事务 A 和事务 B 必须在不同的 Client 中执行。 +-- @author Zhang Peng +-- @date 2023/10/25 +-- ---------------------------------------------------------------------------------------- + +-- --------------------------------------------------------------------- (1)数据初始化 + +-- 创建表 test +CREATE TABLE `test` ( + `id` INT(10) UNSIGNED PRIMARY KEY AUTO_INCREMENT, + `value` INT(10) NOT NULL +); + +-- 数据初始化 +INSERT INTO `test` (`id`, `value`) VALUES (1, 1); +INSERT INTO `test` (`id`, `value`) VALUES (2, 2); +INSERT INTO `test` (`id`, `value`) VALUES (3, 3); + +-- --------------------------------------------------------------------- (2)事务 A + +BEGIN; + +-- 查询 id = 4 的记录 +SELECT * FROM `test` WHERE `id` = 4; +-- 结果为空 + +-- --------------------------------------------------------------------- (3)事务 B + +BEGIN; + +INSERT INTO `test` (`id`, `value`) VALUES (4, 4); + +COMMIT; + +-- --------------------------------------------------------------------- (4)事务 A + +-- 查询 id = 4 的记录 +SELECT * FROM `test` WHERE `id` = 4; +-- 结果依然为空 + +-- 成功更新本应看不到的记录 id = 4 +UPDATE `test` SET `value` = 0 WHERE `id` = 4; + +-- 再一次查询 id = 4 的记录 +SELECT * FROM `test` WHERE `id` = 4; +-- 结果为: +-- +----+-------+ +-- | id | value | +-- +----+-------+ +-- | 4 | 0 | +-- +----+-------+ + +COMMIT; \ No newline at end of file diff --git "a/codes/mysql/\344\272\213\345\212\241/\345\271\273\350\257\273\347\244\272\344\276\2132.sql" "b/codes/mysql/\344\272\213\345\212\241/\345\271\273\350\257\273\347\244\272\344\276\2132.sql" new file mode 100644 index 00000000..2bb125d7 --- /dev/null +++ "b/codes/mysql/\344\272\213\345\212\241/\345\271\273\350\257\273\347\244\272\344\276\2132.sql" @@ -0,0 +1,53 @@ +-- -------------------------------------------------------------------------------------- +-- 幻读示例 +-- 实验说明:以下 SQL 脚本必须严格按照顺序执行,并且事务 A 和事务 B 必须在不同的 Client 中执行。 +-- @author Zhang Peng +-- @date 2023/10/25 +-- ---------------------------------------------------------------------------------------- + +-- --------------------------------------------------------------------- (1)数据初始化 + +-- 创建表 test +CREATE TABLE `test` ( + `id` INT(10) UNSIGNED PRIMARY KEY AUTO_INCREMENT, + `value` INT(10) NOT NULL +); + +-- 数据初始化 +INSERT INTO `test` (`id`, `value`) VALUES (1, 1); +INSERT INTO `test` (`id`, `value`) VALUES (2, 2); +INSERT INTO `test` (`id`, `value`) VALUES (3, 3); + +-- --------------------------------------------------------------------- (2)事务 A + +BEGIN; + +-- 查询 id > 2 的记录数 +SELECT COUNT(*) FROM `test` WHERE `id` > 2; +-- 结果为: +-- +----------+ +-- | count(*) | +-- +----------+ +-- | 1 | +-- +----------+ + +-- --------------------------------------------------------------------- (3)事务 B + +BEGIN; + +INSERT INTO `test` (`id`, `value`) VALUES (4, 4); + +COMMIT; + +-- --------------------------------------------------------------------- (4)事务 A + +-- 查询 id = 4 的记录 +SELECT COUNT(*) FROM `test` WHERE `id` > 2 FOR UPDATE; +-- 结果为: +-- +----------+ +-- | count(*) | +-- +----------+ +-- | 2 | +-- +----------+ + +COMMIT; \ No newline at end of file diff --git "a/codes/mysql/\345\237\272\346\234\254DDL\347\244\272\344\276\213.sql" "b/codes/mysql/\345\237\272\346\234\254DDL\347\244\272\344\276\213.sql" index 87e937d2..608c9605 100644 --- "a/codes/mysql/\345\237\272\346\234\254DDL\347\244\272\344\276\213.sql" +++ "b/codes/mysql/\345\237\272\346\234\254DDL\347\244\272\344\276\213.sql" @@ -1,13 +1,13 @@ -- -------------------------------------------------------------------------------------- -- Mysql 基本 DDL 语句示例 --- @author Zhang Peng +-- @author Zhang Peng -- @date 2018/4/28 -- ---------------------------------------------------------------------------------------- -- --------------------------------------------------------------------- 数据库定义 --- 撤销数据库 test +-- 删除数据库 db_tutorial DROP DATABASE IF EXISTS db_tutorial; -- 创建数据库 db_tutorial @@ -18,11 +18,11 @@ USE db_tutorial; -- --------------------------------------------------------------------- 数据表定义 --- 撤销表 user +-- 删除数据表 user DROP TABLE IF EXISTS user; DROP TABLE IF EXISTS vip_user; --- 创建表 user +-- 创建数据表 user CREATE TABLE user ( id INT(10) UNSIGNED NOT NULL COMMENT 'Id', username VARCHAR(64) NOT NULL DEFAULT 'default' COMMENT '用户名', @@ -37,31 +37,32 @@ FROM user; -- 添加列 age ALTER TABLE user - ADD age INT(3); +ADD age INT(3); -- 修改列 age 的类型为 tinyint ALTER TABLE user - MODIFY COLUMN age TINYINT; +MODIFY COLUMN age TINYINT; --- 撤销列 age +-- 删除列 age ALTER TABLE user - DROP COLUMN age; +DROP COLUMN age; -- --------------------------------------------------------------------- 索引定义 --- 创建表 user 的索引 user_index -CREATE INDEX user_index - ON user(id); +-- 创建表的索引 +CREATE INDEX idx_email + ON user(email); --- 创建表 user 的唯一索引 user_index2 -CREATE UNIQUE INDEX user_index2 - ON user(id); +-- 创建表的唯一索引 +CREATE UNIQUE INDEX uniq_username + ON user(username); --- 撤销表 user 的索引 +-- 删除表 user 的索引 ALTER TABLE user - DROP INDEX user_index; +DROP INDEX idx_email; + ALTER TABLE user - DROP INDEX user_index2; +DROP INDEX uniq_username; -- --------------------------------------------------------------------- 视图定义 @@ -71,5 +72,5 @@ SELECT id, username FROM user WHERE id < 10; --- 撤销表 user 的视图 top_10_user_view +-- 删除表 user 的视图 top_10_user_view DROP VIEW top_10_user_view; diff --git "a/codes/mysql/\345\237\272\346\234\254DML\347\244\272\344\276\213.sql" "b/codes/mysql/\345\237\272\346\234\254DML\347\244\272\344\276\213.sql" index b0db5628..11c3bd2e 100644 --- "a/codes/mysql/\345\237\272\346\234\254DML\347\244\272\344\276\213.sql" +++ "b/codes/mysql/\345\237\272\346\234\254DML\347\244\272\344\276\213.sql" @@ -1,6 +1,6 @@ -- -------------------------------------------------------------------------------------- -- Mysql 基本 DML 语句示例 --- @author Zhang Peng +-- @author Zhang Peng -- @date 2018/4/28 -- ---------------------------------------------------------------------------------------- diff --git "a/codes/mysql/\345\237\272\346\234\254TCL\347\244\272\344\276\213.sql" "b/codes/mysql/\345\237\272\346\234\254TCL\347\244\272\344\276\213.sql" index 28c55dd5..013a5188 100644 --- "a/codes/mysql/\345\237\272\346\234\254TCL\347\244\272\344\276\213.sql" +++ "b/codes/mysql/\345\237\272\346\234\254TCL\347\244\272\344\276\213.sql" @@ -1,6 +1,6 @@ -- -------------------------------------------------------------------------------------- -- Mysql 基本 TCL 语句示例 --- @author Zhang Peng +-- @author Zhang Peng -- @date 2018/4/28 -- ---------------------------------------------------------------------------------------- diff --git "a/codes/mysql/\345\270\270\350\247\201\346\237\245\350\257\242\347\244\272\344\276\213.sql" "b/codes/mysql/\345\270\270\350\247\201\346\237\245\350\257\242\347\244\272\344\276\213.sql" index f6a843c1..a079f543 100644 --- "a/codes/mysql/\345\270\270\350\247\201\346\237\245\350\257\242\347\244\272\344\276\213.sql" +++ "b/codes/mysql/\345\270\270\350\247\201\346\237\245\350\257\242\347\244\272\344\276\213.sql" @@ -1,6 +1,6 @@ -- -------------------------------------------------------------------------------------- -- Mysql 常见查询示例 --- @author Zhang Peng +-- @author Zhang Peng -- @date 2018/5/4 -- ---------------------------------------------------------------------------------------- diff --git "a/codes/mysql/\350\247\246\345\217\221\345\231\250\347\244\272\344\276\213.sql" "b/codes/mysql/\350\247\246\345\217\221\345\231\250\347\244\272\344\276\213.sql" index f9337eb9..d36d9183 100644 --- "a/codes/mysql/\350\247\246\345\217\221\345\231\250\347\244\272\344\276\213.sql" +++ "b/codes/mysql/\350\247\246\345\217\221\345\231\250\347\244\272\344\276\213.sql" @@ -1,11 +1,11 @@ /** * Mysql 触发器(TRIGGER)创建、使用示例 - * @author Zhang Peng + * @author Zhang Peng * @date 2018/5/2 */ -- -------------------------------------------------------------------------------------- -- Mysql 基本 DDL 语句示例 --- @author Zhang Peng +-- @author Zhang Peng -- @date 2018/4/28 -- ---------------------------------------------------------------------------------------- diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index 20417923..fd060731 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -12,7 +12,10 @@ module.exports = { // 注入到页面 中的标签,格式[tagName, { attrName: attrValue }, innerHTML?] ['link', { rel: 'icon', href: '/img/favicon.ico' }], //favicons,资源放在public文件夹 ['meta', { name: 'keywords', content: 'vuepress,theme,blog,vdoing' }], - ['meta', { name: 'theme-color', content: '#11a8cd' }] // 移动浏览器主题颜色 + ['meta', { name: 'theme-color', content: '#11a8cd' }], // 移动浏览器主题颜色 + + ['meta', { name: 'wwads-cn-verify', content: 'mxqWx62nfQQ9ocT4e5DzISHzOWyF4s' }], // 广告相关,你可以去掉 + ['script', { src: 'https://cdn.wwads.cn/js/makemoney.js', type: 'text/javascript' }], // 广告相关,你可以去掉 ], markdown: { // lineNumbers: true, @@ -53,7 +56,7 @@ module.exports = { } ], sidebarDepth: 2, // 侧边栏显示深度,默认1,最大2(显示到h3标题) - logo: 'https://raw.githubusercontent.com/dunwu/images/dev/common/dunwu-logo.png', // 导航栏logo + logo: 'https://raw.githubusercontent.com/dunwu/images/master/common/dunwu-logo.png', // 导航栏logo repo: 'dunwu/db-tutorial', // 导航栏右侧生成Github链接 searchMaxSuggestions: 10, // 搜索结果显示最大数 lastUpdated: '上次更新', // 更新的时间,及前缀文字 string | boolean (取值为git提交时间) @@ -63,9 +66,10 @@ module.exports = { editLinkText: '📝 帮助改善此页面!', // 以下配置是Vdoing主题改动的和新增的配置 - sidebar: { mode: 'structuring', collapsable: false }, // 侧边栏 'structuring' | { mode: 'structuring', collapsable: Boolean} | 'auto' | 自定义 温馨提示:目录页数据依赖于结构化的侧边栏数据,如果你不设置为'structuring',将无法使用目录页 + sidebar: { mode: 'structuring', collapsable: true }, // 侧边栏 'structuring' | { mode: 'structuring', collapsable: + // Boolean} | 'auto' | 自定义 温馨提示:目录页数据依赖于结构化的侧边栏数据,如果你不设置为'structuring',将无法使用目录页 - // sidebarOpen: false, // 初始状态是否打开侧边栏,默认true + sidebarOpen: true, // 初始状态是否打开侧边栏,默认true updateBar: { // 最近更新栏 showToArticle: true // 显示到文章页底部,默认true diff --git a/docs/.vuepress/config/htmlModules.js b/docs/.vuepress/config/htmlModules.js index 5120f420..fc0a47eb 100644 --- a/docs/.vuepress/config/htmlModules.js +++ b/docs/.vuepress/config/htmlModules.js @@ -20,25 +20,42 @@ module.exports = { // 万维广告 - pageB: ` -
- - `, + // pageT: ` + //
+ // + // `, windowRB: ` -
- ` + `, } // module.exports = { diff --git a/docs/.vuepress/enhanceApp.js b/docs/.vuepress/enhanceApp.js index 1589fa48..5bfa34f4 100644 --- a/docs/.vuepress/enhanceApp.js +++ b/docs/.vuepress/enhanceApp.js @@ -1,9 +1,59 @@ -// import vue from 'vue/dist/vue.esm.browser' +/** + * to主题使用者:你可以去掉本文件的所有代码 + */ export default ({ Vue, // VuePress 正在使用的 Vue 构造函数 options, // 附加到根实例的一些选项 router, // 当前应用的路由实例 - siteData // 站点元数据 + siteData, // 站点元数据 + isServer // 当前应用配置是处于 服务端渲染 还是 客户端 }) => { - // window.Vue = vue // 使页面中可以使用Vue构造函数 (使页面中的vue demo生效) + + // 用于监控在路由变化时检查广告拦截器 (to主题使用者:你可以去掉本文件的所有代码) + if (!isServer) { + router.afterEach(() => { + //check if wwads' fire function was blocked after document is ready with 3s timeout (waiting the ad loading) + docReady(function () { + setTimeout(function () { + if (window._AdBlockInit === undefined) { + ABDetected(); + } + }, 3000); + }); + + // 删除事件改为隐藏事件 + setTimeout(() => { + const pageAD = document.querySelector('.page-wwads'); + if (!pageAD) return; + const btnEl = pageAD.querySelector('.wwads-hide'); + if (btnEl) { + btnEl.onclick = () => { + pageAD.style.display = 'none'; + } + } + // 显示广告模块 + if (pageAD.style.display === 'none') { + pageAD.style.display = 'flex'; + } + }, 900); + }) + } +} + + +function ABDetected() { + const h = ""; + const wwadsEl = document.getElementsByClassName("wwads-cn"); + const wwadsContentEl = document.querySelector('.wwads-content'); + if (wwadsEl[0] && !wwadsContentEl) { + wwadsEl[0].innerHTML = h; + } +}; + +//check document ready +function docReady(t) { + "complete" === document.readyState || + "interactive" === document.readyState + ? setTimeout(t, 1) + : document.addEventListener("DOMContentLoaded", t); } diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/01.\346\225\260\346\215\256\345\272\223\347\273\274\345\220\210/01.Nosql\346\212\200\346\234\257\351\200\211\345\236\213.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/01.\346\225\260\346\215\256\345\272\223\347\273\274\345\220\210/01.Nosql\346\212\200\346\234\257\351\200\211\345\236\213.md" index bff8ebfa..13835ed6 100644 --- "a/docs/12.\346\225\260\346\215\256\345\272\223/01.\346\225\260\346\215\256\345\272\223\347\273\274\345\220\210/01.Nosql\346\212\200\346\234\257\351\200\211\345\236\213.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/01.\346\225\260\346\215\256\345\272\223\347\273\274\345\220\210/01.Nosql\346\212\200\346\234\257\351\200\211\345\236\213.md" @@ -13,7 +13,7 @@ permalink: /pages/0e1012/ # Nosql 技术选型 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200209020702.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200209020702.png) ## 一、Nosql 简介 @@ -27,7 +27,7 @@ permalink: /pages/0e1012/ 随着大数据时代的到来,越来越多的网站、应用系统需要支撑海量数据存储,高并发请求、高可用、高可扩展性等特性要求。传统的关系型数据库在应付这些调整已经显得力不从心,暴露了许多能以克服的问题。由此,各种各样的 NoSQL(Not Only SQL)数据库作为传统关系型数据的一个有力补充得到迅猛发展。 -![nosql-history](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200209005228.png) +![nosql-history](https://raw.githubusercontent.com/dunwu/images/master/snap/20200209005228.png) **NoSQL,泛指非关系型的数据库**,可以理解为 SQL 的一个有力补充。 @@ -56,7 +56,7 @@ permalink: /pages/0e1012/ 将表放入存储系统中有两种方法,而我们绝大部分是采用行存储的。 行存储法是将各行放入连续的物理位置,这很像传统的记录和文件系统。 列存储法是将数据按照列存储到数据库中,与行存储类似,下图是两种存储方法的图形化解释: -![按行存储和按列存储模式](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200209005316.png) +![按行存储和按列存储模式](https://raw.githubusercontent.com/dunwu/images/master/snap/20200209005316.png) ### 列式数据库产品 @@ -80,13 +80,13 @@ permalink: /pages/0e1012/ 列式数据库由于其针对不同列的数据特征而发明的不同算法,使其**往往有比行式数据库高的多的压缩率**,普通的行式数据库一般压缩率在 3:1 到 5:1 左右,而列式数据库的压缩率一般在 8:1 到 30:1 左右。 比较常见的,通过字典表压缩数据: 下面中才是那张表本来的样子。经过字典表进行数据压缩后,表中的字符串才都变成数字了。正因为每个字符串在字典表里只出现一次了,所以达到了压缩的目的(有点像规范化和非规范化 Normalize 和 Denomalize) -![通过字典表压缩数据](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200209005406.png) +![通过字典表压缩数据](https://raw.githubusercontent.com/dunwu/images/master/snap/20200209005406.png) - **查询效率高** 读取多条数据的同一列效率高,因为这些列都是存储在一起的,一次磁盘操作可以数据的指定列全部读取到内存中。 下图通过一条查询的执行过程说明列式存储(以及数据压缩)的优点 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200209005611.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200209005611.png) ``` 执行步骤如下: @@ -127,19 +127,19 @@ KV 存储非常适合存储**不涉及过多数据关系业务关系的数据** - Redis - ![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200209010410.png) + ![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200209010410.png) Redis 是一个使用 ANSI C 编写的开源、支持网络、基于内存、可选持久性的键值对存储数据库。从 2015 年 6 月开始,Redis 的开发由 Redis Labs 赞助,而 2013 年 5 月至 2015 年 6 月期间,其开发由 Pivotal 赞助。在 2013 年 5 月之前,其开发由 VMware 赞助。根据月度排行网站 DB-Engines.com 的数据显示,Redis 是最流行的键值对存储数据库。 - Cassandra - ![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200209010451.png) + ![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200209010451.png) Apache Cassandra(社区内一般简称为 C\*)是一套开源分布式 NoSQL 数据库系统。它最初由 Facebook 开发,用于储存收件箱等简单格式数据,集 Google BigTable 的数据模型与 Amazon Dynamo 的完全分布式架构于一身。Facebook 于 2008 将 Cassandra 开源,此后,由于 Cassandra 良好的可扩展性和性能,被 Apple, Comcast,Instagram, Spotify, eBay, Rackspace, Netflix 等知名网站所采用,成为了一种流行的分布式结构化数据存储方案。 - LevelDB - ![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200209011140.png) + ![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200209011140.png) LevelDB 是一个由 Google 公司所研发的键/值对(Key/Value Pair)嵌入式数据库管理系统编程库, 以开源的 BSD 许可证发布。 @@ -176,13 +176,13 @@ KV 存储非常适合存储**不涉及过多数据关系业务关系的数据** - MongoDB - ![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200209012320.png) + ![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200209012320.png) **MongoDB**是一种面向文档的数据库管理系统,由 C++ 撰写而成,以此来解决应用程序开发社区中的大量现实问题。2007 年 10 月,MongoDB 由 10gen 团队所发展。2009 年 2 月首度推出。 - CouchDB - ![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200209012418.png) + ![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200209012418.png) Apache CouchDB 是一个开源数据库,专注于易用性和成为"**完全拥抱 web 的数据库**"。它是一个使用 JSON 作为存储格式,JavaScript 作为查询语言,MapReduce 和 HTTP 作为 API 的 NoSQL 数据库。其中一个显著的功能就是多主复制。CouchDB 的第一个版本发布在 2005 年,在 2008 年成为了 Apache 的项目。 @@ -234,11 +234,11 @@ MongonDB 还是支持多文档事务的 Consistency(一致性)和 Durability(持 现在有如下文档集合: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200209014530.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200209014530.png) 正排索引得到索引如下: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200209014723.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200209014723.png) 可见,正排索引适用于根据文档名称查询文档内容 @@ -248,7 +248,7 @@ MongonDB 还是支持多文档事务的 Consistency(一致性)和 Durability(持 带有单词频率信息的倒排索引如下: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200209014842.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200209014842.png) 可见,倒排索引适用于根据关键词来查询文档内容 @@ -262,7 +262,7 @@ MongonDB 还是支持多文档事务的 Consistency(一致性)和 Durability(持 - Solr - ![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200209014947.png) + ![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200209014947.png) Solr 是 Apache Lucene 项目的开源企业搜索平台。其主要功能包括全文检索、命中标示、分面搜索、动态聚类、数据库集成,以及富文本(如 Word、PDF)的处理。Solr 是高度可扩展的,并提供了分布式搜索和索引复制 @@ -296,7 +296,7 @@ MongonDB 还是支持多文档事务的 Consistency(一致性)和 Durability(持 ## 六、图数据库 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200209015751.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200209015751.png) **图形数据库应用图论存储实体之间的关系信息**。最常见例子就是社会网络中人与人之间的关系。关系型数据库用于存储“关系型”数据的效果并不好,其查询复杂、缓慢、超出预期,而图形数据库的独特设计恰恰弥补了这个缺陷,解决关系型数据库存储和处理复杂关系型数据功能较弱的问题。 @@ -304,19 +304,19 @@ MongonDB 还是支持多文档事务的 Consistency(一致性)和 Durability(持 - Neo4j - ![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200209015817.png) + ![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200209015817.png) Neo4j 是由 Neo4j,Inc。开发的图形数据库管理系统。由其开发人员描述为具有原生图存储和处理的符合 ACID 的事务数据库,根据 DB-Engines 排名, Neo4j 是最流行的图形数据库。 - ArangoDB - ![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200209015858.png) + ![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200209015858.png) ArangoDB 是由 triAGENS GmbH 开发的原生多模型数据库系统。数据库系统支持三个重要的数据模型(键/值,文档,图形),其中包含一个数据库核心和统一查询语言 AQL(ArangoDB 查询语言)。查询语言是声明性的,允许在单个查询中组合不同的数据访问模式。ArangoDB 是一个 NoSQL 数据库系统,但 AQL 在很多方面与 SQL 类似。 - Titan - ![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200209015923.png) + ![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200209015923.png) Titan 是一个可扩展的图形数据库,针对存储和查询包含分布在多机群集中的数百亿个顶点和边缘的图形进行了优化。Titan 是一个事务性数据库,可以支持数千个并发用户实时执行复杂的图形遍历。 @@ -386,4 +386,4 @@ Neo4j 中,存储节点时使用了”index-free adjacency”,即每个节点 ## 参考资料 -- [NoSQL 还是 SQL ?这一篇讲清楚](https://juejin.im/post/5b6d62ddf265da0f491bd200) +- [NoSQL 还是 SQL ?这一篇讲清楚](https://juejin.im/post/5b6d62ddf265da0f491bd200) \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/01.\346\225\260\346\215\256\345\272\223\347\273\274\345\220\210/02.\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\346\225\260\346\215\256\345\272\223\347\264\242\345\274\225.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/01.\346\225\260\346\215\256\345\272\223\347\273\274\345\220\210/02.\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\346\225\260\346\215\256\345\272\223\347\264\242\345\274\225.md" index c2443fa6..6a6b7ed9 100644 --- "a/docs/12.\346\225\260\346\215\256\345\272\223/01.\346\225\260\346\215\256\345\272\223\347\273\274\345\220\210/02.\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\346\225\260\346\215\256\345\272\223\347\264\242\345\274\225.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/01.\346\225\260\346\215\256\345\272\223\347\273\274\345\220\210/02.\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\346\225\260\346\215\256\345\272\223\347\264\242\345\274\225.md" @@ -41,13 +41,13 @@ permalink: /pages/d7cd88/ 在有序数组上应用二分查找法如此高效,为什么几乎没有数据库直接使用数组作为索引?这是因为它的限制条件:**数据有序**——为了保证数据有序,每次添加、删除数组数据时,都必须要进行数据调整,来保证其有序,而 **数组的插入/删除操作,时间复杂度为 `O(n)`**。此外,由于数组空间大小固定,每次扩容只能采用复制数组的方式。数组的这些特性,决定了它不适合用于数据频繁变化的应用场景。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220320115836.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220320115836.png) **链表用不连续的内存空间来存储数据;并通过一个指针按顺序将这些空间串起来,形成一条链**。 区别于数组,链表中的元素不是存储在内存中连续的一片区域,链表中的数据存储在每一个称之为「结点」复合区域里,在每一个结点除了存储数据以外,还保存了到下一个节点的指针(Pointer)。由于不必按顺序存储,**链表的插入/删除操作,时间复杂度为 `O(1)`**,但是,链表只支持顺序访问,其 **查找时间复杂度为 `O(n)`**。其低效的查找方式,决定了链表不适合作为索引。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220320174829.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220320174829.png) ## 哈希索引 @@ -55,7 +55,7 @@ permalink: /pages/d7cd88/ **哈希表** 使用 **哈希函数** 组织数据,以支持快速插入和搜索的数据结构。哈希表的本质是一个数组,其思路是:使用 Hash 函数将 Key 转换为数组下标,利用数组的随机访问特性,使得我们能在 `O(1)` 的时间代价内完成检索。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220320201844.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220320201844.png) 有两种不同类型的哈希表:**哈希集合** 和 **哈希映射**。 @@ -105,7 +105,7 @@ B+ 树索引适用于**全键值查找**、**键值范围查找**和**键前缀 - 第一,所有的关键字(可以理解为数据)都存储在叶子节点,非叶子节点并不存储真正的数据,所有记录节点都是按键值大小顺序存放在同一层叶子节点上。 - 其次,所有的叶子节点由指针连接。如下图为简化了的`B+Tree`。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200304235424.jpg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200304235424.jpg) 根据叶子节点的内容,索引类型分为主键索引和非主键索引。 @@ -214,4 +214,4 @@ LSM 树的这些特点,使得它相对于 B+ 树,在写入性能上有大幅 - [数据结构与算法之美](https://time.geekbang.org/column/intro/100017301) - [检索技术核心 20 讲](https://time.geekbang.org/column/intro/100048401) - [Data Structures for Databases](https://www.cise.ufl.edu/~mschneid/Research/papers/HS05BoCh.pdf) -- [Data Structures and Algorithms for Big Databases](https://people.csail.mit.edu/bradley/BenderKuszmaul-tutorial-xldb12.pdf) +- [Data Structures and Algorithms for Big Databases](https://people.csail.mit.edu/bradley/BenderKuszmaul-tutorial-xldb12.pdf) \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/01.\346\225\260\346\215\256\345\272\223\347\273\274\345\220\210/README.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/01.\346\225\260\346\215\256\345\272\223\347\273\274\345\220\210/README.md" index ef9771af..d2b801e8 100644 --- "a/docs/12.\346\225\260\346\215\256\345\272\223/01.\346\225\260\346\215\256\345\272\223\347\273\274\345\220\210/README.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/01.\346\225\260\346\215\256\345\272\223\347\273\274\345\220\210/README.md" @@ -22,4 +22,4 @@ hidden: true ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/02.\346\225\260\346\215\256\345\272\223\344\270\255\351\227\264\344\273\266/01.Shardingsphere/01.ShardingSphere\347\256\200\344\273\213.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/02.\346\225\260\346\215\256\345\272\223\344\270\255\351\227\264\344\273\266/01.Shardingsphere/01.ShardingSphere\347\256\200\344\273\213.md" index 4aff27b8..f8fe8d6f 100644 --- "a/docs/12.\346\225\260\346\215\256\345\272\223/02.\346\225\260\346\215\256\345\272\223\344\270\255\351\227\264\344\273\266/01.Shardingsphere/01.ShardingSphere\347\256\200\344\273\213.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/02.\346\225\260\346\215\256\345\272\223\344\270\255\351\227\264\344\273\266/01.Shardingsphere/01.ShardingSphere\347\256\200\344\273\213.md" @@ -20,7 +20,7 @@ permalink: /pages/5ed2a2/ ShardingSphere 是一套开源的分布式数据库中间件解决方案组成的生态圈,它由 Sharding-JDBC、Sharding-Proxy 和 Sharding-Sidecar(计划中)这 3 款相互独立的产品组成。 他们均提供标准化的数据分片、分布式事务和数据库治理功能,可适用于如 Java 同构、异构语言、云原生等各种多样化的应用场景。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20201008151613.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20201008151613.png) #### ShardingSphere-JDBC @@ -30,7 +30,7 @@ ShardingSphere 是一套开源的分布式数据库中间件解决方案组成 - 支持任何第三方的数据库连接池,如:DBCP, C3P0, BoneCP, Druid, HikariCP 等。 - 支持任意实现 JDBC 规范的数据库,目前支持 MySQL,Oracle,SQLServer,PostgreSQL 以及任何遵循 SQL92 标准的数据库。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20201008151213.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20201008151213.png) #### Sharding-Proxy @@ -39,7 +39,7 @@ ShardingSphere 是一套开源的分布式数据库中间件解决方案组成 - 向应用程序完全透明,可直接当做 MySQL/PostgreSQL 使用。 - 适用于任何兼容 MySQL/PostgreSQL 协议的的客户端。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20201008151434.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20201008151434.png) #### Sharding-Sidecar(TODO) @@ -47,7 +47,7 @@ ShardingSphere 是一套开源的分布式数据库中间件解决方案组成 Database Mesh 的关注重点在于如何将分布式的数据访问应用与数据库有机串联起来,它更加关注的是交互,是将杂乱无章的应用与数据库之间的交互进行有效地梳理。 使用 Database Mesh,访问数据库的应用和数据库终将形成一个巨大的网格体系,应用和数据库只需在网格体系中对号入座即可,它们都是被啮合层所治理的对象。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20201008151557.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20201008151557.png) | _Sharding-JDBC_ | _Sharding-Proxy_ | _Sharding-Sidecar_ | | | :-------------- | :--------------- | :----------------- | ------ | @@ -64,7 +64,7 @@ ShardingSphere-JDBC 采用无中心化架构,适用于 Java 开发的高性能 Apache ShardingSphere 是多接入端共同组成的生态圈。 通过混合使用 ShardingSphere-JDBC 和 ShardingSphere-Proxy,并采用同一注册中心统一配置分片策略,能够灵活的搭建适用于各种场景的应用系统,使得架构师更加自由地调整适合与当前业务的最佳系统架构。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20201008151658.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20201008151658.png) ### 功能列表 @@ -91,4 +91,4 @@ Apache ShardingSphere 是多接入端共同组成的生态圈。 通过混合使 ## 参考资料 - [shardingsphere Github](https://github.com/apache/incubator-shardingsphere) -- [shardingsphere 官方文档](https://shardingsphere.apache.org/document/current/cn/overview/) +- [shardingsphere 官方文档](https://shardingsphere.apache.org/document/current/cn/overview/) \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/02.\346\225\260\346\215\256\345\272\223\344\270\255\351\227\264\344\273\266/01.Shardingsphere/02.ShardingSphereJdbc.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/02.\346\225\260\346\215\256\345\272\223\344\270\255\351\227\264\344\273\266/01.Shardingsphere/02.ShardingSphereJdbc.md" index 35fb5237..a7247d00 100644 --- "a/docs/12.\346\225\260\346\215\256\345\272\223/02.\346\225\260\346\215\256\345\272\223\344\270\255\351\227\264\344\273\266/01.Shardingsphere/02.ShardingSphereJdbc.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/02.\346\225\260\346\215\256\345\272\223\344\270\255\351\227\264\344\273\266/01.Shardingsphere/02.ShardingSphereJdbc.md" @@ -22,7 +22,7 @@ shardingsphere-jdbc 定位为轻量级 Java 框架,在 Java 的 JDBC 层提供 - 支持任何第三方的数据库连接池,如:DBCP, C3P0, BoneCP, Druid, HikariCP 等。 - 支持任意实现 JDBC 规范的数据库,目前支持 MySQL,Oracle,SQLServer,PostgreSQL 以及任何遵循 SQL92 标准的数据库。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20201008151213.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20201008151213.png) ## 快速入门 @@ -88,7 +88,7 @@ DataSource dataSource = ShardingSphereDataSourceFactory.createDataSource(dataSou ShardingSphere 的 3 个产品的数据分片主要流程是完全一致的。 核心由 `SQL 解析 => 执行器优化 => SQL 路由 => SQL 改写 => SQL 执行 => 结果归并`的流程组成。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20201008153551.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20201008153551.png) - QL 解析:分为词法解析和语法解析。 先通过词法解析器将 SQL 拆分为一个个不可再分的单词。再使用语法解析器对 SQL 进行理解,并最终提炼出解析上下文。 解析上下文包括表、选择项、排序项、分组项、聚合函数、分页信息、查询条件以及可能需要修改的占位符的标记。 - 执行器优化:合并和优化分片条件,如 OR 等。 @@ -137,4 +137,4 @@ SQL 解析作为分库分表类产品的核心,其性能和兼容性是最重 ### 执行引擎 -### 归并引擎 +### 归并引擎 \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/02.\346\225\260\346\215\256\345\272\223\344\270\255\351\227\264\344\273\266/02.Flyway.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/02.\346\225\260\346\215\256\345\272\223\344\270\255\351\227\264\344\273\266/02.Flyway.md" index 03806f36..6e953e2b 100644 --- "a/docs/12.\346\225\260\346\215\256\345\272\223/02.\346\225\260\346\215\256\345\272\223\344\270\255\351\227\264\344\273\266/02.Flyway.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/02.\346\225\260\346\215\256\345\272\223\344\270\255\351\227\264\344\273\266/02.Flyway.md" @@ -397,7 +397,7 @@ migrations 最常用的编写形式就是 SQL。 为了被 Flyway 自动识别,SQL migrations 的文件命名必须遵循规定的模式: -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/flyway/sql-migrations.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/database/flyway/sql-migrations.png) - **Prefix** - `V` 代表 versioned migrations (可配置), `U` 代表 undo migrations (可配置)、 `R` 代表 repeatable migrations (可配置) - **Version** - 版本号通过`.`(点)或`_`(下划线)分隔 (repeatable migrations 不需要) @@ -416,7 +416,7 @@ migrations 最常用的编写形式就是 SQL。 为了被 Flyway 自动识别,JAVA migrations 的文件命名必须遵循规定的模式: -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/flyway/java-migrations.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/database/flyway/java-migrations.png) - **Prefix** - `V` 代表 versioned migrations (可配置), `U` 代表 undo migrations (可配置)、 `R` 代表 repeatable migrations (可配置) - **Version** - 版本号通过`.`(点)或`_`(下划线)分隔 (repeatable migrations 不需要) @@ -501,4 +501,4 @@ Flyway 的功能主要围绕着 7 个基本命令:[Migrate](https://flywaydb.o ## :door: 传送门 -| [钝悟的博客](https://dunwu.github.io/blog/) | [db-tutorial 首页](https://github.com/dunwu/db-tutorial) | +| [钝悟的博客](https://dunwu.github.io/blog/) | [db-tutorial 首页](https://github.com/dunwu/db-tutorial) | \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/02.\346\225\260\346\215\256\345\272\223\344\270\255\351\227\264\344\273\266/README.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/02.\346\225\260\346\215\256\345\272\223\344\270\255\351\227\264\344\273\266/README.md" index 95cda949..cdadf2f4 100644 --- "a/docs/12.\346\225\260\346\215\256\345\272\223/02.\346\225\260\346\215\256\345\272\223\344\270\255\351\227\264\344\273\266/README.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/02.\346\225\260\346\215\256\345\272\223\344\270\255\351\227\264\344\273\266/README.md" @@ -30,4 +30,4 @@ hidden: true ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/01.\347\273\274\345\220\210/01.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223\351\235\242\350\257\225.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/01.\347\273\274\345\220\210/01.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223\351\235\242\350\257\225.md" index ee0bc746..81f9a582 100644 --- "a/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/01.\347\273\274\345\220\210/01.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223\351\235\242\350\257\225.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/01.\347\273\274\345\220\210/01.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223\351\235\242\350\257\225.md" @@ -8,12 +8,13 @@ categories: tags: - 数据库 - 关系型数据库 + - 面试 permalink: /pages/9bb28f/ --- # 关系型数据库面试 -## 一、索引和约束 +## 索引和约束 ### 什么是索引 @@ -85,7 +86,7 @@ permalink: /pages/9bb28f/ 对于任意结点,其内部的关键字 Key 是升序排列的。每个节点中都包含了 data。
- +
对于每个结点,主要包含一个关键字数组 `Key[]`,一个指针数组(指向儿子)`Son[]`。 @@ -104,7 +105,7 @@ B+Tree 是 B-Tree 的变种: - 非叶子节点不存储 data,只存储 key;叶子节点不存储指针。
- +
由于并不是所有节点都具有相同的域,因此 B+Tree 中叶节点和内节点一般大小不同。这点与 B-Tree 不同,虽然 B-Tree 中不同节点存放的 key 和指针可能数量不一致,但是每个节点的域和上限是一致的,所以在实现中 B-Tree 往往对每个节点申请同等大小的空间。 @@ -114,7 +115,7 @@ B+Tree 是 B-Tree 的变种: 一般在数据库系统或文件系统中使用的 B+Tree 结构都在经典 B+Tree 的基础上进行了优化,增加了顺序访问指针。
- +
在 B+Tree 的每个叶子节点增加一个指向相邻叶子节点的指针,就形成了带有顺序访问指针的 B+Tree。 @@ -252,7 +253,7 @@ MySQL 会一直向右匹配直到遇到范围查询 `(>,<,BETWEEN,LIKE)` 就停 - `FOREIGN KEY` - 在一个表中存在的另一个表的主键称此表的外键。用于预防破坏表之间连接的动作,也能防止非法数据插入外键列,因为它必须是它指向的那个表中的值之一。 - `CHECK` - 用于控制字段的值范围。 -## 二、并发控制 +## 并发控制 ### 乐观锁和悲观锁 @@ -336,11 +337,11 @@ MVCC 不能解决幻读问题,**Next-Key 锁就是为了解决幻读问题** 当两个事务同时执行,一个锁住了主键索引,在等待其他相关索引。另一个锁定了非主键索引,在等待主键索引。这样就会发生死锁。发生死锁后,`InnoDB` 一般都可以检测到,并使一个事务释放锁回退,另一个获取锁完成事务。 -## 三、事务 +## 事务 > 事务简单来说:**一个 Session 中所进行所有的操作,要么同时成功,要么同时失败**。具体来说,事务指的是满足 ACID 特性的一组操作,可以通过 `Commit` 提交一个事务,也可以使用 `Rollback` 进行回滚。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/RDB/数据库事务.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/database/RDB/数据库事务.png) ### ACID @@ -353,7 +354,7 @@ ACID — 数据库事务正确执行的四个基本要素: **一个支持事务(Transaction)中的数据库系统,必需要具有这四种特性,否则在事务过程(Transaction processing)当中无法保证数据的正确性,交易过程极可能达不到交易。** -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/RDB/数据库ACID.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/database/RDB/数据库ACID.png) ### 并发一致性问题 @@ -363,25 +364,25 @@ ACID — 数据库事务正确执行的四个基本要素: T1 和 T2 两个事务都对一个数据进行修改,T1 先修改,T2 随后修改,T2 的修改覆盖了 T1 的修改。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/RDB/数据库并发一致性-丢失修改.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/database/RDB/数据库并发一致性-丢失修改.png) - **脏读** T1 修改一个数据,T2 随后读取这个数据。如果 T1 撤销了这次修改,那么 T2 读取的数据是脏数据。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/RDB/数据库并发一致性-脏数据.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/database/RDB/数据库并发一致性-脏数据.png) - **不可重复读** T2 读取一个数据,T1 对该数据做了修改。如果 T2 再次读取这个数据,此时读取的结果和第一次读取的结果不同。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/RDB/数据库并发一致性-不可重复读.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/database/RDB/数据库并发一致性-不可重复读.png) - **幻读** T1 读取某个范围的数据,T2 在这个范围内插入新的数据,T1 再次读取这个范围的数据,此时读取的结果和和第一次读取的结果不同。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/RDB/数据库并发一致性-幻读.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/database/RDB/数据库并发一致性-幻读.png) 并发一致性解决方案: @@ -441,7 +442,7 @@ T1 读取某个范围的数据,T2 在这个范围内插 | 并发性能 | 无影响 | 严重衰退 | 略微衰退 | | 适合场景 | 业务方处理不一致 | 短事务 & 低并发 | 长事务 & 高并发 | -## 四、分库分表 +## 分库分表 ### 什么是分库分表 @@ -461,7 +462,7 @@ T1 读取某个范围的数据,T2 在这个范围内插 > **垂直切分**,是 **把一个有很多字段的表给拆分成多个表,或者是多个库上去**。一般来说,会 **将较少的、访问频率较高的字段放到一个表里去**,然后 **将较多的、访问频率较低的字段放到另外一个表里去**。因为数据库是有缓存的,访问频率高的行字段越少,就可以在缓存里缓存更多的行,性能就越好。这个一般在表层面做的较多一些。 -![image-20200114211639899](https://raw.githubusercontent.com/dunwu/images/dev/snap/image-20200114211639899.png) +![image-20200114211639899](https://raw.githubusercontent.com/dunwu/images/master/snap/image-20200114211639899.png) 一般来说,满足下面的条件就可以考虑扩容了: @@ -474,7 +475,7 @@ T1 读取某个范围的数据,T2 在这个范围内插 > **水平拆分** 又称为 **Sharding**,它是将同一个表中的记录拆分到多个结构相同的表中。当 **单表数据量太大** 时,会极大影响 **SQL 执行的性能** 。分表是将原来一张表的数据分布到数据库集群的不同节点上,从而缓解单点的压力。 -![image-20200114211203589](https://raw.githubusercontent.com/dunwu/images/dev/snap/image-20200114211203589.png) +![image-20200114211203589](https://raw.githubusercontent.com/dunwu/images/master/snap/image-20200114211203589.png) 一般来说,**单表有 200 万条数据** 的时候,性能就会相对差一些了,需要考虑分表了。但是,这也要视具体情况而定,可能是 100 万条,也可能是 500 万条,SQL 越复杂,就最好让单表行数越少。 @@ -577,7 +578,7 @@ T1 读取某个范围的数据,T2 在这个范围内插 来自淘宝综合业务平台团队,它利用对 2 的倍数取余具有向前兼容的特性(如对 4 取余得 1 的数对 2 取余也是 1)来分配数据,避免了行级别的数据迁移,但是依然需要进行表级别的迁移,同时对扩容规模和分表数量都有限制。总得来说,这些方案都不是十分的理想,多多少少都存在一些缺点,这也从一个侧面反映出了 Sharding 扩容的难度。 -## 五、集群 +## 集群 > 这个专题需要根据熟悉哪个数据库而定,但是主流、成熟的数据库都会实现一些基本功能,只是实现方式、策略上有所差异。由于本人较为熟悉 Mysql,所以下面主要介绍 Mysql 系统架构问题。 @@ -593,7 +594,7 @@ Mysql 支持两种复制:基于行的复制和基于语句的复制。 - **I/O 线程** :负责从主服务器上读取二进制日志文件,并写入从服务器的日志中。 - **SQL 线程** :负责读取日志并执行 SQL 语句以更新数据。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/mysql/master-slave.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/database/mysql/master-slave.png) ### 读写分离 @@ -607,9 +608,9 @@ MySQL 读写分离能提高性能的原因在于: - 从服务器可以配置 `MyISAM` 引擎,提升查询性能以及节约系统开销; - 增加冗余,提高可用性。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/mysql/master-slave-proxy.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/database/mysql/master-slave-proxy.png) -## 六、数据库优化 +## 数据库优化 数据库优化的路线一般为:SQL 优化、结构优化、配置优化、硬件优化。前两个方向一般是普通开发的考量点,而后两个方向一般是 DBA 的考量点。 @@ -842,7 +843,7 @@ SQL 关键字尽量大写,如:Oracle 默认会将 SQL 语句中的关键字 数据库扩容、使用高配设备等等。核心就是一个字:钱。 -## 七、数据库理论 +## 数据库理论 ### 函数依赖 @@ -879,7 +880,7 @@ SQL 关键字尽量大写,如:Oracle 默认会将 SQL 语句中的关键字 高级别范式的依赖于低级别的范式,1NF 是最低级别的范式。
- +
#### 第一范式 (1NF) @@ -960,7 +961,7 @@ Sname, Sdept 和 Mname 都部分依赖于键码,当一个学生选修了多门 | 学院-1 | 院长-1 | | 学院-2 | 院长-2 | -## 八、Mysql 存储引擎 +## 存储引擎 Mysql 有多种存储引擎,**不同的存储引擎保存数据和索引的方式是不同的,但表的定义则是在 Mysql 服务层统一处理的**。 @@ -986,7 +987,7 @@ InnoDB 和 MyISAM 是目前使用的最多的两种 Mysql 存储引擎。 - InnoDB 支持故障恢复。 - MyISAM 不支持故障恢复。 -## 九、数据库比较 +## 数据库比较 ### 常见数据库比较 @@ -1070,6 +1071,78 @@ where rr>5 and rr<=10; > 数据类型对比表摘自 [SQL 通用数据类型](https://www.runoob.com/sql/sql-datatypes-general.html)、[SQL 用于各种数据库的数据类型](https://www.runoob.com/sql/sql-datatypes.html) +## SQL FAQ + +### SELECT COUNT(\*)、SELECT COUNT(1) 和 SELECT COUNT(具体字段) 性能有差别吗? + +在 MySQL InnoDB 存储引擎中,`COUNT(*)` 和 `COUNT(1)` 都是对所有结果进行 `COUNT`。因此`COUNT(*)`和`COUNT(1)`本质上并没有区别,执行的复杂度都是 `O(N)`,也就是采用全表扫描,进行循环 + 计数的方式进行统计。 + +如果是 MySQL MyISAM 存储引擎,统计数据表的行数只需要`O(1)`的复杂度,这是因为每张 MyISAM 的数据表都有一个 meta 信息存储了`row_count`值,而一致性则由表级锁来保证。因为 InnoDB 支持事务,采用行级锁和 MVCC 机制,所以无法像 MyISAM 一样,只维护一个`row_count`变量,因此需要采用扫描全表,进行循环 + 计数的方式来完成统计。 + +需要注意的是,在实际执行中,`COUNT(*)`和`COUNT(1)`的执行时间可能略有差别,不过你还是可以把它俩的执行效率看成是相等的。 + +另外在 InnoDB 引擎中,如果采用`COUNT(*)`和`COUNT(1)`来统计数据行数,要尽量采用二级索引。因为主键采用的索引是聚簇索引,聚簇索引包含的信息多,明显会大于二级索引(非聚簇索引)。对于`COUNT(*)`和`COUNT(1)`来说,它们不需要查找具体的行,只是统计行数,系统会自动采用占用空间更小的二级索引来进行统计。 + +然而如果想要查找具体的行,那么采用主键索引的效率更高。如果有多个二级索引,会使用 key_len 小的二级索引进行扫描。当没有二级索引的时候,才会采用主键索引来进行统计。 + +这里我总结一下: + +1. 一般情况下,三者执行的效率为 `COUNT(*)`= `COUNT(1)`> `COUNT(字段)`。我们尽量使用`COUNT(*)`,当然如果你要统计的是某个字段的非空数据行数,则另当别论,毕竟比较执行效率的前提是结果一样才可以。 +2. 如果要统计`COUNT(*)`,尽量在数据表上建立二级索引,系统会自动采用`key_len`小的二级索引进行扫描,这样当我们使用`SELECT COUNT(*)`的时候效率就会提升,有时候可以提升几倍甚至更高。 + +> ——摘自[极客时间 - SQL 必知必会](https://time.geekbang.org/column/intro/192) + +### ORDER BY 是对分的组排序还是对分组中的记录排序呢? + +ORDER BY 就是对记录进行排序。如果你在 ORDER BY 前面用到了 GROUP BY,实际上这是一种分组的聚合方式,已经把一组的数据聚合成为了一条记录,再进行排序的时候,相当于对分的组进行了排序。 + +### SELECT 语句内部的执行步骤 + +一条完整的 SELECT 语句内部的执行顺序是这样的: + +1. FROM 子句组装数据(包括通过 ON 进行连接); +2. WHERE 子句进行条件筛选; +3. GROUP BY 分组 ; +4. 使用聚集函数进行计算; +5. HAVING 筛选分组; +6. 计算所有的表达式; +7. SELECT 的字段; +8. ORDER BY 排序; +9. LIMIT 筛选。 + +> ——摘自[极客时间 - SQL 必知必会](https://time.geekbang.org/column/intro/192) + +### 解哪种情况下应该使用 EXISTS,哪种情况应该用 IN + +索引是个前提,其实选择与否还是要看表的大小。你可以将选择的标准理解为小表驱动大表。在这种方式下效率是最高的。 + +比如下面这样: + +``` + SELECT * FROM A WHERE cc IN (SELECT cc FROM B) + SELECT * FROM A WHERE EXISTS (SELECT cc FROM B WHERE B.cc=A.cc) +``` + +当 A 小于 B 时,用 EXISTS。因为 EXISTS 的实现,相当于外表循环,实现的逻辑类似于: + +``` + for i in A + for j in B + if j.cc == i.cc then ... +``` + +当 B 小于 A 时用 IN,因为实现的逻辑类似于: + +``` + for i in B + for j in A + if j.cc == i.cc then ... +``` + +哪个表小就用哪个表来驱动,A 表小就用 EXISTS,B 表小就用 IN。 + +> ——摘自[极客时间 - SQL 必知必会](https://time.geekbang.org/column/intro/192) + ## 参考资料 - [数据库面试题(开发者必看)](https://juejin.im/post/5a9ca0d6518825555c1d1acd) @@ -1081,4 +1154,4 @@ where rr>5 and rr<=10; - [ShardingSphere 分布式事务](https://shardingsphere.apache.org/document/current/cn/features/transaction/) - [mysql 和 oracle 的区别](https://zhuanlan.zhihu.com/p/39651803) - [RUNOOB SQL 教程](https://www.runoob.com/sql/sql-tutorial.html) -- [如果有人问你数据库的原理,叫他看这篇文章](https://gameinstitute.qq.com/community/detail/107154) +- [如果有人问你数据库的原理,叫他看这篇文章](https://gameinstitute.qq.com/community/detail/107154) \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/01.\347\273\274\345\220\210/02.SQL\350\257\255\346\263\225\345\237\272\347\241\200\347\211\271\346\200\247.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/01.\347\273\274\345\220\210/02.SQL\350\257\255\346\263\225\345\237\272\347\241\200\347\211\271\346\200\247.md" new file mode 100644 index 00000000..36f19e5b --- /dev/null +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/01.\347\273\274\345\220\210/02.SQL\350\257\255\346\263\225\345\237\272\347\241\200\347\211\271\346\200\247.md" @@ -0,0 +1,601 @@ +--- +title: SQL 语法基础特性 +date: 2018-06-15 16:07:17 +categories: + - 数据库 + - 关系型数据库 + - 综合 +tags: + - 数据库 + - 关系型数据库 + - SQL +permalink: /pages/b71c9e/ +--- + +# SQL 语法基础特性 + +> 本文针对关系型数据库的基本语法。限于篇幅,本文侧重说明用法,不会展开讲解特性、原理。 +> +> 本文语法主要针对 Mysql,但大部分的语法对其他关系型数据库也适用。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200115160512.png) + +## SQL 简介 + +### 数据库术语 + +- `数据库(database)` - 保存有组织的数据的容器(通常是一个文件或一组文件)。 +- `数据表(table)` - 某种特定类型数据的结构化清单。 +- `模式(schema)` - 关于数据库和表的布局及特性的信息。模式定义了数据在表中如何存储,包含存储什么样的数据,数据如何分解,各部分信息如何命名等信息。数据库和表都有模式。 +- `列(column)` - 表中的一个字段。所有表都是由一个或多个列组成的。 +- `行(row)` - 表中的一个记录。 +- `主键(primary key)` - 一列(或一组列),其值能够唯一标识表中每一行。 + +### SQL 语法 + +> SQL(Structured Query Language),标准 SQL 由 ANSI 标准委员会管理,从而称为 ANSI SQL。各个 DBMS 都有自己的实现,如 PL/SQL、Transact-SQL 等。 + +#### SQL 语法结构 + +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/database/mysql/sql-syntax.png) + +SQL 语法结构包括: + +- **`子句`** - 是语句和查询的组成成分。(在某些情况下,这些都是可选的。) +- **`表达式`** - 可以产生任何标量值,或由列和行的数据库表 +- **`谓词`** - 给需要评估的 SQL 三值逻辑(3VL)(true/false/unknown)或布尔真值指定条件,并限制语句和查询的效果,或改变程序流程。 +- **`查询`** - 基于特定条件检索数据。这是 SQL 的一个重要组成部分。 +- **`语句`** - 可以持久地影响纲要和数据,也可以控制数据库事务、程序流程、连接、会话或诊断。 + +#### SQL 语法要点 + +- **SQL 语句不区分大小写**,但是数据库表名、列名和值是否区分,依赖于具体的 DBMS 以及配置。 + +例如:`SELECT` 与 `select` 、`Select` 是相同的。 + +- **多条 SQL 语句必须以分号(`;`)分隔**。 + +- 处理 SQL 语句时,**所有空格都被忽略**。SQL 语句可以写成一行,也可以分写为多行。 + +```sql +-- 一行 SQL 语句 +UPDATE user SET username='robot', password='robot' WHERE username = 'root'; + +-- 多行 SQL 语句 +UPDATE user +SET username='robot', password='robot' +WHERE username = 'root'; +``` + +- SQL 支持三种注释 + +```sql +## 注释1 +-- 注释2 +/* 注释3 */ +``` + +#### SQL 分类 + +#### 数据定义语言(DDL) + +数据定义语言(Data Definition Language,DDL)是 SQL 语言集中负责数据结构定义与数据库对象定义的语言。 + +DDL 的主要功能是**定义数据库对象**。 + +DDL 的核心指令是 `CREATE`、`ALTER`、`DROP`。 + +#### 数据操纵语言(DML) + +数据操纵语言(Data Manipulation Language, DML)是用于数据库操作,对数据库其中的对象和数据运行访问工作的编程语句。 + +DML 的主要功能是 **访问数据**,因此其语法都是以**读写数据库**为主。 + +DML 的核心指令是 `INSERT`、`UPDATE`、`DELETE`、`SELECT`。这四个指令合称 CRUD(Create, Read, Update, Delete),即增删改查。 + +#### 事务控制语言(TCL) + +事务控制语言 (Transaction Control Language, TCL) 用于**管理数据库中的事务**。这些用于管理由 DML 语句所做的更改。它还允许将语句分组为逻辑事务。 + +TCL 的核心指令是 `COMMIT`、`ROLLBACK`。 + +#### 数据控制语言(DCL) + +数据控制语言 (Data Control Language, DCL) 是一种可对数据访问权进行控制的指令,它可以控制特定用户账户对数据表、查看表、预存程序、用户自定义函数等数据库对象的控制权。 + +DCL 的核心指令是 `GRANT`、`REVOKE`。 + +DCL 以**控制用户的访问权限**为主,因此其指令作法并不复杂,可利用 DCL 控制的权限有:`CONNECT`、`SELECT`、`INSERT`、`UPDATE`、`DELETE`、`EXECUTE`、`USAGE`、`REFERENCES`。 + +根据不同的 DBMS 以及不同的安全性实体,其支持的权限控制也有所不同。 + +--- + +**(以下为 DML 语句用法)** + +## 增删改查(CRUD) + +增删改查,又称为 **`CRUD`**,是数据库基本操作中的基本操作。 + +### 插入数据 + +> - `INSERT INTO` 语句用于向表中插入新记录。 + +#### 插入完整的行 + +```sql +INSERT INTO user +VALUES (10, 'root', 'root', 'xxxx@163.com'); +``` + +#### 插入行的一部分 + +```sql +INSERT INTO user(username, password, email) +VALUES ('admin', 'admin', 'xxxx@163.com'); +``` + +#### 插入查询出来的数据 + +```sql +INSERT INTO user(username) +SELECT name +FROM account; +``` + +### 更新数据 + +> - `UPDATE` 语句用于更新表中的记录。 + +```sql +UPDATE user +SET username='robot', password='robot' +WHERE username = 'root'; +``` + +### 删除数据 + +> - `DELETE` 语句用于删除表中的记录。 +> - `TRUNCATE TABLE` 可以清空表,也就是删除所有行。 + +#### 删除表中的指定数据 + +```sql +DELETE FROM user WHERE username = 'robot'; +``` + +#### 清空表中的数据 + +```sql +TRUNCATE TABLE user; +``` + +### 查询数据 + +> - `SELECT` 语句用于从数据库中查询数据。 +> - `DISTINCT` 用于返回唯一不同的值。它作用于所有列,也就是说所有列的值都相同才算相同。 +> - `LIMIT` 限制返回的行数。可以有两个参数,第一个参数为起始行,从 0 开始;第二个参数为返回的总行数。 +> - `ASC` :升序(默认) +> - `DESC` :降序 + +#### 查询单列 + +```sql +SELECT prod_name FROM products; +``` + +#### 查询多列 + +```sql +SELECT prod_id, prod_name, prod_price FROM products; +``` + +#### 查询所有列 + +```sql +SELECT * FROM products; +``` + +#### 查询不同的值 + +```sql +SELECT DISTINCT vend_id FROM products; +``` + +#### 限制查询数量 + +```sql +-- 返回前 5 行 +SELECT * FROM products LIMIT 5; +SELECT * FROM products LIMIT 0, 5; +-- 返回第 3 ~ 5 行 +SELECT * FROM products LIMIT 2, 3; +``` + +## 过滤数据(WHERE) + +子查询是嵌套在较大查询中的 SQL 查询。子查询也称为**内部查询**或**内部选择**,而包含子查询的语句也称为**外部查询**或**外部选择**。 + +- 子查询可以嵌套在 `SELECT`,`INSERT`,`UPDATE` 或 `DELETE` 语句内或另一个子查询中。 + +- 子查询通常会在另一个 `SELECT` 语句的 `WHERE` 子句中添加。 + +- 您可以使用比较运算符,如 `>`,`<`,或 `=`。比较运算符也可以是多行运算符,如 `IN`,`ANY` 或 `ALL`。 + +- 子查询必须被圆括号 `()` 括起来。 + +- 内部查询首先在其父查询之前执行,以便可以将内部查询的结果传递给外部查询。执行过程可以参考下图: + +

+ sql-subqueries +

+ +**子查询的子查询** + +```sql +SELECT cust_name, cust_contact +FROM customers +WHERE cust_id IN (SELECT cust_id + FROM orders + WHERE order_num IN (SELECT order_num + FROM orderitems + WHERE prod_id = 'RGAN01')); +``` + +### WHERE 子句 + +在 SQL 语句中,数据根据 `WHERE` 子句中指定的搜索条件进行过滤。 + +`WHERE` 子句的基本格式如下: + +```sql +SELECT ……(列名) FROM ……(表名) WHERE ……(子句条件) +``` + +`WHERE` 子句用于过滤记录,即缩小访问数据的范围。`WHERE` 后跟一个返回 `true` 或 `false` 的条件。 + +`WHERE` 可以与 `SELECT`,`UPDATE` 和 `DELETE` 一起使用。 + +**`SELECT` 语句中的 `WHERE` 子句** + +```sql +SELECT * FROM Customers +WHERE cust_name = 'Kids Place'; +``` + +**`UPDATE` 语句中的 `WHERE` 子句** + +```sql +UPDATE Customers +SET cust_name = 'Jack Jones' +WHERE cust_name = 'Kids Place'; +``` + +**`DELETE` 语句中的 `WHERE` 子句** + +```sql +DELETE FROM Customers +WHERE cust_name = 'Kids Place'; +``` + +可以在 `WHERE` 子句中使用的操作符: + +### 比较操作符 + +| 运算符 | 描述 | +| ------ | ------------------------------------------------------ | +| `=` | 等于 | +| `<>` | 不等于。注释:在 SQL 的一些版本中,该操作符可被写成 != | +| `>` | 大于 | +| `<` | 小于 | +| `>=` | 大于等于 | +| `<=` | 小于等于 | + +### 范围操作符 + +| 运算符 | 描述 | +| --------- | -------------------------- | +| `BETWEEN` | 在某个范围内 | +| `IN` | 指定针对某个列的多个可能值 | + +- `IN` 操作符在 `WHERE` 子句中使用,作用是在指定的几个特定值中任选一个值。 + +- `BETWEEN` 操作符在 `WHERE` 子句中使用,作用是选取介于某个范围内的值。 + +**IN 示例** + +```sql +SELECT * +FROM products +WHERE vend_id IN ('DLL01', 'BRS01'); +``` + +**BETWEEN 示例** + +```sql +SELECT * +FROM products +WHERE prod_price BETWEEN 3 AND 5; +``` + +### 逻辑操作符 + +| 运算符 | 描述 | +| ------ | ---------- | +| `AND` | 并且(与) | +| `OR` | 或者(或) | +| `NOT` | 否定(非) | + +`AND`、`OR`、`NOT` 是用于对过滤条件的逻辑处理指令。 + +- `AND` 优先级高于 `OR`,为了明确处理顺序,可以使用 `()`。`AND` 操作符表示左右条件都要满足。 +- `OR` 操作符表示左右条件满足任意一个即可。 + +- `NOT` 操作符用于否定一个条件。 + +**AND 示例** + +```sql +SELECT prod_id, prod_name, prod_price +FROM products +WHERE vend_id = 'DLL01' AND prod_price <= 4; +``` + +**OR 示例** + +```sql +SELECT prod_id, prod_name, prod_price +FROM products +WHERE vend_id = 'DLL01' OR vend_id = 'BRS01'; +``` + +**NOT 示例** + +```sql +SELECT * +FROM products +WHERE prod_price NOT BETWEEN 3 AND 5; +``` + +### 通配符 + +| 运算符 | 描述 | +| ------ | -------------------------- | +| `LIKE` | 搜索某种模式 | +| `%` | 表示任意字符出现任意次数 | +| `_` | 表示任意字符出现一次 | +| `[]` | 必须匹配指定位置的一个字符 | + +`LIKE` 操作符在 `WHERE` 子句中使用,作用是确定字符串是否匹配模式。只有字段是文本值时才使用 `LIKE`。 + +`LIKE` 支持以下通配符匹配选项: + +- `%` 表示任何字符出现任意次数。 +- `_` 表示任何字符出现一次。 +- `[]` 必须匹配指定位置的一个字符。 + +> 注意:**不要滥用通配符,通配符位于开头处匹配会非常慢**。 + +`%` 示例: + +```sql +SELECT prod_id, prod_name, prod_price +FROM products +WHERE prod_name LIKE '%bean bag%'; +``` + +`_` 示例: + +```sql +SELECT prod_id, prod_name, prod_price +FROM products +WHERE prod_name LIKE '__ inch teddy bear'; +``` + +- + +## 排序(ORDER BY) + +> `ORDER BY` 用于对结果集进行排序。 + +`ORDER BY` 有两种排序模式: + +- `ASC` :升序(默认) +- `DESC` :降序 + +可以按多个列进行排序,并且为每个列指定不同的排序方式。 + +指定多个列的排序示例: + +```sql +SELECT * FROM products +ORDER BY prod_price DESC, prod_name ASC; +``` + +## 数据定义(CREATE、ALTER、DROP) + +> DDL 的主要功能是定义数据库对象(如:数据库、数据表、视图、索引等)。 + +### 数据库(DATABASE) + +#### 创建数据库 + +```sql +CREATE DATABASE IF NOT EXISTS db_tutorial; +``` + +#### 删除数据库 + +```sql +DROP DATABASE IF EXISTS db_tutorial; +``` + +#### 选择数据库 + +```sql +USE db_tutorial; +``` + +### 数据表(TABLE) + +#### 删除数据表 + +```sql +DROP TABLE IF EXISTS user; +DROP TABLE IF EXISTS vip_user; +``` + +#### 创建数据表 + +**普通创建** + +```sql +CREATE TABLE user ( + id INT(10) UNSIGNED NOT NULL COMMENT 'Id', + username VARCHAR(64) NOT NULL DEFAULT 'default' COMMENT '用户名', + password VARCHAR(64) NOT NULL DEFAULT 'default' COMMENT '密码', + email VARCHAR(64) NOT NULL DEFAULT 'default' COMMENT '邮箱' +) COMMENT ='用户表'; +``` + +**根据已有的表创建新表** + +```sql +CREATE TABLE vip_user AS +SELECT * +FROM user; +``` + +#### 修改数据表 + +##### 添加列 + +```sql +ALTER TABLE user +ADD age int(3); +``` + +##### 删除列 + +```sql +ALTER TABLE user +DROP COLUMN age; +``` + +##### 修改列 + +```sql +ALTER TABLE `user` +MODIFY COLUMN age tinyint; +``` + +### 视图(VIEW) + +> 视图是基于 SQL 语句的结果集的可视化的表。**视图是虚拟的表,本身不存储数据,也就不能对其进行索引操作**。对视图的操作和对普通表的操作一样。 + +视图的作用: + +- 简化复杂的 SQL 操作,比如复杂的连接。 +- 只使用实际表的一部分数据。 +- 通过只给用户访问视图的权限,保证数据的安全性。 +- 更改数据格式和表示。 + +#### 创建视图 + +```sql +CREATE VIEW top_10_user_view AS +SELECT id, username +FROM user +WHERE id < 10; +``` + +#### 删除视图 + +```sql +DROP VIEW top_10_user_view; +``` + +### 索引(INDEX) + +> 通过索引可以更加快速高效地查询数据。用户无法看到索引,它们只能被用来加速查询。 + +更新一个包含索引的表需要比更新一个没有索引的表花费更多的时间,这是由于索引本身也需要更新。因此,理想的做法是仅仅在常常被搜索的列(以及表)上面创建索引。 + +唯一索引:唯一索引表明此索引的每一个索引值只对应唯一的数据记录。 + +#### 创建索引 + +```sql +CREATE INDEX idx_email + ON user(email); +``` + +#### 创建唯一索引 + +```sql +CREATE UNIQUE INDEX uniq_username + ON user(username); +``` + +#### 删除索引 + +```sql +ALTER TABLE user +DROP INDEX idx_email; +ALTER TABLE user +DROP INDEX uniq_username; +``` + +#### 添加主键 + +```sql +ALTER TABLE user +ADD PRIMARY KEY (id); +``` + +#### 删除主键 + +```sql +ALTER TABLE user +DROP PRIMARY KEY; +``` + +### 约束 + +> SQL 约束用于规定表中的数据规则。 + +- 如果存在违反约束的数据行为,行为会被约束终止。 +- 约束可以在创建表时规定(通过 CREATE TABLE 语句),或者在表创建之后规定(通过 ALTER TABLE 语句)。 +- 约束类型 + - `NOT NULL` - 指示某列不能存储 NULL 值。 + - `UNIQUE` - 保证某列的每行必须有唯一的值。 + - `PRIMARY KEY` - NOT NULL 和 UNIQUE 的结合。确保某列(或两个列多个列的结合)有唯一标识,有助于更容易更快速地找到表中的一个特定的记录。 + - `FOREIGN KEY` - 保证一个表中的数据匹配另一个表中的值的参照完整性。 + - `CHECK` - 保证列中的值符合指定的条件。 + - `DEFAULT` - 规定没有给列赋值时的默认值。 + +创建表时使用约束条件: + +```sql +CREATE TABLE Users ( + Id INT(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增Id', + Username VARCHAR(64) NOT NULL UNIQUE DEFAULT 'default' COMMENT '用户名', + Password VARCHAR(64) NOT NULL DEFAULT 'default' COMMENT '密码', + Email VARCHAR(64) NOT NULL DEFAULT 'default' COMMENT '邮箱地址', + Enabled TINYINT(4) DEFAULT NULL COMMENT '是否有效', + PRIMARY KEY (Id) +) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COMMENT='用户表'; +``` + +## 参考资料 + +- [《SQL 必知必会》](https://book.douban.com/subject/35167240/) +- [『浅入深出』MySQL 中事务的实现](https://draveness.me/mysql-transaction) +- [MySQL 的学习--触发器](https://www.cnblogs.com/CraryPrimitiveMan/p/4206942.html) +- [维基百科词条 - SQL](https://zh.wikipedia.org/wiki/SQL) +- [https://www.sitesbay.com/sql/index](https://www.sitesbay.com/sql/index) +- [SQL Subqueries](https://www.w3resource.com/sql/subqueries/understanding-sql-subqueries.php) +- [Quick breakdown of the types of joins](https://stackoverflow.com/questions/6294778/mysql-quick-breakdown-of-the-types-of-joins) +- [SQL UNION](https://www.w3resource.com/sql/sql-union.php) +- [SQL database security](https://www.w3resource.com/sql/database-security/create-users.php) +- [Mysql 中的存储过程](https://www.cnblogs.com/chenpi/p/5136483.html) \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/01.\347\273\274\345\220\210/02.SqlCheatSheet.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/01.\347\273\274\345\220\210/02.SqlCheatSheet.md" deleted file mode 100644 index d9ec04e7..00000000 --- "a/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/01.\347\273\274\345\220\210/02.SqlCheatSheet.md" +++ /dev/null @@ -1,1119 +0,0 @@ ---- -title: sql-cheat-sheet -date: 2018-06-15 16:07:17 -categories: - - 数据库 - - 关系型数据库 - - 综合 -tags: - - 数据库 - - 关系型数据库 - - SQL -permalink: /pages/b71c9e/ ---- - -# SQL Cheat Sheet - -> 本文针对关系型数据库的基本语法。限于篇幅,本文侧重说明用法,不会展开讲解特性、原理。 -> -> 本文语法主要针对 Mysql,但大部分的语法对其他关系型数据库也适用。 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200115160512.png) - -## 一、基本概念 - -### 数据库术语 - -- `数据库(database)` - 保存有组织的数据的容器(通常是一个文件或一组文件)。 -- `数据表(table)` - 某种特定类型数据的结构化清单。 -- `模式(schema)` - 关于数据库和表的布局及特性的信息。模式定义了数据在表中如何存储,包含存储什么样的数据,数据如何分解,各部分信息如何命名等信息。数据库和表都有模式。 -- `列(column)` - 表中的一个字段。所有表都是由一个或多个列组成的。 -- `行(row)` - 表中的一个记录。 -- `主键(primary key)` - 一列(或一组列),其值能够唯一标识表中每一行。 - -### SQL 语法 - -> SQL(Structured Query Language),标准 SQL 由 ANSI 标准委员会管理,从而称为 ANSI SQL。各个 DBMS 都有自己的实现,如 PL/SQL、Transact-SQL 等。 - -#### SQL 语法结构 - -![img](https://raw.githubusercontent.com/dunwu/images/master/cs/database/mysql/sql-syntax.png) - -SQL 语法结构包括: - -- **`子句`** - 是语句和查询的组成成分。(在某些情况下,这些都是可选的。) -- **`表达式`** - 可以产生任何标量值,或由列和行的数据库表 -- **`谓词`** - 给需要评估的 SQL 三值逻辑(3VL)(true/false/unknown)或布尔真值指定条件,并限制语句和查询的效果,或改变程序流程。 -- **`查询`** - 基于特定条件检索数据。这是 SQL 的一个重要组成部分。 -- **`语句`** - 可以持久地影响纲要和数据,也可以控制数据库事务、程序流程、连接、会话或诊断。 - -#### SQL 语法要点 - -- **SQL 语句不区分大小写**,但是数据库表名、列名和值是否区分,依赖于具体的 DBMS 以及配置。 - -例如:`SELECT` 与 `select` 、`Select` 是相同的。 - -- **多条 SQL 语句必须以分号(`;`)分隔**。 - -- 处理 SQL 语句时,**所有空格都被忽略**。SQL 语句可以写成一行,也可以分写为多行。 - -```sql --- 一行 SQL 语句 -UPDATE user SET username='robot', password='robot' WHERE username = 'root'; - --- 多行 SQL 语句 -UPDATE user -SET username='robot', password='robot' -WHERE username = 'root'; -``` - -- SQL 支持三种注释 - -```sql -## 注释1 --- 注释2 -/* 注释3 */ -``` - -#### SQL 分类 - -#### 数据定义语言(DDL) - -数据定义语言(Data Definition Language,DDL)是 SQL 语言集中负责数据结构定义与数据库对象定义的语言。 - -DDL 的主要功能是**定义数据库对象**。 - -DDL 的核心指令是 `CREATE`、`ALTER`、`DROP`。 - -#### 数据操纵语言(DML) - -数据操纵语言(Data Manipulation Language, DML)是用于数据库操作,对数据库其中的对象和数据运行访问工作的编程语句。 - -DML 的主要功能是 **访问数据**,因此其语法都是以**读写数据库**为主。 - -DML 的核心指令是 `INSERT`、`UPDATE`、`DELETE`、`SELECT`。这四个指令合称 CRUD(Create, Read, Update, Delete),即增删改查。 - -#### 事务控制语言(TCL) - -事务控制语言 (Transaction Control Language, TCL) 用于**管理数据库中的事务**。这些用于管理由 DML 语句所做的更改。它还允许将语句分组为逻辑事务。 - -TCL 的核心指令是 `COMMIT`、`ROLLBACK`。 - -#### 数据控制语言(DCL) - -数据控制语言 (Data Control Language, DCL) 是一种可对数据访问权进行控制的指令,它可以控制特定用户账户对数据表、查看表、预存程序、用户自定义函数等数据库对象的控制权。 - -DCL 的核心指令是 `GRANT`、`REVOKE`。 - -DCL 以**控制用户的访问权限**为主,因此其指令作法并不复杂,可利用 DCL 控制的权限有:`CONNECT`、`SELECT`、`INSERT`、`UPDATE`、`DELETE`、`EXECUTE`、`USAGE`、`REFERENCES`。 - -根据不同的 DBMS 以及不同的安全性实体,其支持的权限控制也有所不同。 - ---- - -**(以下为 DML 语句用法)** - -## 二、增删改查 - -> 增删改查,又称为 CRUD,数据库基本操作中的基本操作。 - -### 插入数据 - -> - `INSERT INTO` 语句用于向表中插入新记录。 - -**插入完整的行** - -```sql -INSERT INTO user -VALUES (10, 'root', 'root', 'xxxx@163.com'); -``` - -**插入行的一部分** - -```sql -INSERT INTO user(username, password, email) -VALUES ('admin', 'admin', 'xxxx@163.com'); -``` - -**插入查询出来的数据** - -```sql -INSERT INTO user(username) -SELECT name -FROM account; -``` - -### 更新数据 - -> - `UPDATE` 语句用于更新表中的记录。 - -```sql -UPDATE user -SET username='robot', password='robot' -WHERE username = 'root'; -``` - -### 删除数据 - -> - `DELETE` 语句用于删除表中的记录。 -> - `TRUNCATE TABLE` 可以清空表,也就是删除所有行。 - -**删除表中的指定数据** - -```sql -DELETE FROM user -WHERE username = 'robot'; -``` - -**清空表中的数据** - -```sql -TRUNCATE TABLE user; -``` - -### 查询数据 - -> - `SELECT` 语句用于从数据库中查询数据。 -> - `DISTINCT` 用于返回唯一不同的值。它作用于所有列,也就是说所有列的值都相同才算相同。 -> - `LIMIT` 限制返回的行数。可以有两个参数,第一个参数为起始行,从 0 开始;第二个参数为返回的总行数。 -> - `ASC` :升序(默认) -> - `DESC` :降序 - -**查询单列** - -```sql -SELECT prod_name -FROM products; -``` - -**查询多列** - -```sql -SELECT prod_id, prod_name, prod_price -FROM products; -``` - -**查询所有列** - -```sql -ELECT * -FROM products; -``` - -**查询不同的值** - -```sql -SELECT DISTINCT -vend_id FROM products; -``` - -**限制查询结果** - -```sql --- 返回前 5 行 -SELECT * FROM mytable LIMIT 5; -SELECT * FROM mytable LIMIT 0, 5; --- 返回第 3 ~ 5 行 -SELECT * FROM mytable LIMIT 2, 3; -``` - -## 三、子查询 - -> 子查询是嵌套在较大查询中的 SQL 查询。子查询也称为**内部查询**或**内部选择**,而包含子查询的语句也称为**外部查询**或**外部选择**。 - -- 子查询可以嵌套在 `SELECT`,`INSERT`,`UPDATE` 或 `DELETE` 语句内或另一个子查询中。 - -- 子查询通常会在另一个 `SELECT` 语句的 `WHERE` 子句中添加。 - -- 您可以使用比较运算符,如 `>`,`<`,或 `=`。比较运算符也可以是多行运算符,如 `IN`,`ANY` 或 `ALL`。 - -- 子查询必须被圆括号 `()` 括起来。 - -- 内部查询首先在其父查询之前执行,以便可以将内部查询的结果传递给外部查询。执行过程可以参考下图: - -

- sql-subqueries -

- -**子查询的子查询** - -```sql -SELECT cust_name, cust_contact -FROM customers -WHERE cust_id IN (SELECT cust_id - FROM orders - WHERE order_num IN (SELECT order_num - FROM orderitems - WHERE prod_id = 'RGAN01')); -``` - -### WHERE - -> `WHERE` 子句用于过滤记录,即缩小访问数据的范围。`WHERE` 后跟一个返回 `true` 或 `false` 的条件。 -> -> `WHERE` 可以与 `SELECT`,`UPDATE` 和 `DELETE` 一起使用。 - -可以在 `WHERE` 子句中使用的操作符: - -| 运算符 | 描述 | -| ------- | ------------------------------------------------------ | -| = | 等于 | -| <> | 不等于。注释:在 SQL 的一些版本中,该操作符可被写成 != | -| > | 大于 | -| < | 小于 | -| >= | 大于等于 | -| <= | 小于等于 | -| BETWEEN | 在某个范围内 | -| LIKE | 搜索某种模式 | -| IN | 指定针对某个列的多个可能值 | - -**`SELECT` 语句中的 `WHERE` 子句** - -```sql -SELECT * FROM Customers -WHERE cust_name = 'Kids Place'; -``` - -**`UPDATE` 语句中的 `WHERE` 子句** - -```sql -UPDATE Customers -SET cust_name = 'Jack Jones' -WHERE cust_name = 'Kids Place'; -``` - -**`DELETE` 语句中的 `WHERE` 子句** - -```sql -DELETE FROM Customers -WHERE cust_name = 'Kids Place'; -``` - -### IN 和 BETWEEN - -> `IN` 操作符在 `WHERE` 子句中使用,作用是在指定的几个特定值中任选一个值。 -> -> `BETWEEN` 操作符在 `WHERE` 子句中使用,作用是选取介于某个范围内的值。 - -**IN 示例** - -```sql -SELECT * -FROM products -WHERE vend_id IN ('DLL01', 'BRS01'); -``` - -**BETWEEN 示例** - -```sql -SELECT * -FROM products -WHERE prod_price BETWEEN 3 AND 5; -``` - -### AND、OR、NOT - -> `AND`、`OR`、`NOT` 是用于对过滤条件的逻辑处理指令。 -> -> `AND` 优先级高于 `OR`,为了明确处理顺序,可以使用 `()`。`AND` 操作符表示左右条件都要满足。 -> -> `OR` 操作符表示左右条件满足任意一个即可。 -> -> `NOT` 操作符用于否定一个条件。 - -**AND 示例** - -```sql -SELECT prod_id, prod_name, prod_price -FROM products -WHERE vend_id = 'DLL01' AND prod_price <= 4; -``` - -**OR 示例** - -```sql -SELECT prod_id, prod_name, prod_price -FROM products -WHERE vend_id = 'DLL01' OR vend_id = 'BRS01'; -``` - -**NOT 示例** - -```sql -SELECT * -FROM products -WHERE prod_price NOT BETWEEN 3 AND 5; -``` - -### LIKE - -> `LIKE` 操作符在 `WHERE` 子句中使用,作用是确定字符串是否匹配模式。只有字段是文本值时才使用 `LIKE`。 -> -> `LIKE` 支持两个通配符匹配选项:`%` 和 `_`。 -> -> - `%` 表示任何字符出现任意次数。 -> - `_` 表示任何字符出现一次。 -> -> 不要滥用通配符,通配符位于开头处匹配会非常慢。 - -`%` 示例: - -```sql -SELECT prod_id, prod_name, prod_price -FROM products -WHERE prod_name LIKE '%bean bag%'; -``` - -`_` 示例: - -```sql -SELECT prod_id, prod_name, prod_price -FROM products -WHERE prod_name LIKE '__ inch teddy bear'; -``` - -## 四、连接和组合 - -### 连接(JOIN) - -> 连接用于连接多个表,使用 `JOIN` 关键字,并且条件语句使用 `ON` 而不是 `WHERE`。 - -如果一个 `JOIN` 至少有一个公共字段并且它们之间存在关系,则该 `JOIN` 可以在两个或多个表上工作。 - -`JOIN` 保持基表(结构和数据)不变。**连接可以替换子查询,并且比子查询的效率一般会更快**。 - -`JOIN` 有两种连接类型:内连接和外连接。 - -
- sql-join -
- -#### 内连接(INNER JOIN) - -内连接又称等值连接,**使用 `INNER JOIN` 关键字**。在没有条件语句的情况下**返回笛卡尔积**。 - -```sql -SELECT vend_name, prod_name, prod_price -FROM vendors INNER JOIN products -ON vendors.vend_id = products.vend_id; -``` - -##### 自连接(`=`) - -自连接可以看成内连接的一种,只是**连接的表是自身**而已。**自然连接是把同名列通过 `=` 连接起来**的,同名列可以有多个。 - -```sql -SELECT c1.cust_id, c1.cust_name, c1.cust_contact -FROM customers c1, customers c2 -WHERE c1.cust_name = c2.cust_name -AND c2.cust_contact = 'Jim Jones'; -``` - -##### 自然连接(NATURAL JOIN) - -内连接提供连接的列,而自然连接**自动连接所有同名列**。自然连接使用 `NATURAL JOIN` 关键字。 - -```sql -SELECT * -FROM Products -NATURAL JOIN Customers; -``` - -#### 外连接(OUTER JOIN) - -外连接返回一个表中的所有行,并且仅返回来自此表中满足连接条件的那些行,即两个表中的列是相等的。外连接分为左外连接、右外连接、全外连接(Mysql 不支持)。 - -##### 左连接(LEFT JOIN) - -左外连接就是保留左表没有关联的行。 - -```sql -SELECT customers.cust_id, orders.order_num -FROM customers LEFT JOIN orders -ON customers.cust_id = orders.cust_id; -``` - -##### 右连接(RIGHT JOIN) - -右外连接就是保留右表没有关联的行。 - -```sql -SELECT customers.cust_id, orders.order_num -FROM customers RIGHT JOIN orders -ON customers.cust_id = orders.cust_id; -``` - -### 组合(UNION) - -> `UNION` 运算符**将两个或更多查询的结果组合起来,并生成一个结果集**,其中包含来自 `UNION` 中参与查询的提取行。 - -`UNION` 基本规则: - -- 所有查询的列数和列顺序必须相同。 -- 每个查询中涉及表的列的数据类型必须相同或兼容。 -- 通常返回的列名取自第一个查询。 - -默认会去除相同行,如果需要保留相同行,使用 `UNION ALL`。 - -只能包含一个 `ORDER BY` 子句,并且必须位于语句的最后。 - -应用场景: - -- 在一个查询中从不同的表返回结构数据。 -- 对一个表执行多个查询,按一个查询返回数据。 - -组合查询示例: - -```sql -SELECT cust_name, cust_contact, cust_email -FROM customers -WHERE cust_state IN ('IL', 'IN', 'MI') -UNION -SELECT cust_name, cust_contact, cust_email -FROM customers -WHERE cust_name = 'Fun4All'; -``` - -### JOIN vs UNION - -- `JOIN` 中连接表的列可能不同,但在 `UNION` 中,所有查询的列数和列顺序必须相同。 -- `UNION` 将查询之后的行放在一起(垂直放置),但 `JOIN` 将查询之后的列放在一起(水平放置),即它构成一个笛卡尔积。 - -## 五、函数 - -> 🔔 注意:不同数据库的函数往往各不相同,因此不可移植。本节主要以 Mysql 的函数为例。 - -### 文本处理 - -| 函数 | 说明 | -| :------------------: | :--------------------: | -| `LEFT()`、`RIGHT()` | 左边或者右边的字符 | -| `LOWER()`、`UPPER()` | 转换为小写或者大写 | -| `LTRIM()`、`RTIM()` | 去除左边或者右边的空格 | -| `LENGTH()` | 长度 | -| `SOUNDEX()` | 转换为语音值 | - -其中, **SOUNDEX()** 可以将一个字符串转换为描述其语音表示的字母数字模式。 - -```sql -SELECT * -FROM mytable -WHERE SOUNDEX(col1) = SOUNDEX('apple') -``` - -### 日期和时间处理 - -- 日期格式:`YYYY-MM-DD` -- 时间格式:`HH:MM:SS` - -| 函 数 | 说 明 | -| :-------------: | :----------------------------: | -| `AddDate()` | 增加一个日期(天、周等) | -| `AddTime()` | 增加一个时间(时、分等) | -| `CurDate()` | 返回当前日期 | -| `CurTime()` | 返回当前时间 | -| `Date()` | 返回日期时间的日期部分 | -| `DateDiff()` | 计算两个日期之差 | -| `Date_Add()` | 高度灵活的日期运算函数 | -| `Date_Format()` | 返回一个格式化的日期或时间串 | -| `Day()` | 返回一个日期的天数部分 | -| `DayOfWeek()` | 对于一个日期,返回对应的星期几 | -| `Hour()` | 返回一个时间的小时部分 | -| `Minute()` | 返回一个时间的分钟部分 | -| `Month()` | 返回一个日期的月份部分 | -| `Now()` | 返回当前日期和时间 | -| `Second()` | 返回一个时间的秒部分 | -| `Time()` | 返回一个日期时间的时间部分 | -| `Year()` | 返回一个日期的年份部分 | - -```sql -mysql> SELECT NOW(); -``` - -``` -2018-4-14 20:25:11 -``` - -### 数值处理 - -| 函数 | 说明 | -| :----: | :----: | -| SIN() | 正弦 | -| COS() | 余弦 | -| TAN() | 正切 | -| ABS() | 绝对值 | -| SQRT() | 平方根 | -| MOD() | 余数 | -| EXP() | 指数 | -| PI() | 圆周率 | -| RAND() | 随机数 | - -### 汇总 - -| 函 数 | 说 明 | -| :-------: | :--------------: | -| `AVG()` | 返回某列的平均值 | -| `COUNT()` | 返回某列的行数 | -| `MAX()` | 返回某列的最大值 | -| `MIN()` | 返回某列的最小值 | -| `SUM()` | 返回某列值之和 | - -`AVG()` 会忽略 NULL 行。 - -使用 DISTINCT 可以让汇总函数值汇总不同的值。 - -```sql -SELECT AVG(DISTINCT col1) AS avg_col -FROM mytable -``` - -## 六、排序和分组 - -### ORDER BY - -> `ORDER BY` 用于对结果集进行排序。 - -`ORDER BY` 有两种排序模式: - -- `ASC` :升序(默认) -- `DESC` :降序 - -可以按多个列进行排序,并且为每个列指定不同的排序方式。 - -指定多个列的排序示例: - -```sql -SELECT * FROM products -ORDER BY prod_price DESC, prod_name ASC; -``` - -### GROUP BY - -> `GROUP BY` 子句将记录分组到汇总行中,`GROUP BY` 为每个组返回一个记录。 - -`GROUP BY` 可以按一列或多列进行分组。 - -`GROUP BY` 通常还涉及聚合函数:COUNT,MAX,SUM,AVG 等。 - -`GROUP BY` 按分组字段进行排序后,`ORDER BY` 可以以汇总字段来进行排序。 - -分组示例: - -```sql -SELECT cust_name, COUNT(cust_address) AS addr_num -FROM Customers GROUP BY cust_name; -``` - -分组后排序示例: - -```sql -SELECT cust_name, COUNT(cust_address) AS addr_num -FROM Customers GROUP BY cust_name -ORDER BY cust_name DESC; -``` - -### HAVING - -> `HAVING` 用于对汇总的 `GROUP BY` 结果进行过滤。`HAVING` 要求存在一个 `GROUP BY` 子句。 - -`WHERE` 和 `HAVING` 可以在相同的查询中。 - -`HAVING` vs `WHERE`: - -- `WHERE` 和 `HAVING` 都是用于过滤。 -- `HAVING` 适用于汇总的组记录;而 `WHERE` 适用于单个记录。 - -使用 `WHERE` 和 `HAVING` 过滤数据示例: - -```sql -SELECT cust_name, COUNT(*) AS num -FROM Customers -WHERE cust_email IS NOT NULL -GROUP BY cust_name -HAVING COUNT(*) >= 1; -``` - ---- - -**(以下为 DDL 语句用法)** - -## 七、数据定义 - -> DDL 的主要功能是定义数据库对象(如:数据库、数据表、视图、索引等)。 - -### 数据库(DATABASE) - -#### 创建数据库 - -```sql -CREATE DATABASE test; -``` - -#### 删除数据库 - -```sql -DROP DATABASE test; -``` - -#### 选择数据库 - -```sql -USE test; -``` - -### 数据表(TABLE) - -#### 创建数据表 - -**普通创建** - -```sql -CREATE TABLE user ( - id int(10) unsigned NOT NULL COMMENT 'Id', - username varchar(64) NOT NULL DEFAULT 'default' COMMENT '用户名', - password varchar(64) NOT NULL DEFAULT 'default' COMMENT '密码', - email varchar(64) NOT NULL DEFAULT 'default' COMMENT '邮箱' -) COMMENT='用户表'; -``` - -**根据已有的表创建新表** - -```sql -CREATE TABLE vip_user AS -SELECT * FROM user; -``` - -#### 删除数据表 - -```sql -DROP TABLE user; -``` - -#### 修改数据表 - -**添加列** - -```sql -ALTER TABLE user -ADD age int(3); -``` - -**删除列** - -```sql -ALTER TABLE user -DROP COLUMN age; -``` - -**修改列** - -```sql -ALTER TABLE `user` -MODIFY COLUMN age tinyint; -``` - -**添加主键** - -```sql -ALTER TABLE user -ADD PRIMARY KEY (id); -``` - -**删除主键** - -```sql -ALTER TABLE user -DROP PRIMARY KEY; -``` - -### 视图(VIEW) - -> 视图是基于 SQL 语句的结果集的可视化的表。**视图是虚拟的表,本身不存储数据,也就不能对其进行索引操作**。对视图的操作和对普通表的操作一样。 - -视图的作用: - -- 简化复杂的 SQL 操作,比如复杂的连接。 -- 只使用实际表的一部分数据。 -- 通过只给用户访问视图的权限,保证数据的安全性。 -- 更改数据格式和表示。 - -#### 创建视图 - -```sql -CREATE VIEW top_10_user_view AS -SELECT id, username -FROM user -WHERE id < 10; -``` - -#### 删除视图 - -```sql -DROP VIEW top_10_user_view; -``` - -### 索引(INDEX) - -> 通过索引可以更加快速高效地查询数据。用户无法看到索引,它们只能被用来加速查询。 - -更新一个包含索引的表需要比更新一个没有索引的表花费更多的时间,这是由于索引本身也需要更新。因此,理想的做法是仅仅在常常被搜索的列(以及表)上面创建索引。 - -唯一索引:唯一索引表明此索引的每一个索引值只对应唯一的数据记录。 - -#### 创建索引 - -```sql -CREATE INDEX user_index -ON user (id); -``` - -#### 创建唯一索引 - -```sql -CREATE UNIQUE INDEX user_index -ON user (id); -``` - -#### 删除索引 - -```sql -ALTER TABLE user -DROP INDEX user_index; -``` - -### 约束 - -> SQL 约束用于规定表中的数据规则。 - -- 如果存在违反约束的数据行为,行为会被约束终止。 -- 约束可以在创建表时规定(通过 CREATE TABLE 语句),或者在表创建之后规定(通过 ALTER TABLE 语句)。 -- 约束类型 - - `NOT NULL` - 指示某列不能存储 NULL 值。 - - `UNIQUE` - 保证某列的每行必须有唯一的值。 - - `PRIMARY KEY` - NOT NULL 和 UNIQUE 的结合。确保某列(或两个列多个列的结合)有唯一标识,有助于更容易更快速地找到表中的一个特定的记录。 - - `FOREIGN KEY` - 保证一个表中的数据匹配另一个表中的值的参照完整性。 - - `CHECK` - 保证列中的值符合指定的条件。 - - `DEFAULT` - 规定没有给列赋值时的默认值。 - -创建表时使用约束条件: - -```sql -CREATE TABLE Users ( - Id INT(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增Id', - Username VARCHAR(64) NOT NULL UNIQUE DEFAULT 'default' COMMENT '用户名', - Password VARCHAR(64) NOT NULL DEFAULT 'default' COMMENT '密码', - Email VARCHAR(64) NOT NULL DEFAULT 'default' COMMENT '邮箱地址', - Enabled TINYINT(4) DEFAULT NULL COMMENT '是否有效', - PRIMARY KEY (Id) -) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COMMENT='用户表'; -``` - ---- - -**(以下为 TCL 语句用法)** - -## 八、事务处理 - -不能回退 `SELECT` 语句,回退 `SELECT` 语句也没意义;也不能回退 `CREATE` 和 `DROP` 语句。 - -**MySQL 默认采用隐式提交策略(`autocommit`)**,每执行一条语句就把这条语句当成一个事务然后进行提交。当出现 `START TRANSACTION` 语句时,会关闭隐式提交;当 `COMMIT` 或 `ROLLBACK` 语句执行后,事务会自动关闭,重新恢复隐式提交。 - -通过 `set autocommit=0` 可以取消自动提交,直到 `set autocommit=1` 才会提交;`autocommit` 标记是针对每个连接而不是针对服务器的。 - -事务处理指令: - -- `START TRANSACTION` - 指令用于标记事务的起始点。 -- `SAVEPOINT` - 指令用于创建保留点。 -- `ROLLBACK TO` - 指令用于回滚到指定的保留点;如果没有设置保留点,则回退到 `START TRANSACTION` 语句处。 -- `COMMIT` - 提交事务。 - -事务处理示例: - -```sql --- 开始事务 -START TRANSACTION; - --- 插入操作 A -INSERT INTO `user` -VALUES (1, 'root1', 'root1', 'xxxx@163.com'); - --- 创建保留点 updateA -SAVEPOINT updateA; - --- 插入操作 B -INSERT INTO `user` -VALUES (2, 'root2', 'root2', 'xxxx@163.com'); - --- 回滚到保留点 updateA -ROLLBACK TO updateA; - --- 提交事务,只有操作 A 生效 -COMMIT; -``` - ---- - -**(以下为 DCL 语句用法)** - -## 九、权限控制 - -`GRANT` 和 `REVOKE` 可在几个层次上控制访问权限: - -- 整个服务器,使用 `GRANT ALL` 和 `REVOKE ALL`; -- 整个数据库,使用 ON database.\*; -- 特定的表,使用 ON database.table; -- 特定的列; -- 特定的存储过程。 - -新创建的账户没有任何权限。 - -账户用 `username@host` 的形式定义,`username@%` 使用的是默认主机名。 - -MySQL 的账户信息保存在 mysql 这个数据库中。 - -```sql -USE mysql; -SELECT user FROM user; -``` - -### 创建账户 - -```sql -CREATE USER myuser IDENTIFIED BY 'mypassword'; -``` - -### 修改账户名 - -```sql -UPDATE user SET user='newuser' WHERE user='myuser'; -FLUSH PRIVILEGES; -``` - -### 删除账户 - -```sql -DROP USER myuser; -``` - -### 查看权限 - -```sql -SHOW GRANTS FOR myuser; -``` - -### 授予权限 - -```sql -GRANT SELECT, INSERT ON *.* TO myuser; -``` - -### 删除权限 - -```sql -REVOKE SELECT, INSERT ON *.* FROM myuser; -``` - -### 更改密码 - -```sql -SET PASSWORD FOR myuser = 'mypass'; -``` - -## 十、存储过程和游标 - -> 存储过程可以看成是对一系列 SQL 操作的批处理,保存在数据库中。它就像我们编程语言中的函数一样,封装了我们的代码(PLSQL、T-SQL)。 - -### 存储过程优缺点 - -存储过程的优点: - -- 代码封装,保证了一定的安全性。 -- 让编程语言进行调用,提高代码复用。 -- 由于是预先编译,因此具有很高的性能。 -- 一个存储过程替代大量 T_SQL 语句 ,可以降低网络通信量,提高通信速率。 - -存储过程的缺点: - -- 由于不同数据库的存储过程语法几乎都不一样,十分难以维护(不通用)。 -- 业务逻辑放在数据库上,难以迭代。 - -### 使用存储过程 - -创建存储过程的要点: - -- 命令行中创建存储过程需要自定义分隔符,因为命令行是以 `;` 为结束符,而存储过程中也包含了分号,因此会错误把这部分分号当成是结束符,造成语法错误。 -- 包含 in、out 和 inout 三种参数。 -- 给变量赋值都需要用 select into 语句。 -- 每次只能给一个变量赋值,不支持集合的操作。 - -创建存储过程示例: - -```sql -DROP PROCEDURE IF EXISTS `proc_adder`; -DELIMITER ;; -CREATE DEFINER=`root`@`localhost` PROCEDURE `proc_adder`(IN a int, IN b int, OUT sum int) -BEGIN - DECLARE c int; - if a is null then set a = 0; - end if; - - if b is null then set b = 0; - end if; - - set sum = a + b; -END -;; -DELIMITER ; -``` - -使用存储过程示例: - -```sql -set @b=5; -call proc_adder(2,@b,@s); -select @s as sum; -``` - -### 游标 - -> 游标(CURSOR)是一个存储在 DBMS 服务器上的数据库查询,它不是一条 `SELECT` 语句,而是被该语句检索出来的结果集。在存储过程中使用游标可以对一个结果集进行移动遍历。 - -游标主要用于交互式应用,其中用户需要对数据集中的任意行进行浏览和修改。 - -使用游标的四个步骤: - -1. 声明游标,这个过程没有实际检索出数据; -2. 打开游标; -3. 取出数据; -4. 关闭游标; - -游标使用示例: - -```sql -DELIMITER $ -CREATE PROCEDURE getTotal() -BEGIN - DECLARE total INT; - -- 创建接收游标数据的变量 - DECLARE sid INT; - DECLARE sname VARCHAR(10); - -- 创建总数变量 - DECLARE sage INT; - -- 创建结束标志变量 - DECLARE done INT DEFAULT false; - -- 创建游标 - DECLARE cur CURSOR FOR SELECT id,name,age from cursor_table where age>30; - -- 指定游标循环结束时的返回值 - DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = true; - SET total = 0; - OPEN cur; - FETCH cur INTO sid, sname, sage; - WHILE(NOT done) - DO - SET total = total + 1; - FETCH cur INTO sid, sname, sage; - END WHILE; - - CLOSE cur; - SELECT total; -END $ -DELIMITER ; - --- 调用存储过程 -call getTotal(); -``` - -### 触发器 - -> 触发器可以视为一种特殊的存储过程。 -> -> 触发器是一种与表操作有关的数据库对象,当触发器所在表上出现指定事件时,将调用该对象,即表的操作事件触发表上的触发器的执行。 - -#### 触发器特性 - -可以使用触发器来进行审计跟踪,把修改记录到另外一张表中。 - -MySQL 不允许在触发器中使用 `CALL` 语句 ,也就是不能调用存储过程。 - -**`BEGIN` 和 `END`** - -当触发器的触发条件满足时,将会执行 `BEGIN` 和 `END` 之间的触发器执行动作。 - -> 🔔 注意:在 MySQL 中,分号 `;` 是语句结束的标识符,遇到分号表示该段语句已经结束,MySQL 可以开始执行了。因此,解释器遇到触发器执行动作中的分号后就开始执行,然后会报错,因为没有找到和 BEGIN 匹配的 END。 -> -> 这时就会用到 `DELIMITER` 命令(`DELIMITER` 是定界符,分隔符的意思)。它是一条命令,不需要语句结束标识,语法为:`DELIMITER new_delemiter`。`new_delemiter` 可以设为 1 个或多个长度的符号,默认的是分号 `;`,我们可以把它修改为其他符号,如 `$` - `DELIMITER $` 。在这之后的语句,以分号结束,解释器不会有什么反应,只有遇到了 `$`,才认为是语句结束。注意,使用完之后,我们还应该记得把它给修改回来。 - -**`NEW` 和 `OLD`** - -- MySQL 中定义了 `NEW` 和 `OLD` 关键字,用来表示触发器的所在表中,触发了触发器的那一行数据。 -- 在 `INSERT` 型触发器中,`NEW` 用来表示将要(`BEFORE`)或已经(`AFTER`)插入的新数据; -- 在 `UPDATE` 型触发器中,`OLD` 用来表示将要或已经被修改的原数据,`NEW` 用来表示将要或已经修改为的新数据; -- 在 `DELETE` 型触发器中,`OLD` 用来表示将要或已经被删除的原数据; -- 使用方法: `NEW.columnName` (columnName 为相应数据表某一列名) - -#### 触发器指令 - -> 提示:为了理解触发器的要点,有必要先了解一下创建触发器的指令。 - -`CREATE TRIGGER` 指令用于创建触发器。 - -语法: - -```sql -CREATE TRIGGER trigger_name -trigger_time -trigger_event -ON table_name -FOR EACH ROW -BEGIN - trigger_statements -END; -``` - -说明: - -- trigger_name:触发器名 -- trigger_time: 触发器的触发时机。取值为 `BEFORE` 或 `AFTER`。 -- trigger_event: 触发器的监听事件。取值为 `INSERT`、`UPDATE` 或 `DELETE`。 -- table_name: 触发器的监听目标。指定在哪张表上建立触发器。 -- FOR EACH ROW: 行级监视,Mysql 固定写法,其他 DBMS 不同。 -- trigger_statements: 触发器执行动作。是一条或多条 SQL 语句的列表,列表内的每条语句都必须用分号 `;` 来结尾。 - -创建触发器示例: - -```sql -DELIMITER $ -CREATE TRIGGER `trigger_insert_user` -AFTER INSERT ON `user` -FOR EACH ROW -BEGIN - INSERT INTO `user_history`(user_id, operate_type, operate_time) - VALUES (NEW.id, 'add a user', now()); -END $ -DELIMITER ; -``` - -查看触发器示例: - -```sql -SHOW TRIGGERS; -``` - -删除触发器示例: - -```sql -DROP TRIGGER IF EXISTS trigger_insert_user; -``` - -## 参考资料 - -- [《SQL 必知必会》](https://item.jd.com/11232698.html) -- [『浅入深出』MySQL 中事务的实现](https://draveness.me/mysql-transaction) -- [MySQL 的学习--触发器](https://www.cnblogs.com/CraryPrimitiveMan/p/4206942.html) -- [维基百科词条 - SQL](https://zh.wikipedia.org/wiki/SQL) -- [https://www.sitesbay.com/sql/index](https://www.sitesbay.com/sql/index) -- [SQL Subqueries](https://www.w3resource.com/sql/subqueries/understanding-sql-subqueries.php) -- [Quick breakdown of the types of joins](https://stackoverflow.com/questions/6294778/mysql-quick-breakdown-of-the-types-of-joins) -- [SQL UNION](https://www.w3resource.com/sql/sql-union.php) -- [SQL database security](https://www.w3resource.com/sql/database-security/create-users.php) -- [Mysql 中的存储过程](https://www.cnblogs.com/chenpi/p/5136483.html) diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/01.\347\273\274\345\220\210/03.SQL\350\257\255\346\263\225\351\253\230\347\272\247\347\211\271\346\200\247.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/01.\347\273\274\345\220\210/03.SQL\350\257\255\346\263\225\351\253\230\347\272\247\347\211\271\346\200\247.md" new file mode 100644 index 00000000..2131af12 --- /dev/null +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/01.\347\273\274\345\220\210/03.SQL\350\257\255\346\263\225\351\253\230\347\272\247\347\211\271\346\200\247.md" @@ -0,0 +1,610 @@ +--- +title: SQL 语法高级特性 +date: 2022-04-27 22:13:55 +categories: + - 数据库 + - 关系型数据库 + - 综合 +tags: + - 数据库 + - 关系型数据库 + - SQL +permalink: /pages/1ae1ca/ +--- + +# SQL 语法高级特性 + +> 本文针对关系型数据库的基本语法。限于篇幅,本文侧重说明用法,不会展开讲解特性、原理。 +> +> 本文语法主要针对 Mysql,但大部分的语法对其他关系型数据库也适用。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200115160512.png) + +## 连接和组合 + +### 连接(JOIN) + +> 连接用于连接多个表,使用 `JOIN` 关键字,并且条件语句使用 `ON` 而不是 `WHERE`。 + +如果一个 `JOIN` 至少有一个公共字段并且它们之间存在关系,则该 `JOIN` 可以在两个或多个表上工作。 + +`JOIN` 保持基表(结构和数据)不变。**连接可以替换子查询,并且比子查询的效率一般会更快**。 + +`JOIN` 有两种连接类型:内连接和外连接。 + +
+ sql-join +
+ +#### 内连接(INNER JOIN) + +内连接又称等值连接,**使用 `INNER JOIN` 关键字**。在没有条件语句的情况下**返回笛卡尔积**。 + +```sql +SELECT vend_name, prod_name, prod_price +FROM vendors INNER JOIN products +ON vendors.vend_id = products.vend_id; +``` + +##### 自连接(`=`) + +自连接可以看成内连接的一种,只是**连接的表是自身**而已。**自然连接是把同名列通过 `=` 连接起来**的,同名列可以有多个。 + +```sql +SELECT c1.cust_id, c1.cust_name, c1.cust_contact +FROM customers c1, customers c2 +WHERE c1.cust_name = c2.cust_name +AND c2.cust_contact = 'Jim Jones'; +``` + +##### 自然连接(NATURAL JOIN) + +内连接提供连接的列,而自然连接**自动连接所有同名列**。自然连接使用 `NATURAL JOIN` 关键字。 + +```sql +SELECT * +FROM Products +NATURAL JOIN Customers; +``` + +#### 外连接(OUTER JOIN) + +外连接返回一个表中的所有行,并且仅返回来自此表中满足连接条件的那些行,即两个表中的列是相等的。外连接分为左外连接、右外连接、全外连接(Mysql 不支持)。 + +##### 左连接(LEFT JOIN) + +左外连接就是保留左表没有关联的行。 + +```sql +SELECT customers.cust_id, orders.order_num +FROM customers LEFT JOIN orders +ON customers.cust_id = orders.cust_id; +``` + +##### 右连接(RIGHT JOIN) + +右外连接就是保留右表没有关联的行。 + +```sql +SELECT customers.cust_id, orders.order_num +FROM customers RIGHT JOIN orders +ON customers.cust_id = orders.cust_id; +``` + +### 组合(UNION) + +> `UNION` 运算符**将两个或更多查询的结果组合起来,并生成一个结果集**,其中包含来自 `UNION` 中参与查询的提取行。 + +`UNION` 基本规则: + +- 所有查询的列数和列顺序必须相同。 +- 每个查询中涉及表的列的数据类型必须相同或兼容。 +- 通常返回的列名取自第一个查询。 + +默认会去除相同行,如果需要保留相同行,使用 `UNION ALL`。 + +只能包含一个 `ORDER BY` 子句,并且必须位于语句的最后。 + +应用场景: + +- 在一个查询中从不同的表返回结构数据。 +- 对一个表执行多个查询,按一个查询返回数据。 + +组合查询示例: + +```sql +SELECT cust_name, cust_contact, cust_email +FROM customers +WHERE cust_state IN ('IL', 'IN', 'MI') +UNION +SELECT cust_name, cust_contact, cust_email +FROM customers +WHERE cust_name = 'Fun4All'; +``` + +### JOIN vs UNION + +- `JOIN` 中连接表的列可能不同,但在 `UNION` 中,所有查询的列数和列顺序必须相同。 +- `UNION` 将查询之后的行放在一起(垂直放置),但 `JOIN` 将查询之后的列放在一起(水平放置),即它构成一个笛卡尔积。 + +## 函数 + +> 🔔 注意:不同数据库的函数往往各不相同,因此不可移植。本节主要以 Mysql 的函数为例。 + +### 文本处理 + +| 函数 | 说明 | +| :------------------: | :--------------------: | +| `LEFT()`、`RIGHT()` | 左边或者右边的字符 | +| `LOWER()`、`UPPER()` | 转换为小写或者大写 | +| `LTRIM()`、`RTIM()` | 去除左边或者右边的空格 | +| `LENGTH()` | 长度 | +| `SOUNDEX()` | 转换为语音值 | + +其中, **SOUNDEX()** 可以将一个字符串转换为描述其语音表示的字母数字模式。 + +```sql +SELECT * +FROM mytable +WHERE SOUNDEX(col1) = SOUNDEX('apple') +``` + +### 日期和时间处理 + +- 日期格式:`YYYY-MM-DD` +- 时间格式:`HH:MM:SS` + +| 函 数 | 说 明 | +| :-------------: | :----------------------------: | +| `AddDate()` | 增加一个日期(天、周等) | +| `AddTime()` | 增加一个时间(时、分等) | +| `CurDate()` | 返回当前日期 | +| `CurTime()` | 返回当前时间 | +| `Date()` | 返回日期时间的日期部分 | +| `DateDiff()` | 计算两个日期之差 | +| `Date_Add()` | 高度灵活的日期运算函数 | +| `Date_Format()` | 返回一个格式化的日期或时间串 | +| `Day()` | 返回一个日期的天数部分 | +| `DayOfWeek()` | 对于一个日期,返回对应的星期几 | +| `Hour()` | 返回一个时间的小时部分 | +| `Minute()` | 返回一个时间的分钟部分 | +| `Month()` | 返回一个日期的月份部分 | +| `Now()` | 返回当前日期和时间 | +| `Second()` | 返回一个时间的秒部分 | +| `Time()` | 返回一个日期时间的时间部分 | +| `Year()` | 返回一个日期的年份部分 | + +```sql +mysql> SELECT NOW(); +``` + +``` +2018-4-14 20:25:11 +``` + +### 数值处理 + +| 函数 | 说明 | +| :----: | :----: | +| SIN() | 正弦 | +| COS() | 余弦 | +| TAN() | 正切 | +| ABS() | 绝对值 | +| SQRT() | 平方根 | +| MOD() | 余数 | +| EXP() | 指数 | +| PI() | 圆周率 | +| RAND() | 随机数 | + +### 汇总 + +| 函 数 | 说 明 | +| :-------: | :--------------: | +| `AVG()` | 返回某列的平均值 | +| `COUNT()` | 返回某列的行数 | +| `MAX()` | 返回某列的最大值 | +| `MIN()` | 返回某列的最小值 | +| `SUM()` | 返回某列值之和 | + +`AVG()` 会忽略 NULL 行。 + +使用 DISTINCT 可以让汇总函数值汇总不同的值。 + +```sql +SELECT AVG(DISTINCT col1) AS avg_col +FROM mytable +``` + +## 分组 + +### GROUP BY + +> `GROUP BY` 子句将记录分组到汇总行中,`GROUP BY` 为每个组返回一个记录。 + +`GROUP BY` 可以按一列或多列进行分组。 + +`GROUP BY` 通常还涉及聚合函数:COUNT,MAX,SUM,AVG 等。 + +`GROUP BY` 按分组字段进行排序后,`ORDER BY` 可以以汇总字段来进行排序。 + +分组示例: + +```sql +SELECT cust_name, COUNT(cust_address) AS addr_num +FROM Customers GROUP BY cust_name; +``` + +分组后排序示例: + +```sql +SELECT cust_name, COUNT(cust_address) AS addr_num +FROM Customers GROUP BY cust_name +ORDER BY cust_name DESC; +``` + +### HAVING + +> `HAVING` 用于对汇总的 `GROUP BY` 结果进行过滤。`HAVING` 要求存在一个 `GROUP BY` 子句。 + +`WHERE` 和 `HAVING` 可以在相同的查询中。 + +`HAVING` vs `WHERE`: + +- `WHERE` 和 `HAVING` 都是用于过滤。 +- `HAVING` 适用于汇总的组记录;而 `WHERE` 适用于单个记录。 + +使用 `WHERE` 和 `HAVING` 过滤数据示例: + +```sql +SELECT cust_name, COUNT(*) AS num +FROM Customers +WHERE cust_email IS NOT NULL +GROUP BY cust_name +HAVING COUNT(*) >= 1; +``` + +--- + +**(以下为 DDL 语句用法)** + +## 事务 + +不能回退 `SELECT` 语句,回退 `SELECT` 语句也没意义;也不能回退 `CREATE` 和 `DROP` 语句。 + +**MySQL 默认采用隐式提交策略(`autocommit`)**,每执行一条语句就把这条语句当成一个事务然后进行提交。当出现 `START TRANSACTION` 语句时,会关闭隐式提交;当 `COMMIT` 或 `ROLLBACK` 语句执行后,事务会自动关闭,重新恢复隐式提交。 + +通过 `set autocommit=0` 可以取消自动提交,直到 `set autocommit=1` 才会提交;`autocommit` 标记是针对每个连接而不是针对服务器的。 + +事务处理指令: + +- `START TRANSACTION` - 指令用于标记事务的起始点。 +- `SAVEPOINT` - 指令用于创建保留点。 +- `ROLLBACK TO` - 指令用于回滚到指定的保留点;如果没有设置保留点,则回退到 `START TRANSACTION` 语句处。 +- `COMMIT` - 提交事务。 +- `RELEASE SAVEPOINT`:删除某个保存点。 +- `SET TRANSACTION`:设置事务的隔离级别。 + +事务处理示例: + +```sql +-- 开始事务 +START TRANSACTION; + +-- 插入操作 A +INSERT INTO `user` +VALUES (1, 'root1', 'root1', 'xxxx@163.com'); + +-- 创建保留点 updateA +SAVEPOINT updateA; + +-- 插入操作 B +INSERT INTO `user` +VALUES (2, 'root2', 'root2', 'xxxx@163.com'); + +-- 回滚到保留点 updateA +ROLLBACK TO updateA; + +-- 提交事务,只有操作 A 生效 +COMMIT; +``` + +### ACID + +### 事务隔离级别 + +--- + +**(以下为 DCL 语句用法)** + +## 权限控制 + +`GRANT` 和 `REVOKE` 可在几个层次上控制访问权限: + +- 整个服务器,使用 `GRANT ALL` 和 `REVOKE ALL`; +- 整个数据库,使用 ON database.\*; +- 特定的表,使用 ON database.table; +- 特定的列; +- 特定的存储过程。 + +新创建的账户没有任何权限。 + +账户用 `username@host` 的形式定义,`username@%` 使用的是默认主机名。 + +MySQL 的账户信息保存在 mysql 这个数据库中。 + +```sql +USE mysql; +SELECT user FROM user; +``` + +### 创建账户 + +```sql +CREATE USER myuser IDENTIFIED BY 'mypassword'; +``` + +### 修改账户名 + +```sql +UPDATE user SET user='newuser' WHERE user='myuser'; +FLUSH PRIVILEGES; +``` + +### 删除账户 + +```sql +DROP USER myuser; +``` + +### 查看权限 + +```sql +SHOW GRANTS FOR myuser; +``` + +### 授予权限 + +```sql +GRANT SELECT, INSERT ON *.* TO myuser; +``` + +### 删除权限 + +```sql +REVOKE SELECT, INSERT ON *.* FROM myuser; +``` + +### 更改密码 + +```sql +SET PASSWORD FOR myuser = 'mypass'; +``` + +## 存储过程 + +存储过程的英文是 Stored Procedure。它可以视为一组 SQL 语句的批处理。一旦存储过程被创建出来,使用它就像使用函数一样简单,我们直接通过调用存储过程名即可。 + +定义存储过程的语法格式: + +```sql +CREATE PROCEDURE 存储过程名称 ([参数列表]) +BEGIN + 需要执行的语句 +END +``` + +存储过程定义语句类型: + +- `CREATE PROCEDURE` 用于创建存储过程 +- `DROP PROCEDURE` 用于删除存储过程 +- `ALTER PROCEDURE` 用于修改存储过程 + +### 使用存储过程 + +创建存储过程的要点: + +- `DELIMITER` 用于定义语句的结束符 +- 存储过程的 3 种参数类型: + - `IN`:存储过程的入参 + - `OUT`:存储过程的出参 + - `INPUT`:既是存储过程的入参,也是存储过程的出参 +- 流控制语句: + - `BEGIN…END`:`BEGIN…END` 中间包含了多个语句,每个语句都以(`;`)号为结束符。 + - `DECLARE`:`DECLARE` 用来声明变量,使用的位置在于 `BEGIN…END` 语句中间,而且需要在其他语句使用之前进行变量的声明。 + - `SET`:赋值语句,用于对变量进行赋值。 + - `SELECT…INTO`:把从数据表中查询的结果存放到变量中,也就是为变量赋值。每次只能给一个变量赋值,不支持集合的操作。 + - `IF…THEN…ENDIF`:条件判断语句,可以在 `IF…THEN…ENDIF` 中使用 `ELSE` 和 `ELSEIF` 来进行条件判断。 + - `CASE`:`CASE` 语句用于多条件的分支判断。 + +创建存储过程示例: + +```sql +DROP PROCEDURE IF EXISTS `proc_adder`; +DELIMITER ;; +CREATE DEFINER=`root`@`localhost` PROCEDURE `proc_adder`(IN a int, IN b int, OUT sum int) +BEGIN + DECLARE c int; + if a is null then set a = 0; + end if; + + if b is null then set b = 0; + end if; + + set sum = a + b; +END +;; +DELIMITER ; +``` + +使用存储过程示例: + +```sql +set @b=5; +call proc_adder(2,@b,@s); +select @s as sum; +``` + +### 存储过程的利弊 + +存储过程的优点: + +- **执行效率高**:一次编译多次使用。 +- **安全性强**:在设定存储过程的时候可以设置对用户的使用权限,这样就和视图一样具有较强的安全性。 +- **可复用**:将代码封装,可以提高代码复用。 +- **性能好** + - 由于是预先编译,因此具有很高的性能。 + - 一个存储过程替代大量 T_SQL 语句 ,可以降低网络通信量,提高通信速率。 + +存储过程的缺点: + +- **可移植性差**:存储过程不能跨数据库移植。由于不同数据库的存储过程语法几乎都不一样,十分难以维护(不通用)。 +- **调试困难**:只有少数 DBMS 支持存储过程的调试。对于复杂的存储过程来说,开发和维护都不容易。 +- **版本管理困难**:比如数据表索引发生变化了,可能会导致存储过程失效。我们在开发软件的时候往往需要进行版本管理,但是存储过程本身没有版本控制,版本迭代更新的时候很麻烦。 +- **不适合高并发的场景**:高并发的场景需要减少数据库的压力,有时数据库会采用分库分表的方式,而且对可扩展性要求很高,在这种情况下,存储过程会变得难以维护,增加数据库的压力,显然就不适用了。 + +> _综上,存储过程的优缺点都非常突出,是否使用一定要慎重,需要根据具体应用场景来权衡_。 + +### 触发器 + +> 触发器可以视为一种特殊的存储过程。 +> +> 触发器是一种与表操作有关的数据库对象,当触发器所在表上出现指定事件时,将调用该对象,即表的操作事件触发表上的触发器的执行。 + +#### 触发器特性 + +可以使用触发器来进行审计跟踪,把修改记录到另外一张表中。 + +MySQL 不允许在触发器中使用 `CALL` 语句 ,也就是不能调用存储过程。 + +**`BEGIN` 和 `END`** + +当触发器的触发条件满足时,将会执行 `BEGIN` 和 `END` 之间的触发器执行动作。 + +> 🔔 注意:在 MySQL 中,分号 `;` 是语句结束的标识符,遇到分号表示该段语句已经结束,MySQL 可以开始执行了。因此,解释器遇到触发器执行动作中的分号后就开始执行,然后会报错,因为没有找到和 BEGIN 匹配的 END。 +> +> 这时就会用到 `DELIMITER` 命令(`DELIMITER` 是定界符,分隔符的意思)。它是一条命令,不需要语句结束标识,语法为:`DELIMITER new_delemiter`。`new_delemiter` 可以设为 1 个或多个长度的符号,默认的是分号 `;`,我们可以把它修改为其他符号,如 `$` - `DELIMITER $` 。在这之后的语句,以分号结束,解释器不会有什么反应,只有遇到了 `$`,才认为是语句结束。注意,使用完之后,我们还应该记得把它给修改回来。 + +**`NEW` 和 `OLD`** + +- MySQL 中定义了 `NEW` 和 `OLD` 关键字,用来表示触发器的所在表中,触发了触发器的那一行数据。 +- 在 `INSERT` 型触发器中,`NEW` 用来表示将要(`BEFORE`)或已经(`AFTER`)插入的新数据; +- 在 `UPDATE` 型触发器中,`OLD` 用来表示将要或已经被修改的原数据,`NEW` 用来表示将要或已经修改为的新数据; +- 在 `DELETE` 型触发器中,`OLD` 用来表示将要或已经被删除的原数据; +- 使用方法: `NEW.columnName` (columnName 为相应数据表某一列名) + +#### 触发器指令 + +> 提示:为了理解触发器的要点,有必要先了解一下创建触发器的指令。 + +`CREATE TRIGGER` 指令用于创建触发器。 + +语法: + +```sql +CREATE TRIGGER trigger_name +trigger_time +trigger_event +ON table_name +FOR EACH ROW +BEGIN + trigger_statements +END; +``` + +说明: + +- trigger_name:触发器名 +- trigger_time: 触发器的触发时机。取值为 `BEFORE` 或 `AFTER`。 +- trigger_event: 触发器的监听事件。取值为 `INSERT`、`UPDATE` 或 `DELETE`。 +- table_name: 触发器的监听目标。指定在哪张表上建立触发器。 +- FOR EACH ROW: 行级监视,Mysql 固定写法,其他 DBMS 不同。 +- trigger_statements: 触发器执行动作。是一条或多条 SQL 语句的列表,列表内的每条语句都必须用分号 `;` 来结尾。 + +创建触发器示例: + +```sql +DELIMITER $ +CREATE TRIGGER `trigger_insert_user` +AFTER INSERT ON `user` +FOR EACH ROW +BEGIN + INSERT INTO `user_history`(user_id, operate_type, operate_time) + VALUES (NEW.id, 'add a user', now()); +END $ +DELIMITER ; +``` + +查看触发器示例: + +```sql +SHOW TRIGGERS; +``` + +删除触发器示例: + +```sql +DROP TRIGGER IF EXISTS trigger_insert_user; +``` + +## 游标 + +> 游标(CURSOR)是一个存储在 DBMS 服务器上的数据库查询,它不是一条 `SELECT` 语句,而是被该语句检索出来的结果集。在存储过程中使用游标可以对一个结果集进行移动遍历。 + +游标主要用于交互式应用,其中用户需要对数据集中的任意行进行浏览和修改。 + +使用游标的步骤: + +1. **定义游标**:通过 `DECLARE cursor_name CURSOR FOR <语句>` 定义游标。这个过程没有实际检索出数据。 +2. **打开游标**:通过 `OPEN cursor_name` 打开游标。 +3. **取出数据**:通过 `FETCH cursor_name INTO var_name ...` 获取数据。 +4. **关闭游标**:通过 `CLOSE cursor_name` 关闭游标。 +5. **释放游标**:通过 `DEALLOCATE PREPARE` 释放游标。 + +游标使用示例: + +```sql +DELIMITER $ +CREATE PROCEDURE getTotal() +BEGIN + DECLARE total INT; + -- 创建接收游标数据的变量 + DECLARE sid INT; + DECLARE sname VARCHAR(10); + -- 创建总数变量 + DECLARE sage INT; + -- 创建结束标志变量 + DECLARE done INT DEFAULT false; + -- 创建游标 + DECLARE cur CURSOR FOR SELECT id,name,age from cursor_table where age>30; + -- 指定游标循环结束时的返回值 + DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = true; + SET total = 0; + OPEN cur; + FETCH cur INTO sid, sname, sage; + WHILE(NOT done) + DO + SET total = total + 1; + FETCH cur INTO sid, sname, sage; + END WHILE; + + CLOSE cur; + SELECT total; +END $ +DELIMITER ; + +-- 调用存储过程 +call getTotal(); +``` + +## 参考资料 + +- [《SQL 必知必会》](https://book.douban.com/subject/35167240/) +- [『浅入深出』MySQL 中事务的实现](https://draveness.me/mysql-transaction) +- [MySQL 的学习--触发器](https://www.cnblogs.com/CraryPrimitiveMan/p/4206942.html) +- [维基百科词条 - SQL](https://zh.wikipedia.org/wiki/SQL) +- [https://www.sitesbay.com/sql/index](https://www.sitesbay.com/sql/index) +- [SQL Subqueries](https://www.w3resource.com/sql/subqueries/understanding-sql-subqueries.php) +- [Quick breakdown of the types of joins](https://stackoverflow.com/questions/6294778/mysql-quick-breakdown-of-the-types-of-joins) +- [SQL UNION](https://www.w3resource.com/sql/sql-union.php) +- [SQL database security](https://www.w3resource.com/sql/database-security/create-users.php) +- [Mysql 中的存储过程](https://www.cnblogs.com/chenpi/p/5136483.html) \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/01.\347\273\274\345\220\210/03.\346\211\251\345\261\225SQL.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/01.\347\273\274\345\220\210/03.\346\211\251\345\261\225SQL.md" index 06745861..4b80d891 100644 --- "a/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/01.\347\273\274\345\220\210/03.\346\211\251\345\261\225SQL.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/01.\347\273\274\345\220\210/03.\346\211\251\345\261\225SQL.md" @@ -14,8 +14,6 @@ permalink: /pages/55e9a7/ # 扩展 SQL -> 不同于 [SQL Cheat Sheet](02.SqlCheatSheet.md) 中的一般语法,本文主要整理收集一些高级但是很有用的 SQL - ## 数据库 ## 表 @@ -57,6 +55,31 @@ where id <= ? order by id limit 1000; ``` +### 修改表的编码格式 + +utf8mb4 编码是 utf8 编码的超集,兼容 utf8,并且能存储 4 字节的表情字符。如果表的编码指定为 utf8,在保存 emoji 字段时会报错。 + +```sql +ALTER TABLE CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; +``` + +## 其他 + +### 显示哪些线程正在运行 + +```sql +mysql> show processlist; ++----+-----------------+-----------------+------+---------+-------+------------------------+------------------+ +| Id | User | Host | db | Command | Time | State | Info | ++----+-----------------+-----------------+------+---------+-------+------------------------+------------------+ +| 5 | event_scheduler | localhost | NULL | Daemon | 40230 | Waiting on empty queue | NULL | +| 10 | root | localhost:10120 | NULL | Query | 0 | init | show processlist | ++----+-----------------+-----------------+------+---------+-------+------------------------+------------------+ +2 rows in set (0.00 sec) +``` + +Mysql 连接完成后,如果你没有后续的动作,这个连接就处于空闲状态,你可以在 `show processlist` 命令中看到它。其中的 Command 列显示为“Sleep”的这一行,就表示现在系统里面有一个空闲连接。客户端如果太长时间没动静,连接器就会自动将它断开。这个时间是由参数 wait_timeout 控制的,默认值是 8 小时。 + ## 参考资料 -- [《SQL 必知必会》](https://item.jd.com/11232698.html) +- [《SQL 必知必会》](https://book.douban.com/subject/35167240/) \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/01.\347\273\274\345\220\210/99.SqlCheatSheet.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/01.\347\273\274\345\220\210/99.SqlCheatSheet.md" new file mode 100644 index 00000000..d152f9df --- /dev/null +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/01.\347\273\274\345\220\210/99.SqlCheatSheet.md" @@ -0,0 +1,192 @@ +--- +title: SQL Cheat Sheet +date: 2022-07-16 14:17:08 +categories: + - 数据库 + - 关系型数据库 + - 综合 +tags: + - 数据库 + - 关系型数据库 + - SQL +permalink: /pages/e438a7/ +--- + +# SQL Cheat Sheet + +## 查找数据的查询 + +### **SELECT**: 用于从数据库中选择数据 + +- `SELECT` \* `FROM` table_name; + +### **DISTINCT**: 用于过滤掉重复的值并返回指定列的行 + +- `SELECT DISTINCT` column_name; + +### **WHERE**: 用于过滤记录/行 + +- `SELECT` column1, column2 `FROM` table_name `WHERE` condition; +- `SELECT` \* `FROM` table_name `WHERE` condition1 `AND` condition2; +- `SELECT` \* `FROM` table_name `WHERE` condition1 `OR` condition2; +- `SELECT` \* `FROM` table_name `WHERE NOT` condition; +- `SELECT` \* `FROM` table_name `WHERE` condition1 `AND` (condition2 `OR` condition3); +- `SELECT` \* `FROM` table_name `WHERE EXISTS` (`SELECT` column_name `FROM` table_name `WHERE` condition); + +### **ORDER BY**: 用于结果集的排序,升序(ASC)或者降序(DESC) + +- `SELECT` \* `FROM` table_name `ORDER BY` column; +- `SELECT` \* `FROM` table_name `ORDER BY` column `DESC`; +- `SELECT` \* `FROM` table_name `ORDER BY` column1 `ASC`, column2 `DESC`; + +### **SELECT TOP**: 用于指定从表顶部返回的记录数 + +- `SELECT TOP` number columns_names `FROM` table_name `WHERE` condition; +- `SELECT TOP` percent columns_names `FROM` table_name `WHERE` condition; +- 并非所有数据库系统都支持`SELECT TOP`。 MySQL 中是`LIMIT`子句 +- `SELECT` column_names `FROM` table_name `LIMIT` offset, count; + +### **LIKE**: 用于搜索列中的特定模式,WHERE 子句中使用的运算符 + +- % (percent sign) 是一个表示零个,一个或多个字符的通配符 +- \_ (underscore) 是一个表示单个字符通配符 +- `SELECT` column_names `FROM` table_name `WHERE` column_name `LIKE` pattern; +- `LIKE` ‘a%’ (查找任何以“a”开头的值) +- `LIKE` ‘%a’ (查找任何以“a”结尾的值) +- `LIKE` ‘%or%’ (查找任何包含“or”的值) +- `LIKE` ‘\_r%’ (查找任何第二位是“r”的值) +- `LIKE` ‘a*%*%’ (查找任何以“a”开头且长度至少为 3 的值) +- `LIKE` ‘[a-c]%’(查找任何以“a”或“b”或“c”开头的值) + +### **IN**: 用于在 WHERE 子句中指定多个值的运算符 + +- 本质上,IN 运算符是多个 OR 条件的简写 +- `SELECT` column_names `FROM` table_name `WHERE` column_name `IN` (value1, value2, …); +- `SELECT` column_names `FROM` table_name `WHERE` column_name `IN` (`SELECT STATEMENT`); + +### **BETWEEN**: 用于过滤给定范围的值的运算符 + +- `SELECT` column_names `FROM` table_name `WHERE` column_name `BETWEEN` value1 `AND` value2; +- `SELECT` \* `FROM` Products `WHERE` (column_name `BETWEEN` value1 `AND` value2) `AND NOT` column_name2 `IN` (value3, value4); +- `SELECT` \* `FROM` Products `WHERE` column_name `BETWEEN` #01/07/1999# AND #03/12/1999#; + +### **NULL**: 代表一个字段没有值 + +- `SELECT` \* `FROM` table_name `WHERE` column_name `IS NULL`; +- `SELECT` \* `FROM` table_name `WHERE` column_name `IS NOT NULL`; + +### **AS**: 用于给表或者列分配别名 + +- `SELECT` column_name `AS` alias_name `FROM` table_name; +- `SELECT` column_name `FROM` table_name `AS` alias_name; +- `SELECT` column_name `AS` alias_name1, column_name2 `AS` alias_name2; +- `SELECT` column_name1, column_name2 + ‘, ‘ + column_name3 `AS` alias_name; + +### **UNION**: 用于组合两个或者多个 SELECT 语句的结果集的运算符 + +- 每个 SELECT 语句必须拥有相同的列数 +- 列必须拥有相似的数据类型 +- 每个 SELECT 语句中的列也必须具有相同的顺序 +- `SELECT` columns_names `FROM` table1 `UNION SELECT` column_name `FROM` table2; +- `UNION` 仅允许选择不同的值, `UNION ALL` 允许重复 + +### **ANY|ALL**: 用于检查 WHERE 或 HAVING 子句中使用的子查询条件的运算符 + +- `ANY` 如果任何子查询值满足条件,则返回 true。 +- `ALL` 如果所有子查询值都满足条件,则返回 true。 +- `SELECT` columns_names `FROM` table1 `WHERE` column_name operator (`ANY`|`ALL`) (`SELECT` column_name `FROM` table_name `WHERE` condition); + +### **GROUP BY**: 通常与聚合函数(COUNT,MAX,MIN,SUM,AVG)一起使用,用于将结果集分组为一列或多列 + +- `SELECT` column_name1, COUNT(column_name2) `FROM` table_name `WHERE` condition `GROUP BY` column_name1 `ORDER BY` COUNT(column_name2) DESC; + +### **HAVING**: HAVING 子句指定 SELECT 语句应仅返回聚合值满足指定条件的行。它被添加到 SQL 语言中,因为 WHERE 关键字不能与聚合函数一起使用。 + +- `SELECT` `COUNT`(column_name1), column_name2 `FROM` table `GROUP BY` column_name2 `HAVING` `COUNT(`column_name1`)` > 5; + +## 修改数据的查询 + +### **INSERT INTO**: 用于在表中插入新记录/行 + +- `INSERT INTO` table_name (column1, column2) `VALUES` (value1, value2); +- `INSERT INTO` table_name `VALUES` (value1, value2 …); + +### **UPDATE**: 用于修改表中的现有记录/行 + +- `UPDATE` table_name `SET` column1 = value1, column2 = value2 `WHERE` condition; +- `UPDATE` table_name `SET` column_name = value; + +### **DELETE**: 用于删除表中的现有记录/行 + +- `DELETE FROM` table_name `WHERE` condition; +- `DELETE` \* `FROM` table_name; + +## 聚合查询 + +### **COUNT**: 返回出现次数 + +- `SELECT COUNT (DISTINCT` column_name`)`; + +### **MIN() and MAX()**: 返回所选列的最小/最大值 + +- `SELECT MIN (`column_names`) FROM` table_name `WHERE` condition; +- `SELECT MAX (`column_names`) FROM` table_name `WHERE` condition; + +### **AVG()**: 返回数字列的平均值 + +- `SELECT AVG (`column_name`) FROM` table_name `WHERE` condition; + +### **SUM()**: 返回数值列的总和 + +- `SELECT SUM (`column_name`) FROM` table_name `WHERE` condition; + +## 连接查询 + +### **INNER JOIN**: 内连接,返回在两张表中具有匹配值的记录 + +- `SELECT` column_names `FROM` table1 `INNER JOIN` table2 `ON` table1.column_name=table2.column_name; +- `SELECT` table1.column_name1, table2.column_name2, table3.column_name3 `FROM` ((table1 `INNER JOIN` table2 `ON` relationship) `INNER JOIN` table3 `ON` relationship); + +### **LEFT (OUTER) JOIN**: 左外连接,返回左表(table1)中的所有记录,以及右表中的匹配记录(table2) + +- `SELECT` column_names `FROM` table1 `LEFT JOIN` table2 `ON` table1.column_name=table2.column_name; + +### **RIGHT (OUTER) JOIN**: 右外连接,返回右表(table2)中的所有记录,以及左表(table1)中匹配的记录 + +- `SELECT` column_names `FROM` table1 `RIGHT JOIN` table2 `ON` table1.column_name=table2.column_name; + +### **FULL (OUTER) JOIN**: 全外连接,全连接是左右外连接的并集. 连接表包含被连接的表的所有记录, 如果缺少匹配的记录, 以 NULL 填充。 + +- `SELECT` column_names `FROM` table1 `FULL OUTER JOIN` table2 `ON` table1.column_name=table2.column_name; + +### **Self JOIN**: 自连接,表自身连接 + +- `SELECT` column_names `FROM` table1 T1, table1 T2 `WHERE` condition; + +## 视图查询 + +### **CREATE**: 创建视图 + +- `CREATE VIEW` view_name `AS SELECT` column1, column2 `FROM` table_name `WHERE` condition; + +### **SELECT**: 检索视图 + +- `SELECT` \* `FROM` view_name; + +### **DROP**: 删除视图 + +- `DROP VIEW` view_name; + +## 修改表的查询 + +### **ADD**: 添加字段 + +- `ALTER TABLE` table_name `ADD` column_name column_definition; + +### **MODIFY**: 修改字段数据类型 + +- `ALTER TABLE` table_name `MODIFY` column_name column_type; + +### **DROP**: 删除字段 + +- `ALTER TABLE` table_name `DROP COLUMN` column_name; \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/01.\347\273\274\345\220\210/README.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/01.\347\273\274\345\220\210/README.md" index 0483001d..4db87d95 100644 --- "a/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/01.\347\273\274\345\220\210/README.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/01.\347\273\274\345\220\210/README.md" @@ -16,17 +16,15 @@ hidden: true ## 📖 内容 -### [关系型数据库面试题 💯](01.关系型数据库面试.md) +### [关系型数据库面试总结](01.关系型数据库面试.md) 💯 -### [SQL Cheat Sheet](02.SqlCheatSheet.md) +### [SQL 语法基础特性](02.SQL语法基础特性.md) -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200115160512.png) +### [SQL 语法高级特性](03.SQL语法高级特性.md) -### [分布式存储基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/theory/distributed-storage.md) +### [扩展 SQL](03.扩展SQL.md) -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200716110854.png) - -### [分布式事务基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/theory/distributed-transaction.md) +### [SQL Cheat Sheet](99.SqlCheatSheet.md) ## 📚 资料 @@ -36,7 +34,7 @@ hidden: true - [Mysql 官方文档之命令行客户端](https://dev.mysql.com/doc/refman/8.0/en/mysql.html) - **书籍** - [《高性能 MySQL》](https://item.jd.com/11220393.html) - Mysql 经典 - - [《SQL 必知必会》](https://item.jd.com/11232698.html) - SQL 入门 + - [《SQL 必知必会》](https://book.douban.com/subject/35167240/) - SQL 入门 - **教程** - [runoob.com MySQL 教程](http://www.runoob.com/mymysql-tutorial.html) - 入门级 SQL 教程 - [mysql-tutorial](https://github.com/jaywcjlove/mysql-tutorial) @@ -45,4 +43,4 @@ hidden: true ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/01.Mysql\345\272\224\347\224\250\346\214\207\345\215\227.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/01.Mysql\345\272\224\347\224\250\346\214\207\345\215\227.md" index fe18e0a7..db45a48a 100644 --- "a/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/01.Mysql\345\272\224\347\224\250\346\214\207\345\215\227.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/01.Mysql\345\272\224\347\224\250\346\214\207\345\215\227.md" @@ -14,17 +14,17 @@ permalink: /pages/5fe0f3/ # Mysql 应用指南 -## 1. SQL 执行过程 +## SQL 执行过程 学习 Mysql,最好是先从宏观上了解 Mysql 工作原理。 -> 参考:[Mysql 工作流](docs/sql/mysql/mysql-index.md) +> 参考:[Mysql 工作流](02.MySQL工作流.md) -## 2. 存储引擎 +## 存储引擎 在文件系统中,Mysql 将每个数据库(也可以成为 schema)保存为数据目录下的一个子目录。创建表示,Mysql 会在数据库子目录下创建一个和表同名的 `.frm` 文件保存表的定义。因为 Mysql 使用文件系统的目录和文件来保存数据库和表的定义,大小写敏感性和具体平台密切相关。Windows 中大小写不敏感;类 Unix 中大小写敏感。**不同的存储引擎保存数据和索引的方式是不同的,但表的定义则是在 Mysql 服务层统一处理的。** -### 2.1. 选择存储引擎 +### 选择存储引擎 #### Mysql 内置的存储引擎 @@ -73,7 +73,7 @@ mysql> SHOW ENGINES; ALTER TABLE mytable ENGINE = InnoDB ``` -### 2.2. MyISAM +### MyISAM MyISAM 设计简单,数据以紧密格式存储。对于只读数据,或者表比较小、可以容忍修复操作,则依然可以使用 MyISAM。 @@ -81,7 +81,7 @@ MyISAM 引擎使用 B+Tree 作为索引结构,**叶节点的 data 域存放的 MyISAM 提供了大量的特性,包括:全文索引、压缩表、空间函数等。但是,MyISAM 不支持事务和行级锁。并且 MyISAM 不支持崩溃后的安全恢复。 -### 2.3. InnoDB +### InnoDB InnoDB 是 MySQL 默认的事务型存储引擎,只有在需要 InnoDB 不支持的特性时,才考虑使用其它存储引擎。 @@ -95,9 +95,9 @@ InnoDB 是基于聚簇索引建立的,与其他存储引擎有很大不同。 支持真正的在线热备份。其它存储引擎不支持在线热备份,要获取一致性视图需要停止对所有表的写入,而在读写混合场景中,停止写入可能也意味着停止读取。 -## 3. 数据类型 +## 数据类型 -### 3.1. 整型 +### 整型 `TINYINT`, `SMALLINT`, `MEDIUMINT`, `INT`, `BIGINT` 分别使用 `8`, `16`, `24`, `32`, `64` 位存储空间,一般情况下越小的列越好。 @@ -105,7 +105,7 @@ InnoDB 是基于聚簇索引建立的,与其他存储引擎有很大不同。 `INT(11)` 中的数字只是规定了交互工具显示字符的个数,对于存储和计算来说是没有意义的。 -### 3.2. 浮点型 +### 浮点型 `FLOAT` 和 `DOUBLE` 为浮点类型。 @@ -113,7 +113,7 @@ InnoDB 是基于聚簇索引建立的,与其他存储引擎有很大不同。 `FLOAT`、`DOUBLE` 和 `DECIMAL` 都可以指定列宽,例如 `DECIMAL(18, 9)` 表示总共 18 位,取 9 位存储小数部分,剩下 9 位存储整数部分。 -### 3.3. 字符串 +### 字符串 主要有 `CHAR` 和 `VARCHAR` 两种类型,一种是定长的,一种是变长的。 @@ -121,7 +121,7 @@ InnoDB 是基于聚簇索引建立的,与其他存储引擎有很大不同。 `VARCHAR` 会保留字符串末尾的空格,而 `CHAR` 会删除。 -### 3.4. 时间和日期 +### 时间和日期 MySQL 提供了两种相似的日期时间类型:`DATATIME` 和 `TIMESTAMP`。 @@ -145,17 +145,17 @@ MySQL 提供了 FROM_UNIXTIME() 函数把 UNIX 时间戳转换为日期,并提 应该尽量使用 TIMESTAMP,因为它比 DATETIME 空间效率更高。 -### 3.5. BLOB 和 TEXT +### BLOB 和 TEXT `BLOB` 和 `TEXT` 都是为了存储大的数据而设计,前者存储二进制数据,后者存储字符串数据。 不能对 `BLOB` 和 `TEXT` 类型的全部内容进行排序、索引。 -### 3.6. 枚举类型 +### 枚举类型 大多数情况下没有使用枚举类型的必要,其中一个缺点是:枚举的字符串列表是固定的,添加和删除字符串(枚举选项)必须使用`ALTER TABLE`(如果只只是在列表末尾追加元素,不需要重建表)。 -### 3.7. 类型的选择 +### 类型的选择 - 整数类型通常是标识列最好的选择,因为它们很快并且可以使用 `AUTO_INCREMENT`。 @@ -163,25 +163,25 @@ MySQL 提供了 FROM_UNIXTIME() 函数把 UNIX 时间戳转换为日期,并提 - 应该尽量避免用字符串类型作为标识列,因为它们很消耗空间,并且通常比数字类型慢。对于 `MD5`、`SHA`、`UUID` 这类随机字符串,由于比较随机,所以可能分布在很大的空间内,导致 `INSERT` 以及一些 `SELECT` 语句变得很慢。 - 如果存储 UUID ,应该移除 `-` 符号;更好的做法是,用 `UNHEX()` 函数转换 UUID 值为 16 字节的数字,并存储在一个 `BINARY(16)` 的列中,检索时,可以通过 `HEX()` 函数来格式化为 16 进制格式。 -## 4. 索引 +## 索引 > 详见:[Mysql 索引](05.Mysql索引.md) -## 5. 锁 +## 锁 > 详见:[Mysql 锁](04.Mysql锁.md) -## 6. 事务 +## 事务 > 详见:[Mysql 事务](03.Mysql事务.md) -## 7. 性能优化 +## 性能优化 > 详见:[Mysql 性能优化](06.Mysql性能优化.md) -## 8. 复制 +## 复制 -### 8.1. 主从复制 +### 主从复制 Mysql 支持两种复制:基于行的复制和基于语句的复制。 @@ -194,10 +194,10 @@ Mysql 支持两种复制:基于行的复制和基于语句的复制。 - **SQL 线程** :负责读取中继日志并重放其中的 SQL 语句。
- +
-### 8.2. 读写分离 +### 读写分离 主服务器用来处理写操作以及实时性要求比较高的读操作,而从服务器用来处理读操作。 @@ -210,27 +210,16 @@ MySQL 读写分离能提高性能的原因在于: - 增加冗余,提高可用性。
- +
------- -(分割线)以下为高级特性,也是关系型数据库通用方案 - -## 9. 分布式事务 - -> 参考:[分布式事务基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/theory/distributed-transaction.md) - -## 10. 分库分表 - -> 参考:[分布式存储基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/theory/distributed-storage.md) - -## 11. 参考资料 +## 参考资料 - [《高性能 MySQL》](https://book.douban.com/subject/23008813/) - [20+ 条 MySQL 性能优化的最佳经验](https://www.jfox.info/20-tiao-mysql-xing-nen-you-hua-de-zui-jia-jing-yan.html) - [How to create unique row ID in sharded databases?](https://stackoverflow.com/questions/788829/how-to-create-unique-row-id-in-sharded-databases) - [SQL Azure Federation – Introduction](http://geekswithblogs.net/shaunxu/archive/2012/01/07/sql-azure-federation-ndash-introduction.aspx) -## 12. 传送门 +## 传送门 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/02.MySQL\345\267\245\344\275\234\346\265\201.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/02.MySQL\345\267\245\344\275\234\346\265\201.md" index e39244bb..05d9f88a 100644 --- "a/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/02.MySQL\345\267\245\344\275\234\346\265\201.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/02.MySQL\345\267\245\344\275\234\346\265\201.md" @@ -14,7 +14,7 @@ permalink: /pages/8262aa/ # MySQL 工作流 -## 1. 基础架构 +## 基础架构 大体来说,MySQL 可以分为 Server 层和存储引擎层两部分。 @@ -22,24 +22,24 @@ permalink: /pages/8262aa/ **存储引擎层负责数据的存储和提取**。其架构模式是插件式的,支持 InnoDB、MyISAM、Memory 等多个存储引擎。现在最常用的存储引擎是 InnoDB,它从 MySQL 5.5.5 版本开始成为了默认存储引擎。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200227201908.jpg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200227201908.jpg) -## 2. 查询过程 +## 查询过程 SQL 语句在 MySQL 中是如何执行的? -MySQL 整个查询执行过程,总的来说分为 6 个步骤: +MySQL 整个查询执行过程,总的来说分为 6 个步骤,分别对应 6 个组件: -1. 客户端和 MySQL 服务器建立连接;客户端向 MySQL 服务器发送一条查询请求。 +1. 连接器:客户端和 MySQL 服务器建立连接;连接器负责跟客户端建立连接、获取权限、维持和管理连接。 2. MySQL 服务器首先检查查询缓存,如果命中缓存,则立刻返回结果。否则进入下一阶段。 3. MySQL 服务器进行 SQL 分析:语法分析、词法分析。 4. MySQL 服务器用优化器生成对应的执行计划。 5. MySQL 服务器根据执行计划,调用存储引擎的 API 来执行查询。 6. MySQL 服务器将结果返回给客户端,同时缓存查询结果。 -### 2.1. (一)连接 +### (一)连接器 -使用 MySQL 第一步自然是要连接数据库。 +使用 MySQL 第一步自然是要连接数据库。**连接器负责跟客户端建立连接、获取权限、维持和管理连接**。 MySQL 客户端/服务端通信是**半双工模式**:即任一时刻,要么是服务端向客户端发送数据,要么是客户端向服务器发送数据。客户端用一个单独的数据包将查询请求发送给服务器,所以当查询语句很长的时候,需要设置`max_allowed_packet`参数。但是需要注意的是,如果查询实在是太大,服务端会拒绝接收更多数据并抛出异常。 @@ -56,7 +56,7 @@ MySQL 客户端连接命令:`mysql -h<主机> -P<端口> -u<用户名> -p<密 - **定期断开长连接**。使用一段时间,或者程序里面判断执行过一个占用内存的大查询后,断开连接,之后要查询再重连。 - 如果你用的是 MySQL 5.7 或更新版本,可以在每次执行一个比较大的操作后,通过执行 `mysql_reset_connection` 来重新初始化连接资源。这个过程不需要重连和重新做权限验证,但是会将连接恢复到刚刚创建完时的状态。 -### 2.2. (二)查询缓存 +### (二)查询缓存 > **不建议使用数据库缓存,因为往往弊大于利**。 @@ -76,14 +76,14 @@ select SQL_CACHE * from T where ID=10; > 注意:MySQL 8.0 版本直接将查询缓存的整块功能删掉了。 -### 2.3. (三)语法分析 +### (三)语法分析 如果没有命中查询缓存,就要开始真正执行语句了。首先,MySQL 需要知道你要做什么,因此需要对 SQL 语句做解析。MySQL 通过关键字对 SQL 语句进行解析,并生成一颗对应的语法解析树。这个过程中,分析器主要通过语法规则来验证和解析。比如 SQL 中是否使用了错误的关键字或者关键字的顺序是否正确等等。预处理则会根据 MySQL 规则进一步检查解析树是否合法。比如检查要查询的数据表和数据列是否存在等等。 - 分析器先会先做“**词法分析**”。你输入的是由多个字符串和空格组成的一条 SQL 语句,MySQL 需要识别出里面的字符串分别是什么,代表什么。MySQL 从你输入的"select"这个关键字识别出来,这是一个查询语句。它也要把字符串“T”识别成“表名 T”,把字符串“ID”识别成“列 ID”。 - 接下来,要做“**语法分析**”。根据词法分析的结果,语法分析器会根据语法规则,判断你输入的这个 SQL 语句是否满足 MySQL 语法。如果你的语句不对,就会收到“You have an error in your SQL syntax”的错误提醒,比如下面这个语句 select 少打了开头的字母“s”。 -### 2.4. (四)查询优化 +### (四)查询优化 经过了分析器,MySQL 就知道你要做什么了。在开始执行之前,还要先经过优化器的处理。 @@ -116,11 +116,11 @@ MySQL 的查询优化器是一个非常复杂的部件,它使用了非常多 随着 MySQL 的不断发展,优化器使用的优化策略也在不断的进化,这里仅仅介绍几个非常常用且容易理解的优化策略,其他的优化策略,大家自行查阅吧。 -### 2.5. (五)查询执行引擎 +### (五)查询执行引擎 在完成解析和优化阶段以后,MySQL 会生成对应的执行计划,查询执行引擎根据执行计划给出的指令逐步执行得出结果。整个执行过程的大部分操作均是通过调用存储引擎实现的接口来完成,这些接口被称为`handler API`。查询过程中的每一张表由一个`handler`实例表示。实际上,MySQL 在查询优化阶段就为每一张表创建了一个`handler`实例,优化器可以根据这些实例的接口来获取表的相关信息,包括表的所有列名、索引统计信息等。存储引擎接口提供了非常丰富的功能,但其底层仅有几十个接口,这些接口像搭积木一样完成了一次查询的大部分操作。 -### 2.6. (六)返回结果 +### (六)返回结果 查询过程的最后一个阶段就是将结果返回给客户端。即使查询不到数据,MySQL 仍然会返回这个查询的相关信息,比如该查询影响到的行数以及执行时间等等。 @@ -128,11 +128,11 @@ MySQL 的查询优化器是一个非常复杂的部件,它使用了非常多 结果集返回客户端是一个增量且逐步返回的过程。有可能 MySQL 在生成第一条结果时,就开始向客户端逐步返回结果集了。这样服务端就无须存储太多结果而消耗过多内存,也可以让客户端第一时间获得返回结果。需要注意的是,结果集中的每一行都会以一个满足 ① 中所描述的通信协议的数据包发送,再通过 TCP 协议进行传输,在传输过程中,可能对 MySQL 的数据包进行缓存然后批量发送。 -## 3. 更新过程 +## 更新过程 MySQL 更新过程和 MySQL 查询过程类似,也会将流程走一遍。不一样的是:**更新流程还涉及两个重要的日志模块,:redo log(重做日志)和 binlog(归档日志)**。 -### 3.1. redo log +### redo log **redo log 是 InnoDB 引擎特有的日志**。**redo log 即重做日志**。redo log 是物理日志,记录的是“在某个数据页上做了什么修改”。 @@ -140,11 +140,11 @@ MySQL 更新过程和 MySQL 查询过程类似,也会将流程走一遍。不 InnoDB 的 redo log 是固定大小的,比如可以配置为一组 4 个文件,每个文件的大小是 1GB,那么这块“粉板”总共就可以记录 4GB 的操作。从头开始写,写到末尾就又回到开头循环写。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200630180342.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200630180342.png) 有了 redo log,InnoDB 就可以保证即使数据库发生异常重启,之前提交的记录都不会丢失,这个能力称为**crash-safe**。 -### 3.2. bin log +### bin log **bin log 即归档日志**。binlog 是逻辑日志,记录的是这个语句的原始逻辑。 @@ -154,7 +154,7 @@ binlog 是可以追加写入的,即写到一定大小后会切换到下一个 `sync_binlog` 这个参数设置成 1 的时候,表示每次事务的 binlog 都持久化到磁盘。这个参数我也建议你设置成 1,这样可以保证 MySQL 异常重启之后 binlog 不丢失。 -### 3.3. redo log vs. bin log +### redo log vs. bin log 这两种日志有以下三点不同。 @@ -172,9 +172,9 @@ binlog 是可以追加写入的,即写到一定大小后会切换到下一个 这里我给出这个 update 语句的执行流程图,图中浅色框表示是在 InnoDB 内部执行的,深色框表示是在执行器中执行的。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200714133806.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200714133806.png) -### 3.4. 两阶段提交 +### 两阶段提交 redo log 的写入拆成了两个步骤:prepare 和 commit,这就是"两阶段提交"。为什么日志需要“两阶段提交”。 @@ -187,7 +187,7 @@ redo log 的写入拆成了两个步骤:prepare 和 commit,这就是"两阶 可以看到,如果不使用“两阶段提交”,那么数据库的状态就有可能和用它的日志恢复出来的库的状态不一致。 -## 4. 参考资料 +## 参考资料 - [《高性能 MySQL》](https://book.douban.com/subject/23008813/) -- [MySQL 实战 45 讲](https://time.geekbang.org/column/intro/139) +- [MySQL 实战 45 讲](https://time.geekbang.org/column/intro/139) \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/03.Mysql\344\272\213\345\212\241.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/03.Mysql\344\272\213\345\212\241.md" index d62dad37..75a07c06 100644 --- "a/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/03.Mysql\344\272\213\345\212\241.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/03.Mysql\344\272\213\345\212\241.md" @@ -19,13 +19,13 @@ permalink: /pages/00b04d/ > > 用户可以根据业务是否需要事务处理(事务处理可以保证数据安全,但会增加系统开销),选择合适的存储引擎。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200716074533.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220721072721.png) -## 1. 事务简介 +## 事务简介 > 事务简单来说:**一个 Session 中所进行所有的操作,要么同时成功,要么同时失败**。进一步说,事务指的是满足 ACID 特性的一组操作,可以通过 `Commit` 提交一个事务,也可以使用 `Rollback` 进行回滚。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/RDB/数据库事务.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/database/RDB/数据库事务.png) **事务就是一组原子性的 SQL 语句**。具体来说,事务指的是满足 ACID 特性的一组操作。 @@ -37,11 +37,11 @@ permalink: /pages/00b04d/ T1 和 T2 两个线程都对一个数据进行修改,T1 先修改,T2 随后修改,T2 的修改覆盖了 T1 的修改。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/RDB/数据库并发一致性-丢失修改.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/database/RDB/数据库并发一致性-丢失修改.png) -## 2. 事务用法 +## 事务用法 -### 2.1. 事务处理指令 +### 事务处理指令 Mysql 中,使用 `START TRANSACTION` 语句开始一个事务;使用 `COMMIT` 语句提交所有的修改;使用 `ROLLBACK` 语句撤销所有的修改。不能回退 `SELECT` 语句,回退 `SELECT` 语句也没意义;也不能回退 `CREATE` 和 `DROP` 语句。 @@ -103,7 +103,7 @@ SELECT * FROM user; 1 root1 root1 xxxx@163.com ``` -### 2.2. AUTOCOMMIT +### AUTOCOMMIT **MySQL 默认采用隐式提交策略(`autocommit`)**。每执行一条语句就把这条语句当成一个事务然后进行提交。当出现 `START TRANSACTION` 语句时,会关闭隐式提交;当 `COMMIT` 或 `ROLLBACK` 语句执行后,事务会自动关闭,重新恢复隐式提交。 @@ -120,7 +120,7 @@ SET autocommit = 0; SET autocommit = 1; ``` -## 3. ACID +## ACID ACID 是数据库事务正确执行的四个基本要素。 @@ -143,13 +143,13 @@ ACID 是数据库事务正确执行的四个基本要素。 - 在并发的情况下,多个事务并行执行,事务不仅要满足原子性,还需要满足隔离性,才能满足一致性。 - 事务满足持久化是为了能应对系统崩溃的情况。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/RDB/数据库ACID.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/database/RDB/数据库ACID.png) > MySQL 默认采用自动提交模式(`AUTO COMMIT`)。也就是说,如果不显式使用 `START TRANSACTION` 语句来开始一个事务,那么每个查询操作都会被当做一个事务并自动提交。 -## 4. 事务隔离级别 +## 事务隔离级别 -### 4.1. 事务隔离简介 +### 事务隔离简介 在并发环境下,事务的隔离性很难保证,因此会出现很多并发一致性问题: @@ -160,8 +160,8 @@ ACID 是数据库事务正确执行的四个基本要素。 在 SQL 标准中,定义了四种事务隔离级别(级别由低到高): -- **未提交读** -- **提交读** +- **读未提交** +- **读提交** - **可重复读** - **串行化** @@ -184,63 +184,61 @@ SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ; SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE; ``` -### 4.2. 未提交读 +### 读未提交 -**`未提交读(READ UNCOMMITTED)` 是指:事务中的修改,即使没有提交,对其它事务也是可见的**。 +**`读未提交(read uncommitted)` 是指:事务中的修改,即使没有提交,对其它事务也是可见的**。 -未提交读的问题:事务可以读取未提交的数据,也被称为 **脏读(Dirty Read)**。 +读未提交的问题:事务可以读取未提交的数据,也被称为 **脏读(Dirty Read)**。 T1 修改一个数据,T2 随后读取这个数据。如果 T1 撤销了这次修改,那么 T2 读取的数据是脏数据。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/RDB/数据库并发一致性-脏数据.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/database/RDB/数据库并发一致性-脏数据.png) -### 4.3. 提交读 +### 读提交 -**`提交读(READ COMMITTED)` 是指:事务提交后,其他事务才能看到它的修改**。换句话说,一个事务所做的修改在提交之前对其它事务是不可见的。提交读解决了脏读的问题。 +**`读提交(read committed)` 是指:事务提交后,其他事务才能看到它的修改**。换句话说,一个事务所做的修改在提交之前对其它事务是不可见的。读提交解决了脏读的问题。 -提交读是大多数数据库的默认事务隔离级别。 +读提交是大多数数据库的默认事务隔离级别。 -提交读有时也叫不可重复读,它的问题是:执行两次相同的查询,得到的结果可能不一致。 +读提交有时也叫不可重复读,它的问题是:执行两次相同的查询,得到的结果可能不一致。 T2 读取一个数据,T1 对该数据做了修改。如果 T2 再次读取这个数据,此时读取的结果和第一次读取的结果不同。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/RDB/数据库并发一致性-不可重复读.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/database/RDB/数据库并发一致性-不可重复读.png) -### 4.4. 可重复读 +### 可重复读 **`可重复读(REPEATABLE READ)` 是指:保证在同一个事务中多次读取同样数据的结果是一样的**。可重复读解决了不可重复读问题。 可重复读是 Mysql 的默认事务隔离级别。 -可重复读的问题:当某个事务读取某个范围内的记录时,另外一个事务又在该范围内插入了新的记录,当之前的事务又再次读取该范围的记录时,会产生 **幻读(Phantom Read)**。 +可重复读的问题:事务 T1 读取某个范围内的记录时,事务 T2 在该范围内插入了新的记录,T1 再次读取这个范围的数据,此时读取的结果和和第一次读取的结果不同,即为 **幻读(Phantom Read)**。 -T1 读取某个范围的数据,T2 在这个范围内插入新的数据,T1 再次读取这个范围的数据,此时读取的结果和和第一次读取的结果不同。 +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/database/RDB/数据库并发一致性-幻读.png) -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/RDB/数据库并发一致性-幻读.png) +### 串行化 -### 4.5. 串行化 - -**`串行化(SERIALIXABLE)` 是指:强制事务串行执行**。 +**`串行化(SERIALIXABLE)` 是指:强制事务串行执行,对于同一行记录,加读写锁,一旦出现锁冲突,必须等前面的事务释放锁**。 强制事务串行执行,则避免了所有的并发问题。串行化策略会在读取的每一行数据上都加锁,这可能导致大量的超时和锁竞争。这对于高并发应用基本上是不可接受的,所以一般不会采用这个级别。 -### 4.6. 隔离级别小结 +### 隔离级别小结 -- **`未提交读(READ UNCOMMITTED)`** - 事务中的修改,即使没有提交,对其它事务也是可见的。 -- **`提交读(READ COMMITTED)`** - 一个事务只能读取已经提交的事务所做的修改。换句话说,一个事务所做的修改在提交之前对其它事务是不可见的。 +- **`读未提交(READ UNCOMMITTED)`** - 事务中的修改,即使没有提交,对其它事务也是可见的。 +- **`读提交(READ COMMITTED)`** - 一个事务只能读取已经提交的事务所做的修改。换句话说,一个事务所做的修改在提交之前对其它事务是不可见的。 - **`重复读(REPEATABLE READ)`** - 保证在同一个事务中多次读取同样数据的结果是一样的。 -- **`串行化(SERIALIXABLE)`** - 强制事务串行执行。 +- **`串行化(SERIALIXABLE)`** - 对于同一行记录,加读写锁,一旦出现锁冲突,必须等前面的事务释放锁。 数据库隔离级别解决的问题: | 隔离级别 | 丢失修改 | 脏读 | 不可重复读 | 幻读 | | :------: | :------: | :--: | :--------: | :--: | -| 未提交读 | ✔️ | ❌ | ❌ | ❌ | -| 提交读 | ✔️ | ✔️ | ❌ | ❌ | +| 读未提交 | ✔️ | ❌ | ❌ | ❌ | +| 读提交 | ✔️ | ✔️ | ❌ | ❌ | | 可重复读 | ✔️ | ✔️ | ✔️ | ❌ | | 可串行化 | ✔️ | ✔️ | ✔️ | ✔️ | -## 5. 死锁 +## 死锁 **死锁是指两个或多个事务竞争同一资源,并请求锁定对方占用的资源,从而导致恶性循环的现象**。 @@ -250,7 +248,7 @@ T1 读取某个范围的数据,T2 在这个范围内插 - 多个事务同时锁定同一个资源时,也会产生死锁。 -### 5.1. 死锁的原因 +### 死锁的原因 行锁的具体实现算法有三种:record lock、gap lock 以及 next-key lock。record lock 是专门对索引项加锁;gap lock 是对索引项之间的间隙加锁;next-key lock 则是前面两种的组合,对索引项以其之间的间隙加锁。 @@ -268,21 +266,21 @@ T1 读取某个范围的数据,T2 在这个范围内插 > INSERT INTO `demo`.`order_record`(`order_no`, `status`, `create_date`) VALUES (5, 1, ‘2019-07-13 10:57:03’); -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200630153139.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200630153139.png) **另一个死锁场景** InnoDB 存储引擎的主键索引为聚簇索引,其它索引为辅助索引。如果使用辅助索引来更新数据库,就需要使用聚簇索引来更新数据库字段。如果两个更新事务使用了不同的辅助索引,或一个使用了辅助索引,一个使用了聚簇索引,就都有可能导致锁资源的循环等待。由于本身两个事务是互斥,也就构成了以上死锁的四个必要条件了。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200630154606.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200630154606.png) 出现死锁的步骤: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200630154619.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200630154619.png) 综上可知,在更新操作时,我们应该尽量使用主键来更新表字段,这样可以有效避免一些不必要的死锁发生。 -### 5.2. 避免死锁 +### 避免死锁 预防死锁的注意事项: @@ -296,7 +294,7 @@ InnoDB 存储引擎的主键索引为聚簇索引,其它索引为辅助索引 我们还可以使用其它的方式来代替数据库实现幂等性校验。例如,使用 Redis 以及 ZooKeeper 来实现,运行效率比数据库更佳。 -### 5.3. 解决死锁 +### 解决死锁 当出现死锁以后,有两种策略: @@ -311,7 +309,7 @@ InnoDB 存储引擎的主键索引为聚簇索引,其它索引为辅助索引 主动死锁检测在发生死锁的时候,是能够快速发现并进行处理的,但是它也是有额外负担的。你可以想象一下这个过程:每当一个事务被锁的时候,就要看看它所依赖的线程有没有被别人锁住,如此循环,最后判断是否出现了循环等待,也就是死锁。 -## 6. 分布式事务 +## 分布式事务 在单一数据节点中,事务仅限于对单一数据库资源的访问控制,称之为 **本地事务**。几乎所有的成熟的关系型数据库都提供了对本地事务的原生支持。 @@ -336,21 +334,21 @@ InnoDB 存储引擎的主键索引为聚簇索引,其它索引为辅助索引 - 本地消息表/MQ 事务 都适用于事务中参与方支持操作幂等,对一致性要求不高,业务上能容忍数据不一致到一个人工检查周期,事务涉及的参与方、参与环节较少,业务上有对账/校验系统兜底。 - Saga 事务 由于 Saga 事务不能保证隔离性,需要在业务层控制并发,适合于业务场景事务并发操作同一资源较少的情况。 Saga 相比缺少预提交动作,导致补偿动作的实现比较麻烦,例如业务是发送短信,补偿动作则得再发送一次短信说明撤销,用户体验比较差。Saga 事务较适用于补偿动作容易处理的场景。 -> 分布式事务详细说明、分析请参考:[分布式事务基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/theory/distributed-transaction.md) +> 分布式事务详细说明、分析请参考:[分布式事务基本原理](https://dunwu.github.io/blog/pages/e1881c/) -## 7. 事务最佳实践 +## 事务最佳实践 高并发场景下的事务到底该如何调优? -### 7.1. 尽量使用低级别事务隔离 +### 尽量使用低级别事务隔离 结合业务场景,尽量使用低级别事务隔离 -### 7.2. 避免行锁升级表锁 +### 避免行锁升级表锁 在 InnoDB 中,行锁是通过索引实现的,如果不通过索引条件检索数据,行锁将会升级到表锁。我们知道,表锁是会严重影响到整张表的操作性能的,所以应该尽力避免。 -### 7.3. 缩小事务范围 +### 缩小事务范围 有时候,数据库并发访问量太大,会出现以下异常: @@ -362,7 +360,7 @@ MySQLQueryInterruptedException: Query execution was interrupted 又因为锁的竞争是不公平的,当多个事务同时对一条记录进行更新时,极端情况下,一个更新操作进去排队系统后,可能会一直拿不到锁,最后因超时被系统打断踢出。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200630112600.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200630112600.png) 如上图中的操作,虽然都是在一个事务中,但锁的申请在不同时间,只有当其他操作都执行完,才会释放所有锁。因为扣除库存是更新操作,属于行锁,这将会影响到其他操作该数据的事务,所以我们应该尽量避免长时间地持有该锁,尽快释放该锁。又因为先新建订单和先扣除库存都不会影响业务,所以我们可以将扣除库存操作放到最后,也就是使用执行顺序 1,以此尽量减小锁的持有时间。 @@ -370,8 +368,8 @@ MySQLQueryInterruptedException: Query execution was interrupted 知道了这个设定,对我们使用事务有什么帮助呢?那就是,如果你的事务中需要锁多个行,要把最可能造成锁冲突、最可能影响并发度的锁尽量往后放。 -## 8. 参考资料 +## 参考资料 - [《高性能 MySQL》](https://book.douban.com/subject/23008813/) - [《Java 性能调优实战》](https://time.geekbang.org/column/intro/100028001) -- [ShardingSphere 分布式事务](https://shardingsphere.apache.org/document/current/cn/features/transaction/) +- [ShardingSphere 分布式事务](https://shardingsphere.apache.org/document/current/cn/features/transaction/) \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/04.Mysql\351\224\201.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/04.Mysql\351\224\201.md" index 866e9c54..7474e504 100644 --- "a/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/04.Mysql\351\224\201.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/04.Mysql\351\224\201.md" @@ -15,9 +15,9 @@ permalink: /pages/f1f151/ # Mysql 锁 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200716064947.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200716064947.png) -## 1. 悲观锁和乐观锁 +## 悲观锁和乐观锁 确保在多个事务同时存取数据库中同一数据时不破坏事务的隔离性和统一性以及数据库的统一性,**乐观锁和悲观锁是并发控制主要采用的技术手段。** @@ -42,7 +42,7 @@ where id=#{id} and version=#{version}; > 更详细的乐观锁说可以参考:[使用 mysql 乐观锁解决并发问题](https://www.cnblogs.com/laoyeye/p/8097684.html) -## 2. 表级锁和行级锁 +## 表级锁和行级锁 从数据库的锁粒度来看,MySQL 中提供了两种封锁粒度:行级锁和表级锁。 @@ -55,7 +55,7 @@ where id=#{id} and version=#{version}; 在 `InnoDB` 中,**行锁是通过给索引上的索引项加锁来实现的**。**如果没有索引,`InnoDB` 将会通过隐藏的聚簇索引来对记录加锁**。 -## 3. 读写锁 +## 读写锁 - 独享锁(Exclusive),简写为 X 锁,又称写锁。使用方式:`SELECT ... FOR UPDATE;` - 共享锁(Shared),简写为 S 锁,又称读锁。使用方式:`SELECT ... LOCK IN SHARE MODE;` @@ -64,7 +64,7 @@ where id=#{id} and version=#{version}; **`InnoDB` 下的行锁、间隙锁、next-key 锁统统属于独享锁**。 -## 4. 意向锁 +## 意向锁 **当存在表级锁和行级锁的情况下,必须先申请意向锁(表级锁,但不是真的加锁),再获取行级锁**。使用意向锁(Intention Locks)可以更容易地支持多粒度封锁。 @@ -95,13 +95,13 @@ where id=#{id} and version=#{version}; - 任意 IS/IX 锁之间都是兼容的,因为它们只表示想要对表加锁,而不是真正加锁; - 这里兼容关系针对的是表级锁,而表级的 IX 锁和行级的 X 锁兼容,两个事务可以对两个数据行加 X 锁。(事务 T1 想要对数据行 R1 加 X 锁,事务 T2 想要对同一个表的数据行 R2 加 X 锁,两个事务都需要对该表加 IX 锁,但是 IX 锁是兼容的,并且 IX 锁与行级的 X 锁也是兼容的,因此两个事务都能加锁成功,对同一个表中的两个数据行做修改。) -## 5. MVCC +## MVCC **多版本并发控制(Multi-Version Concurrency Control, MVCC)可以视为行级锁的一个变种。它在很多情况下都避免了加锁操作,因此开销更低**。不仅是 Mysql,包括 Oracle、PostgreSQL 等其他数据库都实现了各自的 MVCC,实现机制没有统一标准。 MVCC 是 `InnoDB` 存储引擎实现隔离级别的一种具体方式,**用于实现提交读和可重复读这两种隔离级别**。而未提交读隔离级别总是读取最新的数据行,要求很低,无需使用 MVCC。可串行化隔离级别需要对所有读取的行都加锁,单纯使用 MVCC 无法实现。 -### 5.1. MVCC 思想 +### MVCC 思想 加锁能解决多个事务同时执行时出现的并发一致性问题。在实际场景中读操作往往多于写操作,因此又引入了读写锁来避免不必要的加锁操作,例如读和读没有互斥关系。读写锁中读和写操作仍然是互斥的。 @@ -110,14 +110,14 @@ MVCC 的思想是: - **保存数据在某个时间点的快照,写操作(DELETE、INSERT、UPDATE)更新最新的版本快照;而读操作去读旧版本快照,没有互斥关系**。这一点和 `CopyOnWrite` 类似。 - 脏读和不可重复读最根本的原因是**事务读取到其它事务未提交的修改**。在事务进行读取操作时,为了解决脏读和不可重复读问题,**MVCC 规定只能读取已经提交的快照**。当然一个事务可以读取自身未提交的快照,这不算是脏读。 -### 5.2. 版本号 +### 版本号 InnoDB 的 MVCC 实现是:在每行记录后面保存两个隐藏列,一个列保存行的创建时间,另一个列保存行的过期时间(这里的时间是指系统版本号)。每开始一个新事务,系统版本号会自动递增,事务开始时刻的系统版本号会作为事务的版本号,用来和查询到的每行记录的版本号进行比较。 - 系统版本号 `SYS_ID`:是一个递增的数字,每开始一个新的事务,系统版本号就会自动递增。 - 事务版本号 `TRX_ID` :事务开始时的系统版本号。 -### 5.3. Undo 日志 +### Undo 日志 MVCC 的多版本指的是多个版本的快照,快照存储在 Undo 日志中,该日志通过回滚指针 `ROLL_PTR` 把一个数据行的所有快照连接起来。 @@ -133,11 +133,11 @@ UPDATE t SET x="c" WHERE id=1; `INSERT`、`UPDATE`、`DELETE` 操作会创建一个日志,并将事务版本号 `TRX_ID` 写入。`DELETE` 可以看成是一个特殊的 `UPDATE`,还会额外将 DEL 字段设置为 1。 -### 5.4. ReadView +### ReadView MVCC 维护了一个一致性读视图 `consistent read view` ,主要包含了当前系统**未提交的事务列表** `TRX_IDs {TRX_ID_1, TRX_ID_2, ...}`,还有该列表的最小值 `TRX_ID_MIN` 和 `TRX_ID_MAX`。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200715135809.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200715135809.png) 这样,对于当前事务的启动瞬间来说,一个数据版本的 row trx_id,有以下几种可能: @@ -157,7 +157,7 @@ MVCC 维护了一个一致性读视图 `consistent read view` ,主要包含了 在数据行快照不可使用的情况下,需要沿着 Undo Log 的回滚指针 ROLL_PTR 找到下一个快照,再进行上面的判断。 -### 5.5. 快照读与当前读 +### 快照读与当前读 快照读 @@ -184,7 +184,7 @@ SELECT * FROM table WHERE ? lock in share mode; SELECT * FROM table WHERE ? for update; ``` -## 6. 行锁 +## 行锁 行锁的具体实现算法有三种:record lock、gap lock 以及 next-key lock。 @@ -200,10 +200,10 @@ MVCC 不能解决幻读问题,**Next-Key 锁就是为了解决幻读问题** 当两个事务同时执行,一个锁住了主键索引,在等待其他相关索引。另一个锁定了非主键索引,在等待主键索引。这样就会发生死锁。发生死锁后,`InnoDB` 一般都可以检测到,并使一个事务释放锁回退,另一个获取锁完成事务。 -## 7. 参考资料 +## 参考资料 - [《高性能 MySQL》](https://book.douban.com/subject/23008813/) - [《Java 性能调优实战》](https://time.geekbang.org/column/intro/100028001) - [数据库系统原理](https://github.com/CyC2018/Interview-Notebook/blob/master/notes/数据库系统原理.md) - [数据库两大神器【索引和锁】](https://juejin.im/post/5b55b842f265da0f9e589e79) -- [使用 mysql 乐观锁解决并发问题](https://www.cnblogs.com/laoyeye/p/8097684.html) +- [使用 mysql 乐观锁解决并发问题](https://www.cnblogs.com/laoyeye/p/8097684.html) \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/05.Mysql\347\264\242\345\274\225.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/05.Mysql\347\264\242\345\274\225.md" index e4997829..1c1dde2e 100644 --- "a/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/05.Mysql\347\264\242\345\274\225.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/05.Mysql\347\264\242\345\274\225.md" @@ -19,15 +19,15 @@ permalink: /pages/fcb19c/ > > 接下来将向你展示一系列创建高性能索引的策略,以及每条策略其背后的工作原理。但在此之前,先了解与索引相关的一些算法和数据结构,将有助于更好的理解后文的内容。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200715172009.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200715172009.png) -## 1. 索引简介 +## 索引简介 **索引是数据库为了提高查找效率的一种数据结构**。 索引对于良好的性能非常关键,在数据量小且负载较低时,不恰当的索引对于性能的影响可能还不明显;但随着数据量逐渐增大,性能则会急剧下降。因此,索引优化应该是查询性能优化的最有效手段。 -### 1.1. 索引的优缺点 +### 索引的优缺点 B 树是最常见的索引,按照顺序存储数据,所以 Mysql 可以用来做 `ORDER BY` 和 `GROUP BY` 操作。因为数据是有序的,所以 B 树也就会将相关的列值都存储在一起。最后,因为索引中存储了实际的列值,所以某些查询只使用索引就能够完成全部查询。 @@ -45,7 +45,7 @@ B 树是最常见的索引,按照顺序存储数据,所以 Mysql 可以用 - **索引需要占用额外的物理空间**,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立组合索引那么需要的空间就会更大。 - **写操作(`INSERT`/`UPDATE`/`DELETE`)时很可能需要更新索引,导致数据库的写操作性能降低**。 -### 1.2. 何时使用索引 +### 何时使用索引 > 索引能够轻易将查询性能提升几个数量级。 @@ -62,7 +62,7 @@ B 树是最常见的索引,按照顺序存储数据,所以 Mysql 可以用 - **非常小的表**,对于非常小的表,大部分情况下简单的全表扫描更高效。 - **特大型的表**,建立和使用索引的代价将随之增长。可以考虑使用分区技术或 Nosql。 -## 2. 索引的数据结构 +## 索引的数据结构 在 Mysql 中,索引是在存储引擎层而不是服务器层实现的。所以,并没有统一的索引标准;不同存储引擎的索引的数据结构也不相同。 @@ -79,13 +79,13 @@ B 树是最常见的索引,按照顺序存储数据,所以 Mysql 可以用 这意味着,如果使用数组作为索引,如果要保证数组有序,其更新操作代价高昂。 -### 2.1. 哈希索引 +### 哈希索引 哈希表是一种以键 - 值(key-value)对形式存储数据的结构,我们只要输入待查找的值即 key,就可以找到其对应的值即 Value。 **哈希表** 使用 **哈希函数** 组织数据,以支持快速插入和搜索的数据结构。哈希表的本质是一个数组,其思路是:使用 Hash 函数将 Key 转换为数组下标,利用数组的随机访问特性,使得我们能在 `O(1)` 的时间代价内完成检索。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220320201844.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220320201844.png) 有两种不同类型的哈希表:**哈希集合** 和 **哈希映射**。 @@ -112,7 +112,7 @@ B 树是最常见的索引,按照顺序存储数据,所以 Mysql 可以用 > 因为种种限制,所以哈希索引只适用于特定的场合。而一旦使用哈希索引,则它带来的性能提升会非常显著。 -### 2.2. B 树索引 +### B 树索引 通常我们所说的索引是指`B-Tree`索引,它是目前关系型数据库中查找数据最为常用和有效的索引,大多数存储引擎都支持这种索引。使用`B-Tree`这个术语,是因为 MySQL 在`CREATE TABLE`或其它语句中使用了这个关键字,但实际上不同的存储引擎可能使用不同的数据结构,比如 InnoDB 就是使用的`B+Tree`。 @@ -137,7 +137,7 @@ B+ 树索引适用于**全键值查找**、**键值范围查找**和**键前缀 - 第一,所有的关键字(可以理解为数据)都存储在叶子节点,非叶子节点并不存储真正的数据,所有记录节点都是按键值大小顺序存放在同一层叶子节点上。 - 其次,所有的叶子节点由指针连接。如下图为简化了的`B+Tree`。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200304235424.jpg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200304235424.jpg) 根据叶子节点的内容,索引类型分为主键索引和非主键索引。 @@ -164,7 +164,7 @@ B+ 树索引适用于**全键值查找**、**键值范围查找**和**键前缀 这时候我们就要优先考虑上一段提到的“尽量使用主键查询”原则,直接将这个索引设置为主键,可以避免每次查询需要搜索两棵树。 -### 2.3. 全文索引 +### 全文索引 MyISAM 存储引擎支持全文索引,用于查找文本中的关键词,而不是直接比较是否相等。查找条件使用 MATCH AGAINST,而不是普通的 WHERE。 @@ -172,17 +172,17 @@ MyISAM 存储引擎支持全文索引,用于查找文本中的关键词,而 InnoDB 存储引擎在 MySQL 5.6.4 版本中也开始支持全文索引。 -### 2.4. 空间数据索引 +### 空间数据索引 MyISAM 存储引擎支持空间数据索引(R-Tree),可以用于地理数据存储。空间数据索引会从所有维度来索引数据,可以有效地使用任意维度来进行组合查询。 必须使用 GIS 相关的函数来维护数据。 -## 3. 索引的类型 +## 索引的类型 主流的关系型数据库一般都支持以下索引类型: -### 3.1. 主键索引(`PRIMARY`) +### 主键索引(`PRIMARY`) 主键索引:一种特殊的唯一索引,不允许有空值。一个表只能有一个主键(在 InnoDB 中本质上即聚簇索引),一般是在建表的时候同时创建主键索引。 @@ -194,7 +194,7 @@ CREATE TABLE `table` ( ) ``` -### 3.2. 唯一索引(`UNIQUE`) +### 唯一索引(`UNIQUE`) 唯一索引:**索引列的值必须唯一,但允许有空值**。如果是组合索引,则列值的组合必须唯一。 @@ -205,7 +205,7 @@ CREATE TABLE `table` ( ) ``` -### 3.3. 普通索引(`INDEX`) +### 普通索引(`INDEX`) 普通索引:最基本的索引,没有任何限制。 @@ -216,7 +216,7 @@ CREATE TABLE `table` ( ) ``` -### 3.4. 全文索引(`FULLTEXT`) +### 全文索引(`FULLTEXT`) 全文索引:主要用来查找文本中的关键字,而不是直接与索引中的值相比较。 @@ -230,7 +230,7 @@ CREATE TABLE `table` ( ) ``` -### 3.5. 联合索引 +### 联合索引 组合索引:多个字段上创建的索引,只有在查询条件中使用了创建索引时的第一个字段,索引才会被使用。使用组合索引时遵循最左前缀集合。 @@ -241,7 +241,7 @@ CREATE TABLE `table` ( ) ``` -## 4. 索引的策略 +## 索引的策略 假设有以下表: @@ -257,7 +257,7 @@ CREATE TABLE `t` ( ) ENGINE=InnoDB; ``` -### 4.1. 索引基本原则 +### 索引基本原则 - **索引不是越多越好,不要为所有列都创建索引**。要考虑到索引的维护代价、空间占用和查询时回表的代价。索引一定是按需创建的,并且要尽可能确保足够轻量。一旦创建了多字段的联合索引,我们要考虑尽可能利用索引本身完成数据查询,减少回表的成本。 - 要**尽量避免冗余和重复索引**。 @@ -265,7 +265,7 @@ CREATE TABLE `t` ( - **尽量的扩展索引,不要新建索引**。 - **频繁作为 `WHERE` 过滤条件的列应该考虑添加索引**。 -### 4.2. 独立的列 +### 独立的列 **“独立的列” 是指索引列不能是表达式的一部分,也不能是函数的参数**。 @@ -280,7 +280,7 @@ SELECT actor_id FROM actor WHERE actor_id + 1 = 5; SELECT ... WHERE TO_DAYS(current_date) - TO_DAYS(date_col) <= 10; ``` -### 4.3. 覆盖索引 +### 覆盖索引 **覆盖索引是指,索引上的信息足够满足查询请求,不需要回表查询数据。** @@ -313,7 +313,7 @@ select * from T where k between 3 and 5 **由于覆盖索引可以减少树的搜索次数,显著提升查询性能,所以使用覆盖索引是一个常用的性能优化手段。** -### 4.4. 使用索引来排序 +### 使用索引来排序 Mysql 有两种方式可以生成排序结果:通过排序操作;或者按索引顺序扫描。 @@ -325,7 +325,7 @@ Mysql 有两种方式可以生成排序结果:通过排序操作;或者按 2. 从索引 (city,name,age) 取下一个记录,同样取出这三个字段的值,作为结果集的一部分直接返回; 3. 重复执行步骤 2,直到查到第 1000 条记录,或者是不满足 city='杭州’条件时循环结束。 -### 4.5. 前缀索引 +### 前缀索引 有时候需要索引很长的字符列,这会让索引变得大且慢。 @@ -358,7 +358,7 @@ from SUser; 此外,**`order by` 无法使用前缀索引,无法把前缀索引用作覆盖索引**。 -### 4.6. 最左前缀匹配原则 +### 最左前缀匹配原则 不只是索引的全部定义,只要满足最左前缀,就可以利用索引来加速检索。这个最左前缀可以是联合索引的最左 N 个字段,也可以是字符串索引的最左 M 个字符。 @@ -391,21 +391,21 @@ customer_id_selectivity: 0.0373 COUNT(*): 16049 ``` -### 4.7. = 和 in 可以乱序 +### = 和 in 可以乱序 **不需要考虑 `=`、`IN` 等的顺序**,Mysql 会自动优化这些条件的顺序,以匹配尽可能多的索引列。 【示例】如有索引 (a, b, c, d),查询条件 `c > 3 and b = 2 and a = 1 and d < 4` 与 `a = 1 and c > 3 and b = 2 and d < 4` 等顺序都是可以的,MySQL 会自动优化为 a = 1 and b = 2 and c > 3 and d < 4,依次命中 a、b、c、d。 -## 5. 索引最佳实践 +## 索引最佳实践 创建了索引,并非一定有效。比如不满足前缀索引、最左前缀匹配原则、查询条件涉及函数计算等情况都无法使用索引。此外,即使 SQL 本身符合索引的使用条件,MySQL 也会通过评估各种查询方式的代价,来决定是否走索引,以及走哪个索引。 因此,在尝试通过索引进行 SQL 性能优化的时候,务必通过执行计划(`EXPLAIN`)或实际的效果来确认索引是否能有效改善性能问题,否则增加了索引不但没解决性能问题,还增加了数据库增删改的负担。如果对 EXPLAIN 给出的执行计划有疑问的话,你还可以利用 `optimizer_trace` 查看详细的执行计划做进一步分析。 -## 6. 参考资料 +## 参考资料 - [《高性能 MySQL》](https://book.douban.com/subject/23008813/) - [数据库两大神器【索引和锁】](https://juejin.im/post/5b55b842f265da0f9e589e79) - [MySQL 索引背后的数据结构及算法原理](http://blog.codinglabs.org/articles/theory-of-mysql-index.html) -- [MySQL 实战 45 讲](https://time.geekbang.org/column/intro/139) +- [MySQL 实战 45 讲](https://time.geekbang.org/column/intro/139) \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/06.Mysql\346\200\247\350\203\275\344\274\230\345\214\226.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/06.Mysql\346\200\247\350\203\275\344\274\230\345\214\226.md" index 5c70ba70..c1c29c68 100644 --- "a/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/06.Mysql\346\200\247\350\203\275\344\274\230\345\214\226.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/06.Mysql\346\200\247\350\203\275\344\274\230\345\214\226.md" @@ -15,11 +15,11 @@ permalink: /pages/396816/ # Mysql 性能优化 -## 1. 数据结构优化 +## 数据结构优化 良好的逻辑设计和物理设计是高性能的基石。 -### 1.1. 数据类型优化 +### 数据类型优化 #### 数据类型优化基本原则 @@ -38,16 +38,16 @@ permalink: /pages/396816/ - 应该尽量避免用字符串类型作为标识列,因为它们很消耗空间,并且通常比数字类型慢。对于 `MD5`、`SHA`、`UUID` 这类随机字符串,由于比较随机,所以可能分布在很大的空间内,导致 `INSERT` 以及一些 `SELECT` 语句变得很慢。 - 如果存储 UUID ,应该移除 `-` 符号;更好的做法是,用 `UNHEX()` 函数转换 UUID 值为 16 字节的数字,并存储在一个 `BINARY(16)` 的列中,检索时,可以通过 `HEX()` 函数来格式化为 16 进制格式。 -### 1.2. 表设计 +### 表设计 应该避免的设计问题: - **太多的列** - 设计者为了图方便,将大量冗余列加入表中,实际查询中,表中很多列是用不到的。这种宽表模式设计,会造成不小的性能代价,尤其是 `ALTER TABLE` 非常耗时。 -- **太多的关联** - 所谓的实体 - 属性 - 值(EVA)设计模式是一个常见的糟糕设计模式。Mysql 限制了每个关联操作最多只能有 61 张表,但 EVA 模式需要许多自关联。 +- **太多的关联** - 所谓的实体 - 属性 - 值(EAV)设计模式是一个常见的糟糕设计模式。Mysql 限制了每个关联操作最多只能有 61 张表,但 EAV 模式需要许多自关联。 - **枚举** - 尽量不要用枚举,因为添加和删除字符串(枚举选项)必须使用 `ALTER TABLE`。 - 尽量避免 `NULL` -### 1.3. 范式和反范式 +### 范式和反范式 **范式化目标是尽量减少冗余,而反范式化则相反**。 @@ -63,7 +63,7 @@ permalink: /pages/396816/ 在真实世界中,很少会极端地使用范式化或反范式化。实际上,应该权衡范式和反范式的利弊,混合使用。 -### 1.4. 索引优化 +### 索引优化 > 索引优化应该是查询性能优化的最有效手段。 > @@ -92,7 +92,7 @@ permalink: /pages/396816/ - **覆盖索引** - **自增字段作主键** -## 2. SQL 优化 +## SQL 优化 使用 `EXPLAIN` 命令查看当前 SQL 是否使用了索引,优化后,再通过执行计划(`EXPLAIN`)来查看优化效果。 @@ -106,7 +106,7 @@ SQL 优化基本思路: - **使用索引来覆盖查询** -### 2.1. 优化 `COUNT()` 查询 +### 优化 `COUNT()` 查询 `COUNT()` 有两种作用: @@ -130,7 +130,7 @@ FROM world.city WHERE id <= 5; 有时候某些业务场景并不需要完全精确的统计值,可以用近似值来代替,`EXPLAIN` 出来的行数就是一个不错的近似值,而且执行 `EXPLAIN` 并不需要真正地去执行查询,所以成本非常低。通常来说,执行 `COUNT()` 都需要扫描大量的行才能获取到精确的数据,因此很难优化,MySQL 层面还能做得也就只有覆盖索引了。如果不还能解决问题,只有从架构层面解决了,比如添加汇总表,或者使用 Redis 这样的外部缓存系统。 -### 2.2. 优化关联查询 +### 优化关联查询 在大数据场景下,表与表之间通过一个冗余字段来关联,要比直接使用 `JOIN` 有更好的性能。 @@ -167,11 +167,11 @@ while(outer_row) { 可以看到,最外层的查询是根据`A.xx`列来查询的,`A.c`上如果有索引的话,整个关联查询也不会使用。再看内层的查询,很明显`B.c`上如果有索引的话,能够加速查询,因此只需要在关联顺序中的第二张表的相应列上创建索引即可。 -### 2.3. 优化 `GROUP BY` 和 `DISTINCT` +### 优化 `GROUP BY` 和 `DISTINCT` Mysql 优化器会在内部处理的时候相互转化这两类查询。它们都**可以使用索引来优化,这也是最有效的优化方法**。 -### 2.4. 优化 `LIMIT` +### 优化 `LIMIT` 当需要分页操作时,通常会使用 `LIMIT` 加上偏移量的办法实现,同时加上合适的 `ORDER BY` 字句。**如果有对应的索引,通常效率会不错,否则,MySQL 需要做大量的文件排序操作**。 @@ -204,13 +204,13 @@ SELECT id FROM t WHERE id > 10000 LIMIT 10; 其他优化的办法还包括使用预先计算的汇总表,或者关联到一个冗余表,冗余表中只包含主键列和需要做排序的列。 -### 2.5. 优化 UNION +### 优化 UNION MySQL 总是通过创建并填充临时表的方式来执行 `UNION` 查询。因此很多优化策略在`UNION`查询中都没有办法很好的时候。经常需要手动将`WHERE`、`LIMIT`、`ORDER BY`等字句“下推”到各个子查询中,以便优化器可以充分利用这些条件先优化。 除非确实需要服务器去重,否则就一定要使用`UNION ALL`,如果没有`ALL`关键字,MySQL 会给临时表加上`DISTINCT`选项,这会导致整个临时表的数据做唯一性检查,这样做的代价非常高。当然即使使用 ALL 关键字,MySQL 总是将结果放入临时表,然后再读出,再返回给客户端。虽然很多时候没有这个必要,比如有时候可以直接把每个子查询的结果返回给客户端。 -### 2.6. 优化查询方式 +### 优化查询方式 #### 切分大查询 @@ -251,7 +251,7 @@ SELECT * FROM tag_post WHERE tag_id=1234; SELECT * FROM post WHERE post.id IN (123,456,567,9098,8904); ``` -## 3. 执行计划(`EXPLAIN`) +## 执行计划(`EXPLAIN`) 如何判断当前 SQL 是否使用了索引?如何检验修改后的 SQL 确实有优化效果? @@ -304,7 +304,7 @@ possible_keys: PRIMARY > 更多内容请参考:[MySQL 性能优化神器 Explain 使用分析](https://segmentfault.com/a/1190000008131735) -## 4. optimizer trace +## optimizer trace 在 MySQL 5.6 及之后的版本中,我们可以使用 optimizer trace 功能查看优化器生成执行计划的整个过程。有了这个功能,我们不仅可以了解优化器的选择过程,更可以了解每一个执行环节的成本,然后依靠这些信息进一步优化查询。 @@ -317,14 +317,14 @@ SELECT * FROM information_schema.OPTIMIZER_TRACE; SET optimizer_trace="enabled=off"; ``` -## 5. 数据模型和业务 +## 数据模型和业务 - 表字段比较复杂、易变动、结构难以统一的情况下,可以考虑使用 Nosql 来代替关系数据库表存储,如 ElasticSearch、MongoDB。 - 在高并发情况下的查询操作,可以使用缓存(如 Redis)代替数据库操作,提高并发性能。 - 数据量增长较快的表,需要考虑水平分表或分库,避免单表操作的性能瓶颈。 - 除此之外,我们应该通过一些优化,尽量避免比较复杂的 JOIN 查询操作,例如冗余一些字段,减少 JOIN 查询;创建一些中间表,减少 JOIN 查询。 -## 6. 参考资料 +## 参考资料 - [《高性能 MySQL》](https://book.douban.com/subject/23008813/) - [《Java 性能调优实战》](https://time.geekbang.org/column/intro/100028001) diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/20.Mysql\350\277\220\347\273\264.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/20.Mysql\350\277\220\347\273\264.md" index a22d9d27..6550078b 100644 --- "a/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/20.Mysql\350\277\220\347\273\264.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/20.Mysql\350\277\220\347\273\264.md" @@ -17,9 +17,9 @@ permalink: /pages/e33b92/ > 如果你的公司有 DBA,那么我恭喜你,你可以无视 Mysql 运维。如果你的公司没有 DBA,那你就好好学两手 Mysql 基本运维操作,行走江湖,防身必备。 -## 1. 安装部署 +## 安装部署 -### 1.1. Windows 安装 +### Windows 安装 (1)下载 Mysql 5.7 免安装版 @@ -66,7 +66,7 @@ mysqld -install 在控制台执行 `net start mysql` 启动服务。 -### 1.2. CentOS 安装 +### CentOS 安装 > 本文仅介绍 rpm 安装方式 @@ -156,7 +156,7 @@ systemctl restart mysqld systemctl stop mysqld ``` -### 1.3. 初始化数据库密码 +### 初始化数据库密码 查看一下初始密码 @@ -179,7 +179,7 @@ ALTER user 'root'@'localhost' IDENTIFIED BY '你的密码'; 注:密码强度默认为中等,大小写字母、数字、特殊符号,只有修改成功后才能修改配置再设置更简单的密码 -### 1.4. 配置远程访问 +### 配置远程访问 ```sql CREATE USER 'root'@'%' IDENTIFIED BY '你的密码'; @@ -188,7 +188,7 @@ ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '你的密码'; FLUSH PRIVILEGES; ``` -### 1.5. 跳过登录认证 +### 跳过登录认证 ```shell vim /etc/my.cnf @@ -200,9 +200,9 @@ vim /etc/my.cnf 执行 `systemctl restart mysqld`,重启 mysql -## 2. 基本运维 +## 基本运维 -### 2.1. 客户端连接 +### 客户端连接 语法:`mysql -h<主机> -P<端口> -u<用户名> -p<密码>` @@ -228,13 +228,13 @@ Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. mysql> ``` -### 2.2. 查看连接 +### 查看连接 连接完成后,如果你没有后续的动作,这个连接就处于空闲状态,你可以在 `show processlist` 命令中看到它。客户端如果太长时间没动静,连接器就会自动将它断开。这个时间是由参数 `wait_timeout` 控制的,默认值是 8 小时。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200714115031.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200714115031.png) -### 2.3. 创建用户 +### 创建用户 ```sql CREATE USER 'username'@'host' IDENTIFIED BY 'password'; @@ -260,7 +260,7 @@ CREATE USER 'pig'@'%'; > > 所以,需要加上 `IDENTIFIED WITH mysql_native_password`,例如:`CREATE USER 'slave'@'%' IDENTIFIED WITH mysql_native_password BY '123456';` -### 2.4. 查看用户 +### 查看用户 ```sql -- 查看所有用户 @@ -268,7 +268,7 @@ SELECT DISTINCT CONCAT('User: ''', user, '''@''', host, ''';') AS query FROM mysql.user; ``` -### 2.5. 授权 +### 授权 命令: @@ -301,7 +301,7 @@ GRANT privileges ON databasename.tablename TO 'username'@'host' WITH GRANT OPTIO GRANT ALL ON *.* TO 'root'@'%' IDENTIFIED BY '密码' WITH GRANT OPTION; ``` -### 2.6. 撤销授权 +### 撤销授权 命令: @@ -325,14 +325,14 @@ REVOKE SELECT ON *.* FROM 'pig'@'%'; 具体信息可以用命令`SHOW GRANTS FOR 'pig'@'%';` 查看。 -### 2.7. 查看授权 +### 查看授权 ```SQL -- 查看用户权限 SHOW GRANTS FOR 'root'@'%'; ``` -### 2.8. 更改用户密码 +### 更改用户密码 ```sql SET PASSWORD FOR 'username'@'host' = PASSWORD('newpassword'); @@ -350,7 +350,7 @@ SET PASSWORD = PASSWORD("newpassword"); SET PASSWORD FOR 'pig'@'%' = PASSWORD("123456"); ``` -### 2.9. 备份与恢复 +### 备份与恢复 Mysql 备份数据使用 mysqldump 命令。 @@ -401,7 +401,7 @@ mysql -h -P -u -p < backup.sql mysql -u -p --all-databases < backup.sql ``` -### 2.10. 卸载 +### 卸载 (1)查看已安装的 mysql @@ -421,7 +421,7 @@ mysql-community-libs-8.0.12-1.el7.x86_64 yum remove mysql-community-server.x86_64 ``` -### 2.11. 主从节点部署 +### 主从节点部署 假设需要配置一个主从 Mysql 服务器环境 @@ -627,13 +627,57 @@ mysql> show global variables like "%read_only%"; > 注:设置 slave 服务器为只读,并不影响主从同步。 -## 3. 服务器配置 +### 慢查询 + +查看慢查询是否开启 + +```sql +show variables like '%slow_query_log'; +``` + +可以通过 `set global slow_query_log` 命令设置慢查询是否开启:ON 表示开启;OFF 表示关闭。 + +```sql +set global slow_query_log='ON'; +``` + +查看慢查询时间阈值 + +```sql +show variables like '%long_query_time%'; +``` + +设置慢查询阈值 + +```sql +set global long_query_time = 3; +``` + +### 隔离级别 + +查看隔离级别: + +```sql +mysql> show variables like 'transaction_isolation'; + ++-----------------------+----------------+ + +| Variable_name | Value | + ++-----------------------+----------------+ + +| transaction_isolation | READ-COMMITTED | + ++-----------------------+----------------+ +``` + +## 服务器配置 > **_大部分情况下,默认的基本配置已经足够应付大多数场景,不要轻易修改 Mysql 服务器配置,除非你明确知道修改项是有益的。_** > > 尽量不要使用 Mysql 的缓存功能,因为其要求每次请求参数完全相同,才能命中缓存。这种方式实际上并不高效,还会增加额外开销,实际业务场景中一般使用 Redis 等 key-value 存储来解决缓存问题,性能远高于 Mysql 的查询缓存。 -### 3.1. 配置文件路径 +### 配置文件路径 配置 Mysql 首先要确定配置文件在哪儿。 @@ -649,7 +693,7 @@ Default options are read from the following files in the given order: /etc/my.cnf /etc/mysql/my.cnf /usr/etc/my.cnf ~/.my.cnf ``` -### 3.2. 配置项语法 +### 配置项语法 **Mysql 配置项设置都使用小写,单词之间用下划线或横线隔开(二者是等价的)。** @@ -661,7 +705,7 @@ Default options are read from the following files in the given order: /usr/sbin/mysqld --auto_increment_offset=5 ``` -### 3.3. 常用配置项说明 +### 常用配置项说明 > 这里介绍比较常用的基本配置,更多配置项说明可以参考:[Mysql 服务器配置说明](21.Mysql配置.md) @@ -760,9 +804,9 @@ port = 3306 - 注意:仍然可能出现报错信息 Can't create a new thread;此时观察系统 `cat /proc/mysql` 进程号/limits,观察进程 ulimit 限制情况 - 过小的话,考虑修改系统配置表,`/etc/security/limits.conf` 和 `/etc/security/limits.d/90-nproc.conf` -## 4. 常见问题 +## 常见问题 -### 4.1. Too many connections +### Too many connections **现象** @@ -833,7 +877,7 @@ mysql soft nofile 65535 如果是使用 rpm 方式安装 mysql,检查 **mysqld.service** 文件中的 `LimitNOFILE` 是否配置的太小。 -### 4.2. 时区(time_zone)偏差 +### 时区(time_zone)偏差 **现象** @@ -874,7 +918,7 @@ Query OK, 0 rows affected (0.00 sec) 修改 `my.cnf` 文件,在 `[mysqld]` 节下增加 `default-time-zone='+08:00'` ,然后重启。 -### 4.3. 数据表损坏如何修复 +### 数据表损坏如何修复 使用 myisamchk 来修复,具体步骤: @@ -884,7 +928,7 @@ Query OK, 0 rows affected (0.00 sec) 使用 repair table 或者 OPTIMIZE table 命令来修复,REPAIR TABLE table_name 修复表 OPTIMIZE TABLE table_name 优化表 REPAIR TABLE 用于修复被破坏的表。 OPTIMIZE TABLE 用于回收闲置的数据库空间,当表上的数据行被删除时,所占据的磁盘空间并没有立即被回收,使用了 OPTIMIZE TABLE 命令后这些空间将被回收,并且对磁盘上的数据行进行重排(注意:是磁盘上,而非数据库) -### 4.4. 数据结构 +### 数据结构 > 问题现象:ERROR 1071: Specified key was too long; max key length is 767 bytes @@ -892,14 +936,14 @@ Query OK, 0 rows affected (0.00 sec) 解决方法:优化索引结构,索引字段不宜过长。 -## 5. 脚本 +## 脚本 这里推荐我写的几个一键运维脚本,非常方便,欢迎使用: - [Mysql 安装脚本](https://github.com/dunwu/linux-tutorial/tree/master/codes/linux/soft/mysql-install.sh) - [Mysql 备份脚本](https://github.com/dunwu/linux-tutorial/tree/master/codes/linux/soft/mysql-backup.sh) -## 6. 参考资料 +## 参考资料 - [《高性能 MySQL》](https://book.douban.com/subject/23008813/) - https://www.cnblogs.com/xiaopotian/p/8196464.html @@ -909,4 +953,4 @@ Query OK, 0 rows affected (0.00 sec) - https://www.cnblogs.com/xyabk/p/8967990.html - [MySQL 8.0 主从(Master-Slave)配置](https://blog.csdn.net/zyhlwzy/article/details/80569422) - [Mysql 主从同步实战](https://juejin.im/post/58eb5d162f301e00624f014a) -- [MySQL 备份和恢复机制](https://juejin.im/entry/5a0aa2026fb9a045132a369f) +- [MySQL 备份和恢复机制](https://juejin.im/entry/5a0aa2026fb9a045132a369f) \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/21.Mysql\351\205\215\347\275\256.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/21.Mysql\351\205\215\347\275\256.md" index 81aad76e..d558d801 100644 --- "a/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/21.Mysql\351\205\215\347\275\256.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/21.Mysql\351\205\215\347\275\256.md" @@ -17,7 +17,7 @@ permalink: /pages/5da42d/ > 版本:![mysql](https://img.shields.io/badge/mysql-8.0-blue) -## 1. 基本配置 +## 基本配置 ```ini [mysqld] @@ -62,7 +62,7 @@ socket = /var/lib/mysql/mysql.sock port = 3306 ``` -## 2. 配置项说明 +## 配置项说明 ```ini [client] @@ -260,7 +260,8 @@ max_binlog_size = 1000M # 关于 binlog 日志格式问题,请查阅网络资料 binlog_format = row -# 默认值 N=1,使 binlog 在每 N 次 binlog 写入后与硬盘同步,ps:1 最慢 +# 表示每 N 次写入 binlog 后,持久化到磁盘,默认值 N=1 +# 建议设置成 1,这样可以保证 MySQL 异常重启之后 binlog 不丢失。 # sync_binlog = 1 # MyISAM 引擎配置 @@ -419,8 +420,9 @@ innodb_flush_log_at_timeout = 1 # 说明:参数可设为 0,1,2; # 参数 0:表示每秒将 log buffer 内容刷新到系统 buffer 中,再调用系统 flush 操作写入磁盘文件。 -# 参数 1:表示每次事物提交,将 log buffer 内容刷新到系统 buffer 中,再调用系统 flush 操作写入磁盘文件。 -# 参数 2:表示每次事物提交,将 log buffer 内容刷新到系统 buffer 中,隔 1 秒后再调用系统 flush 操作写入磁盘文件。 +# 参数 1:表示每次事务提交,redo log 都直接持久化到磁盘。 +# 参数 2:表示每次事务提交,隔 1 秒后再将 redo log 持久化到磁盘。 +# 建议设置成 1,这样可以保证 MySQL 异常重启之后数据不丢失。 innodb_flush_log_at_trx_commit = 1 # 说明:限制 Innodb 能打开的表的数据,如果库里的表特别多的情况,请增加这个。 @@ -484,7 +486,7 @@ auto-rehash socket = /var/lib/mysql/mysql.sock ``` -## 3. 参考资料 +## 参考资料 - [《高性能 MySQL》](https://item.jd.com/11220393.html) -- [Mysql 配置文件/etc/my.cnf 解析](https://www.jianshu.com/p/5f39c486561b) +- [Mysql 配置文件/etc/my.cnf 解析](https://www.jianshu.com/p/5f39c486561b) \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/99.Mysql\345\270\270\350\247\201\351\227\256\351\242\230.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/99.Mysql\345\270\270\350\247\201\351\227\256\351\242\230.md" index 1dc7b314..02facdb9 100644 --- "a/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/99.Mysql\345\270\270\350\247\201\351\227\256\351\242\230.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/99.Mysql\345\270\270\350\247\201\351\227\256\351\242\230.md" @@ -17,7 +17,7 @@ permalink: /pages/7b0caf/ > **📦 本文以及示例源码已归档在 [db-tutorial](https://github.com/dunwu/db-tutorial/)** -## 1. 为什么表数据删掉一半,表文件大小不变 +## 为什么表数据删掉一半,表文件大小不变 【问题】数据库占用空间太大,我把一个最大的表删掉了一半的数据,怎么表文件的大小还是没变? @@ -43,7 +43,7 @@ permalink: /pages/7b0caf/ 要达到收缩空洞的目的,可以使用重建表的方式。 -## 2. 参考资料 +## 参考资料 - [《高性能 MySQL》](https://book.douban.com/subject/23008813/) -- [MySQL 实战 45 讲](https://time.geekbang.org/column/intro/139) +- [MySQL 实战 45 讲](https://time.geekbang.org/column/intro/139) \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/README.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/README.md" index 792f40f5..076d5964 100644 --- "a/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/README.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/README.md" @@ -15,7 +15,7 @@ hidden: true # Mysql 教程 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200716103611.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200716103611.png) ## 📖 内容 @@ -27,19 +27,19 @@ hidden: true > 关键词:`ACID`、`AUTOCOMMIT`、`事务隔离级别`、`死锁`、`分布式事务` -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200716074533.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220721072721.png) ### [Mysql 锁](04.Mysql锁.md) > 关键词:`乐观锁`、`表级锁`、`行级锁`、`意向锁`、`MVCC`、`Next-key 锁` -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200716064947.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200716064947.png) ### [Mysql 索引](05.Mysql索引.md) > 关键词:`Hash`、`B 树`、`聚簇索引`、`回表` -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200715172009.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200715172009.png) ### [Mysql 性能优化](06.Mysql性能优化.md) @@ -49,14 +49,6 @@ hidden: true ### [Mysql 常见问题](99.Mysql常见问题) ---- - -相关扩展知识: - -- [关系型数据库面试总结](https://github.com/dunwu/db-tutorial/blob/master/docs/sql/sql-interview.md) 💯 -- [SQL Cheat Sheet](https://github.com/dunwu/db-tutorial/blob/master/docs/sql/sql-cheat-sheet.md) -- [分布式事务基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/theory/distributed-transaction.md) - ## 📚 资料 - **官方** @@ -75,4 +67,4 @@ hidden: true ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/99.\345\205\266\344\273\226/01.PostgreSQL.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/99.\345\205\266\344\273\226/01.PostgreSQL.md" index a4f38be6..61c81ca0 100644 --- "a/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/99.\345\205\266\344\273\226/01.PostgreSQL.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/99.\345\205\266\344\273\226/01.PostgreSQL.md" @@ -18,7 +18,7 @@ permalink: /pages/52609d/ > > 关键词:Database, RDBM, psql -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20180920181010182614.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20180920181010182614.png) ## 安装 @@ -28,7 +28,7 @@ permalink: /pages/52609d/ 官方下载页面要求用户选择相应版本,然后动态的给出安装提示,如下图所示: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20180920181010174348.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20180920181010174348.png) 前 3 步要求用户选择,后 4 步是根据选择动态提示的安装步骤 @@ -196,4 +196,4 @@ psql -h 127.0.0.1 -U user_name db_name < dump.sql ## :door: 传送门 -| [钝悟的博客](https://dunwu.github.io/blog/) | [db-tutorial 首页](https://github.com/dunwu/db-tutorial) | +| [钝悟的博客](https://dunwu.github.io/blog/) | [db-tutorial 首页](https://github.com/dunwu/db-tutorial) | \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/99.\345\205\266\344\273\226/02.H2.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/99.\345\205\266\344\273\226/02.H2.md" index 47ca4251..7073f93a 100644 --- "a/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/99.\345\205\266\344\273\226/02.H2.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/99.\345\205\266\344\273\226/02.H2.md" @@ -28,11 +28,11 @@ H2 允许用户通过浏览器接口方式访问 SQL 数据库。 2. 启动方式:在 bin 目录下,双击 jar 包;执行 `java -jar h2*.jar`;执行脚本:`h2.bat` 或 `h2.sh`。 3. 在浏览器中访问:`http://localhost:8082`,应该可以看到下图中的页面: -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/h2/h2-console.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/database/h2/h2-console.png) 点击 **Connect** ,可以进入操作界面: -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/h2/h2-console-02.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/database/h2/h2-console-02.png) 操作界面十分简单,不一一细说。 @@ -471,4 +471,4 @@ H2 可以通过 CreateCluster 工具创建集群,示例步骤如下(在在 ## :door: 传送门 -| [钝悟的博客](https://dunwu.github.io/blog/) | [db-tutorial 首页](https://github.com/dunwu/db-tutorial) | +| [钝悟的博客](https://dunwu.github.io/blog/) | [db-tutorial 首页](https://github.com/dunwu/db-tutorial) | \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/99.\345\205\266\344\273\226/03.Sqlite.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/99.\345\205\266\344\273\226/03.Sqlite.md" index ca191c63..bcc3b9c1 100644 --- "a/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/99.\345\205\266\344\273\226/03.Sqlite.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/99.\345\205\266\344\273\226/03.Sqlite.md" @@ -14,10 +14,16 @@ permalink: /pages/bdcd7e/ # SQLite -> SQLite 是一个实现了自给自足的、无服务器的、零配置的、事务性的 SQL 数据库引擎。 -> :point_right: [完整示例源码](https://github.com/dunwu/db-tutorial/tree/master/codes/javadb/javadb-sqlite) +> SQLite 是一个无服务器的、零配置的、事务性的的开源数据库引擎。 +> 💻 [完整示例源码](https://github.com/dunwu/db-tutorial/tree/master/codes/javadb/javadb-sqlite) -## 简介 +## SQLite 简介 + +SQLite 是一个C语言编写的轻量级、全功能、无服务器、零配置的的开源数据库引擎。 + +SQLite 的设计目标是嵌入式的数据库,很多嵌入式产品中都使用了它。SQLite 十分轻量,占用资源非常的低,在嵌入式设备中,可能只需要几百K的内存就够了。SQLite 能够支持Windows/Linux/Unix等等主流的操作系统,同时能够跟很多程序语言相结合,同样比起Mysql、PostgreSQL这两款开源的世界著名数据库管理系统来讲,它的处理速度比他们都快。 + +SQLite 大小只有 3M 左右,可以将整个 SQLite 嵌入到应用中,而不用采用传统的客户端/服务器(Client/Server)的架构。这样做的好处就是非常轻便,在许多智能设备和应用中都可以使用 SQLite,比如微信就采用了 SQLite 作为本地聊天记录的存储。 ### 优点 @@ -47,11 +53,11 @@ Sqlite 可在 UNIX(Linux, Mac OS-X, Android, iOS)和 Windows(Win32, WinCE, 一般,Linux 和 Mac 上会预安装 sqlite。如果没有安装,可以在[官方下载地址](https://www.sqlite.org/download.html)下载合适安装版本,自行安装。 -## 语法 +## SQLite 语法 > 这里不会详细列举所有 SQL 语法,仅列举 SQLite 除标准 SQL 以外的,一些自身特殊的 SQL 语法。 > -> :point_right: 扩展阅读:[标准 SQL 基本语法](https://github.com/dunwu/blog/blob/master/docs/database/sql/sql.md) +> 📖 扩展阅读:[标准 SQL 基本语法](https://github.com/dunwu/blog/blob/master/docs/database/sql/sql.md) ### 大小写敏感 @@ -143,7 +149,7 @@ sqlite3 test.db .dump > /home/test.sql sqlite3 test.db < test.sql ``` -## 数据类型 +## SQLite 数据类型 SQLite 使用一个更普遍的动态类型系统。在 SQLite 中,值的数据类型与值本身是相关的,而不是与它的容器相关。 @@ -296,7 +302,7 @@ goodbye|20 $ ``` -## JAVA Client +## SQLite JAVA Client (1)在[官方下载地址](https://bitbucket.org/xerial/sqlite-jdbc/downloads)下载 sqlite-jdbc-(VERSION).jar ,然后将 jar 包放在项目中的 classpath。 @@ -377,6 +383,7 @@ Connection connection = DriverManager.getConnection("jdbc:sqlite::memory:"); ## 参考资料 - [SQLite 官网](https://www.sqlite.org/index.html) +- [SQLite Github](https://github.com/sqlite/sqlite) - [SQLite 官方文档](https://www.sqlite.org/docs.html) - [SQLite 官方命令行手册](https://www.sqlite.org/cli.html) - http://www.runoob.com/sqlite/sqlite-commands.html @@ -385,4 +392,4 @@ Connection connection = DriverManager.getConnection("jdbc:sqlite::memory:"); ## :door: 传送门 -| [钝悟的博客](https://dunwu.github.io/blog/) | [db-tutorial 首页](https://github.com/dunwu/db-tutorial) | +| [钝悟的博客](https://dunwu.github.io/blog/) | [db-tutorial 首页](https://github.com/dunwu/db-tutorial) | \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/99.\345\205\266\344\273\226/README.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/99.\345\205\266\344\273\226/README.md" index e078d164..cc289d7b 100644 --- "a/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/99.\345\205\266\344\273\226/README.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/99.\345\205\266\344\273\226/README.md" @@ -24,4 +24,4 @@ hidden: true ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/README.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/README.md" index c143642c..6d0e15db 100644 --- "a/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/README.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/README.md" @@ -15,15 +15,17 @@ hidden: true ## 📖 内容 -### 公共知识 +### 关系型数据库综合 - [关系型数据库面试总结](01.综合/01.关系型数据库面试.md) 💯 -- [SQL Cheat Sheet](01.综合/02.SqlCheatSheet.md) 是一个 SQL 入门教程。 -- [扩展 SQL](01.综合/03.扩展SQL.md) 是一个 SQL 入门教程。 +- [SQL 语法基础特性](01.综合/02.SQL语法基础特性.md) +- [SQL 语法高级特性](01.综合/03.SQL语法高级特性.md) +- [扩展 SQL](01.综合/03.扩展SQL.md) +- [SQL Cheat Sheet](01.综合/99.SqlCheatSheet.md) ### Mysql -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200716103611.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200716103611.png) - [Mysql 应用指南](02.Mysql/01.Mysql应用指南.md) ⚡ - [Mysql 工作流](02.Mysql/02.MySQL工作流.md) - 关键词:`连接`、`缓存`、`语法分析`、`优化`、`执行引擎`、`redo log`、`bin log`、`两阶段提交` @@ -46,7 +48,7 @@ hidden: true ### 综合 - [《数据库的索引设计与优化》](https://book.douban.com/subject/26419771/) -- [《SQL 必知必会》](https://book.douban.com/subject/35167240/) - SQL 的基本概念和语法【入门】 +- [《SQL 必知必会》](https://book.douban.com/subject/35167240/) - SQL 入门经典 ### Mysql @@ -56,7 +58,7 @@ hidden: true - [Mysql 官方文档之命令行客户端](https://dev.mysql.com/doc/refman/8.0/en/mysql.html) - **书籍** - [《高性能 MySQL》](https://book.douban.com/subject/23008813/) - 经典,适合 DBA 或作为开发者的参考手册 - - [《MySQL 必知必会》](https://book.douban.com/subject/3354490/) - 适合入门者 + - [《MySQL 必知必会》](https://book.douban.com/subject/3354490/) - MySQL 入门经典 - **教程** - [MySQL 实战 45 讲](https://time.geekbang.org/column/intro/139) - [runoob.com MySQL 教程](http://www.runoob.com/mysql/mysql-tutorial.html) @@ -70,4 +72,4 @@ hidden: true ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/01.MongoDB\345\272\224\347\224\250\346\214\207\345\215\227.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/01.MongoDB\345\272\224\347\224\250\346\214\207\345\215\227.md" index 7c024fd0..78856a46 100644 --- "a/docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/01.MongoDB\345\272\224\347\224\250\346\214\207\345\215\227.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/01.MongoDB\345\272\224\347\224\250\346\214\207\345\215\227.md" @@ -666,4 +666,4 @@ db.<集合>.aggregate(pipeline, {options}); - [MongoDB 官网](https://www.mongodb.com/) - [MongoDB Github](https://github.com/mongodb/mongo) -- [MongoDB 教程](https://www.runoob.com/mongodb/mongodb-tutorial.html) +- [MongoDB 教程](https://www.runoob.com/mongodb/mongodb-tutorial.html) \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/02.MongoDB\347\232\204CRUD\346\223\215\344\275\234.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/02.MongoDB\347\232\204CRUD\346\223\215\344\275\234.md" index 69a151bc..e4207ac9 100644 --- "a/docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/02.MongoDB\347\232\204CRUD\346\223\215\344\275\234.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/02.MongoDB\347\232\204CRUD\346\223\215\344\275\234.md" @@ -27,7 +27,7 @@ MongoDB 提供以下操作向一个 collection 插入 document > 注:以上操作都是原子操作。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200924112342.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200924112342.svg) 插入操作的特性: @@ -76,7 +76,7 @@ db.inventory.insertMany([ MongoDB 提供 [`db.collection.find()`](https://docs.mongodb.com/manual/reference/method/db.collection.find/#db.collection.find) 方法来检索 document。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200924113832.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200924113832.svg) ### Update 操作 @@ -92,7 +92,7 @@ MongoDB 提供以下操作来更新 collection 中的 document - [`db.collection.updateMany(, , )`](https://docs.mongodb.com/manual/reference/method/db.collection.updateMany/#db.collection.updateMany) - [`db.collection.replaceOne(, , )`](https://docs.mongodb.com/manual/reference/method/db.collection.replaceOne/#db.collection.replaceOne) -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200924114043.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200924114043.svg) 【示例】插入测试数据 @@ -201,7 +201,7 @@ MongoDB 提供以下操作来删除 collection 中的 document - [`db.collection.deleteOne()`](https://docs.mongodb.com/manual/reference/method/db.collection.deleteOne/#db.collection.deleteOne):删除一条 document - [`db.collection.deleteMany()`](https://docs.mongodb.com/manual/reference/method/db.collection.deleteMany/#db.collection.deleteMany):删除多条 document -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200924120007.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200924120007.svg) 删除操作的特性: @@ -327,4 +327,4 @@ try { - [MongoDB 官方免费教程](https://university.mongodb.com/) - **教程** - [MongoDB 教程](https://www.runoob.com/mongodb/mongodb-tutorial.html) - - [MongoDB 高手课](https://time.geekbang.org/course/intro/100040001) + - [MongoDB 高手课](https://time.geekbang.org/course/intro/100040001) \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/03.MongoDB\347\232\204\350\201\232\345\220\210\346\223\215\344\275\234.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/03.MongoDB\347\232\204\350\201\232\345\220\210\346\223\215\344\275\234.md" index 9fe17f8c..6c4d00a3 100644 --- "a/docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/03.MongoDB\347\232\204\350\201\232\345\220\210\346\223\215\344\275\234.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/03.MongoDB\347\232\204\350\201\232\345\220\210\346\223\215\344\275\234.md" @@ -29,7 +29,7 @@ MongoDB Pipeline 由多个阶段([stages](https://docs.mongodb.com/manual/refe 同一个阶段可以在 pipeline 中出现多次,但 [`$out`](https://docs.mongodb.com/manual/reference/operator/aggregation/out/#pipe._S_out)、[`$merge`](https://docs.mongodb.com/manual/reference/operator/aggregation/merge/#pipe._S_merge),和 [`$geoNear`](https://docs.mongodb.com/manual/reference/operator/aggregation/geoNear/#pipe._S_geoNear) 阶段除外。所有可用 pipeline 阶段可以参考:[Aggregation Pipeline Stages](https://docs.mongodb.com/manual/reference/operator/aggregation-pipeline/#aggregation-pipeline-operator-reference)。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200921092725.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200921092725.png) - 第一阶段:[`$match`](https://docs.mongodb.com/manual/reference/operator/aggregation/match/#pipe._S_match) 阶段按状态字段过滤 document,然后将状态等于“ A”的那些 document 传递到下一阶段。 - 第二阶段:[`$group`](https://docs.mongodb.com/manual/reference/operator/aggregation/group/#pipe._S_group) 阶段按 cust_id 字段对 document 进行分组,以计算每个唯一 cust_id 的金额总和。 @@ -239,7 +239,7 @@ Pipeline 的内存限制为 100 MB。 Map-reduce 是一种数据处理范式,用于将大量数据汇总为有用的聚合结果。为了执行 map-reduce 操作,MongoDB 提供了 [`mapReduce`](https://docs.mongodb.com/manual/reference/command/mapReduce/#dbcmd.mapReduce) 数据库命令。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200921155546.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200921155546.svg) 在上面的操作中,MongoDB 将 map 阶段应用于每个输入 document(即 collection 中与查询条件匹配的 document)。 map 函数分发出多个键-值对。对于具有多个值的那些键,MongoDB 应用 reduce 阶段,该阶段收集并汇总聚合的数据。然后,MongoDB 将结果存储在 collection 中。可选地,reduce 函数的输出可以通过 finalize 函数来进一步汇总聚合结果。 @@ -255,7 +255,7 @@ MongoDB 支持一下单一目的的聚合操作: 所有这些操作都汇总了单个 collection 中的 document。尽管这些操作提供了对常见聚合过程的简单访问,但是它们相比聚合 pipeline 和 map-reduce,缺少灵活性和丰富的功能性。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200921155935.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200921155935.svg) ## SQL 和 MongoDB 聚合对比 @@ -386,7 +386,7 @@ db.orders.insertMany([ SQL 和 MongoDB 聚合方式对比: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200921200556.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200921200556.png) ## 参考资料 @@ -396,4 +396,4 @@ SQL 和 MongoDB 聚合方式对比: - [MongoDB 官方免费教程](https://university.mongodb.com/) - **教程** - [MongoDB 教程](https://www.runoob.com/mongodb/mongodb-tutorial.html) - - [MongoDB 高手课](https://time.geekbang.org/course/intro/100040001) + - [MongoDB 高手课](https://time.geekbang.org/course/intro/100040001) \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/04.MongoDB\344\272\213\345\212\241.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/04.MongoDB\344\272\213\345\212\241.md" index a4c33204..527172f5 100644 --- "a/docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/04.MongoDB\344\272\213\345\212\241.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/04.MongoDB\344\272\213\345\212\241.md" @@ -41,4 +41,4 @@ conf=rs.conf() conf.memebers[2].slaveDelay=5 conf.memebers[2].priority=0 rs.reconfig(conf) -``` +``` \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/05.MongoDB\345\273\272\346\250\241.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/05.MongoDB\345\273\272\346\250\241.md" index d4ff969b..5d3d082c 100644 --- "a/docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/05.MongoDB\345\273\272\346\250\241.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/05.MongoDB\345\273\272\346\250\241.md" @@ -120,7 +120,7 @@ This looks very different from the tabular data structure you started with in St 嵌入式 document 通过将相关数据存储在单个 document 结构中来捕获数据之间的关系。 MongoDB document 可以将 document 结构嵌入到另一个 document 中的字段或数组中。这些非规范化的数据模型允许应用程序在单个数据库操作中检索和操纵相关数据。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200910193231.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200910193231.png) 对于 MongoDB 中的很多场景,非规范化数据模型都是最佳的。 @@ -132,7 +132,7 @@ This looks very different from the tabular data structure you started with in St 引用通过包含从一个 document 到另一个 document 的链接或引用来存储数据之间的关系。 应用程序可以解析这些引用以访问相关数据。 广义上讲,这些是规范化的数据模型。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200910193234.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200910193234.png) 通常,在以下场景使用引用式的数据模型: @@ -384,4 +384,4 @@ MongoDB 允许这条操作执行,但是服务器会记录下告警信息。 - [MongoDB 官方免费教程](https://university.mongodb.com/) - **教程** - [MongoDB 教程](https://www.runoob.com/mongodb/mongodb-tutorial.html) - - [MongoDB 高手课](https://time.geekbang.org/course/intro/100040001) + - [MongoDB 高手课](https://time.geekbang.org/course/intro/100040001) \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/06.MongoDB\345\273\272\346\250\241\347\244\272\344\276\213.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/06.MongoDB\345\273\272\346\250\241\347\244\272\344\276\213.md" index 09f53d91..c6513321 100644 --- "a/docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/06.MongoDB\345\273\272\346\250\241\347\244\272\344\276\213.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/06.MongoDB\345\273\272\346\250\241\347\244\272\344\276\213.md" @@ -370,7 +370,7 @@ review collection 存储所有的评论 ## 树形结构模型 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200911194846.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200911194846.svg) ### 具有父节点的树形结构模型 @@ -524,7 +524,7 @@ db.categories.insertMany([ ### 具有嵌套集的树形结构模型 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200911204252.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200911204252.svg) ```javascript db.categories.insertMany([ @@ -553,7 +553,7 @@ db.categories.find({ 解决方案是:列转行 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200919225901.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200919225901.png) ### 管理文档不同版本 @@ -582,4 +582,4 @@ MongoDB 文档格式非常灵活,势必会带来版本维护上的难度。 ## 参考资料 -- [Data Model Examples and Patterns](https://docs.mongodb.com/manual/applications/data-models/) +- [Data Model Examples and Patterns](https://docs.mongodb.com/manual/applications/data-models/) \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/07.MongoDB\347\264\242\345\274\225.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/07.MongoDB\347\264\242\345\274\225.md" index 50880c33..b9b7cc5e 100644 --- "a/docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/07.MongoDB\347\264\242\345\274\225.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/07.MongoDB\347\264\242\345\274\225.md" @@ -27,7 +27,7 @@ permalink: /pages/10c674/ 索引是特殊的数据结构,索引存储在一个易于遍历读取的数据集合中,索引是对数据库表中一列或多列的值进行排序的一种结构。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200921210621.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200921210621.svg) ### createIndex() 方法 @@ -68,4 +68,4 @@ db.collection.createIndex( { name: -1 } ) - [MongoDB 官方免费教程](https://university.mongodb.com/) - **教程** - [MongoDB 教程](https://www.runoob.com/mongodb/mongodb-tutorial.html) - - [MongoDB 高手课](https://time.geekbang.org/course/intro/100040001) + - [MongoDB 高手课](https://time.geekbang.org/course/intro/100040001) \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/08.MongoDB\345\244\215\345\210\266.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/08.MongoDB\345\244\215\345\210\266.md" index 56035487..220f9a6d 100644 --- "a/docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/08.MongoDB\345\244\215\345\210\266.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/08.MongoDB\345\244\215\345\210\266.md" @@ -27,15 +27,15 @@ MongoDB 中的副本集是一组维护相同数据集的 mongod 进程。一个 **主节点负责接收所有写操作**。副本集只能有一个主副本,能够以 [`{ w: "majority" }`](https://docs.mongodb.com/manual/reference/write-concern/#writeconcern."majority") 来确认集群中节点的写操作成功情况;尽管在某些情况下,另一个 MongoDB 实例可能会暂时认为自己也是主要的。主节点在其操作日志(即 [oplog](https://docs.mongodb.com/manual/core/replica-set-oplog/))中记录了对其数据集的所有更改。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200920165054.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200920165054.svg) **从节点复制主节点的操作日志,并将操作应用于其数据集**,以便同步主节点的数据。如果主节点不可用,则符合条件的从节点将选举新的主节点。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200920165055.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200920165055.svg) 在某些情况下(例如,有一个主节点和一个从节点,但由于成本限制,禁止添加另一个从节点),您可以选择将 mongod 实例作为仲裁节点添加到副本集。仲裁节点参加选举但不保存数据(即不提供数据冗余)。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200920165053.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200920165053.svg) 仲裁节点将永远是仲裁节点。在选举期间,主节点可能会降级成为次节点,而次节点可能会升级成为主节点。 @@ -61,7 +61,7 @@ MongoDB 中的副本集是一组维护相同数据集的 mongod 进程。一个 当主节点与集群中的其他成员通信的时间超过配置的 `electionTimeoutMillis`(默认为 10 秒)时,符合选举要求的从节点将要求选举,并提名自己为新的主节点。集群尝试完成选举新主节点并恢复正常工作。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200920175429.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200920175429.svg) 选举完成前,副本集无法处理写入操作。如果将副本集配置为:在主节点处于脱机状态时,在次节点上运行,则副本集可以继续提供读取查询。 @@ -79,7 +79,7 @@ MongoDB 中的副本集是一组维护相同数据集的 mongod 进程。一个 默认情况下,客户端从主节点读取数据;但是,客户端可以指定读取首选项,以将读取操作发送到从节点。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200920204024.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200920204024.svg) 异步复制到从节点意味着向从节点读取数据可能会返回与主节点不一致的数据。 @@ -108,4 +108,4 @@ MongoDB 中的副本集是一组维护相同数据集的 mongod 进程。一个 - [MongoDB 官方免费教程](https://university.mongodb.com/) - **教程** - [MongoDB 教程](https://www.runoob.com/mongodb/mongodb-tutorial.html) - - [MongoDB 高手课](https://time.geekbang.org/course/intro/100040001) + - [MongoDB 高手课](https://time.geekbang.org/course/intro/100040001) \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/09.MongoDB\345\210\206\347\211\207.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/09.MongoDB\345\210\206\347\211\207.md" index 288e68a7..a0d437be 100644 --- "a/docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/09.MongoDB\345\210\206\347\211\207.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/09.MongoDB\345\210\206\347\211\207.md" @@ -37,7 +37,7 @@ MongoDB 分片集群含以下组件: - [mongos](https://docs.mongodb.com/manual/core/sharded-cluster-query-router/):mongos 充当查询路由器,在客户端应用程序和分片集群之间提供接口。从 MongoDB 4.4 开始,mongos 可以支持 [hedged reads](https://docs.mongodb.com/manual/core/sharded-cluster-query-router/#mongos-hedged-reads) 以最大程度地减少延迟。 - [config servers](https://docs.mongodb.com/manual/core/sharded-cluster-config-servers/):提供集群元数据存储和分片数据分布的映射。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200920210057.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200920210057.svg) ### 分片集群的分布 @@ -49,7 +49,7 @@ MongoDB 数据库可以同时包含分片和未分片的集合的 collection。 分片和未分片的 collection: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200920212159.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200920212159.svg) ### 路由节点 mongos @@ -57,7 +57,7 @@ MongoDB 数据库可以同时包含分片和未分片的集合的 collection。 连接 [`mongos`](https://docs.mongodb.com/manual/reference/program/mongos/#bin.mongos) 的方式和连接 [`mongod`](https://docs.mongodb.com/manual/reference/program/mongod/#bin.mongod) 相同,例如通过 [`mongo`](https://docs.mongodb.com/manual/reference/program/mongo/#bin.mongo) shell 或 [MongoDB 驱动程序](https://docs.mongodb.com/drivers/?jump=docs)。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200920212157.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200920212157.svg) 路由节点的作用: @@ -104,7 +104,7 @@ Hash 分片策略会先计算分片 Key 字段值的哈希值;然后,根据 > 注意:使用哈希索引解析查询时,MongoDB 会自动计算哈希值,应用程序不需要计算哈希。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200920213343.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200920213343.svg) 尽管分片 Key 范围可能是“接近”的,但它们的哈希值不太可能在同一 [chunk](https://docs.mongodb.com/manual/reference/glossary/#term-chunk) 上。基于 Hash 的数据分发有助于更均匀的数据分布,尤其是在分片 Key 单调更改的数据集中。 @@ -114,7 +114,7 @@ Hash 分片策略会先计算分片 Key 字段值的哈希值;然后,根据 范围分片根据分片 Key 值将数据划分为多个范围。然后,根据分片 Key 值为每个 [chunk](https://docs.mongodb.com/manual/reference/glossary/#term-chunk) 分配一个范围。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200920213345.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200920213345.svg) 值比较近似的一系列分片 Key 更有可能驻留在同一 [chunk](https://docs.mongodb.com/manual/reference/glossary/#term-chunk) 上。范围分片的效率取决于选择的分片 Key。分片 Key 考虑不周全会导致数据分布不均,这可能会削弱分片的某些优势或导致性能瓶颈。 @@ -126,7 +126,7 @@ Hash 分片策略会先计算分片 Key 字段值的哈希值;然后,根据 每个区域覆盖一个或多个分片 Key 值范围。区域覆盖的每个范围始终包括其上下边界。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200920214854.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200920214854.svg) 在定义要覆盖的区域的新范围时,必须使用分片 Key 中包含的字段。如果使用复合分片 Key,则范围必须包含分片 Key 的前缀。 @@ -140,4 +140,4 @@ Hash 分片策略会先计算分片 Key 字段值的哈希值;然后,根据 - [MongoDB 官方免费教程](https://university.mongodb.com/) - **教程** - [MongoDB 教程](https://www.runoob.com/mongodb/mongodb-tutorial.html) - - [MongoDB 高手课](https://time.geekbang.org/course/intro/100040001) + - [MongoDB 高手课](https://time.geekbang.org/course/intro/100040001) \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/20.MongoDB\350\277\220\347\273\264.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/20.MongoDB\350\277\220\347\273\264.md" index 84e29375..2f528495 100644 --- "a/docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/20.MongoDB\350\277\220\347\273\264.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/20.MongoDB\350\277\220\347\273\264.md" @@ -296,4 +296,4 @@ $ mongoexport -h 127.0.0.1 --port 27017 -d test -c product --type csv -f name,pr - [MongoDB 官网](https://www.mongodb.com/) - [MongoDB Github](https://github.com/mongodb/mongo) - [MongoDB 官方免费教程](https://university.mongodb.com/) -- [MongoDB 教程](https://www.runoob.com/mongodb/mongodb-tutorial.html) +- [MongoDB 教程](https://www.runoob.com/mongodb/mongodb-tutorial.html) \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/README.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/README.md" index 5b0fef52..cc6f228b 100644 --- "a/docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/README.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/README.md" @@ -59,4 +59,4 @@ hidden: true ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/01.Redis\351\235\242\350\257\225\346\200\273\347\273\223.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/01.Redis\351\235\242\350\257\225\346\200\273\347\273\223.md" index b1e5288b..be358ab9 100644 --- "a/docs/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/01.Redis\351\235\242\350\257\225\346\200\273\347\273\223.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/01.Redis\351\235\242\350\257\225\346\200\273\347\273\223.md" @@ -253,7 +253,7 @@ Redis 集群基于复制特性实现节点间的数据一致性。 由一个或多个 Sentinel 实例组成的 Sentinel 系统可以监视任意多个主服务器,以及这些主服务器的所有从服务器,并在被监视的主服务器进入下线状态时,自动将下线主服务器的某个从服务器升级为新的主服务器,然后由新的主服务器代替已下线的主服务器继续处理命令请求。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200131135847.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200131135847.png) ## Redis vs. Memcached @@ -288,4 +288,4 @@ Memcached 是多线程,非阻塞 IO 复用的网络模型;Redis 使用单线 ## 参考资料 - [面试中关于 Redis 的问题看这篇就够了](https://juejin.im/post/5ad6e4066fb9a028d82c4b66) -- [advanced-java](https://github.com/doocs/advanced-java#缓存) +- [advanced-java](https://github.com/doocs/advanced-java#缓存) \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/02.Redis\345\272\224\347\224\250\346\214\207\345\215\227.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/02.Redis\345\272\224\347\224\250\346\214\207\345\215\227.md" index 4836e299..417cfdc7 100644 --- "a/docs/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/02.Redis\345\272\224\347\224\250\346\214\207\345\215\227.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/02.Redis\345\272\224\347\224\250\346\214\207\345\215\227.md" @@ -184,7 +184,7 @@ Redis 基于 Reactor 模式开发了自己的网络时间处理器。 文件事件处理器有四个组成部分:套接字、I/O 多路复用程序、文件事件分派器、事件处理器。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200130172525.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200130172525.png) ### 时间事件 @@ -246,7 +246,7 @@ def main(): 从事件处理的角度来看,服务器运行流程如下:
- +
## 六、Redis 事务 @@ -508,4 +508,4 @@ redis 官方推荐的 Java Redis Client: - [redisson 官方文档(中文,略有滞后)](https://github.com/redisson/redisson/wiki/%E7%9B%AE%E5%BD%95) - [redisson 官方文档(英文)](https://github.com/redisson/redisson/wiki/Table-of-Content) - [CRUG | Redisson PRO vs. Jedis: Which Is Faster? 翻译](https://www.jianshu.com/p/82f0d5abb002) - - [redis 分布锁 Redisson 性能测试](https://blog.csdn.net/everlasting_188/article/details/51073505) + - [redis 分布锁 Redisson 性能测试](https://blog.csdn.net/everlasting_188/article/details/51073505) \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/03.Redis\346\225\260\346\215\256\347\261\273\345\236\213\345\222\214\345\272\224\347\224\250.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/03.Redis\346\225\260\346\215\256\347\261\273\345\236\213\345\222\214\345\272\224\347\224\250.md" index 108b2574..05ab7421 100644 --- "a/docs/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/03.Redis\346\225\260\346\215\256\347\261\273\345\236\213\345\222\214\345\272\224\347\224\250.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/03.Redis\346\225\260\346\215\256\347\261\273\345\236\213\345\222\214\345\272\224\347\224\250.md" @@ -21,7 +21,7 @@ permalink: /pages/ed757c/ ## 一、Redis 基本数据类型 -![Redis 数据类型](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200226113813.png) +![Redis 数据类型](https://raw.githubusercontent.com/dunwu/images/master/snap/20200226113813.png) | 数据类型 | 可以存储的值 | 操作 | | -------- | ---------------------- | ---------------------------------------------------------------------------------------------------------------- | @@ -36,7 +36,7 @@ permalink: /pages/ed757c/ ### STRING
- +
**适用场景:缓存、计数器、共享 Session** @@ -68,7 +68,7 @@ OK ### HASH
- +
**适用场景:存储结构化数据**,如一个对象:用户信息、产品信息等。 @@ -113,7 +113,7 @@ OK ### LIST
- +
**适用场景:用于存储列表型数据**。如:粉丝列表、商品列表等。 @@ -157,7 +157,7 @@ OK ### SET
- +
**适用场景:用于存储去重的列表型数据**。 @@ -203,7 +203,7 @@ OK ### ZSET
- +
适用场景:由于可以设置 score,且不重复。**适合用于存储各种排行数据**,如:按评分排序的有序商品集合、按时间排序的有序文章集合。 @@ -432,7 +432,7 @@ redis> PFCOUNT databases # 估计数量增一 使用 `HASH` 类型存储文章信息。其中:key 是文章 ID;field 是文章的属性 key;value 是属性对应值。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200225143038.jpg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200225143038.jpg) 操作: @@ -444,7 +444,7 @@ redis> PFCOUNT databases # 估计数量增一 使用 `ZSET` 类型分别存储按照时间排序和按照评分排序的文章 ID 集合。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200225145742.jpg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200225145742.jpg) 操作: @@ -454,7 +454,7 @@ redis> PFCOUNT databases # 估计数量增一 (3)为了防止重复投票,使用 `SET` 类型记录每篇文章 ID 对应的投票集合。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200225150105.jpg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200225150105.jpg) 操作: @@ -463,7 +463,7 @@ redis> PFCOUNT databases # 估计数量增一 (4)假设 user:115423 给 article:100408 投票,分别需要高更新评分排序集合以及投票集合。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200225150138.jpg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200225150138.jpg) 当需要对一篇文章投票时,程序需要用 ZSCORE 命令检查记录文章发布时间的有序集合,判断文章的发布时间是否超过投票有效期(比如:一星期)。 @@ -579,7 +579,7 @@ redis> PFCOUNT databases # 估计数量增一 取出群组里的文章: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200225214210.jpg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200225214210.jpg) - 通过对存储群组文章的集合和存储文章评分的有序集合执行 `ZINTERSTORE` 命令,可以得到按照文章评分排序的群组文章。 - 通过对存储群组文章的集合和存储文章发布时间的有序集合执行 `ZINTERSTORE` 命令,可以得到按照文章发布时间排序的群组文章。 @@ -1184,4 +1184,4 @@ SDIFF interviewee:002 job:003 - [Redis 命令参考](http://redisdoc.com/) - **文章** - [一看就懂系列之 详解 redis 的 bitmap 在亿级项目中的应用](https://blog.csdn.net/u011957758/article/details/74783347) - - [Fast, easy, realtime metrics using Redis bitmaps](http://blog.getspool.com/2011/11/29/fast-easy-realtime-metrics-using-redis-bitmaps/) + - [Fast, easy, realtime metrics using Redis bitmaps](http://blog.getspool.com/2011/11/29/fast-easy-realtime-metrics-using-redis-bitmaps/) \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/04.Redis\346\214\201\344\271\205\345\214\226.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/04.Redis\346\214\201\344\271\205\345\214\226.md" index 2314a08d..cac2b4c1 100644 --- "a/docs/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/04.Redis\346\214\201\344\271\205\345\214\226.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/04.Redis\346\214\201\344\271\205\345\214\226.md" @@ -90,7 +90,7 @@ RDB 文件是一个经过压缩的二进制文件,由多个部分组成。 对于不同类型(STRING、HASH、LIST、SET、SORTED SET)的键值对,RDB 文件会使用不同的方式来保存它们。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/redis/redis-rdb-structure.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/database/redis/redis-rdb-structure.png) Redis 本身提供了一个 RDB 文件检查工具 redis-check-dump。 @@ -170,7 +170,7 @@ AOF 载入过程如下: 6. 载入完毕。
- +
### AOF 的重写 @@ -192,7 +192,7 @@ AOF 重写并非读取和分析现有 AOF 文件的内容,而是直接从数 - 由于彼此不是在同一个进程中工作,AOF 重写不影响 AOF 写入和同步。当子进程完成创建新 AOF 文件的工作之后,服务器会将重写缓冲区中的所有内容追加到新 AOF 文件的末尾,使得新旧两个 AOF 文件所保存的数据库状态一致。 - 最后,服务器用新的 AOF 文件替换就的 AOF 文件,以此来完成 AOF 重写操作。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200130153716.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200130153716.png) 可以通过设置 `auto-aof-rewrite-percentage` 和 `auto-aof-rewrite-min-size`,使得 Redis 在满足条件时,自动执行 `BGREWRITEAOF`。 @@ -284,7 +284,7 @@ Redis 的容灾备份基本上就是对数据进行备份,并将这些备份 ## 五、要点总结 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200224214047.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200224214047.png) ## 参考资料 @@ -296,4 +296,4 @@ Redis 的容灾备份基本上就是对数据进行备份,并将这些备份 - [《Redis 实战》](https://item.jd.com/11791607.html) - [《Redis 设计与实现》](https://item.jd.com/11486101.html) - **教程** - - [Redis 命令参考](http://redisdoc.com/) + - [Redis 命令参考](http://redisdoc.com/) \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/05.Redis\345\244\215\345\210\266.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/05.Redis\345\244\215\345\210\266.md" index 62747e95..15c4756c 100644 --- "a/docs/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/05.Redis\345\244\215\345\210\266.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/05.Redis\345\244\215\345\210\266.md" @@ -19,7 +19,7 @@ permalink: /pages/379cd8/ > > Redis 2.8 以前的复制不能高效处理断线后重复制的情况,而 Redis 2.8 新添的部分重同步可以解决这个问题。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200712182603.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200712182603.png) ## 一、复制简介 @@ -56,7 +56,7 @@ Redis 的复制功能分为同步(sync)和命令传播(command propagate 3. 主服务器执行 `BGSAVE` 完毕后,主服务器会将生成的 RDB 文件发送给从服务器。从服务器接收并载入 RDB 文件,更新自己的数据库状态。 4. 主服务器将记录在缓冲区中的所有写命令发送给从服务器,从服务器执行这些写命令,更新自己的数据库状态。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200224220353.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200224220353.png) ### 命令传播 @@ -101,7 +101,7 @@ Redis 的复制功能分为同步(sync)和命令传播(command propagate - 如果主从服务器的复制偏移量相同,则说明二者的数据库状态一致; - 反之,则说明二者的数据库状态不一致。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/redis/redis-replication-offset.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/database/redis/redis-replication-offset.png) #### 复制积压缓冲区 @@ -148,7 +148,7 @@ Redis 的复制功能分为同步(sync)和命令传播(command propagate - 假如主从服务器的 **master run id 相同**,并且**指定的偏移量(offset)在内存缓冲区中还有效**,复制就会从上次中断的点开始继续。 - 如果其中一个条件不满足,就会进行完全重新同步(在 2.8 版本之前就是直接进行完全重新同步)。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/redis/redis-psync-workflow.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/database/redis/redis-psync-workflow.png) ## 四、心跳检测 @@ -295,4 +295,4 @@ REPLCONF ACK - [《Redis 实战》](https://item.jd.com/11791607.html) - [《Redis 设计与实现》](https://item.jd.com/11486101.html) - **教程** - - [Redis 命令参考](http://redisdoc.com/) + - [Redis 命令参考](http://redisdoc.com/) \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/06.Redis\345\223\250\345\205\265.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/06.Redis\345\223\250\345\205\265.md" index b4a001df..88e6b652 100644 --- "a/docs/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/06.Redis\345\223\250\345\205\265.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/06.Redis\345\223\250\345\205\265.md" @@ -17,15 +17,15 @@ permalink: /pages/615afe/ > Redis 哨兵(Sentinel)是 Redis 的**高可用性**(Hight Availability)解决方案。 > -> Redis 哨兵是 [Raft 算法](https://github.com/dunwu/blog/blob/master/source/_posts/theory/raft.md) 的具体实现。 +> Redis 哨兵是 [Raft 算法](https://dunwu.github.io/blog/pages/4907dc/) 的具体实现。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200713072747.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200713072747.png) ## 一、哨兵简介 Redis 哨兵(Sentinel)是 Redis 的**高可用性**(Hight Availability)解决方案:由一个或多个 Sentinel 实例组成的 Sentinel 系统可以监视任意多个主服务器,以及这些主服务器的所有从服务器,并在被监视的主服务器进入下线状态时,自动将下线主服务器的某个从服务器升级为新的主服务器,然后由新的主服务器代替已下线的主服务器继续处理命令请求。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200131135847.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200131135847.png) Sentinel 的主要功能如下: @@ -85,7 +85,7 @@ Sentinel 模式下 Redis 服务器只支持 `PING`、`SENTINEL`、`INFO`、`SUBS 对于每个与 Sentinel 连接的服务器,Sentinel 既会向服务器的 `__sentinel__:hello` 频道发送消息,也会订阅服务器的 `__sentinel__:hello` 频道的消息。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200131153842.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200131153842.png) ### 向服务器发送消息 @@ -107,7 +107,7 @@ Sentinel 对 `__sentinel__:hello` 频道的订阅会一直持续到 Sentinel 与 > Redis Sentinel 系统选举 Leader 的算法是 [Raft](https://ramcloud.atlassian.net/wiki/download/attachments/6586375/raft.pdf) 的实现。 > -> Raft 是一种共识性算法,想了解其原理,可以参考 [深入剖析共识性算法 Raft](https://github.com/dunwu/blog/blob/master/source/_posts/theory/raft.md)。 +> Raft 是一种共识性算法,想了解其原理,可以参考 [深入剖析共识性算法 Raft](https://dunwu.github.io/blog/pages/4907dc/)。 **当一个主服务器被判断为客观下线时,监视这个下线主服务器的各个 Sentinel 会进行协商,选举出一个领头的 Sentinel,并由领头 Sentinel 对下线主服务器执行故障转移操作**。 @@ -181,4 +181,4 @@ Sentinel Leader 将旧的主服务器标记为从服务器。当旧的主服务 - [Redis 命令参考](http://redisdoc.com/) - **文章** - [渐进式解析 Redis 源码 - 哨兵 sentinel](http://www.web-lovers.com/redis-source-sentinel.html) - - [深入剖析 Redis 系列(二) - Redis 哨兵模式与高可用集群](https://juejin.im/post/5b7d226a6fb9a01a1e01ff64) + - [深入剖析 Redis 系列(二) - Redis 哨兵模式与高可用集群](https://juejin.im/post/5b7d226a6fb9a01a1e01ff64) \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/07.Redis\351\233\206\347\276\244.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/07.Redis\351\233\206\347\276\244.md" index 0f53d616..c8e0a89c 100644 --- "a/docs/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/07.Redis\351\233\206\347\276\244.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/07.Redis\351\233\206\347\276\244.md" @@ -23,7 +23,7 @@ permalink: /pages/77dfbe/ > - Redis 集群采用主从模型,提供复制和故障转移功能,来保证 Redis 集群的高可用。 > - 根据 CAP 理论,Consistency、Availability、Partition tolerance 三者不可兼得,而 Redis 集群的选择是 AP。Redis 集群节点间采用异步通信方式,不保证强一致性,尽力达到最终一致性。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200713100613.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200713100613.png) ## 1. Redis Cluster 分区 @@ -99,7 +99,7 @@ Redis 集群的重新分区操作由 Redis 集群管理软件 **redis-trib** 负 重新分区的实现原理如下图所示: -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/redis/redis-cluster-trib.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/database/redis/redis-cluster-trib.png) ### 1.5. ASK 错误 @@ -107,7 +107,7 @@ Redis 集群的重新分区操作由 Redis 集群管理软件 **redis-trib** 负 判断 ASK 错误的过程如下图所示: -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/redis/redis-ask.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/database/redis/redis-ask.png) ## 2. Redis Cluster 故障转移 @@ -258,4 +258,4 @@ Redis Cluster 不太适合用于大规模集群,所以,如果要构建超大 - **文章** - [Redis 集群教程](http://ifeve.com/redis-cluster-tutorial/) - [Redis 集群的原理和搭建](https://www.jianshu.com/p/c869feb5581d) - - [深入剖析 Redis 系列(三) - Redis 集群模式搭建与原理详解](https://juejin.im/post/5b8fc5536fb9a05d2d01fb11) + - [深入剖析 Redis 系列(三) - Redis 集群模式搭建与原理详解](https://juejin.im/post/5b8fc5536fb9a05d2d01fb11) \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/08.Redis\345\256\236\346\210\230.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/08.Redis\345\256\236\346\210\230.md" index 8c5ec3b9..bc425f9a 100644 --- "a/docs/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/08.Redis\345\256\236\346\210\230.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/08.Redis\345\256\236\346\210\230.md" @@ -44,7 +44,7 @@ BitMap 和 BloomFilter 都可以用于解决缓存穿透问题。要点在于: - **避免永远不释放锁** - 使用 `expire` 加一个过期时间,避免一直不释放锁,导致阻塞。 - **原子性** - setnx 和 expire 必须合并为一个原子指令,避免 setnx 后,机器崩溃,没来得及设置 expire,从而导致锁永不释放。 -> 更多分布式锁的实现方式及细节,请参考:[分布式锁基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/theory/distributed-lock.md) +> 更多分布式锁的实现方式及细节,请参考:[分布式锁基本原理](https://dunwu.github.io/blog/pages/40ac64/) ## 二、技巧 @@ -74,4 +74,4 @@ BitMap 和 BloomFilter 都可以用于解决缓存穿透问题。要点在于: - **教程** - [Redis 命令参考](http://redisdoc.com/) - **文章** - - [《我们一起进大厂》系列- Redis 基础](https://juejin.im/post/5db66ed9e51d452a2f15d833) + - [《我们一起进大厂》系列- Redis 基础](https://juejin.im/post/5db66ed9e51d452a2f15d833) \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/20.Redis\350\277\220\347\273\264.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/20.Redis\350\277\220\347\273\264.md" index 65a2aac9..57ef46d0 100644 --- "a/docs/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/20.Redis\350\277\220\347\273\264.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/20.Redis\350\277\220\347\273\264.md" @@ -619,11 +619,11 @@ rebalance:表明让 Redis 自动根据节点数进行均衡哈希槽分配。 --cluster-use-empty-masters:表明 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200712125827.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200712125827.png) 执行结束后,查看状态: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200712130234.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200712130234.png) ## 四、Redis 命令 @@ -682,4 +682,4 @@ redis-cli --cluster reshard 172.22.6.3 7001 - **教程** - [Redis 命令参考](http://redisdoc.com/) - **文章** - - [深入剖析 Redis 系列(三) - Redis 集群模式搭建与原理详解](https://juejin.im/post/5b8fc5536fb9a05d2d01fb11) + - [深入剖析 Redis 系列(三) - Redis 集群模式搭建与原理详解](https://juejin.im/post/5b8fc5536fb9a05d2d01fb11) \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/README.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/README.md" index f38f486c..f65b88bb 100644 --- "a/docs/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/README.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/README.md" @@ -29,25 +29,25 @@ hidden: true > 关键词:`内存淘汰`、`事件`、`事务`、`管道`、`发布与订阅` -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200713105627.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200713105627.png) ### [Redis 数据类型和应用](03.Redis数据类型和应用.md) > 关键词:`STRING`、`HASH`、`LIST`、`SET`、`ZSET`、`BitMap`、`HyperLogLog`、`Geo` -![Redis 数据类型](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200226113813.png) +![Redis 数据类型](https://raw.githubusercontent.com/dunwu/images/master/snap/20200226113813.png) ### [Redis 持久化](04.Redis持久化.md) > 关键词:`RDB`、`AOF`、`SAVE`、`BGSAVE`、`appendfsync` -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200224214047.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200224214047.png) ### [Redis 复制](05.Redis复制.md) > 关键词:`SLAVEOF`、`SYNC`、`PSYNC`、`REPLCONF ACK` -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200712182603.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200712182603.png) ### [Redis 哨兵](06.Redis哨兵.md) @@ -57,13 +57,13 @@ hidden: true > > 关键词:`Sentinel`、`PING`、`INFO`、`Raft` -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200713072747.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200713072747.png) ### [Redis 集群](07.Redis集群.md) > 关键词:`CLUSTER MEET`、`Hash slot`、`MOVED`、`ASK`、`SLAVEOF no one`、`redis-trib` -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200713100613.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200713100613.png) ### [Redis 实战](08.Redis实战.md) @@ -100,4 +100,4 @@ hidden: true ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/01.HBase\345\277\253\351\200\237\345\205\245\351\227\250.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/01.HBase\345\277\253\351\200\237\345\205\245\351\227\250.md" new file mode 100644 index 00000000..2dc28428 --- /dev/null +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/01.HBase\345\277\253\351\200\237\345\205\245\351\227\250.md" @@ -0,0 +1,292 @@ +--- +title: HBase 快速入门 +date: 2020-02-10 14:27:39 +categories: + - 数据库 + - 列式数据库 + - HBase +tags: + - 数据库 + - 列式数据库 + - 大数据 + - HBase +permalink: /pages/7ab03c/ +--- + +# HBase 快速入门 + +## HBase 简介 + +### 为什么需要 HBase + +在介绍 HBase 之前,我们不妨先了解一下为什么需要 HBase,或者说 HBase 是为了达到什么目的而产生。 + +在 HBase 诞生之前,Hadoop 可以通过 HDFS 来存储结构化、半结构甚至非结构化的数据,它是传统数据库的补充,是海量数据存储的最佳方法,它针对大文件的存储,批量访问和流式访问都做了优化,同时也通过多副本解决了容灾问题。 + +Hadoop 的缺陷在于:它只能执行批处理,并且只能以顺序方式访问数据。这意味着即使是最简单的工作,也必须搜索整个数据集,即:**Hadoop 无法实现对数据的随机访问**。实现数据的随机访问是传统的关系型数据库所擅长的,但它们却不能用于海量数据的存储。在这种情况下,必须有一种新的方案来**同时解决海量数据存储和随机访问的问题**,HBase 就是其中之一 (HBase,Cassandra,couchDB,Dynamo 和 MongoDB 都能存储海量数据并支持随机访问)。 + +> 注:数据结构分类: +> +> - 结构化数据:即以关系型数据库表形式管理的数据; +> - 半结构化数据:非关系模型的,有基本固定结构模式的数据,例如日志文件、XML 文档、JSON 文档、Email 等; +> - 非结构化数据:没有固定模式的数据,如 WORD、PDF、PPT、EXL,各种格式的图片、视频等。 + +### 什么是 HBase + +**HBase 是一个构建在 HDFS(Hadoop 文件系统)之上的列式数据库**。 + +HBase 是一种类似于 `Google’s Big Table` 的数据模型,它是 Hadoop 生态系统的一部分,它将数据存储在 HDFS 上,客户端可以通过 HBase 实现对 HDFS 上数据的随机访问。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200601170449.png) + +HBase 的**核心特性**如下: + +- **分布式** + - **伸缩性**:支持通过增减机器进行水平扩展,以提升整体容量和性能 + - **高可用**:支持 RegionServers 之间的自动故障转移 + - **自动分区**:Region 分散在集群中,当行数增长的时候,Region 也会自动的分区再均衡 +- **超大数据集**:HBase 被设计用来读写超大规模的数据集(数十亿行至数百亿行的表) +- **支持结构化、半结构化和非结构化的数据**:由于 HBase 基于 HDFS 构建,所以和 HDFS 一样,支持结构化、半结构化和非结构化的数据 +- **非关系型数据库** + - **不支持标准 SQL 语法** + - **没有真正的索引** + - **不支持复杂的事务**:只支持行级事务,即单行数据的读写都是原子性的 + +HBase 的其他特性 + +- 读写操作遵循强一致性 +- 过滤器支持谓词下推 +- 易于使用的 Java 客户端 API +- 它支持线性和模块化可扩展性。 +- HBase 表支持 Hadoop MapReduce 作业的便捷基类 +- 很容易使用 Java API 进行客户端访问 +- 为实时查询提供块缓存 BlockCache 和布隆过滤器 +- 它通过服务器端过滤器提供查询谓词下推 + +### 什么时候使用 HBase + +根据上一节对于 HBase 特性的介绍,我们可以梳理出 HBase 适用、不适用的场景: + +HBase 不适用场景: + +- 需要索引 +- 需要复杂的事务 +- 数据量较小(比如:数据量不足几百万行) + +HBase 适用场景: + +- 能存储海量数据并支持随机访问(比如:数据量级达到十亿级至百亿级) +- 存储结构化、半结构化数据 +- 硬件资源充足 + +> 一言以蔽之——HBase 适用的场景是:**实时地随机访问超大数据集**。 + +HBase 的典型应用场景 + +- 存储监控数据 +- 存储用户/车辆 GPS 信息 +- 存储用户行为数据 +- 存储各种日志数据,如:访问日志、操作日志、推送日志等。 +- 存储短信、邮件等消息类数据 +- 存储网页数据 + +### HBase 数据模型简介 + +前面已经提及,HBase 是一个列式数据库,其数据模型和关系型数据库有所不同。其数据模型的关键术语如下: + +- Table:HBase 表由多行组成。 +- Row:HBase 中的一行由一个行键和一个或多个列以及与之关联的值组成。 行在存储时按行键的字母顺序排序。 为此,行键的设计非常重要。 目标是以相关行彼此靠近的方式存储数据。 常见的行键模式是网站域。 如果您的行键是域,您应该将它们反向存储(org.apache.www、org.apache.mail、org.apache.jira)。 这样,所有 Apache 域在表中彼此靠近,而不是根据子域的第一个字母展开。 +- Column:HBase 中的列由列族和列限定符组成,它们由 :(冒号)字符分隔。 +- Column Family:通常出于性能原因,列族在物理上将一组列及其值放在一起。 每个列族都有一组存储属性,例如它的值是否应该缓存在内存中,它的数据是如何压缩的,它的行键是如何编码的,等等。 表中的每一行都有相同的列族,尽管给定的行可能不在给定的列族中存储任何内容。 +- 列限定符:将列限定符添加到列族以提供给定数据片段的索引。 给定列族内容,列限定符可能是 content:html,另一个可能是 content:pdf。 尽管列族在表创建时是固定的,但列限定符是可变的,并且行之间可能有很大差异。 +- Cell:单元格是行、列族和列限定符的组合,包含一个值和一个时间戳,代表值的版本。 +- Timestamp:时间戳写在每个值旁边,是给定版本值的标识符。 默认情况下,时间戳表示写入数据时 RegionServer 上的时间,但您可以在将数据放入单元格时指定不同的时间戳值。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/bigdata/hbase/1551164224778.png) + +### 特性比较 + +#### HBase vs. RDBMS + +| RDBMS | HBase | +| ---------------------------------------- | -------------------------------------------------- | +| RDBMS 有它的模式,描述表的整体结构的约束 | HBase 无模式,它不具有固定列模式的概念;仅定义列族 | +| 支持的文件系统有 FAT、NTFS 和 EXT | 支持的文件系统只有 HDFS | +| 使用提交日志来存储日志 | 使用预写日志 (WAL) 来存储日志 | +| 使用特定的协调系统来协调集群 | 使用 ZooKeeper 来协调集群 | +| 存储的都是中小规模的数据表 | 存储的是超大规模的数据表,并且适合存储宽表 | +| 通常支持复杂的事务 | 仅支持行级事务 | +| 适用于结构化数据 | 适用于半结构化、结构化数据 | +| 使用主键 | 使用 row key | + +#### HBase vs. HDFS + +| HDFS | HBase | +| ----------------------------------------- | ---------------------------------------------------- | +| HDFS 提供了一个用于分布式存储的文件系统。 | HBase 提供面向表格列的数据存储。 | +| HDFS 为大文件提供优化存储。 | HBase 为表格数据提供了优化。 | +| HDFS 使用块文件。 | HBase 使用键值对数据。 | +| HDFS 数据模型不灵活。 | HBase 提供了一个灵活的数据模型。 | +| HDFS 使用文件系统和处理框架。 | HBase 使用带有内置 Hadoop MapReduce 支持的表格存储。 | +| HDFS 主要针对一次写入多次读取进行了优化。 | HBase 针对读/写许多进行了优化。 | + +#### 行式数据库 vs. 列式数据库 + +| 行式数据库 | 列式数据库 | +| ------------------------------ | ------------------------------ | +| 对于添加/修改操作更高效 | 对于读取操作更高效 | +| 读取整行数据 | 仅读取必要的列数据 | +| 最适合在线事务处理系统(OLTP) | 不适合在线事务处理系统(OLTP) | +| 将行数据存储在连续的页内存中 | 将列数据存储在非连续的页内存中 | + +列式数据库的优点: + +- 支持数据压缩 +- 支持快速数据检索 +- 简化了管理和配置 +- 有利于聚合查询(例如 COUNT、SUM、AVG、MIN 和 MAX)的高性能 +- 分区效率很高,因为它提供了自动分片机制的功能,可以将较大的区域分配给较小的区域 + +列式数据库的缺点: + +- JOIN 查询和来自多个表的数据未优化 +- 必须为频繁的删除和更新创建拆分,因此降低了存储效率 +- 由于非关系数据库的特性,分区和索引的设计非常困难 + +## HBase 安装 + +- [独立模式](https://hbase.apache.org/book.html#quickstart) +- [伪分布式模式](https://hbase.apache.org/book.html#quickstart_pseudo) +- [全分布式模式](https://hbase.apache.org/book.html#quickstart_fully_distributed) +- [Docker 部署](https://github.com/big-data-europe/docker-hbase) + +## HBase Hello World 示例 + +1. 连接 HBase + + 在 HBase 安装目录的 `/bin` 目录下执行 `hbase shell` 命令进入 HBase 控制台。 + + ```shell + $ ./bin/hbase shell + hbase(main):001:0> + ``` + +2. 输入 `help` 可以查看 HBase Shell 命令。 + +3. 创建表 + + 可以使用 `create` 命令创建一张新表。必须要指定表名和 Column Family。 + + ```shell + hbase(main):001:0> create 'test', 'cf' + 0 row(s) in 0.4170 seconds + + => Hbase::Table - test + ``` + +4. 列出表信息 + + 使用 `list` 命令来确认新建的表已存在。 + + ```shell + hbase(main):002:0> list 'test' + TABLE + test + 1 row(s) in 0.0180 seconds + + => ["test"] + ``` + + 可以使用 `describe` 命令可以查看表的细节信息,包括配置信息 + + ```shell + hbase(main):003:0> describe 'test' + Table test is ENABLED + test + COLUMN FAMILIES DESCRIPTION + {NAME => 'cf', VERSIONS => '1', EVICT_BLOCKS_ON_CLOSE => 'false', NEW_VERSION_BEHAVIOR => 'false', KEEP_DELETED_CELLS => 'FALSE', CACHE_DATA_ON_WRITE => + 'false', DATA_BLOCK_ENCODING => 'NONE', TTL => 'FOREVER', MIN_VERSIONS => '0', REPLICATION_SCOPE => '0', BLOOMFILTER => 'ROW', CACHE_INDEX_ON_WRITE => 'f + alse', IN_MEMORY => 'false', CACHE_BLOOMS_ON_WRITE => 'false', PREFETCH_BLOCKS_ON_OPEN => 'false', COMPRESSION => 'NONE', BLOCKCACHE => 'true', BLOCKSIZE + => '65536'} + 1 row(s) + Took 0.9998 seconds + ``` + +5. 向表中写数据 + + 可以使用 `put` 命令向 HBase 表中写数据。 + + ```shell + hbase(main):003:0> put 'test', 'row1', 'cf:a', 'value1' + 0 row(s) in 0.0850 seconds + + hbase(main):004:0> put 'test', 'row2', 'cf:b', 'value2' + 0 row(s) in 0.0110 seconds + + hbase(main):005:0> put 'test', 'row3', 'cf:c', 'value3' + 0 row(s) in 0.0100 seconds + ``` + +6. 一次性扫描表的所有数据 + + 使用 `scan` 命令来扫描表数据。 + + ```shell + hbase(main):006:0> scan 'test' + ROW COLUMN+CELL + row1 column=cf:a, timestamp=1421762485768, value=value1 + row2 column=cf:b, timestamp=1421762491785, value=value2 + row3 column=cf:c, timestamp=1421762496210, value=value3 + 3 row(s) in 0.0230 seconds + ``` + +7. 查看一行数据 + + 使用 `get` 命令可以查看一行表数据。 + + ```shell + hbase(main):007:0> get 'test', 'row1' + COLUMN CELL + cf:a timestamp=1421762485768, value=value1 + 1 row(s) in 0.0350 seconds + ``` + +8. 禁用表 + + 如果想要删除表或修改表设置,必须先使用 `disable` 命令禁用表。如果想再次启用表,可以使用 `enable` 命令。 + + ```shell + hbase(main):008:0> disable 'test' + 0 row(s) in 1.1820 seconds + + hbase(main):009:0> enable 'test' + 0 row(s) in 0.1770 seconds + ``` + +9. 删除表 + + 使用 `drop` 命令可以删除表。 + + ```shell + hbase(main):011:0> drop 'test' + 0 row(s) in 0.1370 seconds + ``` + +10. 退出 HBase Shell + + 使用 `quit` 命令,就能退出 HBase Shell 控制台。 + +## 参考资料 + +- **官方** + - [HBase 官网](http://hbase.apache.org/) + - [HBase 官方文档](https://hbase.apache.org/book.html) + - [HBase 官方文档中文版](http://abloz.com/hbase/book.html) +- **书籍** + - [Hadoop 权威指南](https://book.douban.com/subject/27600204/) +- **文章** + - [Bigtable: A Distributed Storage System for Structured Data](https://static.googleusercontent.com/media/research.google.com/zh-CN//archive/bigtable-osdi06.pdf) + - [An In-Depth Look at the HBase Architecture](https://mapr.com/blog/in-depth-look-hbase-architecture) +- **教程** + - https://github.com/heibaiying/BigData-Notes + - https://www.cloudduggu.com/hbase/introduction/ diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/02.HBase\346\225\260\346\215\256\346\250\241\345\236\213.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/02.HBase\346\225\260\346\215\256\346\250\241\345\236\213.md" new file mode 100644 index 00000000..d2af7ecb --- /dev/null +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/02.HBase\346\225\260\346\215\256\346\250\241\345\236\213.md" @@ -0,0 +1,84 @@ +--- +title: HBase 数据模型 +date: 2023-03-16 15:58:10 +categories: + - 数据库 + - 列式数据库 + - HBase +tags: + - 数据库 + - 列式数据库 + - 大数据 + - HBase +permalink: /pages/c8cfeb/ +--- + +# HBase 数据模型 + +HBase 是一个面向 `列` 的数据库管理系统,这里更为确切的而说,HBase 是一个面向 `列族` 的数据库管理系统。表 schema 仅定义列族,表具有多个列族,每个列族可以包含任意数量的列,列由多个单元格(cell)组成,单元格可以存储多个版本的数据,多个版本数据以时间戳进行区分。 + +## HBase 逻辑存储结构 + +- **`Table`**:Table 由 Row 和 Column 组成。 +- **`Row`**:Row 是列族(Column Family)的集合。 +- **`Row Key`**:**`Row Key` 是用来检索记录的主键**。 + - `Row Key` 是未解释的字节数组,所以理论上,任何数据都可以通过序列化表示成字符串或二进制,从而存为 HBase 的键值。 + - 表中的行,是按照 `Row Key` 的字典序进行排序。这里需要注意以下两点: + - 因为字典序对 Int 排序的结果是 1,10,100,11,12,13,14,15,16,17,18,19,2,20,21,…,9,91,92,93,94,95,96,97,98,99。如果你使用整型的字符串作为行键,那么为了保持整型的自然序,行键必须用 0 作左填充。 + - 行的一次读写操作是原子性的 (不论一次读写多少列)。 + - 所有对表的访问都要通过 Row Key,有以下三种方式: + - 通过指定的 `Row Key` 进行访问; + - 通过 `Row Key` 的 range 进行访问,即访问指定范围内的行; + - 进行全表扫描。 +- **`Column Family`**:即列族。HBase 表中的每个列,都归属于某个列族。列族是表的 Schema 的一部分,所以列族需要在创建表时进行定义。 + - 一个表的列族必须作为表模式定义的一部分预先给出,但是新的列族成员可以随后按需加入。 + - 同一个列族的所有成员具有相同的前缀,例如 `info:format`,`info:geo` 都属于 `info` 这个列族。 +- **`Column Qualifier`**:列限定符。可以理解为是具体的列名,例如 `info:format`,`info:geo` 都属于 `info` 这个列族,它们的列限定符分别是 `format` 和 `geo`。列族和列限定符之间始终以冒号分隔。需要注意的是列限定符不是表 Schema 的一部分,你可以在插入数据的过程中动态创建列。 +- **`Column`**:HBase 中的列由列族和列限定符组成,由 `:`(冒号) 进行分隔,即一个完整的列名应该表述为 `列族名 :列限定符`。 +- **`Cell`**:`Cell` 是行,列族和列限定符的组合,并包含值和时间戳。HBase 中通过 `row key` 和 `column` 确定的为一个存储单元称为 `Cell`,你可以等价理解为关系型数据库中由指定行和指定列确定的一个单元格,但不同的是 HBase 中的一个单元格是由多个版本的数据组成的,每个版本的数据用时间戳进行区分。 + - `Cell` 由行和列的坐标交叉决定,是有版本的。默认情况下,版本号是自动分配的,为 HBase 插入 `Cell` 时的时间戳。`Cell` 的内容是未解释的字节数组。 + - +- **`Timestamp`**:`Cell` 的版本通过时间戳来索引,时间戳的类型是 64 位整型,时间戳可以由 HBase 在数据写入时自动赋值,也可以由客户显式指定。每个 `Cell` 中,不同版本的数据按照时间戳倒序排列,即最新的数据排在最前面。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/bigdata/hbase/1551164224778.png) + +## HBase 物理存储结构 + +HBase 自动将表水平划分成区域(Region)。每个 Region 由表中 Row 的子集构成。每个 Region 由它所属的表的起始范围来表示(包含的第一行和最后一行)。初始时,一个表只有一个 Region,随着 Region 膨胀,当超过一定阈值时,会在某行的边界上分裂成两个大小基本相同的新 Region。在第一次划分之前,所有加载的数据都放在原始 Region 所在的那台服务器上。随着表变大,Region 个数也会逐渐增加。Region 是在 HBase 集群上分布数据的最小单位。 + +## HBase 数据模型示例 + +下图为 HBase 中一张表的: + +- RowKey 为行的唯一标识,所有行按照 RowKey 的字典序进行排序; +- 该表具有两个列族,分别是 personal 和 office; +- 其中列族 personal 拥有 name、city、phone 三个列,列族 office 拥有 tel、addres 两个列。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200601172926.png) + +> _图片引用自 : HBase 是列式存储数据库吗_ *https://www.iteblog.com/archives/2498.html* + +## HBase 表特性 + +Hbase 的表具有以下特点: + +- **容量大**:一个表可以有数十亿行,上百万列; +- **面向列**:数据是按照列存储,每一列都单独存放,数据即索引,在查询时可以只访问指定列的数据,有效地降低了系统的 I/O 负担; +- **稀疏性**:空 (null) 列并不占用存储空间,表可以设计的非常稀疏 ; +- **数据多版本**:每个单元中的数据可以有多个版本,按照时间戳排序,新的数据在最上面; +- **存储类型**:所有数据的底层存储格式都是字节数组 (byte[])。 + +## 参考资料 + +- **官方** + - [HBase 官网](http://hbase.apache.org/) + - [HBase 官方文档](https://hbase.apache.org/book.html) + - [HBase 官方文档中文版](http://abloz.com/hbase/book.html) +- **书籍** + - [Hadoop 权威指南](https://book.douban.com/subject/27600204/) +- **文章** + - [Bigtable: A Distributed Storage System for Structured Data](https://static.googleusercontent.com/media/research.google.com/zh-CN//archive/bigtable-osdi06.pdf) + - [An In-Depth Look at the HBase Architecture](https://mapr.com/blog/in-depth-look-hbase-architecture) +- **教程** + - https://github.com/heibaiying/BigData-Notes + - https://www.cloudduggu.com/hbase/introduction/ \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/03.HBaseSchema\350\256\276\350\256\241.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/03.HBaseSchema\350\256\276\350\256\241.md" new file mode 100644 index 00000000..d4e29c79 --- /dev/null +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/03.HBaseSchema\350\256\276\350\256\241.md" @@ -0,0 +1,229 @@ +--- +title: HBase Schema 设计 +date: 2023-03-15 20:28:32 +categories: + - 数据库 + - 列式数据库 + - HBase +tags: + - 大数据 + - HBase +permalink: /pages/a69528/ +--- + +# HBase Schema 设计 + +## HBase Schema 设计要素 + +- 这个表应该有多少 Column Family +- Column Family 使用什么数据 +- 每个 Column Family 有有多少列 +- 列名是什么,尽管列名不必在建表时定义,但读写数据是要知道的 +- 单元应该存放什么数据 +- 每个单元存储多少时间版本 +- 行健(rowKey)结构是什么,应该包含什么信息 + +## Row Key 设计 + +### Row Key 的作用 + +在 HBase 中,所有对表的访问都要通过 Row Key,有三种访问方式: + +- 使用 `get` 命令,查询指定的 Row Key,即精确查找。 +- 使用 scan 命令,根据 Row Key 进行范围查找。 +- 全表扫描,即直接扫描表中所有行记录。 + +此外,在 HBase 中,表中的行,是按照 Row Key 的字典序进行排序的。 + +由此,可见,Row Key 的良好设计对于 HBase CRUD 的性能至关重要。 + +### Row Key 的设计原则 + +**长度原则** + +RowKey 是一个二进制码流,可以是任意字符串,最大长度为 64kb,实际应用中一般为 10-100byte,以 byte[]形式保存,一般设计成定长。建议越短越好,不要超过 16 个字节,原因如下: + +1. 数据的持久化文件 HFile 中时按照 Key-Value 存储的,如果 RowKey 过长,例如超过 100byte,那么 1000w 行的记录,仅 RowKey 就需占用近 1GB 的空间。这样会极大影响 HFile 的存储效率。 +2. MemStore 会缓存部分数据到内存中,若 RowKey 字段过长,内存的有效利用率就会降低,就不能缓存更多的数据,从而降低检索效率。 +3. 目前操作系统都是 64 位系统,内存 8 字节对齐,控制在 16 字节,8 字节的整数倍利用了操作系统的最佳特性。 + +**唯一原则** + +必须在设计上保证 RowKey 的唯一性。由于在 HBase 中数据存储是 Key-Value 形式,若向 HBase 中同一张表插入相同 RowKey 的数据,则原先存在的数据会被新的数据覆盖。 + +**排序原则** + +HBase 的 RowKey 是按照 ASCII 有序排序的,因此我们在设计 RowKey 的时候要充分利用这点。 + +**散列原则** + +设计的 RowKey 应均匀的分布在各个 HBase 节点上。 + +### 热点问题 + +Region 是在 HBase 集群上分布数据的最小单位。每个 Region 由它所属的表的起始范围来表示(即起始 Row Key 和结束 Row Key)。 + +如果,Row Key 使用单调递增的整数或时间戳,就会产生一个问题:因为 Hbase 的 Row Key 是就近存储的,这会导致一段时间内大部分读写集中在某一个 Region 或少数 Region 上(根据二八原则,最近产生的数据,往往是读写频率最高的数据),即所谓 **热点问题**。 + +#### 反转(Reversing) + +第一种咱们要分析的方法是反转,顾名思义它就是把固定长度或者数字格式的 RowKey 进行反转,反转分为一般数据反转和时间戳反转,其中以时间戳反转较常见。 + +- **反转固定格式的数值** - 以手机号为例,手机号的前缀变化比较少(如 `152、185` 等),但后半部分变化很多。如果将它反转过来,可以有效地避免热点。不过其缺点就是失去了有序性。 +- **反转时间** - 如果数据访问以查找最近的数据为主,可以将时间戳存储为反向时间戳(例如: `timestamp = Long.MAX_VALUE – timestamp`),这样有利于扫描最近的数据。 + +#### 加盐(Salting) + +这里的“加盐”与密码学中的“加盐”不是一回事。它是指在 RowKey 的前面增加一些前缀,加盐的前缀种类越多,RowKey 就被打得越散。 + +需要注意的是分配的随机前缀的种类数量应该和我们想把数据分散到的那些 region 的数量一致。只有这样,加盐之后的 rowkey 才会根据随机生成的前缀分散到各个 region 中,避免了热点现象。 + +#### 哈希(Hashing) + +其实哈希和加盐的适用场景类似,但我们前缀不可以是随机的,因为必须要让客户端能够完整地重构 RowKey。所以一般会拿原 RowKey 或其一部分计算 Hash 值,然后再对 Hash 值做运算作为前缀。 + +## HBase Schema 设计规则 + +### Column Family 设计 + +HBase 不能很好处理 2 ~ 3 个以上的 Column Family,所以 **HBase 表应尽可能减少 Column Family 数**。如果可以,请只使用一个列族,只有需要经常执行 Column 范围查询时,才引入多列族。也就是说,尽量避免同时查询多个列族。 + +- **Column Family 数量多,会影响数据刷新**。HBase 的数据刷新是在每个 Region 的基础上完成的。因此,如果一个 Column Family 携带大量导致刷新的数据,那么相邻的列族即使携带的数据量很小,也会被刷新。当存在许多 Column Family 时,刷新交互会导致一堆不必要的 IO。 此外,在表/区域级别的压缩操作也会在每个存储中发生。 +- **Column Family 数量多,会影响查找效率**。如:Column Family A 有 100 万行,Column Family B 有 10 亿行,那么 Column Family A 的数据可能会分布在很多很多区域(和 RegionServers)。 这会降低 Column Family A 的批量扫描效率。 + +Column Family 名尽量简短,最好是一个字符。Column Family 会在列限定符中被频繁使用,缩短长度有利于节省空间并提升效率。 + +### Row 设计 + +**HBase 中的 Row 按 Row Key 的字典顺序排序**。 + +- **不要将 Row Key 设计为单调递增的**,例如:递增的整数或时间戳 + + - 问题:因为 Hbase 的 Row Key 是就近存储的,这样会导致一段时间内大部分写入集中在某一个 Region 上,即所谓热点问题。 + + - 解决方法一、加盐:这里的不是指密码学的加盐,而是指将随机分配的前缀添加到行键的开头。这么做是为了避免相同前缀的 Row Key 数据被存储在相邻位置,从而导致热点问题。示例如下: + + - ``` + foo0001 + foo0002 + foo0003 + foo0004 + + 改为 + + a-foo0003 + b-foo0001 + c-foo0003 + c-foo0004 + d-foo0002 + ``` + + - 解决方法二、Hash:Row Key 的前缀使用 Hash + +- **尽量减少行和列的长度** + +- **反向时间戳**:反向时间戳可以极大地帮助快速找到值的最新版本。 + +- **行健不能改变**:唯一可以改变的方式是先删除后插入。 + +- **Row Key 和 Column Family**:Row Key 从属于 Column Family,因此,相同的 Row Key 可以存在每一个 Column Family 中而不会出现冲突。 + +### Version 设计 + +最大、最小 Row 版本号:表示 HBase 会保留的版本号数的上下限。均可以通过 HColumnDescriptor 对每个列族进行配置 + +Row 版本号过大,会大大增加 StoreFile 的大小;所以,最大 Row 版本号应按需设置。HBase 会在主要压缩时,删除多余的版本。 + +### TTL 设计 + +Column Family 会设置一个以秒为单位的 TTL,一旦达到 TTL 时,HBase 会自动删除行记录。 + +仅包含过期行的存储文件在次要压缩时被删除。 将 hbase.store.delete.expired.storefile 设置为 false 会禁用此功能。将最小版本数设置为 0 以外的值也会禁用此功能。 + +在较新版本的 HBase 上,还支持在 Cell 上设置 TTL,与 Column Family 的 TTL 不同的是,单位是毫秒。 + +### Column Family 属性配置 + +- HFile 数据块,默认是 64KB,数据库的大小影响数据块索引的大小。数据块大的话一次加载进内存的数据越多,扫描查询效果越好。但是数据块小的话,随机查询性能更好 + +``` +> create 'mytable',{NAME => 'cf1', BLOCKSIZE => '65536'} +复制代码 +``` + +- 数据块缓存,数据块缓存默认是打开的,如果一些比较少访问的数据可以选择关闭缓存 + +``` +> create 'mytable',{NAME => 'cf1', BLOCKCACHE => 'FALSE'} +复制代码 +``` + +- 数据压缩,压缩会提高磁盘利用率,但是会增加 CPU 的负载,看情况进行控制 + +``` +> create 'mytable',{NAME => 'cf1', COMPRESSION => 'SNAPPY'} +复制代码 +``` + +Hbase 表设计是和需求相关的,但是遵守表设计的一些硬性指标对性能的提升还是很有帮助的,这里整理了一些设计时用到的要点。 + +## Schema 设计案例 + +### 案例:日志数据和时序数据 + +假设采集以下数据 + +- Hostname +- Timestamp +- Log event +- Value/message + +应该如何设计 Row Key? + +(1)Timestamp 在 Row Key 头部 + +如果 Row Key 设计为 `[timestamp][hostname][log-event]` 形式,会出现热点问题。 + +如果针对时间的扫描很重要,可以采用时间戳分桶策略,即 + +``` +bucket = timestamp % bucketNum +``` + +计算出桶号后,将 Row Key 指定为:`[bucket][timestamp][hostname][log-event]` + +如上所述,要为特定时间范围选择数据,需要对每个桶执行扫描。 例如,100 个桶将在键空间中提供广泛的分布,但需要 100 次扫描才能获取单个时间戳的数据,因此需要权衡取舍。 + +(2)Hostname 在 Row Key 头部 + +如果主机样本量很大,将 Row Key 设计为 `[hostname][log-event][timestamp]`,这样有利于扫描 hostname。 + +(3)Timestamp 还是反向 Timestamp + +如果数据访问以查找最近的数据为主,可以将时间戳存储为反向时间戳(例如: `timestamp = Long.MAX_VALUE – timestamp`),这样有利于扫描最近的数据。 + +(4)Row Key 是可变长度还是固定长度 + +拼接 Row Key 的关键字长度不一定是固定的,例如 hostname 有可能很长,也有可能很短。如果想要统一长度,可以参考以下做法: + +- 将关键字 Hash 编码:使用某种 Hash 算法计算关键字,并取固定长度的值(例如:8 位或 16 位)。 +- 使用数字替代关键字:例如:使用事件类型 Code 替换事件类型;hostname 如果是 IP,可以转换为 long +- 截取关键字:截取后的关键字需要有足够的辨识度,长度大小根据具体情况权衡。 + +(5)时间分片 + +``` +[hostname][log-event][timestamp1] +[hostname][log-event][timestamp2] +[hostname][log-event][timestamp3] +``` + +上面的例子中,每个详细事件都有单独的行键,可以重写如下,即每个时间段存储一次: + +``` +[hostname][log-event][timerange] +``` + +## 参考资料 + +- [HBase 官方文档之 HBase and Schema Design](https://hbase.apache.org/book.html#schema) diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/04.HBase\346\236\266\346\236\204.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/04.HBase\346\236\266\346\236\204.md" new file mode 100644 index 00000000..85423705 --- /dev/null +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/04.HBase\346\236\266\346\236\204.md" @@ -0,0 +1,160 @@ +--- +title: HBase 架构 +date: 2020-07-24 06:52:07 +categories: + - 数据库 + - 列式数据库 + - HBase +tags: + - 大数据 + - HBase +permalink: /pages/62f8d9/ +--- + +# HBase 架构 + +> **_HBase 是一个在 HDFS 上开发的面向列的分布式数据库。_** + +## HBase 存储架构 + +> 在 HBase 中,表被分割成多个更小的块然后分散的存储在不同的服务器上,这些小块叫做 Regions,存放 Regions 的地方叫做 RegionServer。Master 进程负责处理不同的 RegionServer 之间的 Region 的分发。 + +### 概览 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200612151239.png) + +HBase 主要处理两种文件:预写日志(WAL)和实际数据文件 HFile。一个基本的流程是客户端首先联系 ZooKeeper 集群查找行键。上述过程是通过 ZooKeeper 获取欧含有 `-ROOT-` 的 region 服务器来完成的。通过含有 `-ROOT-` 的 region 服务器可以查询到含有 `.META.` 表中对应的 region 服务器名,其中包含请求的行键信息。这两种内容都会被缓存下来,并且只查询一次。最终,通过查询 .META. 服务器来获取客户端查询的行键数据所在 region 的服务器名。 + +### Region + +HBase Table 中的所有行按照 `Row Key` 的字典序排列。HBase Table 根据 Row Key 的范围分片,每个分片叫做 `Region`。一个 `Region` 包含了在 start key 和 end key 之间的所有行。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/bigdata/hbase/1551165887616.png) + +**HBase 支持自动分区**:每个表初始只有一个 `Region`,随着数据不断增加,`Region` 会不断增大,当增大到一个阀值的时候,`Region` 就会分裂为两个新的 `Region`。当 Table 中的行不断增多,就会有越来越多的 `Region`。 + +`Region` 是 HBase 中**分布式存储和负载均衡的最小单元**。这意味着不同的 `Region` 可以分布在不同的 `Region Server` 上。但一个 `Region` 是不会拆分到多个 Server 上的。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200601181219.png) + +### Region Server + +`Region` 只不过是表被拆分,并分布在 Region Server。 + +`Region Server` 运行在 HDFS 的 DataNode 上。它具有以下组件: + +- **WAL(Write Ahead Log,预写日志)**:用于存储尚未进持久化存储的数据记录,以便在发生故障时进行恢复。如果写 WAL 失败了,那么修改数据的完整操作就是失败的。 + - 通常情况,每个 RegionServer 只有一个 WAL 实例。在 2.0 之前,WAL 的实现叫做 HLog + - WAL 位于 `/hbase/WALs/` 目录下 + - 如果每个 RegionServer 只有一个 WAL,由于 HDFS 必须是连续的,导致必须写 WAL 连续的,然后出现性能问题。MultiWAL 可以让 RegionServer 同时写多个 WAL 并行的,通过 HDFS 底层的多管道,最终提升总的吞吐量,但是不会提升单个 Region 的吞吐量。 +- **BlockCache**:**读缓存**。它将频繁读取的数据存储在内存中,如果存储不足,它将按照 `最近最少使用原则` 清除多余的数据。 +- **MemStore**:**写缓存**。它存储尚未写入磁盘的新数据,并会在数据写入磁盘之前对其进行排序。每个 Region 上的每个列族都有一个 MemStore。 +- **HFile**:**将行数据按照 Key/Values 的形式存储在文件系统上**。HFile 是 HBase 在 HDFS 中存储数据的格式,它包含多层的索引,这样在 HBase 检索数据的时候就不用完全的加载整个文件。HFile 存储的根目录默认为为 `/hbase`。索引的大小(keys 的大小,数据量的大小)影响 block 的大小,在大数据集的情况下,block 的大小设置为每个 RegionServer 1GB 也是常见的。 + - 起初,HFile 中并没有任何 Block,数据还存在于 MemStore 中。 + - Flush 发生时,创建 HFile Writer,第一个空的 Data Block 出现,初始化后的 Data Block 中为 Header 部分预留了空间,Header 部分用来存放一个 Data Block 的元数据信息。 + - 而后,位于 MemStore 中的 KeyValues 被一个个 append 到位于内存中的第一个 Data Block 中: + +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/bigdata/hbase/1551166602999.png) + +Region Server 存取一个子表时,会创建一个 Region 对象,然后对表的每个列族创建一个 `Store` 实例,每个 `Store` 会有 0 个或多个 `StoreFile` 与之对应,每个 `StoreFile` 则对应一个 `HFile`,HFile 就是实际存储在 HDFS 上的文件。 + +## HBase 系统架构 + +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/bigdata/hbase/1551164744748.png) + +和 HDFS、YARN 一样,**HBase 也遵循 master / slave 架构**: + +- HBase 有一个 master 节点。**master 节点负责协调管理 region server 节点**。 + - master 负责将 region 分配给 region server 节点; + - master 负责恢复 region server 节点的故障。 +- HBase 有多个 region server 节点。**region server 节点负责零个或多个 region 的管理并响应客户端的读写请求。region server 节点还负责 region 的划分并通知 master 节点有了新的子 region**。 +- HBase 依赖 ZooKeeper 来实现故障恢复。 + +### Master Server + +**Master Server 负责协调 Region Server**。具体职责如下: + +- 为 Region Server 分配 Region ; +- 负责 Region Server 的负载均衡 ; +- 发现失效的 Region Server 并重新分配其上的 Region; +- GFS 上的垃圾文件回收; +- 处理 Schema 的更新请求。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/bigdata/hbase/1551166513572.png) + +### Region Server + +- Region Server 负责维护 Master Server 分配给它的 Region,并处理发送到 Region 上的 IO 请求; +- 当 Region 过大,Region Server 负责自动分区,并通知 Master Server 记录更新。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200612151602.png) + +### ZooKeeper + +**HBase 依赖 ZooKeeper 作为分布式协调服务来维护集群中的服务器状态**。Zookeeper 维护哪些服务器是活动的和可用的,并提供服务器故障通知。集群至少应该有 3 个节点。 + +ZooKeeper 的作用: + +- 保证任何时候,集群中只有一个 Master; +- 存储所有 Region 的寻址入口; +- 实时监控 Region Server 的状态,将 Region Server 的上线和下线信息实时通知给 Master; +- 存储 HBase 的 Schema,包括有哪些 Table,每个 Table 有哪些 Column Family 等信息。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/bigdata/hbase/1551166447147.png) + +以上,最重要的一点是 ZooKeeper 如何保证 HBase 集群中只有一个 Master Server 的呢? + +- 所有 Master Server 会竞争 Zookeeper 的 znode 锁(一个临时节点),只有一个 Master Server 能够创建成功,此时该 Master 就是主 Master。 +- 主 Master 会定期向 Zookeeper 发送心跳。从 Master 则通过 Watcher 机制对主 Master 所在节点进行监听。 +- 如果,主 Master 未能及时发送心跳,则其持有的 ZooKeeper 会话会过期,相应的 znode 锁(一个临时节点)会被自动删除。这会触发定义在该节点上的 Watcher 事件,所有从 Master 会得到通知,并再次开始竞争 znode 锁,直到完成主 Master 的选举。 + +HBase 内部保留名为 hbase:meta 的特殊目录表(catalog table)。它维护着当前集群上所有 region 的列表、状态和位置。hbase:meta 表中的项使用 region 作为键。region 名由所属的表名、region 的起始行、region的创建时间以及基于整体计算得出的 MD5 组成。 + +## HBase 读写流程 + +### 写入数据的流程 + +1. Client 向 Region Server 提交写请求; +2. Region Server 找到目标 Region; +3. Region 检查数据是否与 Schema 一致; +4. 如果客户端没有指定版本,则获取当前系统时间作为数据版本; +5. 将更新写入 WAL Log; +6. 将更新写入 Memstore; +7. 判断 Memstore 存储是否已满,如果存储已满则需要 flush 为 Store Hfile 文件。 + +> 更为详细写入流程可以参考:[HBase - 数据写入流程解析](http://hbasefly.com/2016/03/23/hbase_writer/) + +### 读取数据的流程 + +以下是客户端首次读写 HBase 上数据的流程: + +1. 客户端从 Zookeeper 获取 `META` 表所在的 Region Server; +2. 客户端访问 `META` 表所在的 Region Server,从 `META` 表中查询到访问行键所在的 Region Server,之后客户端将缓存这些信息以及 `META` 表的位置; +3. 客户端从行键所在的 Region Server 上获取数据。 + +如果再次读取,客户端将从缓存中获取行键所在的 Region Server。这样客户端就不需要再次查询 `META` 表,除非 Region 移动导致缓存失效,这样的话,则将会重新查询并更新缓存。 + +注:`META` 表是 HBase 中一张特殊的表,它保存了所有 Region 的位置信息,META 表自己的位置信息则存储在 ZooKeeper 上。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200601182655.png) + +> 更为详细读取数据流程参考: +> +> [HBase 原理-数据读取流程解析](http://hbasefly.com/2016/12/21/hbase-getorscan/) +> +> [HBase 原理-迟到的‘数据读取流程部分细节](http://hbasefly.com/2017/06/11/hbase-scan-2/) + +## 参考资料 + +- **官方** + - [HBase 官网](http://hbase.apache.org/) + - [HBase 官方文档](https://hbase.apache.org/book.html) + - [HBase 官方文档中文版](http://abloz.com/hbase/book.html) + - [HBase API](https://hbase.apache.org/apidocs/index.html) +- **教程** + - [BigData-Notes](https://github.com/heibaiying/BigData-Notes) +- **文章** + - [Bigtable: A Distributed Storage System for Structured Data](https://static.googleusercontent.com/media/research.google.com/zh-CN//archive/bigtable-osdi06.pdf) + - [An In-Depth Look at the HBase Architecture](https://mapr.com/blog/in-depth-look-hbase-architecture/) + - [入门 HBase,看这一篇就够了](https://juejin.im/post/5c666cc4f265da2da53eb714) + - https://bighadoop.wordpress.com/tag/hbase/ \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/10.HBaseJavaApi\345\237\272\347\241\200\347\211\271\346\200\247.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/10.HBaseJavaApi\345\237\272\347\241\200\347\211\271\346\200\247.md" new file mode 100644 index 00000000..ec871c15 --- /dev/null +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/10.HBaseJavaApi\345\237\272\347\241\200\347\211\271\346\200\247.md" @@ -0,0 +1,555 @@ +--- +title: HBase Java API 基础特性 +date: 2023-03-15 20:28:32 +categories: + - 数据库 + - 列式数据库 + - HBase +tags: + - 大数据 + - HBase +permalink: /pages/a8cad3/ +--- + +# HBase Java API 基础特性 + +## HBase Client API + +### HBase Java API 示例 + +引入依赖 + +```xml + + org.apache.hbase + hbase-client + 2.1.4 + +``` + +示例 + +```java +public class HBaseUtils { + + private static Connection connection; + + static { + Configuration configuration = HBaseConfiguration.create(); + configuration.set("hbase.zookeeper.property.clientPort", "2181"); + // 如果是集群 则主机名用逗号分隔 + configuration.set("hbase.zookeeper.quorum", "hadoop001"); + try { + connection = ConnectionFactory.createConnection(configuration); + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * 创建 HBase 表 + * + * @param tableName 表名 + * @param columnFamilies 列族的数组 + */ + public static boolean createTable(String tableName, List columnFamilies) { + try { + HBaseAdmin admin = (HBaseAdmin) connection.getAdmin(); + if (admin.tableExists(TableName.valueOf(tableName))) { + return false; + } + TableDescriptorBuilder tableDescriptor = TableDescriptorBuilder.newBuilder(TableName.valueOf(tableName)); + columnFamilies.forEach(columnFamily -> { + ColumnFamilyDescriptorBuilder cfDescriptorBuilder = ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes(columnFamily)); + cfDescriptorBuilder.setMaxVersions(1); + ColumnFamilyDescriptor familyDescriptor = cfDescriptorBuilder.build(); + tableDescriptor.setColumnFamily(familyDescriptor); + }); + admin.createTable(tableDescriptor.build()); + } catch (IOException e) { + e.printStackTrace(); + } + return true; + } + + + /** + * 删除 hBase 表 + * + * @param tableName 表名 + */ + public static boolean deleteTable(String tableName) { + try { + HBaseAdmin admin = (HBaseAdmin) connection.getAdmin(); + // 删除表前需要先禁用表 + admin.disableTable(TableName.valueOf(tableName)); + admin.deleteTable(TableName.valueOf(tableName)); + } catch (Exception e) { + e.printStackTrace(); + } + return true; + } + + /** + * 插入数据 + * + * @param tableName 表名 + * @param rowKey 唯一标识 + * @param columnFamilyName 列族名 + * @param qualifier 列标识 + * @param value 数据 + */ + public static boolean putRow(String tableName, String rowKey, String columnFamilyName, String qualifier, + String value) { + try { + Table table = connection.getTable(TableName.valueOf(tableName)); + Put put = new Put(Bytes.toBytes(rowKey)); + put.addColumn(Bytes.toBytes(columnFamilyName), Bytes.toBytes(qualifier), Bytes.toBytes(value)); + table.put(put); + table.close(); + } catch (IOException e) { + e.printStackTrace(); + } + return true; + } + + + /** + * 插入数据 + * + * @param tableName 表名 + * @param rowKey 唯一标识 + * @param columnFamilyName 列族名 + * @param pairList 列标识和值的集合 + */ + public static boolean putRow(String tableName, String rowKey, String columnFamilyName, List> pairList) { + try { + Table table = connection.getTable(TableName.valueOf(tableName)); + Put put = new Put(Bytes.toBytes(rowKey)); + pairList.forEach(pair -> put.addColumn(Bytes.toBytes(columnFamilyName), Bytes.toBytes(pair.getKey()), Bytes.toBytes(pair.getValue()))); + table.put(put); + table.close(); + } catch (IOException e) { + e.printStackTrace(); + } + return true; + } + + + /** + * 根据 rowKey 获取指定行的数据 + * + * @param tableName 表名 + * @param rowKey 唯一标识 + */ + public static Result getRow(String tableName, String rowKey) { + try { + Table table = connection.getTable(TableName.valueOf(tableName)); + Get get = new Get(Bytes.toBytes(rowKey)); + return table.get(get); + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } + + + /** + * 获取指定行指定列 (cell) 的最新版本的数据 + * + * @param tableName 表名 + * @param rowKey 唯一标识 + * @param columnFamily 列族 + * @param qualifier 列标识 + */ + public static String getCell(String tableName, String rowKey, String columnFamily, String qualifier) { + try { + Table table = connection.getTable(TableName.valueOf(tableName)); + Get get = new Get(Bytes.toBytes(rowKey)); + if (!get.isCheckExistenceOnly()) { + get.addColumn(Bytes.toBytes(columnFamily), Bytes.toBytes(qualifier)); + Result result = table.get(get); + byte[] resultValue = result.getValue(Bytes.toBytes(columnFamily), Bytes.toBytes(qualifier)); + return Bytes.toString(resultValue); + } else { + return null; + } + + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } + + + /** + * 检索全表 + * + * @param tableName 表名 + */ + public static ResultScanner getScanner(String tableName) { + try { + Table table = connection.getTable(TableName.valueOf(tableName)); + Scan scan = new Scan(); + return table.getScanner(scan); + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } + + + /** + * 检索表中指定数据 + * + * @param tableName 表名 + * @param filterList 过滤器 + */ + + public static ResultScanner getScanner(String tableName, FilterList filterList) { + try { + Table table = connection.getTable(TableName.valueOf(tableName)); + Scan scan = new Scan(); + scan.setFilter(filterList); + return table.getScanner(scan); + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } + + /** + * 检索表中指定数据 + * + * @param tableName 表名 + * @param startRowKey 起始 RowKey + * @param endRowKey 终止 RowKey + * @param filterList 过滤器 + */ + + public static ResultScanner getScanner(String tableName, String startRowKey, String endRowKey, + FilterList filterList) { + try { + Table table = connection.getTable(TableName.valueOf(tableName)); + Scan scan = new Scan(); + scan.withStartRow(Bytes.toBytes(startRowKey)); + scan.withStopRow(Bytes.toBytes(endRowKey)); + scan.setFilter(filterList); + return table.getScanner(scan); + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } + + /** + * 删除指定行记录 + * + * @param tableName 表名 + * @param rowKey 唯一标识 + */ + public static boolean deleteRow(String tableName, String rowKey) { + try { + Table table = connection.getTable(TableName.valueOf(tableName)); + Delete delete = new Delete(Bytes.toBytes(rowKey)); + table.delete(delete); + } catch (IOException e) { + e.printStackTrace(); + } + return true; + } + + + /** + * 删除指定行指定列 + * + * @param tableName 表名 + * @param rowKey 唯一标识 + * @param familyName 列族 + * @param qualifier 列标识 + */ + public static boolean deleteColumn(String tableName, String rowKey, String familyName, + String qualifier) { + try { + Table table = connection.getTable(TableName.valueOf(tableName)); + Delete delete = new Delete(Bytes.toBytes(rowKey)); + delete.addColumn(Bytes.toBytes(familyName), Bytes.toBytes(qualifier)); + table.delete(delete); + table.close(); + } catch (IOException e) { + e.printStackTrace(); + } + return true; + } + +} +``` + +## 数据库连接 + +在上面的代码中,在类加载时就初始化了 Connection 连接,并且之后的方法都是复用这个 Connection,这时我们可能会考虑是否可以使用自定义连接池来获取更好的性能表现?实际上这是没有必要的。 + +首先官方对于 `Connection` 的使用说明如下: + +``` +Connection Pooling For applications which require high-end multithreaded +access (e.g., web-servers or application servers that may serve many +application threads in a single JVM), you can pre-create a Connection, +as shown in the following example: + +对于高并发多线程访问的应用程序(例如,在单个 JVM 中存在的为多个线程服务的 Web 服务器或应用程序服务器), +您只需要预先创建一个 Connection。例子如下: + +// Create a connection to the cluster. +Configuration conf = HBaseConfiguration.create(); +try (Connection connection = ConnectionFactory.createConnection(conf); + Table table = connection.getTable(TableName.valueOf(tablename))) { + // use table as needed, the table returned is lightweight +} +``` + +之所以能这样使用,这是因为 Connection 并不是一个简单的 socket 连接,[接口文档](https://hbase.apache.org/apidocs/org/apache/hadoop/hbase/client/Connection.html) 中对 Connection 的表述是: + +``` +A cluster connection encapsulating lower level individual connections to actual servers and a +connection to zookeeper. Connections are instantiated through the ConnectionFactory class. +The lifecycle of the connection is managed by the caller, who has to close() the connection +to release the resources. + +Connection 是一个集群连接,封装了与多台服务器(Matser/Region Server)的底层连接以及与 zookeeper 的连接。 +连接通过 ConnectionFactory 类实例化。连接的生命周期由调用者管理,调用者必须使用 close() 关闭连接以释放资源。 +``` + +之所以封装这些连接,是因为 HBase 客户端需要连接三个不同的服务角色: + +- **Zookeeper** :主要用于获取 `meta` 表的位置信息,Master 的信息; +- **HBase Master** :主要用于执行 HBaseAdmin 接口的一些操作,例如建表等; +- **HBase RegionServer** :用于读、写数据。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20230315202403.png) + +Connection 对象和实际的 Socket 连接之间的对应关系如下图: + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20230315202426.png) + +在 HBase 客户端代码中,真正对应 Socket 连接的是 `RpcConnection` 对象。HBase 使用 `PoolMap` 这种数据结构来存储客户端到 HBase 服务器之间的连接。`PoolMap` 的内部有一个 `ConcurrentHashMap` 实例,其 key 是 `ConnectionId`(封装了服务器地址和用户 ticket),value 是一个 `RpcConnection` 对象的资源池。当 HBase 需要连接一个服务器时,首先会根据 `ConnectionId` 找到对应的连接池,然后从连接池中取出一个连接对象。 + +``` +@InterfaceAudience.Private +public class PoolMap implements Map { + private PoolType poolType; + + private int poolMaxSize; + + private Map> pools = new ConcurrentHashMap<>(); + + public PoolMap(PoolType poolType) { + this.poolType = poolType; + } + ..... +``` + +HBase 中提供了三种资源池的实现,分别是 `Reusable`,`RoundRobin` 和 `ThreadLocal`。具体实现可以通 `hbase.client.ipc.pool.type` 配置项指定,默认为 `Reusable`。连接池的大小也可以通过 `hbase.client.ipc.pool.size` 配置项指定,默认为 1,即每个 Server 1 个连接。也可以通过修改配置实现: + +``` +config.set("hbase.client.ipc.pool.type",...); +config.set("hbase.client.ipc.pool.size",...); +connection = ConnectionFactory.createConnection(config); +``` + +由此可以看出 HBase 中 Connection 类已经实现了对连接的管理功能,所以我们不必在 Connection 上在做额外的管理。 + +另外,Connection 是线程安全的,但 Table 和 Admin 却不是线程安全的,因此正确的做法是一个进程共用一个 Connection 对象,而在不同的线程中使用单独的 Table 和 Admin 对象。Table 和 Admin 的获取操作 `getTable()` 和 `getAdmin()` 都是轻量级,所以不必担心性能的消耗,同时建议在使用完成后显示的调用 `close()` 方法来关闭它们。 + +## 概述 + +HBase 的主要客户端操作是由 `org.apache.hadoop.hbase.client.HTable` 提供的。创建 HTable 实例非常耗时,所以,建议每个线程只创建一次 HTable 实例。 + +HBase 所有修改数据的操作都保证了行级别的原子性。要么读到最新的修改,要么等待系统允许写入改行修改 + +用户要尽量使用批处理(batch)更新来减少单独操作同一行数据的次数 + +写操作中设计的列的数目并不会影响该行数据的原子性,行原子性会同时保护到所有列 + +创建 HTable 实例(指的是在 java 中新建该类),每个实例都要扫描.META. 表,以检查该表是否存在,推荐用户只创建一次 HTable 实例,而且是每个线程创建一个 + +如果用户需要多个 HTable 实例,建议使用 HTablePool 类(类似连接池) + +## CRUD 操作 + +### put + +`Table` 接口提供了两个 `put` 方法 + +```java +// 写入单行 put +void put(Put put) throws IOException; +// 批量写入 put +void put(List puts) throws IOException; +``` + +Put 类提供了多种构造器方法用来初始化实例。 + +Put 类还提供了一系列有用的方法: + +多个 `add` 方法:用于添加指定的列数据。 + +`has` 方法:用于检查是否存在特定的单元格,而不需要遍历整个集合 + +`getFamilyMap` 方法:可以遍历 Put 实例中每一个可用的 KeyValue 实例 + +getRow 方法:用于获取 rowkey +Put.heapSize() 可以计算当前 Put 实例所需的堆大小,既包含其中的数据,也包含内部数据结构所需的空间 + +#### KeyValue 类 + +特定单元格的数据以及坐标,坐标包括行键、列族名、列限定符以及时间戳 +`KeyValue(byte[] row, int roffset, int rlength, byte[] family, int foffoset, int flength, byte[] qualifier, int qoffset, int qlength, long timestamp, Type type, byte[] value, int voffset, int vlength)` +每一个字节数组都有一个 offset 参数和一个 length 参数,允许用户提交一个已经存在的字节数组进行字节级别操作。 +行目前来说指的是行键,即 Put 构造器里的 row 参数。 + +#### 客户端的写缓冲区 + +每一个 put 操作实际上都是一个 RPC 操作,它将客户端数据传送到服务器然后返回。 + +HBase 的 API 配备了一个客户端的写缓冲区,缓冲区负责收集 put 操作,然后调用 RPC 操作一次性将 put 送往服务器。 + +```java +void setAutoFlush(boolean autoFlush) +boolean isAutoFlush() +``` + +默认情况下,客户端缓冲区是禁用的。可以通过 `table.setAutoFlush(false)` 来激活缓冲区。 + +#### Put 列表 + +批量提交 `put` 列表: + +```java +void put(List puts) throws IOException +``` + +注意:批量提交可能会有部分修改失败。 + +#### 原子性操作 compare-and-set + +`checkAndPut` 方法提供了 CAS 机制来保证 put 操作的原子性。 + +### get + +``` +Result get(Get get) throws IOException +``` + +```csharp +Get(byte[] row) +Get(byte[] row, RowLock rowLock) +Get addColumn(byte[] family, byte[] qualifier) +Get addFamily(byte[] family) +``` + +#### Result 类 + +当用户使用 `get()` 方法获取数据,HBase 返回的结果包含所有匹配的单元格数据,这些数据被封装在一个 `Result` 实例中返回给用户。 + +Result 类提供的方法如下: + +```java +byte[] getValue(byte[] family, byte[] qualifier) +byte[] value() +byte[] getRow() +int size() +boolean isEmpty() +KeyValue[] raw() +List list() +``` + +## delete + +``` +void delete(Delete delete) throws IOException +``` + +```csharp +Delte(byte[] row) +Delete(byte[] row, long timestamp, RowLock rowLock) +``` + +```csharp +Delete deleteFamily(byte[] family) +Delete deleteFamily(byte[] family, long timestamp) +Delete deleteColumns(byte[] family, byte[] qualifier) +Delete deleteColumn(byte[] family, byte[] qualifier) // 只删除最新版本 +``` + +## 批处理操作 + +Row 是 Put、Get、Delete 的父类。 + +```java +void batch(List actions, Object[] results) throws IOException, InterruptedException +Object batch(List actions) throws IOException, InterruptedException +``` + +## 行锁 + +region 服务器提供了行锁特性,这个特性保证了只有一个客户端能获取一行数据相应的锁,同时对该行进行修改。 + +如果不显示指定锁,服务器会隐式加锁。 + +## 扫描 + +scan,类似数据库系统中的 cursor,利用了 HBase 提供的底层顺序存储的数据结构。 + +调用 HTable 的 getScanner 就可以返回扫描器 + +```java +ResultScanner getScanner(Scan scan) throws IOException +ResultScanner getScanner(byte[] family) throws IOException +``` + +Scan 类构造器可以有 startRow,区间一般为 [startRow, stopRow) + +```csharp +Scan(byte[] startRow, Filter filter) +Scan(byte[] startRow) +``` + +### ResultScanner + +以行为单位进行返回 + +```java +Result next() throws IOException +Result[] next(int nbRows) throws IOException +void close() +``` + +### 缓存与批量处理 + +每一个 next()调用都会为每行数据生成一个单独的 RPC 请求 + +可以设置扫描器缓存 + +```cpp +void setScannerCaching(itn scannerCaching) +int getScannerCaching() +``` + +缓存是面向行一级操作,批量是面向列一级操作 + +```cpp +void setBatch(int batch) +int getBatch +``` + +RPC 请求的次数=(行数\*每行列数)/Min(每行的列数,批量大小)/扫描器缓存 + +## 各种特性 + +`Bytes` 类提供了一系列将原生 Java 类型和字节数组互转的方法。 + +## 参考资料 + +- [《HBase 权威指南》](https://item.jd.com/11321037.html) +- [《HBase 权威指南》官方源码](https://github.com/larsgeorge/hbase-book) diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/11.HBaseJavaApi\351\253\230\347\272\247\347\211\271\346\200\247\344\271\213\350\277\207\346\273\244\345\231\250.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/11.HBaseJavaApi\351\253\230\347\272\247\347\211\271\346\200\247\344\271\213\350\277\207\346\273\244\345\231\250.md" new file mode 100644 index 00000000..7dbbceb0 --- /dev/null +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/11.HBaseJavaApi\351\253\230\347\272\247\347\211\271\346\200\247\344\271\213\350\277\207\346\273\244\345\231\250.md" @@ -0,0 +1,382 @@ +--- +title: HBase Java API 高级特性之过滤器 +date: 2023-03-16 09:45:10 +categories: + - 数据库 + - 列式数据库 + - HBase +tags: + - 大数据 + - HBase + - API +permalink: /pages/a3347e/ +--- + +# HBase Java API 高级特性之过滤器 + +HBase 中两种主要的数据读取方法是 `get()` 和 `scan()`,它们都支持直接访问数据和通过指定起止 row key 访问数据。此外,可以指定列族、列、时间戳和版本号来进行条件查询。它们的缺点是不支持细粒度的筛选功能。为了弥补这种不足,`Get` 和 `Scan` 支持通过过滤器(`Filter`)对 row key、列或列值进行过滤。 + +HBase 提供了一些内置过滤器,也允许用户通过继承 `Filter` 类来自定义过滤器。所有的过滤器都在服务端生效,称为 **谓词下推**。这样可以保证被过滤掉的数据不会被传到客户端。 + +![](https://www.oreilly.com/api/v2/epubs/9781449314682/files/httpatomoreillycomsourceoreillyimages889252.png) + +_图片来自 HBase 权威指南_ + +HBase 过滤器层次结构的最底层是 `Filter` 接口和 `FilterBase` 抽象类。大部分过滤器都直接继承自 `FilterBase`。 + +## 比较过滤器 + +所有比较过滤器均继承自 `CompareFilter`。`CompareFilter` 比 `FilterBase` 多了一个 `compare()` 方法,它需要传入参数定义比较操作的过程:比较运算符和比较器。 + +创建一个比较过滤器需要两个参数,分别是**比较运算符**和**比较器实例**。 + +``` + public CompareFilter(final CompareOp compareOp,final ByteArrayComparable comparator) { + this.compareOp = compareOp; + this.comparator = comparator; + } +``` + +### 比较运算符 + +- LESS (<) +- LESS_OR_EQUAL (<=) +- EQUAL (=) +- NOT_EQUAL (!=) +- GREATER_OR_EQUAL (>=) +- GREATER (>) +- NO_OP (排除所有符合条件的值) + +比较运算符均定义在枚举类 `CompareOperator` 中 + +``` +@InterfaceAudience.Public +public enum CompareOperator { + LESS, + LESS_OR_EQUAL, + EQUAL, + NOT_EQUAL, + GREATER_OR_EQUAL, + GREATER, + NO_OP, +} +``` + +> 注意:在 1.x 版本的 HBase 中,比较运算符定义在 `CompareFilter.CompareOp` 枚举类中,但在 2.0 之后这个类就被标识为 @deprecated ,并会在 3.0 移除。所以 2.0 之后版本的 HBase 需要使用 `CompareOperator` 这个枚举类。 + +### 比较器 + +所有比较器均继承自 `ByteArrayComparable` 抽象类,常用的有以下几种: + +- **BinaryComparator** : 使用 `Bytes.compareTo(byte [],byte [])` 按字典序比较指定的字节数组。 +- **BinaryPrefixComparator** : 按字典序与指定的字节数组进行比较,但只比较到这个字节数组的长度。 +- **RegexStringComparator** : 使用给定的正则表达式与指定的字节数组进行比较。仅支持 `EQUAL` 和 `NOT_EQUAL` 操作。 +- **SubStringComparator** : 测试给定的子字符串是否出现在指定的字节数组中,比较不区分大小写。仅支持 `EQUAL` 和 `NOT_EQUAL` 操作。 +- **NullComparator** :判断给定的值是否为空。 +- **BitComparator** :按位进行比较。 + +`BinaryPrefixComparator` 和 `BinaryComparator` 的区别不是很好理解,这里举例说明一下: + +在进行 `EQUAL` 的比较时,如果比较器传入的是 `abcd` 的字节数组,但是待比较数据是 `abcdefgh`: + +- 如果使用的是 `BinaryPrefixComparator` 比较器,则比较以 `abcd` 字节数组的长度为准,即 `efgh` 不会参与比较,这时候认为 `abcd` 与 `abcdefgh` 是满足 `EQUAL` 条件的; +- 如果使用的是 `BinaryComparator` 比较器,则认为其是不相等的。 + +### 比较过滤器种类 + +比较过滤器共有五个(Hbase 1.x 版本和 2.x 版本相同): + +- **RowFilter** :基于行键来过滤数据; +- **FamilyFilterr** :基于列族来过滤数据; +- **QualifierFilterr** :基于列限定符(列名)来过滤数据; +- **ValueFilterr** :基于单元格 (cell) 的值来过滤数据; +- **DependentColumnFilter** :指定一个参考列来过滤其他列的过滤器,过滤的原则是基于参考列的时间戳来进行筛选 。 + +前四种过滤器的使用方法相同,均只要传递比较运算符和运算器实例即可构建,然后通过 `setFilter` 方法传递给 `scan`: + +``` + Filter filter = new RowFilter(CompareOperator.LESS_OR_EQUAL, + new BinaryComparator(Bytes.toBytes("xxx"))); + scan.setFilter(filter); +``` + +`DependentColumnFilter` 的使用稍微复杂一点,这里单独做下说明。 + +### DependentColumnFilter + +可以把 `DependentColumnFilter` 理解为**一个 valueFilter 和一个时间戳过滤器的组合**。`DependentColumnFilter` 有三个带参构造器,这里选择一个参数最全的进行说明: + +``` +DependentColumnFilter(final byte [] family, final byte[] qualifier, + final boolean dropDependentColumn, final CompareOperator op, + final ByteArrayComparable valueComparator) +``` + +- **family** :列族 +- **qualifier** :列限定符(列名) +- **dropDependentColumn** :决定参考列是否被包含在返回结果内,为 true 时表示参考列被返回,为 false 时表示被丢弃 +- **op** :比较运算符 +- **valueComparator** :比较器 + +这里举例进行说明: + +``` +DependentColumnFilter dependentColumnFilter = new DependentColumnFilter( + Bytes.toBytes("student"), + Bytes.toBytes("name"), + false, + CompareOperator.EQUAL, + new BinaryPrefixComparator(Bytes.toBytes("xiaolan"))); +``` + +- 首先会去查找 `student:name` 中值以 `xiaolan` 开头的所有数据获得 `参考数据集`,这一步等同于 valueFilter 过滤器; +- 其次再用参考数据集中所有数据的时间戳去检索其他列,获得时间戳相同的其他列的数据作为 `结果数据集`,这一步等同于时间戳过滤器; +- 最后如果 `dropDependentColumn` 为 true,则返回 `参考数据集`+`结果数据集`,若为 false,则抛弃参考数据集,只返回 `结果数据集`。 + +## 专用过滤器 + +专用过滤器通常直接继承自 `FilterBase`,用于更特定的场景。 + +### 单列列值过滤器 (SingleColumnValueFilter) + +基于某列(参考列)的值决定某行数据是否被过滤。其实例有以下方法: + +- **setFilterIfMissing(boolean filterIfMissing)** :默认值为 false,即如果该行数据不包含参考列,其依然被包含在最后的结果中;设置为 true 时,则不包含; +- **setLatestVersionOnly(boolean latestVersionOnly)** :默认为 true,即只检索参考列的最新版本数据;设置为 false,则检索所有版本数据。 + +``` +SingleColumnValueFilter singleColumnValueFilter = new SingleColumnValueFilter( + "student".getBytes(), + "name".getBytes(), + CompareOperator.EQUAL, + new SubstringComparator("xiaolan")); +singleColumnValueFilter.setFilterIfMissing(true); +scan.setFilter(singleColumnValueFilter); +``` + +### 单列列值排除器 (SingleColumnValueExcludeFilter) + +`SingleColumnValueExcludeFilter` 继承自上面的 `SingleColumnValueFilter`,过滤行为与其相反。 + +### 行键前缀过滤器 (PrefixFilter) + +基于 RowKey 值决定某行数据是否被过滤。 + +``` +PrefixFilter prefixFilter = new PrefixFilter(Bytes.toBytes("xxx")); +scan.setFilter(prefixFilter); +``` + +### 列名前缀过滤器 (ColumnPrefixFilter) + +基于列限定符(列名)决定某行数据是否被过滤。 + +``` +ColumnPrefixFilter columnPrefixFilter = new ColumnPrefixFilter(Bytes.toBytes("xxx")); + scan.setFilter(columnPrefixFilter); +``` + +### 分页过滤器 (PageFilter) + +可以使用这个过滤器实现对结果按行进行分页,创建 PageFilter 实例的时候需要传入每页的行数。 + +``` +public PageFilter(final long pageSize) { + Preconditions.checkArgument(pageSize >= 0, "must be positive %s", pageSize); + this.pageSize = pageSize; + } +``` + +下面的代码体现了客户端实现分页查询的主要逻辑,这里对其进行一下解释说明: + +客户端进行分页查询,需要传递 `startRow`(起始 RowKey),知道起始 `startRow` 后,就可以返回对应的 pageSize 行数据。这里唯一的问题就是,对于第一次查询,显然 `startRow` 就是表格的第一行数据,但是之后第二次、第三次查询我们并不知道 `startRow`,只能知道上一次查询的最后一条数据的 RowKey(简单称之为 `lastRow`)。 + +我们不能将 `lastRow` 作为新一次查询的 `startRow` 传入,因为 scan 的查询区间是[startRow,endRow) ,即前开后闭区间,这样 `startRow` 在新的查询也会被返回,这条数据就重复了。 + +同时在不使用第三方数据库存储 RowKey 的情况下,我们是无法通过知道 `lastRow` 的下一个 RowKey 的,因为 RowKey 的设计可能是连续的也有可能是不连续的。 + +由于 Hbase 的 RowKey 是按照字典序进行排序的。这种情况下,就可以在 `lastRow` 后面加上 `0` ,作为 `startRow` 传入,因为按照字典序的规则,某个值加上 `0` 后的新值,在字典序上一定是这个值的下一个值,对于 HBase 来说下一个 RowKey 在字典序上一定也是等于或者大于这个新值的。 + +所以最后传入 `lastRow`+`0`,如果等于这个值的 RowKey 存在就从这个值开始 scan,否则从字典序的下一个 RowKey 开始 scan。 + +> 25 个字母以及数字字符,字典排序如下: +> +> ``` +> '0' < '1' < '2' < ... < '9' < 'a' < 'b' < ... < 'z' +> ``` + +分页查询主要实现逻辑: + +``` +byte[] POSTFIX = new byte[] { 0x00 }; +Filter filter = new PageFilter(15); + +int totalRows = 0; +byte[] lastRow = null; +while (true) { + Scan scan = new Scan(); + scan.setFilter(filter); + if (lastRow != null) { + // 如果不是首行 则 lastRow + 0 + byte[] startRow = Bytes.add(lastRow, POSTFIX); + System.out.println("start row: " + + Bytes.toStringBinary(startRow)); + scan.withStartRow(startRow); + } + ResultScanner scanner = table.getScanner(scan); + int localRows = 0; + Result result; + while ((result = scanner.next()) != null) { + System.out.println(localRows++ + ": " + result); + totalRows++; + lastRow = result.getRow(); + } + scanner.close(); + //最后一页,查询结束 + if (localRows == 0) break; +} +System.out.println("total rows: " + totalRows); +``` + +> 需要注意的是在多台 Regin Services 上执行分页过滤的时候,由于并行执行的过滤器不能共享它们的状态和边界,所以有可能每个过滤器都会在完成扫描前获取了 PageCount 行的结果,这种情况下会返回比分页条数更多的数据,分页过滤器就有失效的可能。 + +### 时间戳过滤器 (TimestampsFilter) + +``` +List list = new ArrayList<>(); +list.add(1554975573000L); +TimestampsFilter timestampsFilter = new TimestampsFilter(list); +scan.setFilter(timestampsFilter); +``` + +### 首次行键过滤器 (FirstKeyOnlyFilter) + +`FirstKeyOnlyFilter` 只扫描每行的第一列,扫描完第一列后就结束对当前行的扫描,并跳转到下一行。相比于全表扫描,其性能更好,通常用于行数统计的场景,因为如果某一行存在,则行中必然至少有一列。 + +``` +FirstKeyOnlyFilter firstKeyOnlyFilter = new FirstKeyOnlyFilter(); +scan.set(firstKeyOnlyFilter); +``` + +## 包装过滤器 + +包装过滤器就是通过包装其他过滤器以实现某些拓展的功能。 + +### SkipFilter 过滤器 + +`SkipFilter` 包装一个过滤器,当被包装的过滤器遇到一个需要过滤的 KeyValue 实例时,则拓展过滤整行数据。下面是一个使用示例: + +``` +// 定义 ValueFilter 过滤器 +Filter filter1 = new ValueFilter(CompareOperator.NOT_EQUAL, + new BinaryComparator(Bytes.toBytes("xxx"))); +// 使用 SkipFilter 进行包装 +Filter filter2 = new SkipFilter(filter1); +``` + +### WhileMatchFilter 过滤器 + +`WhileMatchFilter` 包装一个过滤器,当被包装的过滤器遇到一个需要过滤的 KeyValue 实例时,`WhileMatchFilter` 则结束本次扫描,返回已经扫描到的结果。下面是其使用示例: + +``` +Filter filter1 = new RowFilter(CompareOperator.NOT_EQUAL, + new BinaryComparator(Bytes.toBytes("rowKey4"))); + +Scan scan = new Scan(); +scan.setFilter(filter1); +ResultScanner scanner1 = table.getScanner(scan); +for (Result result : scanner1) { + for (Cell cell : result.listCells()) { + System.out.println(cell); + } +} +scanner1.close(); + +System.out.println("--------------------"); + +// 使用 WhileMatchFilter 进行包装 +Filter filter2 = new WhileMatchFilter(filter1); + +scan.setFilter(filter2); +ResultScanner scanner2 = table.getScanner(scan); +for (Result result : scanner1) { + for (Cell cell : result.listCells()) { + System.out.println(cell); + } +} +scanner2.close(); +rowKey0/student:name/1555035006994/Put/vlen=8/seqid=0 +rowKey1/student:name/1555035007019/Put/vlen=8/seqid=0 +rowKey2/student:name/1555035007025/Put/vlen=8/seqid=0 +rowKey3/student:name/1555035007037/Put/vlen=8/seqid=0 +rowKey5/student:name/1555035007051/Put/vlen=8/seqid=0 +rowKey6/student:name/1555035007057/Put/vlen=8/seqid=0 +rowKey7/student:name/1555035007062/Put/vlen=8/seqid=0 +rowKey8/student:name/1555035007068/Put/vlen=8/seqid=0 +rowKey9/student:name/1555035007073/Put/vlen=8/seqid=0 +-------------------- +rowKey0/student:name/1555035006994/Put/vlen=8/seqid=0 +rowKey1/student:name/1555035007019/Put/vlen=8/seqid=0 +rowKey2/student:name/1555035007025/Put/vlen=8/seqid=0 +rowKey3/student:name/1555035007037/Put/vlen=8/seqid=0 +``` + +可以看到被包装后,只返回了 `rowKey4` 之前的数据。 + +## FilterList + +以上都是讲解单个过滤器的作用,当需要多个过滤器共同作用于一次查询的时候,就需要使用 `FilterList`。`FilterList` 支持通过构造器或者 `addFilter` 方法传入多个过滤器。 + +``` +// 构造器传入 +public FilterList(final Operator operator, final List filters) +public FilterList(final List filters) +public FilterList(final Filter... filters) + +// 方法传入 + public void addFilter(List filters) + public void addFilter(Filter filter) +``` + +多个过滤器组合的结果由 `operator` 参数定义 ,其可选参数定义在 `Operator` 枚举类中。只有 `MUST_PASS_ALL` 和 `MUST_PASS_ONE` 两个可选的值: + +- **MUST_PASS_ALL** :相当于 AND,必须所有的过滤器都通过才认为通过; +- **MUST_PASS_ONE** :相当于 OR,只有要一个过滤器通过则认为通过。 + +``` +@InterfaceAudience.Public + public enum Operator { + /** !AND */ + MUST_PASS_ALL, + /** !OR */ + MUST_PASS_ONE + } +``` + +使用示例如下: + +``` +List filters = new ArrayList(); + +Filter filter1 = new RowFilter(CompareOperator.GREATER_OR_EQUAL, + new BinaryComparator(Bytes.toBytes("XXX"))); +filters.add(filter1); + +Filter filter2 = new RowFilter(CompareOperator.LESS_OR_EQUAL, + new BinaryComparator(Bytes.toBytes("YYY"))); +filters.add(filter2); + +Filter filter3 = new QualifierFilter(CompareOperator.EQUAL, + new RegexStringComparator("ZZZ")); +filters.add(filter3); + +FilterList filterList = new FilterList(filters); + +Scan scan = new Scan(); +scan.setFilter(filterList); +``` + +## 参考资料 + +- [《HBase 权威指南》](https://item.jd.com/11321037.html) +- [《HBase 权威指南》官方源码](https://github.com/larsgeorge/hbase-book) diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/12.HBaseJavaApi\351\253\230\347\272\247\347\211\271\346\200\247\344\271\213\345\215\217\345\244\204\347\220\206\345\231\250.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/12.HBaseJavaApi\351\253\230\347\272\247\347\211\271\346\200\247\344\271\213\345\215\217\345\244\204\347\220\206\345\231\250.md" new file mode 100644 index 00000000..b6db0202 --- /dev/null +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/12.HBaseJavaApi\351\253\230\347\272\247\347\211\271\346\200\247\344\271\213\345\215\217\345\244\204\347\220\206\345\231\250.md" @@ -0,0 +1,24 @@ +--- +title: HBase Java API 高级特性之协处理器 +date: 2023-03-16 09:46:37 +categories: + - 数据库 + - 列式数据库 + - HBase +tags: + - 大数据 + - HBase + - API +permalink: /pages/5f1bc3/ +--- + +# HBase Java API 高级特性之协处理器 + +## 简述 + +在使用 HBase 时,如果你的数据量达到了数十亿行或数百万列,此时能否在查询中返回大量数据将受制于网络的带宽,即便网络状况允许,但是客户端的计算处理也未必能够满足要求。在这种情况下,协处理器(Coprocessors)应运而生。它允许你将业务计算代码放入在 RegionServer 的协处理器中,将处理好的数据再返回给客户端,这可以极大地降低需要传输的数据量,从而获得性能上的提升。同时协处理器也允许用户扩展实现 HBase 目前所不具备的功能,如权限校验、二级索引、完整性约束等。 + +## 参考资料 + +- [《HBase 权威指南》](https://item.jd.com/11321037.html) +- [《HBase 权威指南》官方源码](https://github.com/larsgeorge/hbase-book) diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/13.HBaseJavaApi\345\205\266\344\273\226\351\253\230\347\272\247\347\211\271\346\200\247.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/13.HBaseJavaApi\345\205\266\344\273\226\351\253\230\347\272\247\347\211\271\346\200\247.md" new file mode 100644 index 00000000..ee69608c --- /dev/null +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/13.HBaseJavaApi\345\205\266\344\273\226\351\253\230\347\272\247\347\211\271\346\200\247.md" @@ -0,0 +1,157 @@ +--- +title: HBase Java API 其他高级特性 +date: 2023-03-31 16:20:27 +categories: + - 数据库 + - 列式数据库 + - HBase +tags: + - 大数据 + - HBase + - API +permalink: /pages/ce5ca0/ +--- + +# HBase Java API 其他高级特性 + +## 计数器 + +HBase 提供了一种高级功能:计数器(counter)。**HBase 计数器可以用于实时统计,无需延时较高的批量处理操作**。HBase 有一种机制可以将列当作计数器:即读取并修改(其实就是一种 CAS 模式),其保证了在一次操作中的原子性。否则,用户需要对一行数据加锁,然后读取数据,再对当前数据做加法,最后写回 HBase 并释放行锁,这一系列操作会引起大量的资源竞争问题。 + +早期的 HBase 版本会在每次计数器更新操作调用一次 RPC 请求,新版本中可以在一次 RPC 请求中完成多个计数器的更新操作,但是多个计数器必须在同一行。 + +### 计数器使用 Shell 命令行 + +计数器不需要初始化,创建一个新列时初始值为 0,第一次 `incr` 操作返回 1。 + +计数器使用 `incr` 命令,增量可以是正数也可以是负数,但是必须是长整数 Long: + +```bash +incr '','','',[''] +``` + +计数器使用的例子: + +```python +hbase(main):001:0> create 'counters','daily','weekly','monthly' +0 row(s) in 1.2260 seconds + +hbase(main):002:0> incr 'counters','20190301','daily:hites',1 +COUNTER VALUE = 1 + +hbase(main):003:0> incr'counters','20190301','daily:hites',1 +COUNTER VALUE = 2 + +hbase(main):004:0> get_counter 'counters','20190301','daily:hites' +COUNTER VALUE = 2 +``` + +需要注意的是,增加的参数必须是长整型 Long,如果按照错误的格式更新了计数器(如字符串格式),下次调用 `incr` 会得到错误的结果: + +```python +hbase(main):005:0> put 'counters','20190301','daily:clicks','1' +0 row(s) in 1.3250 seconds + +hbase(main):006:0> incr'counters','20190301','daily:clicks',1 +COUNTER VALUE = 3530822107858468865 +``` + +### 单计数器 + +操作一个计数器,类似 shell 命令 `incr` + +```java +HTable table = new HTable(conf, "counters"); + +long cnt1 = table.incrementColumnValue(Bytes.toBytes("20190301"), + Bytes.toBytes("daily"), + Bytes.toBytes("hits"), + 1L); + +long cnt2 = table.incrementColumnValue(Bytes.toBytes("20190301"), + Bytes.toBytes("daily"), + Bytes.toBytes("hits"), + 1L); + +long current = table.incrementColumnValue(Bytes.toBytes("20190301"), + Bytes.toBytes("daily"), + Bytes.toBytes("hits"), + 0); +``` + +### 多计数器 + +使用 `Table` 的 `increment()` 方法可以操作一行的多个计数器,需要构建 `Increment` 实例,并且指定行键: + +```cpp +HTable table = new HTable(conf, "counters"); + +Increment incr1 = new Increment(Bytes.toBytes("20190301")); +incr1.addColumn(Bytes.toBytes("daily"), Bytes.toBytes("clicks"),1); +incr1.addColumn(Bytes.toBytes("daily"), Bytes.toBytes("hits"), 1); +incr1.addColumn(Bytes.toBytes("weekly"), Bytes.toBytes("clicks"), 2); +incr1.addColumn(Bytes.toBytes("weekly"), Bytes.toBytes("hits"), 2); + +Result result = table.increment(incr1); +for(Cell cell : result.rawCells()) { + // ... +} +``` + +Increment 类还有一种构造器: + +```csharp +Increment(byte[] row, RowLock rowLock) +``` + +`rowLock` 参数可选,可以设置用户自定义锁,可以限制其他写程序操作此行,但是不保证读的操作性。 + +## 连接管理 + +### 连接管理简介 + +在 HBase Java API 中,`Connection` 类代表了一个集群连接,封装了与多台服务器(Matser/Region Server)的底层连接以及与 zookeeper 的连接。`Connection` 通过 `ConnectionFactory` 类实例化,而连接的生命周期则由调用者管理,调用者必须显示调用 `close()` 来释放连接。`Connection` 是线程安全的。创建 `Connection` 实例的开销很高,因此一个进程只需要实例化一个 `Connection` 即可。 + +`Table` 接口用于对指定的 HBase 表进行 CRUD 操作。一般,通过 `Connection` 获取 `Table` 实例,用完后,调用 `close()` 释放连接。 + +`Admin` 接口主要用于创建、删除、查看、启用/禁用 HBase 表,以及一些其他管理操作。一般,通过 `Connection` 获取 `Admin` 实例,用完后,调用 `close()` 释放连接。 + +`Table` 和 `Admin` 实例都是轻量级且并非线程安全的。建议每个线程只实例化一个 `Table` 或 `Admin` 实例。 + +### 连接池 + +问题:HBase 为什么没有提供 `Connection` 的连接池来获取更好的性能?是否需要自定义 `Connection` 连接池? + +答:不需要。官方对于 `Connection` 的使用说明中,明确指出:对于高并发多线程访问的应用程序,一个进程中只需要预先创建一个 `Connection`。 + +问题:HBase 老版本中 `HTablePool` 为什么废弃?是否需要自定义 Table 的连接池? + +答:不需要。Table 和 Admin 的连接本质上是复用 Connection,实例化是一个较为轻量级的操作,因此,并不需要缓存或池化。实际上,HBase Java API 官方就是这么建议的。 + +下面是管理 HBase 连接的一个正确编程模型 + +```java +// 所有进程共用一个 connection 对象 +connection = ConnectionFactory.createConnection(config); + +// 每个线程使用单独的 table 对象 +Table table = connection.getTable(TableName.valueOf("tableName")); +try { + ... +} finally { + table.close(); +} + +Admin admin = connection.getAdmin(); +try { + ... +} finally { + admin.close(); +} +``` + +## 参考资料 + +- [《HBase 权威指南》](https://item.jd.com/11321037.html) +- [《HBase 权威指南》官方源码](https://github.com/larsgeorge/hbase-book) +- [连接 HBase 的正确姿势](https://developer.aliyun.com/article/581702) diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/14.HBaseJavaApi\347\256\241\347\220\206\345\212\237\350\203\275.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/14.HBaseJavaApi\347\256\241\347\220\206\345\212\237\350\203\275.md" new file mode 100644 index 00000000..3915ce86 --- /dev/null +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/14.HBaseJavaApi\347\256\241\347\220\206\345\212\237\350\203\275.md" @@ -0,0 +1,124 @@ +--- +title: HBase Java API 管理功能 +date: 2023-04-13 16:36:48 +categories: + - 数据库 + - 列式数据库 + - HBase +tags: + - 大数据 + - HBase + - API +permalink: /pages/b59ba2/ +--- + +# HBase Java API 管理功能 + +## 初始化 Admin 实例 + +```java +Configuration conf = HBaseConfiguration.create(); +Connection connection = ConnectionFactory.createConnection(conf); +Admin admin = connection.getAdmin(); +``` + +## 管理命名空间 + +### 查看命名空间 + +```java +TableName[] tableNames = admin.listTableNamesByNamespace("test"); +for (TableName tableName : tableNames) { + System.out.println(tableName.getName()); +} +``` + +### 创建命名空间 + +```java +NamespaceDescriptor namespace = NamespaceDescriptor.create("test").build(); +admin.createNamespace(namespace); +``` + +### 修改命名空间 + +```java +NamespaceDescriptor namespace = NamespaceDescriptor.create("test") + .addConfiguration("Description", "Test Namespace") + .build(); +admin.modifyNamespace(namespace); +``` + +### 删除命名空间 + +```java +admin.deleteNamespace("test"); +``` + +## 管理表 + +### 创建表 + +```java +TableName tableName = TableName.valueOf("test:test"); +HTableDescriptor tableDescriptor = new HTableDescriptor(tableName); +HColumnDescriptor columnDescriptor = new HColumnDescriptor(Bytes.toBytes("cf")); +tableDescriptor.addFamily(columnDescriptor); +admin.createTable(tableDescriptor); +``` + +### 删除表 + +```java +admin.deleteTable(TableName.valueOf("test:test")); +``` + +### 修改表 + +```java +// 原始表 +TableName tableName = TableName.valueOf("test:test"); +HColumnDescriptor columnDescriptor = new HColumnDescriptor("cf1"); +HTableDescriptor tableDescriptor = new HTableDescriptor(tableName) + .addFamily(columnDescriptor) + .setValue("Description", "Original Table"); +admin.createTable(tableDescriptor, Bytes.toBytes(1L), Bytes.toBytes(10000L), 50); + +// 修改表 +HTableDescriptor newTableDescriptor = admin.getTableDescriptor(tableName); +HColumnDescriptor newColumnDescriptor = new HColumnDescriptor("cf2"); +newTableDescriptor.addFamily(newColumnDescriptor) + .setMaxFileSize(1024 * 1024 * 1024L) + .setValue("Description", "Modified Table"); + +// 修改表必须先禁用再想修改 +admin.disableTable(tableName); +admin.modifyTable(tableName, newTableDescriptor); +``` + +### 禁用表 + +需要注意:HBase 表在删除前,必须先禁用。 + +```java +admin.disableTable(TableName.valueOf("test:test")); +``` + +### 启用表 + +``` +admin.enableTable(TableName.valueOf("test:test")); +``` + +### 查看表是否有效 + +```java +boolean isOk = admin.isTableAvailable(tableName); +System.out.println("Table available: " + isOk); +``` + +## 参考资料 + +- [《HBase 权威指南》](https://item.jd.com/11321037.html) +- [《HBase 权威指南》官方源码](https://github.com/larsgeorge/hbase-book) +- [连接 HBase 的正确姿势](https://developer.aliyun.com/article/581702) diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/21.HBase\350\277\220\347\273\264.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/21.HBase\350\277\220\347\273\264.md" new file mode 100644 index 00000000..3b9c76cb --- /dev/null +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/21.HBase\350\277\220\347\273\264.md" @@ -0,0 +1,83 @@ +--- +title: HBase 运维 +date: 2019-05-07 20:19:25 +categories: + - 数据库 + - 列式数据库 + - HBase +tags: + - 大数据 + - HBase + - 运维 +permalink: /pages/f808fc/ +--- + +# HBase 运维 + +## 配置文件 + +- `backup-masters` - 默认情况下不存在。列出主服务器应在其上启动备份主进程的主机,每行一个主机。 +- `hadoop-metrics2-hbase.properties` - 用于连接 HBase Hadoop 的 Metrics2 框架。 +- `hbase-env.cmd` and hbase-env.sh - 用于 Windows 和 Linux / Unix 环境的脚本,用于设置 HBase 的工作环境,包括 Java,Java 选项和其他环境变量的位置。 +- `hbase-policy.xml` - RPC 服务器用于对客户端请求进行授权决策的默认策略配置文件。仅在启用 HBase 安全性时使用。 +- `hbase-site.xml` - 主要的 HBase 配置文件。此文件指定覆盖 HBase 默认配置的配置选项。您可以在 docs / hbase-default.xml 中查看(但不要编辑)默认配置文件。您还可以在 HBase Web UI 的 HBase 配置选项卡中查看群集的整个有效配置(默认值和覆盖)。 +- `log4j.properties` - log4j 日志配置。 +- `regionservers` - 包含应在 HBase 集群中运行 RegionServer 的主机列表。默认情况下,此文件包含单个条目 localhost。它应包含主机名或 IP 地址列表,每行一个,并且如果群集中的每个节点将在其 localhost 接口上运行 RegionServer,则应仅包含 localhost。 + +## 环境要求 + +- Java + - HBase 2.0+ 要求 JDK8+ + - HBase 1.2+ 要求 JDK7+ +- SSH - 环境要支持 SSH +- DNS - 环境中要在 hosts 配置本机 hostname 和本机 IP +- NTP - HBase 集群的时间要同步,可以配置统一的 NTP +- 平台 - 生产环境不推荐部署在 Windows 系统中 +- Hadoop - 依赖 Hadoop 配套版本 +- Zookeeper - 依赖 Zookeeper 配套版本 + +## 运行模式 + +### 单点 + +hbase-site.xml 配置如下: + +```xml + + + hbase.rootdir + hdfs://namenode.example.org:8020/hbase + + + hbase.cluster.distributed + false + + +``` + +### 分布式 + +hbase-site.xm 配置如下: + +```xml + + + hbase.rootdir + hdfs://namenode.example.org:8020/hbase + + + hbase.cluster.distributed + true + + + hbase.zookeeper.quorum + node-a.example.com,node-b.example.com,node-c.example.com + + +``` + +## 引用和引申 + +### 扩展阅读 + +- [Apache HBase Configuration](http://hbase.apache.org/book.html#configuration) \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/22.HBase\345\221\275\344\273\244.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/22.HBase\345\221\275\344\273\244.md" new file mode 100644 index 00000000..209ef620 --- /dev/null +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/22.HBase\345\221\275\344\273\244.md" @@ -0,0 +1,205 @@ +--- +title: HBase 命令 +date: 2020-06-02 22:28:18 +categories: + - 数据库 + - 列式数据库 + - HBase +tags: + - 大数据 + - HBase +permalink: /pages/263c40/ +--- + +# HBase 命令 + +> 进入 HBase Shell 控制台:`./bin/hbase shell` +> +> 如果有 kerberos 认证,需要事先使用相应的 keytab 进行一下认证(使用 kinit 命令),认证成功之后再使用 hbase shell 进入可以使用 whoami 命令可查看当前用户. + +## 基本命令 + +- 获取帮助信息:`help` +- 获取命令的详细帮助信息:`help 'status'` +- 查看服务器状态:`status` +- 查看版本信息:`version` +- 查看当前登录用户:`whoami` + +## DDL + +### 创建表 + +【语法】`create '表名称','列族名称 1','列族名称 2','列名称 N'` + +【示例】 + +```shell +# 创建一张名为 test 的表,columnFamliy1、columnFamliy2 是 table1 表的列族。 +create 'test','columnFamliy1','columnFamliy2' +``` + +### 启用、禁用表 + +- 启用表:`enable 'test'` +- 禁用表:`disable 'test'` +- 检查表是否被启用:`is_enabled 'test'` +- 检查表是否被禁用:`is_disabled 'test'` + +### 删除表 + +注意:删除表前需要先禁用表 + +```shell +disable 'test' +drop 'test' +``` + +### 修改表 + +#### 添加列族 + +**命令格式**: alter '表名', '列族名' + +```shell +alter 'test', 'teacherInfo' +``` + +#### 删除列族 + +**命令格式**:alter '表名', {NAME => '列族名', METHOD => 'delete'} + +```shell +alter 'test', {NAME => 'teacherInfo', METHOD => 'delete'} +``` + +#### 更改列族存储版本的限制 + +默认情况下,列族只存储一个版本的数据,如果需要存储多个版本的数据,则需要修改列族的属性。修改后可通过 `desc` 命令查看。 + +```shell +alter 'test',{NAME=>'columnFamliy1',VERSIONS=>3} +``` + +### 查看表 + +- 查看所有表:`list` +- 查看表的详细信息:`describe 'test'` +- 检查表是否存在:`exists 'test'` + +## 增删改 + +### 插入数据 + +**命令格式**:`put '表名', '行键','列族:列','值'` + +**注意:如果新增数据的行键值、列族名、列名与原有数据完全相同,则相当于更新操作** + +```shell +put 'test', 'rowkey1', 'columnFamliy1:a', 'valueA' +put 'test', 'rowkey1', 'columnFamliy1:b', 'valueB' +put 'test', 'rowkey1', 'columnFamliy1:c', 'valueC' + +put 'test', 'rowkey2', 'columnFamliy1:a', 'valueA' +put 'test', 'rowkey2', 'columnFamliy1:b', 'valueB' +put 'test', 'rowkey2', 'columnFamliy1:c', 'valueC' + +put 'test', 'rowkey3', 'columnFamliy1:a', 'valueA' +put 'test', 'rowkey3', 'columnFamliy1:b', 'valueB' +put 'test', 'rowkey3', 'columnFamliy1:c', 'valueC' + +put 'test', 'rowkey1', 'columnFamliy2:a', 'valueA' +put 'test', 'rowkey1', 'columnFamliy2:b', 'valueB' +put 'test', 'rowkey1', 'columnFamliy2:c', 'valueC' +``` + +### 获取指定行、列族、列 + +- 获取指定行中所有列的数据信息:`get 'test','rowkey2'` +- 获取指定行中指定列族下所有列的数据信息:`get 'test','rowkey2','columnFamliy1'` +- 获取指定行中指定列的数据信息:`get 'test','rowkey2','columnFamliy1:a'` + +### 删除指定行、列 + +- 删除指定行:`delete 'test','rowkey2'` +- 删除指定行中指定列的数据:`delete 'test','rowkey2','columnFamliy1:a'` + +## 查询 + +hbase 中访问数据有两种基本的方式: + +- 按指定 rowkey 获取数据:`get` 方法; +- 按指定条件获取数据:`scan` 方法。 + +`scan` 可以设置 begin 和 end 参数来访问一个范围内所有的数据。get 本质上就是 begin 和 end 相等的一种特殊的 scan。 + +### get 查询 + +- 获取指定行中所有列的数据信息:`get 'test','rowkey2'` +- 获取指定行中指定列族下所有列的数据信息:`get 'test','rowkey2','columnFamliy1'` +- 获取指定行中指定列的数据信息:`get 'test','rowkey2','columnFamliy1:a'` + +### scan 查询 + +#### 查询整表数据 + +```shell +scan 'test' +``` + +#### 查询指定列簇的数据 + +```shell +scan 'test', {COLUMN=>'columnFamliy1'} +``` + +#### 条件查询 + +```shell +# 查询指定列的数据 +scan 'test', {COLUMNS=> 'columnFamliy1:a'} +``` + +除了列 `(COLUMNS)` 修饰词外,HBase 还支持 `Limit`(限制查询结果行数),`STARTROW`(`ROWKEY` 起始行,会先根据这个 `key` 定位到 `region`,再向后扫描)、`STOPROW`(结束行)、`TIMERANGE`(限定时间戳范围)、`VERSIONS`(版本数)、和 `FILTER`(按条件过滤行)等。 + +如下代表从 `rowkey2` 这个 `rowkey` 开始,查找下两个行的最新 3 个版本的 name 列的数据: + +```shell +scan 'test', {COLUMNS=> 'columnFamliy1:a',STARTROW => 'rowkey2',STOPROW => 'rowkey3',LIMIT=>2, VERSIONS=>3} +``` + +#### 条件过滤 + +Filter 可以设定一系列条件来进行过滤。如我们要查询值等于 24 的所有数据: + +```shell +scan 'test', FILTER=>"ValueFilter(=,'binary:24')" +``` + +值包含 valueA 的所有数据: + +```shell +scan 'test', FILTER=>"ValueFilter(=,'substring:valueA')" +``` + +列名中的前缀为 b 的: + +```shell +scan 'test', FILTER=>"ColumnPrefixFilter('b')" +``` + +FILTER 中支持多个过滤条件通过括号、AND 和 OR 进行组合: + +```shell +# 列名中的前缀为 b 且列值中包含1998的数据 +scan 'test', FILTER=>"ColumnPrefixFilter('b') AND ValueFilter ValueFilter(=,'substring:A')" +``` + +`PrefixFilter` 用于对 Rowkey 的前缀进行判断: + +```shell +scan 'test', FILTER=>"PrefixFilter('wr')" +``` + +## 参考资料 + +- [Hbase 常用 Shell 命令](https://github.com/heibaiying/BigData-Notes/blob/master/notes/Hbase_Shell.md) \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/README.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/README.md" new file mode 100644 index 00000000..588cb320 --- /dev/null +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/README.md" @@ -0,0 +1,50 @@ +--- +title: HBase 教程 +date: 2020-09-09 17:53:08 +categories: + - 数据库 + - 列式数据库 + - HBase +tags: + - 大数据 + - HBase +permalink: /pages/417be6/ +hidden: true +--- + +# HBase 教程 + +## 📖 内容 + +- [HBase 快速入门](01.HBase快速入门.md) +- [HBase 数据模型](02.HBase数据模型.md) +- [HBase Schema 设计](03.HBaseSchema设计.md) +- [HBase 架构](04.HBase架构.md) +- [HBase Java API 基础特性](10.HBaseJavaApi基础特性.md) +- [HBase Java API 高级特性之过滤器](11.HBaseJavaApi高级特性之过滤器.md) +- [HBase Java API 高级特性之协处理器](12.HBaseJavaApi高级特性之协处理器.md) +- [HBase Java API 其他高级特性](13.HBaseJavaApi其他高级特性.md) +- [HBase 运维](21.HBase运维.md) +- [HBase 命令](22.HBase命令.md) +- HBase 配置 +- HBase 灾备 + +## 📚 资料 + +- **官方** + - [HBase 官网](http://hbase.apache.org/) + - [HBase 官方文档](https://hbase.apache.org/book.html) + - [HBase 官方文档中文版](http://abloz.com/hbase/book.html) + - [HBase API](https://hbase.apache.org/apidocs/index.html) +- **教程** + - [BigData-Notes](https://github.com/heibaiying/BigData-Notes) +- **书籍** + - [《Hadoop 权威指南(第四版)》](https://item.jd.com/12109713.html) +- **文章** + - [Bigtable: A Distributed Storage System for Structured Data](https://static.googleusercontent.com/media/research.google.com/zh-CN//archive/bigtable-osdi06.pdf) + - [Intro to HBase](https://www.slideshare.net/alexbaranau/intro-to-hbase) + - [深入理解 Hbase 架构](https://segmentfault.com/a/1190000019959411) + +## 🚪 传送 + +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.Hbase.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.Hbase.md" deleted file mode 100644 index f8b75275..00000000 --- "a/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.Hbase.md" +++ /dev/null @@ -1,139 +0,0 @@ ---- -title: Hbase -date: 2020-02-10 14:27:39 -categories: - - 数据库 - - 列式数据库 -tags: - - 数据库 - - 列式数据库 - - Hbase -permalink: /pages/7ab03c/ ---- - -# HBase - -## 简介 - -HBase 是建立在 HDFS 基础上的面向列的分布式数据库。 - -- HBase 参考了谷歌的 BigTable 建模,实现的编程语言为 Java。 -- 它是 Hadoop 项目的子项目,运行于 HDFS 文件系统之上。 - -HBase 适用场景:实时地随机访问超大数据集。 - -在 [CAP 理论](https://zh.wikipedia.org/wiki/CAP%E5%AE%9A%E7%90%86)中,HBase 属于 CP 类型的系统。 - -## 基础 - -[HBase 维护](HBase运维.md) - -## 原理 - -### 数据模型 - -HBase 是一个面向列的数据库,在表中它由行排序。 - -HBase 表模型结构为: - -- 表(table)是行的集合。 -- 行(row)是列族的集合。 -- 列族(column family)是列的集合。 -- 列(row)是键值对的集合。 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/bigdata/hbase/1551164163369.png) - -HBase 表的单元格(cell)由行和列的坐标交叉决定,是有版本的。默认情况下,版本号是自动分配的,为 HBase 插入单元格时的时间戳。单元格的内容是未解释的字节数组。 - -行的键也是未解释的字节数组,所以理论上,任何数据都可以通过序列化表示成字符串或二进制,从而存为 HBase 的键值。 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/bigdata/hbase/1551164224778.png) - -### HBase 架构 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/bigdata/hbase/1551164744748.png) - -和 HDFS、YARN 一样,HBase 也采用 master / slave 架构: - -- HBase 有一个 master 节点。master 节点负责将区域(region)分配给 region 节点;恢复 region 节点的故障。 -- HBase 有多个 region 节点。region 节点负责零个或多个区域(region)的管理并相应客户端的读写请求。region 节点还负责区域的划分并通知 master 节点有了新的子区域。 - -HBase 依赖 ZooKeeper 来实现故障恢复。 - -#### Regin - -HBase 表按行键范围水平自动划分为区域(region)。每个区域由表中行的子集构成。每个区域由它所属的表、它所含的第一行及最后一行来表示。 - -**区域只不过是表被拆分,并分布在区域服务器。** - -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/bigdata/hbase/1551165887616.png) - -#### Master 服务器 - -区域分配、DDL(create、delete)操作由 HBase master 服务器处理。 - -- master 服务器负责协调 region 服务器 - - 协助区域启动,出现故障恢复或负载均衡情况时,重新分配 region 服务器 - - 监控集群中的所有 region 服务器 -- 支持 DDL 接口(创建、删除、更新表) - -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/bigdata/hbase/1551166513572.png) - -#### Regin 服务器 - -区域服务器运行在 HDFS 数据节点上,具有以下组件 - -- `WAL` - Write Ahead Log 是 HDFS 上的文件。WAL 存储尚未持久存储到永久存储的新数据,它用于在发生故障时进行恢复。 - -- `BlockCache` - 是读缓存。它将频繁读取的数据存储在内存中。至少最近使用的数据在完整时被逐出。 -- `MemStore` - 是写缓存。它存储尚未写入磁盘的新数据。在写入磁盘之前对其进行排序。每个区域每个列族有一个 MemStore。 -- `Hfiles` - 将行存储为磁盘上的排序键值对。 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/bigdata/hbase/1551166602999.png) - -#### ZooKeeper - -HBase 使用 ZooKeeper 作为分布式协调服务来维护集群中的服务器状态。Zookeeper 维护哪些服务器是活动的和可用的,并提供服务器故障通知。集群至少应该有 3 个节点。 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/bigdata/hbase/1551166447147.png) - -## HBase 和 RDBMS - -| HBase | RDBMS | -| --------------------------------------------------- | ------------------------------------------ | -| HBase 无模式,它不具有固定列模式的概念;仅定义列族。 | RDBMS 有它的模式,描述表的整体结构的约束。 | -| 它专门创建为宽表。 HBase 是横向扩展。 | 这些都是细而专为小表。很难形成规模。 | -| 没有任何事务存在于 HBase。 | RDBMS 是事务性的。 | -| 它反规范化的数据。 | 它具有规范化的数据。 | -| 它用于半结构以及结构化数据是非常好的。 | 用于结构化数据非常好。 | - -## API - -Java API 归纳总结在这里:[HBase 应用](hbase-api-java.md) - -## 附录 - -### 命令行 - -HBase 命令行可以参考这里:[HBase 命令行](HBase命令.md) - -## 更多内容 - -### 扩展阅读 - -- [HBase 命令](HBase命令.md) -- [HBase 运维](HBase运维.md) - -### 参考资料 - -#### 官方 - -- [HBase 官网](http://hbase.apache.org/) -- [HBase 官方文档](https://hbase.apache.org/book.html) -- [HBase 官方文档中文版](http://abloz.com/hbase/book.html) -- [HBase API](https://hbase.apache.org/apidocs/index.html) - -#### 文章 - -- [Bigtable: A Distributed Storage System for Structured Data](https://static.googleusercontent.com/media/research.google.com/zh-CN//archive/bigtable-osdi06.pdf) -- [An In-Depth Look at the HBase Architecture](https://mapr.com/blog/in-depth-look-hbase-architecture) diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/02.Cassandra.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/02.Cassandra.md" index d1bb3578..7a18759d 100644 --- "a/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/02.Cassandra.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/02.Cassandra.md" @@ -56,4 +56,4 @@ Cassandra 的主要特点就是它不是一个数据库,而是由一堆数据 ## :door: 传送门 -| [钝悟的博客](https://dunwu.github.io/blog/) | [db-tutorial 首页](https://github.com/dunwu/db-tutorial) | +| [钝悟的博客](https://dunwu.github.io/blog/) | [db-tutorial 首页](https://github.com/dunwu/db-tutorial) | \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/01.Elasticsearch\351\235\242\350\257\225\346\200\273\347\273\223.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/01.Elasticsearch\351\235\242\350\257\225\346\200\273\347\273\223.md" index d1d0f70c..a81581f8 100644 --- "a/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/01.Elasticsearch\351\235\242\350\257\225\346\200\273\347\273\223.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/01.Elasticsearch\351\235\242\350\257\225\346\200\273\347\273\223.md" @@ -643,4 +643,4 @@ d(x,y) + d(y,z) >= d(x,z) -- 三角不等式 (3)查询相似词如下:计算单词与根节点的编辑距离 d,然后递归查找每个子节点标号为 d-n 到 d+n(包含)的边。假如被检查的节点与搜索单词的距离 d 小于 n,则返回该节点并继续查询。比如输入 cape 且最大容忍距离为 1,则先计算和根的编辑距离 d(“book”, “cape”)=4,然后接着找和根节点之间编辑距离为 3 到 5 的,这个就找到了 cake 这个节点,计算 d(“cake”, “cape”)=1,满足条件所以返回 cake,然后再找和 cake 节点编辑距离是 0 到 2 的,分别找到 cape 和 cart 节点,这样就得到 cape 这个满足条件的结果。 -![img](https://pic4.zhimg.com/80/v2-79f2a89041e546d9feccf55e4ff1c0d7_720w.jpg) +![img](https://pic4.zhimg.com/80/v2-79f2a89041e546d9feccf55e4ff1c0d7_720w.jpg) \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/02.Elasticsearch\345\277\253\351\200\237\345\205\245\351\227\250.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/02.Elasticsearch\345\277\253\351\200\237\345\205\245\351\227\250.md" index 955d5281..2ba7638b 100644 --- "a/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/02.Elasticsearch\345\277\253\351\200\237\345\205\245\351\227\250.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/02.Elasticsearch\345\277\253\351\200\237\345\205\245\351\227\250.md" @@ -114,7 +114,7 @@ Document 使用 JSON 格式表示,下面是一个例子。 - 实际的 node 上的 `primary shard` 处理请求,然后将数据同步到 `replica node`。 - `coordinating node` 如果发现 `primary node` 和所有 `replica node` 都搞定之后,就返回响应结果给客户端。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210712104055.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210712104055.png) ### ES 读数据过程 @@ -245,4 +245,4 @@ buffer 每 refresh 一次,就会产生一个 `segment file`,所以默认情 - [Install Elasticsearch with RPM](https://www.elastic.co/guide/en/elasticsearch/reference/current/rpm.html#rpm) - [https://www.ruanyifeng.com/blog/2017/08/elasticsearch.html](https://www.ruanyifeng.com/blog/2017/08/elasticsearch.html) - [es-introduction](https://github.com/doocs/advanced-java/blob/master/docs/high-concurrency/es-introduction.md) - - [es-write-query-search](https://github.com/doocs/advanced-java/blob/master/docs/high-concurrency/es-write-query-search.md) + - [es-write-query-search](https://github.com/doocs/advanced-java/blob/master/docs/high-concurrency/es-write-query-search.md) \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/03.Elasticsearch\347\256\200\344\273\213.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/03.Elasticsearch\347\256\200\344\273\213.md" index 24b2f6f0..74f2c01d 100644 --- "a/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/03.Elasticsearch\347\256\200\344\273\213.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/03.Elasticsearch\347\256\200\344\273\213.md" @@ -122,7 +122,7 @@ Elasticsearch 是一个近乎实时的搜索平台。这意味着**从索引文 #### 倒排索引 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220108215559.PNG) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220108215559.PNG) #### index template @@ -469,4 +469,4 @@ Elasticsearch 是利用分片将数据分发到集群内各处的。分片是数 ## 参考资料 - [Elasticsearch 官网](https://www.elastic.co/) -- [Elasticsearch 简介](https://www.knowledgedict.com/tutorial/elasticsearch-intro.html) +- [Elasticsearch 简介](https://www.knowledgedict.com/tutorial/elasticsearch-intro.html) \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/04.Elasticsearch\347\264\242\345\274\225.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/04.Elasticsearch\347\264\242\345\274\225.md" index cac40c2f..f4ac7d86 100644 --- "a/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/04.Elasticsearch\347\264\242\345\274\225.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/04.Elasticsearch\347\264\242\345\274\225.md" @@ -469,4 +469,4 @@ URI 中允许的参数: ## 参考资料 - [Elasticsearch 官网](https://www.elastic.co/) -- [Elasticsearch 索引映射类型及 mapping 属性详解](https://www.knowledgedict.com/tutorial/elasticsearch-index-mapping.html) +- [Elasticsearch 索引映射类型及 mapping 属性详解](https://www.knowledgedict.com/tutorial/elasticsearch-index-mapping.html) \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/05.Elasticsearch\346\230\240\345\260\204.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/05.Elasticsearch\346\230\240\345\260\204.md" index 0a653246..acd1f5bc 100644 --- "a/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/05.Elasticsearch\346\230\240\345\260\204.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/05.Elasticsearch\346\230\240\345\260\204.md" @@ -351,4 +351,4 @@ Elasticsearch 提供了以下映射参数: ## 参考资料 -- [Elasticsearch 官方文档之 Mapping](https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping.html) +- [Elasticsearch 官方文档之 Mapping](https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping.html) \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/05.Elasticsearch\346\237\245\350\257\242.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/05.Elasticsearch\346\237\245\350\257\242.md" index 2d020b28..16f50b2e 100644 --- "a/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/05.Elasticsearch\346\237\245\350\257\242.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/05.Elasticsearch\346\237\245\350\257\242.md" @@ -1631,4 +1631,4 @@ GET /my-index/_search } ``` -文档符合 query 中的条件,返回结果中可以查到上文中注册的 bool query。percolate query 的这种特性适用于数据分类、数据路由、事件监控和预警等场景。 +文档符合 query 中的条件,返回结果中可以查到上文中注册的 bool query。percolate query 的这种特性适用于数据分类、数据路由、事件监控和预警等场景。 \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/06.Elasticsearch\351\253\230\344\272\256.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/06.Elasticsearch\351\253\230\344\272\256.md" index 683ae842..6c9e404a 100644 --- "a/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/06.Elasticsearch\351\253\230\344\272\256.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/06.Elasticsearch\351\253\230\344\272\256.md" @@ -126,4 +126,4 @@ PUT /example } } } -``` +``` \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/07.Elasticsearch\346\216\222\345\272\217.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/07.Elasticsearch\346\216\222\345\272\217.md" index 2b54f087..710e34e3 100644 --- "a/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/07.Elasticsearch\346\216\222\345\272\217.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/07.Elasticsearch\346\216\222\345\272\217.md" @@ -200,4 +200,4 @@ _\_geo_distance_ 的选项具体如下: ## 参考资料 -- [Elasticsearch 教程](https://www.knowledgedict.com/tutorial/elasticsearch-intro.html) +- [Elasticsearch 教程](https://www.knowledgedict.com/tutorial/elasticsearch-intro.html) \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/08.Elasticsearch\350\201\232\345\220\210.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/08.Elasticsearch\350\201\232\345\220\210.md" index 21d00b22..451ef126 100644 --- "a/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/08.Elasticsearch\350\201\232\345\220\210.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/08.Elasticsearch\350\201\232\345\220\210.md" @@ -733,4 +733,4 @@ Range Aggregation 范围聚合是一个基于多组值来源的聚合,可以 ## 参考资料 -- [Elasticsearch 教程](https://www.knowledgedict.com/tutorial/elasticsearch-intro.html) +- [Elasticsearch 教程](https://www.knowledgedict.com/tutorial/elasticsearch-intro.html) \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/09.Elasticsearch\345\210\206\346\236\220\345\231\250.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/09.Elasticsearch\345\210\206\346\236\220\345\231\250.md" index 6bef4170..7f7b2a6d 100644 --- "a/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/09.Elasticsearch\345\210\206\346\236\220\345\231\250.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/09.Elasticsearch\345\210\206\346\236\220\345\231\250.md" @@ -403,4 +403,4 @@ elasticsearch 的同义词有如下两种形式: ## 参考资料 -- [Elasticsearch 教程](https://www.knowledgedict.com/tutorial/elasticsearch-intro.html) +- [Elasticsearch 教程](https://www.knowledgedict.com/tutorial/elasticsearch-intro.html) \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/10.Elasticsearch\346\200\247\350\203\275\344\274\230\345\214\226.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/10.Elasticsearch\346\200\247\350\203\275\344\274\230\345\214\226.md" index dc1c7b67..0105a61f 100644 --- "a/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/10.Elasticsearch\346\200\247\350\203\275\344\274\230\345\214\226.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/10.Elasticsearch\346\200\247\350\203\275\344\274\230\345\214\226.md" @@ -307,4 +307,4 @@ ES 一旦创建好索引后,就无法调整分片的设置,而在 ES 中, ## 参考资料 -- [Elasticsearch 教程](https://www.knowledgedict.com/tutorial/elasticsearch-intro.html) +- [Elasticsearch 教程](https://www.knowledgedict.com/tutorial/elasticsearch-intro.html) \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/11.ElasticsearchRestApi.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/11.ElasticsearchRestApi.md" index 24fedf0a..57cdbac2 100644 --- "a/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/11.ElasticsearchRestApi.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/11.ElasticsearchRestApi.md" @@ -46,11 +46,11 @@ ElasticSearch Rest API 分为两种: URI Search 示例: -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220530072511.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220530072511.png) Request Body Search 示例: -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220530072654.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220530072654.png) ## 索引 API @@ -1158,4 +1158,4 @@ GET /_cat - **官方** - [Elasticsearch 官网](https://www.elastic.co/cn/products/elasticsearch) - [Elasticsearch Github](https://github.com/elastic/elasticsearch) - - [Elasticsearch 官方文档](https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html) + - [Elasticsearch 官方文档](https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html) \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/12.ElasticsearchHighLevelRestJavaApi.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/12.ElasticsearchHighLevelRestJavaApi.md" index 9ce91bc5..509abf33 100644 --- "a/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/12.ElasticsearchHighLevelRestJavaApi.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/12.ElasticsearchHighLevelRestJavaApi.md" @@ -378,4 +378,4 @@ public void matchPhraseQuery() throws IOException { ## 参考资料 - **官方** - - [Java High Level REST Client](https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/java-rest-high.html) + - [Java High Level REST Client](https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/java-rest-high.html) \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/13.Elasticsearch\351\233\206\347\276\244\345\222\214\345\210\206\347\211\207.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/13.Elasticsearch\351\233\206\347\276\244\345\222\214\345\210\206\347\211\207.md" index ab03f18b..56401b5b 100644 --- "a/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/13.Elasticsearch\351\233\206\347\276\244\345\222\214\345\210\206\347\211\207.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/13.Elasticsearch\351\233\206\347\276\244\345\222\214\345\210\206\347\211\207.md" @@ -83,7 +83,7 @@ Elasticsearch 是利用分片将数据分发到集群内各处的。分片是数 让我们在包含一个空节点的集群内创建名为 `blogs` 的索引。 索引在默认情况下会被分配 5 个主分片, 但是为了演示目的,我们将分配 3 个主分片和一份副本(每个主分片拥有一个副本分片): -```java +```json PUT /blogs { "settings" : { @@ -192,7 +192,7 @@ PUT /blogs 在运行中的集群上是可以动态调整副本分片数目的,我们可以按需伸缩集群。让我们把副本数从默认的 `1` 增加到 `2` : -```sense +```json PUT /blogs/_settings { "number_of_replicas" : 2 @@ -518,4 +518,4 @@ POST /logstash-2014-10/_optimize?max_num_segments=1 ## 参考资料 -- [Elasticsearch 官方文档之 集群内的原理](https://www.elastic.co/guide/cn/elasticsearch/guide/current/distributed-cluster.html) +- [Elasticsearch 官方文档之 集群内的原理](https://www.elastic.co/guide/cn/elasticsearch/guide/current/distributed-cluster.html) \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/20.Elasticsearch\350\277\220\347\273\264.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/20.Elasticsearch\350\277\220\347\273\264.md" index 9582af17..084843fb 100644 --- "a/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/20.Elasticsearch\350\277\220\347\273\264.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/20.Elasticsearch\350\277\220\347\273\264.md" @@ -216,4 +216,4 @@ echo "* hard nproc 4096" > /etc/security/limits.conf - [Elasticsearch 官方下载安装说明](https://www.elastic.co/cn/downloads/elasticsearch) - [Install Elasticsearch with RPM](https://www.elastic.co/guide/en/elasticsearch/reference/current/rpm.html#rpm) -- [Elasticsearch 使用积累](http://siye1982.github.io/2015/09/17/es-optimize/) +- [Elasticsearch 使用积累](http://siye1982.github.io/2015/09/17/es-optimize/) \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/README.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/README.md" index 7df96f19..c7372c85 100644 --- "a/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/README.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/README.md" @@ -74,4 +74,4 @@ hidden: true ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/01.Elastic\345\277\253\351\200\237\345\205\245\351\227\250.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/01.Elastic\345\277\253\351\200\237\345\205\245\351\227\250.md" index d45f30d9..82e6f4ee 100644 --- "a/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/01.Elastic\345\277\253\351\200\237\345\205\245\351\227\250.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/01.Elastic\345\277\253\351\200\237\345\205\245\351\227\250.md" @@ -287,4 +287,4 @@ Filebeat 将每个事件的传递状态存储在注册表文件中。所以它 - **文章** - [什么是 ELK Stack?](https://www.elastic.co/cn/what-is/elk-stack) - [https://github.com/doocs/advanced-java/blob/master/docs/high-concurrency/es-introduction.md](https://github.com/doocs/advanced-java/blob/master/docs/high-concurrency/es-introduction.md) - - [es-write-query-search](https://github.com/doocs/advanced-java/blob/master/docs/high-concurrency/es-write-query-search.md) + - [es-write-query-search](https://github.com/doocs/advanced-java/blob/master/docs/high-concurrency/es-write-query-search.md) \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/02.Elastic\346\212\200\346\234\257\346\240\210\344\271\213Filebeat.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/02.Elastic\346\212\200\346\234\257\346\240\210\344\271\213Filebeat.md" index 3a835f02..3f03ea08 100644 --- "a/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/02.Elastic\346\212\200\346\234\257\346\240\210\344\271\213Filebeat.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/02.Elastic\346\212\200\346\234\257\346\240\210\344\271\213Filebeat.md" @@ -294,4 +294,4 @@ Filebeat 将每个事件的传递状态存储在注册表文件中。所以它 ## 资料 -[Beats 官方文档](https://www.elastic.co/guide/en/beats/libbeat/current/index.html) +[Beats 官方文档](https://www.elastic.co/guide/en/beats/libbeat/current/index.html) \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/03.Filebeat\350\277\220\347\273\264.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/03.Filebeat\350\277\220\347\273\264.md" index bbe8cc8c..375f44b9 100644 --- "a/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/03.Filebeat\350\277\220\347\273\264.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/03.Filebeat\350\277\220\347\273\264.md" @@ -250,4 +250,4 @@ setup.kibana: - [Beats 官网](https://www.elastic.co/cn/products/beats) - [Beats Github](https://github.com/elastic/beats) -- [Beats 官方文档](https://www.elastic.co/guide/en/beats/libbeat/current/index.html) +- [Beats 官方文档](https://www.elastic.co/guide/en/beats/libbeat/current/index.html) \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/04.Elastic\346\212\200\346\234\257\346\240\210\344\271\213Kibana.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/04.Elastic\346\212\200\346\234\257\346\240\210\344\271\213Kibana.md" index 610254f1..e803d546 100644 --- "a/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/04.Elastic\346\212\200\346\234\257\346\240\210\344\271\213Kibana.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/04.Elastic\346\212\200\346\234\257\346\240\210\344\271\213Kibana.md" @@ -317,4 +317,4 @@ Visualize 工具使您能够以多种方式(如饼图、柱状图、曲线图 1. 点击侧面导航栏中的 Dashboard。 2. 点击添加显示保存的可视化列表。 3. 点击之前保存的 `Visualize`,然后点击列表底部的小向上箭头关闭可视化列表。 -4. 将鼠标悬停在可视化对象上会显示允许您编辑,移动,删除和调整可视化对象大小的容器控件。 +4. 将鼠标悬停在可视化对象上会显示允许您编辑,移动,删除和调整可视化对象大小的容器控件。 \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/05.Kibana\350\277\220\347\273\264.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/05.Kibana\350\277\220\347\273\264.md" index 0e509c90..5a81a10e 100644 --- "a/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/05.Kibana\350\277\220\347\273\264.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/05.Kibana\350\277\220\347\273\264.md" @@ -358,4 +358,4 @@ Unable to fetch mapping. Do you have indices matching the pattern? - [Kibana 官网](https://www.elastic.co/cn/products/kibana) - [Kibana Github](https://github.com/elastic/kibana) -- [Kibana 官方文档](https://www.elastic.co/guide/en/kibana/current/index.html) +- [Kibana 官方文档](https://www.elastic.co/guide/en/kibana/current/index.html) \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/06.Elastic\346\212\200\346\234\257\346\240\210\344\271\213Logstash.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/06.Elastic\346\212\200\346\234\257\346\240\210\344\271\213Logstash.md" index 10960be7..708cb24f 100644 --- "a/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/06.Elastic\346\212\200\346\234\257\346\240\210\344\271\213Logstash.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/06.Elastic\346\212\200\346\234\257\346\240\210\344\271\213Logstash.md" @@ -510,4 +510,4 @@ kill -9 ${PID} ## 推荐阅读 - [Elastic 技术栈](https://github.com/dunwu/JavaStack/blob/master/docs/javatool/elastic/README.md) -- [JavaStack](https://github.com/dunwu/JavaStack) +- [JavaStack](https://github.com/dunwu/JavaStack) \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/07.Logstash\350\277\220\347\273\264.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/07.Logstash\350\277\220\347\273\264.md" index ed32b671..83927491 100644 --- "a/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/07.Logstash\350\277\220\347\273\264.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/07.Logstash\350\277\220\347\273\264.md" @@ -507,4 +507,4 @@ kill -9 ${PID} - [Logstash 官方文档](https://www.elastic.co/guide/en/logstash/current/index.html) - [logstash-logback-encoder](https://github.com/logstash/logstash-logback-encoder) - [ELK Stack 权威指南](https://github.com/chenryn/logstash-best-practice-cn) -- [ELK(Elasticsearch、Logstash、Kibana)安装和配置](https://github.com/judasn/Linux-Tutorial/blob/master/ELK-Install-And-Settings.md) +- [ELK(Elasticsearch、Logstash、Kibana)安装和配置](https://github.com/judasn/Linux-Tutorial/blob/master/ELK-Install-And-Settings.md) \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/README.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/README.md" index ea8f90f9..9e12932f 100644 --- "a/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/README.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/README.md" @@ -55,4 +55,4 @@ hidden: true ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file diff --git "a/docs/12.\346\225\260\346\215\256\345\272\223/README.md" "b/docs/12.\346\225\260\346\215\256\345\272\223/README.md" index 41af13ca..ff1852cf 100644 --- "a/docs/12.\346\225\260\346\215\256\345\272\223/README.md" +++ "b/docs/12.\346\225\260\346\215\256\345\272\223/README.md" @@ -11,7 +11,7 @@ hidden: true

- logo + logo

@@ -26,7 +26,7 @@ hidden: true - commit + build @@ -48,28 +48,28 @@ hidden: true #### 分布式理论 -- [分布式理论](https://dunwu.github.io/design/pages/367308/) -- [深入剖析共识性算法 Paxos](https://dunwu.github.io/design/pages/874539/) -- [深入剖析共识性算法 Raft](https://dunwu.github.io/design/pages/e40812/) -- [分布式算法 Gossip](https://dunwu.github.io/design/pages/d15993/) +- [分布式一致性](https://dunwu.github.io/blog/pages/dac0e2/) +- [深入剖析共识性算法 Paxos](https://dunwu.github.io/blog/pages/874539/) +- [深入剖析共识性算法 Raft](https://dunwu.github.io/blog/pages/e40812/) +- [分布式算法 Gossip](https://dunwu.github.io/blog/pages/d15993/) #### 分布式关键技术 ##### 流量调度 -- [流量控制](https://dunwu.github.io/design/pages/282676/) -- [深入浅出负载均衡](https://dunwu.github.io/design/pages/b7ca44/) -- [服务路由](https://dunwu.github.io/design/pages/d04ece/) -- [分布式会话基本原理](https://dunwu.github.io/design/pages/3e66c2/) +- [流量控制](https://dunwu.github.io/blog/pages/282676/) +- [负载均衡](https://dunwu.github.io/blog/pages/98a1c1/) +- [服务路由](https://dunwu.github.io/blog/pages/d04ece/) +- [分布式会话基本原理](https://dunwu.github.io/blog/pages/3e66c2/) ##### 数据调度 -- [缓存基本原理](https://dunwu.github.io/design/pages/471208/) -- [读写分离基本原理](https://dunwu.github.io/design/pages/7da6ca/) -- [分库分表基本原理](https://dunwu.github.io/design/pages/103382/) -- [分布式 ID 基本原理](https://dunwu.github.io/design/pages/0b2e59/) -- [分布式事务基本原理](https://dunwu.github.io/design/pages/910bad/) -- [分布式锁基本原理](https://dunwu.github.io/design/pages/69360c/) +- [缓存基本原理](https://dunwu.github.io/blog/pages/471208/) +- [读写分离基本原理](https://dunwu.github.io/blog/pages/7da6ca/) +- [分库分表基本原理](https://dunwu.github.io/blog/pages/103382/) +- [分布式 ID 基本原理](https://dunwu.github.io/blog/pages/0b2e59/) +- [分布式事务基本原理](https://dunwu.github.io/blog/pages/910bad/) +- [分布式锁基本原理](https://dunwu.github.io/blog/pages/69360c/) ### 其他 @@ -86,15 +86,17 @@ hidden: true > [关系型数据库](03.关系型数据库) 整理主流关系型数据库知识点。 -### 公共知识 +### 关系型数据库综合 - [关系型数据库面试总结](03.关系型数据库/01.综合/01.关系型数据库面试.md) 💯 -- [SQL Cheat Sheet](03.关系型数据库/01.综合/02.SqlCheatSheet.md) 是一个 SQL 入门教程。 -- [扩展 SQL](03.关系型数据库/01.综合/03.扩展SQL.md) 是一个 SQL 入门教程。 +- [SQL 语法基础特性](03.关系型数据库/01.综合/02.SQL语法基础特性.md) +- [SQL 语法高级特性](03.关系型数据库/01.综合/03.SQL语法高级特性.md) +- [扩展 SQL](03.关系型数据库/01.综合/03.扩展SQL.md) +- [SQL Cheat Sheet](03.关系型数据库/01.综合/99.SqlCheatSheet.md) ### Mysql -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200716103611.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200716103611.png) - [Mysql 应用指南](03.关系型数据库/02.Mysql/01.Mysql应用指南.md) ⚡ - [Mysql 工作流](03.关系型数据库/02.Mysql/02.MySQL工作流.md) - 关键词:`连接`、`缓存`、`语法分析`、`优化`、`执行引擎`、`redo log`、`bin log`、`两阶段提交` @@ -137,7 +139,7 @@ hidden: true ### Redis -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200713105627.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200713105627.png) - [Redis 面试总结](05.KV数据库/01.Redis/01.Redis面试总结.md) 💯 - [Redis 应用指南](05.KV数据库/01.Redis/02.Redis应用指南.md) ⚡ - 关键词:`内存淘汰`、`事件`、`事务`、`管道`、`发布与订阅` @@ -153,12 +155,16 @@ hidden: true ### HBase -> [HBase](https://dunwu.github.io/bigdata-tutorial/hbase) 📚 因为常用于大数据项目,所以将其文档和源码整理在 [bigdata-tutorial](https://dunwu.github.io/bigdata-tutorial/) 项目中。 - -- [HBase 原理](https://github.com/dunwu/bigdata-tutorial/blob/master/docs/hbase/HBase原理.md) ⚡ -- [HBase 命令](https://github.com/dunwu/bigdata-tutorial/blob/master/docs/hbase/HBase命令.md) -- [HBase 应用](https://github.com/dunwu/bigdata-tutorial/blob/master/docs/hbase/HBase应用.md) -- [HBase 运维](https://github.com/dunwu/bigdata-tutorial/blob/master/docs/hbase/HBase运维.md) +- [HBase 快速入门](06.列式数据库/01.HBase/01.HBase快速入门.md) +- [HBase 数据模型](06.列式数据库/01.HBase/02.HBase数据模型.md) +- [HBase Schema 设计](06.列式数据库/01.HBase/03.HBaseSchema设计.md) +- [HBase 架构](06.列式数据库/01.HBase/04.HBase架构.md) +- [HBase Java API 基础特性](06.列式数据库/01.HBase/10.HBaseJavaApi基础特性.md) +- [HBase Java API 高级特性之过滤器](06.列式数据库/01.HBase/11.HBaseJavaApi高级特性之过滤器.md) +- [HBase Java API 高级特性之协处理器](06.列式数据库/01.HBase/12.HBaseJavaApi高级特性之协处理器.md) +- [HBase Java API 其他高级特性](06.列式数据库/01.HBase/13.HBaseJavaApi其他高级特性.md) +- [HBase 运维](06.列式数据库/01.HBase/21.HBase运维.md) +- [HBase 命令](06.列式数据库/01.HBase/22.HBase命令.md) ## 搜索引擎数据库 diff --git a/docs/README.md b/docs/README.md index 00e189e0..d4f91507 100644 --- a/docs/README.md +++ b/docs/README.md @@ -19,7 +19,7 @@ footer: CC-BY-SA-4.0 Licensed | Copyright © 2018-Now Dunwu - commit + build @@ -33,55 +33,36 @@ footer: CC-BY-SA-4.0 Licensed | Copyright © 2018-Now Dunwu > - 🔁 项目同步维护:[Github](https://github.com/dunwu/db-tutorial/) | [Gitee](https://gitee.com/turnon/db-tutorial/) > - 📖 电子书阅读:[Github Pages](https://dunwu.github.io/db-tutorial/) | [Gitee Pages](https://turnon.gitee.io/db-tutorial/) -## 分布式 - -### 分布式综合 - -- [分布式面试总结](https://dunwu.github.io/waterdrop/pages/f9209d/) - -### 分布式理论 - -- [分布式理论](https://dunwu.github.io/waterdrop/pages/286bb3/) - 关键词:`拜占庭将军`、`CAP`、`BASE`、`错误的分布式假设` -- [共识性算法 Paxos](https://dunwu.github.io/waterdrop/pages/0276bb/) - 关键词:`共识性算法` -- [共识性算法 Raft](https://dunwu.github.io/waterdrop/pages/4907dc/) - 关键词:`共识性算法` -- [分布式算法 Gossip](https://dunwu.github.io/waterdrop/pages/71539a/) - 关键词:`数据传播` - -### 分布式关键技术 - -- 集群 -- 复制 -- 分区 -- 选主 +## 数据库综合 -#### 流量调度 +### 分布式存储原理 -- [流量控制](https://dunwu.github.io/waterdrop/pages/60bb6d/) - 关键词:`限流`、`熔断`、`降级`、`计数器法`、`时间窗口法`、`令牌桶法`、`漏桶法` -- [负载均衡](https://dunwu.github.io/waterdrop/pages/98a1c1/) - 关键词:`轮询`、`随机`、`最少连接`、`源地址哈希`、`一致性哈希`、`虚拟 hash 槽` -- [服务路由](https://dunwu.github.io/waterdrop/pages/3915e8/) - 关键词:`路由`、`条件路由`、`脚本路由`、`标签路由` -- 服务网关 -- [分布式会话](https://dunwu.github.io/waterdrop/pages/95e45f/) - 关键词:`粘性 Session`、`Session 复制共享`、`基于缓存的 session 共享` +#### 分布式理论 -#### 数据调度 +- [分布式一致性](https://dunwu.github.io/blog/pages/dac0e2/) +- [深入剖析共识性算法 Paxos](https://dunwu.github.io/blog/pages/874539/) +- [深入剖析共识性算法 Raft](https://dunwu.github.io/blog/pages/e40812/) +- [分布式算法 Gossip](https://dunwu.github.io/blog/pages/d15993/) -- [数据缓存](https://dunwu.github.io/waterdrop/pages/fd0aaa/) - 关键词:`进程内缓存`、`分布式缓存`、`缓存雪崩`、`缓存穿透`、`缓存击穿`、`缓存更新`、`缓存预热`、`缓存降级` -- [读写分离](https://dunwu.github.io/waterdrop/pages/3faf18/) -- [分库分表](https://dunwu.github.io/waterdrop/pages/e1046e/) - 关键词:`分片`、`路由`、`迁移`、`扩容`、`双写`、`聚合` -- [分布式 ID](https://dunwu.github.io/waterdrop/pages/3ae455/) - 关键词:`UUID`、`自增序列`、`雪花算法`、`Leaf` -- [分布式事务](https://dunwu.github.io/waterdrop/pages/e1881c/) - 关键词:`2PC`、`3PC`、`TCC`、`本地消息表`、`MQ 消息`、`SAGA` -- [分布式锁](https://dunwu.github.io/waterdrop/pages/40ac64/) - 关键词:`数据库`、`Redis`、`ZooKeeper`、`互斥`、`可重入`、`死锁`、`容错`、`自旋尝试` +#### 分布式关键技术 -#### 资源调度 +##### 流量调度 -- 弹性伸缩 +- [流量控制](https://dunwu.github.io/blog/pages/282676/) +- [负载均衡](https://dunwu.github.io/blog/pages/98a1c1/) +- [服务路由](https://dunwu.github.io/blog/pages/d04ece/) +- [分布式会话基本原理](https://dunwu.github.io/blog/pages/3e66c2/) -#### 服务治理 +##### 数据调度 -- [服务注册和发现](https://dunwu.github.io/waterdrop/pages/1a90aa/) -- [服务容错](https://dunwu.github.io/waterdrop/pages/e32c7e/) -- 服务编排 -- 服务版本管理 +- [缓存基本原理](https://dunwu.github.io/blog/pages/471208/) +- [读写分离基本原理](https://dunwu.github.io/blog/pages/7da6ca/) +- [分库分表基本原理](https://dunwu.github.io/blog/pages/103382/) +- [分布式 ID 基本原理](https://dunwu.github.io/blog/pages/0b2e59/) +- [分布式事务基本原理](https://dunwu.github.io/blog/pages/910bad/) +- [分布式锁基本原理](https://dunwu.github.io/blog/pages/69360c/) -## 数据库综合 +### 其他 - [Nosql 技术选型](12.数据库/01.数据库综合/01.Nosql技术选型.md) - [数据结构与数据库索引](12.数据库/01.数据库综合/02.数据结构与数据库索引.md) @@ -96,15 +77,17 @@ footer: CC-BY-SA-4.0 Licensed | Copyright © 2018-Now Dunwu > [关系型数据库](12.数据库/03.关系型数据库) 整理主流关系型数据库知识点。 -### 公共知识 +### 关系型数据库综合 - [关系型数据库面试总结](12.数据库/03.关系型数据库/01.综合/01.关系型数据库面试.md) 💯 -- [SQL Cheat Sheet](12.数据库/03.关系型数据库/01.综合/02.SqlCheatSheet.md) 是一个 SQL 入门教程。 -- [扩展 SQL](12.数据库/03.关系型数据库/01.综合/03.扩展SQL.md) 是一个 SQL 入门教程。 +- [SQL 语法基础特性](12.数据库/03.关系型数据库/01.综合/02.SQL语法基础特性.md) +- [SQL 语法高级特性](12.数据库/03.关系型数据库/01.综合/03.SQL语法高级特性.md) +- [扩展 SQL](12.数据库/03.关系型数据库/01.综合/03.扩展SQL.md) +- [SQL Cheat Sheet](12.数据库/03.关系型数据库/01.综合/99.SqlCheatSheet.md) ### Mysql -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200716103611.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200716103611.png) - [Mysql 应用指南](12.数据库/03.关系型数据库/02.Mysql/01.Mysql应用指南.md) ⚡ - [Mysql 工作流](12.数据库/03.关系型数据库/02.Mysql/02.MySQL工作流.md) - 关键词:`连接`、`缓存`、`语法分析`、`优化`、`执行引擎`、`redo log`、`bin log`、`两阶段提交` @@ -147,7 +130,7 @@ footer: CC-BY-SA-4.0 Licensed | Copyright © 2018-Now Dunwu ### Redis -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200713105627.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200713105627.png) - [Redis 面试总结](12.数据库/05.KV数据库/01.Redis/01.Redis面试总结.md) 💯 - [Redis 应用指南](12.数据库/05.KV数据库/01.Redis/02.Redis应用指南.md) ⚡ - 关键词:`内存淘汰`、`事件`、`事务`、`管道`、`发布与订阅` @@ -163,12 +146,16 @@ footer: CC-BY-SA-4.0 Licensed | Copyright © 2018-Now Dunwu ### HBase -> [HBase](https://dunwu.github.io/bigdata-tutorial/hbase) 📚 因为常用于大数据项目,所以将其文档和源码整理在 [bigdata-tutorial](https://dunwu.github.io/bigdata-tutorial/) 项目中。 - -- [HBase 原理](https://github.com/dunwu/bigdata-tutorial/blob/master/docs/hbase/HBase原理.md) ⚡ -- [HBase 命令](https://github.com/dunwu/bigdata-tutorial/blob/master/docs/hbase/HBase命令.md) -- [HBase 应用](https://github.com/dunwu/bigdata-tutorial/blob/master/docs/hbase/HBase应用.md) -- [HBase 运维](https://github.com/dunwu/bigdata-tutorial/blob/master/docs/hbase/HBase运维.md) +- [HBase 快速入门](12.数据库/06.列式数据库/01.HBase/01.HBase快速入门.md) +- [HBase 数据模型](12.数据库/06.列式数据库/01.HBase/02.HBase数据模型.md) +- [HBase Schema 设计](12.数据库/06.列式数据库/01.HBase/03.HBaseSchema设计.md) +- [HBase 架构](12.数据库/06.列式数据库/01.HBase/04.HBase架构.md) +- [HBase Java API 基础特性](12.数据库/06.列式数据库/01.HBase/10.HBaseJavaApi基础特性.md) +- [HBase Java API 高级特性之过滤器](12.数据库/06.列式数据库/01.HBase/11.HBaseJavaApi高级特性之过滤器.md) +- [HBase Java API 高级特性之协处理器](12.数据库/06.列式数据库/01.HBase/12.HBaseJavaApi高级特性之协处理器.md) +- [HBase Java API 其他高级特性](12.数据库/06.列式数据库/01.HBase/13.HBaseJavaApi其他高级特性.md) +- [HBase 运维](12.数据库/06.列式数据库/01.HBase/21.HBase运维.md) +- [HBase 命令](12.数据库/06.列式数据库/01.HBase/22.HBase命令.md) ## 搜索引擎数据库 diff --git a/package.json b/package.json index 04e91733..41b3b676 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,11 @@ { - "name": "java-tutorial", + "name": "db-tutorial", "version": "1.0.0", "private": true, "scripts": { "clean": "rimraf docs/.temp", - "start": "vuepress dev docs", - "build": "vuepress build docs", + "start": "node --max_old_space_size=4096 ./node_modules/vuepress/cli.js dev docs", + "build": "node --max_old_space_size=4096 ./node_modules/vuepress/cli.js build docs", "deploy": "bash scripts/deploy.sh", "updateTheme": "yarn remove vuepress-theme-vdoing && rm -rf node_modules && yarn && yarn add vuepress-theme-vdoing -D", "editFm": "node utils/editFrontmatter.js", @@ -15,19 +15,23 @@ "view-info": "vuepress view-info ./ --temp docs/.temp" }, "devDependencies": { - "dayjs": "^1.9.7", - "inquirer": "^7.1.0", + "dayjs": "^1.11.7", + "inquirer": "^9.1.4", "json2yaml": "^1.1.0", - "vuepress": "1.9.2", + "markdownlint-cli": "^0.33.0", + "markdownlint-rule-emphasis-style": "^1.0.1", + "rimraf": "^4.1.2", + "vue-toasted": "^1.1.25", + "vuepress": "1.9.9", "vuepress-plugin-baidu-tongji": "^1.0.1", "vuepress-plugin-comment": "^0.7.3", "vuepress-plugin-demo-block": "^0.7.2", + "vuepress-plugin-flowchart": "^1.4.2", "vuepress-plugin-fulltext-search": "^2.2.1", "vuepress-plugin-one-click-copy": "^1.0.2", "vuepress-plugin-thirdparty-search": "^1.0.2", "vuepress-plugin-zooming": "^1.1.7", - "vuepress-plugin-flowchart": "^1.4.2", - "vuepress-theme-vdoing": "^1.10.3", + "vuepress-theme-vdoing": "^1.12.9", "yamljs": "^0.3.0", "markdownlint-cli": "^0.25.0", "markdownlint-rule-emphasis-style": "^1.0.1", diff --git a/prettier.config.js b/prettier.config.js new file mode 100644 index 00000000..c9848e74 --- /dev/null +++ b/prettier.config.js @@ -0,0 +1,10 @@ +/** + * @see https://prettier.io/docs/en/options.html + * @see https://prettier.io/docs/en/configuration.html + */ +module.exports = { + tabWidth: 2, + semi: false, + singleQuote: true, + trailingComma: 'none' +} diff --git a/scripts/deploy.sh b/scripts/deploy.sh index ba8e17a2..678bbf2d 100644 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -2,7 +2,7 @@ # ------------------------------------------------------------------------------ # gh-pages 部署脚本 -# @author Zhang Peng +# @author Zhang Peng # @since 2020/2/10 # ------------------------------------------------------------------------------ diff --git a/utils/config.yml b/utils/config.yml index 6fac6a22..d387646b 100644 --- a/utils/config.yml +++ b/utils/config.yml @@ -1,14 +1,15 @@ -#批量添加和修改、删除front matter配置文件 +# 批量添加和修改、删除front matter配置文件 -# 需要批量处理的路径,docs文件夹内的文件夹 (数组。映射路径:docs/arr[0]/arr[1] ... ) +# 需要批量处理的路径,docs文件夹内的文件夹 (数组,映射路径:path[0]/path[1]/path[2] ... ) path: - docs # 第一个成员必须是docs # 要删除的字段 (数组) -delete: - # - test +delete: # - tags - # 要添加、修改front matter的数据 (front matter中没有的数据则添加,已有的数据则覆盖) + # 要添加、修改front matter的数据 (front matter中没有的数据则添加,已有的数据则覆盖) data: - article: false \ No newline at end of file + # author: + # name: xugaoyi + # link: https://github.com/xugaoyi diff --git a/utils/editFrontmatter.js b/utils/editFrontmatter.js index 8c223f4e..0998bf3d 100644 --- a/utils/editFrontmatter.js +++ b/utils/editFrontmatter.js @@ -1,41 +1,43 @@ /** * 批量添加和修改front matter ,需要配置 ./config.yml 文件。 */ -const fs = require('fs'); // 文件模块 -const path = require('path'); // 路径模块 -const matter = require('gray-matter'); // front matter解析器 https://github.com/jonschlinkert/gray-matter +const fs = require('fs') // 文件模块 +const path = require('path') // 路径模块 +const matter = require('gray-matter') // front matter解析器 https://github.com/jonschlinkert/gray-matter const jsonToYaml = require('json2yaml') const yamlToJs = require('yamljs') const inquirer = require('inquirer') // 命令行操作 const chalk = require('chalk') // 命令行打印美化 -const readFileList = require('./modules/readFileList'); -const { type, repairDate} = require('./modules/fn'); +const readFileList = require('./modules/readFileList') +const { type, repairDate } = require('./modules/fn') const log = console.log const configPath = path.join(__dirname, 'config.yml') // 配置文件的路径 -main(); +main() /** * 主体函数 */ async function main() { + const promptList = [ + { + type: 'confirm', + message: chalk.yellow('批量操作frontmatter有修改数据的风险,确定要继续吗?'), + name: 'edit' + } + ] + let edit = true - const promptList = [{ - type: "confirm", - message: chalk.yellow('批量操作frontmatter有修改数据的风险,确定要继续吗?'), - name: "edit", - }]; - let edit = true; - - await inquirer.prompt(promptList).then(answers => { + await inquirer.prompt(promptList).then((answers) => { edit = answers.edit }) - if(!edit) { // 退出操作 + if (!edit) { + // 退出操作 return } - + const config = yamlToJs.load(configPath) // 解析配置文件的数据转为js对象 if (type(config.path) !== 'array') { @@ -48,27 +50,26 @@ async function main() { return } - const filePath = path.join(__dirname, '..', ...config.path); // 要批量修改的文件路径 - const files = readFileList(filePath); // 读取所有md文件数据 + const filePath = path.join(__dirname, '..', ...config.path) // 要批量修改的文件路径 + const files = readFileList(filePath) // 读取所有md文件数据 - files.forEach(file => { - let dataStr = fs.readFileSync(file.filePath, 'utf8');// 读取每个md文件的内容 + files.forEach((file) => { + let dataStr = fs.readFileSync(file.filePath, 'utf8') // 读取每个md文件的内容 const fileMatterObj = matter(dataStr) // 解析md文件的front Matter。 fileMatterObj => {content:'剔除frontmatter后的文件内容字符串', data:{}, ...} - let matterData = fileMatterObj.data; // 得到md文件的front Matter - + let matterData = fileMatterObj.data // 得到md文件的front Matter + let mark = false // 删除操作 if (config.delete) { - if( type(config.delete) !== 'array' ) { + if (type(config.delete) !== 'array') { log(chalk.yellow('未能完成删除操作,delete字段的值应该是一个数组!')) } else { - config.delete.forEach(item => { + config.delete.forEach((item) => { if (matterData[item]) { delete matterData[item] mark = true } }) - } } @@ -77,16 +78,21 @@ async function main() { Object.assign(matterData, config.data) // 将配置数据合并到front Matter对象 mark = true } - + // 有操作时才继续 if (mark) { - if(matterData.date && type(matterData.date) === 'date') { + if (matterData.date && type(matterData.date) === 'date') { matterData.date = repairDate(matterData.date) // 修复时间格式 } - const newData = jsonToYaml.stringify(matterData).replace(/\n\s{2}/g,"\n").replace(/"/g,"") + '---\r\n' + fileMatterObj.content; - fs.writeFileSync(file.filePath, newData); // 写入 + const newData = + jsonToYaml + .stringify(matterData) + .replace(/\n\s{2}/g, '\n') + .replace(/"/g, '') + + '---\r\n' + + fileMatterObj.content + fs.writeFileSync(file.filePath, newData) // 写入 log(chalk.green(`update frontmatter:${file.filePath} `)) } - }) } diff --git a/utils/modules/fn.js b/utils/modules/fn.js index 48cbbd17..8d4635b7 100644 --- a/utils/modules/fn.js +++ b/utils/modules/fn.js @@ -1,21 +1,25 @@ // 类型判断 -exports.type = function (o){ +exports.type = function (o) { var s = Object.prototype.toString.call(o) return s.match(/\[object (.*?)\]/)[1].toLowerCase() } - // 修复date时区格式的问题 - exports.repairDate = function (date) { - date = new Date(date); - return `${date.getUTCFullYear()}-${zero(date.getUTCMonth()+1)}-${zero(date.getUTCDate())} ${zero(date.getUTCHours())}:${zero(date.getUTCMinutes())}:${zero(date.getUTCSeconds())}`; +// 修复date时区格式的问题 +exports.repairDate = function (date) { + date = new Date(date) + return `${date.getUTCFullYear()}-${zero(date.getUTCMonth() + 1)}-${zero(date.getUTCDate())} ${zero( + date.getUTCHours() + )}:${zero(date.getUTCMinutes())}:${zero(date.getUTCSeconds())}` } // 日期的格式 exports.dateFormat = function (date) { - return `${date.getFullYear()}-${zero(date.getMonth()+1)}-${zero(date.getDate())} ${zero(date.getHours())}:${zero(date.getMinutes())}:${zero(date.getSeconds())}` + return `${date.getFullYear()}-${zero(date.getMonth() + 1)}-${zero(date.getDate())} ${zero(date.getHours())}:${zero( + date.getMinutes() + )}:${zero(date.getSeconds())}` } // 小于10补0 -function zero(d){ - return d.toString().padStart(2,'0') -} \ No newline at end of file +function zero(d) { + return d.toString().padStart(2, '0') +} diff --git a/utils/modules/readFileList.js b/utils/modules/readFileList.js index 8eb97c62..74a4eb6b 100644 --- a/utils/modules/readFileList.js +++ b/utils/modules/readFileList.js @@ -1,43 +1,49 @@ /** * 读取所有md文件数据 */ -const fs = require('fs'); // 文件模块 -const path = require('path'); // 路径模块 -const docsRoot = path.join(__dirname, '..', '..', 'docs'); // docs文件路径 +const fs = require('fs') // 文件模块 +const path = require('path') // 路径模块 +const docsRoot = path.join(__dirname, '..', '..', 'docs') // docs文件路径 function readFileList(dir = docsRoot, filesList = []) { - const files = fs.readdirSync(dir); - files.forEach( (item, index) => { - let filePath = path.join(dir, item); - const stat = fs.statSync(filePath); - if (stat.isDirectory() && item !== '.vuepress') { - readFileList(path.join(dir, item), filesList); //递归读取文件 - } else { - if(path.basename(dir) !== 'docs'){ // 过滤docs目录级下的文件 + const files = fs.readdirSync(dir) + files.forEach((item, index) => { + let filePath = path.join(dir, item) + const stat = fs.statSync(filePath) + if (stat.isDirectory() && item !== '.vuepress') { + readFileList(path.join(dir, item), filesList) //递归读取文件 + } else { + if (path.basename(dir) !== 'docs') { + // 过滤docs目录级下的文件 - const fileNameArr = path.basename(filePath).split('.') - let name = null, type = null; - if (fileNameArr.length === 2) { // 没有序号的文件 - name = fileNameArr[0] - type = fileNameArr[1] - } else if (fileNameArr.length === 3) { // 有序号的文件 - name = fileNameArr[1] - type = fileNameArr[2] - } else { // 超过两个‘.’的 - log(chalk.yellow(`warning: 该文件 "${filePath}" 没有按照约定命名,将忽略生成相应数据。`)) - return - } - if(type === 'md'){ // 过滤非md文件 - filesList.push({ - name, - filePath - }); - } + const filename = path.basename(filePath) + const fileNameArr = filename.split('.') + const firstDotIndex = filename.indexOf('.') + const lastDotIndex = filename.lastIndexOf('.') + let name = null, + type = null + if (fileNameArr.length === 2) { + // 没有序号的文件 + name = fileNameArr[0] + type = fileNameArr[1] + } else if (fileNameArr.length >= 3) { + // 有序号的文件(或文件名中间有'.') + name = filename.substring(firstDotIndex + 1, lastDotIndex) + type = filename.substring(lastDotIndex + 1) } - } - }); - return filesList; + + if (type === 'md') { + // 过滤非md文件 + filesList.push({ + name, + filePath + }) + } + } + } + }) + return filesList } -module.exports = readFileList; \ No newline at end of file +module.exports = readFileList