MyBatis框架基础知识(03)

举报
海拥 发表于 2021/10/05 14:32:26 2021/10/05
【摘要】 🌊 作者主页:海拥🌊 简介:🏆CSDN全栈领域优质创作者、🥇HDZ核心组成员、🥈蝉联C站周榜前十 1. 在抽象方法中定义多个参数假设需要实现:根据用户的id修改用户的电子邮箱。需要执行的SQL语句大致是:update t_user set email=? where id=?抽象方法可以设计为:Integer updateEmailById(Integer id, String e...

🌊 作者主页:海拥
🌊 简介:🏆CSDN全栈领域优质创作者、🥇HDZ核心组成员、🥈蝉联C站周榜前十

1. 在抽象方法中定义多个参数

假设需要实现:根据用户的id修改用户的电子邮箱。

需要执行的SQL语句大致是:

update t_user set email=? where id=?

抽象方法可以设计为:

Integer updateEmailById(Integer id, String email);

映射的SQL语句配置为:

<update id="updateEmailById">
	UPDATE t_user SET email=#{email} WHERE id=#{id}
</update>

完成后,在UserMapperTests中编写并执行单元测试:

@Test
public void updateEmailById() {
    Integer id = 1;
    String email = "user01@163.com";
    Integer rows = userMapper.updateEmailById(id, email);
    System.out.println("rows=" + rows);
}

如果直接执行以上单元测试,会出现如下错误:

Caused by: 
org.apache.ibatis.binding.BindingException: Parameter 'email' not found. Available parameters are [arg1, arg0, param1, param2]

在错误的提示信息中,可以明确的看到:可用的参数是[arg1, arg0, param1, param2]

其实,在配置SQL映射时,可以使用arg0表示抽象方法中的第1个参数,使用arg1表示抽象方法中的第2个参数,例如:

<update id="updateEmailById">
	UPDATE t_user SET email=#{arg1} WHERE id=#{arg0}
</update>

当然,也可以使用param1表示抽象方法中的第1个参数,使用param2表示抽象方法的第2个参数,例如:

<update id="updateEmailById">
	UPDATE t_user SET email=#{param2} WHERE id=#{param1}
</update>

在默认情况下,如果抽象方法的参数超过1个,在配置SQL映射时,应该使用arg系列的参数名称,第1个参数名使用arg0,第2个使用arg1,如果还有第3个、第4个甚至更多参数,以此类推,即使用arg2arg3……当然,也可以使用param系列的参数名称,只不过是从1开始顺序编号的,例如参数名为param1param2param3param4……

虽然使用argparam系列的名称可以解决多参数的问题,但是,会导致SQL映射的配置代码不直观的问题!MyBatis推荐的解决方法是在抽象方法的每一个参数之前添加@Param注解,在注解中配置参数名,例如:

Integer updateEmailById(
    @Param("id") Integer id, 
    @Param("email") String email
);

后续,在配置SQL映射时,在#{}占位符中,需要使用的就是@Param注解中配置的注解参数!

使用这种做法,既保证了方法的简单调用,又保证了XML文件中配置的SQL映射是直观的!

小结:如果抽象方法的参数列表中的参数超过了1个(达到2个或更多个),就必须为每一个参数添加@Param注解,并且,在#{}占位符中,需要使用的就是@Param注解中配置的注解参数!

练习:根据用户名和密码查询用户数据。

2. 动态SQL–foreach

动态SQL:根据执行时的参数不同,最终执行的SQL语句可能不同!

假设需要实现:一次性删除若干个用户数据。

需要执行的SQL语句大致是:

delete from t_user where id in (?,?,?,?,?);

以上SQL语句中,IN语法中的?的数量是不确定的。

由于在SQL语句中参数的数量并不确定,同时,这些参数的类型、表现的意义却是相同的,则可以将抽象方法声明为:

Integer deleteByIds(List<Integer> ids);

其实,也可以使用数组来表示若干个id值,例如:

Integer deleteByIds(Integer[] ids);

甚至,还可以将参数声明为可变参数,例如:

Integer deleteByIds(Integer... ids);

可变参数在被处理时,本质上就是一个数组。

然后,配置SQL映射,需要使用<foreach>节点对参数进行遍历:

<delete id="deleteByIds">
	DELETE FROM t_user WHERE id IN (
    	<foreach collection="list" item="id" separator=",">
    		#{id}
    	</foreach>
    )
</delete>

在配置<foreach>节点,关于各属性的配置:

  • collection:被遍历的对象,该对象可能是一个List集合,也可能是一个数组。当抽象方法的参数只有1个,且没有添加@Param注解时,该属性的值取决于参数的类型,当参数是List集合类型时,取值为list,当参数是数组或可变参数时,取值为array;如果抽象方法的参数超过1个,则参数必然添加了@Param注解,则该属性的值就是@Param注解的参数值。

  • item:遍历过程中,得到的集合或数组中的元素的名称,当确定该属性的名称后,在<foreach>节点的子级,就可以通过#{}占位符中填写这个名称来表示集合或数组中的某个值。

  • separator:生成动态SQL中的SQL语句片段时,各值之间使用什么符号进行分隔。

  • openclose:遍历生成的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);
}

