简体中文 繁體中文 English 日本語 Deutsch 한국 사람 بالعربية TÜRKÇE português คนไทย Français

站内搜索

搜索

活动公告

11-02 12:46
10-23 09:32
通知:本站资源由网友上传分享,如有违规等问题请到版务模块进行投诉,将及时处理!
10-23 09:31
10-23 09:28
通知:签到时间调整为每日4:00(东八区)
10-23 09:26

Spring MVC应用中SQL语句输出的完整指南从配置到实现助你轻松调试数据库操作

3万

主题

318

科技点

3万

积分

大区版主

木柜子打湿

积分
31894

财Doro三倍冰淇淋无人之境【一阶】立华奏小樱(小丑装)⑨的冰沙以外的星空【二阶】

发表于 2025-8-25 20:30:03 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
引言:为什么需要输出SQL语句进行调试

在Spring MVC应用开发过程中,数据库操作是一个核心环节。当应用出现性能问题或数据操作异常时,能够清晰地查看实际执行的SQL语句对于问题诊断至关重要。通过输出SQL语句,开发者可以:

• 验证生成的SQL是否符合预期
• 检查参数绑定是否正确
• 分析SQL执行效率
• 快速定位数据操作错误

然而,Spring MVC应用默认情况下并不会直接输出执行的SQL语句,需要我们进行相应的配置才能实现。本文将详细介绍在Spring MVC应用中如何配置和实现SQL语句的输出,帮助开发者轻松调试数据库操作。

基础准备:了解Spring MVC中的数据访问层

在深入配置SQL输出之前,我们需要先了解Spring MVC应用中常见的数据访问技术:

1. JDBC (Java Database Connectivity):Java中最基础的数据库访问方式
2. Hibernate:一个流行的ORM(对象关系映射)框架
3. JPA (Java Persistence API):Java持久化API标准
4. Spring Data JPA:Spring基于JPA的数据访问抽象层
5. MyBatis:另一个流行的持久层框架,更注重SQL控制

不同的数据访问技术,配置SQL输出的方式也有所不同。接下来,我们将逐一介绍各种技术栈下如何配置SQL输出。

通过日志框架配置SQL输出

日志框架是输出SQL语句最常用的方式,它不仅可以输出SQL语句,还能输出参数、执行时间等详细信息。常见的日志框架有Logback、Log4j2等。

使用Logback配置SQL输出

Logback是Spring Boot默认的日志框架,配置简单且功能强大。以下是通过Logback配置SQL输出的方法:

1. 首先,在src/main/resources目录下创建logback-spring.xml文件:
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <configuration>
  3.     <!-- 定义日志输出格式 -->
  4.     <property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"/>
  5.    
  6.     <!-- 控制台输出 -->
  7.     <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
  8.         <encoder>
  9.             <pattern>${LOG_PATTERN}</pattern>
  10.         </encoder>
  11.     </appender>
  12.    
  13.     <!-- 文件输出 -->
  14.     <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
  15.         <file>logs/application.log</file>
  16.         <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
  17.             <fileNamePattern>logs/application.%d{yyyy-MM-dd}.log</fileNamePattern>
  18.             <maxHistory>30</maxHistory>
  19.         </rollingPolicy>
  20.         <encoder>
  21.             <pattern>${LOG_PATTERN}</pattern>
  22.         </encoder>
  23.     </appender>
  24.    
  25.     <!-- Hibernate SQL输出配置 -->
  26.     <logger name="org.hibernate.SQL" level="DEBUG" additivity="false">
  27.         <appender-ref ref="CONSOLE"/>
  28.         <appender-ref ref="FILE"/>
  29.     </logger>
  30.    
  31.     <!-- Hibernate SQL参数输出配置 -->
  32.     <logger name="org.hibernate.type.descriptor.sql.BasicBinder" level="TRACE" additivity="false">
  33.         <appender-ref ref="CONSOLE"/>
  34.         <appender-ref ref="FILE"/>
  35.     </logger>
  36.    
  37.     <!-- Spring Data JPA Repository输出配置 -->
  38.     <logger name="org.springframework.data" level="DEBUG" additivity="false">
  39.         <appender-ref ref="CONSOLE"/>
  40.         <appender-ref ref="FILE"/>
  41.     </logger>
  42.    
  43.     <!-- MyBatis SQL输出配置 -->
  44.     <logger name="com.yourpackage.mapper" level="DEBUG" additivity="false">
  45.         <appender-ref ref="CONSOLE"/>
  46.         <appender-ref ref="FILE"/>
  47.     </logger>
  48.    
  49.     <!-- 根日志级别 -->
  50.     <root level="INFO">
  51.         <appender-ref ref="CONSOLE"/>
  52.         <appender-ref ref="FILE"/>
  53.     </root>
  54. </configuration>
复制代码

1. 在application.properties或application.yml中配置日志级别:
  1. # application.properties
  2. logging.level.org.hibernate.SQL=DEBUG
  3. logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE
  4. logging.level.org.springframework.data=DEBUG
  5. logging.level.com.yourpackage.mapper=DEBUG
复制代码

或者使用YAML格式:
  1. # application.yml
  2. logging:
  3.   level:
  4.     org.hibernate.SQL: DEBUG
  5.     org.hibernate.type.descriptor.sql.BasicBinder: TRACE
  6.     org.springframework.data: DEBUG
  7.     com.yourpackage.mapper: DEBUG
