1. 动态SQL–foreach
动态SQL:在配置SQL映射时,可以添加某些特殊的标签,使得最终执行时,根据参数不同,每次使用/执行的SQL语句不同!
在配置SQL映射时,可以使用节点实现对参数的遍历,以实现动态SQL效果!
假设需要实现:删除多个id对应的数据。
在UserMapper接口中声明抽象方法:
Integer deleteByIds(Integer[] ids);
在设计以上参数时,可以设计为数组类型,也可以设计为List集合类型,还可以设计为可变参数,例如设计为Integer… ids。
在SomeMapper.xml中配置以上抽象方法的映射:
<delete id="deleteByIds">
DELETE FROM t_user WHERE id IN (
<foreach collection="array"
item="id" separator=",">
#{id}
</foreach>
)
</delete>
关于<foreach>节点中的属性的配置:
collection:被遍历的对象,当参数的类型是数组类型时,取值为array,当参数的类型是List类型时,取值为list;
item:遍历过程中,取出的元素的名称,在子级节点中使用#{}占位时,占位符中填写的也应该是此处指定的名称;
separator:生成遍历的内容时使用的分隔符;
open / close:动态生成的SQL语句部分的起始字符串和结束字符串。
完成后,在Tests中编写并执行单元测试:
@Test
public void deleteByIds() {
Integer[] ids = {1,2,3,4,5,6,7,8};
Integer rows = userMapper.deleteByIds(ids);
System.out.println("rows=" + rows);
}
2. 动态SQL–if
在配置SQL映射时,可以使用对参数进行判断,以生成不同的SQL语句部分。
例如:
<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>
注意:在实际开发时,并不会开发以上功能!
3. 配置SQL语句时使用#{}或${}占位符
在配置SQL映射时,可以使用#{}或${}格式的占位符。
使用#{}时,MyBatis在处理时,会先使用问号?表示占位符对应的内容,并将SQL语句进行预编译,编译通过后,再使用值替换掉问号,最终执行;
而使用${}时,MyBatis在处理时,会先将占位符对应的内容拼接到SQL语句中,再尝试编译,最终执行。
在选取占位符时,可以简单的认为:以前使用JDBC技术时,可以在SQL语句中写问号?的部分,都应该使用#{}格式的占位符!
应该优先选取#{}格式的占位符,因为这种占位符在执行时,是预编译的,通常可以不关心参数的数据类型,也不用担心SQL注入的风险!
4. 名称不对应时的查询方案
准备工作
在数据表中添加is_delete字段,表示“是否被标记为删除”:
ALTER TABLE t_user ADD COLUMN is_delete INT;
UPDATE t_user SET is_delete=0;
然后,在User中补充添加新的属性:
private Integer isDelete;
并添加该属性对应的SET/GET方法,在toString()方法中也输出该属性的值!
问题
现有的查询功能无法查询到is_delete字段的值!
分析
当MyBatis处理查询结果时,会将查询到的数据,封装到与查询结果的列名相同的属性名中(本质上是要求与SET方法的名称对应)!
字段(Field)名:设计数据表时取的名称;
列(Column)名:查询结果中第一行显示的名称,默认情况下,查询结果中的列名就是字段名;
属性(Property)名:类中的属性名称。
如果查询结果中的列名(is_delete)与类(User)的属性名(isDelete)不对应时,查询到的数据就无法封装到对应的属性中!
解决方案一
在查询数据的SQL语句中,在字段列表中,为名称不匹配的字段定义别名即可,例如:
SELECT id,username,password,age,phone,email,is_delete AS isDelete FROM t_user;
则查询结果中的列号就是自定义的别名,只要该列名与类中的属性名一致,就可以装查询结果正确的封装!
配置SQL映射:
<select id="findAll" resultType="cn.tedu.mybatis.User">
SELECT
id, username,
password, age,
phone, email,
is_delete AS isDelete
FROM
t_user
</select>
解决方案二
使用<resultMap>配置查询结果的列,与属性的对应关系!该节点的作用就是指导MyBatis将查询结果封装到返回值对象的属性中!
<!-- resultMap节点:指导MyBatis将查询结果封装到返回类型的对象中 -->
<!-- id:自定义名称 -->
<!-- type:查询结果需要封装到哪种类型的数据中 -->
<resultMap id="UserMap"
type="cn.tedu.mybatis.User">
<!-- column:查询结果中的列名 -->
<!-- property:该列的数据封装到哪个属性中 -->
<id column="id" property="id" />
<result column="is_delete" property="isDelete" />
</resultMap>
注意:在配置<resultMap>内部的子级节点时,如果配置的是主键的对应关系,应该使用<id>节点,其它的使用<result>节点;如果名称本来就是对应的,则不需要配置。
当定义了<resultMap>后,并以此作为查询结果的封装方式,则在<select>节点中,就需要配置resultMap属性,其值就是<resultMap>节点的id值!
<select id="findAll" resultMap="UserMap">
SELECT
*
FROM
t_user
</select>
5. 简单的关联查询
准备工作
创建t_department部门信息表,该表中至少包含部门id(id)、部门名称(name)这2个字段。
CREATE TABLE t_department (
id INT AUTO_INCREMENT,
name VARCHAR(20) NOT NULL,
PRIMARY KEY (id)
) DEFAULT CHARSET=utf8mb4;
在项目中,创建名为Department的类,对应以上数据表:
public class Department {
private Integer id;
private String name;
}
在项目中,创建名为DepartmentMapper的接口,在接口中声明:
Integer insert(Department department);
在项目中,复制原有的SomeMapper.xml,粘贴为DepartmentMapper.xml,删除现有的配置,并在这个文件中配置以上抽象方法的映射:
<mapper namespace="cn.tedu.mybatis.DepartmentMapper">
<insert id="insert">
INSERT INTO t_department (
name
) VALUES (
#{name}
)
</insert>
</mapper>
完成后,执行单元测试:
public class DepartmentMapperTests {
ClassPathXmlApplicationContext ac;
DepartmentMapper mapper;
@Before
public void doBefore() {
ac = new ClassPathXmlApplicationContext(
"spring-dao.xml");
mapper = ac.getBean("departmentMapper", DepartmentMapper.class);
}
@Test
public void insert() {
Department department = new Department();
department.setName("软件研发部");
Integer rows = mapper.insert(department);
System.out.println("rows=" + rows);
}
@After
public void doAfter() {
ac.close();
}
}
然后,在用户表中添加department_id字段,表示该用户归属于哪个部门:
ALTER TABLE t_user ADD COLUMN department_id INT;
并且,为现有的用户分配部门:
UPDATE t_user SET department_id=1 WHERE id IN (11,15);
UPDATE t_user SET department_id=2 WHERE id IN (8,10,14);
UPDATE t_user SET department_id=3 WHERE id IN (12,13);
问题-1
当根据id查询某个用户信息时,如何把该用户所归属的部门的名称也查询出来?
分析-1
需要使用关联查询,查询用户数据的同时,找到与用户数据中department_id匹配的部门信息,就可以找到部门名称!需要执行的SQL语句大致是:
select * from t_user left join t_department on t_user.department_id=t_department.id where t_user.id=11;
问题-2
如果执行以上查询,在设计抽象方法时,没有合适的返回值类型!
分析-2
目前项目中的User与Department类都是与数据表的结构相对应的!这种类通常称之为实体(Entity)类!
只要是多表关联查询,查询一定与实体类不相符!为了封装查询结果,需要自定义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 isDelete;
private Integer departmentId;
private String departmentName;
}
其实,VO类的声明方式,包括其中的代码,与实体类几乎是一样的,只是VO类与实体类的定位不同,实体类是与数据表相对应的,而VO类是与查询结果相对应的!
解决方案
在UserMapper接口中添加抽象方法:
UserVO findVOById(Integer id);
在SomeMapper.xml中配置映射:
<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>
6. 关于一对多的查询解决方案
需求
查询某个部门的信息时,查出该部门所有的员工的信息!
分析
需要执行的SQL语句大致是:
select * from t_department left join t_user on t_department.id=t_user.department_id where t_department.id=1;
问题&解决方案
目前依然没有哪种数据类型可以封装以上查询结果,所以,需要再创建VO类:
public class DepartmentVO {
private Integer id;
private String name;
private List<User> users;
}
问题&解决方案
如果要根据id查询某个部门信息,返回的结果应该是1个对象!但是,由于每个部门都可以有多个用户,在执行SQL查询后得到的查询结果却有多条数据!
在设计抽象方法时,可以在DepartmentMapper接口中添加:
DepartmentVO findVOById(Integer id);
执行查询后,需要将多条查询结果封装到1个DeaprtmentVO类型的对象中,MyBatis框架默认并不知道应该如何处理,所以,需要配置节点,以指定如何封装查询结果:
<resultMap id="DepartmentVOMap"
type="cn.tedu.mybatis.DepartmentVO">
<id column="did" property="id" />
<!-- 尽管名称一致,也需要配置 -->
<result column="name" property="name" />
<!-- collection节点:配置1对多的属性 -->
<!-- ofType:集合中的元素的类型 -->
<collection property="users"
ofType="cn.tedu.mybatis.User">
<id column="uid" 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" />
<result column="department_id" property="departmentId" />
</collection>
</resultMap>
然后,在DepartmentMapper.xml中配置:
<select id="findVOById" resultMap="DepartmentVOMap">
SELECT
t_department.id AS did, name,
t_user.id AS uid, username,
password, age,
phone, email,
is_delete,
department_id
FROM
t_department
LEFT JOIN
t_user
ON
t_department.id=t_user.department_id
WHERE
t_department.id=#{id}
</select>
7. 插入数据时获取自动编号的id值
在<insert>节点中配置2个属性:useGeneratedKeys="true"和keyProperty="xx",第1个属性表示需要获取自动生成的键值,第2个属性用于指定获取到的自动编号的值之后,封装到参数对象的哪个属性中!
配置代码例如:
<insert id="insert"
useGeneratedKeys="true"
keyProperty="id">
INSERT INTO t_department (
name
) VALUES (
#{name}
)
</insert>
测试代码例如:
@Test
public void insert() {
Department department = new Department();
department.setName("软件测试部");
System.out.println(department);
Integer rows = mapper.insert(department);
System.out.println("rows=" + rows);
System.out.println(department);
}
输出结果例如:
Department [id=null, name=软件测试部]
rows=1
Department [id=4, name=软件测试部]
8.MyBatis小结
理解MyBatis框架的作用;
认识使用MyBatis框架开发时所需要的依赖;
认识使用MyBatis框架开发时所需要进行的前序配置;
掌握抽象方法的设计原则:增删改返回Integer,查询返回所期望的类型,方法名可以自定义,但是不可以重载,参数列表根据SQL语句中的问号?来设计,如果参数的数量超过1个,则在每个参数之前添加@Param注解;
掌握XML映射的配置;
掌握节点的使用;
理解#{}和${}占位符的区别;
掌握动态SQL中的的使用。