### 1. 关于MyBatis的基本使用
1. 添加一系列依赖:`mysql-connector-java`、`commons-dbcp`、`junit`、`mybatis`、`mybatis-spring`、`spring-context/spring-webmvc`、`spring-jdbc`;
2. 关于配置:配置数据库连接、在Spring中读取数据库连接的配置、配置数据源、配置接口文件的位置、配置SQL语句的XML文件的位置及所使用的数据源;
3. 关于抽象方法:必须写在接口中;如果需要执行的操作是增、删、改类型的,使用`Integer`作为返回值类型,如果需要执行的操作是查询类型的,则将返回值声明为所期望的类型(能够封装查询结果的类型);方法的名称可以自定义,不允许重载;可以根据所执行的SQL语句中不确定的值(SQL语句中有哪些问号)来设计方法的参数列表,当方法的参数超过1个时,必须为每一个参数添加`@Param`注解,并在注解中指定名称;
4. 关于配置XML映射:XML的根节点必须有文档声明;根节点`<mapper>`必须配置对应的接口;根据需要执行的SQL语句的类型选择子级节点;如果执行的是增加数据的操作,可以配置`useGeneratedKeys="true"`和`keyProperty="属性名"`来获取自增的字段的值;如果执行的是查询操作,则必须配置`resultType`或`resultMap`中的1个属性(二选一),在配置`resultType`时,取值为返回值类型的全名,如果返回值是`List`集合类型的,只需要配置为集合中的元素的类型,例如返回`List<User>`时,配置`resultType`的值就是`User`类的全名;在编写SQL语句时,所有的变量值都使用`#{}`格式的占位符,占位符中的名称应该是参数名,或对象型的参数中的属性名;
5. 每写完一个功能,或者改动了一个功能,都应该及时的执行单元测试!
### 2. 动态SQL--foreach【重要】
动态SQL:在配置SQL语句时,可以添加例如`<if>`、`<foreach>`相关的配置,最终可以根据参数值不同,生成不同的SQL语句!
在配置MyBatis中映射的SQL语句时,可以添加`<foreach>`实现对参数的遍历!
例如:需要实现“根据若干个id批量删除用户数据(一次性删除若干条)”。
需要执行的SQL语句大致是`delete from t_user where id in (?,?,?)`,其中,问号的数量是开发者无法确定的,应该是由软件的使用者来决定的!
在设计抽象方法时,应该使用某1个参数来表示若干个即将被删除的数据的id,例如使用`List<Integer>`就可以表示多个`id`,也可以使用`Integer[]`或`Integer...`,所以,抽象方法可以设计为:
Integer deleteByIds(List<Integer> ids);
Integer deleteByIds(Integer[] ids);
Integer deleteByIds(Integer... ids);
然后,需要配置以上某个抽象方法的映射:
<delete id="deleteByIds">
DELETE FROM t_user WHERE id IN (
<foreach collection="array" item="id" separator=",">
#{id}
</foreach>
)
</delete>
关于`<foreach>`节点的配置:
- `collection`属性:表示被遍历的参数!当抽象方法的参数只有1个时,当参数的类型是`List`集合时,取值为`list`,当参数的类型是数组或可变参数时,取值为`array`;当抽象方法的参数有多个时,肯定添加了`@Param`注解,此处的取值就是注解中配置的名称;
- `item`属性:表示遍历过程中每个元素的名称,也是子级中`#{}`中的名称,是自定义的名称;
- `separator`属性:表示遍历的各个值用什么符号分隔;
- `open`属性与`close`属性:表示遍历生成的SQL语句片断的最左侧字符串和最右侧字符串。
### 3. 动态SQL--if【一般】
关于`<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>
注意:强烈不推荐使用这种做法,使用1个查询的配置完成若干种查询需求!
### 4. 关于#{}和${}格式的占位符【理解】
最简化原则:先全部使用`#{}`格式的占位符,如果不可用,则改为`${}`格式的占位符!
使用`#{}`占位符时,MyBatis是预编译处理的,即各个占位符都会先使用`?`进行占位,并进行编译,编译通过后,再将值代进SQL语句中执行;简单的说,使用`#{}`格式的占位符只能表示某个“值”,并且,由于是预编译的,所以,不必关心值的类型,例如值是字符串类型的,也不必考虑使用单引号`'`框住值的问题;
使用`${}`占位符时,MyBatis并不会预编译,而是先将占位符中的值代进SQL语句,进行单纯的字符串拼接,然后再编译,最后执行,所以,必须保证拼接后的SQL语句是符合SQL语法的,否则会导致编译错误,最直接的表现就是例如字符串类型的值需要使用单引号`'`框住,同时,由于并没有使用编译的做法,还存在SQL注入的风险!简单的说,使用这种格式的占位符时,既需要考虑值的数据类型的问题,还需要考虑SQL注入的风险,应该是“能不这么用,就不这么用”!
### 5. 课堂练习
准备工作:
创建“部门信息表”(`t_department`),要求在该表中至少中存在`id`和部门名称(`name`)这2个字段:
CREATE TABLE t_department (
id INT AUTO_INCREMENT,
name VARCHAR(10) UNIQUE NOT NULL,
PRIMARY KEY (id)
) DEFAULT CHARSET=UTF8MB4;
然后,向该数据表中插入不少于3条记录。
INSERT INTO t_department (name) VALUES ('软件研发部'), ('人力资源部'), ('财力部');
课堂练习:使用MyBatis框架实现:
1. 向部门表中插入部门信息;
2. 批量删除部门信息;
3. 查询id=?的部门的信息;
4. 查询所有部门的信息。
### 6. 使用自定义的别名解决名称不匹配的问题
准备工作:在“用户表”中增加新的字段“部门(department_id)”,用于表示某个用户归属于哪个部门!
ALTER TABLE t_user ADD COLUMN department_id INT;
然后,为用户分配部门:
UPDATE t_user SET department_id=2 WHERE id IN (4);
UPDATE t_user SET department_id=4 WHERE id IN (6,8,9);
UPDATE t_user SET department_id=6 WHERE id IN (5,7);
接下来,还应该在`User`类中添加新的属性`private Integer departmentId;`,并为该属性添加SET/GET方法,重新生成`toString()`方法!
再次查询时,会发现:查询的输出结果中,`departmentId`的值都是`null`!其原因是:查询到了相关的数据,却因为名称不匹配(数据表中的是`department_id`,而`User`类中的是`departmentId`),所以,MyBatis框架就无法正确的将结果封装到`User`类的对象中!
其实,MyBatis框架在执行查询并处理查询结果时,默认情况下,要求**查询结果的列名(Column)与封装结果的类的属性名(Property)必须一致**!
所以,在查询时,可以自定义别名,使得列名与属性名保持一致!例如:
<select id="findAll"
resultType="cn.tedu.mybatis.User">
SELECT
id, username,
password, age,
phone, email,
department_id AS departmentId
FROM
t_user
ORDER BY
id
</select>
### 7. 使用resultMap解决名称不匹配的问题
在配置SQL语句的XML文件中,可以配置`<resultMap>`节点,该节点的配置就是用于指导MyBatis如何将查询结果封装到返回结果中!
<!-- id属性:自定义名称 -->
<!-- type属性:封装查询结果的数据类型的全名 -->
<resultMap type="cn.tedu.mybatis.User"
id="UserMap">
<!-- 将哪一列的结果封装到哪个属性中去 -->
<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="department_id" property="departmentId" />
</resultMap>
在配置`<select>`节点时,就应该使用`resultMap`属性指定以上`<resultMap>`节点的`id`:
<select id="findById" resultMap="UserMap">
SELECT * FROM t_user WHERE id=#{id}
</select>
### 8. 关联表查询
需求:根据id查询某个用户信息,在查询结果中,要求体现该用户所在的部门的名称!
需要执行的SQL语句大致是:
select
t_user.id, username,
password, age,
phone, email,
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=6;
在设计抽象方法时,没有某个实体类能够封装查询结果!则需要创建新的VO(Value Object)类来封装查询结果:
public class UserVO {
private Integer id;
private String username;
private String password;
private Integer age;
private String phone;
private String email;
private Integer departmentId;
private String departmentName;
// SET/GET/toString
}
> 实体类是与数据表相对应的,而VO类是与查询结果相对应的!从编写代码的角度来看,VO类与实体类是高度相似的,只是定位不同!
创建好VO类后,就可以设计抽象方法:
UserVO findVOById(Integer id);
然后,配置以上抽象方法的映射:
<select id="findVOById" resultType="cn.tedu.mybatis.UserVO">
select
t_user.id, username,
password, age,
phone, email,
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>
### 1.
需求:根据id查询部门信息,并且,同时查出该部门的员工信息。
分析:需要执行的SQL语句大致是:
SELECT
*
FROM
t_department
LEFT JOIN
t_user
ON
t_department.id=t_user.department_id
WHERE
t_department.id=?
在开发功能时,必须先创建VO类,用于封装此次的查询结果:
public class DepartmentVO {
private Integer id;
private String name;
private List<User> users;
}
然后在`DepartmentMapper`接口中添加抽象方法:
DepartmentVO findVOById(Integer id);
接下来,在**DepartmentMapper.xml**中配置以上抽象方法的映射:
<select id="findVOById"
resultType="cn.tedu.mybatis.DepartmentVO">
SELECT
t_department.id AS did, name,
t_user.*
FROM
t_department
LEFT JOIN
t_user
ON
t_department.id=t_user.department_id
WHERE
t_department.id=#{id}
</select>
目前,MyBatis框架并不知道如何将找到的若干条结果正确的封装到返回的`DepartmentVO`对象中!所以,需要配置`<resultMap>`!配置代码如下:
<resultMap id="DepartmentVOMap"
type="cn.tedu.mybatis.DepartmentVO">
<id column="did" property="id" />
<result column="name" property="name" />
<!-- collection节点:用于配置1对多的查询结果 -->
<!-- property属性:类中的哪个属性用于封装多个结果中的数据 -->
<!-- ofType属性:集合属性中的元素的类型,即:这个List集合中放的是什么类型的数据 -->
<collection property="users"
ofType="cn.tedu.mybatis.User">
<id 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="department_id" property="departmentId" />
</collection>
</resultMap>
**注意:此次涉及多表查询,即使名称对应(查询结果的列名与期望封装到的对象的属性名是相同的),也必须显式的配置!**
则对应的查询节点就应该使用`resultMap`属性来配置,并且,在查询时,为了避免出现2个`id`列,会导致无法正确的封装查询结果,所以,需要为至少其中1列定义别名:
<select id="findVOById" resultMap="DepartmentVOMap">
SELECT
t_department.id AS did, name,
t_user.*
FROM
t_department
LEFT JOIN
t_user
ON
t_department.id=t_user.department_id
WHERE
t_department.id=#{id}
</select>