首页 文章 精选 留言 我的

精选列表

搜索[CLI能力],共10000篇文章
优秀的个人博客,低调大师

Apache Doris 基于 Workload Group 的负载隔离能力|Deep Dive

作者:SelectDB 技术团队 现如今企业的数据查询需求在不断增多,在共享同一集群时,往往需要同时面对多个业务线或多种分析负载的并发查询。在有限的资源条件下,查询任务间的资源抢占将导致性能下降甚至集群不稳定,因此负载管理的重要性不言而喻。 从业务场景出发,用户负载管理的需求主要来自以下几方面: 多个业务部门或租户可能共享同一套集群时,为避免不同租户间的负载相互影响,需保证每个租户的资源使用独立性和性能稳定性。 不同业务对查询任务的响应度和优先级有着不同要求,对于关键业务或高优先级任务,如实时数据分析、在线交易等,需要确保这些任务能够获得足够的资源并优先执行,避免因资源竞争对查询性能产生影响。 用户不仅关心资源的分配和管理,还注重成本控制和资源利用率。负载管理方案需在满足隔离要求的同时,实现用户对低使用成本和高资源利用率的诉求。 在早期版本中,Apache Doris 推出了基于资源标签(Resource Tag)的隔离方案,包括集群内节点级别的资源组划分以及针对单个查询的资源限制,实现了不同用户间的资源物理隔离。而为给用户提供更完善的负载管理方案,Apache Doris 自 2.0 版本起,推出了基于 Workload Group 的管理方案,实现了 CPU 资源的软限,为用户提供较高的资源利用率。在新发布的 2.1 版本基于 Linux 内核提供的 CGroup 技术,进一步地实现了对 CPU 资源的硬限,为用户提供更好的查询稳定性。 基于 Resource Tag 的物理隔离方案 在 Apache Doris 中有 FE 和 BE 两类节点,FE 节点负责元数据存储、集群管理、用户请求接入以及查询计划解析等,BE 节点则负责数据的存储和计算。在查询执行过程中涉及资源消耗的主要是 BE 节点,因此 Apache Doris 负载隔离方案都是面向 BE 节点设计。 在 Resource Tag 资源物理隔离方案中,可以对同一个集群内的 BE 节点设置标签,标签相同的 BE 节点会组成一个资源组(Resource Group),可将资源组看作数据存储和计算的一个单元。数据入库时会按照资源组配置将数据的副本写入到不同的资源组中,查询时按照资源组的划分使用对应资源组上的计算资源进行计算。 参考文档:https://doris.apache.org/zh-CN/docs/2.0/admin-manual/resource-admin/multi-tenant 我们以常见的读写分析场景为例,假设集群中有 3 台 BE,具体使用步骤如下: BE 节点绑定 Resource Tag:将两台 BE 绑定到 Tag Read 上,服务于读负载;一台 BE 绑定到 Tag Write 上,服务于写负载。读负载和写负载位于不同的机器上,以实现读写隔离。 数据副本绑定 Resource Tag:Table 1 有三个副本,两个副本绑定到 Tag Read 上,一个副本绑定到 Tag write 上。写入 replica 3 的数据会自动同步到 replica 1 和 replica 2 上,同步过程不会占用过多 BE 1 和 BE 2 的计算资源。 工作负载绑定到 Resource Tag:如果查询 SQL 携带的 Tag 为 Read,查询将被自动路由到 Tag 为 Read 上的机器上(BE 1 、BE 2)上执行;如果将 Stream Load 导入负载指定 Tag 为 Write,那么 Stream Load 就会路由到 Tag 为 Write 的机器上( BE 3)。在此过程除了副本同步时产生的开销,查询和导入之间不再有资源的竞争。 Resource Tag 还可以实现多租户的功能。例如有两个用户 UserA 和 UserB,期望创建各自独立的租户以避免相互影响,那么可以把 UserA 的计算和存储资源绑定到名为 UserA 的 Tag 上,把 UserB 的计算和存储资源绑定到名为 UserB 的 Tag 上,那么两个用户在 BE 侧就实现了租户间的资源隔离。 Resource Tag 本质是通过对 BE 节点的分组实现了资源隔离,这个方案的优点是: 隔离性好,多个租户之间通过物理机隔离,对 CPU、内存、IO 都实现了完全的隔离; 故障隔离,当一个租户出现问题(比如进程崩溃),另外一个租户丝毫不受影响; 基于这个技术,有的用户将不同的资源组放置到不同的物理机房内,实现同城 2 个机房的双活。 但是也存在一定的局限性: 读写隔离场景下,当写入负载停止时,Tag 为 Write 的机器就处于空闲状态,从而降低整个集群的资源利用率,显然无法满足用户对资源充分利用的期望。 多租户场景下,同一个租户内的多个业务方的负载之间也会相互影响。即使可以通过为每个业务方配置单独物理机来满足隔离性,却带来了高成本、低资源利用率等问题。 灵活性差,租户的数量实际是跟副本数绑定的,如果要建立 5 个租户,那么至少需要有 5 个副本才可以,这在一定程度上造成了存储空间的浪费。 基于 Workload Group 的负载管理方案 为解决上述问题,Apache Doris 推出了基于 Workload Group 的管理方案,支持了更细粒度的资源隔离机制——进程内的资源隔离,这意味着同一个 BE 内多个 Query 间也可以实现一定程度上的隔离,有效避免了进程内的资源竞争,提高资源的利用率。 Workload Group 是通过对工作负载进行分组管理,实现对内存和 CPU 资源的精细化管控。通过将用户执行的 Query 与 Workload Group 相关联,限制单个 Query 在单个 BE 节点上的 CPU 和内存资源的百分比。同时可以配置开启内存资源限制,集群资源紧张时自动终止组内高内存占用查询以缓解压力。资源空闲时,多个 Workload Group 共享空闲资源并自动突破限制,确保查询稳定执行。 CPU 资源的限制可细分为软限和硬限,CPU 软限具备资源利用率更高的特点,允许在资源空闲时候灵活分配资源;而 CPU 硬限则更侧重于性能稳定性的保障,确保各 Group 之间不会因负载变化而相互干扰。 (CPU 硬限和软限这两种隔离方式可匹配不同使用场景,但不可同时应用,用户可根据自身需求灵活选择) Workload Group 与 Resource Tag 的方案主要有以下不同: 从计算资源的角度来说,Workload Group 是对 BE 进程内的 CPU 和内存资源进一步划分,多个 Workload Group 需要在同一个 BE 上竞争资源。而 Resource Tag 则是对 BE 节点进行分组,不同业务方的负载发送到不同分组的 BE 上实现资源隔离,不同 BE 分组间的业务负载不会有直接的资源竞争。 从存储资源的角度来说,Workload Group 无需关注存储资源,只关注单 BE 内计算资源的分配。Resource Tag 则需要对数据的副本进行分组,确保需要隔离的业务方数据分布在不同的 BE 上。 01 CPU 软限制 CPU 优先级主要通过参数 cpu_share 体现,可以将它类比为权重的概念。在相同的时间周期内,权重越高的 Group 可以获得更多的 CPU 时间。 以 Group A 和 Group B 为例,若配置 Group A 的 cpu_share 为 1、Group B 的 cpu_share 为 9,给定 10s 的时间周期。当两者负载均饱和时,权重更高的 Group B 可以获得 CPU 的时间为 9s(所有资源的 90%),Group A 可获得 CPU 时间为 1s(所有资源的 10%)。而在实际使用中,并非所有业务都是满负载运行,若 Group B 的负载较低或无负载,那么 Group A 可以独占 10s 的 CPU 时间。这种方式可以提供更高的资源分配灵活性,从而提高集群 CPU 资源的整体利用率。 02 CPU 硬限制 使用 CPU 软限时,如果系统负载较高或 CPU 资源紧张时,可能引起查询性能的波动。为满足更用户对查询性能稳定的高要求, Apache Doris 在最新的 2.1 版本中,实现了 Workload Group 的 CPU 硬限制——无论当前物理机整体 CPU 是否空闲,配置了硬限的 Group 最大 CPU 用量不能超过预先配置的限制值。 以 Group A 和 Group B 为例,若配置 Group A 的 cpu_hard_limit=10%,Group B 的cpu_hard_limit=90%。当两者单机 CPU 资源均达到饱和时, Group A 的 CPU 利用率为 10%, Group B 的 CPU 利用率为 90%,这与 CPU 软限是一样的。但是当 Group B 的负载降低或没有负载时,即便 Group A 增加查询负载,其最大 CPU 利用率仍被严格限制在 10%,无法获得更多的资源。虽然这种方式牺牲了资源分配的弹性,但也确保了查询性能的稳定性。 03 内存资源限制 使用须知,BE 节点内存主要划分为以下几部分: 操作系统保留内存 BE 进程内非查询部分的内存,暂时无法被 Workload Group 统计到。 BE 进程内的查询部分的内存(包括导入操作),可被 Workload Group 统计并管理。 内存资源限制主要通过参数 memory_limit 来限制(设置可以使用 BE 内存的百分比)。不仅可以设置预配置内存用量,还影响内存过度分配(Overcommit)之后的归还优先级。 在初始状态下,高优先级的资源组会被配置更多的内存、低优先级的资源组被分配较少的内存。为了提升内存的利用率,可以通过 enable_memory_overcommit 开启资源组的内存软限制,如果系统有空闲内存资源时可以超限使用。 为了保证系统稳定运行,当系统整体内存资源不足时,系统将会优先取消占用内存较大的任务,以回收超额分配(Overcommit)的内存资源。在此过程中,系统会尽量保留高优先级的资源组内存资源,低优先级资源组超额内存将被更快收回。 04 查询排队 当业务负载超过系统可承载上限时,继续提交新的查询不仅无法有效执行,还会对运行中的查询造成影响。为避免该问题出现,Workload Group 支持查询排队功能。当查询达到预设的最大并发时,新提交计划会进入排队逻辑,当队列已满或等待超时,查询会被拒绝,以此来缓解高负载下系统的压力。 查询排队功能主要有三个属性: max_concurrency:当前 Group 允许同时运行的最大 SQL 数,如果超过最大数值,则进入排队逻辑。 max_queue_size:排队队列中的允许最大查询个数,如果队列满了,那么查询会被拒绝、执行失败。 queue_timeout:队列中排队的时间限制,如果超时会直接失败,单位是毫秒。 参考文档:https://doris.apache.org/zh-CN/docs/admin-manual/workload-group Workload Group 使用测试 接下来,我们对 Workload Group 的 CPU 软限制和硬限制进行详细测试,以便为用户清晰呈现这两种限制在相同硬件条件下的负载管理效果与性能表现。 测试环境:16 核 64G 内存单台物理机 部署方式:1 台 FE、1 台 BE 测试数据集:Clickbench、TPCH 压测工具:JMeter 01 CPU 软限测试 启动两个客户端(1、2),分别在未使用/使用 CPU 软限的前提下,测试 CPU 软限制对负载管理的效果。需要注意的是,在该测试中 Page Cache 会影响测试结果,需要关闭 Page Cache 才会达到理想的测试效果。 通过对比分析两次测试中的客户端的吞吐量数据,我们可以得出以下结论: 未使用 Workload Group 的情况下,两个客户端的吞吐量比例为 1:1,表明它们在相同运行时间内获得的 CPU 资源是相同的。 使用 Workload Group 之后, 分别设置 cpu_share 为 2048 及 1024,结果表明吞吐量比例变为 2:1。这说明在相同的运行时间内,cpu_share参数更大的客户端 1 获得了更高比例的 CPU 资源。 02 CPU 硬限测试 由上文介绍可知,CPU 硬限制在负载较高时,可以保证很好的隔离性。因此我们使用硬限限制 CPU 使用率为 50%(cpu_hard_limit=50%),并使用同一客户端分别在并发数为 1、2、4 时(模拟不同负载)下执行 q23 查询测试,每次测试运行时间为 5 分钟。 从上方测试结果可知,随着查询并发数的增加,CPU 的利用率的始终稳定在 800% 上下(在一个 16 核的机器上,800% 的意味着使用 8 个核,实际的 CPU 利用率 为 50%)。由于 CPU 资源被硬限,因此在并发增加时,tp99 延时增加是符合预期的。 03 模拟生产环境测试 在实际生产环境中,用户往往更关注查询的延迟性能而非单纯的吞吐量。为了更贴近实际应用场景并准确评估性能,我们选取了一系列延迟约为 1 秒的查询 SQL(包括 CKBench 的 q15、q17、q23 和 TPCH 的 q3、q7、q19),构成一个 SQL 集合。这些查询涵盖了单表聚合和 Join 计算等多种特性,使用的 TPCH 数据集大小为 100G。 我们设计了两组测试,分别模拟了未使用 Workload Group 和使用 Workload Group 的场景。在客户端 1 和客户端 2 上进行了四次测试,重点关注 tp90 和 tp99 的延迟情况。 通过观察上表 4 次测试中查询延迟,可得出以下结论: 未使用 Workload Group(测试 1、2):当客户端 2 的并发量从 1 增加到 4 时,客户端 1、2 的查询延迟均显著上升。对比客户端 1 的性能表现,median、tp90 和 tp95 查询响应时间均增加了 2-3 倍。 使用 Workload Group(测试 3、4): 这两次测试中应用了 CPU 硬限制:设置客户端 1 cpu_hard_limit=90%、客户端 2 cpu_hard_limit=90%。从测试结果可知,即使客户端 2 的并发量增加,客户端 1 的查询延迟仅呈现小幅上升,明显优于测试 2 中性能表现。这一结果充分展现了 Workload Group 在负载隔离和性能稳定保障上的有效性。 结束语 目前 Resource Tag 和 Workload Group 功能已经在多个社区用户的生产业务中上线并得到大规模验证,推荐有资源隔离需求的用户使用。 无论是 Resource Tag 或是 Workload Group,其目标都在于在资源隔离的独立性和资源的利用率二者之间进行平衡,前者采取了更彻底的隔离方案,而后者在保证隔离性的同时实现了资源的充分利用,并通过查询队列和任务排队机制进一步保证了在高工作负载场景下的系统稳定性。 在资源隔离的实际使用过程中,我们建议两种方案可以根据业务场景结合起来应用: 如果是跨体系/跨业务部门之间共享同一集群,希望实现资源和数据的物理隔离,可以采取 Resource Tag 方案; 如果是在同一集群内同时面对多种类型的查询负载,可以通过 Workload Group 将不同负载区分开来,通过灵活的资源分配保证多种查询负载均可以获得合适的资源; 在后续的功能完善上,我们还有很多规划: 当前内存限制通过 Cancel Query 来释放内存,未来通过算子落盘可以进一步提升大查询的稳定性、避免资源紧张时的查询任务失败。 目前在 BE 进程的内存模型中,还存在部分非查询的内存未被统计到,这可能导致用户看到的 BE 进程内存和 Workload Group 使用内存之间存在差异,未来版本中将尝试解决这个问题。 查询排队功能只支持根据最大查询并发数排队,未来将通过 BE 的资源用量来约束最大并发数,从而对客户端形成自动的反压,提升 Doris 在客户端持续提交高负载情况下的可用性。 Resource Tag 功能是对 BE 机器资源的划分,Workload Group 则是对单机进程内的资源划分,这两种资源划分的方式都对用户暴露了 BE 节点的概念。而用户在使用资源管理功能时,本质上仅需要关注自己的工作负载在整个集内的可用资源量和资源分配的优先级。未来会探索资源划分新方式,降低用户的理解和使用成本。 致谢 Workload Group 功能是开源社区合作开发的项目,感谢以下同学的贡献:罗甑林(luozenglin),刘立家(liutang123),赵立伟(levy5307)

