1. 问题背景
在使用Java的mybatis-plus框架进行开发时,有时候会遇到需要打印SQL语句的情况,以便于调试和排查问题。mybatis-plus提供了默认的打印SQL语句的方式,但是有时候会发现打印出来的SQL语句并不符合预期或者不够详细,因此我们需要对打印方式进行定制和调整,以满足实际的开发需求。
2. 默认打印方式
在mybatis-plus中,可以通过设置log4j的日志级别来控制是否打印SQL语句,打印SQL语句的日志级别为DEBUG。mybatis-plus默认的打印方式可以通过设置mybatis-plus的全局配置对象进行配置,具体代码如下:
GlobalConfig globalConfig = new GlobalConfig();
globalConfig.setSqlInjector(new LogicSqlInjector());
globalConfig.setDbConfig(
new GlobalConfig.DbConfig()
.setDbType(DbType.MYSQL)
.setLogicDeleteValue("0")
.setLogicNotDeleteValue("1")
);
globalConfig.setSqlParserCache(true);
globalConfig.setBanner(false);
globalConfig.setMetaObjectHandler(new MyMetaObjectHandler());
globalConfig.setRefresh(true);
globalConfig.setSqlRunner(new MySqlRunner());
globalConfig.setSqlRunner(new SqlServerRunner());
globalConfig.setSqlRunner(new OracleRunner());
globalConfig.setSqlRunner(new PostgreSqlRunner());
globalConfig.setSqlRunner(new H2Runner());
globalConfig.setSqlRunner(new SqliteRunner());
globalConfig.setSqlRunner(new DerbyRunner());
globalConfig.setSqlRunner(new HSqlRunner());
globalConfig.setSqlRunner(new MariaDBRunner());
globalConfig.setSqlRunner(new SophiaRunner());
globalConfig.setPerformanceInterceptor(new PerformanceInterceptor());
globalConfig.setSqlInLog(true);
globalConfig.setIdentifierGenerator(new CustomIdGenerator());
globalConfig.setDbType(DbType.MYSQL);
globalConfig.setDbConfig(
new GlobalConfig.DbConfig()
.setAutoCommit(false)
);
globalConfig.setSqlSessionFactoryBuilder(new SqlSessionFactoryBuilder());
globalConfig.setSqlSessionFactory(new SqlSessionFactoryBuilder().build(inputStream));
在上述配置代码中,我们可以看到有一个属性叫作"sqlInLog",它的作用就是控制是否打印SQL语句。如果将这个属性设置为true,则mybatis-plus会在DEBUG级别下打印SQL语句到日志中。
2.1 示例
例如,我们有一个简单的查询方法:
@Override
public List list() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.lambda()
.eq(User::getEnabled, true)
.orderByDesc(User::getCreatedTime);
return userDao.selectList(wrapper);
}
默认情况下mybatis-plus打印出来的SQL语句如下所示:
SELECT id,name,age,enabled,created_time FROM user WHERE (enabled = ?) order by created_time desc
可以看到,打印出来的SQL语句并不包括具体的参数值,这在调试时可能会比较困难。
3. 定制SQL打印方式
由于mybatis-plus的默认打印方式无法满足实际的开发需求,我们需要对其进行定制和调整。下面介绍两种常用的定制方式。
3.1 方式一:使用日志框架打印参数
通过查看mybatis-plus的源码,我们可以发现mybatis-plus对于打印SQL参数的处理是通过log4j的消息格式化机制来实现的。因此,我们可以通过调整log4j的消息格式化模板,来实现定制化的SQL打印方式。
在log4j的配置文件中,添加如下配置:
<appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d %-5p %c{1}:%L - %m%n %X{params}\n"/>
</layout>
</appender>
在这个配置文件中,我们将log4j的消息格式化模板中的%X{params}表示可以打印出SQL参数信息。具体到mybatis-plus中,我们需要在代码中将参数信息保存到MDC(Mapped Diagnostic Context)中,代码如下所示:
@Override
public List list() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.lambda()
.eq(User::getEnabled, true)
.orderByDesc(User::getCreatedTime);
MDC.put("params", wrapper.getParamNameValuePairs().toString());
try {
return userDao.selectList(wrapper);
}finally {
MDC.remove("params");
}
}
这里我们使用MDC的方式来保存SQL参数信息,当查询结束后再将其移除,以免对其他查询产生干扰。这样配置后,在调用上述的list方法时,日志中将会打印出SQL参数信息,例如:
SELECT id,name,age,enabled,created_time FROM user WHERE (enabled = 1) order by created_time desc
可见,打印出来的SQL语句包含了具体的参数值信息,方便我们进行调试。
3.2 方式二:使用Interceptor拦截器打印参数
除了通过日志框架调整输出格式以外,我们还可以通过使用mybatis-plus提供的Interceptor拦截器来实现SQL打印方式的定制化。Interceptor拦截器是mybatis-plus提供的一个扩展机制,可以对SQL执行过程中的各个环节进行拦截和处理。下面是在mybatis-plus中添加Interceptor的方式:
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
MybatisSqlSessionFactoryBean sqlSessionFactory = new MybatisSqlSessionFactoryBean();
sqlSessionFactory.setDataSource(dataSource);
Interceptor[] interceptors = new Interceptor[]{
new SqlPrintInterceptor(), //添加一个SQL打印拦截器
};
sqlSessionFactory.setPlugins(interceptors);
return sqlSessionFactory.getObject();
}
使用Interceptor打印SQL语句的关键在于实现SqlPrintInterceptor拦截器。下面是SqlPrintInterceptor拦截器的实现代码:
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor;
import java.sql.Statement;
import java.util.Properties;
/**
* SQL打印拦截器
*/
@Intercepts({
@Signature(type = StatementHandler.class, method = "prepare", args = {Statement.class, Integer.class})
})
public class SqlPrintInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
Object parameterObject = statementHandler.getParameterHandler().getParameterObject();
String sql = statementHandler.getBoundSql().getSql();
System.out.println("SQL: " + sql);
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
// do nothing
}
}
这个拦截器的实现比较简单,只需要在prepare方法中获取SQL语句并打印出来即可。
3.3 示例
通过使用方式二中的拦截器,在调用上述的list方法时,日志中将会打印出SQL语句,例如:
SELECT id,name,age,enabled,created_time FROM user WHERE (enabled = ?) order by created_time desc; - parameter: true
这里我们可以看到,打印出来的SQL语句还包含了参数信息,这对于调试和排查问题非常有帮助。
4. 结论
通过本文的介绍,读者可以了解到mybatis-plus的默认SQL打印方式以及两种定制化的SQL打印方式。在实际的开发中,我们可以根据不同的需求选择不同的定制方式,以满足自身的需求。同时,本文的介绍也让我们了解到mybatis-plus的一些内部实现机制,这些知识对于我们深入理解mybatis-plus的使用和内部实现都是非常有帮助的。