网站开发用什么电脑好,上海快速建站提供商,旅游网站开发背景意义,西安做网站公司MyBatis插件介绍
MyBatis提供了一种插件(plugin)的功能#xff0c;虽然叫做插件#xff0c;但其实这是拦截器功能。
MyBatis允许使用者在映射语句执行过程中的某一些指定的节点进行拦截调用#xff0c;通过织入拦截器#xff0c;在不同节点修改一些执行过程中的关键属性虽然叫做插件但其实这是拦截器功能。
MyBatis允许使用者在映射语句执行过程中的某一些指定的节点进行拦截调用通过织入拦截器在不同节点修改一些执行过程中的关键属性从而影响SQL的生成、执行和返回结果如来影响Mapper.xml到SQL语句的生成、执行SQL前对预编译的SQL执行参数的修改、SQL执行后返回结果到Mapper接口方法返参POJO对象的类型转换和封装等。
根据上面的对Mybatis拦截器作用的描述可以分析其可能的用途最常见的就是Mybatis自带的分页插件PageHelper或Rowbound参数通过打印实际执行的SQL语句发现我们的分页查询之前先执行了COUNT(*)语句查询数量然后再执行查询时修改了SQL语句即在我们写的SQL语句后拼接上了分页语句LIMIT(offset, pageSize)
此外实际工作中可以使用Mybatis拦截器来做一些数据过滤、数据加密脱敏、SQL执行时间性能监控和告警等既然要准备使用它下面先来了解下其原理
默认情况下MyBatis 允许使用插件来拦截的四种相关操作类方法
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
ParameterHandler (getParameterObject, setParameters)
ResultSetHandler (handleResultSets, handleOutputParameters)
StatementHandler (prepare, parameterize, batch, update, query)这几个接口之间的关系大概是这样的
Mybatis整体执行流程 核心对象
Configuration初始化基础配置比如MyBatis的别名等一些重要的类型对象如插件映射器ObjectFactory和typeHandler对象MyBatis所有的配置信息都维持在Configuration对象之中。SqlSessionFactorySqlSession工厂。SqlSession作为MyBatis工作的主要顶层API表示和数据库交互的会话完成必要的数据库增删改查功能。ExecutorMyBatis的内部执行器它负责调用StatementHandler操作数据库并把结果集通过ResultSetHandler进行自动映射另外它还处理二级缓存的操作。StatementHandlerMyBatis直接在数据库执行SQL脚本的对象。另外它也实现了MyBatis的一级缓存。ParameterHandler负责将用户传递的参数转换成JDBC Statement所需要的参数。是MyBatis实现SQL入参设置的对象。ResultSetHandler负责将JDBC返回的ResultSet结果集对象转换成List类型的集合。是MyBatis把ResultSet集合映射成POJO的接口对象。TypeHandler负责Java数据类型和JDBC数据类型之间的映射和转换。MappedStatementMappedStatement维护了一条select|update|delete|insert节点的封装。SqlSource 负责根据用户传递的parameterObject动态地生成SQL语句将信息封装到BoundSql对象中并返回。BoundSql表示动态生成的SQL语句以及相应的参数信息。
MyBatis自定义插件的实现
通过 MyBatis 提供的强大机制使用插件是非常简单的只需实现 Interceptor 接口并指定想要拦截的方法签名即可。
Interceptor 接口的定义如下所示
public interface Interceptor {//拦截器具体实现Object intercept(Invocation invocation) throws Throwable;//拦截器的代理类Object plugin(Object target);//添加属性void setProperties(Properties properties);
}相关注解
Intercepts // 描述标志该类是一个拦截器
Signature // 描述指明该拦截器需要拦截哪一个接口的哪一个方法// Signature注解中属性:
type; // 四种类型接口中的某一个接口如Executor.class
method; // 对应接口中的某一个方法名比如Executor的query方法
args; // 对应接口中的某一个方法的参数比如Executor中query方法因为重载原因有多个args就是指明参数类型从而确定是具体哪一个方法下面来看一个自定义的简单Interceptor示例
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.springframework.stereotype.Component;import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.util.Properties;Component
//拦截StatementHandler类中参数类型为Statement的prepare方法prepare在预编译SQL前加入修改的逻辑
//即拦截 Statement prepare(Connection var1, Integer var2) 方法
Intercepts({Signature(type StatementHandler.class, method prepare, args {Connection.class, Integer.class})
})
Slf4j
public class MyPlugin implements Interceptor {Overridepublic Object intercept(Invocation invocation) throws Throwable {// 获取原始sqlStatementHandler statementHandler (StatementHandler) invocation.getTarget();BoundSql boundSql statementHandler.getBoundSql();// 通过MetaObject优雅访问对象的属性这里是访问statementHandler的属性;MetaObject是Mybatis提供的一个用于方便、// 优雅访问对象属性的对象通过它可以简化代码、不需要try/catch各种reflect异常同时它支持对JavaBean、Collection、Map三种类型对象的操作。MetaObject metaObject MetaObject.forObject(statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY, SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY,new DefaultReflectorFactory());// 先拦截到RoutingStatementHandler里面有个StatementHandler类型的delegate变量其实现类是BaseStatementHandler然后就到BaseStatementHandler的成员变量mappedStatementMappedStatement mappedStatement (MappedStatement) metaObject.getValue(delegate.mappedStatement);// 通过反射拦截方法上带有自定义InterceptAnnotation注解的方法并修改sqlString mSql sqlAnnotationEnhance(mappedStatement, boundSql);Field field boundSql.getClass().getDeclaredField(sql);field.setAccessible(true);field.set(boundSql, mSql);return invocation.proceed();}Overridepublic Object plugin(Object target) {if (target instanceof StatementHandler) {return Plugin.wrap(target, this);} else {return target;}}Overridepublic void setProperties(Properties properties) {}/*** 通过反射拦截方法上带有自定义InterceptAnnotation注解的方法并增强sql* param id 方法全路径* param sqlCommandType sql类型* param sql 所执行的sql语句*/private String sqlAnnotationEnhance(MappedStatement mappedStatement, BoundSql boundSql) throws ClassNotFoundException {// 获取到原始sql语句String sql boundSql.getSql().toLowerCase();// sql语句类型 select、delete、insert、updateString sqlCommandType mappedStatement.getSqlCommandType().toString();// 数据库连接信息// Configuration configuration mappedStatement.getConfiguration();// ComboPooledDataSource dataSource (ComboPooledDataSource)configuration.getEnvironment().getDataSource();// dataSource.getJdbcUrl();// id为执行的mapper方法的全路径名如com.cq.UserMapper.insertUser 便于后续使用反射String id mappedStatement.getId();// 获取当前所拦截的方法名称String mName id.substring(id.lastIndexOf(.) 1);// 通过类全路径获取Class对象Class? classType Class.forName(id.substring(0, id.lastIndexOf(.)));// 获得参数集合String paramString null;if (boundSql.getParameterObject() ! null) {paramString boundSql.getParameterObject().toString();}// 遍历类中所有方法名称并匹配上当前所拦截的方法for (Method method : classType.getDeclaredMethods()) {if (mName.equals(method.getName())) {// 判断方法上是否带有自定义InterceptAnnotation注解InterceptAnnotation interceptorAnnotation method.getAnnotation(InterceptAnnotation.class);if (interceptorAnnotation ! null interceptorAnnotation.flag()) {log.info(intercept func:{}, type:{}, origin SQL{}, mName, sqlCommandType, sql);// 场景1分页功能 return sql limit 1;if (select.equals(sqlCommandType.toLowerCase())) {if (!sql.toLowerCase().contains(limit)) {sql sql limit 1;}}// 场景2校验功能 :update/delete必须要有where条件并且打印出where中的条件if (update.equals((sqlCommandType.toLowerCase())) || delete.equals(sqlCommandType.toLowerCase())) {if (!sql.toLowerCase().contains(where)) {log.warn(update or delete not safe!);}}// 场景3分库分表: 根据userId哈希,替换注解中的表名if (sql.toLowerCase().contains(interceptorAnnotation.value())) {String userId getValue(paramString, userId);if (userId ! null) {int num Integer.parseInt(userId);// 模拟分10个库5个表String data_source_id String.valueOf(num % 10);String new_table interceptorAnnotation.value().concat(_).concat(String.valueOf(num % 5));log.info(set data_source_id:{}, table: {}, data_source_id, new_table);// 设置data_source_id路由 替换sql表名sql StringUtils.replace(sql, interceptorAnnotation.value(), new_table);}}log.info(new SQL{}, sql);return sql;}}}return sql;}String getValue(String param, String key) {if (param null) {return null;}String[] keyValuePairs param.substring(1, param.length() - 1).split(,);for (String pair : keyValuePairs) {String[] entry pair.split();if (entry[0].trim().equals(key)) {return entry[1].trim();}}return null;}
}自定义注解如下
import java.lang.annotation.*;Target({ElementType.METHOD, ElementType.TYPE})
Retention(RetentionPolicy.RUNTIME)
Documented
public interface InterceptAnnotation {String value() default ;/*** true增强、false忽略*/boolean flag() default true;
}添加插件
Component
public class DynamicPluginHelper {Autowiredprivate ListSqlSessionFactory sqlSessionFactoryList;Autowiredprivate MyPlugin myPlugin;PostConstructpublic void addMysqlInterceptor() {for (SqlSessionFactory sqlSessionFactory : sqlSessionFactoryList) {org.apache.ibatis.session.Configuration configuration sqlSessionFactory.getConfiguration();configuration.addInterceptor(myPlugin);}}
}
测试结果如下
sql.MyPlugin : intercept func:findUser, type:SELECT, origin SQLselect * from t_user where id ?
sql.MyPlugin : set data_source_id:4, table: t_user_4
sql.MyPlugin : new SQLselect * from t_user_4 where id ? limit 1问题记录
错误描述 There is no getter for property named delegate in class com.sun.proxy.$Proxy32
错误原因 1、你有多个拦截器拦截同一对象的同一行为。测试时避免其他拦截器的干扰可以先把注册的拦截器注释掉。 2、依赖包版本不对 3、拦截器配置类放置的位置不正确导致包没找到
参考 https://blog.csdn.net/minghao0508/article/details/124420953 https://blog.csdn.net/qq_36881887/article/details/111589294 https://www.cnblogs.com/simplejavahome/p/16617112.html https://www.cnblogs.com/nefure/p/16948633.html https://blog.csdn.net/u011602668/article/details/128735771