优秀的个人博客,低调大师

Dromara 新晋开源项目 MPE ,MybatisPlus 能力拓展增强包

​ 借用 MybatisPlus 的口号:为简化开发工作、提高生产率而生 ​ 尽管MybatisPlus(后文简称 MP)相比较 Mybatis 丝滑了很多,但是日常使用中,是否偶尔仍会怀念 JPA(Hibernate)的那种纵享丝滑的感受,更好的一心投入业务开发中,如果你也是如此,那么恭喜你发现了 MybatisPlusExt(后文简称 MPE)。 ​ MPE 对 MP 做了进一步的拓展封装,即保留 MP 原功能,又添加更多有用便捷的功能。同样坚持 MP 的原则,只做增强不做改变,所以,即便是在使用 MPE 的情况下,也可以百分百的只使用 MP 的方式,因此 MP 能做的,MPE 不仅能做还能做的更多。 ​ 增强功能具体体现在几个方面:免手写Mapper、自动建表、数据自动填充(类似JPA中的审计)、关联查询(类似sql中的join)、冗余数据自动更新、动态查询条件。 开始 一、引入 jar 包 <!-- spring boot2.* --> <dependency> <groupId>com.tangzc</groupId> <artifactId>mybatis-plus-ext-spring-boot-starter</artifactId> <version>{maven仓库搜索最新版}</version> </dependency> <!-- spring boot3.* --> <dependency> <groupId>com.tangzc</groupId> <artifactId>mybatis-plus-ext-spring-boot3-starter</artifactId> <version>{maven仓库搜索最新版}</version> </dependency> 二、代码预生成 痛点: 某个地方的代码想使用下实体字段的名称,但是又不想写死一个字符串(丑、编译期不可校验)。 手动为每个实体写一个 Mapper 类,但是 Mapper 类中都是空的。 这些交给 MPE 吧!!! 它在代码编译期前,自动预生成实体字段的定义、实体 Mapper 的接口定义、实体 Repository 类的定义(该类是进一步封装 Mapper 的) // 标记生成表字段定义 @AutoDefine // 标记生成Mapper和Repository @AutoRepository @Data public class TestTable { private String id; private String name; private int age; } 效果如下: <img src="https://cdn.nlark.com/yuque/0/2024/png/279660/1709718028160-1660d6a7-f0b6-4c23-aad1-249c3ab11898.png" alt="img" style="zoom:50%;" /> 三、自动建表 MPE 自动建表依托于另一款自研框架AutoTable,MPE 是基于 AutoTable 做了部分注解的拓展,同时做了 Mybatis-plus 的兼容处理。 此处做一个简单的使用介绍 @EnableAutoTable @SpringBootApplication public class DemoAutoTableApplication { public static void main(String[] args) { SpringApplication.run(DemoAutoTableApplication.class, args); } } @Data @Table public class MyTable { private Integer id; private String userName; } 上述代码就会自动把 MyTable 映射为my_table表,字段分别是id:int、user_name:varchar(255) PS:具体表名、字段名是否转下划线是根据 MybatisPlus 的配置来的 下面展示一个注解全面的例子: @Data @AutoDefine // 指定表的编码 @MysqlCharset(value = "utf8mb4", collate = "utf8mb4_general_ci") // 指定表的存储引擎 @MysqlEngine("myisam") // 表头同样可以声明单个索引(此处只是举例,等价于username字段上的@Index) @TableIndex(name = "username_index", fields = {MyTableDefine.username}, type = IndexTypeEnum.UNIQUE) // 需要在表头声明多个索引的情况下,需要用@TableIndexes包裹起来 @TableIndexes({ // 声明普通联合索引 @TableIndex(name = "username_phone_index", fields = {MyTableDefine.username, MyTableDefine.phone}), // 声明唯一联合索引,单独指定phone的索引排序方式,构建索引的时候indexFields中字段的顺序权重高于fields中的字段 @TableIndex(name = "username_phone_uni_index", fields = {MyTableDefine.username}, indexFields = {@IndexField(field = MyTableDefine.phone, sort = IndexSortTypeEnum.DESC)}, type = IndexTypeEnum.UNIQUE), }) // 指定表名、表注释、数据源、忽略字段(不参与建表,等效于字段上的@Ignore) @Table(value = "test_table", comment = "测试表", dsName = "my-mysql", excludeProperty={MyTableDefine.extra}) public class MyTable { // 指定主键自增注释、类型(数据库数字类型可以跟java字符串类型相互转化)、长度 // 注意字段名称id会被自动认定为主键不需要再额外指定 @ColumnComment("id主键(因为我是独立注解,所以我是大哥,会覆盖下面的comment属性)") @ColumnId(mode = IdType.AUTO, comment = "id主键", type = MysqlTypeConstant.BIGINT, length = 32) private String id; // 字段非NULL @NotNull // 字段默认值是空字符串 @ColumnDefault(type = DefaultValueEnum.EMPTY_STRING) // 指定字段长度 @ColumnType(length = 100) // 指定字段注释 @ColumnComment("用户名") // 唯一索引 @Index(type = IndexTypeEnum.UNIQUE) private String username; // 设置默认值为0 @ColumnDefault("0") @ColumnComment("年龄") private Integer age; @ColumnType(length = 20) // 设置注释、默认值、不为空 @Column(comment = "电话", defaultValue = "+00 00000000", notNull = true) // 唯一索引快捷方式 @UniqueIndex private String phone; // 设置注释、小数(等同于@ColumnType(length = 12, decimalLength = 6)) @Column(comment = "资产", length = 12, decimalLength = 6) private BigDecimal money; // boolean值设置默认值 @ColumnDefault("true") @Column(comment = "激活状态") // 普通索引:指定索引名称、注释、索引方法 @Index(name = "active_index", comment = "激活状态索引") private Boolean active; // 单独设置字段类型 @ColumnType(MysqlTypeConstant.TEXT) @ColumnComment("个人简介") private String description; // 设置默认值为当前时间 @ColumnDefault("CURRENT_TIMESTAMP") @Column(comment = "注册时间") private LocalDateTime registerTime; // 忽略该字段,不参与建表 @Ignore private String extra; } 四、数据填充 可以在对数据库做插入或更新操作的时候,自动赋值数据操作人、操作时间、默认值等。 以文章发布为例,在发布 Artice 的时候,我们无需再去关心过多的与业务无关的字段值,最终只需要关心 title、content 两个核心数据即可,其他的数据均会被框架处理。 其中分别涉及了数据插入、数据更新、数据插入及更新三个处理时机,其中每个时机均可以插入系统时间及自定义用户信息。 定义文章实体 @Data @Table(comment = "文章") public class Article { // 字符串类型的ID,默认也是雪花算法的一串数字(MP的默认功能) @ColumnComment("主键") private String id; @ColumnComment("标题") private String title; @ColumnComment("内容") private String content; // 默认值用法:文章默认激活状态,ACTIVE为ActicleStatusEnum[ACTIVE, INACTIVE]的枚举名称字符串 @DefaultValue("ACTIVE") @ColumnComment("内容") private ActicleStatusEnum status; @ColumnComment("发布时间") // 【插入】数据时候会自动获取系统当前时间赋值,支持多种数据类型,具体可参考@FillTime注解详细介绍(注意,这里的时间是MP执行insert的操作的时候的时间,并不是对象构建时候的时间) @InsertFillTime private Date publishedTime; @ColumnComment("发布人") // 【插入】的时候,自动填充用户id,UserIdAutoFillHandler看下面代码 @InsertFillData(UserIdAutoFillHandler.class) private String publishedUserId; @ColumnComment("发布人名字") // 【插入】的时候,自动填充用户名字,UsernameAutoFillHandler看下面代码 @InsertFillData(UsernameAutoFillHandler.class) private String publishedUsername; @ColumnComment("最后更新时间") // 【插入和更新】数据时候会自动获取系统当前时间赋值,支持多种数据类型,具体可参考@FillTime注解详细介绍 @InsertUpdateFillTime private Date publishedTime; @ColumnComment("最后更新人") // 【更新】的时候,自动填充用户id,UserIdAutoFillHandler看下面代码 // @UpdateFillData(UserIdAutoFillHandler.class) // 【插入和更新】的时候,自动填充用户id,UserIdAutoFillHandler看下面代码 @InsertUpdateFillData(UserIdAutoFillHandler.class) private String publishedUserId; @ColumnComment("最后更新人名字") // 【更新】的时候,自动填充用户名字,UsernameAutoFillHandler看下面代码 // @UpdateFillData(UsernameAutoFillHandler.class) // 【插入和更新】的时候,自动填充用户名字,UsernameAutoFillHandler看下面代码 @InsertUpdateFillData(UsernameAutoFillHandler.class) private String publishedUsername; } 实现动态填充【用户 id】的接口 /** * 全局获取用户ID * 此处实现IOptionByAutoFillHandler接口和AutoFillHandler接口均可, * 实现IOptionByAutoFillHandler接口,可以兼容框架内的BaseEntity。 * BaseEntity默认需要IOptionByAutoFillHandler的实现。BaseEntity的使用请查看官网。 */ @Component public class UserIdAutoFillHandler implements IOptionByAutoFillHandler<String> { /** * @param object 当前操作的数据对象 * @param clazz 当前操作的数据对象的class * @param field 当前操作的数据对象上的字段 * @retur */ @Override public String getVal(Object object, Class<?> clazz, Field field) { RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes(); HttpServletRequest request = ((ServletRequestAttributes)requestAttributes).getRequest(); // 配合网关或者过滤器,token校验成功后就把用户信息塞到header中 return request.getHeader("user-id"); } } 实现动态填充【用户名】的接口 /** * 全局获取用户名 */ @Component public class UsernameAutoFillHandler implements AutoFillHandler<String> { /** * @param object 当前操作的数据对象 * @param clazz 当前操作的数据对象的class * @param field 当前操作的数据对象上的字段 * @return 当前登录用户id */ @Override public String getVal(Object object, Class<?> clazz, Field field) { RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes(); HttpServletRequest request = ((ServletRequestAttributes)requestAttributes).getRequest(); // 配合网关或者过滤器,token校验成功后就把用户信息塞到header中 return request.getHeader("user-name"); } } 五、关联查询 类似 JPA 的数据关联查询解决方案,替代 sql 中的 join 方式(或者内存组装数据的方式),通过注解关联多表之间的关系,查询某实体的时候,自动带出其关联性的数据。 以用户与文章之间的关系来举例 定义实体 @Data @AutoDefine @Table(comment = "文章") public class Article { @ColumnComment("主键") private String id; @ColumnComment("标题") private String title; @Column(comment = "内容", type = MySqlTypeConstant.MEDIUMTEXT) private String content; @ColumnComment("发布人") private String publishedUserId; @ColumnComment("审核: 0 不通过、1 通过") private int audit; @ColumnComment("发布时间(时间戳)") private Long publishedTime; } @Data @AutoDefine @Table(comment = "用户信息") public class User { @ColumnComment("主键") private String id; @ColumnComment("用户名") private String username; @ColumnComment("密码") private String password; // 关联该用户发布的所有文章("audit = 1" 表示的是Article下的audit为1的情况,customCondition的值只能是被关联表下的字段值,且会以and的形式添加在查询条件末尾。) @BindEntity(conditions = @JoinCondition(selfField = UserDefine.id, joinField = ArticleDefine.publishedUserId), customCondition = "audit = 1", orderBy = @JoinOrderBy(field = ArticleDefine.publishedTime, isAsc = false)) private List<Article> articles; } 需求:获取用户信息的同时只想获取用户已通过审核的发布记录,并且根据发布时间倒序排序。(通过自定义 SQL 条件) 【写法一】 // 获取到需要的user集合 User user = userMapper.getByUsername(name); // 【推荐】用法一、指定属性关联。 Binder.bindOn(user, User::getArticles); // 【不推荐】用法二、全关联。此种用法关联user下所有声明需要绑定的属性。 // Binder.bind(user); 【写法二】 // 本框架拓展的lambda查询器lambdaQueryPlus,增加了bindOne、bindList、bindPage // 显然这是一种更加简便的查询方式,但是如果存在多级深度的关联关系,此种方法就不适用了,还需要借助Binder User user = userRepository.lambdaQueryPlus() .eq(User::getUsername, name) // 【推荐】用法一、指定属性关联,只关联文章这个字段。 .bindList(User::getArticles); // 【不推荐】用法二、全关联。此种用法关联user下所有声明需要绑定的属性。 // .bindList(); * 如果你打开 sql 打印,会看到 2 条 sql 语句,第一条根据 name 去 user 查询信息,第二条根据 userId 去 article 中查询关联的所有数据。 篇幅有限,更多用法(中间表查询、多对多查询等),请移步官方文档 注意: 为了解决数据库兼容支持的问题,关联查询底层原理是基于 MybatisPlus 的 BaseMapper<T> 实现的,所以要求所有关联的实体必须要对应的 Mapper 且继承自 MybatisPlus 的 BaseMapper<T>,包括中间表的实体,在使用中间表关联查询的情况下,也需要遵循此约束。 MPE 相当于把实体对应的 Mapper 视为数据访问窗口了,所以但凡需要从数据库查询数据的行为均需要通过对应的 Mapper 完成。 六、数据冗余 为了避免高频的数据关联查询,一种方案是做数据冗余,将其他表的部分字段冗余到当前表。但是这个方案牵扯一个数据修改后如何同步的问题,本功能就是为了解决这个问题而生的。 假设用户评论的场景,评论上需要冗余用户名和头像,如果用户的名字和头像有改动,则需要同步新的改动,代码如下: @Data @AutoDefine @Table(comment = "用户信息") public class User { @ColumnComment("主键") private String id; @ColumnComment("用户名") private String username; @ColumnComment("头像") private String icon; // 省略其他属性 ...... } @Data @AutoDefine @Table(comment = "评论") public class Comment { @ColumnComment("主键") private String id; @ColumnComment("评论内容") private String content; @ColumnComment("评论人id") private String userId; // source指定了数据来源的Entity,同样可以使用sourceName来指定全路径的方式,field指定了映射哪个字段 // conditions中隐含了一个joinField字段,该字段默认是“id”,即@Condition(selfField = "userId", joinField = "id")等同于示例中的写法 @DataSource(source = User.class, field = UserDefine.username, conditions = @Condition(selfField = UserDefine.userId)) @ColumnComment("评论人名称") private String userName; // 如上,同理 @DataSource(source = User.class, field = UserDefine.icon, condition = @Condition(selfField = UserDefine.userId)) @ColumnComment("评论人头像") private String userIcon; } 基于 @DataSource 注解,框架会自动为指定字段注册监听EntityUpdateEvent事件(MPE 内置事件,可手动发起),所有 MP 的 Mapper 的updateById和updateBatchById两个方法执行的时候会自动发布EntityUpdateEvent事件。如果使用其他数据更新方式(比如手动写 sql 的形式)不会自动触发数据自动更新,如果想触发,需要用户自己抛出EntityUpdateEvent事件,即可完成数据自动更新。 具体用法及讲解,请移步官方文档 七、动态条件 根据预先设置的条件函数,对数据的更新、删除、查询做动态的筛选。常用于数据权限方面。 比如根据不同权限获取不同数据,用户只能看到自己的数据,管理员能看到所有人的数据,我们通常需要在每一个查询、更新、删除的 sql 操作上都追加上某个条件,这种操作比较机械化,而且某些情况下很容易忘记,可以抽象成注解直接配置到 Entity 上,就省去了每个数据操作关心这个特殊条件了。 /** * congfig中注册动态条件拦截器【1.3.0之前的版本(不包括1.3.0)可以忽略,不注册该Bean】 */ @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); // 添加动态条件,若同时添加了其他的拦截器,继续添加即可 interceptor.addInnerInterceptor(new DynamicConditionInterceptor()); // 如果使用了分页,请放在DynamicConditionInterceptor之后 interceptor.addInnerInterceptor(new PaginationInnerInterceptor()); return interceptor; } @Data @Table(comment = "文章") public class Article { @ColumnComment("主键") private String id; @ColumnComment("标题") private String title; @ColumnComment("内容") private String content; @ColumnComment("发布人") // 添加了该注解后,针对文章的查询、修改、删除操作,均会被自动带上 published_user_id=?或者published_user_id in (?)的条件,?值来自于CurrentUserDynamicConditionHandler的values()返回值 @DynamicCondition(CurrentUserDynamicConditionHandler.class) private String publishedUserId; // 省略其他字段 ...... } @Component public class CurrentUserDynamicConditionHandler implements IDynamicConditionHandler { @Resource private HttpServletRequest request; @Override public List<Object> values() { // 只有当enable()返回true的时候 本动态条件才生效。 // 返回空集合或者null的时候,sql上体现的是 [column] is null,只返回一个值的时候sql上体现的是 [column]=***, // 返回集合的时候,sql上体现的是 [column] in (***) String userId = request.getHeader("USER_ID"); return Collections.singletonList(userId); } @Override public boolean enable() { // 简单例子:header中取用户权限,如果是非管理员则执行该过滤条件,如果是管理员默认查全部,返回false,本动态条件失效 String userRule = request.getHeader("USER_ROLE"); return !"ADMIN".equals(userRule); } } 具体用法及讲解,请移步官方文档 八、字段序列化与反序列化 数据存储的时候自动序列化字段上的复杂数据类型为字符串(类 json 格式),数据读取的时候自动反序列化回来,无需额外编写转化的 Handler(MP 官方的方案,需要手动为每一个复杂数据类型指定一个 BaseTypeHandler)。 该方案存在一定的局限性,实际是借鉴了 Redisson 的一种数据序列化方案,将数据本身的特征(类全名称)在序列化的时候,一并记录下来,用于反序列的依据,所以序列化之后的字符串并不是一个标准的 json。这种方案的缺点很明显,就是类的全名称(包名 + 类名)不能随意更改,因为一旦更改,会导致找不到 class 的问题,进而无法正常的反序列化已经存在的数据。 @Data @TableName(autoResultMap = true) // 必须 @Table(comment = "用户") public class Users { @ColumnComment("ID") private Long id; @Serializable // 必须 @ColumnComment("爱好") private List<Like> likes; } @Data public class Like { private String id; private String name; } 感谢 感谢dromara.org开源社区提供的机会 感谢支持的小伙伴 作者介绍 90 年,男,已婚,前后端均有涉猎,毕业后一直在济南这座城市,如果有同地区的小伙伴随时可约 作者开源项目,求各位看官动动发财的小手,给个 star https://gitee.com/dromara/mybatis-plus-ext https://gitee.com/tangzc/auto-table

