MyBatis入门及进阶 – Mapper.xml动态sql详解

分享知识, 不咕自己.

前言

在我们实际开发中. 相比于数据的存储, 数据提取操作会更为频繁. 且会随着数据的体量变大和业务需求变更而变得越来越复杂. 很多时候简单的查询操作已经不能满足业务的需求. 所以我们需要sql变得更加"聪明". 本章即将要学习的, 在Mapper.xml(映射文件)中的动态sql相关标签解决了我们这样的需求.

一、 准备

在开始学习之前. 我们需要对之前所创建的测试数据库mybatis_test库中的user表进行修改. 增加两个字段gender(性别)和age(年龄). gender字段为int类型, 我们约定如果gender值为1我们把他视为男生, 如果值为2我们把她视为女生. DDL代码如下:

CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(255) DEFAULT NULL,
  `gender` int(11) DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4;
复制代码

修改完之后我们在user中插入两条数据作为测试数据

微信截图_20210512214933.png

并且我们还需要在user表的映射实体类User.clss中添加gender和age属性并添加get/set方法. 代码如下:

public class User {

    private Integer id;
    private String username;
    private Integer gender;
    private Integer age;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public Integer getGender() {
        return gender;
    }

    public void setGender(Integer gender) {
        this.gender = gender;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}
复制代码

完成准备之后我们就可以开始编码.

二、 动态Sql之if标签

动态sql中的if标签, 常用于我们常说的多条件查询中. 在实际开发中, 某个sql查询中可能会有不确定个数的查询条件. 这时我们就可以使用if标签. 与我们java代码中的if代码同理. 它可以给定一个结果为boolean类型的表达式来判断是否将该条件拼接到查询sql中. 接下来我们会对getListByCondition方法进行改进. 代码如下:

    <!-- 条件查询 -->
    <select id="getListByCondition" resultType="User" parameterType="User">
        select * from user
            <where>
                <if test="id != null"> id = #{id} </if>
                <if test="username != null and username != ''"> and username = #{username} </if>
                <if test="gender != null"> and gender = #{gender} </if>
                <if test="age != null"> and age < #{age} </if>
            </where>
    </select>
复制代码

上面代码的意思是, 如果if标签中有一条或一条以上满足条件, 将会在sql中追加关键字where. if标签条件中的属性有值的情况下才会追加到sql语句中, 因为username使String类型所以必须满足两个条件. 编写完代码之后, 先不着急测试. 我们先思考一个问题. 如果在执行上面sql的时候, 入参实体中只有username有值, 我们的方法执行会发生报错, 最终生成的sql会变成select * from user where and username = #{username}. 显然这样的语法是不正确的.

我们如何规避这样的错误呢. 其实很简单, 我们只需要一点点改动. 代码如下:

<!-- 条件查询 -->
    <select id="getListByCondition" resultType="User" parameterType="User">
        select * from user
            <where>
                1 = 1 
                <if test="id != null"> and id = #{id} </if>
                <if test="username != null and username != ''"> and username = #{username} </if>
                <if test="gender != null"> and gender = #{gender} </if>
                <if test="age != null"> and age < #{age} </if>
            </where>
    </select>
复制代码

我们在where标签中加如了1 = 1. 这样即使if标签中一个没有满足条件也不会有语法错误的情况.

注意: 如果在查询中需要范围查询, 那么不推荐直接使用 >、>=、<、<=这类符号. 它会导致XMl文件的解析错误. 所以我们应该改写成对应的转义符并以分号结尾

原符号 > < >= <= &
转义符 &gt; &lt; &gt;= &lt;= &amp; &apos; &quot;

接下来我们来编写测试类. 假定我们要查找性别为男生并且年龄小于18. 测试类代码为:

@Test
    public void testGetListByCondition() throws IOException {
        // 获取配置文件 转换为输入流
        InputStream resourceAsStream =
                Resources.getResourceAsStream("mybatis-config.xml");
        // 获取 SqlSessionFactory 后面细说
        SqlSessionFactory sqlSessionFactory =
                new SqlSessionFactoryBuilder().build(resourceAsStream);
        // 获取sqlSessin
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 通过JDK动态代理获取接口代理对象
        UserDao userDao = sqlSession.getMapper(UserDao.class);
        // 构建入参对象
        User user = new User();
        user.setGender(1);
        user.setAge(18);
        // 执行方法
        List<User> users = userDao.getListByCondition(user);
        for (User user1 : users) {
            System.out.println("id = " + user1.getId() +
                    ", username = " + user1.getUsername() +
                    ", gender = " + user1.getGender() +
                    ", age = " +user1.getAge());
        }
        // 关闭sqlSession
        sqlSession.close();
    }
复制代码

输出结果:

微信截图_20210512224018.png

其他查询情况作为练习, 大家可以自行模拟.

三、 动态查询之foreach标签

foreach标签的使用场景常见于基于批量查询和批量新增、修改、删除情况中.
foreach可以作为where标签的子标签可以单独使用. foreach标签有多个属性. 属性含义分别为:

  • collection: 方法的入参对象类型. List类型默认为list, 数组类型默认为array.
  • index:在list和数组中,index是元素的序号,在map中,index是元素的key,参数不是必须.
  • open: sql拼接的开始符号, 与close结合使用.
  • close: sql拼接的结束符号, 与open结合使用.
  • separator: 元素之间的分隔符, separator=”,”.

了解了上面概念后我们开始编写代码. 首先在UserDao接口中声明两个方法:

    // 批量查询
    List<User> selectByIds(int[] ids);

    // 批量新增
    int batchInsert(List<User> users);
复制代码

然后再UserMapper.xml文件中编写sql如下:

    <!-- 批量查询 -->
    <select id="selectByIds" resultType="user" parameterType="_int[]">
        select * from user
        <where>
            <foreach collection="array" open="id in (" close=")" separator="," item="id">
                #{id}
            </foreach>
        </where>
    </select>

    <!-- 批量新增 -->
    <insert id="batchInsert" parameterType="list">
        insert into user(id,username,gender,age) values
            <foreach collection="list" separator="," item="user">
                (
                 #{user.id},
                 #{user.username},
                 #{user.gender},
                 #{user.age}
                )
            </foreach>
    </insert>
复制代码

最后开始编写测试类. selectByIds:

    @Test
    public void TestSelectByIds() throws IOException {
        // 获取配置文件 转换为输入流
        InputStream resourceAsStream =
                Resources.getResourceAsStream("mybatis-config.xml");
        // 获取 SqlSessionFactory 后面细说
        SqlSessionFactory sqlSessionFactory =
                new SqlSessionFactoryBuilder().build(resourceAsStream);
        // 获取sqlSessin
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 通过JDK动态代理获取接口代理对象
        UserDao userDao = sqlSession.getMapper(UserDao.class);
        // 构建入参对象
        int[] ids = new int[]{1,2};
        List<User> users = userDao.selectByIds(ids);
        for (User user : users) {
            System.out.println("id = " + user.getId() +
                    ", username = " + user.getUsername() +
                    ", gender = " + user.getGender() +
                    ", age = " +user.getAge());
        }
        // 关闭sqlSession
        sqlSession.close();
    }
复制代码

微信截图_20210512234416.png
测试方法batchInsert:

    @Test
    public void TestBatchInsert() throws IOException {
        // 获取配置文件 转换为输入流
        InputStream resourceAsStream =
                Resources.getResourceAsStream("mybatis-config.xml");
        // 获取 SqlSessionFactory 后面细说
        SqlSessionFactory sqlSessionFactory =
                new SqlSessionFactoryBuilder().build(resourceAsStream);
        // 获取sqlSessin
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 通过JDK动态代理获取接口代理对象
        UserDao userDao = sqlSession.getMapper(UserDao.class);
        // 构建入参对象
        List<User> users = new ArrayList<>();
        User user1 = new User();
        user1.setId(3);
        user1.setUsername("tom");
        user1.setGender(1);
        user1.setAge(20);
        User user2 = new User();
        user2.setId(4);
        user2.setUsername("lisa");
        user2.setGender(2);
        user2.setAge(22);
        users.add(user1);
        users.add(user2);
        int row = userDao.batchInsert(users);
        System.out.println(row);
        // 提交事务
        sqlSession.commit();
        // 关闭sqlSession
        sqlSession.close();
    }
复制代码

微信截图_20210512233828.png

微信截图_20210512234555.png

日志显示受影响行数2并且数据库也插入成功.

受篇幅影响批量修改与批量删除仅给出代码, 大家可以自行测试

    int batchUpdate(List<User> users);

    int deleteByIds(int[] ids);
复制代码
    <!-- 批量修改 -->
    <update id="batchUpdate" parameterType="list">
        <foreach collection="list" item="user" separator=";">
            update user
            <set>
                <if test="user.username != null and user.username != ''">
                    username = #{user.username},
                </if>
                <if test="user.gender != null">
                    gender = #{user.gender},
                </if>
                <if test="user.age != null">
                    age = #{user.age}
                </if>
            </set>
            where id = #{user.id}
        </foreach>
    </update>

    <!-- 批量删除 -->
    <delete id="deleteByIds" parameterType="_int[]">
        delete from user
        <where>
            <foreach collection="array" open="id in (" close=")" separator="," item="id">
                #{id}
            </foreach>
        </where>
    </delete>
复制代码

四、总结

好的设计总是给你了明确的边界又有足够的灵活度, 就像今天学习的三个标签where, if, foreach. 他们既可以单独使用也可以组合使用, 这样保证了我们代码的灵活度, 保证尽量可以满足我们开发需求. 初次学习MyBatis的同学可能会被一些太过灵活的设计弄晕. 我们应该以更高的视角来学习. 从整个大的框架看待这些特性, 而不是钻入某个知识点纠结不已. 提高思考的维度加上勤奋的练习, 驾驭这复杂性, 更好的利用工具.

五、历史链接

如果有概念含糊不清或者错误的情况. 欢迎大家指出. 感谢.

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享