物化视图、索引设计与报表性能优化

物化视图、索引设计与报表性能优化 1. 对应简历段落 这篇文章对应简历中“负责核心业务系统报表 SQL 优化,参与 Oracle 物化视图、索引和统计口径迁移改造,在 OceanBase 环境下通过预聚合、索引设计和异步任务提升报表性能”等经历。 面试官问报表优化,通常不满足于“加索引后快了”。他会继续问:报表为什么慢?是明细数据太大、join 太多、聚合太重、日期条件不走索引,还是统计口径适合预计算?物化视图迁移到目标库后怎么替代?索引怎么兼顾查询和写入?日报、月报、实时看板分别怎么设计? 成熟回答要体现“按时效性和成本选方案”:实时列表优先靠索引和 SQL 改写;准实时看板可以增量汇总;T+1 报表适合批处理预聚合;复杂多维分析则考虑宽表、汇总表或数仓,而不是把所有压力压在 OLTP 主库上。 2. 业务背景 老系统中有大量经营分析和管理报表,比如机构保费日报、客户转化漏斗、活动效果统计、代理人业绩排名、产品销售分布、续保提醒明细。这些报表通常要关联客户、保单、机构、人员、活动、产品等多张表。 Oracle 时代可能使用物化视图承载部分预计算: CREATE MATERIALIZED VIEW mv_org_daily_premium REFRESH COMPLETE ON DEMAND AS SELECT org_id, TRUNC(create_time) AS biz_date, SUM(premium) AS total_premium, COUNT(*) AS policy_count FROM policy_order WHERE status = 'PAID' GROUP BY org_id, TRUNC(create_time); 迁移到 OceanBase 后,物化视图能力、刷新机制和兼容性要重新评估。即使目标库支持类似能力,也不能盲目照搬,因为报表的瓶颈不一定是“没有物化视图”,可能是统计口径混乱、索引设计不合理、实时性要求过高,或者 OLTP 与报表负载混在一起。 业务上,报表有不同实时性要求。销售首页可能希望接近实时;管理层日报通常 T+1 可以接受;月度结算要求准确但不一定秒级;导出明细可以异步。优化方案必须围绕这些要求设计。 3. 核心原理 报表 SQL 慢的原因主要有四类。 第一,扫描数据多。报表经常按月、季度、机构范围统计,涉及百万到千万级明细。没有有效分区或索引时,只能大范围扫描。 第二,聚合成本高。GROUP BY、COUNT DISTINCT、多维度统计会产生排序、哈希聚合和临时结果。 第三,join 链路长。客户、机构、人员、产品、活动维表都要关联,任何一个 join 条件缺索引都会放大中间结果。 第四,实时性和准确性要求冲突。越实时,越倾向查明细;越快,越倾向预聚合。二者需要按场景取舍。 物化视图的本质是把查询结果提前计算并存储起来。它适合读多写少、口径稳定、刷新窗口可控的报表。缺点是刷新成本、数据延迟、存储成本和口径变更成本。 索引适合加速选择性较高、访问模式稳定的查询。报表索引设计常见思路是: ...

April 6, 2025 · 2 min · 385 words · WY

OceanBase 执行计划与慢 SQL 优化

