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 #{}.