MyBatis框架基础知识(03)
1. 在抽象方法中定义多个参数
假设需要实现:根据用户的id修改用户的电子邮箱。
需要执行的SQL语句大致是:
update t_user set email=? where id=?
- 1
抽象方法可以设计为:
Integer updateEmailById(Integer id, String email);
- 1
映射的SQL语句配置为:
<update id="updateEmailById">
UPDATE t_user SET email=#{email} WHERE id=#{id}
</update>
- 1
- 2
- 3
完成后,在UserMapperTests
中编写并执行单元测试:
@Test
public void updateEmailById() { Integer id = 1; String email = "user01@163.com"; Integer rows = userMapper.updateEmailById(id, email); System.out.println("rows=" + rows);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
如果直接执行以上单元测试,会出现如下错误:
Caused by:
org.apache.ibatis.binding.BindingException: Parameter 'email' not found. Available parameters are [arg1, arg0, param1, param2]
- 1
- 2
在错误的提示信息中,可以明确的看到:可用的参数是[arg1, arg0, param1, param2]
!
其实,在配置SQL映射时,可以使用arg0
表示抽象方法中的第1个参数,使用arg1
表示抽象方法中的第2个参数,例如:
<update id="updateEmailById">
UPDATE t_user SET email=#{arg1} WHERE id=#{arg0}
</update>
- 1
- 2
- 3
当然,也可以使用param1
表示抽象方法中的第1个参数,使用param2
表示抽象方法的第2个参数,例如:
<update id="updateEmailById">
UPDATE t_user SET email=#{param2} WHERE id=#{param1}
</update>
- 1
- 2
- 3
在默认情况下,如果抽象方法的参数超过1个,在配置SQL映射时,应该使用arg
系列的参数名称,第1个参数名使用arg0
,第2个使用arg1
,如果还有第3个、第4个甚至更多参数,以此类推,即使用arg2
、arg3
……当然,也可以使用param
系列的参数名称,只不过是从1开始顺序编号的,例如参数名为param1
、param2
、param3
、param4
……
虽然使用arg
或param
系列的名称可以解决多参数的问题,但是,会导致SQL映射的配置代码不直观的问题!MyBatis推荐的解决方法是在抽象方法的每一个参数之前添加@Param
注解,在注解中配置参数名,例如:
Integer updateEmailById( @Param("id") Integer id, @Param("email") String email
);
- 1
- 2
- 3
- 4
后续,在配置SQL映射时,在#{}
占位符中,需要使用的就是@Param
注解中配置的注解参数!
使用这种做法,既保证了方法的简单调用,又保证了XML文件中配置的SQL映射是直观的!
小结:如果抽象方法的参数列表中的参数超过了1个(达到2个或更多个),就必须为每一个参数添加@Param
注解,并且,在#{}
占位符中,需要使用的就是@Param
注解中配置的注解参数!
练习:根据用户名和密码查询用户数据。
2. 动态SQL–foreach
动态SQL:根据执行时的参数不同,最终执行的SQL语句可能不同!
假设需要实现:一次性删除若干个用户数据。
需要执行的SQL语句大致是:
delete from t_user where id in (?,?,?,?,?);
- 1
以上SQL语句中,
IN
语法中的?
的数量是不确定的。
由于在SQL语句中参数的数量并不确定,同时,这些参数的类型、表现的意义却是相同的,则可以将抽象方法声明为:
Integer deleteByIds(List<Integer> ids);
- 1
其实,也可以使用数组来表示若干个id值,例如:
Integer deleteByIds(Integer[] ids);
- 1
甚至,还可以将参数声明为可变参数,例如:
Integer deleteByIds(Integer... ids);
- 1
可变参数在被处理时,本质上就是一个数组。
然后,配置SQL映射,需要使用<foreach>
节点对参数进行遍历:
<delete id="deleteByIds">
DELETE FROM t_user WHERE id IN ( <foreach collection="list" item="id" separator=","> #{id} </foreach> )
</delete>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
在配置<foreach>
节点,关于各属性的配置:
-
collection
:被遍历的对象,该对象可能是一个List
集合,也可能是一个数组。当抽象方法的参数只有1个,且没有添加@Param
注解时,该属性的值取决于参数的类型,当参数是List
集合类型时,取值为list
,当参数是数组或可变参数时,取值为array
;如果抽象方法的参数超过1个,则参数必然添加了@Param
注解,则该属性的值就是@Param
注解的参数值。 -
item
:遍历过程中,得到的集合或数组中的元素的名称,当确定该属性的名称后,在<foreach>
节点的子级,就可以通过#{}
占位符中填写这个名称来表示集合或数组中的某个值。 -
separator
:生成动态SQL中的SQL语句片段时,各值之间使用什么符号进行分隔。 -
open
与close
:遍历生成的SQL语句片段的最左侧字符串与最右侧字符串。
完成后,即可进行测试:
@Test
public void deleteByIds() { List<Integer> ids = new ArrayList<Integer>(); ids.add(9); ids.add(14); ids.add(16); Integer rows = userMapper.deleteByIds(ids); System.out.println("rows=" + rows);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
3. 动态SQL–判断与选择
在动态SQL中还可以实现if
判断的效果,需要使用<if>
节点来配置,其格式是:
<if test="表达式"> 满足表达式的判断条件时的SQL片段
</if>
- 1
- 2
- 3
但是,并没有匹配的相当于else
作用的节点,所以,如果某次判断,无论是true
还是false
都有对应的配置时,可以使用2个<if>
分别使用完全相反的判断标准来进行配置。
当然,也可以使用<choose>
系列的节点实现if...else
的效果,其格式是:
<choose>
<when test="条件"> 满足表达式的判断条件时的SQL片段 </when> <otherwise> 不满足表达式的判断条件时的SQL片段 </otherwise>
</choose>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
4. 关于#{}和${}格式的占位符
在MyBatis中,配置SQL映射时,可以使用#{}
或${}
格式的占位符表示某个变量。
当需要表示的是某个值时,应该使用#{}
格式的占位符,简单的说,在学习JDBC时,自行编写的SQL语句中可以使用问号?
的位置都应该使用#{}
格式的占位符。严格来说,当使用#{}
格式的占位符时,MyBatis会先使用问号?
对这些位置进行占位,然后,将SQL语句发送到MySQL服务器,MySQL服务器对例如delete from t_user where id=?
这类存在问号?
的SQL语句进行词法分析、语义分析、编译等过程,当编译通过后,再将各个值代进去执行,所以,整个过程是预编译处理的!由于是使用预编译处理的,所以,在使用各个值时,并不需要关心数据类型的问题,也不存在SQL注入的风险!
当需要表示的是SQL语句中的某个片段时,应该使用${}
格式的占位符,凡在SQL语句中不可以写成问号?
的部分必须使用${}
格式的占位符。当使用${}
格式的占位符时,不可能使用预编译的做法,因为例如select * from t_user where ?
这样的SQL语句是不正常的,甚至有些还是不合法的!MyBatis在处理时,必须先将${}
占位符的值与所配置的SQL语句进行拼接,然后再执行词法分析、语义分析、编译等过程,如果编译通过,则直接执行(值在这之前就已经代进去了)。由于在编译之前就把${}
占位符的值已经代入到SQL语句中了,所以,必须严格区分在完整的SQL语句中的各个值的数据类型!同时,还存在SQL注入的风险!
懒汉式小结:当需要使用占位符表示某个参数值是,全部使用#{}
的格式,如果发现该格式的无效,则改用${}
格式。
小结:使用#{}
格式的占位符只能表示SQL语句中的某个值,在处理过程中是预编译的,可以无视值的数据类型,没有SQL注入的风险!使用${}
格式的占位符可以表示SQL语句中的任何片段,是直接与SQL语句进行拼接再编译、执行的,必须严格表现值的数据类型,且存在SQL注入的风险!
5. 解决查询时名称不匹配导致无法封装数据的问题【1】
在MyBatis处理查询时,会自动将“查询结果中的列名”与“封装查询结果的属性名”进行对照,如果一致,则会将查询结果中的值封装到对应的属性中!
例如在查询结果中存在名为username
的列,值是root
,同时,该查询返回的结果是User
类型的,且User
类中存在名为username
的属性,则MyBatis会将root
封装到User
类对象的username
属性中!
在配置SQL语句时,可以自定义别名,使得查询结果中的列名与属性名完全一致,则MyBatis就可以自动完成封装:
<select id="findAll" resultType="cn.tedu.spring.User"> SELECT id, username, password, age, phone, email, group_id AS groupId FROM t_user ORDER BY id LIMIT 0, 100
</select>
- 1
- 2
- 3
- 4
- 5
- 6
6. 解决查询时名称不匹配导致无法封装数据的问题【2】
当名称不匹配时,还可以在XML文件中配置<resultMap>
节点,以指导MyBatis如何完成正确的封装!例如:
<!-- id:自定义的名称 -->
<!-- type:封装查询结果的数据类型 -->
<resultMap type="cn.tedu.spring.User" id="UserMap"> <!-- column:查询结果中的列名 --> <!-- property:封装查询结果的类中的属性名 --> <result column="id" property="id"/> <result column="username" property="username"/> <result column="password" property="password"/> <result column="age" property="age"/> <result column="phone" property="phone"/> <result column="email" property="email"/> <result column="group_id" property="groupId"/>
</resultMap>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
配置以上<resultMap>
使用时,<select>
节点就必须配置resultMap
属性,并且,不允许再配置resultType
属性:
<select id="findById" resultMap="UserMap"> SELECT * FROM t_user WHERE id=#{id}
</select>
- 1
- 2
- 3
在执行单表数据查询时,在配置<resultMap>
时,如果查询结果的列名与类中的属性名本来就是完全一致的,则可以不必配置对应的<result>
子节点!也就是以上配置可以改为:
<resultMap type="cn.tedu.spring.User" id="UserMap"> <result column="group_id" property="groupId"/>
</resultMap>
- 1
- 2
- 3
另外,关于主键的配置,推荐使用<id>
节点来配置,而不要使用<result>
节点,并且,无论主键字段的名称是否匹配,都推荐显式的配置出来,也就是说,以上配置的推荐代码应该是:
<!-- id:自定义的名称 -->
<!-- type:封装查询结果的数据类型 -->
<resultMap type="cn.tedu.spring.User" id="UserMap"> <!-- column:查询结果中的列名 --> <!-- property:封装查询结果的类中的属性名 --> <id column="id" property="id"/> <result column="group_id" property="groupId"/>
</resultMap>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
关于<resultMap>
的配置小结:
- 使用
<id>
节点来配置主键的对应关系,不要使用<result>
来配置,并且,即使名称完全一致,也应该配置; - 在执行单表数据查询时,如果名称本来就是完全一致的,则可以不必配置对应的
<result>
子节点!
7. 一对一关系的关联查询
假设需要实现:根据id查询某个用户详情时,显示该用户归属的组的名称!
需要执行的SQL语句大致是:
select
t_user.*, t_group.name
from t_user left join t_group on t_user.group_id=t_group.id where t_user.id=10;
- 1
- 2
- 3
项目的实体类都是不满足关联查询需求的!因为每1个实体类都是与每1张数据表相对应的,决不可能存在某1个实体类可以对应多张表的关联查询结果,为了封装关联查询的结果,需要创建对应的VO
类:
public class UserVO { private Integer id; private String username; private String password; private Integer age; private String phone; private String email; private Integer groupId; private String groupName; // Getters & Setters // toString()
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
实体类与VO类,从代码设计方面来看,几乎是一样的,只不过,这2种类的定位不同,实体类是需要与数据表相对应的,而VO类是需要与查询结果相对应的!
设计的抽象方法为:
UserVO findVOById(Integer id);
- 1
映射的SQL语句是:
<select id="findVOById" resultType="cn.tedu.spring.UserVO"> SELECT t_user.id, username, password, age, phone, email, group_id AS groupId, t_group.name AS groupName
FROM t_user LEFT JOIN t_group ON t_user.group_id=t_group.id WHERE t_user.id=#{id}
</select>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
注意:在关联查询时,一定不要使用星号*
表示字段列表!
【阶段小结】当查询时,如果出现名称不匹配的问题(查询结果中的列名与封装结果的数据类型中的属性名)时,可以使用自定义别名的方式,使得列名与属性名一致,也可以使用<resultMap>
指导MyBatis进行封装,暂定的规则是:当查询允许使用星号(*
)表示字段列表时,应该使用<resultMap>
进行配置,当查询不允许使用星号(*
)时,就需要自行穷举字段列表,就顺便自定义别名,以解决名称不匹配的问题。
根据id查询某个用户组的详情时,显示该组的所有用户的信息!需要执行的SQL语句大致是:
select * from t_group left join t_user on t_group.id=t_user.group_id where t_group.id=3;
- 1
课后:自行学习JSON的语句格式。
课后:复习SpringMVC框架的相关知识。
文章来源: haiyong.blog.csdn.net,作者:海拥✘,版权归原作者所有,如需转载,请联系作者。
原文链接:haiyong.blog.csdn.net/article/details/106430081
- 点赞
- 收藏
- 关注作者
评论(0)