开发者博客 – IT技术 尽在开发者博客

开发者博客 – 科技是第一生产力


  • 首页

  • 归档

  • 搜索

spring cloud + mybatis + seate

发表于 2019-12-08

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

本文链接:gudepeng.github.io/note/2019/1…

demo样例:github.com/gudepeng/de…

一.客户端

1.引包

1
2
3
4
5
复制代码<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<version>2.1.1.RELEASE</version>
</dependency>

2.编写配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
复制代码@Configuration
public class DataSourceConfig {

@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource druidDataSource() {
DruidDataSource druidDataSource = new DruidDataSource();
return druidDataSource;
}

@Primary
@Bean("dataSource")
public DataSourceProxy dataSource(DataSource druidDataSource) {
return new DataSourceProxy(druidDataSource);
}

@Bean
public SqlSessionFactory sqlSessionFactory(DataSourceProxy dataSourceProxy) throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSourceProxy);
factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath*:/mapper/*.xml"));
return factoryBean.getObject();
}
}

3.修改启动类

1
复制代码@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)

4.在需要开启分布式事物的方法上添加注解

1
复制代码@GlobalTransactional

二.服务端

1.下载seata-server

[seata-server]{github.com/seata/seata…}

2.修改配置文件(本文以nacos为样例,store记录为mysql)

修改conf下的registry.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
复制代码registry {
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
type = "nacos"

nacos {
serverAddr = "localhost"
namespace = "piblic"
cluster = "default"
}
}

config {
# file、nacos 、apollo、zk、consul、etcd3
type = "nacos"

nacos {
serverAddr = "localhost"
#namespace为空,否则后面会报找不到store.db.driver-class-name找不到
namespace = ""
}
}

修改registry下的type为nacos,配置nacos的serverAddr为你nacos服务的地址,不要带http和端口号。它会默认去连你的8858端口,如需修改端口,请修改nacos-config.sh文件中的端口

拷贝registry.conf到每个使用seata服务的resources下

修改conf下nacos-config.txt

1
2
3
4
5
6
7
复制代码store.mode=db
store.db.datasource=dbcp
store.db.db-type=mysql
store.db.driver-class-name=com.mysql.jdbc.Driver
store.db.url=jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true
store.db.user=mysql
store.db.password=mysql

修改store.mode为db,修改对应db配置文件为你自己的mysql。
修改service.vgroup_mapping.my_test_tx_group=default属性
0.9.0.1版本后:my_test_tx_group修改为自己的服务名(spring.application.name)+”-seata-service-group”
0.9.0.1版本前:my_test_tx_group修改为自己的服务名(spring.application.name)+”-fescar-service-group”
可以spring-cloud-alibaba-seata项目下的GlobalTransactionAutoConfiguration累中看到

GlobalTransactionAutoConfiguration

有多少个服务需要使用seata就要添加多少个ervice.vgroup_mapping

执行nacos-config.sh Nacos-Server-IP()Nacos-Server-IP为你nacos服务的ip地址),在一步骤是把nacos-config.txt中的配置更新到nacos上,如果nacos-config.txt重新修改,就要重新执行该命令。或者登陆到nacos的管理界面直接进行修改。

之后添加,删除,修改服务,在nacos配置上作对应修改即可

3.创建表

在上一步骤中store.db.url的database中执行建表语句(conf下db_store.sql)

在每一个业务库中执行建表语句(conf下db_undo_log.sql)

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

写给后端的Hadoop初级入门教程:概念篇

发表于 2019-12-08

前言:

Hello大家好,我是韩数。距离我们上一个系列写给后端的Nginx初级入门教程已经过去整整25天了,中间穿插了两篇区块链相关的文章,其实吧,这二十来天我一直在憋大招,那就是这个最新的系列写给后端的Hadoop初级入门教程,由于Hadoop本身的技术细节还是很多的,Hadoop基础环境的搭建,分布式伪分布式的部署,集群启动的准备,hdfs文件系统,MR编程模型,以及最后的优化等等,整个一套写下来工作量还是蛮大的,好在我快放寒假了(开心),这样使得我有充足的时间和精力去写这套教程,一来是为了帮助自己在写作的时候更加深入地理解这方面的知识,而来是希望可以帮助到那些刚刚准备入门大数据的朋友们去理解和使用Hadoop这门技术。毕竟大家都知道,现在网上搜到的那些技术教程,质量参差不齐,一不小心踩到坑就是:

一电脑,一根烟,一篇教程学半天,调试半天却不对,想送作者上青天。

好湿好湿,本篇文章作为整套Hadoop入门教程的第一篇,我们依然从最基础的概念说起,什么是大数据,大数据如何影响我们的生活?什么是Hadoop,Hadoop和其他大数据技术相比又有哪些优势?明白了这些问题,我相信再学大数据,虽然不能说有buff加成,但是至少知道自己接下来要学的这玩意儿是个啥了。

不废话,直接上东西

什么是大数据:

大数据 (Big Data) : 主要是指无法在一定范围内用常规软件工具进行捕捉,管理和处理的数据集合,是需要新处理模式才能具有更强的决策力,洞察发现力和流程优化能力的海量,高增长率和多样化的信息资产。

一句话解释:大数据就是大量数据,数据多到传统方案无法处理的程度。

当然数据的体量并不是最重要的,重要的是隐藏在这些数据中的信息,这些信息不论是在商业上还是在研究上都有着巨大的价值,电商通过挖掘这些数据中的信息为每个用户画像,并且推荐合适的商品给用户增加购买,当然,也可以顺便调整一下改个价格杀个熟什么的。

大数据的单位:

但我们毕竟是严谨的理科生啊,你说大数据大数据,多大才是大数据?为了解决这个问题,减少撕逼,科学家就制定了一系列的数据单位,从小到大依次是:

bit Byte KB MB GB TB PB EB ZB YB BB NB(牛逼) 和 DB(呆逼)

当然,光讲这些单位有什么意思,我怎么能知道这些单位能存多少数据?为了方便大家更加直接的感受到这些数据单位的威力,我找了一些小栗子:

  • 全世界所产生的印刷材料的数据大概是200PB。
  • 全世界人类总共说过的话大概是5EB。
  • 国外知名网站P站2017年网站产生的总数据量为 3732PB 。
  • 一百万个汉字大概所需要的内存是2MB。

刚才好像混入了什么奇怪的东西。

大数据的特点:

  • 大量:必须的,不大都不好意思叫大数据。
  • 高速:这么多数据肯定要快速消化掉的,处理几十年也等不起啊,今年双十一的成交额总不能算到明年双十一再公布吧。
  • 多样:不同的场景会产生不同的数据,优酷就是用户浏览数据,视频数据,QQ音乐就是音乐数据。
  • 低价值密度:这个意思是即使数据量很大,但是我们关注的始终的特定的部分,而非整体,就像警察叔叔调监控一样,一年前一个月前的数据通常对他来说是没什么用的,他只要那么几个关键节点的监控数据就可以了。

应用场景就不说了,哪都是应用场景。

Hadoop是什么?

知道了什么是大数据,我们就得思考另外一个问题,弄这么多的数据我放哪啊?

杠精:不明摆着的么,当然放硬盘里啊,要不放哪儿,还能写纸上?
我:硬盘我知道,可是万一这块硬盘坏了,那数据不就没了吗?

路人:你系不系傻,你多放几块硬盘,分别放上去不就行了吗?

这个时候Hadoop来了,弟弟们都往边上靠靠,你们那种办法太笨拙,交给我,轻轻松松地给你搞定,小意思。

Hadoop是一个由Apache基金会所开发的分布式系统基础架构,主要用来解决大数据的存储和分析计算问题。

当然,Hadoop和Spring一样,到现在已经没法去仅仅理解为Hadoop这门技术了,就像你跟别人说,我这个新电商项目基于Spring写的,那别人肯定不会觉得你只用了Spring,会觉得你可能用了Spring MVC,boot,JPA等一系列Spring生态的技术。同样地,Hadoop也是如此,不仅仅是代表Hadoop本身这项技术,同时也代表围绕Hadoop的技术生态。

而且大家千万不要把事情想复杂,以为分布式存储什么这些概念都是多么深奥的东西,的确,官方概念确实是有点抽象晦涩了,但是我觉得,任何一项理论都一定来源于生活,因为是生活给予了他们灵感,但是生活并不是十分复杂的,所以任何深奥复杂的理论一定可以在生活中找到一个通俗易懂的解释。

什么是分布式存储,不跟大家吹,我初中的时候就已经在搞这个了,那时候流行看玄幻小说,那种大部头知道吧,特厚,通常一个班就只有那么一本,被教导主任没收了就完蛋了,谁都没得看,于是当时盛行把一本玄幻小说一页一页撕下来,每个同学几页,大家互相换着看,就算老师发现了也就只是没收了一部分,没办法全部歼灭。你看,分布式有了,存储有了,这不就是分布式存储吗?为了防止一本书被老师没收了导致这本书不完整,那就买三本,也这么几页几页分开存,这不就是多备份吗,没那么复杂,别老纠结那些学者写的给学者看的概念。

Hadoop发展史:

这个也没啥好讲的,我这里就列几个关键的点,感兴趣的朋友下去可以自己搜,网上一搜一大堆。

  • 一个叫Dung Cutting 没事用java写了一个全文搜索的框架 - Lucene
  • 数据量大的时候,Lucene性能跟不上了就。
  • 巧了,Google本身也是做全文搜索的,为啥人家性能就那么顶呢?
  • 通过学习谷歌,搞了个Nutch
  • 后来谷歌公开了部分GFS和MapReduce的细节。
  • Dung Cutting 一看这答案都给自己了,于是花了两年,注意是业余时间,自己实现了DFS和MapReduce,`Nutch·性能一下字就提上去了,一个字,牛逼。
  • 后来Hadoop作为Lucene子项目Nutch的一部分被正式引进了Apache基金会。
  • 然后Map-Reduce和NDFS一块被整合进了Hadoop项目里面,Hadoop就这么诞生了。

为啥人家业余时间就能搞出来这么牛逼的东西,我业余时间王者荣耀王者都上不去,难道有中间商赚差价?

Hadoop发行版本:

和Linux差不多,不同的公司在此基础上分别定制了自己的发行版本,Hadoop发行版本主要有三个,分别是:

  • Apache版本:最原始(最基础)的版本,对于入门学习最好,毕竟是出生地,血统也是最正的。
  • Cloudera :在大型互联网企业中用的较多。
  • Hortonworks:文档比较全。

不用想,我们肯定选Apache,也没啥别的原因,就是因为它基础,简单,不要钱。

Hadoop优势是什么?

Hadoop为啥这么牛逼,导致我们现在一说大数据开发,就会想到Hadoop?

毕竟写程序不是谈恋爱,没什么就算你不好我也依然爱你这回事,我们坏得很,哪个好用使哪个。

Hadoop在江湖中能混到今天的地位主要靠以下四点:

  • 高可靠性:Hadoop底层使用多个数据副本,即使Hadoop某个计算元素或存储出现故障,也不会导致数据的丢失,想想上面讲的分布式存储的例子。
  • 高扩展性:在集群间分配任务数据,可以方便的扩展数以千计的节点。就是,有一天运维早上一上班,卧槽,集群存储不够了,但是问题不大,因为在集群中加入一个新的节点或者去掉一个节点都分分钟的事儿。
  • 高效性:在MapReduce的思想下,Hadoop是并行工作的,以加快任务处理速度。
  • 高容错性:能够将失败的任务重新分配。

你说了一堆优点,Hadoop就没啥缺点吗?必须有,但是这个要到后面写到HDFS,MR的时候才能说,要不现在都不知道Hdfs是啥,说缺点的话不形象,就跟说人坏话一样,当着人家面儿说才有效果。

下面开始技术总结:

今天这篇文章呢,作为整套Hadoop系列教程的第一篇,主要是按照我写博客的习惯讲了一些基本的概念,希望大家看过之后心里能够对大数据和Hadoop有个基本的认识,另外,我写技术文章比较口语化,废话比较多,这个欢迎大家提建议,放心,提了我也不改,但是我写小说啥的还是非常严肃的,而且废话文你读起来比那些深奥玩弄概念的文章快多了(滑稽),下一篇文章呢,我们同样也是概念篇,主要讲HDFS,YARN,MR这三个Hadoop核心概念,之后就是实打实的要和代码接触了。

非常感谢能读到这里的朋友,你们的支持和关注是我坚持高质量分享下去的动力。

相关代码已经上传至本人github。一定要点个star啊啊啊啊啊啊啊

万水千山总是情,给个star行不行

韩数的开发笔记

欢迎点赞,关注我,有你好果子吃(滑稽)

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

学习WebFlux时常见的问题

发表于 2019-12-08

前言

只有光头才能变强。

文本已收录至我的GitHub精选文章,欢迎Star:github.com/ZhongFuChen…

回顾一下上篇我对WebFlux的入门,如果没读过的同学建议读一下再来看本篇文章,上一篇文章花了我很多的心血~~

  • 外行人都能看懂的WebFlux,错过了血亏

开局再来一张图,内容全靠编:

这篇主要写写我初学时对WebFlux的一些疑问,不知道大家在看上一篇文章的时候有没有相应的问题呢?

这次学WebFlux主要的动力是公司组内分享,写了一个PPT,有需要的同学在我的公众号(Java3y)下回复“PPT”即可获取。

一、本来就能实现异步非阻塞,为啥要用WebFlux?

相信有过相关了解的同学都知道,Servlet 3.1就已经支持异步非阻塞了。

我们可以以自维护线程池的方式实现异步

  • 说白了就是Tomcat的线程处理请求,然后把这个请求分发到自维护的线程处理,Tomcat的请求线程返回
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
复制代码@WebServlet(value = "/nonBlockingThreadPoolAsync", asyncSupported = true)
public class NonBlockingAsyncHelloServlet extends HttpServlet {

private static ThreadPoolExecutor executor = new ThreadPoolExecutor(100, 200, 50000L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(100));

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

AsyncContext asyncContext = request.startAsync();

ServletInputStream inputStream = request.getInputStream();

inputStream.setReadListener(new ReadListener() {
@Override
public void onDataAvailable() throws IOException {

}
@Override
public void onAllDataRead() throws IOException {
executor.execute(() -> {
new LongRunningProcess().run();

try {
asyncContext.getResponse().getWriter().write("Hello World!");
} catch (IOException e) {
e.printStackTrace();
}
asyncContext.complete();

});
}

@Override
public void onError(Throwable t) {
asyncContext.complete();
}
});


}

}

流程图如下:

异步非阻塞的图

上面的例子来源:

  • www.cnblogs.com/davenkin/p/…

简单的方式,我们还可以使用JDK 8 提供的CompletableFuture类,这个类可以方便的处理异步调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
复制代码protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
long t1 = System.currentTimeMillis();

// 开启异步
AsyncContext asyncContext = request.startAsync();

// 执行业务代码(doSomething 指的是处理耗费时间长的方法)
CompletableFuture.runAsync(() -> doSomeThing(asyncContext,
asyncContext.getRequest(), asyncContext.getResponse()));

System.out.println("async use:" + (System.currentTimeMillis() - t1));
}

要处理复杂的逻辑时,无论是回调或 CompletableFuture在代码编写上都会比较复杂(代码量大,不易于看懂),而WebFlux使用的是Reactor响应式流,里边提供了一系列的API供我们去处理逻辑,就很方便了。

回调地狱

更重要的是:

  • WebFlux使用起来可以像使用SpringMVC一样,能够大大减小学习成本
  • WebFlux也可以使用Functional Endpoints方式编程,总的来说还是要比回调/CompletableFuture要简洁和易编写。

无缝与SpringMVC的技术使用

值得一提的是:

如果Web容器使用的是Tomcat,那么就是使用Reactor桥接的servlet async api
如果Web容器是Netty,那么就是使用的Netty,天生支持Reactive

官方的推荐是使用Netty跑WebFlux

二、WebFlux性能的问题

我们从上篇文章中就发现,浏览器去调用处理慢的接口,无论是该接口是同步的,还是说是异步的,返回到浏览器的时间都是一致的。

  • 同步:服务器接收到请求,一个线程会处理请求,直到该请求处理完成,返回给浏览器
  • 异步:服务器接收到请求,一个线程会处理请求,然后指派别的线程处理请求,请求的线程直接空闲出来。

官网也说了:

Reactive and non-blocking generally do not make applications run faster

使用异步非阻塞的好处就是:

The key expected benefit of reactive and non-blocking is the ability to scale with a small, fixed number of threads and less memory.That makes applications more resilient under load, because they scale in a more predictable way

好处:只需要在程序内启动少量线程扩展,而不是水平通过集群扩展。异步能够规避文件IO/网络IO阻塞所带来的线程堆积。

下面来看一下针对相同的请求量,同步阻塞和异步非阻塞的吞吐量和响应时长对比:

吞吐量和RT对比

注:

  • 请求量不大时(3000左右),同步阻塞多线程处理请求,吞吐量和响应时长都没落后。(按道理WebFlux可能还要落后一些,毕竟多做了一步处理-->将请求委派给另一个线程去做处理
  • 请求量大时,线程数不够用,同步阻塞(MVC)只能等待,所以吞吐量要下降,响应时长要提高(排队)。

Spring WebFlux在应对高并发的请求时,借助于异步IO,能够以少量而稳定的线程处理更高吞吐量的请求,尤其是当请求处理过程如果因为业务复杂或IO阻塞等导致处理时长较长时,对比更加显著。

三、WebFlux实际应用

WebFlux需要非阻塞的业务代码,如果阻塞,需要自己开线程池去运行。WebFlux什么场景下可以替换SpringMVC呢?

  • 想要内存和线程数较少的场景
  • 网络较慢或者IO会经常出现问题的场景

SpringMVC和WebFlux更多的是互补关系,而不是替换。阻塞的场景该SpringMVC还是SpringMVC,并不是WebFlux出来就把SpringMVC取代了。

SpringMVC和WebFlux

如果想要发挥出WebFlux的性能,需要从Dao到Service,全部都要是Mono和Flux,目前官方的数据层Reactive框架只支持Redis,Mongo等几个,没有JDBC。

目前对于关系型数据库,Pivotal团队开源出R2DBC(Reactive Relational Database Connectivity),其GitHub地址为:

  • github.com/r2dbc

目前R2DBC支持三种数据源:

  • PostgreSQL
  • H2
  • Microsoft SQL Server

总的来说,因为WebFlux是响应式的,要想发挥出WebFlux的性能就得将代码全改成响应式的,而JDBC目前是没支持的(至少MySQL还没支持),而响应式的程序不好调试和编写(相对于同步的程序),所以现在WebFlux的应用场景还是相对较少的。

所以,我认为在网关层用WebFlux比较合适(本来就是网络IO较多的场景)

现在再回来看Spring官网的图,是不是就更亲切了?

Spring官网介绍图

参考资料:

  • blog.lovezhy.cc/2018/12/29/…

四、有必要学Functional Endpoints 编程模式吗?

前面也提到了,WebFlux提供了两种模式供我们使用,一种是SpringMVC 注解的,一种是叫Functional Endpoints的

Lambda-based, lightweight, and functional programming model

总的来看,就是配合Lambda和流式编程去使用WebFlux。如果你问我:有必要学吗?其实我觉得可以先放着。我认为现在WebFlux的应用场景还是比较少,等真正用到的时候再学也不是什么难事,反正就是学些API嘛~

有Lambda表达式和Stream流的基础,等真正用到的时候再学也不是啥问题~

以下是通过注解的方式来使用WebFlux的示例:

通过注解的方式来使用WebFlux

以下是通过Functional Endpoints的方式来使用WebFlux的示例:

路由分发器,相当于注解的GetMapping…

路由分发器

UserHandler,相当于UserController:

UserHanler

五、WebFlux的实际使用场景

总的来说,因为WebFlux是响应式的,要想发挥出WebFlux的性能就得将代码全改成响应式的,而JDBC目前是没支持的(至少MySQL还没支持),而响应式的程序不好调试和编写(相对于同步的程序),老项目也不太可能把依赖直接升上Spring5.0,所以现在WebFlux的应用场景还是相对较少的(个人觉得)。

网关层用WebFlux比较合适(本来就是网络IO较多的场景)

  • SpringCloud Gateway是基于WebFlux实现的

最后

服务器89/年,229/3年,买来送自己,送女朋友马上过年再合适不过了,买了搭建个项目给面试官看也香,还可以熟悉技术栈,(老用户用家人账号买就好了,我用我女朋友的😂)。扫码或者点击购买

搭建教程,从0开始一步一步带你搭建😂

这次学WebFlux主要的动力是公司组内分享,写了一个PPT,有需要的同学在我的公众号(Java3y)下回复“PPT”即可获取。

本已收录至我的GitHub精选文章,欢迎Star:github.com/ZhongFuChen…

乐于输出干货的Java技术公众号:Java3y。公众号内有300多篇原创技术文章、海量视频资源、精美脑图,关注即可获取!

转发到朋友圈是对我最大的支持!

非常感谢人才们能看到这里,如果这个文章写得还不错,觉得「三歪」我有点东西的话 求点赞 求关注️ 求分享👥 求留言💬 对暖男我来说真的 非常有用!!!

创作不易,各位的支持和认可,就是我创作的最大动力,我们下篇文章见!

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

后端程序员必备:索引失效的十大杂症

发表于 2019-12-08

背景

最近生产爆出一条慢sql,原因是用了or和!=,导致索引失效。于是,总结了索引失效的十大杂症,希望对大家有帮助,加油。

一、查询条件包含or,可能导致索引失效

新建一个user表,它有一个普通索引userId,结构如下:

1
2
3
4
5
6
7
8
复制代码CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`userId` int(11) NOT NULL,
`age` int(11) NOT NULL,
`name` varchar(255) NOT NULL,
PRIMARY KEY (`id`),
KEY `idx_userId` (`userId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
  1. 执行一条查询sql,它是会走索引的,如下图所示:
  2. 把or条件+没有索引的age加上,并不会走索引,如图:

分析&结论:

  • 对于or+没有索引的age这种情况,假设它走了userId的索引,但是走到age查询条件时,它还得全表扫描,也就是需要三步过程: 全表扫描+索引扫描+合并
  • 如果它一开始就走全表扫描,直接一遍扫描就完事。
  • mysql是有优化器的,处于效率与成本,遇到or条件,索引可能失效,看起来也合情合理。

注意: 如果or条件的列都加了索引,索引可能会走的,大家可以自己试一试。

二、如何字段类型是字符串,where时一定用引号括起来,否则索引失效

假设demo表结构如下:

1
2
3
4
5
6
7
复制代码CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`userId` varchar(32) NOT NULL,
`name` varchar(255) NOT NULL,
PRIMARY KEY (`id`),
KEY `idx_userId` (`userId`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

userId为字符串类型,是B+树的普通索引,如果查询条件传了一个数字过去,它是不走索引的,如图所示:

如果给数字加上’’,也就是传一个字符串呢,当然是走索引,如下图:

分析与结论:

为什么第一条语句未加单引号就不走索引了呢?
这是因为不加单引号时,是字符串跟数字的比较,它们类型不匹配,MySQL会做隐式的类型转换,把它们转换为浮点数再做比较。

三、like通配符可能导致索引失效。

并不是用了like通配符,索引一定失效,而是like查询是以%开头,才会导致索引失效。

表结构:

1
2
3
4
5
6
7
复制代码CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`userId` varchar(32) NOT NULL,
`name` varchar(255) NOT NULL,
PRIMARY KEY (`id`),
KEY `idx_userId` (`userId`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

like查询以%开头,索引失效,如图:

把%放后面,发现索引还是正常走的,如下:

把%加回来,改为只查索引的字段(覆盖索引),发现还是走索引,惊不惊喜,意不意外

结论:

like查询以%开头,会导致索引失效。可以有两种方式优化:

  • 使用覆盖索引
  • 把%放后面

附: 索引包含所有满足查询需要的数据的索引,称为覆盖索引(Covering Index)。

四、联合索引,查询时的条件列不是联合索引中的第一个列,索引失效。

表结构:(有一个联合索引idx_userid_age,userId在前,age在后)

1
2
3
4
5
6
7
8
复制代码CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`userId` int(11) NOT NULL,
`age` int(11) DEFAULT NULL,
`name` varchar(255) NOT NULL,
PRIMARY KEY (`id`),
KEY `idx_userid_age` (`userId`,`age`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

在联合索引中,查询条件满足最左匹配原则时,索引是正常生效的。请看demo:

如果条件列不是联合索引中的第一个列,索引失效,如下:

分析与结论:

  • 当我们创建一个联合索引的时候,如(k1,k2,k3),相当于创建了(k1)、(k1,k2)和(k1,k2,k3)三个索引,这就是最左匹配原则。
  • 联合索引不满足最左原则,索引一般会失效,但是这个还跟Mysql优化器有关的。

五、在索引列上使用mysql的内置函数,索引失效。

表结构:

1
2
3
4
5
6
7
8
复制代码CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`userId` varchar(32) NOT NULL,
`loginTime` datetime NOT NULL,
PRIMARY KEY (`id`),
KEY `idx_userId` (`userId`) USING BTREE,
KEY `idx_login_time` (`loginTime`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

虽然loginTime加了索引,但是因为使用了mysql的内置函数Date_ADD(),索引直接GG,如图:

六、对索引列运算(如,+、-、*、/),索引失效。

表结构:

1
2
3
4
5
6
7
复制代码CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`userId` varchar(32) NOT NULL,
`age` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_age` (`age`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

虽然age加了索引,但是因为它进行运算,索引直接迷路了。。。
如图:

七、索引字段上使用(!= 或者 < >,not in)时,可能会导致索引失效。

表结构:

1
2
3
4
5
6
7
8
复制代码CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`userId` int(11) NOT NULL,
`age` int(11) DEFAULT NULL,
`name` varchar(255) NOT NULL,
PRIMARY KEY (`id`),
KEY `idx_age` (`age`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

虽然age加了索引,但是使用了!= 或者 < >,not in这些时,索引如同虚设。如下:

八、索引字段上使用is null, is not null,可能导致索引失效。

表结构:

1
2
3
4
5
6
7
8
复制代码CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`card` varchar(255) DEFAULT NULL,
`name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_name` (`name`) USING BTREE,
KEY `idx_card` (`card`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

单个name字段加上索引,并查询name为非空的语句,其实会走索引的,如下:

单个card字段加上索引,并查询name为非空的语句,其实会走索引的,如下:

但是它两用or连接起来,索引就失效了,如下:

九、左连接查询或者右连接查询查询关联的字段编码格式不一样,可能导致索引失效。

新建两个表,一个user,一个user_job

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
复制代码CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) CHARACTER SET utf8mb4 DEFAULT NULL,
`age` int(11) NOT NULL,
PRIMARY KEY (`id`),
KEY `idx_name` (`name`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

CREATE TABLE `user_job` (
`id` int(11) NOT NULL,
`userId` int(11) NOT NULL,
`job` varchar(255) DEFAULT NULL,
`name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_name` (`name`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

user 表的name字段编码是utf8mb4,而user_job表的name字段编码为utf8。

执行左外连接查询,user_job表还是走全表扫描,如下:

如果把它们改为name字段编码一致,还是会走索引。

十、mysql估计使用全表扫描要比使用索引快,则不使用索引。

  • 当表的索引被查询,会使用最好的索引,除非优化器使用全表扫描更有效。优化器优化成全表扫描取决与使用最好索引查出来的数据是否超过表的30%的数据。
  • 不要给’性别’等增加索引。如果某个数据列里包含了均是”0/1”或“Y/N”等值,即包含着许多重复的值,就算为它建立了索引,索引效果不会太好,还可能导致全表扫描。

Mysql出于效率与成本考虑,估算全表扫描与使用索引,哪个执行快。这跟它的优化器有关,来看一下它的逻辑架构图吧(图片来源网上)

总结

总结了索引失效的十大杂症,在这里来个首尾呼应吧,分析一下我们生产的那条慢sql。
模拟的表结构与肇事sql如下:

1
2
3
4
5
6
7
8
复制代码CREATE TABLE `user_session` (
`user_id` varchar(32) CHARACTER SET utf8mb4 NOT NULL,
`device_id` varchar(64) NOT NULL,
`status` varchar(2) NOT NULL,
`create_time` datetime NOT NULL,
`update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`user_id`,`device_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
1
2
3
4
复制代码explain 
update user_session set status =1
where (`user_id` = '1' and `device_id`!='2')
or (`user_id` != '1' and `device_id`='2')

分析:

  • 执行的sql,使用了or条件,因为组合主键(user_id,device_id),看起来像是每一列都加了索引,索引会生效。
  • 但是出现!=,可能导致索引失效。也就是or+!=两大综合症,导致了慢更新sql。

解决方案:

那么,怎么解决呢?我们是把or条件拆掉,分成两条执行。同时给device_id加一个普通索引。

最后,总结了索引失效的十大杂症,希望大家在工作学习中,参考这十大杂症,多点结合执行计划expain和场景,具体分析 ,而不是按部就班,墨守成规,认定哪个情景一定索引失效。

个人公众号

  • 如果你是个爱学习的好孩子,可以关注我公众号,一起学习讨论。
  • 如果你觉得本文有哪些不正确的地方,可以评论,也可以关注我公众号,私聊我,大家一起学习进步哈。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Redis哨兵、复制、集群的设计原理,以及区别

发表于 2019-12-07

谈到Redis服务器的高可用,如何保证备份的机器是原始服务器的完整备份呢?这时候就需要哨兵和复制。

  • 哨兵(Sentinel):可以管理多个Redis服务器,它提供了监控,提醒以及自动的故障转移的功能。
  • 复制(Replication):则是负责让一个Redis服务器可以配备多个备份的服务器。

Redis正是利用这两个功能来保证Redis的高可用。

file

哨兵(sentinal)

哨兵是Redis集群架构中非常重要的一个组件,哨兵的出现主要是解决了主从复制出现故障时需要人为干预的问题。

1.Redis哨兵主要功能

(1)集群监控:负责监控Redis master和slave进程是否正常工作

(2)消息通知:如果某个Redis实例有故障,那么哨兵负责发送消息作为报警通知给管理员

(3)故障转移:如果master node挂掉了,会自动转移到slave node上

(4)配置中心:如果故障转移发生了,通知client客户端新的master地址

2.Redis哨兵的高可用

原理:当主节点出现故障时,由Redis Sentinel自动完成故障发现和转移,并通知应用方,实现高可用性。

file

哨兵机制建立了多个哨兵节点(进程),共同监控数据节点的运行状况。

同时哨兵节点之间也互相通信,交换对主从节点的监控状况。

每隔1秒每个哨兵会向整个集群:Master主服务器+Slave从服务器+其他Sentinel(哨兵)进程,发送一次ping命令做一次心跳检测。

这个就是哨兵用来判断节点是否正常的重要依据,涉及两个新的概念:主观下线和客观下线。

1. 主观下线:一个哨兵节点判定主节点down掉是主观下线。

2.客观下线:只有半数哨兵节点都主观判定主节点down掉,此时多个哨兵节点交换主观判定结果,才会判定主节点客观下线。

3.原理:基本上哪个哨兵节点最先判断出这个主节点客观下线,就会在各个哨兵节点中发起投票机制Raft算法(选举算法),最终被投为领导者的哨兵节点完成主从自动化切换的过程。

Redis 复制(Replication)

Redis为了解决单点数据库问题,会把数据复制多个副本部署到其他节点上,通过复制,实现Redis的高可用性,实现对数据的冗余备份,保证数据和服务的高度可靠性。

1.数据复制原理(执行步骤)

file

①从数据库向主数据库发送sync(数据同步)命令。

②主数据库接收同步命令后,会保存快照,创建一个RDB文件。

③当主数据库执行完保持快照后,会向从数据库发送RDB文件,而从数据库会接收并载入该文件。

④主数据库将缓冲区的所有写命令发给从服务器执行。

⑤以上处理完之后,之后主数据库每执行一个写命令,都会将被执行的写命令发送给从数据库。

注意:在Redis2.8之后,主从断开重连后会根据断开之前最新的命令偏移量进行增量复制。

file

Redis 主从复制、哨兵和集群这三个有什么区别

1.主从模式:读写分离,备份,一个Master可以有多个Slaves。

2.哨兵sentinel:监控,自动转移,哨兵发现主服务器挂了后,就会从slave中重新选举一个主服务器。

3.集群:为了解决单机Redis容量有限的问题,将数据按一定的规则分配到多台机器,内存/QPS不受限于单机,可受益于分布式集群高扩展性。

file

本文由博客一文多发平台 OpenWrite 发布!

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

HttpClient工具类 HttpClient工具类

发表于 2019-12-07

HttpClient工具类

  1. 什么是HttpClient

HTTP 协议可能是现在 Internet 上使用得最多、最重要的协议了,越来越多的 Java 应用程序需要直接通过 HTTP 协议来访问网络资源。虽然在 JDK 的 java net包中已经提供了访问 HTTP 协议的基本功能,但是对于大部分应用程序来说,JDK 库本身提供的功能还不够丰富和灵活。HttpClient 是Apache HttpComponents 下的子项目,用来提供高效的、最新的、功能丰富的支持 HTTP 协议的客户端编程工具包,并且它支持 HTTP 协议最新的版本和建议。

  1. 功能介绍

  • 实现了所有 HTTP 的方法(GET,POST,PUT,DELETE 等)
  • 支持自动转向
  • 支持 HTTPS 协议
  • 支持代理服务器等
  1. 版本比较

主要是基于 HttpClient4.5.5 版本的来讲解的,也是现在最新的版本,之所以要提供版本说明的是因为 HttpClient 3 版本和 HttpClient 4 版本差别还是很多大的,基本HttpClient里面的接口都变了,你把 HttpClient 3 版本的代码拿到 HttpClient 4 上面都运行不起来,会报错的。所以一定要注意 HtppClient 的版本问题。

  1. HttpClient不能做的事情

HttpClient 不是浏览器,它是一个客户端 HTTP 协议传输类库。HttpClient 被用来发送和接受 HTTP 消息。HttpClient 不会处理 HTTP 消息的内容,不会进行 javascript 解析,不会关心 content type,如果没有明确设置,HttpClient 也不会对请求进行格式化、重定向 url,或者其他任何和 HTTP 消息传输相关的功能。

  1. HttpClient使用流程

使用HttpClient发送请求、接收响应很简单,一般需要如下几步即可。

  1. 创建HttpClient对象。
  2. 创建请求方法的实例,并指定请求URL。如果需要发送GET请求,创建HttpGet对象;如果需要发送POST请求,创建HttpPost对象。
  3. 如果需要发送请求参数,可调用HttpGetsetParams方法来添加请求参数;对于HttpPost对象而言,可调用setEntity(HttpEntity entity)方法来设置请求参数。
  4. 调用HttpClient对象的execute(HttpUriRequest request)发送请求,该方法返回一个HttpResponse对象。
  5. 调用HttpResponse的getAllHeaders()、getHeaders(String name)等方法可获取服务器的响应头;调用HttpResponse的getEntity()方法可获取HttpEntity对象,该对象包装了服务器的响应内容。程序可通过该对象获取服务器的响应内容。
  6. 释放连接。无论执行方法是否成功,都必须释放连接
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
复制代码<properties>
<httpclient.version>4.5.5</httpclient.version>

<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>

<dependencies>
<!-- springboot的web和test启动库 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>

<!-- apache httpclient组件 -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>

</dependencies>

<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<!-- 跳过单元测试 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skipTests>true</skipTests>
</configuration>
</plugin>
</plugins>
</build>

6.2 编写spring-boot启动类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
复制代码/**
* Description: springboot启动类
*
* @author JourWon
* @date Created on 2018年4月19日
*/
@SpringBootApplication
public class Application {

public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}

}

