跳转到主内容
websoft网络软件专家 - 深耕网络技术,打造实用软件!

如何在Java中编写自定义的MyBatis插件_Interceptor拦截SQL执行并实现自动分页

MyBatis插件必须实现Interceptor接口,仅能拦截Executor、StatementHandler、ParameterHandler、ResultSetHandler四大对象;优先选StatementHandler修改SQL,用MetaObject而非字符串拼接;需Plugin.wrap注册,XML中写全限定名;分页参数须通过ThreadLocal传递,Executor拦截适合统计总数,SQL重写需按数据库方言分支处理。 MyBatis插件必须实现
Interceptor
接口,且只能拦截四大对象之一 MyBatis插件不是随便拦SQL的——它底层靠JDK动态代理,只允许对
Executor
、
StatementHandler
、
ParameterHandler
、
ResultSetHandler
这四个对象生效。想改SQL或加
LIMIT
,必须选
StatementHandler
(拼SQL前)或
Executor
(执行前)。选错对象,插件根本不会触发。 常见错误是直接在
intercept()
里解析
boundSql.getSql()
然后字符串拼
" LIMIT ?,?"
——这不行,因为原始SQL可能带
ORDER BY
、子查询、甚至分号,硬拼会语法报错。 优先拦截
StatementHandler
,用
MetaObject
修改
boundSql
内部字段,而不是字符串替换 必须调用
Plugin.wrap(target, this)
完成代理包装,否则Spring Boot里插件不注册 XML中
配置的
interceptor
类名要写全限定名,少一个包名就静默失效 分页参数不能从方法签名硬取,得靠
ThreadLocal
或上下文传递 MyBatis插件拿不到Mapper接口的方法参数,
invocation.getArgs()
里只有
MappedStatement
和
Object
(后者可能是
null
)。所以
PageHelper.startPage(1, 10)
这类调用,本质是把分页信息塞进
ThreadLocal
,插件里再取出来。 自己写时别学网上“从
args[1]
强转Map取pageNo”——那是特定封装框架的hack,原生MyBatis没这约定。 立即学习 “ Java免费学习笔记(深入) ”; 定义
private static final ThreadLocal PAGE_HOLDER = new ThreadLocal<>()
业务代码调用前手动
PAGE_HOLDER.set(new Page(1, 20))
,插件
intercept()
里取,用完
remove()
防内存泄漏 如果用Spring,注意事务方法内多线程调用会导致
ThreadLocal
丢失,得换
RequestContextHolder
或显式传参
Executor
拦截比
StatementHandler
更安全,但要注意
query
方法重载 拦截
Executor
的
query(MappedStatement, Object, RowBounds, ResultHandler)
看起来简单,但MyBatis 3.4+新增了带
ResultHandler
的重载方法,漏掉一个就会跳过插件。而且
RowBounds
默认是
RowBounds.DEFAULT
(
offset=0, limit=0
),不代表用户没分页需求——很多场景下业务压根没传
RowBounds
,全靠插件自动补。 Eclipse导入Android或其他的JAVA项目的正确方法 WORD版 本文档主要讲述的是Eclipse导入Android或其他的JAVA项目的正确方法;希望本文档会给有需要的朋友带来帮助;感兴趣的朋友可以过来看看 下载 真正要改SQL,还是得穿透到
StatementHandler
,但可以在
Executor
里先判断是否需要分页(比如检查
ThreadLocal
非空 +
mappedStatement.getSqlCommandType() == SqlCommandType.SELECT
)。 用
Plugin.signature()
声明多个
@Signature
,覆盖所有
query
重载 不要在
Executor
里直接改
boundSql
,因为
boundSql
在
StatementHandler
创建,此时还没生成 拦截
Executor
适合做统计总数(
count(*)
自动追加),但改SQL必须进
StatementHandler
MySQL的
LIMIT
和Oracle的
ROWNUM
不能共用一套SQL模板 写个通用分页插件时最容易翻车:用
String.format("SELECT * FROM (%s) t LIMIT %d, %d", originSql, offset, size)
应付所有数据库。这在Oracle、SQL Server、PostgreSQL上全错——Oracle要
ROWNUM
嵌套,SQL Server要
OFFSET-FETCH
,PostgreSQL虽支持
LIMIT
但偏移量语义和MySQL不同。 MyBatis本身不解决方言问题,
DatabaseIdProvider
只能帮你区分数据库类型,具体SQL构造还得自己分支处理。 在插件里通过
configuration.getDatabaseId()
判断,分支写
if ("mysql".equals(dbId))
、
else if ("oracle".equals(dbId))
Oracle分页必须两层嵌套:
SELECT * FROM (SELECT ROWNUM rn, t1.* FROM (originSql) t1 WHERE ROWNUM <= ?) WHERE rn > ?
别信“用
limit
兼容所有库”的博客,那是MySQL专用写法,其他库运行时报
ORA-00933
或
SQLServerException
分页插件最难的从来不是拦截,而是SQL重写时对目标数据库语法边界的准确把握——少一个括号、错一个别名、漏一个
WHERE ROWNUM <=
条件,查出来的数据就少一半或者全乱序。

相关文章