OceanBase 执行计划与慢 SQL 优化 1. 对应简历段落 这篇文章对应简历中“Oracle 到 OceanBase 迁移后负责慢 SQL 治理、执行计划分析、索引优化、分页与报表性能回归、上线后稳定性保障”等经历。 面试里讲慢 SQL 优化,不能停留在“加索引”。资深面试官会问:你怎么发现慢 SQL?怎么看执行计划?如何判断是全表扫描、回表、排序、临时表、分布式执行、锁等待还是租户资源不足?OceanBase 与单机 MySQL、Oracle 的差异是什么?优化后如何验证没有改变业务结果? 推荐把经历讲成一套闭环:采集慢 SQL 和业务入口,定位执行计划和资源消耗,结合 SQL 语义、索引、统计信息、租户资源和数据分布做改造,最后用压测、双跑和生产观测验证。 2. 业务背景 信创迁移完成后,应用从 Oracle 切到 OceanBase。语法兼容只是第一关,真正上线后最容易暴露的是性能问题。老系统中有大量列表页、报表页、批处理和权限范围查询。迁移前 Oracle 上可能依赖历史优化器、索引、统计信息和硬件资源,迁移后执行计划变化,原来可接受的 SQL 可能变慢。 典型问题包括: 1. ROWNUM 改 LIMIT 后,深分页扫描大量数据。 2. TRUNC(create_time) 改写不彻底,日期字段索引失效。 3. CONNECT BY 改递归 CTE 后,组织树结果再 join 大表变慢。 4. 报表 SQL 复杂聚合,临时表和排序消耗高。 5. 迁移后统计信息不准确,优化器选择了错误索引。 6. OceanBase 租户 CPU、内存、并发资源限制导致抖动。 业务侧感知通常是“页面打开慢”“导出超时”“晚上批处理跑不完”“数据库连接池占满”。因此慢 SQL 优化不能只在数据库里看单条 SQL,而要把业务入口、调用频率、用户等待时间和系统资源一起看。 3. 核心原理 执行计划是数据库对 SQL 的执行路径描述。看计划时重点关注几个问题。 ...

April 5, 2025 · 2 min · 394 words · WY

CONNECT BY 到递归 CTE:树查询迁移详解

CONNECT BY 到递归 CTE:树查询迁移详解 1. 对应简历段落 这篇文章对应简历中“参与 Oracle 到 OceanBase 信创迁移,负责组织架构、客户归属、销售团队等树形查询从 CONNECT BY 到递归 CTE 的改造,并处理层级、排序、环路和性能问题”等经历。 面试里讲树查询迁移,不能只背一段 WITH RECURSIVE 模板。资深面试官会继续问:CONNECT BY PRIOR 的方向怎么判断?LEVEL、SYS_CONNECT_BY_PATH、CONNECT_BY_ROOT 怎么替代?ORDER SIBLINGS BY 怎么保持?如果数据有环怎么办?递归 CTE 在大组织树上会不会慢?这些问题才是项目经验的分水岭。 可以把这段经历定位为:我负责把历史系统中强依赖 Oracle 层级查询的 SQL 梳理出来,按“向下查子树、向上查父链、查根节点、查路径、查叶子节点、按兄弟排序展示”分类,然后分别用递归 CTE、路径字段、闭包表或应用层缓存改造,并通过结果对比工具校验节点集合、层级、路径和排序。 2. 业务背景 保险、银行、销售管理类系统里树形数据非常多。典型例子包括销售机构树、部门树、代理人团队树、客户归属树、产品分类树、地区行政区划树、菜单权限树等。老 Oracle 系统里这类查询常用 CONNECT BY: SELECT org_id, parent_org_id, org_name, LEVEL AS org_level FROM sales_org START WITH org_id = :rootOrgId CONNECT BY PRIOR org_id = parent_org_id; 业务页面可能依赖它展示“某个机构下所有子机构”,权限系统可能依赖它判断“当前用户能看哪些下级机构”,报表统计可能依赖它聚合“某分公司及所有下级机构业绩”。一旦树查询迁移出错,影响面非常大:用户看不到数据、看到越权数据、统计口径变小或变大、前端树顺序混乱。 迁移到 OceanBase MySQL 模式后,Oracle 的 CONNECT BY 不能直接使用,通常要改成递归 CTE: WITH RECURSIVE org_tree AS ( SELECT org_id, parent_org_id, org_name, 1 AS org_level FROM sales_org WHERE org_id = :rootOrgId UNION ALL SELECT c.org_id, c.parent_org_id, c.org_name, p.org_level + 1 FROM sales_org c JOIN org_tree p ON c.parent_org_id = p.org_id ) SELECT * FROM org_tree; 但树查询迁移的真实难点在于历史 SQL 往往使用了 Oracle 的一整套层级查询能力,不只是查子节点。比如用 LEVEL 控制层级,用 CONNECT_BY_ISLEAF 判断叶子,用 SYS_CONNECT_BY_PATH 拼路径,用 ORDER SIBLINGS BY 控制兄弟节点排序,用 CONNECT_BY_ROOT 取根机构,用 NOCYCLE 防止环。 ...