6.3 编写get和post请求测试controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
复制代码/**
* Description: get和post请求测试controller
*
* @author JourWon
* @date Created on 2018年4月19日
*/
@RestController
@RequestMapping("/hello")
public class HelloWorldController {

@GetMapping("/get")
public String get() throws InterruptedException {
return "get无参请求成功";
}

@GetMapping("/getWithParam")
public String getWithParam(@RequestParam String message) {
return "get带参请求成功,参数message: " + message;
}

@PostMapping("/post")
public String post(@RequestHeader("User-Agent") String userAgent,
@RequestHeader("Accept") String accept,
@RequestHeader("Accept-Language") String acceptLanguage,
@RequestHeader("Accept-Encoding") String acceptEncoding,
@RequestHeader("Cookie") String cookie,
@RequestHeader("Connection") String conn) {
// 打印请求头信息
System.out.println("Cookie = " + cookie);
System.out.println("Connection = " + conn);
System.out.println("Accept = " + accept);
System.out.println("Accept-Language = " + acceptLanguage);
System.out.println("Accept-Encoding = " + acceptEncoding);
System.out.println("User-Agent = " + userAgent);

return "post无参请求成功";
}

@PostMapping("/postWithParam")
public String postWithParam(@RequestParam String code, @RequestParam String message) {
return "post带参请求成功,参数code: " + code + ",参数message: " + message;
}

}