复制代码

使用Log4j2配置SQL输出

Log4j2是另一个强大的日志框架,性能优异。以下是通过Log4j2配置SQL输出的方法:

1. 首先,添加Log4j2依赖(如果使用Spring Boot,需要排除默认的Logback):
  1. <!-- pom.xml -->
  2. <dependency>
  3.     <groupId>org.springframework.boot</groupId>
  4.     <artifactId>spring-boot-starter-web</artifactId>
  5.     <exclusions>
  6.         <exclusion>
  7.             <groupId>org.springframework.boot</groupId>
  8.             <artifactId>spring-boot-starter-logging</artifactId>
  9.         </exclusion>
  10.     </exclusions>
  11. </dependency>
  12. <dependency>
  13.     <groupId>org.springframework.boot</groupId>
  14.     <artifactId>spring-boot-starter-log4j2</artifactId>
  15. </dependency>
复制代码

1. 在src/main/resources目录下创建log4j2-spring.xml文件:
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <Configuration status="WARN">
  3.     <Properties>
  4.         <Property name="LOG_PATTERN">%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</Property>
  5.     </Properties>
  6.    
  7.     <Appenders>
  8.         <Console name="Console" target="SYSTEM_OUT">
  9.             <PatternLayout pattern="${LOG_PATTERN}"/>
  10.         </Console>
  11.         
  12.         <RollingFile name="File" fileName="logs/application.log"
  13.                      filePattern="logs/application-%d{yyyy-MM-dd}-%i.log">
  14.             <PatternLayout pattern="${LOG_PATTERN}"/>
  15.             <Policies>
  16.                 <TimeBasedTriggeringPolicy/>
  17.                 <SizeBasedTriggeringPolicy size="10 MB"/>
  18.             </Policies>
  19.             <DefaultRolloverStrategy max="10"/>
  20.         </RollingFile>
  21.     </Appenders>
  22.    
  23.     <Loggers>
  24.         <!-- Hibernate SQL输出配置 -->
  25.         <Logger name="org.hibernate.SQL" level="DEBUG" additivity="false">
  26.             <AppenderRef ref="Console"/>
  27.             <AppenderRef ref="File"/>
  28.         </Logger>
  29.         
  30.         <!-- Hibernate SQL参数输出配置 -->
  31.         <Logger name="org.hibernate.type.descriptor.sql.BasicBinder" level="TRACE" additivity="false">
  32.             <AppenderRef ref="Console"/>
  33.             <AppenderRef ref="File"/>
  34.         </Logger>
  35.         
  36.         <!-- Spring Data JPA Repository输出配置 -->
  37.         <Logger name="org.springframework.data" level="DEBUG" additivity="false">
  38.             <AppenderRef ref="Console"/>
  39.             <AppenderRef ref="File"/>
  40.         </Logger>
  41.         
  42.         <!-- MyBatis SQL输出配置 -->
  43.         <Logger name="com.yourpackage.mapper" level="DEBUG" additivity="false">
  44.             <AppenderRef ref="Console"/>
  45.             <AppenderRef ref="File"/>
  46.         </Logger>
  47.         
  48.         <Root level="INFO">
  49.             <AppenderRef ref="Console"/>
  50.             <AppenderRef ref="File"/>
  51.         </Root>
  52.     </Loggers>
  53. </Configuration>
复制代码

通过Hibernate/JPA配置SQL输出

Hibernate是最常用的JPA实现之一,提供了多种方式来配置SQL输出。

在application.properties/yml中配置

最简单的方式是在Spring Boot的配置文件中直接配置Hibernate属性:
  1. # application.properties
  2. # 显示SQL语句
  3. spring.jpa.show-sql=true
  4. # 格式化SQL语句
  5. spring.jpa.properties.hibernate.format_sql=true
  6. # 显示SQL注释
  7. spring.jpa.properties.hibernate.use_sql_comments=true
  8. # 显示统计信息
  9. spring.jpa.properties.hibernate.generate_statistics=true
复制代码

或者使用YAML格式:
  1. # application.yml
  2. spring:
  3.   jpa:
  4.     show-sql: true
  5.     properties:
  6.       hibernate:
  7.         format_sql: true
  8.         use_sql_comments: true
  9.         generate_statistics: true
复制代码

通过编程方式配置

如果你更喜欢通过Java代码进行配置,可以创建一个配置类:
  1. import org.hibernate.SessionFactory;
  2. import org.springframework.beans.factory.annotation.Autowired;
  3. import org.springframework.context.annotation.Bean;
  4. import org.springframework.context.annotation.Configuration;
  5. import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
  6. import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
  7. import javax.sql.DataSource;
  8. import java.util.Properties;
  9. @Configuration
  10. public class HibernateConfig {
  11.     @Autowired
  12.     private DataSource dataSource;
  13.     @Bean
  14.     public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
  15.         LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
  16.         em.setDataSource(dataSource);
  17.         em.setPackagesToScan("com.yourpackage.model");
  18.         HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
  19.         em.setJpaVendorAdapter(vendorAdapter);
  20.         Properties properties = new Properties();
  21.         // 显示SQL语句
  22.         properties.setProperty("hibernate.show_sql", "true");
  23.         // 格式化SQL语句
  24.         properties.setProperty("hibernate.format_sql", "true");
  25.         // 显示SQL注释
  26.         properties.setProperty("hibernate.use_sql_comments", "true");
  27.         // 显示统计信息
  28.         properties.setProperty("hibernate.generate_statistics", "true");
  29.         
  30.         em.setJpaProperties(properties);
  31.         return em;
  32.     }
  33. }
