Different parameter syntax?

In MyBatis mapper XML files, you often see $ and # symbols. When you bind parameters in XML SQL, you use ${} or #{}.

They serve different purposes, so you must use them deliberately.


When you use #{}

Write a query using #{} first.

<select id="select" resultType="String" parameterType="Map">
    SELECT
        name AS name
    FROM
        user
    WHERE
        id = #{id}
</select>

When MyBatis executes a query with #{}, it parses the SQL and inserts a ? placeholder.

SELECT
    name AS name, email AS email
FROM
    user
WHERE
    id = ?

MyBatis uses PreparedStatement for #{} and binds the parameter into the ?. The parsed query is reused and cached, which is efficient.

MyBatis also adds single quotes automatically, so you do not need to write '#{id}' in SQL. Because of this behavior, you cannot build dynamic table names like this:

<select id="select" resultType="String" parameterType="Map">
    SELECT
        name AS name
    FROM
        user_#{tableId}
    WHERE
        id = #{id}
</select>

This fails with SQLSyntaxErrorException because the value of tableId is wrapped in quotes.


When you use ${}

With ${}, MyBatis inserts the value directly into the SQL. That means the SQL is re-parsed on every parameter change, which has a performance cost.

Because MyBatis does not add quotes, ${} works for dynamic table or column names.

<select id="select" resultType="String" parameterType="Map">
    SELECT
        name AS name
    FROM
        user_${id}
    WHERE
        id = #{id}
</select>

The downside is that ${} is vulnerable to SQL injection.


SQL Injection

Depending on the context, you should favor #{} for safety. Consider this query:

<select id="selectUserFromTable" parameterType="Map" resultType="...">
    SELECT
        *
    FROM
        user
    WHERE
        id = '${id}' AND password = '${password}'
</select>

If id is admin' -- , the parsed SQL looks like this:

SELECT
    *
FROM
    user
WHERE
    id = 'admin' -- 'AND password = ''

The password condition disappears, and the attacker can access the admin record with only an ID. This is why ${} is more vulnerable to SQL injection than #{}.