6.4 创建httpClient响应结果对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
复制代码/**
* Description: 封装httpClient响应结果
*
* @author JourWon
* @date Created on 2018年4月19日
*/
public class HttpClientResult implements Serializable {

/**
* 响应状态码
*/
private int code;

/**
* 响应数据
*/
private String content;

}

6.5 重点,编写httpclient工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
复制代码/**
* Description: httpClient工具类
*
* @author JourWon
* @date Created on 2018年4月19日
*/
public class HttpClientUtils {

// 编码格式。发送编码格式统一用UTF-8
private static final String ENCODING = "UTF-8";

// 设置连接超时时间,单位毫秒。
private static final int CONNECT_TIMEOUT = 6000;

// 请求获取数据的超时时间(即响应时间),单位毫秒。
private static final int SOCKET_TIMEOUT = 6000;

/**
* 发送get请求;不带请求头和请求参数
*
* @param url 请求地址
* @return
* @throws Exception
*/
public static HttpClientResult doGet(String url) throws Exception {
return doGet(url, null, null);
}

/**
* 发送get请求;带请求参数
*
* @param url 请求地址
* @param params 请求参数集合
* @return
* @throws Exception
*/
public static HttpClientResult doGet(String url, Map<String, String> params) throws Exception {
return doGet(url, null, params);
}

/**
* 发送get请求;带请求头和请求参数
*
* @param url 请求地址
* @param headers 请求头集合
* @param params 请求参数集合
* @return
* @throws Exception
*/
public static HttpClientResult doGet(String url, Map<String, String> headers, Map<String, String> params) throws Exception {
// 创建httpClient对象
CloseableHttpClient httpClient = HttpClients.createDefault();

// 创建访问的地址
URIBuilder uriBuilder = new URIBuilder(url);
if (params != null) {
Set<Entry<String, String>> entrySet = params.entrySet();
for (Entry<String, String> entry : entrySet) {
uriBuilder.setParameter(entry.getKey(), entry.getValue());
}
}

// 创建http对象
HttpGet httpGet = new HttpGet(uriBuilder.build());
/**
* setConnectTimeout:设置连接超时时间,单位毫秒。
* setConnectionRequestTimeout:设置从connect Manager(连接池)获取Connection
* 超时时间,单位毫秒。这个属性是新加的属性,因为目前版本是可以共享连接池的。
* setSocketTimeout:请求获取数据的超时时间(即响应时间),单位毫秒。 如果访问一个接口,多少时间内无法返回数据,就直接放弃此次调用。
*/
RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(CONNECT_TIMEOUT).setSocketTimeout(SOCKET_TIMEOUT).build();
httpGet.setConfig(requestConfig);

// 设置请求头
packageHeader(headers, httpGet);

// 创建httpResponse对象
CloseableHttpResponse httpResponse = null;

try {
// 执行请求并获得响应结果
return getHttpClientResult(httpResponse, httpClient, httpGet);
} finally {
// 释放资源
release(httpResponse, httpClient);
}
}

/**
* 发送post请求;不带请求头和请求参数
*
* @param url 请求地址
* @return
* @throws Exception
*/
public static HttpClientResult doPost(String url) throws Exception {
return doPost(url, null, null);
}

/**
* 发送post请求;带请求参数
*
* @param url 请求地址
* @param params 参数集合
* @return
* @throws Exception
*/
public static HttpClientResult doPost(String url, Map<String, String> params) throws Exception {
return doPost(url, null, params);
}

/**
* 发送post请求;带请求头和请求参数
*
* @param url 请求地址
* @param headers 请求头集合
* @param params 请求参数集合
* @return
* @throws Exception
*/
public static HttpClientResult doPost(String url, Map<String, String> headers, Map<String, String> params) throws Exception {
// 创建httpClient对象
CloseableHttpClient httpClient = HttpClients.createDefault();

// 创建http对象
HttpPost httpPost = new HttpPost(url);
/**
* setConnectTimeout:设置连接超时时间,单位毫秒。
* setConnectionRequestTimeout:设置从connect Manager(连接池)获取Connection
* 超时时间,单位毫秒。这个属性是新加的属性,因为目前版本是可以共享连接池的。
* setSocketTimeout:请求获取数据的超时时间(即响应时间),单位毫秒。 如果访问一个接口,多少时间内无法返回数据,就直接放弃此次调用。
*/
RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(CONNECT_TIMEOUT).setSocketTimeout(SOCKET_TIMEOUT).build();
httpPost.setConfig(requestConfig);
// 设置请求头
/*httpPost.setHeader("Cookie", "");
httpPost.setHeader("Connection", "keep-alive");
httpPost.setHeader("Accept", "application/json");
httpPost.setHeader("Accept-Language", "zh-CN,zh;q=0.9");
httpPost.setHeader("Accept-Encoding", "gzip, deflate, br");
httpPost.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36");*/
packageHeader(headers, httpPost);

// 封装请求参数
packageParam(params, httpPost);

// 创建httpResponse对象
CloseableHttpResponse httpResponse = null;

try {
// 执行请求并获得响应结果
return getHttpClientResult(httpResponse, httpClient, httpPost);
} finally {
// 释放资源
release(httpResponse, httpClient);
}
}

/**
* 发送put请求;不带请求参数
*
* @param url 请求地址
* @param params 参数集合
* @return
* @throws Exception
*/
public static HttpClientResult doPut(String url) throws Exception {
return doPut(url);
}

/**
* 发送put请求;带请求参数
*
* @param url 请求地址
* @param params 参数集合
* @return
* @throws Exception
*/
public static HttpClientResult doPut(String url, Map<String, String> params) throws Exception {
CloseableHttpClient httpClient = HttpClients.createDefault();
HttpPut httpPut = new HttpPut(url);
RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(CONNECT_TIMEOUT).setSocketTimeout(SOCKET_TIMEOUT).build();
httpPut.setConfig(requestConfig);

packageParam(params, httpPut);

CloseableHttpResponse httpResponse = null;

try {
return getHttpClientResult(httpResponse, httpClient, httpPut);
} finally {
release(httpResponse, httpClient);
}
}

/**
* 发送delete请求;不带请求参数
*
* @param url 请求地址
* @param params 参数集合
* @return
* @throws Exception
*/
public static HttpClientResult doDelete(String url) throws Exception {
CloseableHttpClient httpClient = HttpClients.createDefault();
HttpDelete httpDelete = new HttpDelete(url);
RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(CONNECT_TIMEOUT).setSocketTimeout(SOCKET_TIMEOUT).build();
httpDelete.setConfig(requestConfig);

CloseableHttpResponse httpResponse = null;
try {
return getHttpClientResult(httpResponse, httpClient, httpDelete);
} finally {
release(httpResponse, httpClient);
}
}

/**
* 发送delete请求;带请求参数
*
* @param url 请求地址
* @param params 参数集合
* @return
* @throws Exception
*/
public static HttpClientResult doDelete(String url, Map<String, String> params) throws Exception {
if (params == null) {
params = new HashMap<String, String>();
}

params.put("_method", "delete");
return doPost(url, params);
}

/**
* Description: 封装请求头
* @param params
* @param httpMethod
*/
public static void packageHeader(Map<String, String> params, HttpRequestBase httpMethod) {
// 封装请求头
if (params != null) {
Set<Entry<String, String>> entrySet = params.entrySet();
for (Entry<String, String> entry : entrySet) {
// 设置到请求头到HttpRequestBase对象中
httpMethod.setHeader(entry.getKey(), entry.getValue());
}
}
}

/**
* Description: 封装请求参数
*
* @param params
* @param httpMethod
* @throws UnsupportedEncodingException
*/
public static void packageParam(Map<String, String> params, HttpEntityEnclosingRequestBase httpMethod)
throws UnsupportedEncodingException {
// 封装请求参数
if (params != null) {
List<NameValuePair> nvps = new ArrayList<NameValuePair>();
Set<Entry<String, String>> entrySet = params.entrySet();
for (Entry<String, String> entry : entrySet) {
nvps.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
}

// 设置到请求的http对象中
httpMethod.setEntity(new UrlEncodedFormEntity(nvps, ENCODING));
}
}

/**
* Description: 获得响应结果
*
* @param httpResponse
* @param httpClient
* @param httpMethod
* @return
* @throws Exception
*/
public static HttpClientResult getHttpClientResult(CloseableHttpResponse httpResponse,
CloseableHttpClient httpClient, HttpRequestBase httpMethod) throws Exception {
// 执行请求
httpResponse = httpClient.execute(httpMethod);

// 获取返回结果
if (httpResponse != null && httpResponse.getStatusLine() != null) {
String content = "";
if (httpResponse.getEntity() != null) {
content = EntityUtils.toString(httpResponse.getEntity(), ENCODING);
}
return new HttpClientResult(httpResponse.getStatusLine().getStatusCode(), content);
}
return new HttpClientResult(HttpStatus.SC_INTERNAL_SERVER_ERROR);
}

/**
* Description: 释放资源
*
* @param httpResponse
* @param httpClient
* @throws IOException
*/
public static void release(CloseableHttpResponse httpResponse, CloseableHttpClient httpClient) throws IOException {
// 释放资源
if (httpResponse != null) {
httpResponse.close();
}
if (httpClient != null) {
httpClient.close();
}
}

}