April 4, 2025 · 4 min · 677 words · WY

ROWNUM 到 LIMIT:分页语义差异与改造方式

ROWNUM 到 LIMIT:分页语义差异与改造方式 1. 对应简历段落 这篇文章对应简历中“负责 Oracle 到 OceanBase 信创迁移中的分页 SQL 改造、MyBatis 方言适配、列表页性能优化和新旧结果一致性验证”等经历。 面试中如果只说“把 ROWNUM 改成 LIMIT”,会显得很浅。资深 Java 面试官真正关心的是:你是否理解 Oracle ROWNUM 的生成时机,是否知道 ORDER BY 和分页截断的先后关系,是否处理了深分页性能问题,是否改造了框架层分页插件,以及是否验证了翻页结果不会重复、遗漏、乱序。 推荐把这段经历讲成一个完整闭环:我先扫描 Mapper 中所有 ROWNUM 分页 SQL,按“简单列表、嵌套排序、报表聚合、动态排序、深分页”分层;低风险 SQL 由分页拦截器统一生成 OceanBase MySQL 模式的 LIMIT;高风险 SQL 手工改造并补充稳定排序键;最后用新旧库双跑工具对同一查询条件下的总数、每页主键序列和边界页结果做对比。 2. 业务背景 老系统以 Oracle 为核心数据库,Java 应用层主要使用 Spring、MyBatis 和自研分页组件。大量客户列表、保单列表、活动名单、销售人员绩效列表、报表明细页都采用 Oracle 经典分页写法: SELECT * FROM ( SELECT t.*, ROWNUM rn FROM ( SELECT * FROM policy_order WHERE status = :status ORDER BY create_time DESC ) t WHERE ROWNUM <= :endRow ) WHERE rn > :startRow; 迁移到 OceanBase MySQL 模式后,自然会想到改成: ...

April 3, 2025 · 3 min · 603 words · WY

Oracle 空字符串、函数、日期、序列兼容问题

Oracle 空字符串、函数、日期、序列兼容问题 1. 对应简历段落 这篇文章对应简历中“负责 Oracle 到 OceanBase 信创迁移中的 SQL 兼容性治理,处理空字符串、常用函数、日期条件、序列主键和 Java 参数绑定差异”等经历。 面试官问这类问题,通常不是想听一张函数替换表,而是想确认你是否知道“语法能跑”和“业务语义一致”之间的差距。Oracle 中空字符串等同 NULL,日期函数和隐式转换很宽松,Sequence 使用方式深入业务代码和存储过程。迁移到 OceanBase MySQL 模式后,如果不系统治理,结果可能出现看似偶发的错账、漏数、查不到、重复主键或索引失效。 比较成熟的表达是:我把兼容问题分为数据语义、SQL 函数、日期范围、主键生成四类。先通过扫描和样本数据识别风险字段,再建立函数映射和改写规范;日期条件尽量从“字段套函数”改成“范围查询”;序列则按业务主键策略统一到数据库兼容序列、应用号段或分布式 ID,并在双写阶段避免冲突。 2. 业务背景 老系统长期运行在 Oracle 上,很多代码默认接受 Oracle 的行为。比如用户未填写手机号,Oracle 表中可能存的是 NULL,也可能是应用传入的空字符串 '',但在 Oracle 里二者通常都会按 NULL 表现。SQL 中常见: WHERE mobile IS NULL 迁移到 OceanBase MySQL 模式后,NULL 和 '' 是两个不同值。原来能查出的“未填写手机号客户”,迁移后可能少一部分。 函数差异也非常常见。Oracle SQL 里大量使用 NVL、DECODE、TO_DATE、TO_CHAR、TRUNC、SYSDATE、ADD_MONTHS、MONTHS_BETWEEN、SUBSTR、INSTR、|| 字符串拼接。这些函数有的在目标库中没有,有的名字类似但语义细节不同。 日期问题尤其危险。很多历史 SQL 写成: WHERE TRUNC(create_time) = TO_DATE(:bizDate, 'yyyy-mm-dd') 这在 Oracle 中很常见,但迁移后如果改成: WHERE DATE(create_time) = :bizDate 虽然结果可能一致,却会让 create_time 上的索引失效,导致生产慢 SQL。 序列问题也很典型。Oracle 中常用: ...

