Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Parameter Binding Strategies in MyBatis: Security and Performance Implications

Tech 1

MyBatis provides two distinct syntaxes for injecting dynamic values into SQL statements. Consider retrieving records from an employee table based on an email address:

SELECT * FROM employee WHERE email = 'alice@example.com';

To make this query dynamic, MyBatis offers two approaches:

SELECT * FROM employee WHERE email = #{email};

Or alternatively:

SELECT * FROM employee WHERE email = ${email};

While both versions produce identical results for simple queries, their underlying mechanisms differ significantly, leading to distinct security and performance characteristics.

Placeholder Resolution Mechanisms

MyBatis performs dynamic SQL resolution before statement execution, converting XML-mapped statements into BoundSql objects. During this phase, #{} and ${} undergo different transformations.

Prepared Statement Parameters

The #{} syntax indicates a JDBC prepared statement parameter marker. When MyBatis encounters:

SELECT * FROM employee WHERE email = #{email};

It trensforms this into a parameterized query:

SELECT * FROM employee WHERE email = ?;

The actual value binding occurs within the database management system (DBMS), not during SQL construction.

Literal String Interpolation

Conversely, ${} triggers direct string substitution during the dynamic SQL parsing phase. Given:

SELECT * FROM employee WHERE email = ${email};

With an input value of 'alice@example.com', the resulting SQL becomes:

SELECT * FROM employee WHERE email = 'alice@example.com';

By the time this reaches the precompilation stage, no variable reference remains—it has been replaced with the literal text.

Security and Performance Considerations

Prefer Parameterized Queries

Always favor #{} when possible. This approach leverages prepared statement caching, allowing the DBMS to reuse execution plans for identical query structures with different parameters.

More critical, ${} enables SQL injection vulnerabilities. Consider this dynamic table selection:

SELECT * FROM ${tableName} WHERE status = #{status};

If tableName receives malicious input such as employee; TRUNCATE employee; --, the resolved SQL becomes:

SELECT * FROM employee; TRUNCATE employee; -- WHERE status = ?;

The double-dash comments out the remaining clause, executing destructive commands.

Exception: Schema Objects

Database identifiers (tables, columns) require ${} because prepared statement placeholders automatically quote string values, causing syntax errors for object names. Using #{} for a table:

SELECT * FROM #{tableName} WHERE id = #{id};

Results in invalid SQL after parameter binding:

SELECT * FROM 'employee' WHERE id = 123;

Object names cannot appear as quoted string literals, necessitating literal substitution for dynamic schema referenecs.

SQL Precompilation Architecture

Precompilation occurs when the database driver transmits SQL to the DBMS before parameter binding. This process offers two primary advantages:

  1. Execution Plan Optimization: Complex quereis undergo parsing and optimization once, with subsequent executions reusing the compiled structure.
  2. Resource Efficiency: PreparedStatement objects remain in cache, eliminating redundant parsing overhead for recurring query patterns.

MyBatis enables precompilation by default for all mapped statements.

Driver Implementation Analysis

Examining the MySQL Connector/J implementation within ConnectionImpl reveals the precompilation workflow:

public synchronized PreparedStatement prepareStatement(String query,
        int resultSetType, int resultSetConcurrency) throws SQLException {
    checkClosed();
    
    PreparedStatement statement = null;
    boolean serverSideCapable = true;
    
    // Normalize SQL syntax for target database
    String normalizedQuery = getProcessEscapeCodesForPrepStmts() 
        ? nativeSQL(query) 
        : query;
    
    // Evaluate server-side precompilation support
    if (this.useServerPreparedStmts && getEmulateUnsupportedPstmts()) {
        serverSideCapable = canHandleAsServerPreparedStatement(normalizedQuery);
    }
    
    if (this.useServerPreparedStmts && serverSideCapable) {
        if (this.getCachePreparedStatements()) {
            synchronized (this.serverSideStatementCache) {
                // Attempt retrieval from statement cache
                statement = (ServerPreparedStatement) this.serverSideStatementCache.remove(query);
                
                if (statement != null) {
                    statement.setClosed(false);
                    statement.clearParameters();
                }
                
                if (statement == null) {
                    try {
                        // Instantiate new server-prepared statement
                        statement = ServerPreparedStatement.getInstance(
                            getLoadBalanceSafeProxy(), 
                            normalizedQuery,
                            this.database, 
                            resultSetType, 
                            resultSetConcurrency
                        );
                        
                        if (query.length() < getPreparedStatementCacheSqlLimit()) {
                            ((ServerPreparedStatement) statement).isCached = true;
                        }
                        
                        statement.setResultSetType(resultSetType);
                        statement.setResultSetConcurrency(resultSetConcurrency);
                    } catch (SQLException exception) {
                        if (getEmulateUnsupportedPstmts()) {
                            statement = (PreparedStatement) clientPrepareStatement(
                                normalizedQuery, resultSetType, resultSetConcurrency, false
                            );
                            
                            if (query.length() < getPreparedStatementCacheSqlLimit()) {
                                this.serverSideStatementCheckCache.put(query, Boolean.FALSE);
                            }
                        } else {
                            throw exception;
                        }
                    }
                }
            }
        } else {
            // Direct server-side preparation without caching
            try {
                statement = ServerPreparedStatement.getInstance(
                    getLoadBalanceSafeProxy(), 
                    normalizedQuery,
                    this.database, 
                    resultSetType, 
                    resultSetConcurrency
                );
                
                statement.setResultSetType(resultSetType);
                statement.setResultSetConcurrency(resultSetConcurrency);
            } catch (SQLException exception) {
                if (getEmulateUnsupportedPstmts()) {
                    statement = (PreparedStatement) clientPrepareStatement(
                        normalizedQuery, resultSetType, resultSetConcurrency, false
                    );
                } else {
                    throw exception;
                }
            }
        }
    } else {
        // Fallback to client-side preparation
        statement = (PreparedStatement) clientPrepareStatement(
            normalizedQuery, resultSetType, resultSetConcurrency, false
        );
    }
    
    return statement;
}

This implementation demonstrates the dual-path approach: server-side preparation when supported and cached, falling back to client-side emulation when necessary.

Dynamic Parsing Pipeline

Before JDBC precompilation occurs, MyBatis processes SQL through its dynamic language engine. This pipeline handles placeholder substitution, conditional logic, and type validation, transforming mapped XML into executable SQL with bound parameter metadata.

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.