6.6 启动spring-boot,测试get、post请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
复制代码/**
* Description: HttpClientUtils工具类测试
*
* @author JourWon
* @date Created on 2018年4月19日
*/
public class HttpClientUtilsTest {

/**
* Description: 测试get无参请求
*
* @throws Exception
*/
@Test
public void testGet() throws Exception {
HttpClientResult result = HttpClientUtils.doGet("http://127.0.0.1:8080/hello/get");
System.out.println(result);
}

/**
* Description: 测试get带参请求
*
* @throws Exception
*/
@Test
public void testGetWithParam() throws Exception {
Map<String, String> params = new HashMap<String, String>();
params.put("message", "helloworld");
HttpClientResult result = HttpClientUtils.doGet("http://127.0.0.1:8080/hello/getWithParam", params);
System.out.println(result);
}

/**
* Description: 测试post带请求头不带请求参数
*
* @throws Exception
*/
@Test
public void testPost() throws Exception {
Map<String, String> headers = new HashMap<String, String>();
headers.put("Cookie", "123");
headers.put("Connection", "keep-alive");
headers.put("Accept", "application/json");
headers.put("Accept-Language", "zh-CN,zh;q=0.9");
headers.put("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36");
HttpClientResult result = HttpClientUtils.doPost("http://127.0.0.1:8080/hello/post", headers, null);
System.out.println(result);
}

/**
* Description: 测试post带参请求
*
* @throws Exception
*/
@Test
public void testPostWithParam() throws Exception {
Map<String, String> params = new HashMap<String, String>();
params.put("code", "0");
params.put("message", "helloworld");
HttpClientResult result = HttpClientUtils.doPost("http://127.0.0.1:8080/hello/postWithParam", params);
System.out.println(result);
}

}

