天天看点

MyBatis从入门到精通(四):MyBatis XML方式的基本用法之增删改

MyBatis从入门到精通(四):MyBatis XML方式的基本用法之增删改

2019-07-04 10:04 by 申城异乡人, ... 阅读, ... 评论, 收藏, 编辑

最近在读刘增辉老师所著的《MyBatis从入门到精通》一书,很有收获,于是将自己学习的过程以博客形式输出,如有错误,欢迎指正,如帮助到你,不胜荣幸!

1. insert用法

1.1 简单的insert方法

假如现在我们想新增一个用户,该如何操作呢?

首先,在接口SysUserMapper中添加如下方法。

/**
 * 新增用户
 *
 * @param sysUser
 * @return
 */
int insert(SysUser sysUser);           

然后打开对应的SysUserMapper.xml文件,添加如下语句。

<insert id="insert">
    INSERT INTO sys_user(id, user_name, user_password, user_email, user_info, head_img, create_time)
    VALUES (#{id},#{userName},#{userPassword},#{userEmail},#{userInfo},#{headImg,jdbcType=BLOB},#{createTime,jdbcType=TIMESTAMP})
</insert>           

特别说明:

1)为了防止类型错误,对于一些特殊的数据类型,建议指定具体的jdbcType值。例如headImg指定BLOB类型,createTime指定TIMESTAMP类型。

2)BLOB对应的类型是ByteArrayInputStream,就是二进制数据流。

3)由于数据库区分date、time、datetime类型,但是在Java中一般都使用java.util.Date类型。因此为了保证数据类型的正确,需要手动指定日期类型。date、time、datetime对应的JDBC类型分别为DATE、TIME、TIMESTAMP。

在SysUserMapperTest测试类中添加如下代码,测试下insert()方法。

@Test
public void testInsert() {
    SqlSession sqlSession = getSqlSession();

    try {
        SysUserMapper sysUserMapper = sqlSession.getMapper(SysUserMapper.class);

        SysUser sysUser = new SysUser();
        sysUser.setUserName("test1");
        sysUser.setUserPassword("123456");
        sysUser.setUserEmail("[email protected]");
        sysUser.setUserInfo("test info");
        // 正常情况下应该读入一张图片保存到byte数组中
        sysUser.setHeadImg(new byte[]{1, 2, 3});
        sysUser.setCreateTime(new Date());

        // 这里的返回值result是执行的SQL影响的行数
        int result = sysUserMapper.insert(sysUser);
        // 只插入1条数据
        Assert.assertEquals(1, result);
        // id为null,没有给id赋值,并且没有配置回写id的值
        Assert.assertNull(sysUser.getId());
    } finally {
        // 为了不影响其他测试,这里选择回滚
        // 默认的sqlSessionFactory.openSession()是不自动提交的
        // 因此不手动执行commit也不会提交到数据库
        sqlSession.rollback();
        sqlSession.close();
    }
}           

运行该测试方法,输出日志如下。

DEBUG [main] - ==> Preparing: INSERT INTO sys_user(id, user_name, user_password, user_email, user_info, head_img, create_time) VALUES (?,?,?,?,?,?,?)

DEBUG [main] - ==> Parameters: null, test1(String), 123456(String), [email protected](String), test info(String), java.io.ByteArrayInputStream@544a2ea6(ByteArrayInputStream), 2019-07-02 13:09:07.822(Timestamp)

DEBUG [main] - <== Updates: 1

现在我们修改下createTime指定的jdbcType类型,直观的理解下jdbcType值的作用。

createTime,jdbcType=DATE

再次运行测试方法,日志中createTime字段的值如下。

2019-07-02(Date)

再次修改createTime指定的jdbcType类型为TIME。

createTime,jdbcType=TIME

再次运行测试方法,发现报如下错误:

MyBatis从入门到精通(四):MyBatis XML方式的基本用法之增删改

报错的原因是,数据库中的字段类型为datetime,但是这里只有time部分的值。

通过上面的测试,说明数据库的datetime类型可以存储DATE(时间部分默认为00:00:00)和TIMESTAMP这两种类型的时间,不能存储TIME类型的时间。

1.2 返回主键值(JDBC方式)

在1.1的例子中,新增完数据,我们并没有拿到数据库中自增的id值,但有些场景中,我们需要先拿到数据库中自增的值,然后再处理其余的逻辑,那么如何拿到数据库中的自增的id值呢?

首先,在接口SysUserMapper中添加方法如下。

/**
 * 新增用户-使用useGeneratedKeys方式
 *
 * @param sysUser
 * @return
 */