April 2, 2025 · 3 min · 574 words · WY

Oracle 到 OceanBase 信创迁移整体方法论

Oracle 到 OceanBase 信创迁移整体方法论 1. 对应简历段落 这篇文章对应简历中“参与核心业务系统信创改造,负责 Oracle 到 OceanBase 迁移、SQL 兼容性改造、存储过程与定时任务 Java 化、分页与树查询改造、新旧 SQL 结果对比工具建设、慢 SQL 优化和上线验证”等经历。 面试里这段经历通常会被追问得很细。因为它不是简单“把数据库换一下”,而是同时涉及数据库语法、执行计划、事务一致性、批处理调度、MyBatis 插件、数据校验、灰度上线和生产性能治理。真正能讲清楚的人,需要说明三个层次: 第一,为什么要迁移。也就是信创背景、国产数据库替换、运维标准化、成本和风险控制。 第二,怎么迁移。包括迁移评估、对象梳理、SQL 扫描、兼容性改造、数据同步、双跑验证、灰度切换和回退预案。 第三,迁移后怎么稳定运行。包括慢 SQL 优化、OceanBase 执行计划分析、索引调整、分页策略改造、批任务改造、监控告警和问题复盘。 如果面试官问“你在 Oracle 到 OceanBase 迁移中具体做了什么”,不能只回答“改了 SQL”。更好的表达是:我负责应用侧和数据库侧之间的兼容性治理,把 Oracle 方言、存储过程、定时任务和慢 SQL 按风险分层处理,并通过工具化手段做新旧结果对比和性能回归,最终让迁移不是一次性人工改造,而是一套可重复执行的方法论。 2. 业务背景 业务系统可以理解为保险销售、客户经营和交易管理类的核心系统。系统历史比较长,早期以 Oracle 为主,Java 应用层使用 Spring、MyBatis,部分复杂逻辑沉淀在数据库里,比如存储过程、函数、定时作业、报表 SQL、客户层级查询和月底批处理。 这种系统的典型特点是“业务稳定但技术债重”: SQL 数量多,很多 SQL 写法强依赖 Oracle。 报表查询复杂,存在大量嵌套子查询、ROWNUM 分页、NVL、DECODE、TO_DATE、TRUNC 等函数。 组织架构、客户层级、代理人团队等数据经常使用 CONNECT BY PRIOR 做树查询。 批处理逻辑有一部分在 OGG、存储过程、数据库 Job 或调度脚本里,应用侧对这些逻辑感知不足。 空字符串和 NULL 在 Oracle 中语义特殊,迁移后可能影响查询条件、唯一约束、字段默认值和 Java 判空逻辑。 信创迁移的目标不是单纯追求“数据库国产化”这个动作,而是在不影响现有业务连续性的前提下,把底层数据库从 Oracle 迁移到 OceanBase,并尽量减少对上层业务的扰动。对于保险销售系统来说,迁移窗口通常非常敏感:白天有出单、保全、客户跟进、活动转化,晚上有批处理、报表和对账。任何一个 SQL 结果不一致、批任务漏跑或慢查询放大,都可能影响业务人员第二天的使用。 ...

April 1, 2025 · 6 min · 1151 words · WY

WebLogic到Undertow:容器迁移中的兼容性问题

