前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >MyBatis框架基础知识(03)

MyBatis框架基础知识(03)

作者头像
海拥
发布2021-08-23 15:16:54
7390
发布2021-08-23 15:16:54
举报
文章被收录于专栏:全栈技术全栈技术

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

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

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

代码语言:javascript
复制
update t_user set email=? where id=?

抽象方法可以设计为:

代码语言:javascript
复制
Integer updateEmailById(Integer id, String email);

映射的SQL语句配置为:

代码语言:javascript
复制
<update id="updateEmailById">
	UPDATE t_user SET email=#{email} WHERE id=#{id}
</update>

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

代码语言:javascript
复制
@Test
public void updateEmailById() {
    Integer id = 1;
    String email = "user01@163.com";
    Integer rows = userMapper.updateEmailById(id, email);
    System.out.println("rows=" + rows);
}

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

代码语言:javascript
复制
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个参数,例如:

代码语言:javascript
复制
<update id="updateEmailById">
	UPDATE t_user SET email=#{arg1} WHERE id=#{arg0}
</update>

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

代码语言:javascript
复制
<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注解,在注解中配置参数名,例如:

代码语言:javascript
复制
Integer updateEmailById(
    @Param("id") Integer id, 
    @Param("email") String email
);

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

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

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

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

2. 动态SQL–foreach

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

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

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

代码语言:javascript
复制
delete from t_user where id in (?,?,?,?,?);

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

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

代码语言:javascript
复制
Integer deleteByIds(List<Integer> ids);

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

代码语言:javascript
复制
Integer deleteByIds(Integer[] ids);

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

代码语言:javascript
复制
Integer deleteByIds(Integer... ids);

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

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

代码语言:javascript
复制
<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语句片段的最左侧字符串与最右侧字符串。

完成后,即可进行测试:

代码语言:javascript
复制
@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>节点来配置,其格式是:

代码语言:javascript
复制
<if test="表达式">
    满足表达式的判断条件时的SQL片段
</if>

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

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

代码语言:javascript
复制
<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注入的风险!

5. 解决查询时名称不匹配导致无法封装数据的问题【1】

在MyBatis处理查询时,会自动将“查询结果中的列名”与“封装查询结果的属性名”进行对照,如果一致,则会将查询结果中的值封装到对应的属性中!

例如在查询结果中存在名为username的列,值是root,同时,该查询返回的结果是User类型的,且User类中存在名为username的属性,则MyBatis会将root封装到User类对象的username属性中!

在配置SQL语句时,可以自定义别名,使得查询结果中的列名与属性名完全一致,则MyBatis就可以自动完成封装:

代码语言:javascript
复制
<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如何完成正确的封装!例如:

代码语言:javascript
复制
<!-- 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属性:

代码语言:javascript
复制
<select id="findById" resultMap="UserMap">
    SELECT * FROM t_user WHERE id=#{id}
</select>

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

代码语言:javascript
复制
<resultMap type="cn.tedu.spring.User" id="UserMap">
    <result column="group_id" property="groupId"/>
</resultMap>

另外,关于主键的配置,推荐使用<id>节点来配置,而不要使用<result>节点,并且,无论主键字段的名称是否匹配,都推荐显式的配置出来,也就是说,以上配置的推荐代码应该是:

代码语言:javascript
复制
<!-- 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语句大致是:

代码语言:javascript
复制
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类:

代码语言:javascript
复制
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类是需要与查询结果相对应的!

设计的抽象方法为:

代码语言:javascript
复制
UserVO findVOById(Integer id);

映射的SQL语句是:

代码语言:javascript
复制
<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语句大致是:

代码语言:javascript
复制
select * from t_group left join t_user on t_group.id=t_user.group_id where t_group.id=3;

课后:自行学习JSON的语句格式。

课后:复习SpringMVC框架的相关知识。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020-05-29 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 在抽象方法中定义多个参数
  • 2. 动态SQL–foreach
  • 3. 动态SQL–判断与选择
  • 4. 关于#{}和${}格式的占位符
  • 5. 解决查询时名称不匹配导致无法封装数据的问题【1】
  • 6. 解决查询时名称不匹配导致无法封装数据的问题【2】
  • 7. 一对一关系的关联查询
相关产品与服务
云数据库 SQL Server
腾讯云数据库 SQL Server (TencentDB for SQL Server)是业界最常用的商用数据库之一,对基于 Windows 架构的应用程序具有完美的支持。TencentDB for SQL Server 拥有微软正版授权,可持续为用户提供最新的功能,避免未授权使用软件的风险。具有即开即用、稳定可靠、安全运行、弹性扩缩等特点。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档