3. 动态SQL–判断与选择

在动态SQL中还可以实现if判断的效果,需要使用<if>节点来配置,其格式是:

<if test="表达式">
    满足表达式的判断条件时的SQL片段
</if>

但是,并没有匹配的相当于else作用的节点,所以,如果某次判断,无论是true还是false都有对应的配置时,可以使用2个<if>分别使用完全相反的判断标准来进行配置。

当然,也可以使用<choose>系列的节点实现if...else的效果,其格式是:

<choose>
	<when test="条件">
        满足表达式的判断条件时的SQL片段
    </when>
    <otherwise>
    	不满足表达式的判断条件时的SQL片段
    </otherwise>
</choose>

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>

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>

配置以上<resultMap>使用时,<select>节点就必须配置resultMap属性,并且,不允许再配置resultType属性:

<select id="findById" resultMap="UserMap">
    SELECT * FROM t_user WHERE id=#{id}
</select>

在执行单表数据查询时,在配置<resultMap>时,如果查询结果的列名与类中的属性名本来就是完全一致的,则可以不必配置对应的<result>子节点!也就是以上配置可以改为:

<resultMap type="cn.tedu.spring.User" id="UserMap">
    <result column="group_id" property="groupId"/>
</resultMap>

另外,关于主键的配置,推荐使用<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>

关于<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个实体类都是与每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()
}

实体类与VO类,从代码设计方面来看,几乎是一样的,只不过,这2种类的定位不同,实体类是需要与数据表相对应的,而VO类是需要与查询结果相对应的!

设计的抽象方法为:

UserVO findVOById(Integer id);

映射的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>

注意:在关联查询时,一定不要使用星号*表示字段列表!

【阶段小结】当查询时,如果出现名称不匹配的问题(查询结果中的列名与封装结果的数据类型中的属性名)时,可以使用自定义别名的方式,使得列名与属性名一致,也可以使用<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;

🌊 行业资料:精品PPT模板几千套,简历模板一千多套
🌊 面试题库:Java核心知识点大全和面试真题资料
🌊 学习资料:2300套PHP建站源码,微信小程序入门资料,Python全集(400集)
🌊 学习交流群:点击此处进入

公众号【海拥】内回复【资源】获取以上所有资料

我已经写了很长一段时间的技术博客,这是我的一篇MyBatis框架基础知识(03)教程。我乐于通过文章分享技术与快乐。您可以访问我的博客主页: 华为云-海拥、我的个人博客:haiyong.site 以了解更多信息。希望你们会喜欢!

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。