StatementHandler

本文详细解析了MyBatis中StatementHandler的工作原理,包括Configuration如何生成RoutingStatementHandler对象,以及其如何根据Executor类型创建具体的statementHandler对象,如SimpleStatementHandler、PreparedStatementHandler和CallableStatementHandler。探讨了在不同场景下,如SQL修改、参数修改和结果集控制时,如何通过拦截statementHandler进行操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在MyBatis中,

  • Configuration对象会采用new RoutingStatementHandler()来生成StatementHandler对象,
    • 换句话说我们真正使用的是RoutingStatementHandler对象,
    • 然后它会根据Executor的类型去创建对应具体的statementHandler对象
      • SimpleStatementHandlerPreparedStatementHandlerCallableStatementHandler)。

然后利用具体statementHandler的方法完成所需要的功能。

  • 那么这个具体的statementHandler是保存在RoutingStatementHandler对象的delegate属性的,
  • 所以当我们拦截statementHandler的时候就要常常访问它了。它们的关系如下图所示。

ab05b4a1e5c0dcf75560e8717ff5e56ff09.jpg

StatementHandler是MyBatis四大对象里面最重要的对象,它的方法是十分重要的,也是我们插件的基础。

  • 当我们需要改变sql的时候,
    • 显然我们要在预编译SQL(prepare方法前加入修改的逻辑)。
  • 当我们需要修改参数的时候
    • 我们可以在调用parameterize方法前修改逻辑。
    • 或者使用ParameterHandler来改造设置参数。
  • 我们需要控制组装结果集的时候,
    • 也可以在query方法前后加入逻辑,
    • 或者使用ResultHandler来改造组装结果。
  • 懂的这些方法,才能理解我需要拦截什么对象,如何处理插件,这是MyBatis的核心内容。
     

 

转载于:https://my.oschina.net/u/3847203/blog/3019245