HttpsUtils工具类

实现利用HttpClient发送Https请求,信任任何证书、不对主机校验

参见官方文档

1571896539517

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
复制代码public class HttpsUtils {
private static final String HTTP = "http";
private static final String HTTPS = "https";
private static SSLConnectionSocketFactory sslsf = null;
private static PoolingHttpClientConnectionManager cm = null;
private static SSLContextBuilder builder = null;
static {
try {
builder = new SSLContextBuilder();
// 全部信任 不做身份鉴定
builder.loadTrustMaterial(null, (org.apache.http.ssl.TrustStrategy) (x509Certificates, s) -> true);
sslsf = new SSLConnectionSocketFactory(builder.build(), new String[]{"SSLv2Hello", "SSLv3", "TLSv1", "TLSv1.2"},
null, NoopHostnameVerifier.INSTANCE);
//设置协议http和https对应的处理socket连接工厂的对象
Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
.register(HTTP, new PlainConnectionSocketFactory())
.register(HTTPS, sslsf)
.build();
cm = new PoolingHttpClientConnectionManager(registry);
cm.setMaxTotal(200);//max connection
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* httpClient get请求
* @param url 请求url
* @param
* @param
* @return 可能为空 需要处理
* @throws Exception
*
*/
public static String get(String url) throws Exception {
String result = "";
CloseableHttpClient httpClient = null;
try {
httpClient = getHttpClient();
HttpGet httpGet = new HttpGet(url);
// 设置头信息 ,必须添加,否则会报403
httpGet.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36");

HttpResponse httpResponse = httpClient.execute(httpGet);
int statusCode = httpResponse.getStatusLine().getStatusCode();
if (statusCode == HttpStatus.SC_OK) {
HttpEntity resEntity = httpResponse.getEntity();
result = EntityUtils.toString(resEntity, "utf-8");
} else {
readHttpResponse(httpResponse);
}
} catch (Exception e) {throw e;
} finally {
if (httpClient != null) {
httpClient.close();
}
}
return result;
}
public static CloseableHttpClient getHttpClient() throws Exception {
CloseableHttpClient httpClient = HttpClients.custom()
.setSSLSocketFactory(sslsf)
.setConnectionManager(cm)
.setConnectionManagerShared(true)
.build();
return httpClient;
}
public static String readHttpResponse(HttpResponse httpResponse)
throws ParseException, IOException {
StringBuilder builder = new StringBuilder();
// 获取响应消息实体
HttpEntity entity = httpResponse.getEntity();
// 响应状态
builder.append("status:" + httpResponse.getStatusLine());
builder.append("headers:");
HeaderIterator iterator = httpResponse.headerIterator();
while (iterator.hasNext()) {
builder.append("\t" + iterator.next());
}
// 判断响应实体是否为空
if (entity != null) {
String responseString = EntityUtils.toString(entity);
builder.append("response length:" + responseString.length());
builder.append("response content:" + responseString.replace("\r\n", ""));
}
return builder.toString();
}
}

参考github开源工具

github.com/Arronlong/h…

github.com/JourWon/htt…

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

elasticsearch-head插件安装

发表于 2019-12-06

前言介绍

安装Elasticsearch的head插件,用于方便操作Elasticsearch

elasticsearch-head 是用于监控 Elasticsearch 状态的客户端插件,包括数据可视化、执行增删改查操作等。elasticsearch-head 插件的安装在 Linux 和 Windows 没什么区别,安装之前确保当前系统已经安装 nodejs 即可。

安装环境

  1. 安装node.js并配置环境变量PATH{path:D:\Program Files\nodejs}
  • nodejs下载
  • 执行安装,配置环境变量{path:D:\Program Files\nodejs}
  • 查看nodejs版本;node -v
  1. 安装elasticsearch-head
  • 下载elasticsearch-head
  • 将elasticsearch-head放到与elasticsearch同层级文件夹下
  • 修改elasticsearch-head/Gruntfile.js
1
2
3
4
5
6
7
8
9
复制代码connect: {
server: {
options: {
port: 9100,
base: '.',
keepalive: true
}
}
}
  • 修改elasticsearch-6.2.2/config/elasticsearch.yml *添加配置信息
1
2
复制代码http.cors.enabled: true
http.cors.allow-origin: "*"

3、启动elasticsearch-head

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
复制代码Microsoft Windows [版本 6.1.7601]
版权所有 (c) 2009 Microsoft Corporation。保留所有权利。

C:\Users\user>node -v
v10.16.0

C:\Users\user>D:

D:\>cd D:\Program Files\elasticsearch\head

D:\Program Files\elasticsearch\head>npm run start

> elasticsearch-head@0.0.0 start D:\Program Files\elasticsearch\head
> grunt server

Running "connect:server" (connect) task
Waiting forever...
Started connect web server on http://localhost:9100

运行结果


微信公众号:bugstack虫洞栈,欢迎关注&获取源码

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

windows环境下安装elasticsearch622

发表于 2019-12-06

前言介绍

在windows环境下安装Elasticsearch 6.2.2

ElasticSearch是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。Elasticsearch是用Java语言开发的,并作为Apache许可条款下的开放源码发布,是一种流行的企业级搜索引擎。ElasticSearch用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便。官方客户端在Java、.NET(C#)、PHP、Python、Apache Groovy、Ruby和许多其他语言中都是可用的。根据DB-Engines的排名显示,Elasticsearch是最受欢迎的企业搜索引擎,其次是Apache Solr,也是基于Lucene。

安装环境

1、Elasticsearch 6.2.2 下载 www.elastic.co/cn/download…

2、JDK 1.8 (jdk1.7及以下不能支持Elasticsearch)下载 www.oracle.com/technetwork…

3、安装cmd进入elasticsearch-6.2.2\bin,执行elasticsearch.bat

4、启动完成后打开http://localhost:9200 {默认端口9200}


微信公众号:bugstack虫洞栈,欢迎关注&获取源码

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

【面试官之你说我听】-MyBatis常见面试题 【面试官之你

发表于 2019-12-06

【面试官之你说我听】-MyBatis常见面试题

欢迎关注文章系列,一起学习
《提升能力,涨薪可待篇》

《面试知识,工作可待篇》

《实战演练,拒绝996篇》

也欢迎关注微信公众号【Ccww技术博客】,原创技术文章第一时间推出

如果此文对你有帮助、喜欢的话,那就点个赞呗,点个关注呗!

往期文章系列:

  • 【面试宝典】:检验是否为合格的初中级程序员的面试知识点,你都知道了吗?查漏补缺
  • 《面试知识,工作可待:集合篇》-java集合面试知识大全
  • java多线程并发系列–基础知识点(笔试、面试必备)
  • 一文理解JVM虚拟机(内存、垃圾回收、性能优化)解决面试中遇到问题
  • ….

精讲#{}和${}的区别是什么?

  • mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值。
  • mybatis在处理${}时,就是把${}替换成变量的值。
  • 使用#{}可以有效的防止SQL注入,提高系统安全性。原因在于:预编译机制。预编译完成之后,SQL的结构已经固定,即便用户输入非法参数,也不会对SQL的结构产生影响,从而避免了潜在的安全风险。
  • 预编译是提前对SQL语句进行预编译,而其后注入的参数将不会再进行SQL编译。我们知道,SQL注入是发生在编译的过程中,因为恶意注入了某些特殊字符,最后被编译成了恶意的执行操作。而预编译机制则可以很好的防止SQL注入。

既然${}会引起sql注入,为什么有了#{}还需要有${}呢?那其存在的意义是什么?

#{}主要用于预编译,而预编译的场景其实非常受限,而${}用于替换,很多场景会出现替换,而这种场景可不是预编译

数据库链接中断如何处理

数据库的访问底层是通过tcp实现的,当链接中断是程序是无法得知,导致程序一直会停顿一段时间在这,最终会导致用户体验不好,因此面对数据库连接中断的异常,该怎么设置mybatis呢?

connection操作底层是一个循环处理操作,因此可以进行时间有关的参数:

  • max_idle_time : 表明最大的空闲时间,超过这个时间socket就会关闭
  • connect_timeout : 表明链接的超时时间

数据库服务器活的杠杠的,但是因为网络用塞,客户端仍然连不上服务器端,这个时候就要设置timeout,别一直傻等着

在开发过程中,经常遇到插入重复的现象,这种情况该如何解决呢?

插入的过程一般都是分两步的:先判断是否存在记录,没有存在则插入否则不插入。如果存在并发操作,那么同时进行了第一步,然后大家都发现没有记录,然后都插入了数据从而造成数据的重复

解决插入重复的思路 :

  • 先判断数据库是否存在数据,有的话则不进行任何操作。没有数据的话,进行下一步;
  • 向redis set key,其中只有一个插入操作A会成功,其他并发的操作(B和C…)都会失败的 ;
  • 当set key 成功的操作A,开始执行插入数据操作,无论是否插入数据成功,都在需要将redis key删除。【注】插入不成功可以多尝试几次,增加成功的概率 ;
  • 然而set key 失败的操作B和C,sleep一下,竞争赢的插入操作重复以上步骤。

总结:多线程同时插入数据,谁获取锁并插入数据成功了其他线程不做任何操作。当插入数据失败后,其他线程抢锁进行插入数据。

事务执行过程中宕机的应对处理方式

数据库插入百万级数据的时候,还没操作完,但是把服务器重启了,数据库会继续执行吗? 还是直接回滚了?

不会自动继续执行,不会自动直接回滚 ,但可以依据事务日志进行回滚或者进行执行。

事务开启时,事务中的操作,都会先写入存储引擎的日志缓冲中,在事务提交之前,这些缓冲的日志都需要提前刷新到磁盘上持久化 ,两种类型:

在事务执行的过程中,除了记录redo log,还会记录一定量的undo log。

  • redo log :按语句的执行顺序,依次交替的记录在一起
  • undo log: 主要为事务的回滚服务。undo log记录了数据在每个操作前的状态,如果事务执行过程中需要回滚,就可以根据undo log进行回滚操作。

Java客户端中的一个Connection是不是在MySQL中就对应一个线程来处理这个链接呢?

Java客户端中的一个Connection不是在MySQL中就对应一个线程来处理这个链接,而是:

监听socket的主线程+线程池里面固定数目的工作线程来处理的

高性能服务器端端开发底层主要靠I/O复用来处理,这种模式:

单线程+事件处理机制

在MySQL有一个主线程,这是单线程(与Java中处处强调多线程的思想有点不同哦),它不断的循环查看是否有socket是否有读写事件,如果有读写事件,再从线程池里面找个工作线程处理这个socket的读写事件,完事之后工作线程会回到线程池。

Mybatis中的Dao接口和XML文件里的SQL是如何建立关系的?

  • 解析XML: 初始化SqlSessionFactoryBean会将mapperLocations路径下所有的XML文件进行解析
    • 创建SqlSource: Mybatis会把每个SQL标签封装成SqlSource对象,可以为动态SQL和静态SQL
    • 创建MappedStatement: XML文件中的每一个SQL标签就对应一个MappedStatement对象 ,并由 Configuration解析XML
  • Dao接口代理: Spring中的FactoryBean 和 JDK动态代理返回了可以注入的一个Dao接口的代理对象
  • 执行: 通过statement全限定类型+方法名拿到MappedStatement 对象,然后通过执行器Executor去执行具体SQL并返回

当实体类中的属性名和表中的字段名不一样,怎么办 ?

  • 通过在查询的sql语句中定义字段名的别名,让字段名的别名和实体类的属性名一致
  • 通过来映射字段名和实体类属性名的一一对应的关系。

模糊查询like语句该怎么写?

  • 在Java代码中添加sql通配符
1
2
复制代码string name = "%Ccww%"; 
list<name> names = mapper.selectName(name);
1
2
3
复制代码<select id="selectName"> 
select * from users where name like #{value}
</select>
  • 在sql语句中拼接通配符,会引起sql注入
1
2
3
复制代码<select id="selectName">
select * from users where name like "%"#{value}"%"
</select>

什么是MyBatis的接口绑定?有哪些实现方式?

接口绑定 : 在MyBatis中任意定义接口,然后把接口里边的方法和SQL语句绑定,我们可以直接调用接口方法,比起SqlSession提供的方法我们可以有更加灵活的选择和设置

接口绑定有两种实现方式 :

  • 通过注解绑定: 在接口的方法上加上 @Select、@Update等注解,里面包含Sql语句来绑定;
  • 通过xml绑定 : 要指定xml映射文件里面的namespace必须为接口的全路径名 。

使用MyBatis的mapper接口调用时要注意的事项

  • Mapper接口方法名和mapper.xml中定义的每个sql的id相同;
  • Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql 的parameterType的类型相同;
  • Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同;
  • Mapper.xml文件中的namespace即是mapper接口的类路径。

通常一个Xml映射文件,都会写一个Dao接口与之对应,请问,这个Dao接口的工作原理是什么?Dao接口里的方法,参数不同时,方法能重载吗?

  • Dao接口为Mapper接口。
  • 接口的全限名为映射文件中的namespace的值;
  • 接口的方法名为映射文件中Mapper的Statement的id值;
  • 接口方法内的参数为传递给sql的参数。

Mapper接口是没有实现类的,当调用接口方法时,接口全限名+方法名拼接字符串作为key值,可唯一定位一个MapperStatement。在Mybatis中,每一个

Mapper接口里的方法,是不能重载的,因为是使用 全限名+方法名 的保存和寻找策略。Mapper 接口的工作原理是JDK动态代理,Mybatis运行时会使用JDK动态代理为Mapper接口生成代理对象proxy,代理对象会拦截接口方法,转而执行MapperStatement所代表的sql,然后将sql执行结果返回。

Mybatis的Xml映射文件中,不同的Xml映射文件,id是否可以重复?

基于上面,可以得知

Statement=namespace+id

如果配置了namespace可以重复的 ,但如果没有配置namespace的话,那么相同的id就会导致覆盖了。

Mybatis的一级、二级缓存的作用是什么?

(1)一级缓存: 基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为 Session,当 Session flush 或 close 之后,该 Session 中的所有 Cache 就将清空,默认打开一级缓存。

(2)二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap 存储,不同在于其存储作用域为 Mapper(Namespace),并且可自定义存储源,如 Ehcache。默认不打开二级缓存,要开启二级缓存,使用二级缓存属性类需要实现Serializable序列化接口(可用来保存对象的状态),可在它的映射文件中配置 ;

(3)对于缓存数据更新机制,当某一个作用域(一级缓存 Session/二级缓存Namespaces)的进行了C/U/D 操作后,默认该作用域下所有 select 中的缓存将被 clear。

Mybatis 是如何进行分页的?分页插件的原理是什么?

Mybatis 使用 RowBounds 对象进行分页,它是针对 ResultSet 结果集执行的内存分页,而非数据库分页。

在实际场景下,使用如下两种方案:

  • 在 SQL 内直接书写带有数据库分页的参数来完成数据库分页功能
  • 也可以使用分页插件来完成数据库分页。

这两者都是基于数据库分页,差别在于前者是工程师手动编写分页条件,后者是插件自动添加分页条件。


分页插件的基本原理是使用 Mybatis 提供的插件接口,实现自定义分页插件。在插件的拦截方法内,拦截待执行的 SQL ,然后重写 SQL ,根据dialect 方言,添加对应的物理分页语句和物理分页参数。

举例:SELECT * FROM student ,拦截 SQL 后重写为:select * FROM student LIMI 0,10 。

目前市面上目前使用比较广泛的 MyBatis 分页插件有:

  • Mybatis-PageHelper
  • MyBatis-Plus

Mybatis 动态 SQL 是做什么的?都有哪些动态 SQL ?能简述一下动态 SQL 的执行原理吗?

  • Mybatis 动态 SQL ,可以让我们在 XML 映射文件内,以 XML 标签的形式编写动态 SQL ,完成逻辑判断和动态拼接 SQL 的功能。
  • Mybatis 提供了 9 种动态 SQL 标签:
    • 、
  • 其执行原理为,使用 OGNL 的表达式,从 SQL 参数对象中计算表达式的值,根据表达式的值动态拼接 SQL ,以此来完成动态 SQL 的功能

Mybatis是否支持延迟加载?如果支持,它的实现原理是什么?

Mybatis仅支持association关联对象和collection关联集合对象的延迟加载,association指的就是一对一,collection指的就是一对多查询。

在Mybatis配置文件中,可以配置是否启用延迟加载:

lazyLoadingEnabled=true|false。

原理是,使用CGLIB创建目标对象的代理对象,当调用目标方法时,进入拦截器方法.

比如调用a.getB().getName(),拦截器invoke()方法发现a.getB()是null值,那么就会单独发送事先保存好的查询关联B对象的sql,把B查询上来,然后调用a.setB(b),于是a的对象b属性就有值了,接着完成a.getB().getName()方法的调用。这就是延迟加载的基本原理。

当然了,不光是Mybatis,几乎所有的包括Hibernate,支持延迟加载的原理都是一样的。

Mybatis都有哪些Executor执行器?它们之间的区别是什么?

Mybatis有三种基本的Executor执行器,SimpleExecutor、ReuseExecutor、BatchExecutor。

  • **SimpleExecutor:**每执行一次update或select,就开启一个Statement对象,用完立刻关闭Statement对象。
  • **ReuseExecutor:**执行update或select,以sql作为key查找Statement对象,存在就使用,不存在就创建,用完后,不关闭Statement对象,而是放置于Map<String, Statement>内,供下一次使用。简言之,就是重复使用Statement对象。
  • **BatchExecutor:**执行update(没有select,JDBC批处理不支持select),将所有sql都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执行executeBatch()批处理。与JDBC批处理相同。

作用范围:Executor的这些特点,都严格限制在SqlSession生命周期范围内

在Mybatis配置文件中,可以指定默认的ExecutorType执行器类型,也可以手动给DefaultSqlSessionFactory的创建SqlSession的方法传递ExecutorType类型参数。

MyBatis与Hibernate区别

  • hibernate是全自动,而mybatis是半自动
  • hibernate数据库移植性远大于mybatis
  • hibernate拥有完整的日志系统,mybatis则欠缺一些
  • mybatis相比hibernate需要关心很多细节
  • sql直接优化上,mybatis要比hibernate方便很多
  • 缓存机制上,hibernate要比mybatis更好一些

总结:

  • Hibernate 属于全自动 ORM 映射工具,使用 Hibernate 查询关联对象或者关联集合对象时,可以根据对象关系模型直接获取。
  • Mybatis 属于半自动 ORM 映射工具,在查询关联对象或关联集合对象时,需要手动编写 SQL 来完成。

参考文章:

www.mybatis.cn/category/in…

www.cnblogs.com/huajiezh/p/…

svip.iocoder.cn/MyBatis/Int…

各位看官还可以吗?喜欢的话,动动手指点个💗,点个关注呗!!谢谢支持!

欢迎关注公众号【Ccww技术博客】,原创技术文章第一时间推出

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

其实 GraphQL 没那么难!全网最简洁的教程 Hello

发表于 2019-12-05

之前请某个我的咨询客户的公司建议他们使用graphql,还专门做了一个培训,然后过了一段时间一问,说还没做,回复原因是一堆常见的你懂得。

我就说这个玩意有什么难!结果看了官方文档,以及类似这样的:juejin.cn/post/684490… ,

真是一堆绕着圈子的屁话啊。

不得已出马,拍出一个入门文档。请对比了解什么叫做简洁,什么叫做不罗嗦不聒噪,艹。

有node基础的,10分钟学会。

Hello World!使用 GraphQL

现在我们来写我们第一个基于express的 GraphQL 服务器。本教程中,我们将使用 Apollo Server库。为此,我们需要安装三个包

1
复制代码npm install --save graphql apollo-server-express express

才能使用 Express 应用程序作为中间件。

graphql 是一个支持库,apollp-server-express 是相应的 HTTP 服务器支持包express 是 Nodejs 的 web 框架。

开始

复制代码让我们简要了解下这些依赖的作用。

新建一个名字为 index.js,包含以下代码的文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
复制代码const express = require('express');
const { ApolloServer, gql } = require('apollo-server-express');
const typeDefs = gql`
type Query {
hello: String
}
`;
const resolvers = {
Query: {
hello: () => 'Hello world!'
}
};
const server = new ApolloServer({ typeDefs, resolvers });
const app = express();
server.applyMiddleware({ app });
app.listen({ port: 4000 }, () =>
console.log(`🚀 Server ready at http://localhost:4000${server.graphqlPath}`)
);

gql 是一个模板文字标记,用于将 GraphQL schema 编写为类型。schema由类型定义组成,上面的例子中,我们定义了 typeDefs 来编写 graphQL 的 schema。

Resolver 用于从 schema 中返回字段的数据。在我们的示例中,我们定义了一个 resolver,它将函数 hello() 映射到我们的 schema 上的实现。

要运行服务器,只需要打开 terminal 并运行命令
node index.js。

现在,从浏览器窗口访问 url:

1
复制代码http://localhost:4000/graphql

来看看它的操作。要运行一个 query,在左侧编辑空白部分,输入以下 query。

1
复制代码{hello}

然后按中间的 ▶ (play)按钮。查看输出:

1
2
3
4
5
复制代码{
"data": {
"hello": "Hello world!"
}
}

瞧!你刚创建了第一个 GraphQL 服务器。

命令行验证服务启动

能够通过命令即可验证的话,就不必急着写代码,因为前者飞快简洁。

操作系统差异,关于单引号和双引号,真tm的折腾人。

Linux | MAC

命令

1
复制代码curl -i -X POST -d'{query: "{hello}"}' http://localhost:4000/graphql

Windows

命令

1
复制代码 curl  -X POST -H "Content-Type: application/json" -d"{\"query\":\"{hello}\"}"  http://localhost:4000/graphql

输出

1
复制代码 {"data":{"hello":"Hello world!"}}

使用 GraphQL 构建 API

接下来做一个看起来像是真的应用的案例,这次我们添加一个像样的Person对象和对它的查询和修改。

添加 Schema。我们需要一个 schema 来启动我们的 GraphQL API。让我们在 api 目录下创建一个名字为 api/schema.js 的新文件。添加以下 schema。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
复制代码const typeDefs = gql`
type Person {
id: Int
name: String
}
type Query {
allPeople: [Person]
person(id: Int!): Person
}
type Mutation {
createPerson(id:String,name: String): Person
}
`;
const defaultData = [
{
id: 1,
name: 'Reco'
},
{
id: 2,
name: 'Tibe',
}
];

复制代码我们的 schema 一共包含两个 query:

  • 第一个是 allPeople,通过它我们可以列出到 API 中的所有的人
  • 第二个查询 person 是使用他们的 id检索一个人
  • 还有一个修改createPerson,必须放到type Mutation 内

这两种查询类型都依赖于一个名为 Person 对象的自定义类型,该对象包含2个属性。

添加 Resolver

我们已经了解了 resolver 的重要性。它基于一种简单的机制,去关联 schema 和 data。Resolver 是包含 query 或者 mutation 背后的逻辑和函数。然后使用它们来检索数据并在相关请求上返回。

如果在使用 Express 之前构建了服务器,则可以将 resolver 视为控制器,其中每一个控制器都是针对特定路由构建。由于我们不在服务器后面使用数据库,因此我们必须提供一些虚拟数据来模拟我们的 API。
创建一个名为 resolvers.js 的新文件并添加下面的文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
复制代码const defaultData = [
{
id: 1,
name: 'Reco'
},
{
id: 2,
name: 'Tibe',
}
];
const resolvers = {
Query: {
allPeople: () => {
return defaultData;
},
person: (root, { id }) => {
return defaultData.filter(character => {
return (character.id = id);
})[0];
}
},
Mutation:{
createPerson:(root,{id,name}) =>{
defaultData.push({id,name})
return defaultData[defaultData.length - 1 ]
}
}
};

首先,我们定义 defaultData 数组,其中包含2个人物的详细信息。person() 箭头函数使用参数 id 来检索具有请求 ID 的 person 对象。这个已经在我们的查询中定义了。

实现服务器

运行服务器。现在要测试我们的 GraphQL API,在浏览器窗口中跳转

1
复制代码http://localhost:4000/graphql

并运行以下 query:

{allPeople{id,name}}

复制代码点击 play 按钮,你将在右侧部分看到熟悉的结果,如下所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
复制代码{
"data": {
"allPeople": [
{
"id": 1,
"name": "Reco"
},
{
"id": 2,
"name": "Tibe"
}
]
}
}

要获取单个人物对象,请尝试运行:

1
复制代码{person(id:1){id,name}}

要创建一个person:

1
复制代码mutation{createPerson(id:"4",name:"reco"){id,name}}

复制代码运行上面的查询,在结果中,你可以获得得到的每个字段/属性的值以进行查询。你的结果将类似于以下内容。

结论

结论就是这就是一个很简单的玩意儿,只是上个 ,哪里需要那么多屁话。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

1…843844845…956

开发者博客

9558 日志
1953 标签
RSS
© 2025 开发者博客
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%