Parameters

In all of the past statements, you’ve seen examples of simple parameters. Parameters are very powerful elements in MyBatis. For simple situations, probably 90% of the cases, there’s not much to them, for example:

  1. <select id="selectUsers" resultType="User">
  2. select id, username, password
  3. from users
  4. where id = #{id}
  5. </select>

The example above demonstrates a very simple named parameter mapping. The parameterType is set to int, so therefore the parameter could be named anything. Primitive or simple data types such as Integer and String have no relevant properties, and thus will replace the full value of the parameter entirely. However, if you pass in a complex object, then the behavior is a little different. For example:

  1. <insert id="insertUser" parameterType="User">
  2. insert into users (id, username, password)
  3. values (#{id}, #{username}, #{password})
  4. </insert>

If a parameter object of type User was passed into that statement, the id, username and password property would be looked up and their values passed to a PreparedStatement parameter.

That’s nice and simple for passing parameters into statements. But there are a lot of other features of parameter maps.

First, like other parts of MyBatis, parameters can specify a more specific data type.

  1. #{property,javaType=int,jdbcType=NUMERIC}

Like the rest of MyBatis, the javaType can almost always be determined from the parameter object, unless that object is a HashMap. Then the javaType should be specified to ensure the correct TypeHandler is used.

NOTE The JDBC Type is required by JDBC for all nullable columns, if null is passed as a value. You can investigate this yourself by reading the JavaDocs for the PreparedStatement.setNull() method.

To further customize type handling, you can also specify a specific TypeHandler class (or alias), for example:

  1. #{age,javaType=int,jdbcType=NUMERIC,typeHandler=MyTypeHandler}

So already it seems to be getting verbose, but the truth is that you’ll rarely set any of these.

For numeric types there’s also a numericScale for determining how many decimal places are relevant.

  1. #{height,javaType=double,jdbcType=NUMERIC,numericScale=2}

Finally, the mode attribute allows you to specify IN, OUT or INOUT parameters. If a parameter is OUT or INOUT, the actual value of the parameter object property will be changed, just as you would expect if you were calling for an output parameter. If the mode=OUT (or INOUT) and the jdbcType=CURSOR (i.e. Oracle REFCURSOR), you must specify a resultMap to map the ResultSet to the type of the parameter. Note that the javaType attribute is optional here, it will be automatically set to ResultSet if left blank with a CURSOR as the jdbcType.

  1. #{department, mode=OUT, jdbcType=CURSOR, javaType=ResultSet, resultMap=departmentResultMap}

MyBatis also supports more advanced data types such as structs, but you must tell the statement the type name when registering the out parameter. For example (again, don’t break lines like this in practice):

  1. #{middleInitial, mode=OUT, jdbcType=STRUCT, jdbcTypeName=MY_TYPE, resultMap=departmentResultMap}

Despite all of these powerful options, most of the time you’ll simply specify the property name, and MyBatis will figure out the rest. At most, you’ll specify the jdbcType for nullable columns.

  1. #{firstName}
  2. #{middleInitial,jdbcType=VARCHAR}
  3. #{lastName}

String Substitution

By default, using the #{} syntax will cause MyBatis to generate PreparedStatement properties and set the values safely against the PreparedStatement parameters (e.g. ?). While this is safer, faster and almost always preferred, sometimes you just want to directly inject an unmodified string into the SQL Statement. For example, for ORDER BY, you might use something like this:

  1. ORDER BY ${columnName}

Here MyBatis won’t modify or escape the string.

String Substitution can be very useful when the metadata(i.e. table name or column name) in the sql statement is dynamic, for example, if you want to select from a table by any one of its columns, instead of writing code like:

  1. @Select("select * from user where id = #{id}")
  2. User findById(@Param("id") long id);
  3. @Select("select * from user where name = #{name}")
  4. User findByName(@Param("name") String name);
  5. @Select("select * from user where email = #{email}")
  6. User findByEmail(@Param("email") String email);
  7. // and more "findByXxx" method

you can just write:

  1. @Select("select * from user where ${column} = #{value}")
  2. User findByColumn(@Param("column") String column, @Param("value") String value);

in which the ${column} will be substituted directly and the #{value} will be “prepared”. Thus you can just do the same work by:

  1. User userOfId1 = userMapper.findByColumn("id", 1L);
  2. User userOfNameKid = userMapper.findByColumn("name", "kid");
  3. User userOfEmail = userMapper.findByColumn("email", "noone@nowhere.com");

This idea can be applied to substitute the table name as well.

NOTE It’s not safe to accept input from a user and supply it to a statement unmodified in this way. This leads to potential SQL Injection attacks and therefore you should either disallow user input in these fields, or always perform your own escapes and checks.