MyBatis的参数处理
从参数的个数来看,可分为单个参数或者多个参数:
单个参数
使用#{参数名}
就能取出参数值。
多个参数
MyBatis遇到多个参数会做特殊处理,多个参数会被封装成一个map,#{}
就是从map中获取指定的key的值。
所以应该写成#{key}
的形式来取出map中对应的值,map中的key为以下形式:param1
、param2
、.....
。
例如:
<select id="getEmpByIdAndLastName" resultType="com.cerr.mybatis.Employee">
select * from tb1_employee where id = #{param1} and last_name = #{param2}
</select>
但是使用param1
这种的话感觉不大方便,因此我们可以使用命名参数。
命名参数
明确指定封装参数时map的key,在接口方法入参中使用@Param
注解来指定封装参数时map的key。
例如我们使用@Param("id")
指定第一个参数的key为id
,因此我们获取其参数值的时候使用#{id}
即可。
public Employee getEmpByIdAndLastName(@Param("id") Integer id,
@Param("lastName") String lastName);
所以sql映射文件中的部分sql代码我们可以修改如下:
<select id="getEmpByIdAndLastName" resultType="com.cerr.mybatis.Employee">
select * from tb1_employee where id = #{id} and last_name = #{lastName}
</select>
从参数的类型来分类,可以讨论如下:
参数处理
如果上述的参数中的多个参数正好是我们业务逻辑的数据模型,那我们就可以直接传入POJP,#{属性名}
就可以取出传入的POJO的属性值;如果多个参数不是业务模型中的数据,没有对应的POJO,为了方便,不经常使用的话,我们也可以传入map。此时#{key}
就可以取出map中对应的值。如果多个参数不是业务模型中的数据,但是经常要使用,推荐来编写一个TO(transfer Object)数据传输对象。
特别注意的是:如果传入的参数是Collection(List、Set)
类型或者是数组时,也会特殊处理,也就是把传入的Collection
或者数组封装到map中:
- 如果是
Collection
,则对应的key为collection
,特别地,如果是List
类型的话,key是list
。 - 如果是数组类型的话,则对应的key为
array
。
(1)传入POJO
在接口中定义一个方法:
public Employee getEmpByBean(Employee employee);
然后在sql映射文件中配置如下:
<select id="getEmpByBean" resultType="com.cerr.mybatis.Employee">
select * from tb1_employee where id = #{id} and last_name = #{lastName}
</select>
测试方法:
package com.cerr.mybatis;
import com.cerr.mybatis.dao.EmployeeMapper;
import com.cerr.mybatis.dao.EmployeeMapperAnnotation;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import static org.junit.Assert.*;
public class MyBatisTest {
//获取SQLSessionFactory
public SqlSessionFactory getSqlSessionFactory() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
return new SqlSessionFactoryBuilder().build(inputStream);
}
@Test
public void test8() throws IOException {
//获取SqlSessionFactory
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
//获取SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
try {
//获取接口的实现类对象:会为接口自动的创建一个代理对象,代理对象去执行增删改查
EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
//传入一个POJO
Employee emp = new Employee(1,"aaa",null,null);
//调用方法
Employee employee = employeeMapper.getEmpByBean(emp);
//打印
System.out.println(employee);
}finally {
//关闭
sqlSession.close();
}
}
}
(2)传入Map
在接口中定义方法:
public Employee getEmpByMap(Map<String,Object> map);
在sql映射文件中配置如下:
<select id="getEmpByMap" resultType="com.cerr.mybatis.Employee">
select * from tb1_employee where id = #{id} and last_name = #{lastName}
</select>
测试方法:
package com.cerr.mybatis;
import com.cerr.mybatis.dao.EmployeeMapper;
import com.cerr.mybatis.dao.EmployeeMapperAnnotation;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import static org.junit.Assert.*;
public class MyBatisTest {
//获取SQLSessionFactory
public SqlSessionFactory getSqlSessionFactory() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
return new SqlSessionFactoryBuilder().build(inputStream);
}
@Test
public void test7() throws IOException {
//获取SqlSessionFactory
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
//获取SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
try {
//获取接口的实现类对象:会为接口自动的创建一个代理对象,代理对象去执行增删改查
EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
//传入一个map
Map<String,Object> map = new HashMap <>();
map.put("id",1);
map.put("lastName","aaa");
//调用方法
Employee employee = employeeMapper.getEmpByMap(map);
//打印
System.out.println(employee);
}finally {
//关闭
sqlSession.close();
}
}
}
(3)传入List
在接口中定义方法:
public Employee getEmpByIdList(List<Integer> id);
在sql映射文件中配置:
<select id="getEmpByIdList" resultType="com.cerr.mybatis.Employee">
select * from tb1_employee where id = #{list[0]}
</select>
测试方法:
package com.cerr.mybatis;
import com.cerr.mybatis.dao.EmployeeMapper;
import com.cerr.mybatis.dao.EmployeeMapperAnnotation;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.junit.Assert.*;
public class MyBatisTest {
//获取SQLSessionFactory
public SqlSessionFactory getSqlSessionFactory() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
return new SqlSessionFactoryBuilder().build(inputStream);
}
@Test
public void test7() throws IOException {
//获取SqlSessionFactory
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
//获取SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
try {
//获取接口的实现类对象:会为接口自动的创建一个代理对象,代理对象去执行增删改查
EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
//传入一个map
Map<String,Object> map = new HashMap <>();
map.put("id",1);
map.put("lastName","aaa");
//调用方法
Employee employee = employeeMapper.getEmpByMap(map);
//打印
System.out.println(employee);
}finally {
//关闭
sqlSession.close();
}
}
}
从源码来看参数处理的过程(即如何封装Map)
(1) 确定names
的值
- 获取每个标注了
@Param
注解的参数的值,然后把其值赋给name。 - 每次解析一个参数,给
names
这个map保存信息:(key:参数索引,key:name的值)。对于name的值,如果标注了Param注解,则name是注解的值;如果没有标注Param注解,如果在全局配置中有配置useActualParamName
这个属性的值,则name就等于参数名;如果没有设置,则name=map.size()
:相当于当前元素的索引。
对于我们上述这个方法,此时我们的names为{0=id,1=lastName,2=2}:
public Employee getEmpByIdAndLastName(@Param("id") Integer id, @Param("lastName") String lastName,String gender);
(2)封装Map
public Object getNamedParams(Object[] args) {
int paramCount = this.names.size();
//传入的参数为null则直接返回
if (args != null && paramCount != 0) {
//如果只有一个元素,并且没有@Param注解。单个参数直接返回:args[0]。
if (!this.hasParamAnnotation && paramCount == 1) {
return args[(Integer)this.names.firstKey()];
} else {
//多个元素或者有@Param标注
Map<String, Object> param = new ParamMap();
int i = 0;
//遍历names集合。
for(Iterator var5 = this.names.entrySet().iterator(); var5.hasNext(); ++i) {
Entry<Integer, String> entry = (Entry)var5.next();
//将names集合的value作为key,names集合的key作为传入的参数值的索引。假设我们传入的参数值为:{1,"Tom","0"}。那么value为args[names[key]],即args[0,1,2]:{1,"Tom","0"}。
//所以现在param的值为{id=1,lastName="Tom",2="0"}。
param.put((String)entry.getValue(), args[(Integer)entry.getKey()]);
//额外的将每一个参数也保存到map中,使用新的key:param1....paramN
//所以现在的话,如果有使用Param注解的话可以使用#{指定的key},或者#{param1}
String genericParamName = "param" + String.valueOf(i + 1);
if (!this.names.containsValue(genericParamName)) {
param.put(genericParamName, args[(Integer)entry.getKey()]);
}
}
return param;
}
} else {
return null;
}
}
详细过程看上述源码中写的注释。
总结
参数多时会封装成map,为了不混乱,我们可以使用@Param
来指定封装时使用的key,这样就可以通过#{指定的值}
来取出map中的值,否则应该使用#{param+数字}
来获取。
参数处理中#{}
与${}
的区别
使用 #{}
和${}
都可以取出map和pojo中的值,但是两者还是有区别的。
-
#{}
:是以预编译的形式,将参数设置到sql语句中。可以防止sql注入。 -
${}
:取出的值直接拼装在sql语句中。会有安全问题。
例如我们配置如下:
<select id="getEmpByMap" resultType="com.cerr.mybatis.Employee">
select * from tb1_employee where id = ${id} and last_name = #{lastName}
</select>
假设我们传入的参数为id=1,last_name="Tom",其执行的sql语句是这样的:select * from tb1_employee where id = 2 and last_name = ?
大多数情况下,我们取参数的值都应该使用#{}
,但是如果原生jdbc不支持占位符的地方我们就可以使用${}
进行取值。
例如分表、排序等等:
按照年份分表拆分:select * from ${year}_salary where ....
然后通过传入年份进行拼接就可以实现。
传入排序规则来排序:select * from tb1_employee order by ${f_name} ${order}
。
示例:使用${}
来使表名可以动态指定
sql映射文件部分代码如下:
<select id="getEmpByMap" resultType="com.cerr.mybatis.Employee">
select * from ${tableName} where id = #{id} and last_name = #{lastName}
</select>
测试方法:
package com.cerr.mybatis;
import com.cerr.mybatis.dao.EmployeeMapper;
import com.cerr.mybatis.dao.EmployeeMapperAnnotation;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.junit.Assert.*;
public class MyBatisTest {
//获取SQLSessionFactory
public SqlSessionFactory getSqlSessionFactory() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
return new SqlSessionFactoryBuilder().build(inputStream);
}
@Test
public void test7() throws IOException {
//获取SqlSessionFactory
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
//获取SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
try {
//获取接口的实现类对象:会为接口自动的创建一个代理对象,代理对象去执行增删改查
EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
//传入一个map
Map<String,Object> map = new HashMap <>();
map.put("id",1);
map.put("lastName","aaa");
map.put("tableName","tb1_employee");
//调用方法
Employee employee = employeeMapper.getEmpByMap(map);
//打印
System.out.println(employee);
}finally {
//关闭
sqlSession.close();
}
}
}
此时就能通过传入一个表名的参数来动态指定sql中的表名,但是如果指定表名那处改为#{tableName}
则会报错,因为sql不能在表名处使用占位符。
#{}
取值时指定参数的相关规则
#{}
有更丰富的用法,规定参数的一些规则:JavaType
、jdbcTyoe
、mode(存储过程)
、numericScale
、resultMap
、typeHandler
、jdbcTypeName
、expression(未来准备支持的功能)
jdbcType
通常需要在某种特定的条件下被设置,在我们数据为null的时候,有些数据库可能不能设备mybatis对null的默认处理,比如Oracle(数据为null时会报错)。
当使用的是Oracle数据库时,如果传入的参数值有一个null值的话,会报错:JdbcType OTHER
:无效的类型。因为mybatis对所有的null都映射的是原生Jdbc的OTHER类型,Oracle不能正确处理。此时有两种解决方法:
- 我们可以使用
jdbcType
来指定类型,例如#{email,jdbcType=NULL}
- 在全局配置文件中设置
jdbcTypeForNull
属性为NULL
。
<settings>
<setting name="jdbcTypeForNull" value="NULL"/>
</settings>
网友评论