<think>我们正在分析MyBatis源码,特别是如何通过StatementHandler打印SQL执行时的参数值。根据引用内容,我们可以关注StatementHandler的使用和参数设置过程。关键点:1.在`SimpleExecutor.doQuery`方法中,通过`StatementHandler`执行SQL查询(引用[1])。2.在`DynamicSqlSource.getBoundSql`方法中处理了动态SQL(包括${}和#{})并生成BoundSql对象(引用[2])。3.StatementHandler负责创建Statement对象并设置参数(引用[3])。为了打印SQL执行时的参数值,我们需要在StatementHandler设置参数时拦截这些参数并打印。通常,我们可以通过自定义一个StatementHandler来实现,或者通过MyBatis的插件(Interceptor)机制来拦截相关方法。根据引用[3],PreparedStatement接口提供了设置参数的方法,我们可以考虑在设置参数时打印参数值。解决方案:1.使用MyBatis的插件机制拦截`StatementHandler.parameterize`方法,该方法用于设置参数(对应PreparedStatement的setXXX方法)。2.在拦截器中,我们可以获取到PreparedStatement对象,并尝试获取其参数值。步骤:-编写一个实现Interceptor接口的插件。-拦截`StatementHandler.parameterize`方法(因为在这个方法中会调用PreparedStatement的setXXX方法设置参数)。-在拦截器内,通过反射获取PreparedStatement对象,然后获取其参数值(注意:PreparedStatement的参数值存储在一个内部数组中,不同数据库驱动实现可能不同,但通常可以通过反射获取)。注意:由于不同数据库驱动的PreparedStatement实现不同,直接通过反射获取参数值可能不稳定。另一种思路是在设置参数时,通过代理PreparedStatement的setXXX方法,记录参数值。但是,MyBatis自身在`ParameterHandler`中设置参数,而`ParameterHandler`是`StatementHandler`的一个成员。我们可以通过拦截`ParameterHandler.setParameters`方法来实现。具体步骤(通过插件拦截):1.拦截`ParameterHandler.setParameters`方法,因为在这个方法中,MyBatis会遍历所有参数并设置到PreparedStatement中。2.在拦截器的方法中,我们可以获取到PreparedStatement和参数映射信息(通过ParameterHandler的BoundSql对象)。3.打印参数值。实现示例:```java@Intercepts({@Signature(type=ParameterHandler.class,method="setParameters",args={PreparedStatement.class})})publicclassSqlParameterInterceptorimplementsInterceptor{@OverridepublicObjectintercept(Invocationinvocation)throwsThrowable{//获取被拦截的对象(ParameterHandler)ParameterHandlerparameterHandler=(ParameterHandler)invocation.getTarget();//获取BoundSql,其中包含SQL语句和参数映射信息FieldboundSqlField=parameterHandler.getClass().getDeclaredField("boundSql");boundSqlField.setAccessible(true);BoundSqlboundSql=(BoundSql)boundSqlField.get(parameterHandler);//获取参数对象(即Mapper方法的参数)ObjectparameterObject=boundSql.getParameterObject();//获取PreparedStatement对象(方法参数)PreparedStatementps=(PreparedStatement)invocation.getArgs()[0];//打印SQL语句(带占位符)System.out.println("SQL:"+boundSql.getSql());//打印参数对象System.out.println("Parameters:"+parameterObject);//继续执行原方法(设置参数)returninvocation.proceed();}}```注意:上面的示例直接打印了参数对象,这适用于简单参数。如果参数是复杂对象(如Map或自定义对象),则可能需要根据BoundSql中的参数映射(ParameterMapping)来获取每个参数的名称和值。更详细的打印每个参数(按顺序):```java//获取参数映射列表List<ParameterMapping>parameterMappings=boundSql.getParameterMappings();if(parameterMappings!=null&&!parameterMappings.isEmpty()){//遍历参数映射for(inti=0;i<parameterMappings.size();i++){ParameterMappingmapping=parameterMappings.get(i);//获取属性名Stringproperty=mapping.getProperty();//从参数对象中获取值(需要借助MetaObject)Objectvalue;if(parameterObject==null){value=null;}elseif(typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())){value=parameterObject;}else{//创建参数对象的元对象MetaObjectmetaObject=configuration.newMetaObject(parameterObject);value=metaObject.getValue(property);}System.out.println("Parameter#"+(i+1)+":"+property+"="+value);}}```但是,在拦截器中获取Configuration可能需要一些额外的反射,因为ParameterHandler本身并没有直接提供获取Configuration的方法。我们可以通过反射从ParameterHandler中获取Configuration(因为ParameterHandler的实现类`DefaultParameterHandler`有一个`configuration`字段)。改进后的完整拦截器代码:```java@Intercepts({@Signature(type=ParameterHandler.class,method="setParameters",args={PreparedStatement.class})})publicclassSqlParameterInterceptorimplementsInterceptor{@OverridepublicObjectintercept(Invocationinvocation)throwsThrowable{ParameterHandlerparameterHandler=(ParameterHandler)invocation.getTarget();PreparedStatementps=(PreparedStatement)invocation.getArgs()[0];//通过反射获取BoundSqlFieldboundSqlField=parameterHandler.getClass().getDeclaredField("boundSql");boundSqlField.setAccessible(true);BoundSqlboundSql=(BoundSql)boundSqlField.get(parameterHandler);//通过反射获取Configuration(因为DefaultParameterHandler有configuration字段)FieldconfigurationField=parameterHandler.getClass().getDeclaredField("configuration");configurationField.setAccessible(true);Configurationconfiguration=(Configuration)configurationField.get(parameterHandler);//获取参数对象ObjectparameterObject=boundSql.getParameterObject();List<ParameterMapping>parameterMappings=boundSql.getParameterMappings();//打印SQLSystem.out.println("ExecutingSQL:"+boundSql.getSql());if(parameterMappings!=null&&!parameterMappings.isEmpty()){TypeHandlerRegistrytypeHandlerRegistry=configuration.getTypeHandlerRegistry();for(inti=0;i<parameterMappings.size();i++){ParameterMappingmapping=parameterMappings.get(i);Stringproperty=mapping.getProperty();Objectvalue;if(typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())){value=parameterObject;}else{MetaObjectmetaObject=configuration.newMetaObject(parameterObject);value=metaObject.getValue(property);}System.out.println("Parameter:"+property+"=>"+value);}}else{System.out.println("Noparameters");}returninvocation.proceed();}}```最后,将这个拦截器注册到MyBatis配置中(在mybatis-config.xml中或通过Spring配置)。注意:这种方法通过反射访问了MyBatis内部对象的私有字段,可能会因为MyBatis版本变化而失效。在生产环境中使用需谨慎,主要用于调试。另外,也可以考虑使用已有的开源工具,比如p6spy,它可以打印完整的SQL语句和参数。总结:通过自定义插件拦截ParameterHandler的setParameters方法,我们可以获取并打印SQL执行时的参数值。</think>### MyBatis 通过 StatementHandler 打印 SQL 参数值的方法 在 MyBatis 中,要打印 SQL 执行时的参数值,可通过自定义插件拦截 `StatementHandler` 的关键方法实现。以下是具体解决方案: --- #### **方法 1:拦截 `ParameterHandler.setParameters()`(推荐)** 在参数设置阶段捕获值: ```java @Intercepts({ @Signature( type = ParameterHandler.class, method = "setParameters", args = {PreparedStatement.class} ) }) public class ParamInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { ParameterHandler handler = (ParameterHandler) invocation.getTarget(); // 通过反射获取 BoundSql 和参数值 Field boundSqlField = handler.getClass().getDeclaredField("boundSql"); boundSqlField.setAccessible(true); BoundSql boundSql = (BoundSql) boundSqlField.get(handler); // 打印原始 SQL 和参数 System.out.println("SQL: " + boundSql.getSql()); System.out.println("Params: " + boundSql.getParameterObject()); return invocation.proceed(); // 继续执行原逻辑 } } ``` --- #### **方法 2:拦截 `StatementHandler.parameterize()`** 在语句参数化阶段拦截: ```java @Intercepts({ @Signature( type = StatementHandler.class, method = "parameterize", args = {Statement.class} ) }) public class StatementInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { StatementHandler handler = (StatementHandler) invocation.getTarget(); BoundSql boundSql = handler.getBoundSql(); // 打印参数化后的 SQL(需数据库驱动支持) if (handler.getParameterHandler() != null) { System.out.println("Parameterized SQL: " + boundSql.getSql()); System.out.println("Arguments: " + boundSql.getParameterObject()); } return invocation.proceed(); } } ``` --- #### **注册插件** 在 MyBatis 配置文件中注册拦截器: ```xml <plugins> <plugin interceptor="com.example.ParamInterceptor"/> <!-- 或 --> <plugin interceptor="com.example.StatementInterceptor"/> </plugins> ``` --- #### **关键说明** 1. **参数获取原理** - `BoundSql` 对象存储了原始 SQL 和参数对象(`getParameterObject()`) - 动态 SQL 中的 `${}` 和 `#{}` 已在 `BoundSql` 中解析完成[^2] 2. **执行流程定位** - 参数设置在 `ParameterHandler.setParameters()` 中完成[^1] - `StatementHandler` 通过 `SimpleExecutor` 调用 `prepareStatement()` 初始化[^1][^3] 3. **注意事项** - 复杂参数(如 Map、POJO)需遍历 `boundSql.getParameterMappings()` 解析具体字段值 - 生产环境建议添加日志级别控制(如仅 DEBUG 模式打印) --- #### **效果示例** ```sql -- 控制台输出示例 -- SQL: SELECT * FROM user WHERE name = ? AND age > ? Params: {name="John", age=25} ``` 通过此方案,可在 SQL 执行前准确捕获参数值,适用于调试和日志审计场景。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值