详解parameterType
Mapper.xml的每一个sql标签的parameterType属性表示输入参数,并且在sql语句中,用#{}或${}来从parameterType中取得对应的值。当输入参数的类型不同时,#{}和${}的用法有很大的区别。
基本类型
当输入参数时八个基本类型或string类型时,#{}和${}既有相似之处,又有不同的用法:
不同
第一、基本类型中#{}中的内容可以写任意值,如# {xxx};而${}中必须写value,如$ {value}(但是在新的jar包中取消了这一限制,所以这一点没有什么区别了)。
第二、对于String类型,#{}会自动给里面的参数套上单引号’ ‘,然而${}会进行原样输出,那么以下这个语句:
<select id="queryPersonByName" resultType="person" parameterType="string">
select * from person where name= ${values}
</select>
实际上提交给数据库的是
select * from person where name= 刘咯咯 ;
这个语句显然在sql中是不成立的,原因是数据库的字符串要加上单引号。需要改写成以下的样子:
<select id="queryPersonByName" resultType="person" parameterType="string"> select * from person where name='${value}' </select>
但是${}在动态排序里面就有很大的作用。一个根据输入的列名的排序语句如果用#{}写是这样的:
<select id="queryPersonOrderedByColumn" resultType="person" parameterType="string">
select * from person order by ${column } desc
</select>
将列名带入后实际上的sql语句是:
<select id="queryPersonOrderedByColumn" resultType="person" parameterType="string"> select * from person order by 'name' desc </select>
‘name’是一个常量,比较无结果,所以这个时候不能用#{}自动生成单引号。
第三、#{}可以防止SQL注入而${}不防止。sql注入是什么我现在也不知道,等以后要用到再说吧,参考资料。
相同
都可以获取对象的值(嵌套类型对象)
第一、在模糊查询中,这两种方式虽然写法稍有不同,但是都能实现:
#{}
<select id="queryPersonByNameOrAge" resultType="person" parameterType="person"> select * from person where name like #{name} or age=#{age} </select> Person person=new Person(); person.setAge(19); person.setName("%刘%");
${}
<select id="queryPersonByNameOrAge" resultType="person" parameterType="person"> select * from person where name like '%${name}%' or age=#{age} </select> Person person=new Person(); person.setAge(19); person.setName("刘");
第二、获取对象的值的方式相同
原来直接传入的是person类的实例,现在新建一个类address,根据address的值来查询。可以直接将address作为输入参数传入:
<select id="queryPersonByAddress" resultType="person" parameterType="address">
select * from person where homeaddress=#{homeAddress} or schooladdress='${schoolAddress}'
</select>
也可以直接获取嵌套对象的值(传入person获取其address):
<select id="queryPersonByAddress" resultType="person" parameterType="person">
select * from person where homeaddress=#{address.homeAddress} or schooladdress='${address.schoolAddress}'
</select>
对象类型
当parameterType的取值是对象类型时,使用#{}和${}都必须写属性名:
<select id="queryPersonByNameOrAge" resultType="person" parameterType="person">
select * from person where name like '%${name}%' or age=#{age}
</select>
如果输入的值为多个,还可以用HashMap来作为parameterType的取值:
<select id="queryPersonByNameOrAgeWithHashMap" resultType="person" parameterType="HashMap"> select * from person where name like '%${mName}%' or age=#{mAge} </select>
传入的HashMap,key的值去匹配占位符,如果名称相同就用value去替换掉占位符:
Map<String,Object> personMap=new HashMap<>();
personMap.put("mName","刘");
personMap.put("mAge",20);
List<Person> persons=personMapper.queryPersonByNameOrAgeWithHashMap(personMap);
parameterType用HashMap来作为输入参数的一个重要用途是调用存储过程。这一点是仅仅传入一个实体类对象是无法实现的。
parameterType为HashMap时调用存储过程
存储过程无论输入参数是什么值,语法上都需要用map来传递该值.
首先在数据库中先创建两个存储过程:
1.查询某个年龄的总人数
DELIMITER //
create PROCEDURE queryCountByAgeWithProcedure (IN mAge int, OUT pNum int)
begin
select count(1) into pNum from person where age=mAge;
end
//
DELIMITER ;
2.根据id值来删除一个人的信息
DELIMITER //
Create procedure deletePersonByIdWithProcedure(In mId int)
Begin
Delete from person where id =mId;
End
//
DELIMITER ;
mysql中创建存储过程要先将分隔符改成别的东西。参考资料
Mapper.xml
除了parameterType为HashMap之外,还需要写上一个statementType=CALLABLE以表示这个语句是执行的存储过程。之前所有的sql标签都没有写是因为statementType默认值是PREPARED;此外statementType还有一个类型是STATEMENT,表示直接操作sql,不进行预编译:
<select id="queryCountByAgeWithProcedure" statementType="CALLABLE" parameterType="HashMap"> ... </select>
通过关键字CALL来调取存储过程,调用的存储过程还需要写明参数的名称、类型、模式,CALL需要在大括号中调用:
<select id="queryCountByAgeWithProcedure" statementType="CALLABLE" parameterType="HashMap">
{
CALL queryCountByAgeWithProcedure(
#{mAge,jdbcType=INTEGER,mode=IN},
#{pNum,jdbcType=INTEGER,mode=OUT}
)
}
</select>
测试类方法
在测试类中,输入的参数还是和上面的一样用HashMap的put方法,同样也是要让key值和占位符匹配。不过在MyBatis中所有的存储过程的方法都没有返回值——返回值存储在HashMap中,所以接口中的方法返回值是void类型:
public interface personMapper { ... void queryCountByAgeWithProcedure(Map<String,Object> para); void deletePersonByIdWithProcedure(Map<String,Object>para); }
从HashMap中取出存储过程的返回值的方式:
//用存储过程来查询年龄总人数 public static void queryCountByAgeWithProcedure() throws IOException { ... Map<String,Object> map=new HashMap<>(); map.put("mAge",19); personMapper.queryCountByAgeWithProcedure(map); Object pNum=map.get("pNum"); System.out.println("查询到"+pNum+"人"); ... }
存储过程的增删改也都需要在最后commit()一下:
//用存储过程来根据id删人
public static void deletePersonByIdWithProcedure() throws IOException {
Reader reader=Resources.getResourceAsReader("conf.xml");
SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(reader);
SqlSession sqlSession=sqlSessionFactory.openSession();
personMapper personMapper =sqlSession.getMapper(personMapper.class);
Map<String,Object> map=new HashMap<>();
map.put("mId",4);
personMapper.deletePersonByIdWithProcedure(map);
sqlSession.commit();
sqlSession.close();
reader.close();
}
jdbcType
jdbcType这个东西是MyBatis独有的,对应还有一个是javaType。虽然说java的类型能直接和数据库的类型映射,但是当java的一个变量的值是null时,数据库可能只知道其为空值,但是不知道它的具体类型是什么。
例如当parameterType为HashMap的时候,我们定义的HashMap的value类型是Object,此时如果value=null,那么数据库就无法将这个null和自己的类型进行匹配。所以Mybatis在实现可能存在未知类型的空值时要求用上jdbcType,以告诉数据库这是一个什么样的空值。
但是一般情况下数据的类型时可以确定的(例如直接写明parameterType的类型),myBatis可以直接将类型告诉给数据库,此时就不是一定要加上jdbcType了。