MyBatis入门-动态SQL

MyBatis动态SQL

本篇内容:

  • 五个标签介绍:if,choose(when、otherwise),trim(where、set)、foreach、bind
  • OGNL(Object-Graph Navigation Language)表达式介绍

介绍-if标签

if的主要用处:

  1. WHERE语句中,通过判断参数值来决定是否使用某个查询条件
  2. UPDATE/INSERT语句中通过判断是否更新/插入一个字段

实践-WHERE中用if

要求:当只输入用户名时,需要根据用户名进行模糊查询;当只输入邮箱时,根据邮箱进行完全匹配;当同时输入用户名和邮箱时,用前面的两个条件去查询。

mapper接口

    /**
     * 查询用户列表
     * @param user 条件
     * @return 用户列表
     */
    List<SysUser> selectByUser(SysUser user);

xml定义,使用if标签来根据条件确定查询sql

<select id="selectByUser" resultType="tk.mybatis.simple.model.SysUser">
        SELECT
            id,
            user_name,
            user_password,
            user_email,
            user_info,
            head_img,
            create_time
        FROM
            sys_user
        <where>
            <if test="userName !=null and userName!=''">
                and user_name like concat('%',#{userName},'%')
            </if>
            <if test="userEmail !=null and userEmail!=''">
                and user_email = #{userEmail}
            </if>
        </where>
    </select>

测试方法:

public void selectByUser() {
        SqlSession sqlSession = getSqlSession();
        try {
            SysUserMapper userMapper = sqlSession.getMapper(SysUserMapper.class);
            //使用用户名加邮箱
            SysUser sysUser1 = new SysUser();
            sysUser1.setUserEmail("admin");
            sysUser1.setUserName("a");
            //只用用户名
            List<SysUser> sysUsers = userMapper.selectByUser(sysUser1);
            LOGGER.info("用户信息:{}",sysUsers.toString());
            SysUser sysUser2 = new SysUser();
            sysUser2.setUserName("a");
            List<SysUser> sysUsers2 = userMapper.selectByUser(sysUser2);
            LOGGER.info("用户信息2:{}",sysUsers2.toString());
            //只用邮箱
            SysUser sysUser3 = new SysUser();
            sysUser3.setUserEmail("admin");
            // 调用查询方法
            List<SysUser> sysUsers3 = userMapper.selectByUser(sysUser3);
            LOGGER.info("用户信息3:{}",sysUsers3.toString());
        } finally {
            sqlSession.close();
        }
    }

测试结果

==>  Preparing: SELECT id, user_name, user_password, user_email, user_info, head_img, create_time FROM sys_user WHERE user_name like concat('%',?,'%') and user_email = ? 
==> Parameters: a(String), admin(String)
<==      Total: 0
用户信息:[]
==>  Preparing: SELECT id, user_name, user_password, user_email, user_info, head_img, create_time FROM sys_user WHERE user_name like concat('%',?,'%') 
==> Parameters: a(String)
<==    Columns: id, user_name, user_password, user_email, user_info, head_img, create_time
<==        Row: 1, admin, 123456, admin@mybatis.tk, <<BLOB>>, <<BLOB>>, 2021-01-28 21:57:32.0
<==      Total: 1
用户信息2[SysUser{id=1, userName='admin', userPassword='123456', userEmail='admin@mybatis.tk', userInfo='管理员', headImg=null, createTime=Thu Jan 28 21:57:32 CST 2021}]
==>  Preparing: SELECT id, user_name, user_password, user_email, user_info, head_img, create_time FROM sys_user WHERE user_email = ? 
==> Parameters: admin(String)
<==      Total: 0
用户信息3[]

总结:

if的test属性是一个符合OGNL要求的判断表达式,表达式的结果为boolean或者非0表示true,0为false。

  • 判断条件property!=null或property==null:适用于任何类型的字段,用于判断属性值是否为空
  • 判断条件property!=’ ‘或property==’ ':仅适用于String类型的字段,用于判断是否为空字符串
  • and和or:多个判断条件使用and或or连接,嵌套的判断使用小括号分组

实践-UPDATE中用if

要求:只更新有变化的字段,更新时不能将原值未变的字段更新为空。

mapper接口

    /**
     * 根据主键更新
     * @param user 条件
     * @return 影响的行数
     */
    int updateByIdSelective(SysUser user);

xml,注意我们最后一个if标签的sql结尾带了逗号,这样的写法在set标签中也是可以执行的

<update id="updateByIdSelective">
        update mybatis.sys_user
        <set>
            <if test="userName != null and userName != ''">
                user_name = #{userName},
            </if>
            <if test="userPassword != null and userPassword != ''">
                user_password = #{userPassword},
            </if>
            <if test="userEmail != null and userEmail != ''">
                user_email = #{userEmail},
            </if>
            <if test="userInfo != null and userInfo != ''">
                user_info = #{userInfo},
            </if>
            <if test="headImg != null">
                head_img = #{headImg,jdbcType=BLOB},
            </if>
            <if test="createTime != null">
                create_time = #{createTime,jdbcType=TIMESTAMP},
            </if>
        </set>
        where id = #{id}
    </update>

测试方法

public void updateByIdSelective() {
        SqlSession sqlSession = getSqlSession();

        try {
            SysUserMapper mapper = sqlSession.getMapper(SysUserMapper.class);
            //获取数据库中一条记录进行修改
            SysUser user = new SysUser();
            user.setId(1L);
            // 修改用户名
            user.setUserName("admin_test");
            user.setUserEmail("test@mybatis.tk");

            //更新数据,update方法返回值是执行sql影响的行数
            int result = mapper.updateByIdSelective(user);
            Assert.assertEquals(1,result);
            //查看修改后的数据
            SysUser sysUser = mapper.queryById(1L);
            LOGGER.info("修改后的用户信息:{}",sysUser.toString());
            Assert.assertEquals("admin_test",sysUser.getUserName());
        } finally {
            //为了不影响其他测试,这里进行回滚
            //由于默认的sqlSessionFactory.openSession()是不自动提交的
            //因此不手动执行commit也不会提交到数据库
            sqlSession.rollback();
            //关闭sqlSession
            sqlSession.close();
        }
    }

测试结果

==>  Preparing: update mybatis.sys_user SET user_name = ?, user_email = ? where id = ? 
==> Parameters: admin_test(String), test@mybatis.tk(String), 1(Long)
<==    Updates: 1
==>  Preparing: select id, user_name, user_password, user_email, user_info, head_img, create_time from mybatis.sys_user where id = ? 
==> Parameters: 1(Long)
<==    Columns: id, user_name, user_password, user_email, user_info, head_img, create_time
<==        Row: 1, admin_test, 123456, test@mybatis.tk, <<BLOB>>, <<BLOB>>, 2021-01-28 21:57:32.0
<==      Total: 1
修改后的用户信息:SysUser{id=1, userName='admin_test', userPassword='123456', userEmail='test@mybatis.tk', userInfo='管理员', headImg=null, createTime=Thu Jan 28 21:57:32 CST 2021}

实践-INSERT中用if

要求:实现动态插入列功能。

修改用户表邮箱字段:

alter table sys_user
modify column user_email varchar(50) null  default 'test@mybatis.tk' comment  '邮箱'
after user_password;

mapper接口

    /**
     * 插入一条记录
     * @param user 条件
     * @return 影响的行数
     */
    int insert4(SysUser user);

xml内容

<insert id="insert4" useGeneratedKeys="true" keyProperty="id">
        insert into `sys_user`(
        user_name,
        user_password,
        <if test="userEmail !=null and userEmail != ''">
            user_email,
        </if>
        user_info,
        head_img,
        create_time
        )
        values (
        #{userName},
        #{userPassword},
        <if test="userEmail !=null and userEmail != ''">
            #{userEmail},
        </if>
        #{userInfo},
        #{headImg,jdbcType=BLOB},
        #{createTime,jdbcType=TIMESTAMP}
        )
    </insert>

测试方法

public void insert4() {
        SqlSession sqlSession = getSqlSession();

        try {
            // 初始化一个sysUser对象
            SysUser sysUser = new SysUser();
            sysUser.setUserName("test1");
            sysUser.setUserPassword("123456");
            sysUser.setUserInfo("test info");
            sysUser.setHeadImg(new byte[]{1,2,3});
            sysUser.setCreateTime(new Date());

            SysUserMapper mapper = sqlSession.getMapper(SysUserMapper.class);
            //将新建的对象插入数据库中,这里的返回值是执行的SQL影响的行数
            int insert = mapper.insert4(sysUser);
            //只插入一条记录
            Assert.assertEquals(1,insert);
            LOGGER.info("插入的记录:{}",sysUser.toString());
            SysUser sysUser1 = mapper.queryById(sysUser.getId());
            LOGGER.info("插入数据库的内容:{}",sysUser1.toString());
        } finally {
            //由于默认的sqlSessionFactory.openSession()是不自动提交的,因此不手动执行commit也不会提交到数据库
            sqlSession.rollback();
//            sqlSession.commit();
            //关闭sqlSession
            sqlSession.close();
        }
    }

测试结果:

==>  Preparing: insert into `sys_user`( user_name, user_password, user_info, head_img, create_time ) values ( ?, ?, ?, ?, ? ) 
==> Parameters: test1(String), 123456(String), test info(String), java.io.ByteArrayInputStream@64c63c79(ByteArrayInputStream), 2021-03-18 16:42:34.822(Timestamp)
<==    Updates: 1
插入的记录:SysUser{id=1003, userName='test1', userPassword='123456', userEmail='null', userInfo='test info', headImg=[1, 2, 3], createTime=Thu Mar 18 16:42:34 CST 2021}
==>  Preparing: select id, user_name, user_password, user_email, user_info, head_img, create_time from mybatis.sys_user where id = ? 
==> Parameters: 1003(Long)
<==    Columns: id, user_name, user_password, user_email, user_info, head_img, create_time
<==        Row: 1003, test1, 123456, test@mybatis.tk, <<BLOB>>, <<BLOB>>, 2021-03-18 16:42:35.0
<==      Total: 1
插入数据库的内容:SysUser{id=1003, userName='test1', userPassword='123456', userEmail='test@mybatis.tk', userInfo='test info', headImg=[1, 2, 3], createTime=Thu Mar 18 16:42:35 CST 2021}

介绍-choose标签

if标签无法实现if…else、if…else的逻辑。choose when otherwise可以。

choose元素中包含when和otherwise两个标签,一个choose中至少有一个when,至多有1个otherwise。

实践

要求:默认用户名不能重复。查询用户,当参数id有值使用id查询;当id无值,判断用户名是否有值,有值就用用户名查询,如果用户名也无值,就使sql查询无结果。

mapper接口

    /**
     * 根据用户id或用户名查询
     * @param user 条件
     * @return 用户数据
     */
    SysUser selectByIdOrUserName(SysUser user);

xml

<select id="selectByIdOrUserName" resultType="tk.mybatis.simple.model.SysUser">
        SELECT
        id,
        user_name,
        user_password,
        user_email,
        user_info,
        head_img,
        create_time
        FROM
        sys_user
        <where>
            <choose>
                <when test="id != null">
                    and id =#{id}
                </when>
                <when test="userName !=null and userName !=''">
                    and user_name=#{userName}
                </when>
                <otherwise>
                    and 1=2
                </otherwise>
            </choose>
        </where>
    </select>

测试方法

public void selectByIdOrUserName() {
        SqlSession sqlSession = getSqlSession();
        try {
            SysUserMapper userMapper = sqlSession.getMapper(SysUserMapper.class);
            //使用id
            SysUser userCondition = new SysUser();
            userCondition.setId(1L);
            //只用用户名
            SysUser sysUser = userMapper.selectByIdOrUserName(userCondition);
            LOGGER.info("用户信息:{}",sysUser.toString());
            //使用用户名
            SysUser userCondition2 = new SysUser();
            userCondition2.setUserName("admin");
            //只用用户名
            SysUser sysUser2 = userMapper.selectByIdOrUserName(userCondition2);
            LOGGER.info("用户信息:{}",sysUser2.toString());
            //使用用户名
            SysUser userCondition3 = new SysUser();
            userCondition3.setUserEmail("admin");
            //只用用户名
            SysUser sysUser3 = userMapper.selectByIdOrUserName(userCondition3);
            LOGGER.info("用户信息:{}",Optional.ofNullable(sysUser3).map(SysUser::toString).orElse("null"));
        } finally {
            sqlSession.close();
        }
    }

测试结果

==>  Preparing: SELECT id, user_name, user_password, user_email, user_info, head_img, create_time FROM sys_user WHERE id =? 
==> Parameters: 1(Long)
<==    Columns: id, user_name, user_password, user_email, user_info, head_img, create_time
<==        Row: 1, admin, 123456, admin@mybatis.tk, <<BLOB>>, <<BLOB>>, 2021-01-28 21:57:32.0
<==      Total: 1
用户信息:SysUser{id=1, userName='admin', userPassword='123456', userEmail='admin@mybatis.tk', userInfo='管理员', headImg=null, createTime=Thu Jan 28 21:57:32 CST 2021}
==>  Preparing: SELECT id, user_name, user_password, user_email, user_info, head_img, create_time FROM sys_user WHERE user_name=? 
==> Parameters: admin(String)
<==    Columns: id, user_name, user_password, user_email, user_info, head_img, create_time
<==        Row: 1, admin, 123456, admin@mybatis.tk, <<BLOB>>, <<BLOB>>, 2021-01-28 21:57:32.0
<==      Total: 1
用户信息:SysUser{id=1, userName='admin', userPassword='123456', userEmail='admin@mybatis.tk', userInfo='管理员', headImg=null, createTime=Thu Jan 28 21:57:32 CST 2021}
==>  Preparing: SELECT id, user_name, user_password, user_email, user_info, head_img, create_time FROM sys_user WHERE 1=2 
==> Parameters: 
<==      Total: 0
用户信息:null

总结:如果没有otherwise这个限制条件,所有的用户都会被查询出来;而接口返回值类型为SysUser,当查出多条记录时,会报错。

otherwise可搭配update使用,避免更新时无条件全部更新。

介绍-where标签

where和set属于trim的一种具体用法。

where标签的作用:

  • 如果标签中有返回值,就插入一个where
  • 如果where后面的字符串是以and和or(不区分大小写)开头的,就将他们剔除

介绍-set标签

set标签的作用:

  • 如果该标签的元素中有返回值,就插入一个set
  • 如果set后面的字符串是以逗号结尾,就将逗号剔除

总结:

在set标签中,最后的逗号问题解决了;当if标签内容都为空时,为了避免出错,需要加一条更新语句:id=#{id}有必要。

例如:

 <update id="updateByIdSelective">
        update mybatis.sys_user
        <set>
            <if test="createTime != null">
                create_time = #{createTime,jdbcType=TIMESTAMP},
            </if>
            id = #{id},
        </set>
        where id = #{id}
    </update>

介绍-trim用法

where和set标签得功能都可以用trim标签来实现。

where标签对应trim的实现如下:

<trim prefix='WHERE' prefixOverrides="AND |OR ">
    ...
</trim>

这里AND和OR后面的空格是为了避免匹配到andes、orders等单词。

set标签对应的trim实现如下:

<trim prefix='SET' suffixOverrides=",">
    ...
</trim>

trim标签有如下属性:

  • prefix:当trim元素内包含内容时,会给内容增加prefix指定的前缀
  • prefixOverrides:当trim元素内包含内容时,会把内容中匹配的前缀字符串去掉
  • suffix:当trim元素内包含内容时,会给内容增加suffix指定的后缀
  • suffixOverrides:当trim元素内包含内容时,会把内容中匹配的后缀字符串去掉

介绍-foreach用法

使用IN关键字,可以使用${ids}方式直接获取值,但这种写法不能防止SQL注入,想要避免SQL注入就得用#{}方式,这时就需要配合使用foreach标签。

foreach可以对数组、Map或实现了Iterable接口(如List、Set)得对象进行遍历。数组在处理时会转化为List对象。

foreach遍历的对象可以分为两大类:Iterable类型和Map类型。这两种类型在遍历循环时情况不一样。

foreach包含以下属性:

  • collection:被循环的属性名
  • item:变量名,从循环对象中取出的每一个值
  • index:索引的属性名,在集合数组情况下值为当前索引值,当迭代循环的对象是map类型时,这个值为map的key
  • open:整个循环内容开头的字符串
  • close:整个循环内容结尾的字符串
  • separator:每次循环的分隔符

MyBatis如何处理参数:

查询-只有一个数组参数或集合参数

下面的是默认情况下的处理逻辑:

当参数类型为集合的时候,默认会转化为Map类型,并添加一个key为collection的值(MyBatis3.3.0),如果参数类型是List集合,那么就继续添加一个key为list的值(MyBatis3.2.8及以下版本),这样,当collection=“list”时,就能得到这个集合,并对它进行循环操作。

当参数类型为数组时,也会转化为Map类型,默认的key为array。

list和array是参数默认的名字,使用@Param来指定参数的名字,这时collection就设置为通过@Param指定的名字。

private Object wrapCollection(final Object object){
    if(object instanceof Collection){
        StrictMap<Object> map=new StrictMap<Object>();
        map.put("collection",object);
        if(object instanceof List){
            map.put("list",object);
        }
        return map;
    }else if(object !=null && object.getClass().isArray()){
        StrictMap<Object> map=new StrictMap<Object>();
        map.put("array",object);
        return map;
    }
    return object;
}

查询-有多个参数

当有多个参数时,需要使用@Param注解给每一个参数指定一个名字。

查询-参数是Map类型

使用Map和使用@Param注解方式类似,将collection指定为对应Map中的key即可。如果要循环所传入的Map,推荐使用@Param注解指定名字,此时可以将collection设置为指定的名字,如果不想指定名字,就使用默认值_parameter

查询-参数是一个对象

这种情况下指定为对象的属性名。当使用对象内多层嵌套的对象时,使用属性.属性(集合和数组可以使用下标取值)的方式指定深层的属性值。

实践-foreach实现in集合

要求:根据用户id的集合来查询用户,如果传入空集合则返回的结果也要为空。

mapper

    /**
     * 通过id查询用户列表
     * @param list id集合
     * @return 用户列表
     */
    List<SysUser> selectByIdList(List<Long> list);

xml

<select id="selectByIdList" resultType="tk.mybatis.simple.model.SysUser">
        select id,
            user_name,
            user_password,
            user_email,
            user_info,
            head_img,
            create_time
        from `sys_user`
        <where>
            <choose>
                <when test="list !=null and list.size()>0">
                    id in
                    <foreach collection="list" item="id" index="id" open="(" separator="," close=")">
                        #{id,jdbcType=BIGINT}
                    </foreach>
                </when>
                <otherwise>
                    1=2
                </otherwise>
            </choose>
        </where>
    </select>

测试方法:

public void selectByIdList() {
        SqlSession sqlSession = getSqlSession();
        try {
            SysUserMapper mapper = sqlSession.getMapper(SysUserMapper.class);
            List<Long> idList = new ArrayList<>();
            idList.add(1L);
            idList.add(2L);
            List<SysUser> sysUserList = mapper.selectByIdList(idList);
            LOGGER.info("用户信息:{}",sysUserList.toString());
            // 查询一个空集合
            List<Long> idList2 = new ArrayList<>();
            List<SysUser> sysUserList2 = mapper.selectByIdList(idList2);
            LOGGER.info("用户信息:{}",sysUserList2.toString());
        } finally {
            sqlSession.close();
        }
    }

测试结果,符合我们的要求

==>  Preparing: select id, user_name, user_password, user_email, user_info, head_img, create_time from `sys_user` WHERE id in ( ? , ? ) 
==> Parameters: 1(Long), 2(Long)
<==    Columns: id, user_name, user_password, user_email, user_info, head_img, create_time
<==        Row: 1, admin, 123456, admin@mybatis.tk, <<BLOB>>, <<BLOB>>, 2021-01-28 21:57:32.0
<==      Total: 1
用户信息:[SysUser{id=1, userName='admin', userPassword='123456', userEmail='admin@mybatis.tk', userInfo='管理员', headImg=null, createTime=Thu Jan 28 21:57:32 CST 2021}]
==>  Preparing: select id, user_name, user_password, user_email, user_info, head_img, create_time from `sys_user` WHERE 1=2 
==> Parameters: 
<==      Total: 0
用户信息:[]

实践-foreach实现批量插入

数据库支持批量插入,可以使用foreach来实现。批量插入语法如下:

INSERT INTO tablename(column-a,[column-b,...])
VALUES('value-1a',[value-1b’,...]),
('value-2a',[value-2b’,...]),
...

mapper接口

    /**
     * 插入用户记录
     * @param userList 用户集合
     * @return 影响的行数
     */
    int insertList(List<SysUser> userList);

xml,注意foreach标签这里只用了separator属性,因为批量插入的sql无需在values后再额外跟括号

 <insert id="insertList">
        insert into mybatis.sys_user(id,user_name, user_password, user_email, user_info, head_img, create_time)
        values
        <foreach collection="list" item="user" separator=",">
            (
            #{user.id},
            #{user.userName},
            #{user.userPassword},
            #{user.userEmail},
            #{user.userInfo},
            #{user.headImg},
            #{user.createTime}
            )
        </foreach>
        <selectKey keyColumn="id" keyProperty="id" resultType="long" order="AFTER">
            select LAST_INSERT_ID()
        </selectKey>
    </insert>

测试类

public void insertList() {
        SqlSession sqlSession = getSqlSession();
        try {
            SysUserMapper mapper = sqlSession.getMapper(SysUserMapper.class);
            //创建一个user对象
            List<SysUser> sysUsers = new ArrayList<>();
            for (int i = 0; i < 2; i++) {
                SysUser sysUser = new SysUser();
                sysUser.setUserName("test"+i);
                sysUser.setUserPassword("123456");
                sysUser.setUserEmail("test@mybatis.tk");
                sysUsers.add(sysUser);
            }
            //将新建的对象批量插入数据库
            //返回值时执行sql影响的行数
            int i = mapper.insertList(sysUsers);
            LOGGER.info("本次影响的行数:{}", i);
            LOGGER.info("插入的记录:{}",sysUsers.toString());
        } finally {
            //回滚
            sqlSession.rollback();
            //关闭sqlSession
            sqlSession.close();
        }
    }

测试结果,发现通过select LAST_INSERT_ID()方法能拿到最新记录的主键,但是无法赋值给集合里的对象。

==>  Preparing: insert into mybatis.sys_user(id,user_name, user_password, user_email, user_info, head_img, create_time) values ( ?, ?, ?, ?, ?, ?, ? ) , ( ?, ?, ?, ?, ?, ?, ? ) 
==> Parameters: null, test0(String), 123456(String), test@mybatis.tk(String), null, null, null, null, test1(String), 123456(String), test@mybatis.tk(String), null, null, null
<==    Updates: 2
==>  Preparing: select LAST_INSERT_ID() 
==> Parameters: 
<==    Columns: LAST_INSERT_ID()
<==        Row: 1025
<==      Total: 1
本次影响的行数:2
插入的记录:[SysUser{id=null, userName='test0', userPassword='123456', userEmail='test@mybatis.tk', userInfo='null', headImg=null, createTime=null}, SysUser{id=null, userName='test1', userPassword='123456', userEmail='test@mybatis.tk', userInfo='null', headImg=null, createTime=null}]

如果需要实现批量插入返回主键给插入记录,需要修改xml为如下,其中只用了useGeneratedKeys和keyProperty属性,含义及取值与单行插入返回主键一致;但是需要升级MyBatis版本为至少3.3.1,否则报错### Cause: org.apache.ibatis.executor.ExecutorException: Error getting generated key or setting result to parameter object. Cause: org.apache.ibatis.binding.BindingException: Parameter 'id' not found. Available parameters are [collection, list]

<insert id="insertList" useGeneratedKeys="true" keyProperty="id">
        insert into mybatis.sys_user(id,user_name, user_password, user_email, user_info, head_img, create_time)
        values
        <foreach collection="list" item="user" separator=",">
            (
            #{user.id},
            #{user.userName},
            #{user.userPassword},
            #{user.userEmail},
            #{user.userInfo},
            #{user.headImg},
            #{user.createTime}
            )
        </foreach>
    </insert>

将pom中mybatis版本升级为3.3.1后,并修改xml后,继续执行测试方法,测试结果如下:发现我们已经能够在插入之后返回主键给插入集合的元素了。

==>  Preparing: insert into mybatis.sys_user(id,user_name, user_password, user_email, user_info, head_img, create_time) values ( ?, ?, ?, ?, ?, ?, ? ) , ( ?, ?, ?, ?, ?, ?, ? ) 
==> Parameters: null, test0(String), 123456(String), test@mybatis.tk(String), null, null, null, null, test1(String), 123456(String), test@mybatis.tk(String), null, null, null
<==    Updates: 2
本次影响的行数:2
插入的记录:[SysUser{id=1037, userName='test0', userPassword='123456', userEmail='test@mybatis.tk', userInfo='null', headImg=null, createTime=null}, SysUser{id=1038, userName='test1', userPassword='123456', userEmail='test@mybatis.tk', userInfo='null', headImg=null, createTime=null}]

实践-foreach实现动态UPDATE

当参数为Map类型时,foreach如何实现动态UPDATE。

当参数是Map类型时,foreach标签的index属性值对应的不是索引值,而是Map中的key,利用这个key可以实现动态UPDATE。

mapper接口

    /**
     * 通过map更新列
     * @param map 以表字段为key,值为value
     * @return 影响的行数
     */
    int updateByMap(Map<String,Object> map);

xml,没有使用@Param注解指定参数名,mybatis使用了默认的_parameter作为该参数的可以。

<update id="updateByMap">
        update sys_user
        set
        <foreach collection="_parameter" item="val" index="key"
                 separator=",">
            ${key}=#{val}
        </foreach>
        where id = #{id}
    </update>

测试方法:

public void updateByMap() {
        SqlSession sqlSession = getSqlSession();
        try {
            SysUserMapper mapper = sqlSession.getMapper(SysUserMapper.class);
            SysUser sysUser1 = mapper.queryById(1L);
            LOGGER.info("更新前的用户数据{}",sysUser1.toString());
            Map<String, Object> map = new HashMap<>();
            // 查询条件,同样也是更新字段,必须保证该值存在
            map.put("id",1L);
            // 要更新的字段
            map.put("user_email","test@mybatis.tk");
            map.put("user_password","123456789");
            // 更新数据
            mapper.updateByMap(map);
            // 根据当前id查询修改后的数据
            SysUser sysUser = mapper.queryById(1L);
            LOGGER.info("更新后的用户数据{}",sysUser.toString());
        } finally {
            sqlSession.rollback();
            sqlSession.close();
        }
    }

执行结果:能发现我们update语句只更新了三个字段,并且也会更新我们的主键字段。这就说明我们在map中设置主键值时,key必须为表主键列字段名。

==>  Preparing: select id, user_name, user_password, user_email, user_info, head_img, create_time from mybatis.sys_user where id = ? 
==> Parameters: 1(Long)
<==    Columns: id, user_name, user_password, user_email, user_info, head_img, create_time
<==        Row: 1, admin, 123456, admin@mybatis.tk, <<BLOB>>, <<BLOB>>, 2021-01-28 21:57:32.0
<==      Total: 1
更新前的用户数据SysUser{id=1, userName='admin', userPassword='123456', userEmail='admin@mybatis.tk', userInfo='管理员', headImg=null, createTime=Thu Jan 28 21:57:32 CST 2021}
==>  Preparing: update sys_user set user_email=? , user_password=? , id=? where id = ? 
==> Parameters: test@mybatis.tk(String), 123456789(String), 1(Long), 1(Long)
<==    Updates: 1
==>  Preparing: select id, user_name, user_password, user_email, user_info, head_img, create_time from mybatis.sys_user where id = ? 
==> Parameters: 1(Long)
<==    Columns: id, user_name, user_password, user_email, user_info, head_img, create_time
<==        Row: 1, admin, 123456789, test@mybatis.tk, <<BLOB>>, <<BLOB>>, 2021-01-28 21:57:32.0
<==      Total: 1
更新后的用户数据SysUser{id=1, userName='admin', userPassword='123456789', userEmail='test@mybatis.tk', userInfo='管理员', headImg=null, createTime=Thu Jan 28 21:57:32 CST 2021}

介绍-bind用法

bind标签常用于构造sql中的模糊查询,在实践-WHERE中用if我们用的是and user_name like concat('%',#{userName},'%'),使用了concat函数连接字符串,在MySQL中,这个函数支持多个参数,但是在Oracle中只支持两个参数。如果更换数据库,上面的concat写法就得重写了。

可以使用bind标签避免上面更换数据库而需要重写sql。将上面的语句修改为

 <if test="userName !=null and userName!=''">
                <bind name="userNameLike" value="'%'+userName+'%'"/>
                and user_name like #{userNameLike}
            </if>

介绍-多数据库支持

bind标签只是解决了模糊查询的问题,别的问题可以使用if标签提供的databaseIdProvider数据库厂商标识配置。

MyBatis会加载不带databaseId属性和带有匹配当前数据库databaseId属性的所有语句。如果同时找到带有databaseId和不带databseId的相同语句,则后者被舍弃。

如需支持多厂商,需要在mybatis-config.xml中添加databaseIdProvider配置。name是数据库产品名,value是我们自定义别名。数据库产品名由JDBC驱动决定,只要找到对应数据库DatabaseMetaData接口实现类,一般在getDatabaseProductName()方法中可以直接找到该值。任何情况下都可以通过调用DatabaseMetaData#getDatabaseProductName()方法获取具体的值。

<databaseIdProvider type="DB_VENDOR">
	<property name="MySQL" value="mysql"
</databaseIdProvider>

除了增加上面的配置外,映射文件中修改if标签配合默认上下文中_databseId参数这种写法可以实现,这样可以避免大量重复的SQL出现。

介绍-OGNL语法

Mybatis中动态SQL和$ {}形式的参数中都用到了OGNL表达式,所以我们有必要了解一下OGNL的简单用法。

Mybatis常用的OGNL表达式如下:

  1. e1 or e2
  2. e1 and e2
  3. e1 == e2 或 e1 eq e2
  4. e1 != e2或e1 neq e2
  5. e1 lt e2 小于
  6. e1 lte e2 小于等于
  7. e1 gt e2 大于
  8. e1 gte e2 大于等于
  9. e1 + e2
  10. e1 * e2
  11. e1/e2
  12. e1 - e2
  13. e1 % e2
  14. !e 或 not e :非,取反
  15. e.method(args):调用对象方法
  16. e.property: 对象属性值
  17. e1 [e2] : 按索引取值(List、数组、Map)
  18. @Class@method(args):调用类的静态方法
  19. @Class@field:调用类的静态字段值

实践-判断集合是否为空

当我们需要判断一个集合是否为空时,可以用如下两种判断方式

<if test="@org.apache.commons.collections.CollectionUtils@isNotEmpty(applyerIds)">
    heu.employee_id IN
    <foreach collection="applyerIds" item="id" index="index"
             open="(" separator="," close=")">
        #{id}
    </foreach>
</if>

<if test="applyerIds !=null and applyerIds.size()>0)">
    heu.employee_id IN
    <foreach collection="applyerIds" item="id" index="index"
             open="(" separator="," close=")">
        #{id}
    </foreach>
</if>

在上面的这种用法中,applyerIds.size()是调用对象的方法,>0是和数字进行比较。

实践-获取对象的属性

e.property及e1 [e2]也经常用到,而且都可以多层嵌套使用。

假设User类的对象user有一个Address类型的属性addr,Address中还有一个属性zipcode,在mybatis中可以通过user.addr.zipcode直接使用zipcode的值。

假设Map类型的属性未map,我们可以通过map[‘userName’]或map.userName来获取map中key为userName的值,一定要注意,不管userName的值是不是null,必须保证userName这个key存在,否则会报错。

@Class@method(args)通常用于简化一些校验,或者进行更特殊的校验。

实践-灵活使用OGNL表达式,获取用户信息

下面三行是h0系统中获取当前登录员工id的操作。1和2行是用来获取登录用户的用户id和tenantId,第三行是用来获取当前登录员工id的,需要注意的是,在bind标签中使用bind标签的值无需加#{}符。当在where条件中使用时会需要加#{}符号

<bind name="currentUserId" value="@io.choerodon.core.oauth.DetailsHelper@getUserDetails().userId"/>
<bind name="currentTenantId" value="@io.choerodon.core.oauth.DetailsHelper@getUserDetails().tenantId"/>
<bind name="currentEmployeeId" value="@org.hzero.boot.platform.plugin.hr.EmployeeHelper@getEmployee(currentUserId,currentTenantId).employeeId"/>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值