在之前的所有语句中,你看到了简单参数的例子。参数在MyBatis中是非常强大的元素。对于简单的情况,也就是90%的情况,它们并没有太多的复杂性,例如:
上面的示例展示了一个非常简单的命名参数映射。参数类型被设置为 int,所以参数可以使用任何名称。原始的或简单的数据类型,如 Integer 和 String,并没有相关的属性,因此会完全替换参数的值。然而,如果你传入一个复杂对象,那么行为就有些不同。例如:
insert into users (id, username, password)
values (#{id}, #{username}, #{password})
如果传入一个类型为 User 的参数对象到该语句中,会查找 id、username 和 password 属性,并将它们的值传递给 PreparedStatement 参数。
那对于将参数传递到语句中来说确实非常简单。但是参数映射还有许多其他功能。
首先,和 MyBatis 的其他部分类似,参数可以指定更具体的数据类型。
#{property,javaType=int,jdbcType=NUMERIC}
和 MyBatis 的其他部分一样,javaType几乎总能从参数对象中确定,除非该对象是HashMap。在这种情况下,应该指定javaType以确保使用正确的TypeHandler。
注意:如果将null作为值传递,那么JDBC要求对所有可空列都提供JDBC类型。你可以通过阅读PreparedStatement.setNull()方法的JavaDocs来自行了解这一点。
为了进一步自定义类型处理,你还可以指定一个特定的TypeHandler类(或别名),例如:
#{age,javaType=int,jdbcType=NUMERIC,typeHandler=MyTypeHandler}
确实,看起来已经有些冗长了,但事实上你很少需要设置这些参数。
对于数值类型,还有一个numericScale属性用于确定有多少位小数是相关的。
#{height,javaType=double,jdbcType=NUMERIC,numericScale=2}
最后,mode属性允许您指定IN、OUT或INOUT参数。如果参数是OUT或INOUT,参数对象的实际值将被修改,就像你调用输出参数一样。如果mode=OUT(或INOUT)并且jdbcType=CURSOR(即Oracle的REFCURSOR),您必须指定一个resultMap来将ResultSet映射到参数的类型。请注意,此处的javaType属性是可选的,如果使用CURSOR作为jdbcType,并且将其留空,则它会自动设置为ResultSet。
#{department, mode=OUT, jdbcType=CURSOR, javaType=ResultSet, resultMap=departmentResultMap}
MyBatis还支持更高级的数据类型,如structs,但是在注册出参数时必须告诉语句类型名称。例如(再次强调,在实际应用中不要打破行):
#{middleInitial, mode=OUT, jdbcType=STRUCT, jdbcTypeName=MY_TYPE, resultMap=departmentResultMap}
尽管有这些强大的选项,大多数情况下你只需指定属性名,MyBatis会自动处理其余部分。最多,你只需为可空列指定jdbcType。
#{firstName}
#{middleInitial,jdbcType=VARCHAR}
#{lastName}
默认情况下,使用#{}语法将导致MyBatis生成PreparedStatement属性,并安全地将值设置到PreparedStatement参数(例如?)中。虽然这样更安全、更快速,并且通常是首选的方式,但有时您只想直接将一个未修改的字符串注入到SQL语句中。例如,在ORDER BY语句中,您可能会这样使用:
ORDER BY ${columnName}
在这里,MyBatis不会修改或转义字符串。
字符串替换在SQL语句中的元数据(例如表名或列名)是动态的时候非常有用,例如,如果你想根据表的任何一个列进行查询,而不是编写如下的代码:
@Select("select * from user where id = #{id}")
User findById(@Param("id") long id);
@Select("select * from user where name = #{name}")
User findByName(@Param("name") String name);
@Select("select * from user where email = #{email}")
User findByEmail(@Param("email") String email);
// and more "findByXxx" method
你只需要写成:
@Select("select * from user where ${column} = #{value}")
User findByColumn(@Param("column") String column, @Param("value") String value);
其中${column}将直接替换,而#{value}将被"准备"。因此,你可以通过以下方式完成相同的工作:
User userOfId1 = userMapper.findByColumn("id", 1L);
User userOfNameKid = userMapper.findByColumn("name", "kid");
User userOfEmail = userMapper.findByColumn("email", "[email protected]");
这个想法同样适用于替换表名。
注意:接受用户输入,并以这种方式未经修改地提供给语句是不安全的。这会导致潜在的SQL注入攻击,因此你应该要么禁止用户在这些字段中输入,要么始终进行自己的转义和检查。