KingbaseES函数三态
理解函数的三态1
VOLATILE:
volatile函数没有限制,可以修改数据(如执行delete,insert,update), 使用同样的参数调用可能返回不同的值.
STABLE:
不允许修改数据, PG8.0以及以上版本不允许在volatile函数中使用非SELECT|PERFORM语句.
使用同样的参数调用返回同样的结果,在事务中有这个特性的也归属stable.
IMMUTABLE:
不允许修改数据, 使用同样的参数调用返回同样的结果.
备注:Pg 8.0以及以上版本不允许在stable或immutable函数中执行非select|perform语句.
示例:
test=# create table tbl(id int primary key, info text, crt_time timestamp);
CREATE TABLE
test=# create or replace function f_tbl(i_id int) returns void as $$
test$# declare
test$# begin
test$# update tbl set crt_time=now() where id=i_id;
test$# end;
test$# $$ language plpgsql stable;
CREATE FUNCTION
test=# \set VERBOSITY verbose
test=# select f_tbl(1);
ERROR: 0A000: UPDATE is not allowed in a non-volatile function
背景: SQL statement "update tbl set crt_time=now() where id=i_id"
PL/pgSQL function f_tbl(integer) line 4 at SQL statement
位置: _SPI_execute_plan_internal, spi.c:2951
漏洞:在stable或immutable函数中调用volatile函数是可以的.
示例:
test=# alter function f_tbl(int) volatile;
ALTER FUNCTION
test=# create or replace function f_tbl1(i_id int) returns void as $$
test$# declare
test$# begin
test$# perform f_tbl(i_id); -- 在stable或immutable函数中调用volatile函数是可以的.
test$# end;
test$# $$ language plpgsql stable;
CREATE FUNCTION
test=#
test=# insert into tbl values(1,'test',now());
INSERT 0 1
test=# select * from tbl;
id | info | crt_time
----+------+----------------------------
1 | test | 2022-11-10 11:46:05.854350
(1 行记录) test=# select f_tbl1(1);
f_tbl1
-------- (1 行记录) test=# select * from tbl;
id | info | crt_time
----+------+----------------------------
1 | test | 2022-11-10 11:46:33.153699
(1 行记录)
同样的参数多次调用.
volatile 函数相同的参数,多次调用返回结果可能不一样.
kbstattuple扩展插件可以参考KingbaseES在线手册
https://help.kingbase.com.cn/v8/development/sql-plsql/ref-extended-plug-in/kbstattuple.html?highlight=kbstattuple test=# create extension kbstattuple ;
CREATE EXTENSION
test=# \dx+ kbstattuple ;
对象用于扩展 "kbstattuple"
对象描述
---------------------------------------
function pg_relpages(regclass)
function pg_relpages(text)
function pgstatginindex(regclass)
function pgstathashindex(regclass)
function pgstatindex(regclass)
function pgstatindex(text)
function pgstattuple_approx(regclass)
function pgstattuple(regclass)
function pgstattuple(text)
(9 行记录) test=# create table t2(id int);
CREATE TABLE
test=# select pg_relpages('t2');
pg_relpages
-------------
0
(1 行记录) test=# insert into t2 values (1);
INSERT 0 1
test=# select pg_relpages('t2'); -- 返回值变化
pg_relpages
-------------
1
(1 行记录) test=# select proname,provolatile from pg_proc where proname='pg_relpages';
proname | provolatile
-------------+-------------
pg_relpages | v
pg_relpages | v
(2 行记录)
stable, immutable函数同样的参数多次调用返回结果不变.
在事务中多次调用返回结果一致的也可归属于stable.
test=# select now();
now
-------------------------------
2022-11-10 13:42:35.689740+08
(1 行记录) test=# select now();
now
-------------------------------
2022-11-10 13:42:36.756038+08
(1 行记录) test=# begin;
BEGIN
test=# select now();
now
-------------------------------
2022-11-10 13:42:39.037893+08
(1 行记录) test=# select now();
now
-------------------------------
2022-11-10 13:42:39.037893+08
(1 行记录) test=# select provolatile,proname,proargtypes from pg_proc where proname='now';
provolatile | proname | proargtypes
-------------+---------+-------------
s | now |
(1 行记录) test=# select now();
now
-------------------------------
2022-11-10 13:42:39.037893+08
(1 行记录) test=# end;
COMMIT
test=# select now();
now
-------------------------------
2022-11-10 13:43:31.772061+08
(1 行记录)
immutable函数同stable, 同样的参数多次调用结果一致.
test=# select proname,provolatile from pg_proc where proname='abs';
proname | provolatile
---------+-------------
abs | i
abs | i
abs | i
abs | i
abs | i
abs | i
abs | i
(7 行记录) test=# select abs(-10);
abs
-----
10
(1 行记录) test=# select abs(-10);
abs
-----
10
(1 行记录) test=# begin;
BEGIN
test=# select abs(-10);
abs
-----
10
(1 行记录) test=# select abs(-10);
abs
-----
10
(1 行记录) test=# end;
COMMIT
test=# select abs(-10);
abs
-----
10
(1 行记录)
VOLATILE/STABLE/IMMUTABLE区别:
VOLATILE
volatile函数不能被优化器作为优化条件.
1)例如单SQL处理多行时不能减少volatile函数的调用次数,
2)不能使用volatile函数创建函数索引,
3)在过滤条件中使用volatile函数时, 不能走索引扫描.
在同一个查询中, 同样参数的情况下可能被多次执行(QUERY有多行返回/扫描的情况下).
STABLE
1)优化器可根据实际场景优化stable函数的调用次数, 同样的参数多次调用可能减少成单次调用.
2)stable函数可以用于优化器选择合适的索引扫描, 因为索引扫描仅评估被比较的值一次, 后多次比较.
3)stable和volatile函数都不能用于创建函数索引, 只有immutable函数可以用于创建函数索引.
IMMUTABLE
优化器在处理immutable函数时, 先评估函数结果, 将结果替换为常量.
理解函数的三态2
test=# create table t3(id int);
CREATE TABLE
test=# insert into t3 values(1),(1),(1),(2),(2),(2);
INSERT 0 6
test=#
test=# create or replace function f_t3(id int) returns int as $$
test$# declare
test$# begin
test$# raise notice 'Called.';
test$# return id;
test$# end;
test$# $$ language plpgsql stable; -- stable 状态
CREATE FUNCTION
test=# select * from t3 where f_t3(id) =1;
-- f_t3(id) --这里id是变量,不能被优化器优化,因此函数调用了6次.
test=# select * from t3 where f_t3(id) =1;
NOTICE: 00000: Called.
位置: exec_stmt_raise, pl_exec.c:3908
NOTICE: 00000: Called.
位置: exec_stmt_raise, pl_exec.c:3908
NOTICE: 00000: Called.
位置: exec_stmt_raise, pl_exec.c:3908
NOTICE: 00000: Called.
位置: exec_stmt_raise, pl_exec.c:3908
NOTICE: 00000: Called.
位置: exec_stmt_raise, pl_exec.c:3908
NOTICE: 00000: Called.
位置: exec_stmt_raise, pl_exec.c:3908
id
----
1
1
1
(3 行记录) select * from t3 where f_t3(1) =1;
-- 这里使用常量调用f_t3()所以可以被优化器优化.
test=# select * from t3 where f_t3(1) =1;
NOTICE: 00000: Called. -- 函数只被调用一次
位置: exec_stmt_raise, pl_exec.c:3908
id
----
1
1
1
2
2
2
(6 行记录) 把函数改成volatile后, 函数不能被优化. test=# alter function f_t3(int) volatile;
ALTER FUNCTION
test=# select proname,provolatile from pg_proc where proname='f_t3';
proname | provolatile
---------+-------------
f_t3 | v
(1 行记录) test=# select * from t3 where f_t3(1) =1;
NOTICE: 00000: Called.
位置: exec_stmt_raise, pl_exec.c:3908
NOTICE: 00000: Called.
位置: exec_stmt_raise, pl_exec.c:3908
NOTICE: 00000: Called.
位置: exec_stmt_raise, pl_exec.c:3908
NOTICE: 00000: Called.
位置: exec_stmt_raise, pl_exec.c:3908
NOTICE: 00000: Called.
位置: exec_stmt_raise, pl_exec.c:3908
NOTICE: 00000: Called.
位置: exec_stmt_raise, pl_exec.c:3908
id
----
1
1
1
2
2
2
(6 行记录)
根据函数的实际情况设置稳定态, 可以达到优化效果.
例如f_t3函数调用一次耗时1秒, 并且是stable的状态,以上例子可以减少5秒的查询时间.使用volatile态则需要6秒.
优化器在处理immutable函数时,先评估函数结果,将结果替换为常量.
test=# explain select * from t2 where id>abs(-1);
QUERY PLAN
-----------------------------------------------------
Seq Scan on t2 (cost=0.00..41.75 rows=847 width=4)
Filter: (id > 1)
(2 行记录)
因为abs(int)是immutable函数,这里abs(-1)替换成常量1.
如果把函数改成stable, 那么将不会替换成常量.
test=# alter function abs(int) stable;
ALTER FUNCTION
test=# select proname,provolatile from pg_proc where proname='abs';
proname | provolatile
---------+-------------
abs | i
abs | i
abs | i
abs | i
abs | i
abs | s
abs | i
(7 行记录)
test=# explain select * from t2 where id>abs(-1);
QUERY PLAN
-----------------------------------------------------
Seq Scan on t2 (cost=0.00..48.10 rows=847 width=4)
Filter: (id > abs('-1'::integer))
(2 行记录)
由于abs(int)被改成stable了, 将不会替换成常量
在prepared statement中使用需要注意区别. 后面会有例子.
只有immutable函数可以创建函数索引.
test=# create table t4(id int,info timestamp(0));
CREATE TABLE
test=# \set VERBOSITY verbose
test=# create index idx_t4_1 on t4(to_char(info,'yyyymmdd'));
ERROR: 42P17: functions in index expression must be marked IMMUTABLE
位置: ComputeIndexAttrs, indexcmds.c:1940
test=# create table t5(id int primary key, info text);
CREATE TABLE
test=# insert into t5 select generate_series(1,100000),md5(random()::text);
INSERT 0 100000
test=# alter function abs(int) volatile;
ALTER FUNCTION
test=#
test=# \d+ t4
数据表 "public.t4"
栏位 | 类型 | 校对规则 | 可空的 | 预设 | 存储 | 统计目标 | 描述
------+--------------------------------+----------+--------+------+-------+----------+------
id | integer | | | | plain | |
info | timestamp(0) without time zone | | | | plain | |
访问方法 heap
test=# \d+ t5
数据表 "public.t5"
栏位 | 类型 | 校对规则 | 可空的 | 预设 | 存储 | 统计目标 | 描述
------+---------+----------+----------+------+----------+----------+------
id | integer | | not null | | plain | |
info | text | | | | extended | |
索引:
"t5_pkey" PRIMARY KEY, btree (id)
访问方法 heap
索引扫描时, 用于过滤条件的表达式只被评估一次后, 再与索引值进行比较判断是否满足条件.
test=# explain select * from t5 where id<abs(10);
QUERY PLAN
----------------------------------------------------------
Seq Scan on t5 (cost=0.00..2341.00 rows=33333 width=37)
Filter: (id < abs(10))
(2 行记录)
只有stable函数和immutable函数符合索引扫描的刚性需求.
test=# alter function abs(int) stable;
ALTER FUNCTION
test=# explain select * from t5 where id<abs(-100);
QUERY PLAN
--------------------------------------------------------------------
Index Scan using t5_pkey on t5 (cost=0.29..9.99 rows=97 width=37)
Index Cond: (id < abs('-100'::integer))
(2 行记录)
test=# alter function abs(int) immutable;
ALTER FUNCTION
test=# explain select * from t5 where id<abs(-100);
QUERY PLAN
--------------------------------------------------------------------
Index Scan using t5_pkey on t5 (cost=0.29..9.99 rows=97 width=37)
Index Cond: (id < 100)
(2 行记录)
volatile函数同样的输入参数可能返回不同值,在一个查询中将被多次调用,不符合索引扫描规则.
stable和immutable同样的参数返回值不变,因此可以作为索引扫描的比较值,优化器允许走索引扫描.
理解函数的三态3
函数内的每条查询语句的数据可见性:
VOLATILE
snapshot为函数内的每个query开始时的snapshot. 因此对外部已提交的数据时可见的.
STABLE
snapshot为外部调用函数的QUERY的snapshot, 函数内部始终保持这个snapshot.
IMMUTABLE
同stable
测试:
test=# create or replace function f_t6() returns void as $$
test$# declare
test$# r record;
test$# begin
test$# for i in 1..10 loop
test$# for r in select * from t6 loop
test$# raise notice 'loop:%,t6:%.',i,r;
test$# end loop;
test$# perform pg_sleep(5);
test$# end loop;
test$# end;
test$# $$ language plpgsql volatile;
CREATE FUNCTION
test=# create table t6(id int,info text);
CREATE TABLE
--执行过程中,新建连接往t6表插入新数据,volatile函数察觉新增行
test=# select f_t6();
NOTICE: loop:2,t6:(1,test).
NOTICE: loop:3,t6:(1,test).
NOTICE: loop:4,t6:(1,test).
NOTICE: loop:5,t6:(1,test).
NOTICE: loop:5,t6:(1,test1).
NOTICE: loop:6,t6:(1,test).
NOTICE: loop:6,t6:(1,test1).
NOTICE: loop:6,t6:(1,test3).
NOTICE: loop:7,t6:(1,test).
NOTICE: loop:7,t6:(1,test1).
NOTICE: loop:7,t6:(1,test3).
NOTICE: loop:7,t6:(1,test5).
NOTICE: loop:8,t6:(1,test).
NOTICE: loop:8,t6:(1,test1).
NOTICE: loop:8,t6:(1,test3).
NOTICE: loop:8,t6:(1,test5).
NOTICE: loop:9,t6:(1,test).
NOTICE: loop:9,t6:(1,test1).
NOTICE: loop:9,t6:(1,test3).
NOTICE: loop:9,t6:(1,test5).
NOTICE: loop:10,t6:(1,test).
NOTICE: loop:10,t6:(1,test1).
NOTICE: loop:10,t6:(1,test3).
NOTICE: loop:10,t6:(1,test5).
NOTICE: loop:10,t6:(1,hello).
f_t6
------
(1 行记录)
test=# insert into t6 values(1,'test');
INSERT 0 1
test=# insert into t6 values(1,'test1');
INSERT 0 1
test=# insert into t6 values(1,'test3');
INSERT 0 1
test=# insert into t6 values(1,'test5');
INSERT 0 1
test=# insert into t6 values(1,'hello');
INSERT 0 1
test=#
--更改稳定态为 stable immutable
test=# alter function f_t6() stable;
ALTER FUNCTION
test=# truncate table t6;
TRUNCATE TABLE
test=# select f_t6();
f_t6
------
(1 行记录)
--f_t6()执行过程中对t6变更, 函数不感知.
test=# insert into t6 values(1,'hello');
INSERT 0 1
test=# insert into t6 values(1,'test5');
INSERT 0 1
test=# insert into t6 values(1,'test3');
INSERT 0 1
test=# insert into t6 values(1,'test3');
INSERT 0 1
test=#
理解函数的三态4
STABLE和IMMUTABLE的区别:
在SELECT子句中,优化器对stable和immutable区别对待.
测试:
test=# create table t7(id int);
CREATE TABLE
test=# insert into t7 values (1),(2),(3);
INSERT 0 3
test=# create or replace function f_t7(i int) returns int as $$
test$# declare
test$# begin
test$# raise notice 'called';
test$# return i;
test$# end;
test$# $$ language plpgsql stable;
CREATE FUNCTION -- SELECT子句中, stable函数不被优化器优化
test=# select f_t7(1),* from t7;
NOTICE: called --多次调用
NOTICE: called
NOTICE: called
f_t7 | id
------+----
1 | 1
1 | 2
1 | 3
(3 行记录) test=# alter function f_t7(int) immutable;
ALTER FUNCTION -- immutable函数被替换成常量, 只执行一次.
test=# select f_t7(1),* from t7;
NOTICE: called --一次调用
f_t7 | id
------+----
1 | 1
1 | 2
1 | 3
(3 行记录) --当函数为stable时, 优化器没有处理这种过滤条件, 理论上是可以优化为一次调用f_t7(1)的. 目前仅仅immutable被优化.
test=# alter function f_t7(int) stable;
ALTER FUNCTION 当使用索引扫描时, stable在这里只会执行一次. test=# select * from t7 where id=f_t7(1);
NOTICE: called --有一次为explain的评估输出.
NOTICE: called
NOTICE: called
NOTICE: called
id
----
1
(1 行记录) -- 将id=f_t7(1)替换成f_t7(1)=1
test=# select * from t7 where f_t7(1)=1;
NOTICE: called
id
----
1
2
3
(3 行记录) test=# explain select * from t7 where f_t7(1)=1;
QUERY PLAN
------------------------------------------------------------
Result (cost=0.25..35.65 rows=2540 width=4)
One-Time Filter: (f_t7(1) = 1)
-> Seq Scan on t7 (cost=0.25..35.65 rows=2540 width=4)
(3 行记录) test=# alter function f_t7(int) immutable;
ALTER FUNCTION
test=# explain select * from t7 where f_t7(1)=1;
NOTICE: called
QUERY PLAN
------------------------------------------------------
Seq Scan on t7 (cost=0.00..35.40 rows=2540 width=4)
(1 行记录)
理解函数的三态
VOLATILE
1)volatile函数没有限制, 可以修改数据(如执行delete, insert , update).
2)使用同样的参数调用可能返回不同的值.
3)volatile函数不能被优化器选择作为优化条件.(例如减少调用, 函数索引, 索引扫描不允许使用volatile函数)
4)在同一个查询中, 同样参数的情况下可能被多次执行(QUERY有多行返回/扫描的情况下).
5)snapshot为函数内的每个query开始时的snapshot. 因此对在函数执行过程中, 外部已提交的数据可见.(仅限于调用函数的事务隔离级别为read committed)
STABLE
1)stable和immutable函数, 函数内不允许修改数据.(如PGver>=8.0 函数内不可执行非SELECT|PERFORM语句.)
2)使用同样的参数调用返回同样的结果, 在事务中有这个特性的也归属stable.
3)优化器可根据实际场景优化stable函数的调用次数, 同样的参数多次调用可减少成单次调用.
4)stable和immutable函数可用于优化器选择合适的索引扫描, 因为索引扫描仅评估被比较的表达式一次, 后多次与索引值进行比较.
5)stable和volatile函数都不能用于创建函数索引, 只有immutable函数可以用于创建函数索引.
6)stable和immutable函数, snapshot为外部调用函数的QUERY的snapshot, 函数内部始终保持这个snapshot, 外部会话带来的的数据变更不被反映到函数执行过程中.
IMMUTABLE
1)不允许修改数据, 使用同样的参数调用返回同样的结果.
2)优化器在处理immutable函数时, 先评估函数结果, 将结果替换为常量.
3)因此使用约束优化查询的场景中也只识别immutable函数.
STABLE和IMMUTABLE的区别
1)STABLE函数在SELECT和WHERE子句中不被优化, 仅仅当使用索引扫描时WHERE子句对应的STABLE函数才会被优化为1次调用.
2)在PREPARED STATEMENT中的使用区别:
3)IMMUTABLE函数在PLAN时以常量替代,STABLE函数在EXECUTE阶段被执行.
4)因此IMMUTABLE函数参数为常量时,在PREPARED STATEMENT场景只执行一次,而STABLE函数被多次执行.
函数稳定性通过查看PG_PROC/SYS_PROC.PROVOLATILE得到
PROVOLATILE 说明函数是仅仅只依赖 于它的输入参数 ,还是会被外部 因素影响。
值 I 表示“不变的”函数,它对于相同的输入总是输出相同的结果。
值 S 表示“稳定的”函数,它的结果(对于固定输入在一次扫描内不会变化。
值 V 表示“不稳定的”函数,它的结果在任何时候都可能变化。
使用V页表示函数具有副作用,所以对它们的调用无法得到优化。
KingbaseES函数三态的更多相关文章
- KingbaseES 函数稳定性与SQL性能
背景:客户现场的一次艰苦的调优过程(https://www.cnblogs.com/kingbase/p/16015834.html),让我觉得非常有必要让数据库用户了解函数的不同稳定性属性,及其对于 ...
- KingbaseES函数如何返回结果集
函数返回值一般是某一类型值,如int,varchar,date等,返回结果集时就需要用到setof语法. 创建数据 create table class(id number primary key, ...
- KingbaseES 与Oracle 函数稳定性对于性能影响差异比较
一.函数的属性 KingbaseES 函数在定义时有三种稳定性级别:volatile.stable 和 immutable.默认情况下,创建函数的稳定性为volatile.以下是这三种函数的区别: V ...
- KingbaseES时间函数的比较
KingbaseES提供了多种的时间函数,这些函数在使用过程中存在哪些不同? **同一事务** test=# begin test-# for i in 1.. 10 loop test-# rais ...
- KingbaseES 格式化函数
KingbaseES格式化函数提供一套有效的工具用于把各种数据类型(日期/时间.integer.floating point和numeric)转换成格式化的字符串以及反过来从格式化的字符串转换成指定的 ...
- KingbaseES 实现MYSQL hex/unhex 函数
MySQL 的hex 和 unhex 函数类似于KingbaseES 的encode 和 decoding,实现字符与16进制之间的转换. 一.先看MySQL例子 mysql> select h ...
- KingbaseES 行列转换函数
关键字: 行专列,列转行, pivot, unpivot 行列转换是在数据分析中经常用到的一项功能,KingbaseES从V8R6C3B0071版本开始通过扩展插件(kdb_utils_func ...
- KingbaseES sys_blocking_pids 函数
会话出现了锁等待,想要快速查询到堵塞的会话,可以使用 sys_blocking_pids 函数来实现这一目的. sys_blocking_pids:获取哪些会话阻塞了某个会话(输入参数). sys_b ...
- KingbaseES DENSE_RANK 函数用法
DENSE_RANK()函数用于为结果集分区内的每一行分配一个排名,排名值之间没有差距,函数为结果集的每个分区中的每一行分配一个等级. 与 RANK() 函数不同的是,DENSE_RANK() 函数总 ...
- KingbaseES 中可以通过构造一个聚集函数来实现mysql的any_value功能。
示例 创建函数 create function coalesce_("anyelement","anyelement") returns "anyel ...
随机推荐
- 聊聊kafka
两个月因为忙于工作毫无输出了,最近想给团队小伙伴分享下kafka的相关知识,于是就想着利用博客来做个提前的准备工作了:接下来会对kafka做一个简单的介绍,包括利用akf原则来解析单机下kafk的各个 ...
- 【云原生 · Docker】Docker虚拟化技术
1.Docker入门简介 Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux机器上,也可以实现虚拟化. 容器是完全使用沙箱 ...
- Spring Boot 多数据源配置
第一种方式: AbstractRoutingDataSource 1.1. 手动切换数据源 application.properties # Order # 如果用Druid作为数据源,应该用url属 ...
- 初学RNN
FNN 定义 FNN(Feedforward Neural Network),即前馈神经网络,它是网络信息单向传递的一种神经网络,数据由输入层开始输入,依次流入隐藏层各层神经元,最终由输出层输出.其当 ...
- Aspose.Cells设置单元格背景色不生效
Style.BackgroundColor property 获取或设置样式的背景颜色.public Color BackgroundColor { get; set; } 评论 如果要设置单元格的颜 ...
- javaweb string
今天遇到一个跨域请求jsonp格式报错,其原因是其中一个参数过从我方数据库取出就带有换行格式的,类似于: 这条数据竟然自带格式换行. 而我们现常用的trim()只能去掉字符串的头部和尾部的空格, 而要 ...
- 浏览器直接修改网站的js代码
1.按下F12打开控制台,找到源代码,然后是替换 2.在本地创建一个文件夹,会提示风险,点击允许 3.再找到你要修改的js文件代码,右击选择保存并覆盖 这样代码会保存到你刚刚创建的本地文件夹当中,接着 ...
- 【vue3】element-plus,Checkbox-Group多选框之绑定选中数据不选中问题
今天记录一下在新项目vue3中,使用的element-plus组价库遇到的一个问题!场景如下:有一个表格的column绑定的数组对象,我需要对表格的头部实现动态可配置显示表格列,由于绑定的column ...
- 下载Font Awesome框架
目录 一:下载Font Awesome框架 二:如何使用font awesome 1.使用图标等样式,点击复制标签即可,需要嵌套在i标签内 2.点击图标,复制标签,然后粘贴使用即可. 3.动态图片等 ...
- IDEA引入本地jar包的几种方法
有时候,项目需要引入一些第三方的依赖,这时候,就需要导入这些jar包.以下分享两种方式: 方式一.使用IDEA程序引入jar包 1.首先,点他! 2.然后,点他! 3.再然后,点他! 4.最后,在这里 ...