Mybatis 之 插件
插件编写(plugin)
步骤:
1. 编写Interceptor的实现类:
1 | MyFirstPlug |
2. 使用@Intercepts注解完成插件签名
3. 将写好的插件注册到全局配置文件中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | package com.feng.config; import org.apache.ibatis.executor.statement.StatementHandler; import org.apache.ibatis.plugin.*; import java.util.Properties; /** * @Desc:完成插件签名: * 告诉mybatis当前插件用来拦截哪个对象的哪个方法 * @Param: Sinature type:拦截哪个对象(四大对象) * method:对象的哪个方法 * args: 当前方法的参数列表 */ @Intercepts ({ @Signature (type = StatementHandler. class , method = "parameterize" , args = java.sql.Statement. class ) }) public class MyFirstPlug implements Interceptor { /** * 拦截目标对象的目标方法的执行 * @param invocation * @return * @throws Throwable */ @Override public Object intercept(Invocation invocation) throws Throwable { //TODO ... 动态修改mybatis运行流程;比如sql分页、修改sql使用的参数。。。 //执行目标方法: parameterrize Object proceed = invocation.proceed(); //返回执行后的返回值 //TODO ... return proceed; } /** * 包装目标对象,为目标对象创建一个代理对象 * @param target * @return */ @Override public Object plugin(Object target) { //借助Plugin的wrap方法来使用当前Interceptor包装目标对象 Object wrap = Plugin.wrap(target, this ); //返回为当前target创建的动态代理对象 return wrap; } /** * 将插件注册时的property属性设置进来 * @param properties */ @Override public void setProperties(Properties properties) { System.out.println( "插件配置信息:" +properties); String username = (String) properties.get( "username" ); String password = (String) properties.get( "password" ); } } |
mybatis-config.xml
1 2 3 4 5 6 7 8 9 | <configuration> <!-- 配置全局插件 --> <plugins> <plugin interceptor= "com.feng.config.MyFirstPlug" > <property name= "username" value= "zhangsan" /> <property name= "password" value= "123456" /> </plugin> </plugins> </configuration> |
插件扩展:
插件拦截的场景:
1 | 场景一:修改sql使用的参数 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | package com.feng.config; import org.apache.ibatis.executor.statement.StatementHandler; import org.apache.ibatis.plugin.*; import org.apache.ibatis.reflection.MetaObject; import org.apache.ibatis.reflection.SystemMetaObject; import java.util.Properties; /** * @Desc:完成插件签名: * 告诉mybatis当前插件用来拦截哪个对象的哪个方法 * @Param: Sinature type:拦截哪个对象(四大对象) * method:对象的哪个方法 * args: 当前方法的参数列表 */ @Intercepts ({ @Signature (type = StatementHandler. class , method = "parameterize" , args = java.sql.Statement. class ) }) public class MyFirstPlug implements Interceptor { /** * 拦截目标对象的目标方法的执行 * @param invocation * @return * @throws Throwable */ @Override public Object intercept(Invocation invocation) throws Throwable { System.out.println( "intercept:" +invocation.getTarget()); //动态的改变一下sql的参数: Object target = invocation.getTarget(); System.out.println( "当前拦截的对象:" +target); //拿到:StatementHandler ==>ParameterHandler ==>parameterObject //拿到target的元数据 MetaObject metaObject = SystemMetaObject.forObject(target); Object value = metaObject.getValue( "parameterHandler.parameterObject" ); System.out.println( "sql语句的参数:" +value); //修改sql语句要用的参数 metaObject.setValue( "parameterHandler.parameterObject" , 12 ); //返回执行后的返回值 Object proceed = invocation.proceed(); return proceed; } /** * 包装目标对象,为目标对象创建一个代理对象 * @param target * @return */ @Override public Object plugin(Object target) { //借助Plugin的wrap方法来使用当前Interceptor包装目标对象 Object wrap = Plugin.wrap(target, this ); //返回为当前target创建的动态代理对象 return wrap; } /** * 将插件注册时的property属性设置进来 * @param properties */ @Override public void setProperties(Properties properties) { System.out.println( "插件配置信息:" +properties); String username = (String) properties.get( "username" ); String password = (String) properties.get( "password" ); } } |
场景二:PageHelper插件进行分页
a . 引入依赖
在pom.xml文件添加如下依赖:
1 2 3 4 5 6 | <!-- mybatis分页插件依赖 --> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version> 4.2 . 0 </version> </dependency> |
b. 在Mybatis配置xml中配置拦截器插件
在mybatis-config.xml文件中添加插件后的内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | <?xml version= "1.0" encoding= "UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd" > <configuration> <plugins> <!-- com.github.pagehelper为PageHelper类所在包名 --> <plugin interceptor= "com.github.pagehelper.PageHelper" > <property name= "dialect" value= "mysql" /> <!-- 该参数默认为 false --> <!-- 设置为 true 时,会将RowBounds第一个参数offset当成pageNum页码使用 --> <!-- 和startPage中的pageNum效果一样--> <property name= "offsetAsPageNum" value= "true" /> <!-- 该参数默认为 false --> <!-- 设置为 true 时,使用RowBounds分页会进行count查询 --> <property name= "rowBoundsWithCount" value= "true" /> <!-- 设置为 true 时,如果pageSize= 0 或者RowBounds.limit = 0 就会查询出全部的结果 --> <!-- (相当于没有执行分页查询,但是返回结果仍然是Page类型)--> <property name= "pageSizeZero" value= "true" /> <!-- 3.3 . 0 版本可用 - 分页参数合理化,默认 false 禁用 --> <!-- 启用合理化时,如果pageNum< 1 会查询第一页,如果pageNum>pages会查询最后一页 --> <!-- 禁用合理化时,如果pageNum< 1 或pageNum>pages会返回空数据 --> <property name= "reasonable" value= "false" /> <!-- 3.5 . 0 版本可用 - 为了支持startPage(Object params)方法 --> <!-- 增加了一个`params`参数来配置参数映射,用于从Map或ServletRequest中取值 --> <!-- 可以配置pageNum,pageSize,count,pageSizeZero,reasonable,不配置映射的用默认值 --> <!-- 不理解该含义的前提下,不要随便复制该配置 --> <property name= "params" value= "pageNum=start;pageSize=limit;" /> <!-- always总是返回PageInfo类型,check检查返回类型是否为PageInfo,none返回Page --> <property name= "returnPageInfo" value= "check" /> </plugin> </plugins> </configuration> |
c. 默认jar包中的实现类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 | package com.github.pagehelper; import com.github.pagehelper.util.MSUtils; import com.github.pagehelper.util.StringUtil; import org.apache.ibatis.cache.CacheKey; import org.apache.ibatis.executor.Executor; import org.apache.ibatis.mapping.BoundSql; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.plugin.*; import org.apache.ibatis.session.ResultHandler; import org.apache.ibatis.session.RowBounds; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Properties; /** * 最简单的分页插件拦截器 */ @SuppressWarnings ( "rawtypes" ) @Intercepts ( @Signature (type = Executor. class , method = "query" , args = {MappedStatement. class , Object. class , RowBounds. class , ResultHandler. class })) public class PaginationInterceptor implements Interceptor { protected Dialect dialect; protected Field additionalParametersField; @Override public Object intercept(Invocation invocation) throws Throwable { //获取拦截方法的参数 Object[] args = invocation.getArgs(); MappedStatement ms = (MappedStatement) args[ 0 ]; Object parameterObject = args[ 1 ]; RowBounds rowBounds = (RowBounds) args[ 2 ]; List resultList; //调用方法判断是否需要进行分页,如果不需要,直接返回结果 if (!dialect.skip(ms, parameterObject, rowBounds)) { ResultHandler resultHandler = (ResultHandler) args[ 3 ]; //当前的目标对象 Executor executor = (Executor) invocation.getTarget(); BoundSql boundSql = ms.getBoundSql(parameterObject); //反射获取动态参数 Map<String, Object> additionalParameters = (Map<String, Object>) additionalParametersField.get(boundSql); //判断是否需要进行 count 查询 if (dialect.beforeCount(ms, parameterObject, rowBounds)) { //创建 count 查询的缓存 key CacheKey countKey = executor.createCacheKey(ms, parameterObject, RowBounds.DEFAULT, boundSql); countKey.update( "_Count" ); //根据当前的 ms 创建一个返回值为 Long 类型的 ms MappedStatement countMs = MSUtils.newCountMappedStatement(ms); //调用方言获取 count sql String countSql = dialect.getCountSql(ms, boundSql, parameterObject, rowBounds, countKey); BoundSql countBoundSql = new BoundSql(ms.getConfiguration(), countSql, boundSql.getParameterMappings(), parameterObject); //当使用动态 SQL 时,可能会产生临时的参数,这些参数需要手动设置到新的 BoundSql 中 for (String key : additionalParameters.keySet()) { countBoundSql.setAdditionalParameter(key, additionalParameters.get(key)); } //执行 count 查询 Object countResultList = executor.query(countMs, parameterObject, RowBounds.DEFAULT, resultHandler, countKey, countBoundSql); Long count = (Long) ((List) countResultList).get( 0 ); //处理查询总数 dialect.afterCount(count, parameterObject, rowBounds); if (count == 0L) { //当查询总数为 0 时,直接返回空的结果 return dialect.afterPage( new ArrayList(), parameterObject, rowBounds); } } //判断是否需要进行分页查询 if (dialect.beforePage(ms, parameterObject, rowBounds)) { //生成分页的缓存 key CacheKey pageKey = executor.createCacheKey(ms, parameterObject, rowBounds, boundSql); //处理参数对象 parameterObject = dialect.processParameterObject(ms, parameterObject, boundSql, pageKey); //调用方言获取分页 sql String pageSql = dialect.getPageSql(ms, boundSql, parameterObject, rowBounds, pageKey); BoundSql pageBoundSql = new BoundSql(ms.getConfiguration(), pageSql, boundSql.getParameterMappings(), parameterObject); //设置动态参数 for (String key : additionalParameters.keySet()) { pageBoundSql.setAdditionalParameter(key, additionalParameters.get(key)); } //执行分页查询 resultList = executor.query(ms, parameterObject, RowBounds.DEFAULT, resultHandler, pageKey, pageBoundSql); } else { resultList = new ArrayList(); } } else { args[ 2 ] = RowBounds.DEFAULT; resultList = (List) invocation.proceed(); } //返回默认查询 return dialect.afterPage(resultList, parameterObject, rowBounds); } @Override public Object plugin(Object target) { return Plugin.wrap(target, this ); } @Override public void setProperties(Properties properties) { String dialectClassStr = properties.getProperty( "dialect" ); if (StringUtil.isEmpty(dialectClassStr)) { throw new RuntimeException( "使用 PaginationInterceptor 分页插件时,必须设置 dialect 属性" ); } try { Class dialectClass = Class.forName(dialectClassStr); dialect = (Dialect) dialectClass.newInstance(); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException( "初始化 dialect [" + dialectClassStr + "]时出错:" + e.getMessage()); } dialect.setProperties(properties); try { //反射获取 BoundSql 中的 additionalParameters 属性 additionalParametersField = BoundSql. class .getDeclaredField( "additionalParameters" ); additionalParametersField.setAccessible( true ); } catch (NoSuchFieldException e) { throw new RuntimeException(e); } } } |
d.使用page
或
场景三:批量操作
与Spring整合进行批量操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | <!--整合mybatis 目的: 1 、Spring管理所有组件, 2 、Spring用来管理事务 --> <bean id= "sqlSessionFactoryBean" class = "org.mybatis.spring.SqlSessionFactoryBean" > <property name= "dataSource" ref= "dataSource" /> <!-- configLocation 指定全局配置文件的位置--> <property name= "configLocation" value= "classpath:mybatis-config.xml" ></property> <!--mapperLocations:指定mapper文件的位置--> <property name= "mapperLocations" value= "classpath:mybatis/mapper/*.xml" ></property> </bean> <!-- 配置一个可以进行批量执行的sqlSession --> <bean id= "sqlSession" class = "org.mybatis.spring.SqlSessionTemplate" > <constructor-arg name= "sqlSessionFactory" ref= "sqlSessionFactoryBean" ></constructor-arg> <constructor-arg name= "executorType" ref= "BATCH" ></constructor-arg> </bean> java中使用: @Autowired private SqlSession sqlSession; |
场景四:存储过程
场景五:typeHandler处理枚举
可以通过自定义TypeHandler的形式来在设置参数或取出结果集的时候自定义参数封装策略
步骤:
1. 实现TypeHandler接口或继承BaseTypeHandler
2. 使用@MappedTypes定义处理的java类型
3. 在自定义结果集标签或参数处理的时候声明使用自定义TypeHandler进行处理
或在全局配置TypeHandler要处理的javaType
多插件编写:
1 2 3 4 5 6 7 8 9 10 | <configuration> <!-- 配置全局插件 --> <plugins> <plugin interceptor= "com.feng.config.MyFirstPlug" > <property name= "username" value= "zhangsan" /> <property name= "password" value= "123456" /> </plugin> <plugin interceptor= "com.feng.config.MySecondPlug" ></plugin> </plugins> </configuration> |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· DeepSeek在M芯片Mac上本地化部署
· 葡萄城 AI 搜索升级:DeepSeek 加持,客户体验更智能