WebLogic到Undertow:容器迁移中的兼容性问题 1. 对应简历段落 这篇文章对应简历中“将部署在 WebLogic 的 Spring3 + MyBatis 遗留应用迁移到 Spring Boot + Undertow 内嵌容器”的项目经历。简历可以写成: 负责核心保险业务系统从 WebLogic WAR 部署向 Spring Boot + Undertow 可执行 JAR 部署迁移,完成 JNDI 数据源替换、类加载冲突治理、Servlet 过滤器顺序适配、Session 与编码兼容、连接池和线程池参数重建,并通过灰度切流、日志回放和压测保障迁移期间交易链路稳定。 面试官看到这段,通常不会满足于“换了一个容器”。真正的追问会集中在:WebLogic 和 Undertow 的运行模型有什么差异?原来依赖容器提供的能力迁移后由谁承担?为什么上线后可能出现 NoSuchMethodError、乱码、上传失败、事务行为变化、连接池耗尽?如果生产上出现接口偶发 500,你如何判断是容器兼容问题还是业务代码问题? 回答这类问题时,要把容器迁移讲成一次“运行时契约重建”。WebLogic 是完整 Java EE 应用服务器,承担了连接池、JNDI、安全域、部署描述符、类加载隔离、线程池、Session 管理等职责。Undertow 是轻量级 Servlet 容器,优势是启动快、内嵌部署简单、适合 Spring Boot 服务化,但它不会自动继承 WebLogic 的历史配置。迁移的难点不是启动一个 Boot 应用,而是把过去藏在服务器控制台、部署描述符和运维脚本里的隐性配置全部显式化、版本化、可验证。 2. 业务背景 保险公司很多核心系统早期采用 WebLogic 部署,原因很现实:当年 Java EE 体系成熟,WebLogic 提供控制台、集群、JNDI、连接池、事务、安全域、监控和热部署,适合支撑大型企业应用。一个传统系统可能以 WAR 包部署,内部包含销售出单、保费试算、客户查询、保单状态同步、回访工单、运营报表等模块。数据库连接池在 WebLogic 控制台配置,应用通过 JNDI 名称获取;上下文路径、Session 超时、错误页和资源引用写在 web.xml、weblogic.xml 或运维文档中;部分公共 jar 放在 domain lib 或 server lib 下。 ...

March 7, 2025 · 3 min · 460 words · WY

Quartz扩展与XXL-Job迁移实践

