mybatis 02

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中的的使用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值