1. 多参数的处理

MyBatis框架只识别抽象方法中的1个参数,即:抽象方法应该最多只有1个参数!

当某个抽象方法的参数只有1个时,例如:

Integer deleteById(Integer uid);

在配置映射时,占位符中的名称可以是随意的名字,例如:

<delete id="deleteById">
    DELETE FROM t_user WHERE id=#{helloworld}
</delete>

因为参数只有1个,框架会自动的读取对应位置的参数并应用于执行SQL语句,所以,参数的名称根本就不重要!

另外,每个.java源文件在被编译成为.class文件之后,也都会丢失参数名称!

可以将抽象方法的参数设计为1个Map,则可以满足“参数只有1个”的需求,但是,使用时非常不方便,因为方法的调用者并不明确向Map中封装数据时应该使用什么名称的key!

为了解决这个问题,MyBatis中提供了@Param注解,在设计抽象方法时,允许使用多个参数,但是,每个参数之前都应该添加该注解,并在注解中设置参数的名称,后续在执行时,MyBatis会基于这些注解将调用时的参数封装为Map来执行:

【小结】 当需要多个参数时(超过1个参数时),每个参数前都添加@Param注解,并且,注解中的配置的名称、方法的参数名称、XML中占位符中的名称应该都使用相同的名称!

2. 查询数据

与增、删、改相差不大,查询时也应该先设计抽象方法,再配置映射。

在配置抽象方法时,返回值的类型应该根据查询需求来决定,即:调用该方法后希望得到什么的数据。

例如:根据用户的id查询用户详情,则对应的抽象方法应该是:

User findById(Integer id);

在配置映射时,查询所使用的<select>必须配置resultType属性或resultMap属性(二选一):

<select id="findById"
    resultType="cn.tedu.mybatis.User">
    SELECT 
        id, username,
        password, age,
        phone, email
    FROM 
        t_user 
    WHERE 
        id=#{id}
</select>

例如:查询当前表中所有的数据

List<User> findAll();

在配置映射时,使用的resultType值依然是User的类型,因为,其实所有的查询得到的都可以是一系列的数据,所以,查询多条数据时,无需告诉MyBatis结果将是List集合,只需要告诉它List集合内部的元素是哪种类型就可以了:

<select id="findAll"
    resultType="cn.tedu.mybatis.User">
    SELECT 
        id, username,
        password, age,
        phone, email
    FROM 
        t_user 
</select>

例如:获取当前表中的数据的数量

Integer countById();

在配置映射时,需要注意:无论多么简单的返回值类型,都必须配置resultType

<select id="countById"
    resultType="java.lang.Integer">
    SELECT COUNT(id) FROM t_user
</select>

3. 关于别名

在数据表中添加is_delete INT字段,用于表示该数据是否标记为删除

ALTER TABLE t_user ADD COLUMN is_delete INT;

执行以上代码后,数据表中会出现新的is_delete字段,原有的各数据的该字段值均为NULL,再执行:

UPDATE t_user SET is_delete=0;

则可以把所有数据的is_delete都设为0

当数据表的结构发生变化时,实体类cn.tedu.mybatis.User也应该一并调整:

public class User {

    private Integer id;
    private String username;
    private String password;
    private Integer age;
    private String phone;
    private String email;
    private Integer isDelete;

    // SET/GET/toString
}

在增加时,需要注意填写字段名与属性名:

<insert id="addnew">
    INSERT INTO t_user (
        username, password,
        age, phone,
        email, is_delete
    ) VALUES (
        #{username}, #{password},
        #{age}, #{phone},
        #{email}, #{isDelete}
    )
</insert>

对应关系应该是:

在查询操作中,需要添加新的字段,并定义别名:

<select id="findAll"
    resultType="cn.tedu.mybatis.User">
    SELECT 
        id, username,
        password, age,
        phone, email,
        is_delete AS isDelete
    FROM 
        t_user 
</select>

对应关系是:

4. MyBatis中的动态SQL

4.1. 基本概念

在配置映射时,可以添加例如<if><foreach>等节点,可以根据参数的不同,从而生成不同的SQL语句。

4.2. 关于

例如有某个需求:批量删除用户的数据,需要执行的SQL语句例如:

DELETE FROM t_user WHERE id=3 OR id=4 OR id=5;

通常,更推荐使用IN关键字:

DELETE FROM t_user WHERE id IN (3,4,5)

以上SQL语句中,IN右侧的括号中的id的数量是未知的,可能由用户的操作来决定!这就需要使用动态SQL来实现。

在MyBatis的应用中,如果需要实现以上功能,首先,还是先设计抽象方法:

Integer deleteByIds(Integer[] ids);

在以上抽象方法中,参数既可以使用数组,也可以使用List集合来表示。

配置映射例如:

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

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

4.3. 关于

使用<if>标签可以实现SQL语句中的判断,例如当某参数有值或没有值时,如何执行后续的SQL语句:

<select id="findUserList"
    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">
    LIMIT 
        #{offset}, #{count}
    </if>
</select>

以上代码几乎可以实现任何的单表且不包含聚合函数的查询,但是,并不推荐在实际开发中这样使用,会造成效率低下、浪费内存资源等相关问题。

4.4. 关于#{}和${}占位符

在MyBatis中支持#{}${}占位符。

在SQL语句中,可以填写?实现预编译的位置,需要使用#{}占位符,例如:

DELETE FROM t_user WHERE id=?

而不可以写?的位置,其内容也表现为SQL语句中的部分语法,需要使用${}占位符。

使用#{}在执行时是预编译的,而${}在执行时只是单纯的通过字符串拼接形成最终的SQL语句,则可能存在SQL注入的风险!

5. 关于

5.1. 使用需求

在设计查询时,<select>节点必须配置resultTyperesultMap其中的一项!

通常,只有在多表查询时,才需要使用resultMap

5.2. 准备工作

创建新的数据表,表示部门信息表

CREATE TABLE t_department (
    did INT AUTO_INCREMENT,
    name VARCHAR(20) NOT NULL,
    PRIMARY KEY (did)
) DEFAULT CHARSET=UTF8;

然后,应该创建与之对应的实体类cn.tedu.mybatis.Department

public class Department {

    private Integer did;
    private String name;

    // SET/GET/toString

}

由于部门信息表应该与用户数据表存在关联,则需要在用户信息表中添加新的字段,用于表示每个用户所归属的部门:

ALTER TABLE t_user ADD COLUMN did INT;

最后,完善一下测试数据:

INSERT INTO t_department (name) VALUES ('软件研发部'), ('市场部'), ('人力资源部');

以及用户数据表中的数据:

UPDATE t_user SET did=3 WHERE id=4;
UPDATE t_user SET did=1 WHERE id=5;
UPDATE t_user SET did=1 WHERE id=9;

假设存在需求:显示某用户的详情,其中,部门应该显示部门的名称,而不是部门的id值,则SQL语句应该是:

SELECT 
    id, username, password, age, phone, email, name
FROM 
    t_user
LEFT JOIN 
    t_department
ON 
    t_user.did=t_department.did
WHERE 
    t_user.id=5

在使用MyBatis时,首先,应该设计抽象方法:

?? findUserById(Integer id);

可以发现,并没有某个实体类能够存储以上查询结果,在处理这种关联查询时,应该在项目中创建对应的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 String departmentName;

    // SET/GET/toString

}

所以,关于这个查询功能对应的抽象方法应该是:

UserVO findUserById(Integer id);

实体类与VO类从类的结构上来说,是高度相似的,区别在于:实体类是与数据表结构相对应的,VO类是与查询结果和实际使用相对应的。

然后,配置映射:

<select id="findUserById"
    resultType="cn.tedu.mybatis.UserVO">
    SELECT 
        id, username, 
        password, age, 
        phone, email, 
        name AS departmentName
    FROM 
        t_user
    LEFT JOIN 
        t_department
    ON 
        t_user.did=t_department.did
    WHERE 
        t_user.id=#{id}
</select>

假设存在需求:显示某部门的详情,其中,应该包括该部门的所有员工,则SQL语句应该是:

SELECT 
    id, username, password, age, phone, email, 
    name AS departmentName
FROM 
    t_department
LEFT JOIN 
    t_user
ON 
    t_user.did=t_department.did
WHERE 
    t_department.did=1

如果部门id为1的有多个用户,在执行SQL查询后,将得到多条查询结果,但是,需求本身是显示某部门的详情,应该只有1个查询结果!

首先,还是应该先创建对应的VO类cn.tedu.mybatis.DepartmentVO

public class DepartmentVO {

    private Integer did;
    private String name;
    private List<User> users; // 表示部门中的若干员工

    // SET/GET/toString

}

则查询时的抽象方法应该是:

DepartmentVO findDepartmentById(Integer did);

配置映射:

<!-- 当前resultMap用于指导mybatis将多条查询结果封装到同1个对象中 -->
<resultMap id="DepartmentVOMap"
    type="cn.tedu.mybatis.DepartmentVO">
    <!-- id节点:用于配置主键 -->
    <!-- column属性:查询结果中的列名 -->
    <!-- property属性:resultMap中type对应的数据类型中的名称 -->
    <id column="did" property="did" />
    <!-- result节点:用于配置主键以外的其它字段的查询 -->
    <result column="name" property="name" />
    <!-- collection节点:用于配置1对多关系的数据,也就是List类型的属性 -->
    <!-- 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" />
    </collection>
</resultMap>

<select id="findDepartmentById"
    resultMap="DepartmentVOMap">
    SELECT 
        did, id, username, password, age, phone, email, name
    FROM 
        t_department
    LEFT JOIN 
        t_user
    ON 
        t_user.did=t_department.did
    WHERE 
        t_department.did=#{did}
</select>