|
|
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
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文件:
- <?xml version="1.0" encoding="UTF-8"?>
- <configuration>
- <!-- 定义日志输出格式 -->
- <property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"/>
-
- <!-- 控制台输出 -->
- <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
- <encoder>
- <pattern>${LOG_PATTERN}</pattern>
- </encoder>
- </appender>
-
- <!-- 文件输出 -->
- <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
- <file>logs/application.log</file>
- <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
- <fileNamePattern>logs/application.%d{yyyy-MM-dd}.log</fileNamePattern>
- <maxHistory>30</maxHistory>
- </rollingPolicy>
- <encoder>
- <pattern>${LOG_PATTERN}</pattern>
- </encoder>
- </appender>
-
- <!-- Hibernate SQL输出配置 -->
- <logger name="org.hibernate.SQL" level="DEBUG" additivity="false">
- <appender-ref ref="CONSOLE"/>
- <appender-ref ref="FILE"/>
- </logger>
-
- <!-- Hibernate SQL参数输出配置 -->
- <logger name="org.hibernate.type.descriptor.sql.BasicBinder" level="TRACE" additivity="false">
- <appender-ref ref="CONSOLE"/>
- <appender-ref ref="FILE"/>
- </logger>
-
- <!-- Spring Data JPA Repository输出配置 -->
- <logger name="org.springframework.data" level="DEBUG" additivity="false">
- <appender-ref ref="CONSOLE"/>
- <appender-ref ref="FILE"/>
- </logger>
-
- <!-- MyBatis SQL输出配置 -->
- <logger name="com.yourpackage.mapper" level="DEBUG" additivity="false">
- <appender-ref ref="CONSOLE"/>
- <appender-ref ref="FILE"/>
- </logger>
-
- <!-- 根日志级别 -->
- <root level="INFO">
- <appender-ref ref="CONSOLE"/>
- <appender-ref ref="FILE"/>
- </root>
- </configuration>
复制代码
1. 在application.properties或application.yml中配置日志级别:
- # application.properties
- logging.level.org.hibernate.SQL=DEBUG
- logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE
- logging.level.org.springframework.data=DEBUG
- logging.level.com.yourpackage.mapper=DEBUG
复制代码
或者使用YAML格式:
- # application.yml
- logging:
- level:
- org.hibernate.SQL: DEBUG
- org.hibernate.type.descriptor.sql.BasicBinder: TRACE
- org.springframework.data: DEBUG
- com.yourpackage.mapper: DEBUG
复制代码
使用Log4j2配置SQL输出
Log4j2是另一个强大的日志框架,性能优异。以下是通过Log4j2配置SQL输出的方法:
1. 首先,添加Log4j2依赖(如果使用Spring Boot,需要排除默认的Logback):
- <!-- pom.xml -->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-web</artifactId>
- <exclusions>
- <exclusion>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-logging</artifactId>
- </exclusion>
- </exclusions>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-log4j2</artifactId>
- </dependency>
复制代码
1. 在src/main/resources目录下创建log4j2-spring.xml文件:
- <?xml version="1.0" encoding="UTF-8"?>
- <Configuration status="WARN">
- <Properties>
- <Property name="LOG_PATTERN">%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</Property>
- </Properties>
-
- <Appenders>
- <Console name="Console" target="SYSTEM_OUT">
- <PatternLayout pattern="${LOG_PATTERN}"/>
- </Console>
-
- <RollingFile name="File" fileName="logs/application.log"
- filePattern="logs/application-%d{yyyy-MM-dd}-%i.log">
- <PatternLayout pattern="${LOG_PATTERN}"/>
- <Policies>
- <TimeBasedTriggeringPolicy/>
- <SizeBasedTriggeringPolicy size="10 MB"/>
- </Policies>
- <DefaultRolloverStrategy max="10"/>
- </RollingFile>
- </Appenders>
-
- <Loggers>
- <!-- Hibernate SQL输出配置 -->
- <Logger name="org.hibernate.SQL" level="DEBUG" additivity="false">
- <AppenderRef ref="Console"/>
- <AppenderRef ref="File"/>
- </Logger>
-
- <!-- Hibernate SQL参数输出配置 -->
- <Logger name="org.hibernate.type.descriptor.sql.BasicBinder" level="TRACE" additivity="false">
- <AppenderRef ref="Console"/>
- <AppenderRef ref="File"/>
- </Logger>
-
- <!-- Spring Data JPA Repository输出配置 -->
- <Logger name="org.springframework.data" level="DEBUG" additivity="false">
- <AppenderRef ref="Console"/>
- <AppenderRef ref="File"/>
- </Logger>
-
- <!-- MyBatis SQL输出配置 -->
- <Logger name="com.yourpackage.mapper" level="DEBUG" additivity="false">
- <AppenderRef ref="Console"/>
- <AppenderRef ref="File"/>
- </Logger>
-
- <Root level="INFO">
- <AppenderRef ref="Console"/>
- <AppenderRef ref="File"/>
- </Root>
- </Loggers>
- </Configuration>
复制代码
通过Hibernate/JPA配置SQL输出
Hibernate是最常用的JPA实现之一,提供了多种方式来配置SQL输出。
在application.properties/yml中配置
最简单的方式是在Spring Boot的配置文件中直接配置Hibernate属性:
- # application.properties
- # 显示SQL语句
- spring.jpa.show-sql=true
- # 格式化SQL语句
- spring.jpa.properties.hibernate.format_sql=true
- # 显示SQL注释
- spring.jpa.properties.hibernate.use_sql_comments=true
- # 显示统计信息
- spring.jpa.properties.hibernate.generate_statistics=true
复制代码
或者使用YAML格式:
- # application.yml
- spring:
- jpa:
- show-sql: true
- properties:
- hibernate:
- format_sql: true
- use_sql_comments: true
- generate_statistics: true
复制代码
通过编程方式配置
如果你更喜欢通过Java代码进行配置,可以创建一个配置类:
- import org.hibernate.SessionFactory;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
- import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
- import javax.sql.DataSource;
- import java.util.Properties;
- @Configuration
- public class HibernateConfig {
- @Autowired
- private DataSource dataSource;
- @Bean
- public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
- LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
- em.setDataSource(dataSource);
- em.setPackagesToScan("com.yourpackage.model");
- HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
- em.setJpaVendorAdapter(vendorAdapter);
- Properties properties = new Properties();
- // 显示SQL语句
- properties.setProperty("hibernate.show_sql", "true");
- // 格式化SQL语句
- properties.setProperty("hibernate.format_sql", "true");
- // 显示SQL注释
- properties.setProperty("hibernate.use_sql_comments", "true");
- // 显示统计信息
- properties.setProperty("hibernate.generate_statistics", "true");
-
- em.setJpaProperties(properties);
- return em;
- }
- }
复制代码
使用Hibernate拦截器/监听器输出SQL
Hibernate提供了拦截器(Interceptor)和事件监听器(EventListener)机制,我们可以利用这些机制来输出更详细的SQL信息:
- import org.hibernate.EmptyInterceptor;
- import org.hibernate.type.Type;
- import java.io.Serializable;
- import java.util.Iterator;
- public class SqlOutputInterceptor extends EmptyInterceptor {
- @Override
- public String onPrepareStatement(String sql) {
- System.out.println("Hibernate SQL: " + sql);
- return super.onPrepareStatement(sql);
- }
- @Override
- public void beforeTransactionCompletion(Transaction tx) {
- System.out.println("Transaction about to complete");
- }
- }
复制代码
然后配置Hibernate使用这个拦截器:
- @Bean
- public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
- LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
- // ... 其他配置
-
- Properties properties = new Properties();
- properties.setProperty("hibernate.ejb.interceptor", "com.yourpackage.interceptor.SqlOutputInterceptor");
-
- em.setJpaProperties(properties);
- return em;
- }
复制代码
通过MyBatis配置SQL输出
MyBatis是另一个流行的持久层框架,它提供了灵活的方式来控制SQL输出。
在MyBatis配置文件中设置
在MyBatis的配置文件mybatis-config.xml中,可以设置settings来控制SQL输出:
- <?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>
- <settings>
- <!-- 打印查询语句 -->
- <setting name="logImpl" value="STDOUT_LOGGING"/>
- <!-- 其他设置 -->
- <setting name="cacheEnabled" value="true"/>
- <setting name="lazyLoadingEnabled" value="true"/>
- <setting name="multipleResultSetsEnabled" value="true"/>
- <setting name="useColumnLabel" value="true"/>
- <setting name="useGeneratedKeys" value="false"/>
- <setting name="defaultExecutorType" value="SIMPLE"/>
- <setting name="defaultStatementTimeout" value="25000"/>
- </settings>
- </configuration>
复制代码
通过Spring Boot配置文件设置
如果你使用的是Spring Boot与MyBatis的集成,可以在application.properties或application.yml中配置:
- # application.properties
- # MyBatis SQL输出配置
- mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
复制代码
或者使用YAML格式:
- # application.yml
- mybatis:
- configuration:
- log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
复制代码
使用MyBatis拦截器输出SQL
MyBatis提供了拦截器(Interceptor)机制,我们可以利用它来实现更复杂的SQL输出功能:
- import org.apache.ibatis.executor.Executor;
- import org.apache.ibatis.mapping.BoundSql;
- import org.apache.ibatis.mapping.MappedStatement;
- import org.apache.ibatis.mapping.ParameterMapping;
- import org.apache.ibatis.plugin.*;
- import org.apache.ibatis.session.ResultHandler;
- import org.apache.ibatis.session.RowBounds;
- import java.text.SimpleDateFormat;
- import java.util.Date;
- import java.util.List;
- import java.util.Properties;
- @Intercepts({
- @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
- @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
- })
- public class SqlOutputInterceptor implements Interceptor {
- @Override
- public Object intercept(Invocation invocation) throws Throwable {
- MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
- Object parameter = invocation.getArgs()[1];
-
- BoundSql boundSql = mappedStatement.getBoundSql(parameter);
- String sql = boundSql.getSql();
- List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
-
- // 格式化SQL输出
- String formattedSql = formatSql(sql, parameter, parameterMappings);
-
- // 输出SQL
- System.out.println("=============================================");
- System.out.println("Time: " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date()));
- System.out.println("SQL ID: " + mappedStatement.getId());
- System.out.println("SQL: " + formattedSql);
- System.out.println("=============================================");
-
- // 执行原方法
- Object result = invocation.proceed();
-
- return result;
- }
- private String formatSql(String sql, Object parameter, List<ParameterMapping> parameterMappings) {
- // 这里可以实现SQL格式化逻辑
- // 简单示例:直接返回原SQL
- return sql;
- }
- @Override
- public Object plugin(Object target) {
- return Plugin.wrap(target, this);
- }
- @Override
- public void setProperties(Properties properties) {
- // 可以读取配置的属性
- }
- }
复制代码
然后,在MyBatis配置中注册这个拦截器:
- import org.apache.ibatis.session.SqlSessionFactory;
- import org.mybatis.spring.SqlSessionFactoryBean;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- import javax.sql.DataSource;
- @Configuration
- public class MyBatisConfig {
- @Bean
- public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
- SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
- sessionFactory.setDataSource(dataSource);
-
- // 注册拦截器
- SqlOutputInterceptor sqlOutputInterceptor = new SqlOutputInterceptor();
- sessionFactory.setPlugins(sqlOutputInterceptor);
-
- return sessionFactory.getObject();
- }
- }
复制代码
通过Spring Data JPA配置SQL输出
Spring Data JPA是Spring基于JPA的数据访问抽象层,它简化了数据访问操作。配置Spring Data JPA的SQL输出主要依赖于底层JPA实现(如Hibernate)的配置。
基本配置
- # application.properties
- # 显示SQL语句
- spring.jpa.show-sql=true
- # 格式化SQL语句
- spring.jpa.properties.hibernate.format_sql=true
- # 显示SQL注释
- spring.jpa.properties.hibernate.use_sql_comments=true
- # 显示统计信息
- spring.jpa.properties.hibernate.generate_statistics=true
复制代码
使用Repository接口方法输出SQL
Spring Data JPA允许我们通过Repository接口定义查询方法,我们可以通过自定义Repository实现来输出SQL:
- import org.springframework.data.jpa.repository.JpaRepository;
- import org.springframework.data.jpa.repository.Query;
- import org.springframework.data.repository.query.Param;
- public interface UserRepository extends JpaRepository<User, Long>, UserRepositoryCustom {
- @Query("SELECT u FROM User u WHERE u.username = :username")
- User findByUsername(@Param("username") String username);
- }
- public interface UserRepositoryCustom {
- User customFindById(Long id);
- }
- public class UserRepositoryImpl implements UserRepositoryCustom {
- @PersistenceContext
- private EntityManager entityManager;
- @Override
- public User customFindById(Long id) {
- // 使用EntityManager创建查询
- TypedQuery<User> query = entityManager.createQuery("SELECT u FROM User u WHERE u.id = :id", User.class);
- query.setParameter("id", id);
-
- // 输出SQL
- System.out.println("Executing custom query: " + query.unwrap(org.hibernate.Query.class).getQueryString());
-
- return query.getSingleResult();
- }
- }
复制代码
使用JPA EntityListener输出SQL
JPA提供了实体监听器(EntityListener)机制,我们可以利用它来监控实体的生命周期事件:
- import javax.persistence.*;
- public class EntityListener {
- @PrePersist
- public void prePersist(Object entity) {
- System.out.println("About to persist: " + entity);
- }
- @PostPersist
- public void postPersist(Object entity) {
- System.out.println("Persisted: " + entity);
- }
- @PreUpdate
- public void preUpdate(Object entity) {
- System.out.println("About to update: " + entity);
- }
- @PostUpdate
- public void postUpdate(Object entity) {
- System.out.println("Updated: " + entity);
- }
- @PreRemove
- public void preRemove(Object entity) {
- System.out.println("About to remove: " + entity);
- }
- @PostRemove
- public void postRemove(Object entity) {
- System.out.println("Removed: " + entity);
- }
- }
- @Entity
- @EntityListeners(EntityListener.class)
- @Table(name = "users")
- public class User {
- @Id
- @GeneratedValue(strategy = GenerationType.IDENTITY)
- private Long id;
-
- @Column(name = "username")
- private String username;
-
- @Column(name = "email")
- private String email;
-
- // getters and setters
- }
复制代码
格式化和美化输出的SQL语句
原始的SQL输出通常是一行字符串,难以阅读。我们可以通过一些工具和技术来格式化和美化SQL输出。
使用Hibernate内置格式化功能
Hibernate提供了内置的SQL格式化功能:
- # application.properties
- spring.jpa.properties.hibernate.format_sql=true
复制代码
这将使Hibernate输出格式化的SQL,例如:
- SELECT
- user0_.id AS id1_0_,
- user0_.email AS email2_0_,
- user0_.username AS username3_0_
- FROM
- users user0_
- WHERE
- user0_.username=?
复制代码
使用P6Spy格式化SQL
P6Spy是一个JDBC驱动包装器,可以拦截和记录数据库操作。以下是使用P6Spy格式化SQL的步骤:
1. 添加P6Spy依赖:
- <!-- pom.xml -->
- <dependency>
- <groupId>p6spy</groupId>
- <artifactId>p6spy</artifactId>
- <version>3.9.1</version>
- </dependency>
复制代码
1. 配置P6Spy:
在src/main/resources目录下创建spy.properties文件:
- # P6Spy配置文件
- modulelist=com.p6spy.engine.logging.P6LogFactory,com.p6spy.engine.outage.P6OutageFactory
- # 自定义日志格式
- logMessageFormat=com.p6spy.engine.spy.appender.MultiLineFormat
- # 使用自定义的日志记录器
- appender=com.p6spy.engine.spy.appender.StdoutLogger
- # 是否记录JDBC操作
- deregisterdrivers=true
复制代码
1. 修改数据源配置:
- # application.properties
- # 将原来的JDBC URL改为P6Spy格式
- spring.datasource.url=jdbc:p6spy:mysql://localhost:3306/your_database
- spring.datasource.username=your_username
- spring.datasource.password=your_password
- spring.datasource.driver-class-name=com.p6spy.engine.spy.P6SpyDriver
复制代码
自定义SQL格式化工具
你也可以创建自己的SQL格式化工具:
- import org.apache.commons.lang3.StringUtils;
- import org.hibernate.engine.spi.QueryParameters;
- import org.hibernate.engine.spi.SessionImplementor;
- import org.hibernate.loader.custom.CustomLoader;
- import org.hibernate.loader.custom.SQLQueryReturn;
- import org.hibernate.persister.collection.SQLLoadableCollection;
- import org.hibernate.persister.entity.SQLLoadable;
- import java.util.List;
- public class SqlFormatter {
- public static String formatSql(String sql, Object[] parameters) {
- // 简单的SQL格式化实现
- if (StringUtils.isBlank(sql)) {
- return "";
- }
- // 添加换行和缩进
- String formattedSql = sql.replaceAll("(?i)\\bSELECT\\b", "\nSELECT")
- .replaceAll("(?i)\\bFROM\\b", "\nFROM")
- .replaceAll("(?i)\\bWHERE\\b", "\nWHERE")
- .replaceAll("(?i)\\bGROUP BY\\b", "\nGROUP BY")
- .replaceAll("(?i)\\bORDER BY\\b", "\nORDER BY")
- .replaceAll("(?i)\\bHAVING\\b", "\nHAVING")
- .replaceAll("(?i)\\bAND\\b", "\n AND")
- .replaceAll("(?i)\\bOR\\b", "\n OR");
- // 替换参数占位符
- if (parameters != null && parameters.length > 0) {
- for (Object param : parameters) {
- String paramStr = param != null ? param.toString() : "NULL";
- formattedSql = formattedSql.replaceFirst("\\?", "'" + paramStr + "'");
- }
- }
- return formattedSql;
- }
- }
复制代码
然后,在拦截器或监听器中使用这个格式化工具:
- @Override
- public String onPrepareStatement(String sql) {
- // 获取参数(这里需要根据实际情况获取)
- Object[] parameters = getParameters();
-
- // 格式化SQL
- String formattedSql = SqlFormatter.formatSql(sql, parameters);
-
- System.out.println("Formatted SQL:\n" + formattedSql);
- return sql;
- }
复制代码
SQL输出中常见问题及解决方案
在配置SQL输出过程中,可能会遇到一些常见问题。下面列出了一些问题及其解决方案。
问题1:SQL语句没有输出
可能原因:
• 日志级别设置不正确
• 配置文件位置或名称不正确
• 没有正确加载配置文件
解决方案:
1. 检查日志级别设置是否正确,确保相关包的日志级别设置为DEBUG或TRACE:
- logging.level.org.hibernate.SQL=DEBUG
- 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. 确保参数输出配置正确:
- # Hibernate参数输出
- logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE
- # MyBatis参数输出
- mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
复制代码
1. 使用P6Spy或其他工具来显示参数值:
- # spy.properties
- logMessageFormat=com.p6spy.engine.spy.appender.MultiLineFormat
- appender=com.p6spy.engine.spy.appender.StdoutLogger
复制代码
问题3:SQL语句输出太多,影响性能
可能原因:
• 生产环境开启了SQL输出
• 日志级别设置过低
解决方案:
1. 在生产环境中关闭SQL输出或提高日志级别:
- # 生产环境配置
- spring.jpa.show-sql=false
- logging.level.org.hibernate.SQL=INFO
复制代码
1. 使用Spring Profile来区分开发和生产环境:
- # application-dev.properties
- spring.jpa.show-sql=true
- logging.level.org.hibernate.SQL=DEBUG
- # application-prod.properties
- spring.jpa.show-sql=false
- logging.level.org.hibernate.SQL=INFO
复制代码
1. 使用条件化配置:
- @Bean
- @Profile("dev")
- public DataSource dataSource() {
- // 开发环境数据源配置
- }
- @Bean
- @Profile("prod")
- public DataSource dataSource() {
- // 生产环境数据源配置
- }
复制代码
问题4:SQL语句格式不美观,难以阅读
可能原因:
• 没有启用SQL格式化
• 使用了默认的简单格式化器
解决方案:
1. 启用Hibernate的SQL格式化:
- spring.jpa.properties.hibernate.format_sql=true
复制代码
1. 使用P6Spy或其他格式化工具:
- # spy.properties
- logMessageFormat=com.p6spy.engine.spy.appender.MultiLineFormat
复制代码
1. 自定义SQL格式化器:
- public class CustomSqlFormatter implements MessageFormattingStrategy {
- @Override
- public String formatMessage(int connectionId, String now, long elapsed, String category, String prepared, String sql, String url) {
- return !"".equals(sql.trim()) ? "=============================\n" +
- "Execute SQL: " + formatSql(sql) + "\n" +
- "Execution Time: " + elapsed + " ms\n" +
- "=============================" : "";
- }
- private String formatSql(String sql) {
- // 实现SQL格式化逻辑
- return sql;
- }
- }
复制代码
然后在spy.properties中配置:
- logMessageFormat=com.yourpackage.CustomSqlFormatter
复制代码
实际应用示例
让我们通过一个完整的Spring MVC应用示例,展示如何配置和实现SQL语句输出。
项目结构
- src/main/java/com/example/demo/
- ├── config/
- │ ├── HibernateConfig.java
- │ └── MyBatisConfig.java
- ├── controller/
- │ └── UserController.java
- ├── interceptor/
- │ └── SqlOutputInterceptor.java
- ├── model/
- │ └── User.java
- ├── repository/
- │ ├── UserRepository.java
- │ └── UserRepositoryImpl.java
- └── DemoApplication.java
- src/main/resources/
- ├── application.yml
- ├── logback-spring.xml
- └── mybatis-config.xml
复制代码
配置文件
- spring:
- datasource:
- url: jdbc:mysql://localhost:3306/demo_db
- username: root
- password: password
- driver-class-name: com.mysql.cj.jdbc.Driver
- jpa:
- show-sql: true
- hibernate:
- ddl-auto: update
- properties:
- hibernate:
- format_sql: true
- use_sql_comments: true
- generate_statistics: true
- mybatis:
- mapper-locations: classpath:mapper/*.xml
- configuration:
- log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
- logging:
- level:
- org.hibernate.SQL: DEBUG
- org.hibernate.type.descriptor.sql.BasicBinder: TRACE
- org.springframework.data: DEBUG
- com.example.demo.mapper: DEBUG
复制代码- <?xml version="1.0" encoding="UTF-8"?>
- <configuration>
- <property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"/>
-
- <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
- <encoder>
- <pattern>${LOG_PATTERN}</pattern>
- </encoder>
- </appender>
-
- <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
- <file>logs/demo.log</file>
- <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
- <fileNamePattern>logs/demo.%d{yyyy-MM-dd}.log</fileNamePattern>
- <maxHistory>30</maxHistory>
- </rollingPolicy>
- <encoder>
- <pattern>${LOG_PATTERN}</pattern>
- </encoder>
- </appender>
-
- <logger name="org.hibernate.SQL" level="DEBUG" additivity="false">
- <appender-ref ref="CONSOLE"/>
- <appender-ref ref="FILE"/>
- </logger>
-
- <logger name="org.hibernate.type.descriptor.sql.BasicBinder" level="TRACE" additivity="false">
- <appender-ref ref="CONSOLE"/>
- <appender-ref ref="FILE"/>
- </logger>
-
- <logger name="org.springframework.data" level="DEBUG" additivity="false">
- <appender-ref ref="CONSOLE"/>
- <appender-ref ref="FILE"/>
- </logger>
-
- <logger name="com.example.demo.mapper" level="DEBUG" additivity="false">
- <appender-ref ref="CONSOLE"/>
- <appender-ref ref="FILE"/>
- </logger>
-
- <root level="INFO">
- <appender-ref ref="CONSOLE"/>
- <appender-ref ref="FILE"/>
- </root>
- </configuration>
复制代码- <?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>
- <settings>
- <setting name="logImpl" value="STDOUT_LOGGING"/>
- </settings>
- </configuration>
复制代码
Java代码
- import javax.persistence.*;
- import java.io.Serializable;
- @Entity
- @Table(name = "users")
- @EntityListeners(EntityListener.class)
- public class User implements Serializable {
- @Id
- @GeneratedValue(strategy = GenerationType.IDENTITY)
- private Long id;
-
- @Column(name = "username", nullable = false, unique = true)
- private String username;
-
- @Column(name = "email", nullable = false)
- private String email;
-
- // 构造函数
- public User() {}
-
- public User(String username, String email) {
- this.username = username;
- this.email = email;
- }
-
- // getters and setters
- public Long getId() {
- return id;
- }
-
- public void setId(Long id) {
- this.id = id;
- }
-
- public String getUsername() {
- return username;
- }
-
- public void setUsername(String username) {
- this.username = username;
- }
-
- public String getEmail() {
- return email;
- }
-
- public void setEmail(String email) {
- this.email = email;
- }
- }
复制代码- import javax.persistence.*;
- public class EntityListener {
- @PrePersist
- public void prePersist(Object entity) {
- System.out.println("About to persist: " + entity);
- }
- @PostPersist
- public void postPersist(Object entity) {
- System.out.println("Persisted: " + entity);
- }
- @PreUpdate
- public void preUpdate(Object entity) {
- System.out.println("About to update: " + entity);
- }
- @PostUpdate
- public void postUpdate(Object entity) {
- System.out.println("Updated: " + entity);
- }
- @PreRemove
- public void preRemove(Object entity) {
- System.out.println("About to remove: " + entity);
- }
- @PostRemove
- public void postRemove(Object entity) {
- System.out.println("Removed: " + entity);
- }
- }
复制代码- import com.example.demo.model.User;
- import org.springframework.data.jpa.repository.JpaRepository;
- import org.springframework.data.jpa.repository.Query;
- import org.springframework.data.repository.query.Param;
- public interface UserRepository extends JpaRepository<User, Long>, UserRepositoryCustom {
- @Query("SELECT u FROM User u WHERE u.username = :username")
- User findByUsername(@Param("username") String username);
- }
复制代码- import com.example.demo.model.User;
- public interface UserRepositoryCustom {
- User customFindById(Long id);
- }
复制代码- import com.example.demo.model.User;
- import javax.persistence.EntityManager;
- import javax.persistence.PersistenceContext;
- import javax.persistence.TypedQuery;
- public class UserRepositoryImpl implements UserRepositoryCustom {
- @PersistenceContext
- private EntityManager entityManager;
- @Override
- public User customFindById(Long id) {
- TypedQuery<User> query = entityManager.createQuery("SELECT u FROM User u WHERE u.id = :id", User.class);
- query.setParameter("id", id);
-
- // 输出SQL
- System.out.println("Executing custom query: " + query.unwrap(org.hibernate.Query.class).getQueryString());
-
- return query.getSingleResult();
- }
- }
复制代码- 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.text.SimpleDateFormat;
- import java.util.Date;
- import java.util.Properties;
- @Intercepts({
- @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
- @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
- })
- public class SqlOutputInterceptor implements Interceptor {
- @Override
- public Object intercept(Invocation invocation) throws Throwable {
- MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
- Object parameter = invocation.getArgs()[1];
-
- BoundSql boundSql = mappedStatement.getBoundSql(parameter);
- String sql = boundSql.getSql();
-
- // 输出SQL
- System.out.println("=============================================");
- System.out.println("Time: " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date()));
- System.out.println("SQL ID: " + mappedStatement.getId());
- System.out.println("SQL: " + sql);
- System.out.println("=============================================");
-
- // 执行原方法
- Object result = invocation.proceed();
-
- return result;
- }
- @Override
- public Object plugin(Object target) {
- return Plugin.wrap(target, this);
- }
- @Override
- public void setProperties(Properties properties) {
- // 可以读取配置的属性
- }
- }
复制代码- import com.example.demo.model.User;
- import com.example.demo.repository.UserRepository;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.web.bind.annotation.*;
- import java.util.List;
- import java.util.Optional;
- @RestController
- @RequestMapping("/api/users")
- public class UserController {
- @Autowired
- private UserRepository userRepository;
- @GetMapping
- public List<User> getAllUsers() {
- return userRepository.findAll();
- }
- @GetMapping("/{id}")
- public User getUserById(@PathVariable Long id) {
- // 使用自定义查询方法
- return userRepository.customFindById(id);
- }
- @GetMapping("/username/{username}")
- public User getUserByUsername(@PathVariable String username) {
- return userRepository.findByUsername(username);
- }
- @PostMapping
- public User createUser(@RequestBody User user) {
- return userRepository.save(user);
- }
- @PutMapping("/{id}")
- public User updateUser(@PathVariable Long id, @RequestBody User userDetails) {
- Optional<User> userOptional = userRepository.findById(id);
- if (userOptional.isPresent()) {
- User user = userOptional.get();
- user.setUsername(userDetails.getUsername());
- user.setEmail(userDetails.getEmail());
- return userRepository.save(user);
- }
- return null;
- }
- @DeleteMapping("/{id}")
- public void deleteUser(@PathVariable Long id) {
- userRepository.deleteById(id);
- }
- }
复制代码- import org.springframework.boot.SpringApplication;
- import org.springframework.boot.autoconfigure.SpringBootApplication;
- @SpringBootApplication
- public class DemoApplication {
- public static void main(String[] args) {
- SpringApplication.run(DemoApplication.class, args);
- }
- }
复制代码
运行示例
当运行这个应用并访问API时,你将在控制台和日志文件中看到类似以下的SQL输出:
- =============================================
- Time: 2023-05-20 14:30:45.123
- SQL ID: com.example.demo.repository.UserRepository.customFindById
- SQL: SELECT u FROM User u WHERE u.id = ?
- =============================================
- Hibernate:
- select
- user0_.id as id1_0_,
- user0_.email as email2_0_,
- user0_.username as username3_0_
- from
- users user0_
- where
- user0_.id=?
- 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]
- About to update: User{id=1, username='john_doe', email='john@example.com'}
- 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语句输出,提高数据库操作调试的效率。
版权声明
1、转载或引用本网站内容(Spring MVC应用中SQL语句输出的完整指南从配置到实现助你轻松调试数据库操作)须注明原网址及作者(威震华夏关云长),并标明本网站网址(https://www.pixtech.cc/)。
2、对于不当转载或引用本网站内容而引起的民事纷争、行政处理或其他损失,本网站不承担责任。
3、对不遵守本声明或其他违法、恶意使用本网站内容者,本网站保留追究其法律责任的权利。
本文地址: https://www.pixtech.cc/thread-31555-1-1.html
|
|