Quartz扩展与XXL-Job迁移实践 1. 对应简历段落 这篇文章对应简历中“遗留 Quartz 定时任务治理与 XXL-Job 平台化迁移”的项目经历。简历可以写成: 负责保险核心系统定时任务治理,将应用内 Quartz 任务逐步迁移至 XXL-Job,完成任务资产盘点、幂等改造、分片执行、失败重试、执行日志、阻塞策略和灰度切换,解决集群环境下任务重复执行、不可观测和补偿困难问题。 面试官会追问:Quartz 和 XXL-Job 的定位差异是什么?为什么要迁移?Quartz 集群如何防止重复执行?XXL-Job 的调度中心和执行器如何交互?迁移时如何保证任务不漏跑、不重复?失败重试和幂等怎么设计?日终任务、补偿任务、报表任务、状态同步任务有什么不同策略? 回答这类问题要注意,不要把 XXL-Job 说成“更高级的定时器”。Quartz 是优秀的调度框架,适合嵌入应用;XXL-Job 更偏任务调度平台,提供控制台、执行日志、手动触发、路由策略、分片、失败重试和告警。迁移的本质是把散落在应用内的任务调度能力平台化、可观测化、可运维化。 2. 业务背景 保险系统里的定时任务很多,而且重要程度不低。常见任务包括:保单状态同步、支付状态补偿、核保超时查询、短信失败重发、客户标签刷新、佣金结算、日终报表、渠道数据同步、文件对账、风险名单更新、临期保单提醒、回访任务生成等。这些任务有的分钟级执行,有的日终批处理,有的依赖外部文件,有的需要分机构并发,有的不能并发执行。 老系统通常把 Quartz 嵌入应用。单机时代问题不大,应用启动时 Scheduler 启动,按 cron 执行 Job。进入集群后问题开始出现:如果没有 Quartz 集群配置,多实例会重复执行同一任务;如果用了数据库锁,配置和表结构又需要维护;任务执行日志散在应用日志里,运营无法手动触发;任务失败后是否重试不清楚;任务参数修改需要发版;某个任务卡住会影响应用线程;业务方问“昨天状态同步跑没跑完”,开发只能翻日志。 迁移到 XXL-Job 的目标是让任务统一调度、统一查看、统一告警、可手动补偿、可分片扩展。对保险业务来说,这能显著提升问题处理效率。比如支付状态补偿失败,运营可以看到失败原因并触发重跑;日终报表耗时过长,可以按机构分片;核保超时查询可以设置失败重试;任务切换期间可以通过调度开关避免 Quartz 和 XXL-Job 同时执行。 3. 核心原理 Quartz 的核心概念是 Scheduler、Job、JobDetail、Trigger 和 JobStore。Scheduler 负责调度,Job 是执行逻辑,Trigger 描述执行时间,JobStore 保存任务和触发器。Quartz 可以使用 RAMJobStore,也可以使用 JDBC JobStore 支持集群。集群模式下多个节点共享数据库表,通过锁和 trigger 状态协调执行,避免同一 trigger 被多个节点同时获取。 Quartz 优点是成熟、灵活、可嵌入、表达能力强,适合应用内部复杂调度。但它的运维界面、执行日志、手动触发、告警、分片等能力通常需要团队自己扩展。遗留系统中常见做法是封装一个任务基类,记录开始结束日志,防止并发执行,再加数据库任务锁。 XXL-Job 的核心是调度中心加执行器。调度中心负责管理任务、触发时间、路由策略、执行日志和告警;执行器嵌入业务应用,注册到调度中心,接收调度请求并执行具体 handler。它把调度控制面从业务应用中抽离出来,使任务开关、参数、日志和失败重试可以在平台上管理。 XXL-Job 常用能力包括路由策略、阻塞处理、失败重试、分片广播、任务参数、执行日志和手动触发。路由策略决定选择哪个执行器实例;阻塞策略决定上一次未结束时新调度如何处理;分片广播可以让多个执行器实例分别处理不同分片;失败重试由调度中心触发,但业务侧必须保证幂等。 ...

March 6, 2025 · 2 min · 328 words · WY

AOP加SpEL实现动态数据权限

AOP加SpEL实现动态数据权限 1. 对应简历段落 这篇文章对应简历中“基于 Spring AOP + SpEL 实现动态数据权限控制,统一处理机构、渠道、角色和业务归属范围”的项目经历。简历可以写成: 设计动态数据权限框架,基于自定义注解、Spring AOP 和 SpEL 表达式解析用户上下文与业务参数,生成机构、渠道、岗位、客户经理等维度的数据范围,并与 MyBatis 查询条件和审计日志集成,解决保险运营后台多角色数据隔离问题。 面试官通常会追问:为什么不用硬编码 if else?AOP 拦截在哪一层?SpEL 怎么取方法参数?权限上下文如何传递到 MyBatis?如何避免越权?如何处理管理员、机构负责人、普通坐席、渠道人员的不同范围?权限表达式会不会有性能问题?如果一个接口查列表和导出都要控制,如何保证一致? 回答时要强调:数据权限不是简单的菜单权限。菜单权限决定“能不能访问功能”,数据权限决定“能看到哪些数据”。在保险业务里,同一个保单列表,不同角色看到的数据范围完全不同,且范围可能由登录人机构、岗位、渠道、保单归属、客户经理、产品线共同决定。动态数据权限的价值,是把这些规则从业务 SQL 和 Controller 中抽离出来,形成可复用、可审计、可测试的横切能力。 2. 业务背景 保险运营系统中,数据权限是非常典型的复杂场景。总部运营可以查看全国数据;分公司经理只能查看本机构及下级机构;渠道经理只能看自己负责渠道的保单;客服坐席只能看分配给自己的回访工单;代理人只能看自己名下客户;风控人员可能按产品线或风险等级查看;外包团队还要屏蔽敏感字段。 如果每个查询都在 SQL 中手写权限条件,系统很快会失控。第一,规则重复。客户列表、保单列表、订单列表、导出接口、统计接口都要写类似机构条件。第二,容易漏控。新接口上线时开发者可能忘记追加权限条件,造成越权。第三,难维护。组织架构、角色模型、渠道关系变化后,需要改大量 SQL。第四,难审计。安全团队很难确认某个接口到底应用了哪些数据范围。第五,测试成本高。不同角色、机构、渠道组合多,靠人工点页面容易漏。 因此项目中引入 AOP + SpEL。AOP 负责在业务方法执行前统一识别需要数据权限的场景;注解描述权限类型、参数来源和策略;SpEL 从方法参数、登录上下文或业务对象中动态取值;权限服务根据规则计算数据范围;最终通过 MyBatis 参数、ThreadLocal 上下文或插件注入 SQL 条件。这样,业务方法保持清晰,权限规则集中治理。 3. 核心原理 Spring AOP 基于代理实现,适合拦截 Spring Bean 的方法调用。数据权限通常放在 Service 层或 Mapper 层之前。放在 Controller 层可以拿到请求信息,但容易和业务参数转换耦合;放在 Mapper 层接近 SQL,但缺少业务语义;放在 Service 层通常较平衡,因为它既有业务参数,又能覆盖列表、详情、导出和统计等入口。 自定义注解用于描述权限需求。例如 @DataScope(resource = "policy", key = "#query.orgCode", mode = ORG_AND_CHANNEL) 表示当前方法查询保单资源,机构参数从 query.orgCode 取,权限模式是机构加渠道。注解不要承载过多业务逻辑,否则会变成另一套难懂的 DSL。复杂规则应该由权限服务实现。 ...