优秀的个人博客,低调大师

ORM 工具 dbVisitor 5.2.2 发布,faker 造数据能力支持 DSL

介绍 dbVisitor 是一个轻量小巧的数据库开发工具,支持ORM、数据生成工具/数据库性能测试。具有对象映射以及丰富的类型处理。提供动态 SQL、存储过程、 内置分页方言 20+、 支持嵌套事务、多数据源、条件构造器、INSERT 策略、多语句 / 多结果。并兼容 Spring 及 MyBatis 用法。 它不依赖任何其它框架,因此可以很方便的和任意一个框架整合在一起使用。 依赖 <dependency> <groupId>net.hasor</groupId> <artifactId>dbvisitor</artifactId> <version>5.2.2</version> </dependency> 新增​ 新增 @RefMapper 注解可以不用在指定 value 属性,默认使用类的路径和类名充当 xml 路径 新增 处理 PG 数组、Money 两个类型的 TypeHandler 新增 BigDecimal、BigInteger,可以作为 String 方式存储的 TypeHandler 新增 LocalDateTime 可以作为 java.sql.Timestamp 方式存储的 TypeHandler 新增 Faker dbType\customTpcConf 配置,可以自定义 tpc 配置文件 新增 Faker 基于 DSL 的 TypeProcessorFactory 的实现,原有的 mysql/pg/oracle/sqlserver 实现全部替换为 DSL 方式 优化​ 优化 XmlTableMappingResolve 减少异常堆栈层数 优化 依赖 cobble 升级到 4.5.3、ognl 升级到 3.3.4 优化 TypeHandler 类命,名称按照新的命名规范进行调整 修复​ 修复 META-INF/custom.keywords 加载只能识别到一个的问题 相关链接 官方网站:https://www.dbvisitor.net/ 源码地址:https://gitee.com/zycgit/dbvisitor Spring Boot 整合手册,https://www.dbvisitor.net/docs/integration/with-springboot Faker介绍:https://www.dbvisitor.net/faker

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

用户登录
用户注册