Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SQL parameter substitution functionality differs substantially from string substitution functionality #206

Open
spencermw opened this issue May 16, 2014 · 3 comments
Labels
enhancement Improve a feature or add a new feature

Comments

@spencermw
Copy link

spencermw commented May 16, 2014

In both the user guide (http://mybatis.github.io/mybatis-3/sqlmap-xml.html#Parameters) and the GitHub FAQ (https://github.com/mybatis/mybatis-3/wiki/FAQ), the #{} syntax and the ${} syntax are treated as being identical, with the exception that ${} performs a direct string substitution and #{} inserts the value as a parameter to a prepared statement.

However, in practice, the #{} syntax limits the expressions that can be used in unexpected ways.

First, the #{} syntax cannot retrieve a value from a map when any syntax other than map.key is used. This is the deal-breaker that first brought this issue to my attention. Given:

try(SqlSession session = ...) {
    HashMap<String, String> map = new HashMap<>();
    map.put("name", "Bob Smith");
    session.getMapper(TestMapper.class).test(map);
}
/* TestMapper.java */
int test(@Param("map") HashMap<String, String> map);
<!-- TestMapper.xml -->
<select id="test" resultType="int">
    SELECT ID FROM PERSON WHERE NAME = #{map.name}
</select>

This will work; however, none of the below will work:

<!-- TestMapper.xml -->
<select id="test" resultType="int">
    SELECT ID FROM PERSON WHERE NAME = #{map['name']}
</select>
<!-- TestMapper.xml -->
<select id="test" resultType="int">
    <bind name="bound" value="'name'"/>
    SELECT ID FROM PERSON WHERE NAME = #{map[bound]}
</select>
<!-- TestMapper.xml -->
<select id="test" resultType="int">
    <!-- #{map.get(bound) with bound defined as above will also fail. -->
    SELECT ID FROM PERSON WHERE NAME = #{map.get('name')}
</select>

Null is used to set the parameter in each case, despite each expression evaluating correctly if used with ${} syntax instead.

Additionally, functions may not be used in parameter bindings. Given an object mapWrapper with a method name() passed as a parameter:

<select id="test" resultType="int">
    SELECT ID FROM PERSON WHERE NAME = #{mapWrapper.name()}
</select>

This results in an error. (Using ${} syntax, obviously, does not.)

Finally, attempting to use <bind> to overcome these limitations results in unexpected behavior. Consider the following:

<select id="test" resultType="int">
    <bind name="actualParameter" value="map['name']">
    SELECT ID FROM PERSON WHERE NAME = #{actualParameter}
</select>

This works as expected, which is great. However, let's say we have a list of columns (name, title, favorite_cookie_variety), and a map of columns to values (name => "Bob", title => "Senior Cookie Tester", favorite_cookie_variety => "Mint Chocolate"). We want to include these columns and values in our WHERE clause, subject to some arbitrary criteria which we don't want to work out beforehand.

<select id="test" resultType="int">
    SELECT ID FROM PERSON
    <where>
        <foreach collection="columns" item="column">
            <if test="column.someArbitraryCriteria">
                <bind name="actualParameter" value="#{map[column.name]}"/>
                AND ${column.name} = #{actualParameter}
            </if>
        </foreach>
    </where>
</select>

If you're familiar with the order in which MyBatis performs operations, you already see what's going to happen here. We re-bind "actualParameter" to a different value every iteration through the loop, but because parameter setting occurs after the script parsing has been completed, each parameter in the SQL query will be bound to the same value: the last value assigned to actualParameter.

While these examples may seem silly and contrived, I believe that the difference between ${} syntax and #{} syntax is unexpected and confusing. The available documentation does not describe or even hint at these limitations. Clarifying the documentation might be helpful, but I believe the usefulness of MyBatis will be enhanced by enabling users to leverage all the power available to them in ${} expressions in #{} expressions as well.

I would propose that the script driver be modified to parse #{} expressions as OGNL expressions the same as ${} expressions are. The result value could be stored in a new temporary variable, and that temporary variable be used in the parameter-setting stage.

@emacarron emacarron added the enhancement Improve a feature or add a new feature label Oct 11, 2014
@avackova
Copy link

Does it exist a workaround for last issue? How can I set actual value in following statement?

<foreach item="c" collection="filter.getFilter()" separator=" AND " open="(" close=")">
    <bind name="column" value="_parameter.mappingWhere(c.colCode)"/>
    <bind name="operator" value="_parameter.conditionOperator(c.condition)"/>
    <bind name="value" value="_parameter.conditionValue(c.condition, c.value)"/>
        ${column} ${operator} #{value}
</foreach>          

I've tried static methods, not static methods,..., but nothing works :-(

@xiangyucao
Copy link

Help! I have the exactly the same problem! How to use bind in foreach. Thanks.

@mnesarco
Copy link
Member

See #575

harawata added a commit to harawata/mybatis-3 that referenced this issue Dec 17, 2022
harawata added a commit to harawata/mybatis-3 that referenced this issue Dec 17, 2022
…phase

- Evaluated param values are stored in `ParameterMapping` and later used in DefaultParameterHandler
- There is no change when processing RawSqlSource
- Removed unused `injectionFilter` from TextSqlNode (mybatisgh-117)

This should fix mybatis#2754 .

This might also fix mybatis#206 and mybatis#575 , but with this patch, it still is not possible to invoke a method with parameter inside a parameter reference like `#{_parameter.mymethod(_parameter.value)}`.
It might be possible to accept OGNL expression in a param reference (e.g. `#{${_parameter.mymethod(_parameter.value)}}`), but I'm not sure if that's a good idea.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement Improve a feature or add a new feature
Projects
None yet
Development

No branches or pull requests

5 participants