March 5, 2025 · 2 min · 404 words · WY

MyBatis拦截器实现分页SQL自动转换

MyBatis拦截器实现分页SQL自动转换 1. 对应简历段落 这篇文章对应简历中“基于 MyBatis 插件机制实现统一分页,减少业务 XML 重复分页 SQL,兼容 Oracle/MySQL 方言”的项目经历。简历可以写成: 设计并落地 MyBatis 分页拦截器,基于 MappedStatement 和 BoundSql 自动识别分页参数,按数据库方言改写分页 SQL 和 count SQL,统一处理排序白名单、参数绑定、插件顺序和慢 SQL 记录,降低遗留系统分页 XML 维护成本。 面试官会继续追问:为什么不用每个 XML 自己写分页?插件拦截 Executor.query 还是 StatementHandler.prepare?如何生成 count SQL?复杂 SQL、order by、group by、union 怎么处理?如何避免 SQL 注入?如何保证数据权限插件和分页插件顺序正确?分页参数从哪里来?如果查询很慢,是 count 慢还是数据页慢? 这类项目的重点不是“我会写拦截器”,而是你能不能把 SQL 改写这件事做成工程上可控的能力。分页插件一旦写错,影响的是所有列表查询;如果没有方言、白名单、测试和降级策略,很容易把一个便利功能变成生产风险。 2. 业务背景 保险运营系统中列表查询非常多,例如客户列表、保单列表、订单列表、回访工单、渠道人员、产品配置、佣金结算、批处理记录、异常流水等。早期 MyBatis XML 通常每个查询自己写分页:Oracle 使用双层 ROWNUM,MySQL 使用 limit offset,size,有些还要额外写一条 count SQL。随着业务迭代,问题越来越明显。 第一,重复代码多。每个列表都要写分页包装和 count,开发效率低,且容易出现排序和条件不一致。第二,数据库方言耦合。老系统如果从 Oracle 扩展到 MySQL,或者测试环境和生产环境数据库不同,XML 中硬编码方言会带来迁移成本。第三,权限条件难统一。机构、渠道、数据范围等条件如果散落在每个 XML 中,既容易漏,也难以审计。第四,分页性能参差不齐。有些查询先查全量再内存分页,有些 count 带复杂排序,有些深分页没有优化。第五,接口风格不统一。不同模块分页参数命名、页码从 0 还是 1 开始、最大页大小限制都不一致。 ...

March 4, 2025 · 4 min · 652 words · WY