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

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


  • 首页

  • 归档

  • 搜索

Mybatis分页插件(pagehelper)数据量稍大出现

发表于 2021-06-04

Mybatis分页插件(pagehelper)数据量稍大出现查询慢

🐉 芜湖~ 优化查询速度~

⚠在项目中使用Mybatis分页插件分页查询十分缓慢,但是在数据库中执行速度却很快?

  1. 该数据库的表共有1000条数据,分页查询时,在数据库中是毫秒级别,但是在使用Mybatis分页插件的时候居然足足!!2s多
  2. 最后打印sql

🗨无语…居然又查询了一遍,怪不得这么慢

1
mysql复制代码select count(*) from ("这里面的把sql又多重复查了一遍") tab
  1. 解决办法💡

从5.0.4版本的Mybatis分页插件之后,支持了自定义Count方法来替换原来的Count方法
官方文档:


实现步骤:

  1. 首先在Mapper层定义一个Count方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
java复制代码/**
* @author yeqian
* @param
* @return
* @Description TODO 查询所有的订单信息
* @Date 2021/5/25 15:41
*/
Page<Map<String, Object>> getDeviceOrderInfo(Map<String, Integer> map);

/**
* @author yeqian
* @param
* @return
* @Description TODO 覆盖pageHelp的计算总数方法
* @Date 2021/6/3 16:57
*/
Long getDeviceOrderInfo_COUNT();
  1. 在去xml中写具体sql
1
2
3
4
5
6
7
xml复制代码<!--查询所有的订单详细信息-->
<select id="getDeviceOrderInfo_COUNT" resultType="Long" useCache="false">
SELECT
COUNT(1)
FROM order_info orderInfo
LEFT JOIN customer_info customer ON orderInfo.customer_num = customer.id
</select>
  1. 再次查询,从2s的延迟降低到毫秒级😊

image-20210603174342399
小结

  1. 原来是分页插件中的计算Count的sql语句,会将需要分页的所有数据查出来然后生成一个临时表,再去计算Count。
  2. 如果数据量此时比较大,那么生产临时表这步因为多查询了一次表,所以会导致最后Count计算会十分慢,从而影响分页的结果。

解决办法:

​ 覆盖原Count的sql,根据官方文档,自定义。注意返回值类型(Long)与方法名(原名_COUNT)

本文转载自: 掘金

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

「分享」基于vue-element+laravel8x+c

发表于 2021-06-04

laravel-casbin-admin

基于 vue-element-admin + laravel8.x+ casbin 整合的前后端分离的rbac权限管理系统。

demo地址

介绍

  • 系统采用前后端分离架构,是一个企业后台解决方案。 前端框架基于vue-element-admin,后台框架使用的是PHP最热门的框架laravel
    控制权限使用的是casbin轻量访问控制框架
  • 如何这是你第一个学习的前后端分离项目的项目 你可以学习到laravel的一些最新用法 路由、中间件、验证器、前后端下第三方授权登录(钉钉 微博) 分离下 JWT校验、laravel的全局异常处理、业务和服务分离以及vue做前端的一些用法。
    以及如何编写后台最常见的的RBAC权限控制
  • 该项目使用的最新的前后端分离技术 权限控制可以细微到单接口
  • 本项目并没有高度封装📦,只提供了完善的rbac权限控制 简单易上手 可以放心使用 并且「免费开源」
  • 打了这么多字~ 最后可以给我点个star吗~ ❤️
  • demo:使用微博扫码登录即可

主要功能

1.登录

  • 1.普通登录 jwt+api 验证码

「分享」基于vue-element-admin+laravel8.x+casbin 我写了一个前后端分离的rbac管理后台

  • 2.第三方登录(目前微博(可以使用) 钉钉登录代码已提供 未测试) 默认给demo权限

「该功能」是基于我写的一个第三方登录包thirdparty_oauth 已兼容Laravel-Octane

「分享」基于vue-element-admin+laravel8.x+casbin 我写了一个前后端分离的rbac管理后台

2.权限管理

「分享」基于vue-element-admin+laravel8.x+casbin 我写了一个前后端分离的rbac管理后台

3.角色管理

  • 赋予角色菜单节点以及api节点

「分享」基于vue-element-admin+laravel8.x+casbin 我写了一个前后端分离的rbac管理后台

4.用户管理

  • 赋予多个角色

「分享」基于vue-element-admin+laravel8.x+casbin 我写了一个前后端分离的rbac管理后台

「分享」基于vue-element-admin+laravel8.x+casbin 我写了一个前后端分离的rbac管理后台

5.「终端」类似于宝塔的伪终端(该功能基于node) 但也受系统rbac权限控制 考虑到安全问题由node服务端请求laravel进行身份验证(哈哈 在也不用登录服务更新代码了) 但是该功能需要暴露端口 请小心使用。

「分享」基于vue-element-admin+laravel8.x+casbin 我写了一个前后端分离的rbac管理后台

最后整个项目都遵循简单易用。没有过度封装,就算是对前端新手「跟着文档学习」都能很快上手开发出一个前后端分离的后台项目,对新手友好~~~ 最后欢迎star

使用遇到问题(上述问题基本是环境以及一些使用问题 基本没有遇到bug 有问题会及时修复)

  • 给项目提issues

「分享」基于vue-element+laravel8.x+casbin 我写了一个前后端分离的rbac管理后台

  • 后端源码:github.com/pl1998/lara…
  • 前端源码:github.com/pl1998/vue_…
  • 体验 使用微博扫码登录即可
  • 后续:考虑到后台主要的rbac功能已完善 有想到的功能会不定期集成更新~

本文转载自: 掘金

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

Nginx系列教程(四) 一文带你读懂Nginx的动静分离

发表于 2021-06-04

作者:JackTian

微信公众号:杰哥的IT之旅(ID:Jake_Internet)

这是我参与更文挑战的第 4 天,活动详情查看:更文挑战


一、Nginx 动静分离

Nginx 动静分离,简单来说,就是把动态和静态请求分开,这里所说的不是将动态页面和静态页面物理分离,可以理解为:Nginx处理静态页面,Tomcat处理动态页面。

二、静态页面

静态页面:是一个页面对应一个内容,也就是一对一的关系,在互联网架构中,页面几乎为不变的或者是页面发生变化频率较低的。比如:html 页面,js/css 样式文件等;