复制代码

使用Hibernate拦截器/监听器输出SQL

Hibernate提供了拦截器(Interceptor)和事件监听器(EventListener)机制,我们可以利用这些机制来输出更详细的SQL信息:
  1. import org.hibernate.EmptyInterceptor;
  2. import org.hibernate.type.Type;
  3. import java.io.Serializable;
  4. import java.util.Iterator;
  5. public class SqlOutputInterceptor extends EmptyInterceptor {
  6.     @Override
  7.     public String onPrepareStatement(String sql) {
  8.         System.out.println("Hibernate SQL: " + sql);
  9.         return super.onPrepareStatement(sql);
  10.     }
  11.     @Override
  12.     public void beforeTransactionCompletion(Transaction tx) {
  13.         System.out.println("Transaction about to complete");
  14.     }
  15. }
复制代码

然后配置Hibernate使用这个拦截器:
  1. @Bean
  2. public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
  3.     LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
  4.     // ... 其他配置
  5.    
  6.     Properties properties = new Properties();
  7.     properties.setProperty("hibernate.ejb.interceptor", "com.yourpackage.interceptor.SqlOutputInterceptor");
  8.    
  9.     em.setJpaProperties(properties);
  10.     return em;
  11. }
复制代码

通过MyBatis配置SQL输出

MyBatis是另一个流行的持久层框架,它提供了灵活的方式来控制SQL输出。

在MyBatis配置文件中设置

在MyBatis的配置文件mybatis-config.xml中,可以设置settings来控制SQL输出:
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
  3. <configuration>
  4.     <settings>
  5.         <!-- 打印查询语句 -->
  6.         <setting name="logImpl" value="STDOUT_LOGGING"/>
  7.         <!-- 其他设置 -->
  8.         <setting name="cacheEnabled" value="true"/>
  9.         <setting name="lazyLoadingEnabled" value="true"/>
  10.         <setting name="multipleResultSetsEnabled" value="true"/>
  11.         <setting name="useColumnLabel" value="true"/>
  12.         <setting name="useGeneratedKeys" value="false"/>
  13.         <setting name="defaultExecutorType" value="SIMPLE"/>
  14.         <setting name="defaultStatementTimeout" value="25000"/>
  15.     </settings>
  16. </configuration>
复制代码

通过Spring Boot配置文件设置

如果你使用的是Spring Boot与MyBatis的集成,可以在application.properties或application.yml中配置:
  1. # application.properties
  2. # MyBatis SQL输出配置
  3. mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
复制代码

或者使用YAML格式:
  1. # application.yml
  2. mybatis:
  3.   configuration:
  4.     log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
复制代码

使用MyBatis拦截器输出SQL

MyBatis提供了拦截器(Interceptor)机制,我们可以利用它来实现更复杂的SQL输出功能:
  1. import org.apache.ibatis.executor.Executor;
  2. import org.apache.ibatis.mapping.BoundSql;
  3. import org.apache.ibatis.mapping.MappedStatement;
  4. import org.apache.ibatis.mapping.ParameterMapping;
  5. import org.apache.ibatis.plugin.*;
  6. import org.apache.ibatis.session.ResultHandler;
  7. import org.apache.ibatis.session.RowBounds;
  8. import java.text.SimpleDateFormat;
  9. import java.util.Date;
  10. import java.util.List;
  11. import java.util.Properties;
  12. @Intercepts({
  13.     @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
  14.     @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
  15. })
  16. public class SqlOutputInterceptor implements Interceptor {
  17.     @Override
  18.     public Object intercept(Invocation invocation) throws Throwable {
  19.         MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
  20.         Object parameter = invocation.getArgs()[1];
  21.         
  22.         BoundSql boundSql = mappedStatement.getBoundSql(parameter);
  23.         String sql = boundSql.getSql();
  24.         List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
  25.         
  26.         // 格式化SQL输出
  27.         String formattedSql = formatSql(sql, parameter, parameterMappings);
  28.         
  29.         // 输出SQL
  30.         System.out.println("=============================================");
  31.         System.out.println("Time: " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date()));
  32.         System.out.println("SQL ID: " + mappedStatement.getId());
  33.         System.out.println("SQL: " + formattedSql);
  34.         System.out.println("=============================================");
  35.         
  36.         // 执行原方法
  37.         Object result = invocation.proceed();
  38.         
  39.         return result;
  40.     }
  41.     private String formatSql(String sql, Object parameter, List<ParameterMapping> parameterMappings) {
  42.         // 这里可以实现SQL格式化逻辑
  43.         // 简单示例:直接返回原SQL
  44.         return sql;
  45.     }
  46.     @Override
  47.     public Object plugin(Object target) {
  48.         return Plugin.wrap(target, this);
  49.     }
  50.     @Override
  51.     public void setProperties(Properties properties) {
  52.         // 可以读取配置的属性
  53.     }
  54. }