int insertUseGeneratedKeys(SysUser sysUser);           

然后打开对应的SysUserMapper.xml,添加如下代码。

<insert id="insertUseGeneratedKeys" useGeneratedKeys="true" keyProperty="id">
    INSERT INTO sys_user(user_name, user_password, user_email, user_info, head_img, create_time)
    VALUES (#{userName},#{userPassword},#{userEmail},#{userInfo},#{headImg,jdbcType=BLOB},#{createTime,jdbcType=TIMESTAMP})
</insert>           

useGeneratedKeys设置为ture后,MyBatis会使用JDBC的getGeneratedKeys()方法来取出由数据库内部生成的主键。获取到主键后将其赋值给keyProperty配置的id属性。

在SysUserMapperTest测试类中添加如下代码,测试新增的insertUseGeneratedKeys()方法。

@Test
public void testInsertUseGeneratedKeys() {
    SqlSession sqlSession = getSqlSession();

    try {
        SysUserMapper sysUserMapper = sqlSession.getMapper(SysUserMapper.class);

        SysUser sysUser = new SysUser();
        sysUser.setUserName("test1");
        sysUser.setUserPassword("123456");
        sysUser.setUserEmail("[email protected]");
        sysUser.setUserInfo("test info");
        // 正常情况下应该读入一张图片保存到byte数组中
        sysUser.setHeadImg(new byte[]{1, 2, 3});
        sysUser.setCreateTime(new Date());

        // 这里的返回值result是执行的SQL影响的行数
        int result = sysUserMapper.insertUseGeneratedKeys(sysUser);
        // 只插入1条数据
        Assert.assertEquals(1, result);
        // 因为id回写,所以id不为null
        Assert.assertNotNull(sysUser.getId());
    } finally {
        sqlSession.rollback();
        sqlSession.close();
    }
}           

运行该测试方法,测试通过,输出日志如下。

DEBUG [main] - ==> Preparing: INSERT INTO sys_user(user_name, user_password, user_email, user_info, head_img, create_time) VALUES (?,?,?,?,?,?)

DEBUG [main] - ==> Parameters: test1(String), 123456(String), [email protected](String), test info(String), java.io.ByteArrayInputStream@544a2ea6(ByteArrayInputStream), 2019-07-02 14:02:22.506(Timestamp)

DEBUG [main] - <== Updates: 1

1.3 返回主键值(selectKey方式)

1.2中回写主键的方法只适用于支持主键自增的数据库。

但有些数据库(比如Oracle)不提供主键自增的功能,而是使用序列得到一个值,然后将这个值赋给id,再将数据插入到数据库。

对于这种情况,就可以采用selectKey方式,因为selectKey方式不仅适用于不提供主键自增功能的数据库,也适用于提供主键自增功能的数据库。

我们先来看下MySql的例子。

首先,在接口SysUserMapper中添加如下方法。

/**
 * 新增用户-使用selectKey方式
 *
 * @param sysUser
 * @return
 */
int insertUseSelectKey(SysUser sysUser);           

然后打开对应的SysUserMapper.xml文件,添加如下代码。

<insert id="insertUseSelectKey">
    INSERT INTO sys_user(user_name, user_password, user_email, user_info, head_img, create_time)
    VALUES (#{userName},#{userPassword},#{userEmail},#{userInfo},#{headImg,jdbcType=BLOB},#{createTime,jdbcType=TIMESTAMP})
    <selectKey keyColumn="id" resultType="long" keyProperty="id" order="AFTER">
        SELECT LAST_INSERT_ID()
    </selectKey>
</insert>           

和1.2相比,这里的语句多了selectKey标签,其中:

  • keyColumn:主键的数据库列名。
  • resultType:返回值类型。
  • keyProperty:主键对应的属性名。
  • order:该属性的设置和使用的数据库有关,如果使用的是MySql数据库,设置的值是AFTER,因为当前记录的主键值在insert语句执行成功后才能获取到。如果使用的是Oracle数据库,设置的值是BEFORE,因为Oracle中需要先从序列获取值,然后将值作为主键插入到数据库中。

如果数据库是Oracle的话,语句如下(因为环境问题,以下代码我并未验证,有兴趣的同学可以自己试下)。

<insert id="insertUseSelectKey">
    <selectKey keyColumn="id" resultType="long" keyProperty="id" order="BEFORE">
        SELECT SEQ_ID.nextval from dual
    </selectKey>
    INSERT INTO sys_user(id,user_name, user_password, user_email, user_info, head_img, create_time)
    VALUES (#{id},#{userName},#{userPassword},#{userEmail},#{userInfo},#{headImg,jdbcType=BLOB},#{createTime,jdbcType=TIMESTAMP})
</insert>           

2. update用法

假如我们现在希望通过主键id来更新用户信息,该如何操作呢?

首先,在接口SysUserMapper中添加如下方法。

/**
 * 根据主键更新
 *
 * @param sysUser
 * @return
 */
int updateById(SysUser sysUser);           

然后,打开对应的SysUserMapper.xml文件,添加如下代码。

<update id="updateById">
    UPDATE sys_user
    SET user_name = #{userName},
        user_password = #{userPassword},
        user_email = #{userEmail},
        user_info = #{userInfo},
        head_img = #{headImg,jdbcType=BLOB},
        create_time = #{createTime,jdbcType=TIMESTAMP}
    WHERE id = #{id}
</update>           

最后在SysUserMapperTest测试类中,添加如下测试方法。

@Test
public void testUpdateById() {
    SqlSession sqlSession = getSqlSession();

    try {
        SysUserMapper sysUserMapper = sqlSession.getMapper(SysUserMapper.class);
        SysUser sysUser = sysUserMapper.selectById(1L);

        Assert.assertEquals("admin", sysUser.getUserName());

        sysUser.setUserName("admin_test");
        sysUser.setUserEmail("[email protected]");
        sysUser.setUserInfo("test info");
        // 正常情况下应该读入一张图片保存到byte数组中
        sysUser.setHeadImg(new byte[]{1, 2, 3});
        sysUser.setCreateTime(new Date());

        // 这里的返回值result是执行的SQL影响的行数
        int result = sysUserMapper.updateById(sysUser);
        // 只更新1条数据
        Assert.assertEquals(1, result);

        sysUser = sysUserMapper.selectById(1L);
        Assert.assertEquals("admin_test", sysUser.getUserName());
        Assert.assertEquals("[email protected]", sysUser.getUserEmail());
    } finally {
        sqlSession.rollback();
        sqlSession.close();
    }
}           

运行测试方法,测试通过,输出的部分日志如下。

DEBUG [main] - ==> Preparing: UPDATE sys_user SET user_name = ?, user_password = ?, user_email = ?, user_info = ?, head_img = ?, create_time = ? WHERE id = ?

DEBUG [main] - ==> Parameters: admin_test(String), 123456(String), [email protected](String), test info(String), java.io.ByteArrayInputStream@78186a70(ByteArrayInputStream), 2019-07-02 14:57:34.792(Timestamp), 1(Long)

DEBUG [main] - <== Updates: 1

3. delete用法

假如我们现在希望通过主键id来删除用户信息,该如何操作呢?

首先,在接口SysUserMapper中添加如下方法。

/**
* 根据主键删除
*
* @param id
* @return
*/
int deleteById(Long id);

/**
* 根据对象的主键删除
*
* @param sysUser
* @return
*/
int deleteBySysUser(SysUser sysUser);           

然后,打开对应的SysUserMapper.xml文件,添加如下代码。

<delete id="deleteById">
    DELETE FROM sys_user WHERE id = #{id}
</delete>
<delete id="deleteBySysUser">
    DELETE FROM sys_user WHERE id = #{id}
</delete>           

最后在SysUserMapperTest测试类中,添加如下测试方法。

@Test
public void testDeleteById() {
    SqlSession sqlSession = getSqlSession();

    try {
        SysUserMapper sysUserMapper = sqlSession.getMapper(SysUserMapper.class);
        SysUser sysUser = sysUserMapper.selectById(1L);
        Assert.assertNotNull(sysUser);

        // 这里是直接根据id删除
        int result = sysUserMapper.deleteById(1L);
        // 只删除1条数据
        Assert.assertEquals(1, result);

        Assert.assertNull(sysUserMapper.selectById(1L));

        SysUser sysUser2 = sysUserMapper.selectById(1001L);
        Assert.assertNotNull(sysUser2);

        // 这里是根据对象的id属性删除
        Assert.assertEquals(1, sysUserMapper.deleteBySysUser(sysUser2));

        Assert.assertNull(sysUserMapper.selectById(1001L));
    } finally {
        sqlSession.rollback();
        sqlSession.close();
    }
}           

运行测试方法,测试通过,输出的部分日志如下。

DEBUG [main] - ==> Preparing: DELETE FROM sys_user WHERE id = ?

DEBUG [main] - ==> Parameters: 1(Long)

DEBUG [main] - <== Updates: 1

4. 多个接口参数的用法

4.1 参数类型是基本类型

截止目前,我们定义的方法都只有1个参数,要么是只有1个基本类型的参数,比如selectById(Long id);。

要么是只有1个对象作为参数,即将多个参数合并成了1个对象。

但有些场景下,比如只有2个参数,没有必要为这2个参数再新建一个对象,比如我们现在需要根据用户的id和角色的状态来获取用户的所有角色,那么该如何使用呢?

首先,在接口SysUserMapper中添加如下方法。

/**
 * 根据用户id和角色的enabled状态获取用户的角色
 *
 * @param userId
 * @param enabled
 * @return
 */
List<SysRole> selectRolesByUserIdAndRoleEnabled(Long userId,Integer enabled);           

然后,打开对应的SysUserMapper.xml文件,添加如下代码。

<select id="selectRolesByUserIdAndRoleEnabled" resultType="com.zwwhnly.mybatisaction.model.SysRole">
    SELECT r.id,
           r.role_name   roleName,
           r.enabled,
           r.create_by   createBy,
           r.create_time createTime
    FROM sys_user u
    INNER JOIN sys_user_role ur ON u.id = ur.user_id
    INNER JOIN sys_role r ON ur.role_id = r.id
    WHERE u.id = #{userId} AND r.enabled = #{enabled}
</select>           

在SysUserMapperTest测试类中,添加如下测试方法。

@Test
public void testselectRolesByUserIdAndRoleEnabled() {
    SqlSession sqlSession = getSqlSession();

    try {
        SysUserMapper sysUserMapper = sqlSession.getMapper(SysUserMapper.class);
        List<SysRole> sysRoleList = sysUserMapper.selectRolesByUserIdAndRoleEnabled(1L, 1);

        Assert.assertNotNull(sysRoleList);
        Assert.assertTrue(sysRoleList.size() > 0);
    } finally {
        sqlSession.rollback();
        sqlSession.close();
    }
}           

运行该测试方法,发现报如下错误。

MyBatis从入门到精通(四):MyBatis XML方式的基本用法之增删改

报错信息中说未找到参数userId,可用的参数是[0,1,param1,param2],也就是说我们将代码修改为:

WHERE u.id = #{0} AND r.enabled = #{1}           

或者修改为:

WHERE u.id = #{param1} AND r.enabled = #{param2}           

这么使用是可以测试通过的,不过这样使用,代码阅读起来不够友好,因此并不推荐这么使用。

推荐在接口方法的参数前添加@Param注解,如下所示:

/**
 * 根据用户id和角色的enabled状态获取用户的角色
 *
 * @param userId
 * @param enabled
 * @return
 */
List<SysRole> selectRolesByUserIdAndRoleEnabled(@Param("userId") Long userId, @Param("enabled") Integer enabled);           

运行刚刚添加的测试方法,测试通过,输出日志如下:

DEBUG [main] - ==> Preparing: SELECT r.id, r.role_name roleName, r.enabled, r.create_by createBy, r.create_time createTime FROM sys_user u INNER JOIN sys_user_role ur ON u.id = ur.user_id INNER JOIN sys_role r ON ur.role_id = r.id WHERE u.id = ? AND r.enabled = ?

DEBUG [main] - ==> Parameters: 1(Long), 1(Integer)

TRACE [main] - <== Columns: id, roleName, enabled, createBy, createTime

TRACE [main] - <== Row: 1, 管理员, 1, 1, 2019-06-27 18:21:12.0

TRACE [main] - <== Row: 2, 普通用户, 1, 1, 2019-06-27 18:21:12.0

DEBUG [main] - <== Total: 2

4.2 参数类型是对象

为了演示参数类型是对象的使用方法,我们在接口SysUserMapper中添加如下方法:

/**
 * 根据用户id和角色的enabled状态获取用户的角色
 *
 * @param user
 * @param role
 * @return
 */
List<SysRole> selectRolesByUserAndRole(@Param("user") SysUser user, @Param("role") SysRole role);           

此时对应的xml中的语句为:

<select id="selectRolesByUserAndRole" resultType="com.zwwhnly.mybatisaction.model.SysRole">
    SELECT r.id,
    r.role_name   roleName,
    r.enabled,
    r.create_by   createBy,
    r.create_time createTime
    FROM sys_user u
    INNER JOIN sys_user_role ur ON u.id = ur.user_id
    INNER JOIN sys_role r ON ur.role_id = r.id
    WHERE u.id = #{user.id} AND r.enabled = #{role.enabled}
</select>           

5. 源码

源码地址:https://github.com/zwwhnly/mybatis-action.git,欢迎下载。

6. 参考

刘增辉《MyBatis从入门到精通》