与其匹配的技术架构来加速。比如:Squid、Nginx、CDN,而静态页面最大的优点:速度快、跨平台、跨服务器。

无论如何访问都只是让服务器传数据给请求端,并不做脚本计算及读取后台数据库,提高访问速度及降低了部分安全隐患。

采用静态页面的方法:可将数据库及后台系统与前台进行划分,两者间没有绝对的联系,从而提高站点安全。

静态页面的特点

  • 每个网页都有一个固定的 URL,且网页URL以.htm、.html、.shtml等常见形式为后缀,而不含有 ?;
  • 网页内容发布到网站服务器上,无论是否有用户访问,每个静态网页的内容都将保存在网站服务器上,也就是说,保存在服务器上的文件,每个网页都是一个独立的文件;
  • 内容相对稳定,容易被搜索引擎所检索;
  • 没数据库的支持,网站制作和维护方面工作量大,当网站信息量很大时,完全依靠静态网页制作方式较困难;
  • 交互性较差,功能方面有较大的限制;
  • 运行数据快;

图片

三、动态页面

动态页面:是一对多访问,通过一个页面可以根据若干参数返回其不同的数据,在互联网架构中,不同的用户访问不同的动态场景页面请求,都可能是不一样的页面。比如:淘宝京东商品列表页面、百度搜索引擎结果页面等;

动态页面,与其之匹配的技术架构,比如:分层架构、服务化架构、数据库、缓存架构;

动态页面的特点

  • 以数据库技术为基础,可大大降低网站维护的工作量;
  • 采用动态网页技术的网站可以实现更多的功能;
  • 不是独立存在于服务器上的网页文件,只有当用户请求时服务器才返回一个完整的网页;
  • 在进行搜索引擎推广时需做一定的技术处理才能够适应搜索引擎的要求;

图片

四、动静分离

动静分离是指:静态页面与动态页面分开不同系统访问的架构设计方法。

静态页面:访问路径短,速度快,几毫秒;

动态页面:访问路径长,速度慢,几十毫秒甚至几百毫秒,架构扩展性要求高;

静态页面与动态页面以不同域名进行区分;

图片

五、动静分离实操案例

1、 在Nginx服务器环境下,准备静态资源,用于访问,在根目录下创建data目录,并在该目录里面创建两个文件夹image、www,在image文件夹里上传一张本地图片,www文件夹里创建一个html文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
shell复制代码# cd /data/
# ll
总用量 8
drwxr-xr-x. 2 root root 4096 3月 29 19:14 image
drwxr-xr-x. 2 root root 4096 3月 29 19:13 www

# cd image/
# ls
游戏.jpg

# cd ../www/
# ls
20200331.html
# cat 20200331.html
<h1>This is a static page!</h1>

2、 修改nginx.conf配置文件