复制代码

然后,在MyBatis配置中注册这个拦截器:
  1. import org.apache.ibatis.session.SqlSessionFactory;
  2. import org.mybatis.spring.SqlSessionFactoryBean;
  3. import org.springframework.context.annotation.Bean;
  4. import org.springframework.context.annotation.Configuration;
  5. import javax.sql.DataSource;
  6. @Configuration
  7. public class MyBatisConfig {
  8.     @Bean
  9.     public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
  10.         SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
  11.         sessionFactory.setDataSource(dataSource);
  12.         
  13.         // 注册拦截器
  14.         SqlOutputInterceptor sqlOutputInterceptor = new SqlOutputInterceptor();
  15.         sessionFactory.setPlugins(sqlOutputInterceptor);
  16.         
  17.         return sessionFactory.getObject();
  18.     }
  19. }
复制代码

通过Spring Data JPA配置SQL输出

Spring Data JPA是Spring基于JPA的数据访问抽象层,它简化了数据访问操作。配置Spring Data JPA的SQL输出主要依赖于底层JPA实现(如Hibernate)的配置。

基本配置
  1. # application.properties
  2. # 显示SQL语句
  3. spring.jpa.show-sql=true
  4. # 格式化SQL语句
  5. spring.jpa.properties.hibernate.format_sql=true
  6. # 显示SQL注释
  7. spring.jpa.properties.hibernate.use_sql_comments=true
  8. # 显示统计信息
  9. spring.jpa.properties.hibernate.generate_statistics=true
复制代码

使用Repository接口方法输出SQL

Spring Data JPA允许我们通过Repository接口定义查询方法,我们可以通过自定义Repository实现来输出SQL:
  1. import org.springframework.data.jpa.repository.JpaRepository;
  2. import org.springframework.data.jpa.repository.Query;
  3. import org.springframework.data.repository.query.Param;
  4. public interface UserRepository extends JpaRepository<User, Long>, UserRepositoryCustom {
  5.     @Query("SELECT u FROM User u WHERE u.username = :username")
  6.     User findByUsername(@Param("username") String username);
  7. }
  8. public interface UserRepositoryCustom {
  9.     User customFindById(Long id);
  10. }
  11. public class UserRepositoryImpl implements UserRepositoryCustom {
  12.     @PersistenceContext
  13.     private EntityManager entityManager;
  14.     @Override
  15.     public User customFindById(Long id) {
  16.         // 使用EntityManager创建查询
  17.         TypedQuery<User> query = entityManager.createQuery("SELECT u FROM User u WHERE u.id = :id", User.class);
  18.         query.setParameter("id", id);
  19.         
  20.         // 输出SQL
  21.         System.out.println("Executing custom query: " + query.unwrap(org.hibernate.Query.class).getQueryString());
  22.         
  23.         return query.getSingleResult();
  24.     }
  25. }
复制代码

使用JPA EntityListener输出SQL

JPA提供了实体监听器(EntityListener)机制,我们可以利用它来监控实体的生命周期事件:
  1. import javax.persistence.*;
  2. public class EntityListener {
  3.     @PrePersist
  4.     public void prePersist(Object entity) {
  5.         System.out.println("About to persist: " + entity);
  6.     }
  7.     @PostPersist
  8.     public void postPersist(Object entity) {
  9.         System.out.println("Persisted: " + entity);
  10.     }
  11.     @PreUpdate
  12.     public void preUpdate(Object entity) {
  13.         System.out.println("About to update: " + entity);
  14.     }
  15.     @PostUpdate
  16.     public void postUpdate(Object entity) {
  17.         System.out.println("Updated: " + entity);
  18.     }
  19.     @PreRemove
  20.     public void preRemove(Object entity) {
  21.         System.out.println("About to remove: " + entity);
  22.     }
  23.     @PostRemove
  24.     public void postRemove(Object entity) {
  25.         System.out.println("Removed: " + entity);
  26.     }
  27. }
  28. @Entity
  29. @EntityListeners(EntityListener.class)
  30. @Table(name = "users")
  31. public class User {
  32.     @Id
  33.     @GeneratedValue(strategy = GenerationType.IDENTITY)
  34.     private Long id;
  35.    
  36.     @Column(name = "username")
  37.     private String username;
  38.    
  39.     @Column(name = "email")
  40.     private String email;
  41.    
  42.     // getters and setters
  43. }
复制代码

格式化和美化输出的SQL语句

原始的SQL输出通常是一行字符串,难以阅读。我们可以通过一些工具和技术来格式化和美化SQL输出。

使用Hibernate内置格式化功能

Hibernate提供了内置的SQL格式化功能:
  1. # application.properties
  2. spring.jpa.properties.hibernate.format_sql=true
复制代码

这将使Hibernate输出格式化的SQL,例如:
  1. SELECT
  2.     user0_.id AS id1_0_,
  3.     user0_.email AS email2_0_,
  4.     user0_.username AS username3_0_
  5. FROM
  6.     users user0_
  7. WHERE
  8.     user0_.username=?
复制代码

使用P6Spy格式化SQL

P6Spy是一个JDBC驱动包装器,可以拦截和记录数据库操作。以下是使用P6Spy格式化SQL的步骤:

1. 添加P6Spy依赖:
  1. <!-- pom.xml -->
  2. <dependency>
  3.     <groupId>p6spy</groupId>
  4.     <artifactId>p6spy</artifactId>
  5.     <version>3.9.1</version>
  6. </dependency>
复制代码

1. 配置P6Spy:

在src/main/resources目录下创建spy.properties文件:
  1. # P6Spy配置文件
  2. modulelist=com.p6spy.engine.logging.P6LogFactory,com.p6spy.engine.outage.P6OutageFactory
  3. # 自定义日志格式
  4. logMessageFormat=com.p6spy.engine.spy.appender.MultiLineFormat
  5. # 使用自定义的日志记录器
  6. appender=com.p6spy.engine.spy.appender.StdoutLogger
  7. # 是否记录JDBC操作
  8. deregisterdrivers=true
复制代码

1. 修改数据源配置:
  1. # application.properties
  2. # 将原来的JDBC URL改为P6Spy格式
  3. spring.datasource.url=jdbc:p6spy:mysql://localhost:3306/your_database
  4. spring.datasource.username=your_username
  5. spring.datasource.password=your_password
  6. spring.datasource.driver-class-name=com.p6spy.engine.spy.P6SpyDriver
复制代码

自定义SQL格式化工具

你也可以创建自己的SQL格式化工具:
  1. import org.apache.commons.lang3.StringUtils;
  2. import org.hibernate.engine.spi.QueryParameters;
  3. import org.hibernate.engine.spi.SessionImplementor;
  4. import org.hibernate.loader.custom.CustomLoader;
  5. import org.hibernate.loader.custom.SQLQueryReturn;
  6. import org.hibernate.persister.collection.SQLLoadableCollection;
  7. import org.hibernate.persister.entity.SQLLoadable;
  8. import java.util.List;
  9. public class SqlFormatter {
  10.     public static String formatSql(String sql, Object[] parameters) {
  11.         // 简单的SQL格式化实现
  12.         if (StringUtils.isBlank(sql)) {
  13.             return "";
  14.         }
  15.         // 添加换行和缩进
  16.         String formattedSql = sql.replaceAll("(?i)\\bSELECT\\b", "\nSELECT")
  17.                 .replaceAll("(?i)\\bFROM\\b", "\nFROM")
  18.                 .replaceAll("(?i)\\bWHERE\\b", "\nWHERE")
  19.                 .replaceAll("(?i)\\bGROUP BY\\b", "\nGROUP BY")
  20.                 .replaceAll("(?i)\\bORDER BY\\b", "\nORDER BY")
  21.                 .replaceAll("(?i)\\bHAVING\\b", "\nHAVING")
  22.                 .replaceAll("(?i)\\bAND\\b", "\n    AND")
  23.                 .replaceAll("(?i)\\bOR\\b", "\n    OR");
  24.         // 替换参数占位符
  25.         if (parameters != null && parameters.length > 0) {
  26.             for (Object param : parameters) {
  27.                 String paramStr = param != null ? param.toString() : "NULL";
  28.                 formattedSql = formattedSql.replaceFirst("\\?", "'" + paramStr + "'");
  29.             }
  30.         }
  31.         return formattedSql;
  32.     }
  33. }
复制代码

然后,在拦截器或监听器中使用这个格式化工具:
  1. @Override
  2. public String onPrepareStatement(String sql) {
  3.     // 获取参数(这里需要根据实际情况获取)
  4.     Object[] parameters = getParameters();
  5.    
  6.     // 格式化SQL
  7.     String formattedSql = SqlFormatter.formatSql(sql, parameters);
  8.    
  9.     System.out.println("Formatted SQL:\n" + formattedSql);
  10.     return sql;
  11. }
复制代码

SQL输出中常见问题及解决方案

在配置SQL输出过程中,可能会遇到一些常见问题。下面列出了一些问题及其解决方案。

问题1:SQL语句没有输出

可能原因:

• 日志级别设置不正确
• 配置文件位置或名称不正确
• 没有正确加载配置文件

解决方案:

1. 检查日志级别设置是否正确,确保相关包的日志级别设置为DEBUG或TRACE:
  1. logging.level.org.hibernate.SQL=DEBUG
  2. logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE
复制代码

1. 确认配置文件位置和名称是否正确:Logback配置文件应位于src/main/resources/logback-spring.xmlLog4j2配置文件应位于src/main/resources/log4j2-spring.xmlMyBatis配置文件应位于src/main/resources/mybatis-config.xml
2. Logback配置文件应位于src/main/resources/logback-spring.xml
3. Log4j2配置文件应位于src/main/resources/log4j2-spring.xml
4. MyBatis配置文件应位于src/main/resources/mybatis-config.xml
5. 确保配置文件被正确加载,可以在应用启动时检查日志输出。

确认配置文件位置和名称是否正确:

• Logback配置文件应位于src/main/resources/logback-spring.xml
• Log4j2配置文件应位于src/main/resources/log4j2-spring.xml
• MyBatis配置文件应位于src/main/resources/mybatis-config.xml

确保配置文件被正确加载,可以在应用启动时检查日志输出。

问题2:SQL语句输出但参数值显示为”?”

可能原因:

• 没有配置参数输出
• 参数日志级别设置不正确

解决方案:

1. 确保参数输出配置正确:
  1. # Hibernate参数输出
  2. logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE
  3. # MyBatis参数输出
  4. mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
复制代码

1. 使用P6Spy或其他工具来显示参数值:
  1. # spy.properties
  2. logMessageFormat=com.p6spy.engine.spy.appender.MultiLineFormat
  3. appender=com.p6spy.engine.spy.appender.StdoutLogger
复制代码

问题3:SQL语句输出太多,影响性能

可能原因:

• 生产环境开启了SQL输出
• 日志级别设置过低

解决方案:

1. 在生产环境中关闭SQL输出或提高日志级别:
  1. # 生产环境配置
  2. spring.jpa.show-sql=false
  3. logging.level.org.hibernate.SQL=INFO
复制代码

1. 使用Spring Profile来区分开发和生产环境:
  1. # application-dev.properties
  2. spring.jpa.show-sql=true
  3. logging.level.org.hibernate.SQL=DEBUG
  4. # application-prod.properties
  5. spring.jpa.show-sql=false
  6. logging.level.org.hibernate.SQL=INFO
复制代码

1. 使用条件化配置:
  1. @Bean
  2. @Profile("dev")
  3. public DataSource dataSource() {
  4.     // 开发环境数据源配置
  5. }
  6. @Bean
  7. @Profile("prod")
  8. public DataSource dataSource() {
  9.     // 生产环境数据源配置
  10. }
复制代码

问题4:SQL语句格式不美观,难以阅读

可能原因:

• 没有启用SQL格式化
• 使用了默认的简单格式化器

解决方案:

1. 启用Hibernate的SQL格式化:
  1. spring.jpa.properties.hibernate.format_sql=true
复制代码

1. 使用P6Spy或其他格式化工具:
  1. # spy.properties
  2. logMessageFormat=com.p6spy.engine.spy.appender.MultiLineFormat
复制代码

1. 自定义SQL格式化器:
  1. public class CustomSqlFormatter implements MessageFormattingStrategy {
  2.     @Override
  3.     public String formatMessage(int connectionId, String now, long elapsed, String category, String prepared, String sql, String url) {
  4.         return !"".equals(sql.trim()) ? "=============================\n" +
  5.                 "Execute SQL: " + formatSql(sql) + "\n" +
  6.                 "Execution Time: " + elapsed + " ms\n" +
  7.                 "=============================" : "";
  8.     }
  9.     private String formatSql(String sql) {
  10.         // 实现SQL格式化逻辑
  11.         return sql;
  12.     }
  13. }
复制代码

然后在spy.properties中配置:
  1. logMessageFormat=com.yourpackage.CustomSqlFormatter
复制代码

实际应用示例

让我们通过一个完整的Spring MVC应用示例,展示如何配置和实现SQL语句输出。

项目结构
  1. src/main/java/com/example/demo/
  2. ├── config/
  3. │   ├── HibernateConfig.java
  4. │   └── MyBatisConfig.java
  5. ├── controller/
  6. │   └── UserController.java
  7. ├── interceptor/
  8. │   └── SqlOutputInterceptor.java
  9. ├── model/
  10. │   └── User.java
  11. ├── repository/
  12. │   ├── UserRepository.java
  13. │   └── UserRepositoryImpl.java
  14. └── DemoApplication.java
  15. src/main/resources/
  16. ├── application.yml
  17. ├── logback-spring.xml
  18. └── mybatis-config.xml
复制代码

