目录
1. 将id=?的数据的电子邮箱改为?
在UserMapper
接口中添加抽象方法:
Integer updateEmailById(
@Param("id") Integer id,
@Param("email") String email
);
如果抽象方法的参数的数量超过1个,应该在每个参数之前添加@Param
注解,并在注解中配置参数的名称,后续在配置SQL语句时,使用的#{}
占位符中也需要写注解中配置的名称!
在SomeMapper.xml中添加配置:
<update id="updateEmailById">
UPDATE t_user SET email=#{email} WHERE id=#{id}
</update>
在配置SQL时,如果抽象方法的参数只有1个,则在 #{} 占位符中写任意名称均可,因为框架会找唯一的那个参数!如果抽象方法的参数有多个(超过1个),由于.java文件在编译成.class文件时会丢失局部变量(参数也是一种局部变量)名称,所以,在#{}占位符中写参数名称是没有意义的,框架在执行时无法区分,可以使用#{arg0}表示第1个参数,使用#{arg1}表示第2个参数,以此类推,或者,使用#{param1}表示第1个参数,使用#{param2}表示第2个参数,以此类推……这样的做法虽然能解决问题,但是,SQL语句的代码的可读性就非常差,当抽象方法的参数都添加了@Param
注解时,在SQL语句中就可以使用注解中配置的名称,以增加代码的可读性!
完成后,可以在Tests
中编写单元测试:
@Test
public void updateEmailById() {
Integer id = 1;
String email = "[email protected]";
Integer rows = userMapper.updateEmailById(id, email);
System.out.println("rows=" + rows);
}
2. 动态SQL–foreach
动态SQL:在配置SQL语句时,可以添加例如<if>
、<foreach>
等节点,实现在SQL语句中的动态生成效果,例如使用<if>
时,就可以对参数进行判断,最终可能生成不同的SQL语句,使用<foreach>
对参数进行遍历,根据参数不同,生成的SQL语句也会不一样,这种机制就称之为“动态SQL”。
例如存在“一次性删除若干条数据”的需求,到底要删除多少条数据,这些数据的id分别是多少,对于开发人员来说是未知的!则SQL语句就是无法确定的!就需要使用动态SQL中的foreach的做法!即要求用户提供若干个id的集合,然后遍历该集合,形成SQL语句中where id in ()
括号中的若干个问号!
首先,还是应该在UserMapper
接口中添加抽象方法:
Integer deleteByIds(List<Integer> ids);
<delete id="deleteByIds">
DELETE FROM t_user WHERE id IN
<foreach collection="list"
item="id" separator=","
open="(" close=")">
#{id}
</foreach>
</delete>
参数需要表示若干个id值,可以使用
List
集合,也可以使用数组,或者声明为可变参数(在处理过程中,等同于数组)。
然后,在SomeMapper.xml中添加配置:
在配置<foreach>
节点时,相关属性的配置:
-
collection
:被遍历的参数对象。当抽象方法的参数只有1个,没有添加@Param
注解时,当对象是List
集合类型时,取值为list
,当对象是数组类型时,取值为array
;当抽象方法的参数有多个,并且添加了@Param
注解时,该属性的值就是注解中定义的名称; -
item
:在遍历过程中,被遍历到的数据的名称,是自定义的,等同于for (Integer id : ids)
中的id
,并且,在子级的文本中使用#{}
占位符时,使用的名称就是该item
属性的值; -
separator
:分隔符,生成遍历结果时,每个数据之间的分隔符,即若干个id值之间的逗号; -
open
与close
:遍历生成的SQL语句部分的最左侧和最右侧的字符串。
3. 动态SQL–if
在配置SQL时,可以使用<if>
对参数进行判断,例如:
<select id="find" resultType="cn.tedu.mybatis.User">
SELECT * FROM t_user
<if test="where != null">
WHERE ${where}
</if>
<if test="orderBy != null">
ORDER BY ${orderBy}
</if>
<if test="offset != null and count != null">
LIMIT #{offset}, #{count}
</if>
</select>
注意:在动态SQL中,虽然有<if>
节点,但是,并没有所谓的else
的节点!如果需要考虑else
对应的情况,可以使用2个<if>
写上完全相反的2个条件,例如<if test="where != null">
和<if test="where == null">
,也可以使用<choose>
、<when>
、<otherwise>
的组合。
注意:凡是能在Java程序中解决的问题,就尽量不要通过动态SQL来解决!
4. 关于#{}和${}占位符
在MyBatis中配置SQL语句时,参数可以使用#{}
格式的占位符,也可以使用${}
格式的占位符!
使用#{}
格式的占位符,是用于对值进行占位的,并不能表示SQL语句结构中的某个部分,也不可以表示某个表达式!而${}
格式的占位符可以表示任何内容!
使用#{}
格式的占位符,是采取预编码的做法的,即先无视值的大小,将SQL执行编译,编译通过后,再将值代进去来执行SQL语句!使用${}
格式的占位符,是先把占位符的值拼接到SQL语句,再进行编译,编译通过后再运行的!
可以简单的认为:以前学JDBC使用PreparedStatement
时,可以写问号?
的位置,都可以使用#{}
格式的占位符,反之,不可以写问号?
的位置,就不能使用#{}
格式的占位符,而需要使用${}
格式的占位符!
由于使用${}
格式的占位符不是预编译的,所以,还存在SQL注入的风险!
简单的来说,优先使用#{}
格式的占位符,如果不正确,换成${}
格式的占位符!
5. 使用自定义别名解决名称不匹配的查询问题
先在t_user
数据表中添加名为is_delete
的字段,用于表示“某用户的数据是否标记为删除”,默认为0
,表示“未删除,正常”,也可以设置为1
表示“已删除”。
添加字段的SQL语句:
ALTER TABLE t_user ADD COLUMN is_delete INT;
UPDATE t_user SET is_delete=0;
然后,User
类应该是与t_user
表相对应的,所以,还应该在User
类中补充对应的属性:
private Integer isDelete;
// SET/GET
添加之后,再次查询用户数据,会发现查询结果中isDelete
的值是null
!
因为MyBatis在处理查询时,默认情况下,要求查询结果的列名与封装结果的属性名(其实是SET方法的名称)保持一致!
既然目前名称并不一致,而属性名(SET方法名)是不可能随便调整的,就只能修改查询结果的列名,通过查询时指定别名就可以实现这样的效果:
<select id="findAll" resultType="cn.tedu.mybatis.User">
SELECT
id, username,
password, age,
phone, email,
is_delete AS isDelete
FROM
t_user
ORDER BY
id
</select>
6. 使用解决查询时名称不匹配的问题
区分3个名词:
字段名(Field):创建数据表时指定的各字段的名称;
列名(Column):查询结果的表格中每项数据上方的名称,默认情况下,列名就是字段名,当然,也可以在查询时指定别名,则列名就是别名;
属性名(Property):类中定义的全局变量的名称;
如果查询结果的列名与封装结果的属性名不一致,且不指定别名,还可以通过配置<resultMap>
来解决!
配置<resultMap>
节点的目的就是指导MyBatis框架,如何将查询结果中的数据封装到返回的对象中!
配置代码如下:
<!-- id:自定义名称 -->
<!-- type:封装查询结果的类的全名 -->
<resultMap id="UserEntityMap"
type="cn.tedu.mybatis.User">
<!-- 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="is_delete" property="isDelete"/>
</resultMap>
配置完成后,在<select>
节点中,就不必再配置resultType
属性了,而是改为配置resultMap
属性,例如:
<select id="findAll" resultMap="UserEntityMap">
SELECT
*
FROM
t_user
ORDER BY
id
</select>
类似以上这种单表的数据查询中,如果查询结果的列名与类中的属性名是完全相同的,其实可以不必配置,所以,以上<resultMap>
可以简化为:
<resultMap id="UserEntityMap" type="cn.tedu.mybatis.User">
<result column="is_delete" property="isDelete"/>
</resultMap>
另外,除了使用<result>
节点配置对应关系以外,还可以使用<id>
节点进行配置,只不过,<id>
节点应该只用于配置主键字段!例如配置为:
<resultMap id="UserEntityMap" type="cn.tedu.mybatis.User">
<id column="id" property="id"/>
<result column="is_delete" property="isDelete"/>
</resultMap>
从查询结果看来,使用<id>
和<result>
这2种节点的配置效果完全相同,但是,使用<id>
配置有利于MyBatis框架实现数据缓存!推荐将主键使用<id>
来配置!
7. 简单的关联表查询
创建t_department
部门信息表:
CREATE TABLE t_department (
id INT AUTO_INCREMENT COMMENT '部门id',
name VARCHAR(20) NOT NULL COMMENT'部门名称',
PRIMARY KEY (id)
) DEFAULT CHARSET=utf8;
然后,还应该在项目中创建Department
类:
public class Department {
private Integer id;
private String name;
// SET/GET/toString
}
如果要使得用户数据与以上部门的数据产生关联,还应该先在用户数据表中添加“用户所归属的部门”字段:
ALTER TABLE t_user ADD COLUMN department_id INT;
修改了用户数据表,则应该在User
类中也补充添加相应的字段:
private Integer departmentId;
然后,在2张中调整一下测试数据:
INSERT INTO t_department (name) VALUES ('软件研发部'), ('人力资源部'), ('财务部');
UPDATE t_user SET department_id=1 WHERE id IN (1,8,15);
UPDATE t_user SET department_id=2 WHERE id IN (4,11,14);
UPDATE t_user SET department_id=3 WHERE id IN (9,10,13);
假设存在需求:查询某个用户的信息,并显示该用户归属的部门的名称。
需要执行的SQL语句大致大致是:
select
t_user.id, username,
password, age,
phone, email,
is_delete, department_id,
name
from
t_user
left join
t_department
on
t_user.department_id=t_department.id
where
t_user.id=?
与数据表相对应的是实体类(Entity),当涉及关联表查询时,必然没有任何一个实体类可以封装查询结果,则需要自定义新的**VO类(Value Object)**来封装查询结果!这种类与实体类只是定位不同,它是与查询结果对应的,或者是根据查询结果来定制的,编码方式几乎一致!
所以,需要先创建VO类,表示此次的查询结果:
public class UserVO {
private Integer id;
private String username;
private String password;
private Integer age;
private String phone;
private String email;
private Integer isDelete;
private Integer departmentId;
private String departmentName;
}
在UserMapper
接口中定义抽象方法:
UserVO findVOById(Integer id);
然后SomeMapper.xml中配置以上方法对应的SQL语句:
<select id="findVOById" resultType="cn.tedu.mybatis.UserVO">
SELECT
t_user.id, username,
password, age,
phone, email,
is_delete AS isDelete,
department_id AS departmentId,
name AS departmentName
FROM
t_user
LEFT JOIN
t_department
ON
t_user.department_id=t_department.id
WHERE
t_user.id=#{id}
</select>
8. 练习
创建DepartmentMapper
接口,用于定义管理部门数据的抽象方法;
将SomeMapper.xml重命名为UserMapper.xml;
将UserMapper.xml复制得到DepartmentMapper.xml,并清空其中的配置,该DepartmentMapper.xml将用于配置管理部门数据的SQL语句;
通过MyBatis框架实现以下功能:
-
增加新的部门;
-
修改部门的名称;
-
根据某个id删除部门;
-
批量删除部门;
-
查询部门列表;
-
根据某个id查询部门。