Skip to content

Latest commit

 

History

History
251 lines (172 loc) · 9.19 KB

File metadata and controls

251 lines (172 loc) · 9.19 KB

import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem';

SQL Logging

When debugging performance issues or tracing application behavior, you often need visibility into the SQL statements your ORM generates. Standard JDBC logging shows raw statements with ? placeholders, giving you no context about which repository method triggered the query or what the actual parameter values were.

Storm provides the @SqlLog annotation for declarative SQL logging on repositories. Place it on a repository interface or an individual method, and Storm will log every SQL statement that method generates, including which method triggered it. No boilerplate, no manual interceptor setup, no dependency on a specific logging framework.

@SqlLog uses the JDK Platform Logging API (System.Logger), which automatically bridges to whatever logging backend is on your classpath (SLF4J, Log4j2, java.util.logging). This means it works out of the box in any environment.


Annotating a Repository

The simplest way to enable SQL logging is to annotate the repository interface itself. This logs every SQL statement generated by any method in the repository.

@SqlLog
interface UserRepository : EntityRepository<User, Int> {

    fun findByEmail(email: String): User? =
        find(User_.email eq email)

    fun findActiveUsers(): List<User> =
        findAll(User_.active eq true)
}
@SqlLog
public interface UserRepository extends EntityRepository<User, Integer> {

    default Optional<User> findByEmail(String email) {
        return select(User_.email.eq(email)).getOptionalResult();
    }

    default List<User> findActiveUsers() {
        return select(User_.active.eq(true)).getResultList();
    }
}

When you call userRepository.findByEmail("alice@example.com"), the log output looks like this:

INFO  com.example.UserRepository - [SQL] (UserRepository.findByEmail(String))
	SELECT u.id, u.email, u.name, u.active
	FROM user u
	WHERE u.email = ?

The log message includes the repository type and method name, making it easy to trace which code path triggered the query.


Annotating Individual Methods

When you only need logging for specific operations (for example, a complex query you are developing or debugging), annotate the method instead of the entire interface. This avoids noisy output from methods you are not interested in.

interface OrderRepository : EntityRepository<Order, Int> {

    // No logging
    fun findById(id: Int): Order? =
        find(Order_.id eq id)

    // Logged
    @SqlLog
    fun findExpiredOrders(cutoff: LocalDate): List<Order> =
        findAll(Order_.expiresAt lt cutoff)
}
public interface OrderRepository extends EntityRepository<Order, Integer> {

    // No logging
    default Optional<Order> findById(int id) {
        return select(Order_.id.eq(id)).getOptionalResult();
    }

    // Logged
    @SqlLog
    default List<Order> findExpiredOrders(LocalDate cutoff) {
        return select(Order_.expiresAt.lt(cutoff)).getResultList();
    }
}

Method-level annotations override type-level annotations. If the interface has @SqlLog but a specific method has @SqlLog(level = Level.DEBUG), the method's configuration takes precedence.


Debugging with Inline Parameters

By default, SQL log output shows parameterized queries with ? placeholders, just as they are sent to the database. This is useful for understanding query structure, but when you are debugging a specific issue, you often want to see the actual values.

Setting inlineParameters = true replaces the ? placeholders with the actual bound values. This produces SQL you can copy directly into a database tool and execute, which makes it invaluable for reproducing issues.

@SqlLog(inlineParameters = true)
interface UserRepository : EntityRepository<User, Int> {

    fun findByEmail(email: String): User? =
        find(User_.email eq email)
}
@SqlLog(inlineParameters = true)
public interface UserRepository extends EntityRepository<User, Integer> {

    default Optional<User> findByEmail(String email) {
        return select(User_.email.eq(email)).getOptionalResult();
    }
}

Compare the output:

Setting Output
inlineParameters = false (default) SELECT u.id, u.email FROM user u WHERE u.email = ?
inlineParameters = true SELECT u.id, u.email FROM user u WHERE u.email = 'alice@example.com'

With inlined parameters, the logged SQL is a complete, executable statement. You can paste it directly into your database client to inspect the result set, check the query plan with EXPLAIN, or verify that the WHERE clause matches the rows you expect. This is especially helpful when debugging queries with multiple parameters, date ranges, or complex filter expressions where it is not obvious which ? corresponds to which value.

Important: inlineParameters only affects the log output. The actual SQL sent to the database always uses parameterized queries with ? placeholders, regardless of this setting. Storm never sends inlined parameter values to the database, so there is no risk of SQL injection or behavioral changes. This is purely a logging convenience.

Tip: Use inlineParameters = true during development and debugging. For production logging, prefer the default (false) to keep log output concise and avoid accidentally logging sensitive data such as passwords or personal information.


Controlling Log Level

The level attribute controls the System.Logger.Level used for log output. If the configured logger is not enabled for the specified level, the interceptor is skipped entirely, so there is zero overhead when logging is disabled.

@SqlLog(level = System.Logger.Level.DEBUG)
interface UserRepository : EntityRepository<User, Int> { ... }
@SqlLog(level = System.Logger.Level.DEBUG)
public interface UserRepository extends EntityRepository<User, Integer> { ... }

The available levels follow the standard System.Logger.Level enum:

Level Typical use
TRACE Very detailed diagnostics, high volume
DEBUG Development-time query inspection
INFO Default; visible in standard log output
WARNING Highlight potentially problematic queries

Custom Logger Name

By default, the logger name is the fully qualified name of the repository interface (e.g., com.example.UserRepository). This works well with standard logging configuration where you can enable or disable logging per package.

If you need a different logger name, for example, to group all SQL logs under a single category, use the name attribute:

@SqlLog(name = "sql")
interface UserRepository : EntityRepository<User, Int> { ... }
@SqlLog(name = "sql")
public interface UserRepository extends EntityRepository<User, Integer> { ... }

This logs to a logger named sql instead of the repository's class name, so you can configure a single logger to capture (or silence) SQL output from all repositories at once.


Attribute Reference

Attribute Type Default Description
inlineParameters boolean false Replace ? placeholders with actual parameter values in log output
level System.Logger.Level INFO Log level for SQL output; logging is skipped entirely if the level is not enabled
name String "" (repository class name) Custom logger name; useful for grouping all SQL logging under one category

Where It Works

@SqlLog is processed by the repository proxy, so it works everywhere repositories are used:

  • Repositories obtained via orm.repository() (standalone usage, no framework required)
  • Spring-managed repository beans (auto-configured through the Spring Boot starter)

No additional configuration or dependencies are needed beyond the Storm dependency you already have.


Tips

  1. Start with type-level annotation during development to see all queries a repository generates, then narrow down to method-level once you know which queries to focus on.
  2. Use inlineParameters = true for debugging to get copy-pasteable SQL. Switch back to false before committing to avoid leaking sensitive values in production logs.
  3. Set level to DEBUG or TRACE for repositories in production code, so SQL logging is available on demand through log level configuration without code changes.
  4. Combine with a custom logger name like @SqlLog(name = "sql") to create a single switch for all SQL logging across your application.