配置文件
  1. spring:
  2.   datasource:
  3.     url: jdbc:mysql://localhost:3306/demo_db
  4.     username: root
  5.     password: password
  6.     driver-class-name: com.mysql.cj.jdbc.Driver
  7.   jpa:
  8.     show-sql: true
  9.     hibernate:
  10.       ddl-auto: update
  11.     properties:
  12.       hibernate:
  13.         format_sql: true
  14.         use_sql_comments: true
  15.         generate_statistics: true
  16. mybatis:
  17.   mapper-locations: classpath:mapper/*.xml
  18.   configuration:
  19.     log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  20. logging:
  21.   level:
  22.     org.hibernate.SQL: DEBUG
  23.     org.hibernate.type.descriptor.sql.BasicBinder: TRACE
  24.     org.springframework.data: DEBUG
  25.     com.example.demo.mapper: DEBUG
复制代码
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <configuration>
  3.     <property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"/>
  4.    
  5.     <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
  6.         <encoder>
  7.             <pattern>${LOG_PATTERN}</pattern>
  8.         </encoder>
  9.     </appender>
  10.    
  11.     <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
  12.         <file>logs/demo.log</file>
  13.         <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
  14.             <fileNamePattern>logs/demo.%d{yyyy-MM-dd}.log</fileNamePattern>
  15.             <maxHistory>30</maxHistory>
  16.         </rollingPolicy>
  17.         <encoder>
  18.             <pattern>${LOG_PATTERN}</pattern>
  19.         </encoder>
  20.     </appender>
  21.    
  22.     <logger name="org.hibernate.SQL" level="DEBUG" additivity="false">
  23.         <appender-ref ref="CONSOLE"/>
  24.         <appender-ref ref="FILE"/>
  25.     </logger>
  26.    
  27.     <logger name="org.hibernate.type.descriptor.sql.BasicBinder" level="TRACE" additivity="false">
  28.         <appender-ref ref="CONSOLE"/>
  29.         <appender-ref ref="FILE"/>
  30.     </logger>
  31.    
  32.     <logger name="org.springframework.data" level="DEBUG" additivity="false">
  33.         <appender-ref ref="CONSOLE"/>
  34.         <appender-ref ref="FILE"/>
  35.     </logger>
  36.    
  37.     <logger name="com.example.demo.mapper" level="DEBUG" additivity="false">
  38.         <appender-ref ref="CONSOLE"/>
  39.         <appender-ref ref="FILE"/>
  40.     </logger>
  41.    
  42.     <root level="INFO">
  43.         <appender-ref ref="CONSOLE"/>
  44.         <appender-ref ref="FILE"/>
  45.     </root>
  46. </configuration>
复制代码
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
  3. <configuration>
  4.     <settings>
  5.         <setting name="logImpl" value="STDOUT_LOGGING"/>
  6.     </settings>
  7. </configuration>
复制代码

Java代码
  1. import javax.persistence.*;
  2. import java.io.Serializable;
  3. @Entity
  4. @Table(name = "users")
  5. @EntityListeners(EntityListener.class)
  6. public class User implements Serializable {
  7.     @Id
  8.     @GeneratedValue(strategy = GenerationType.IDENTITY)
  9.     private Long id;
  10.    
  11.     @Column(name = "username", nullable = false, unique = true)
  12.     private String username;
  13.    
  14.     @Column(name = "email", nullable = false)
  15.     private String email;
  16.    
  17.     // 构造函数
  18.     public User() {}
  19.    
  20.     public User(String username, String email) {
  21.         this.username = username;
  22.         this.email = email;
  23.     }
  24.    
  25.     // getters and setters
  26.     public Long getId() {
  27.         return id;
  28.     }
  29.    
  30.     public void setId(Long id) {
  31.         this.id = id;
  32.     }
  33.    
  34.     public String getUsername() {
  35.         return username;
  36.     }
  37.    
  38.     public void setUsername(String username) {
  39.         this.username = username;
  40.     }
  41.    
  42.     public String getEmail() {
  43.         return email;
  44.     }
  45.    
  46.     public void setEmail(String email) {
  47.         this.email = email;
  48.     }
  49. }
复制代码
  1. import javax.persistence.*;
  2. public class EntityListener {
  3.     @PrePersist
  4.     public void prePersist(Object entity) {
  5.         System.out.println("About to persist: " + entity);
  6.     }
  7.     @PostPersist
  8.     public void postPersist(Object entity) {
  9.         System.out.println("Persisted: " + entity);
  10.     }
  11.     @PreUpdate
  12.     public void preUpdate(Object entity) {
  13.         System.out.println("About to update: " + entity);
  14.     }
  15.     @PostUpdate
  16.     public void postUpdate(Object entity) {
  17.         System.out.println("Updated: " + entity);
  18.     }
  19.     @PreRemove
  20.     public void preRemove(Object entity) {
  21.         System.out.println("About to remove: " + entity);
  22.     }
  23.     @PostRemove
  24.     public void postRemove(Object entity) {
  25.         System.out.println("Removed: " + entity);
  26.     }
  27. }
复制代码
  1. import com.example.demo.model.User;
  2. import org.springframework.data.jpa.repository.JpaRepository;
  3. import org.springframework.data.jpa.repository.Query;
  4. import org.springframework.data.repository.query.Param;
  5. public interface UserRepository extends JpaRepository<User, Long>, UserRepositoryCustom {
  6.     @Query("SELECT u FROM User u WHERE u.username = :username")
  7.     User findByUsername(@Param("username") String username);
  8. }
复制代码
  1. import com.example.demo.model.User;
  2. public interface UserRepositoryCustom {
  3.     User customFindById(Long id);
  4. }
复制代码
  1. import com.example.demo.model.User;
  2. import javax.persistence.EntityManager;
  3. import javax.persistence.PersistenceContext;
  4. import javax.persistence.TypedQuery;
  5. public class UserRepositoryImpl implements UserRepositoryCustom {
  6.     @PersistenceContext
  7.     private EntityManager entityManager;
  8.     @Override
  9.     public User customFindById(Long id) {
  10.         TypedQuery<User> query = entityManager.createQuery("SELECT u FROM User u WHERE u.id = :id", User.class);
  11.         query.setParameter("id", id);
  12.         
  13.         // 输出SQL
  14.         System.out.println("Executing custom query: " + query.unwrap(org.hibernate.Query.class).getQueryString());
  15.         
  16.         return query.getSingleResult();
  17.     }
  18. }
复制代码
  1. import org.apache.ibatis.executor.Executor;
  2. import org.apache.ibatis.mapping.BoundSql;
  3. import org.apache.ibatis.mapping.MappedStatement;
  4. import org.apache.ibatis.plugin.*;
  5. import org.apache.ibatis.session.ResultHandler;
  6. import org.apache.ibatis.session.RowBounds;
  7. import java.text.SimpleDateFormat;
  8. import java.util.Date;
  9. import java.util.Properties;
  10. @Intercepts({
  11.     @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
  12.     @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
  13. })
  14. public class SqlOutputInterceptor implements Interceptor {
  15.     @Override
  16.     public Object intercept(Invocation invocation) throws Throwable {
  17.         MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
  18.         Object parameter = invocation.getArgs()[1];
  19.         
  20.         BoundSql boundSql = mappedStatement.getBoundSql(parameter);
  21.         String sql = boundSql.getSql();
  22.         
  23.         // 输出SQL
  24.         System.out.println("=============================================");
  25.         System.out.println("Time: " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date()));
  26.         System.out.println("SQL ID: " + mappedStatement.getId());
  27.         System.out.println("SQL: " + sql);
  28.         System.out.println("=============================================");
  29.         
  30.         // 执行原方法
  31.         Object result = invocation.proceed();
  32.         
  33.         return result;
  34.     }
  35.     @Override
  36.     public Object plugin(Object target) {
  37.         return Plugin.wrap(target, this);
  38.     }
  39.     @Override
  40.     public void setProperties(Properties properties) {
  41.         // 可以读取配置的属性
  42.     }
  43. }
复制代码
  1. import com.example.demo.model.User;
  2. import com.example.demo.repository.UserRepository;
  3. import org.springframework.beans.factory.annotation.Autowired;
  4. import org.springframework.web.bind.annotation.*;
  5. import java.util.List;
  6. import java.util.Optional;
  7. @RestController
  8. @RequestMapping("/api/users")
  9. public class UserController {
  10.     @Autowired
  11.     private UserRepository userRepository;
  12.     @GetMapping
  13.     public List<User> getAllUsers() {
  14.         return userRepository.findAll();
  15.     }
  16.     @GetMapping("/{id}")
  17.     public User getUserById(@PathVariable Long id) {
  18.         // 使用自定义查询方法
  19.         return userRepository.customFindById(id);
  20.     }
  21.     @GetMapping("/username/{username}")
  22.     public User getUserByUsername(@PathVariable String username) {
  23.         return userRepository.findByUsername(username);
  24.     }
  25.     @PostMapping
  26.     public User createUser(@RequestBody User user) {
  27.         return userRepository.save(user);
  28.     }
  29.     @PutMapping("/{id}")
  30.     public User updateUser(@PathVariable Long id, @RequestBody User userDetails) {
  31.         Optional<User> userOptional = userRepository.findById(id);
  32.         if (userOptional.isPresent()) {
  33.             User user = userOptional.get();
  34.             user.setUsername(userDetails.getUsername());
  35.             user.setEmail(userDetails.getEmail());
  36.             return userRepository.save(user);
  37.         }
  38.         return null;
  39.     }
  40.     @DeleteMapping("/{id}")
  41.     public void deleteUser(@PathVariable Long id) {
  42.         userRepository.deleteById(id);
  43.     }
  44. }
复制代码
  1. import org.springframework.boot.SpringApplication;
  2. import org.springframework.boot.autoconfigure.SpringBootApplication;
  3. @SpringBootApplication
  4. public class DemoApplication {
  5.     public static void main(String[] args) {
  6.         SpringApplication.run(DemoApplication.class, args);
  7.     }
  8. }
复制代码

运行示例

当运行这个应用并访问API时,你将在控制台和日志文件中看到类似以下的SQL输出:
  1. =============================================
  2. Time: 2023-05-20 14:30:45.123
  3. SQL ID: com.example.demo.repository.UserRepository.customFindById
  4. SQL: SELECT u FROM User u WHERE u.id = ?
  5. =============================================
  6. Hibernate:
  7.     select
  8.         user0_.id as id1_0_,
  9.         user0_.email as email2_0_,
  10.         user0_.username as username3_0_
  11.     from
  12.         users user0_
  13.     where
  14.         user0_.id=?
  15. 2023-05-20 14:30:45.125 TRACE 12345 --- [nio-8080-exec-1] o.h.type.descriptor.sql.BasicBinder   : binding parameter [1] as [BIGINT] - [1]
  16. About to update: User{id=1, username='john_doe', email='john@example.com'}
  17. Updated: User{id=1, username='john_doe_updated', email='john.updated@example.com'}
复制代码

总结

在Spring MVC应用中配置和实现SQL语句输出是数据库操作调试的重要手段。本文详细介绍了多种配置方法,包括:

1. 通过日志框架(Logback、Log4j2)配置SQL输出
2. 通过Hibernate/JPA配置SQL输出
3. 通过MyBatis配置SQL输出
4. 通过Spring Data JPA配置SQL输出
5. 格式化和美化SQL输出的方法
6. 常见问题及解决方案

通过这些方法,开发者可以轻松地在开发和测试阶段查看实际执行的SQL语句及其参数,从而更高效地调试数据库操作。

在实际应用中,建议根据项目需求和环境选择合适的SQL输出方式,并注意在生产环境中关闭或限制SQL输出,以避免性能问题和安全风险。同时,利用条件化配置(如Spring Profile)来区分不同环境的配置,是一个良好的实践。

希望本文能帮助你在Spring MVC应用中轻松实现SQL语句输出,提高数据库操作调试的效率。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

频道订阅

频道订阅

加入社群

加入社群

联系我们|TG频道|RSS

Powered by Pixtech

© 2025 Pixtech Team.