添加监听端口、访问名字、重点添加location。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
ini复制代码# vim /usr/local/nginx/conf/nginx.conf
39 server {
40 listen 80;
41 server_name 192.168.1.10;
42
43 #charset koi8-r;
44
45 #access_log logs/host.access.log main;
46
47 location /www/ {
48 # proxy_pass http://myserver;
49 root /data/;
50 index index.html index.htm;
51 }
52 location /image/ {
53 root /data/;
54 autoindex on;
55 }

3、 重启Nginx服务

1
2
shell复制代码# ./nginx -s stop
# ./nginx

4、 验证效果

在客户端浏览器中输入:http://192.168.1.10/image/和http://192.168.1.10/www/20200331.html分别进行测试动静分离是否成功,删除后端tomcat服务器上的某个静态文件,查看是否能访问,如果可以访问说明静态资源Nginx直接返回了,无须在通过后端Tomcat服务器;

图片

总结

通过本篇文章我们介绍了什么是 Nginx 动静分离、 什么是静态页面和动态页面以及它们各自的特点、动静分离的架构设计图和动静分离实操案例等;

总体上来说,动静分离是将原本需要动态生成的站点通过以静态站点的优化技术,如实际情况中,数据量不大时,可生成静态页面数据不多的业务,适合于页面静态化优化。

这篇文章总体结构不长,但希望大家逻辑要清晰,彼此间都有所收获。


原创不易,如果你觉得这篇文章对你有点用的话,麻烦你为本文点个赞、评论或转发一下,因为这将是我输出更多优质文章的动力,感谢!

我们下期再见!

本文转载自: 掘金

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

JavaFX 系列教程【3】-各种常用组件体系介绍

发表于 2021-06-04

组件层次

在上一篇中说过,窗体下面分场景,场景下面分布局,布局里面有控件。

首先,一个应用程序 Application 可能有很多个窗体,比如弹框、新建窗口等。在窗体下面分为场景,我们可以将不同的场景设置到窗体上来达到切换场景的目的。窗体下面分为布局,布局也是一种 Node 节点,布局里面可以放入各种控件。逻辑结构如下所示:

image.png

Node 的类图如下:

image.png

JavaFX 坐标介绍

JavaFX 坐标是从左上角开始计算的,最左上角是 (0,0),往右是正向的 X 轴,往下是正向的 Y 轴,比如右下角某个点的坐标是 (10,20),那么就表示这个点在窗体的右边 10 像素,下方 20 像素的位置。

image.png

Group:通用容器

JavaFX Group 不具备任何的布局样式,只是把所有组件放到一个组里面显示,是一种通用的组件容器。

区域 Region

Region 是区域的意思,是指一块显示区域,其类继承关系如下:

image.png

对于 Region 来说,具有几个重要的概念:

  • 显示区域 Content Area:内容显示的区域
  • 内边距 Padding:显示区域距离边框的距离
  • 边框 Border:即 Region 的边缘位置
  • 外边距 Margin:即边框向外延伸的空白距离

image.png

布局 Pane

布局实际上是一种容器,可以容纳别的布局或者组件。布局顾名思义就是为了页面布局而产生的容器,布局均继承自 Pane 类,常用的布局有以下几个。

  • 水平布局 HBox:水平布局 HBox 就是把一个个的组件在 HBox 内水平放置。
  • 垂直布局 VBox:垂直布局就是把一个个的组件垂直放置。
  • 流式布局 FlowPane:垂直或者水平排列,到头了就继续下一行或者列
  • BorderPane:提供 5 个放置控件的区域,分别为左右上下和中间。
  • …

控件 Control

控件是一些可以被操控的组件,是一些基本显示单元,常见的包括按钮 Button、单选框、复选框等。控件继承自 Control 类,不同的控件具有不同的特性。

其他常用

其他的包括诸如菜单、各种形状等,读者可以自行探索。

本文转载自: 掘金

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

Django-rest-framework 开发的一些建议

发表于 2021-06-03

这是我参与更文挑战的第3天,活动详情查看: 更文挑战

Django是Python的一个Web框架。对于创业公司来说相当的短平快。随着业务的增长,新增的代码也会越来越多。如果一开始没有很好的对项目目录做一个规划的话,后面就基本是重写而不叫重构了。

虽然Python3.5开始有了type hit,其实有点鸡肋,那个只能是叫做变量类型注释,而不会对运行时有任何的影响。但是还是推荐配合pyright使用
由于djangorestframework框架由于名字太长,下面都简称为drf

来,我们先开始一个Django项目

我们先基于Django2.2.10版本建立一个掘金的项目和demo的app。

pip install django==2.2.10

pip install djangorestframework

image.png

再建立一个juejin项目
django-admin startproject juejin

image.png

再来start一个app
python juejin/manage.py startapp demo

image.png

我们修改一下INSTALLED_APPS

image.png

先跑起来再说

image.png

我们可以看到现在的项目目录如下。
image.png

在drf下还会多两个文件。分别是serializers.py和filters.py,用于做序列化和orm的数据筛选。

image.png

对于前期的项目可以都写在一个目录下。但需要留意的是,当一个文件行数超过500行时候。应该开始考虑拆分的问题,而不是继续往目录下面继续写,写的越多后面需要解耦的时间越多。

这时候分析一下现在/未来会膨胀的文件是哪个?然后拆分出来,独立建立一个文件夹。这里已views为例。
我建立了两个viewset代码,分别是文章和热讯,提前拆分做好规划。

image.png

image.png

对于service看法

在另一个技术社区讨论过这个问题。如果有兴趣的可以参考一下文末链接。

但是基于Django的开发来说,因为Django本身的设计就是MVT

Model数据 -> View视图() -> Template模板

对于一般的小项目来说是够用的,因为页面模板不会很多。但如果项目变的中型或者大型了,就有点捉襟见肘了。因为会有一堆的代码都会写入views文件里面。

对于service层(文件夹/文件)来说,是建议加入的,他可以解耦views层次的代码。

service应该是从Java传过来的,对于Django有些天然不合,但是如果继续将代码写入单文件。会使得代码变的无法维护。可以适当的结合其他语言框架的有点来糅合Django才会使其发出最强的威力。

一些建议

校验数据只在serializers中做,views层的视图只做数据输出。

数据的处理应该通过service来做,而不直接写到view中,保持view的输出接口。

app需要提前拆分,否则后期耦合性太强而不利于拆分。

在使用migrate迁移的时,应当考虑清楚字段后再做迁移。Django的数据迁移很方便,这种方便到后期字段变得多了也会变成一把双刃剑。尤其是测试服和正式服的字段不同步的时候,有可能会产生数据迁移时候,一定要小心小心再小心。

参考文章
使用 Django 搭建 APP 服务端的一系列问题

发现一个掘金的小彩蛋

是在编辑文章的控制台里面
image.png

本文转载自: 掘金

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

Esaticseach架构

发表于 2021-06-03

es是哪里来的?

问题:当数据量达到了亿级,兆级,我们将会面临哪些问题?

  1. 如何存储?用什么数据库?关系型还是非关系型?
  2. 如何保证数据的安全性?如何保证服务的可用性?
  3. 如何搜索?
  4. 如何统计分析?

es 来了 解决方案:

  1. 建立集群,分担数据存储压力,应对高并发,提高系统可用性。
  2. 多点存储,提高数据安全性。
  3. 高效索引,提高查询速度和统计分析能力。
  4. 压缩数据,降低存储成本。

简单介绍一下es 答:

es是一个基于lucence的开源的全文检索引擎,高扩展,分布式。它的数据读写数据都很快,尤其是读,搜索速度近乎实时。扩展性也很强大,可以扩展成上百台服务器的集群。es还使用RESTful API来隐藏实现上的复杂性,使得通过任何语言使用起来都变得非常简单
两个核心功能 检索和统计

es节点类型

es为了实现集群的各种能力,集群中的各个节点可能会分别承担不同的角色。主节点:通过node.master=true的配置,可以把一个节点设置为候选主节点。候选主节点经过集群选举,如果成功,就会成为主节点。主节点负责索引的创建和删除,分配分片,追踪集群中的节点状态等工作。数据节点:通过node.data=true的配置,可以把一个节点设置为数据节点。数据节点主要负责数据的读写相关操作,如CRUD,搜索,聚合等。客户端节点:如果某个节点的node.master和node.data都为false,那么,该节点将既不参与竞选主节点,也不会读写数据。那么,它在集群中还有用吗?有。它也可以处理客户的请求,只是处理的方式是:把收到的请求分发到相关节点,并把得到的结果汇总返回。这种节点,称之为客户端节点。协调节点:协调节点不是一个具体的节点类型,它更像是一个节点所扮演的角色。如上面提到的客户端节点,就是一个协调节点的角色,客户端节点的工作内容也正是承担了协调节点角色的工作内容。当然了,主节点,候选主节点或者数据节点,都可以承担起这样的角色。

为什么要有主节点?

这个问题其实很简单,任何一个组织都需要有一个核心领导团队,就像公司有Boss一样。做为一个集群,也需要有一个核心领导力。这个核心领导力,可能来自于某些第三方中间件,也可能像es这样,从内部产生一个master。主节点,主要是针对分布式系统下的CAP,提供解决方案,CAP,即一致性,可用性,分区容忍性。后面我们聊到主节点的具体工作时,就会对这部分有更深入的理解。

主节点的选举

es的主节点的选举可以简单归纳为:有人竞选,有人投票,票多者得。选举的目标是:选出唯一的一个主节点。

谁可以竞选?

所以node.master=true的节点都可以参与主节点的竞选。

谁可以投票?

es集群中的每个节点都可以投票

如何投票?

每个节点都把自己所知道的节点进行一次排序,排第一就是自己要选的。源码如下:

public DiscoveryNode electMaster(Iterable nodes){ List sortedNodes = sortedMasterNodes(nodes);if (sortedNodes == null || sortedNodes.isEmpty()) {returnnull; }return sortedNodes.get(0); }

排序的依据是节点的id:

private static classNodeComparatorimplementsComparator{@Overridepublicintcompare(DiscoveryNode o1, DiscoveryNode o2){if (o1.masterNode() && !o2.masterNode()) {return -1; }if (!o1.masterNode() && o2.masterNode()) {return1; }return o1.id().compareTo(o2.id()); } }

如何当选?

某个候选主节点得到了大于全部候选主节点数量一半以上的票数,并且自己先选了自己,它们,该节点就当选为主节点。假设某集群有9个节点,其中,候选主节点有3个,其中一个是A,那么,A当选是条件就是包含A在内,至少有两个节点投票A为主节点。

选举脑裂

观察投票时,节点排序的源码,我们可以看到,es是简单的通过节点的id来排序的。而节点的id,一旦加入到集群中,就是固定的,所以,一般情况下,大家对于选举其实是有共识的。这种共识,可以保证选择的成功。但是,在发生了分区的情况下,情况就变得复杂了。

image.png
图 1

如上图,在发生分区的情况下,由于每个节点都只在(也只能在)自己知道的节点中进行选举,我们假设5个节点都是候选主节点。那么,在上图的情形下,左分区节点1会当选,右分区节点2会当选。这样,当两个分区再联通时,就会产生两个主节点,这种情况,我们称之为脑裂。那么,该如何避免这种情况发生呢?看如下源码:

publicbooleanhasEnoughMasterNodes(Iterable nodes){if (minimumMasterNodes < 1) {returntrue; }int count = 0;for (DiscoveryNode node : nodes) {if (node.masterNode()) { count++; } }return count >= minimumMasterNodes; }

上述方法会在选举进行前执行,当返回true时,选举正常进行,当返回false时,选择不进行。方法很简单,就是通过比较当前集群中的候选主节点数量和minimumMasterNodes,如果候选主节点数量大于minimumMasterNodes,则可进行选举,否则,不可选举。minimumMasterNodes来源于我们的配置。如图1,我们可以通过将minimumMasterNodes设置为3,来避免脑裂的发生。这是因为,当minimumMasterNodes为3时,左分区由于候选主节点不足3个,所以无法进行选举,只能等待分区的联通。而右分区可以正常选举。于是,当分区恢复后,节点3当选成为了唯一的主节点。

discovery.zen.minimum_master_nodes=3通常,对于一个有n个候选主节点的集群,minimumMasterNodes可以设置为 n/2 + 1。这样可以避免脑裂。需要注意的是,假设有一个集群,有两个候选主节点,那么minimumMasterNodes设置为2就可以避免脑裂,但是,这样会导致,一旦集群发生分区,使得两个候选主节点无法联通时,集群将变得不可用。而minimumMasterNodes设置为1,可以保证可用,但是又无法避免脑裂。所以es推荐,集群中的候选主节点数量至少为3。

es 写数据过程

  1. 客户端选择一个 node 发送请求过去,这个 node 就是 coordinating node (协调节点)。
  2. coordinating node 对 document 进行路由,将请求转发给对应的 node(有 primary shard)。
  3. 实际的 node 上的 primary shard 处理请求,然后将数据同步到 replica node 。
  4. coordinating node 如果发现 primary node 和所有 replica node 都搞定之后,就返回响应结果给客户端。

640.webp

es 读数据过程

可以通过 doc id 来查询,会根据 doc id 进行 hash,判断出来当时把 doc id 分配到了哪个 shard 上面去,从那个 shard 去查询。

  1. 客户端发送请求到任意一个 node,成为 coordinate node 。
  2. coordinate node 对 doc id 进行哈希路由,将请求转发到对应的 node,此时会使用 round-robin 随机轮询算法,在 primary shard 以及其所有 replica 中随机选择一个,让读请求负载均衡。
  3. 接收请求的 node 返回 document 给 coordinate node 。
  4. coordinate node 返回 document 给客户端。

es 搜索数据过程

es 最强大的是做全文检索,就是比如你有三条数据:

java真好玩儿啊
java好难学啊
j2ee特别牛

你根据 java 关键词来搜索,将包含 java 的 document 给搜索出来。es 就会给你返回:java 真好玩儿啊,java 好难学啊。

客户端发送请求到一个 coordinate node 。
协调节点将搜索请求转发到所有的 shard 对应的 primary shard 或 replica shard ,都可以。
query phase:每个 shard 将自己的搜索结果(其实就是一些 doc id )返回给协调节点,由协调节点进行数据的合并、排序、分页等操作,产出最终结果。
fetch phase:接着由协调节点根据 doc id 去各个节点上拉取实际的 document 数据,最终返回给客户端。
写请求是写入 primary shard,然后同步给所有的 replica shard;读请求可以从 primary shard 或 replica shard 读取,采用的是随机轮询算法。

写数据底层原理

image.png

先写入内存 buffer,在 buffer 里的时候数据是搜索不到的;同时将数据写入 translog 日志文件。

如果 buffer 快满了,或者到一定时间,就会将内存 buffer 数据 refresh 到一个新的 segment file 中,但是此时数据不是直接进入 segment file 磁盘文件,而是先进入 os cache 。这个过程就是 refresh 。

每隔 1 秒钟,es 将 buffer 中的数据写入一个新的 segment file ,每秒钟会产生一个新的磁盘文件 segment file ,这个 segment file 中就存储最近 1 秒内 buffer 中写入的数据。

但是如果 buffer 里面此时没有数据,那当然不会执行 refresh 操作,如果 buffer 里面有数据,默认 1 秒钟执行一次 refresh 操作,刷入一个新的 segment file 中。

操作系统里面,磁盘文件其实都有一个东西,叫做 os cache ,即操作系统缓存,就是说数据写入磁盘文件之前,会先进入 os cache ,先进入操作系统级别的一个内存缓存中去。只要 buffer 中的数据被 refresh 操作刷入 os cache 中,这个数据就可以被搜索到了。

为什么叫 es 是准实时的?NRT ,全称 near real-time 。默认是每隔 1 秒 refresh 一次的,所以 es 是准实时的,因为写入的数据 1 秒之后才能被看到。可以通过 es 的 restful api 或者 java api ,手动执行一次 refresh 操作,就是手动将 buffer 中的数据刷入 os cache 中,让数据立马就可以被搜索到。只要数据被输入 os cache 中,buffer 就会被清空了,因为不需要保留 buffer 了,数据在 translog 里面已经持久化到磁盘去一份了。

重复上面的步骤,新的数据不断进入 buffer 和 translog,不断将 buffer 数据写入一个又一个新的 segment file 中去,每次 refresh 完 buffer 清空,translog 保留。随着这个过程推进,translog 会变得越来越大。当 translog 达到一定长度的时候,就会触发 commit 操作。

commit 操作发生第一步,就是将 buffer 中现有数据 refresh 到 os cache 中去,清空 buffer。然后,将一个 commit point 写入磁盘文件,里面标识着这个 commit point 对应的所有 segment file ,同时强行将 os cache 中目前所有的数据都 fsync 到磁盘文件中去。最后清空 现有 translog 日志文件,重启一个 translog,此时 commit 操作完成。

这个 commit 操作叫做 flush 。默认 30 分钟自动执行一次 flush ,但如果 translog 过大,也会触发 flush 。flush 操作就对应着 commit 的全过程,我们可以通过 es api,手动执行 flush 操作,手动将 os cache 中的数据 fsync 强刷到磁盘上去。

translog 日志文件的作用是什么?你执行 commit 操作之前,数据要么是停留在 buffer 中,要么是停留在 os cache 中,无论是 buffer 还是 os cache 都是内存,一旦这台机器死了,内存中的数据就全丢了。所以需要将数据对应的操作写入一个专门的日志文件 translog 中,一旦此时机器宕机,再次重启的时候,es 会自动读取 translog 日志文件中的数据,恢复到内存 buffer 和 os cache 中去。

translog 其实也是先写入 os cache 的,默认每隔 5 秒刷一次到磁盘中去,所以默认情况下,可能有 5 秒的数据会仅仅停留在 buffer 或者 translog 文件的 os cache 中,如果此时机器挂了,会丢失 5 秒钟的数据。但是这样性能比较好,最多丢 5 秒的数据。也可以将 translog 设置成每次写操作必须是直接 fsync 到磁盘,但是性能会差很多。

实际上你在这里,如果面试官没有问你 es 丢数据的问题,你可以在这里给面试官炫一把,你说,其实 es 第一是准实时的,数据写入 1 秒后可以搜索到;可能会丢失数据的。有 5 秒的数据,停留在 buffer、translog os cache、segment file os cache 中,而不在磁盘上,此时如果宕机,会导致 5 秒的数据丢失。

总结一下,数据先写入内存 buffer,然后每隔 1s,将数据 refresh 到 os cache,到了 os cache 数据就能被搜索到(所以我们才说 es 从写入到能被搜索到,中间有 1s 的延迟)。每隔 5s,将数据写入 translog 文件(这样如果机器宕机,内存数据全没,最多会有 5s 的数据丢失),translog 大到一定程度,或者默认每隔 30mins,会触发 commit 操作,将缓冲区的数据都 flush 到 segment file 磁盘文件中。

数据写入 segment file 之后,同时就建立好了倒排索引。

删除/更新数据底层原理

如果是删除操作,commit 的时候会生成一个 .del 文件,里面将某个 doc 标识为 deleted 状态,那么搜索的时候根据 .del 文件就知道这个 doc 是否被删除了。
如果是更新操作,就是将原来的 doc 标识为 deleted 状态,然后新写入一条数据。

buffer 每 refresh 一次,就会产生一个 segment file ,所以默认情况下是 1 秒钟一个 segment file ,这样下来 segment file 会越来越多,此时会定期执行 merge。每次 merge 的时候,会将多个 segment file 合并成一个,同时这里会将标识为 deleted 的 doc 给物理删除掉,然后将新的 segment file 写入磁盘,这里会写一个 commit point ,标识所有新的 segment file ,然后打开 segment file 供搜索使用,同时删除旧的 segment file 。

底层 lucene
简单来说,lucene 就是一个 jar 包,里面包含了封装好的各种建立倒排索引的算法代码。我们用 Java 开发的时候,引入 lucene jar,然后基于 lucene 的 api 去开发就可以了。

通过 lucene,我们可以将已有的数据建立索引,lucene 会在本地磁盘上面,给我们组织索引的数据结构。

下一篇 倒排索引

本文转载自: 掘金

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

Java连接Hive踩坑血泪

发表于 2021-06-03

Java连接Hive踩坑血泪 java.sql.SQLException: Method not supported

  • 今天突然接到一个需求:为部署在医院内部系统增加Hive连接方式,在一顿操作之后加上JDBC驱动以及hadoop-common依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
xml复制代码<!-- https://mvnrepository.com/artifact/org.apache.hive/hive-jdbc -->
<dependency>
<groupId>org.apache.hive</groupId>
<artifactId>hive-jdbc</artifactId>
<version>1.1.1</version>
<exclusions>
<exclusion>
<groupId>org.eclipse.jetty.aggregate</groupId>
<artifactId>jetty-all</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.hive</groupId>
<artifactId>hive-shims</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-common</artifactId>
<version>2.6.0</version>
</dependency>
  • 然后就在测试环境出了问题:java.sql.SQLException: Method not supported

image-20210603143916246

  • 在经过一段时间面向google编程后(下图),觉得可能是Hive版本和JDBC版本不兼容,在确定了测试环境版本是2.1.0版本Hive后立马松了口气,因为医院Hive版本是1.1.0的,觉得报错也是正常的,在更换了JBDC驱动版本为2.1.0之后连接测试环境Hive是正常使用的,因此自信满满更新了医院的系统。 image-20210603141307159
  • 结果更新了医院系统使用Hive还一样的报错:java.sql.SQLException: Method not supported

没有多想觉得还是医院版本驱动不兼容,在官网
看到这么1.1.0驱动的时候感觉头都大了,心里觉得Hive这么垃圾吗?版本驱动兼容都不做。

image-20210603142811297

  • 在经过两个小时的摸索,总觉得不对劲,仔细看了报错后发现忽略了最关键的信息

image-20210603144213959

仔细看了代码之后发现获取数据连接是正常,只是在查询测试sql语句的时候报错了,这个报错是HiveStatement.setQueryTimeout报的错误,这个方法是设置查询语句超时时间的

image-20210603144610066

DBUtil.query代码

image-20210603145218908

当看了HiveStatement源码之后就终于破案了

image-20210603145059723

原来hive-jdbc-1.1.0驱动不允许设置查询超时时间,只要去掉查询超时时间的配置,就能正常使用了。从网上查询的解决办法千篇一律都是说版本不兼容,需要更换版本,实在是误导大家,因此在记录问题的同时也给和我遇到同样问题的朋友一点解决思路。

ps

  • hive-jdbc-2.1.0驱动是可以设置查询语句超时时间的,所以在测试环境使用hive-jdbc-2.1.0驱动连接Hive是没有问题的。

image-20210603150811158

本文转载自: 掘金

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

配置中心 Spring Cloud Config:工作原理解

发表于 2021-06-03

这是我参与更文挑战的第3天,活动详情查看: 更文挑战

《配置中心 Spring Cloud Config 详解》系列文章更新,一起在技术的路上精进!本系列文章将会介绍Spring Cloud 中提供了分布式配置中心Spring Cloud Config。应用服务中除了实现系统功能的代码,还需要连接资源和其它应用,经常有很多需要在外部配置的数据去调整应用的行为,如切换不同的数据库,设置功能开关等。随着微服务的不断增加,需要系统具备可伸缩和可扩展性,除此之外就是管理相当多的服务实例的配置数据。在应用的开发阶段由各个服务自治,但是到了生产环境之后会给运维带来很大的麻烦,特别是微服务的规模比较大,配置的更新更为麻烦。为此,系统需要建立一个统一的配置管理中心。

在前面的文章,我们介绍了 Spring Cloud Config 使用过程中服务端的实现,完整介绍了客户端和服务端实现的具体内容。本文将会介绍 Spring Cloud Config 工作原理,以此来了解 Spring Cloud Config 客户端和服务端交互的细节。

Spring Cloud Config 工作原理

通过上一篇文章的基础应用,我们可以知道配置中心包括如下元素:

  • 配置服务器:为配置客户端提供其对应的配置信息,配置信息的来源为配置仓库,启动时即拉取配置仓库的信息,缓存到本地仓库中。
  • 配置客户端:除了配置服务器之外的应用服务,启动时从配置服务器拉取其对应的配置信息。
  • 配置仓库:为配置服务器提供配置源信息,配置仓库的实现可以支持多种方式。

基于Config服务器,就可以集中管理各种环境下的各种应用的配置信息。客户端和服务端匹配到Spring中的Environment和PropertySource的概念,所以这不仅非常适用于所有的Spring应用,而且对于任意语言的应用都能够适用。一个应用可能有多个环境,从dev开发到test测试,再到生产环境,你可以管理这些不同环境下的配置,而且能够确保应用在环境迁移后有完整的配置能够正常运行。

配置中心的应用架构图如下:

图片.png

在部署环境之前,需要将相应的配置信息推送到配置仓库;先将配置服务器启动,启动之后,将配置信息拉取并同步至本地仓库;然后,配置服务器对外提供REST接口,其他所有的配置客户端启动时根据spring.cloud.config配置的{application}/{profile}/{label}信息去配置服务器拉取相应的配置。配置仓库支持多样的源,如Git、SVN、jdbc数据库和本地文件系统等。最后,其他应用启动,从配置服务器拉取配置。配置中心还支持动态刷新配置信息,不需要重启应用,通过spring-cloud-config-monitor监控模块,其中包含了/monitor刷新API,webhook调用该端点API,达到动态刷新的效果。

config-theory.png

总的来说,Spring Cloud Config具有如下特性:

  • 提供配置的服务端和客户端支持;
  • 集中式管理分布式环境下的应用配置;
  • 基于 Spring 环境,可以无缝与Spring应用集成;
  • 可用于任何语言开发的程序,为其管理与提供配置信息;
  • 默认实现基于git仓库,可以进行版本管理

下面我们分别来对配置服务器和客户端的主要功能进行源码解析。

小结

本文主要介绍了 Spring Cloud Config 服务端与客户端交互的基本过程,配置服务端是分布式应用配置的源,同时其自身也能实现高可用和高并发。接下来,我们将会具体剖析服务端和客户端的实现细节。

本文转载自: 掘金

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

数据库 - MySQL、PG、MongoDB 索引

发表于 2021-06-03

常见索引数据结构

现在的数据库索引结构主要使用如下三个数据结构

  1. B-Tree
  2. B+Tree
  3. LSM(Log-Structured Merge-Trees)

而针对以上三种数据结构。我们对应相对熟知的数据库有以下几个:

  1. MySQL:在使用 InnoDB 引擎的情况下,是使用 B+树作为索引的数据结构
  2. PostgresSQL:号称世界最先进的数据库,支持多种数据结构索引,其中我们最常用的类型是 B 树索引。
1
csharp复制代码CREATE INDEX ON rel_8192_$idx.user_live_rights using btree (user_id, right_id, right_type, scope_id) where status != 'deleted';
  1. MongoDB:先进的 NoSQL 数据库。在官方文档中声明自己是用的是 B-Tree 索引。其中官方文档的原文内容如下MongoDB indexes use a B-tree data structure但是 MongoDB 在「Wired Tiger 」引擎当中实现了一个基于 LSM 的索引结构。相关的性能对比可以查看 Btree vs LSM
  2. HBase:HBase 底层的文件存储采用的是 LSM 树的实现。

数据结构回顾

B-Tree

image

B-树是一种自平衡的搜索树,其特点如下:

    1. 多路,非二叉树
      1. 每个节点既保存索引,又保存数据
      2. 搜索时相当于二分查找

B+Tree

imageB+树是 B-树的变种,其特点如下:

    1. 多路非二叉
      1. 只有叶子节点保存数据
      2. 搜索时相当于二分查找
      3. 增加了相邻接点的指向指针

由此可以看出 B-树和 B+树的区别

    1. B+树查询时间复杂度固定是 logn,B-树查询复杂度最好是 O(1)。
      1. B+树相邻接点的指针可以大大增加区间访问性,可使用在范围查询等,而 B-树每个节点 key 和 data 在一起,则无法区间查找。
      2. B+树更适合外部存储,也就是磁盘存储。由于内节点无 data 域,每个节点能索引的范围更大更精确

如下图也可以看到树的演进过程

image

LSM Tree

基本思想

LSM-tree(Log Struct Merge tree) 其中的 Log 思想来源于 Log Structured FileSystem,而不是数据库的 Write Ahead Log,而 LSM-tree 中的 Tree 理论上可以被任何有序的结构替代;因此,LSM-tree 的内核是 Merge 的思想,它充分利用了多层的存储结构,将 write 进行多层的缓冲,因此有了比较好的写性能。

image

针对上图所示,其主要流程如下:

    1. 对于一个写操作,先写入到 memtable 中
      1. 当 memtable 达到一定的限制后,这部分转成 immutable memtable(不可写)
      2. 当 immutable memtable 达到一定限制,将 flush 到磁盘中,即 sstable
      3. sstable 再进行 compaction 操作

主要数据结构

对于 LSM 树主要有三个重要的结构

    1. MemTable:MemTable 是在内存中的数据结构,用于保存最近更新的数据,会按照 Key 有序地组织这些数据,LSM 树对于具体如何组织有序地组织数据并没有明确的数据结构定义,例如 Hbase 使跳跃表来保证内存中 key 的有序。因为数据暂时保存在内存中,内存并不是可靠存储,如果断电会丢失数据,因此通常会通过 WAL(Write-ahead logging,预写式日志) 的方式来保证数据的可靠性。
      1. **Immutable MemTable:**当 MemTable 达到一定大小后,会转化成 Immutable MemTable。Immutable MemTable 是将转 MemTable 变为 SSTable 的一种中间状态。写操作由新的 MemTable 处理,在转存过程中不阻塞数据更新操作。
      2. SSTable(Sorted Sequence Table):有序键值对集合,是 LSM 树组在*磁盘中的数据结构。为了加快 SSTable 的读取,可以通过建立 key 的索引以及布隆过滤器来加快 key 的查找。

image

LSM 数的数据更新是日志式的,当一条数据更新是直接 append 一条更新记录完成的。这样设计的目的就是为了顺序写,不断地将 Immutable MemTable flush 到持久化存储即可,而不用去修改之前的 SSTable 中的 key,保证了顺序写。

因此当 MemTable 达到一定大小 flush 到持久化存储变成 SSTable 后,在不同的 SSTable 中,可能存在相同 Key 的记录,当然最新的那条记录才是准确的。这样设计的虽然大大提高了写性能,但同时也会带来一些问题:

    1. 冗余存储,对于某个 key,实际上除了最新的那条记录外,其他的记录都是冗余无用的,但是仍然占用了存储空间。因此需要进行 Compact 操作 (合并多个 SSTable) 来清除冗余的记录。
      1. 读取时需要从最新的倒着查询,直到找到某个 key 的记录。最坏情况需要查询完所有的 SSTable,这里可以通过前面提到的索引/布隆过滤器来优化查找速度。

布隆过滤器的内部依赖于哈希算法,当检测某一条数据是否见过时,有一定概率出现假阳性(False Positive),但一定不会出现假阴性(False Negative)。也就是说,当布隆过滤器认为一条数据出现过,那么该条数据很可能出现过;但如果布隆过滤器认为一条数据没出现过,那么该条数据一定没出现过。这种特性刚好与此处的需求相契合,即检验某条数据是否缺失。

特点

  1. 写数据时,首先将数据缓存到内存中的一个有序树结构中(称为 memtable)。同时触发相关结构的更新,例如布隆过滤器、稀疏索引。
  2. 当 memtable 积累到足够大时,会一次性写入磁盘中,生成一个内部有序的 segment 文件。该过程为连续写,因此效率极高。
    1. 进行查询时,首先检查布隆过滤器。如果布隆过滤器报告数据不存在,则直接返回不存在。否则,按照从新到老的顺序依次查询每个 segment。
  3. 在查询每个 segment 时,首先使用二分搜索检索对应的稀疏索引,找到数据所在的 offset 范围。然后读取磁盘上该范围内的数据,再次进行二分查找并获得结果。
  4. 对于大量的 segment 文件,定期在后台执行 compaction 操作,将多个文件合并为更大的文件,以保证查询效率不衰减。

From B-Tree To LSM

主流的关系型数据库均以 B/B+ tree 作为其构建索引的数据结构,这是因为 B tree 提供了理论上最高的查询效率 - image 。但对查询性能的追求也造成了 B tree 的相应缺点,即每次插入或删除一条数据时,均需要更新索引,从而造成一次磁盘 IO。这种特性决定了 B tree 只适用于频繁读、较少写的场景。如果在频繁写的场景下,将造成大量的磁盘 IO,从而导致性能骤降。这种应用场景在传统的关系型数据库中比较常见。

而 LSM tree 则避免了频繁写场景下的磁盘 IO 开销,尽管其查询效率无法达到理想的 image ,但依然非常快,可以接受。所以从本质上来说,LSM tree 相当于牺牲了一部分查询性能,换取了可观的写入性能。这对于 key-value 型或日志型数据库是非常重要的。

B+Tree 索引一定好于传统的 B-Tree 索引么

MongoDB

在 【原创】为什么 Mongodb 索引用 B 树,而 Mysql 用 B+树? 这篇文章中着重描述了 MongoDB 因为是文档型数据库,并且作为聚合型数据库,因此 B-Tree 的数据结构就更加适合这样的场景。

但是实际上的 MongoDB 在实现上面也是采用的最上面所描述的叶子节点存储数据的 B-Tree 索引么?

在 MongoDB 的官方文档 WiredTiger Storage Engine 中已经明确指出。在 MongoDB 3.2 版本之后,WiredTiger 已经成为了 MongoDB 的默认引擎。

在 WiredTiger 的官方文档 Tuning page size and compression 这一节中,其中有对索引实现的明确描述:

WiredTiger maintains a table’s data in memory using a data structure called a B-Tree ( B+ Tree to be specific), referring to the nodes of a B-Tree as pages. Internal pages carry only keys. The leaf pages store both keys and values.

其中明确指出了 MongoDB 也是采用的 B+Tree 实现索引

对于 MongoDB 之前所采用的数据引擎「MMAPv1」也没有找到明确的文献表示原有的数据索引是采用的叶子节点存储数据的 B-Tree 实现。

关于 MongoDB 新旧数据引擎的具体对比可以看 What’s New in MongoDB 3.0 Part 3: Performance & Efficiency Gains, New Storage Architecture

image

Postgres

在使用 Pg 建立数据库索引的时候我们可以看到如下的语句

1
csharp复制代码CREATE INDEX ON rel_8192_$idx.user_live_rights using btree (user_id, right_id, right_type, scope_id) where status != 'deleted';

在语句中明确表述了,在创建数据库索引的时候 using btree。但是这个 btree 是我们印象中叶子节点也存储数据的 b-tree 么?

在 PostgreSQL B-Tree 是传统树算法的一个变种(high-concurrency B-tree management algorithm),其算法详情可以参考入场的 github 描述。其主要的改进如下

  1. compared to a classic B-tree, L&Y adds a right-link pointer to each page to the page’s right sibling.
  2. It also adds a “high key” to each page, which is an upper bound on the keys that are allowed on that page.

These two additions make it possible detect a concurrent page split, which allows the tree to be searched without holding any read locks (except to keep a single page from being modified while reading it).

其主要的组织形式可以查看《深入浅出 PostgreSQL B-Tree 索引结构》

image

总结

B+Tree 的优点

  1. B+ 树有更低的树高:平衡树的树高 O(h)=O(logdN),其中 d 为每个节点的出度。红黑树的出度为 2,而 B+ Tree 的出度一般都非常大,所以红黑树的树高 h 很明显比 B+ Tree 大非常多。
  2. 磁盘访问原理:操作系统一般将内存和磁盘分割成固定大小的块,每一块称为一页,内存与磁盘以页为单位交换数据。
    • 数据库系统将索引的一个节点的大小设置为页的大小,使得一次 I/O 就能完全载入一个节点。
      • 如果数据不在同一个磁盘块上,那么通常需要移动制动手臂进行寻道,而制动手臂因为其物理结构导致了移动效率低下,从而增加磁盘数据读取时间。
      • B+ 树有更低的树高,进行寻道的次数与树高成正比,在同一个磁盘块上进行访问只需要很短的磁盘旋转时间,所以 B+ 树更适合磁盘数据的读取。
  1. 磁盘预读特性:为了减少磁盘 I/O 操作,磁盘往往不是严格按需读取,而是每次都会预读。预读过程中,磁盘进行顺序读取,顺序读取不需要进行磁盘寻道,并且只需要很短的磁盘旋转时间,速度会非常快。并且可以利用预读特性,相邻的节点也能够被预先载入。

参考资料参考资料

  1. MongoDB WiredTiger Internals
  2. Indexes in PostgreSQL — 4 (Btree)
  3. LSM 数增删改查步骤
  4. MONGODB MANUAL - Indexes
  5. 深入浅出 PostgreSQL B-Tree 索引结构
  6. wiredtiger manual
  7. The Difference Between B-trees and B+trees
  8. 为什么 MySQL 使用 B+ 树
  9. LSM-Trees and B-Trees: The Best of Both Worlds
  10. The advantages of an LSM vs a B-Tree
  11. B-Tree-vs-Log-Structured-Merge-Tree
  12. MongoDB · 内核特性 · wiredtiger page 逐出
  13. postgres B 树算法解析
  14. 从 MongoDB 及 Mysql 谈 B-/B+树
  15. From Btree To LSM-tree
  16. LSM 算法的原理是什么?:知乎话题讨论。里面提供了很多 LSM 的实现细节
  17. 【原创】为什么 Mongodb 索引用 B 树,而 Mysql 用 B+树?
  18. LSM 树详解 :里面详细解释了 LSM 的两种合并策略,以及每个策略的优缺点
  19. Effective MongoDB Indexing (Part 1)
  20. [mongodb] MMAP 和 wiredTiger 的比较
  21. MongoDB Engines: MMAPV1 Vs WiredTiger
  22. Read, write & space amplification - B-Tree vs LSM
  23. Indexes in PostgreSQL — 4 (Btree)
  24. [新旧存储引擎比较, WiredTiger与MMAPv1](

本文转载自: 掘金

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

Spring 的事务实现原理和传播机制

发表于 2021-06-03

Spring 为事务管理提供了丰富的功能支持。Spring 事务管理分为编程式和声明式两种。编程式事务指的是通过编码方式实现事务;声明式事务基于 AOP,将具体的逻辑与事务处理解耦。生命式事务管理使业务代码逻辑不受污染,因此实际使用中声明式事务用的比较多。

声明式事务有两种方式,一种是在配置文件(XML)中做相关的事务规则声明,另一种是基于 @Transactional 注解的方式。本文着重介绍基于 @Transactional 注解的事务管理。

需要明确几点:

默认配置下 Spring 只会回滚运行时、未检查异常(继承自 RuntimeException 的异常)或者 Error。@Transactional 注解只能应用到 public 方法才有效。

Spring 官方文档:docs.spring.io/spring/docs…

图片.png

在默认的代理模式下,只有目标方法由外部调用,才能被 Spring 的事务拦截器拦截。在同一个类中的两个方法直接调用,是不会被 Spring 的事务拦截器拦截,就像上面的 save 方法直接调用了同一个类中的 method1方法,method1 方法不会被 Spring 的事务拦截器拦截。可以使用 AspectJ 取代 Spring AOP 代理来解决这个问题,但是这里暂不讨论。

为了解决这个问题,我们可以新建一个类。

1
2
3
4
5
6
7
8
9
10
11
12
13
java复制代码@Service
public class OtherServiceImpl implements OtherService {

@Autowired
private UserMapper userMapper;

@Transactional(propagation = Propagation.REQUIRES_NEW)
@Override
public void method1() {
User user = new User("李四");
userMapper.insertSelective(user);
}
}

然后在 save 方法中调用 otherService.method1 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
java复制代码@Autowired
private OtherService otherService;

@Transactional(propagation = Propagation.REQUIRED)
@Override
public void save() {

otherService.method1();

User user = new User("zhangsan");
userMapper.insertSelective(user);

if (true) {
throw new RuntimeException("save 抛异常了");
}
}

这下,otherService.method1 方法的数据插入成功,save 方法的数据未插入,事务回滚。

参考链接:
blog.csdn.net/NathanniuBe…
zhuanlan.zhihu.com/p/157646604

本文转载自: 掘金

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

1…653654655…956

开发者博客

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