MyBatis修炼MyBatis我爱编程

【MyBatis】 MyBatis修炼之五 MyBatis

2017-08-15  本文已影响227人  开心跳蚤

和select相比,insert要简单的多。只有让他返回主键时,由于不同数据库的主键生成方式不同,这种情况下会有一些复杂。

MyBatis参考文档:

中文版:http://www.mybatis.org/mybatis-3/zh/index.html
英文版:http://www.mybatis.org/mybatis-3/

工具

JDK 1.6及以上版本
MyBatis 3.30版本
MySQL 6.3版本
Eclipse4 及以上版本
Apache Maven 构建工具


项目源码下载地址:https://github.com/JFAlex/MyBatis/tree/master/MyBatis_No.3/alex


简单的insert用法

现在我们向用户表中添加一个新用户,在UserMapper接口中添加如下方法:

public int insert(SysUser sysUser);

在UserMapper.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>

首先解释一下<select>标签下包含的属性。

  • id:命名空间的唯一标识,可以用来代表这条语句。
  • parameterType:即将传入的语句参数的完全限定类名或别名。这个属性是可选的,因为MyBatis可以推断出传入语句的具体参数,因此不建议配置该属性。
  • flushCache:默认值为true,任何时候只要语句被调用,都会清空一级缓存和二级缓存。
  • timeout:设置在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。
  • statementType:对于STATEMENT、CALLABLE、PREPARED,MyBatis会分别使用对应的Statement、CallableStatement、PreparedStatement,默认值为PREPARED。
  • useGeneratedKeys:默认值为false。如果设置为true,MyBati会使用JDBC的getGeneratedKeys方法来取出有数据库内部生成的主键。
  • keyProperty:用于设置MyBatis通过getGeneratedKeys获取主键值后,将要赋值给哪个属性(属性名)。
  • keyColumn:仅对INSERT和UPDATE有用。通过生成的键值设置表中的列名,这个设置仅在某些数据库(如PostgreSQL)中是必须的,当主键列不是表中的第一列时需要设置。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。
  • databaseId:如果配置了databaseIdProvider,MyBatis会加载所有的不带databaseId的或匹配当前databaseId的语句。如果同时存在带databaseId和不带databaseId的语句,则不带databaseId的语句被忽略。

在XML配置SQL语句时我们可以看到,最后两个字段(#{headImg和 jdbcType=BLOB},#{createTime,jdbcType=TIMESTAMP})中添加了jdbcType,这里的作用是为了防止类型错误,对于一些特殊的数据类型,需要指定具体的jdbcType值。

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

现在在UserMapperTest测试类中添加一个方法来测试insert方法:

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

        // 获取UserMapper接口
        try {
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
            //创建一个user对象
            SysUser user = new SysUser();
            user.setUserName("insert");
            user.setUserPassword("123456");
            user.setUserEmail("mybatis@my.test");
            user.setUserInfo("insert data");
            //正常情况下应该存入一张图片
            user.setHeadImg(new byte[]{1,2,3});
            user.setCreateTime(new Date());
            
            //将新建的对象插入数据库中,特别注意这里的返回值result是执行的SQL影响的行数
            int result = userMapper.insert(user);
            System.out.println("插入成功数据条数为:" + result);
        }catch(Exception e){
            e.printStackTrace();
        } finally {
            //为了不对其他的测试造成影响,此处进行数据回滚
            //由于默认的sqlSessionFactory.openSession()是不会自动提交的,因此如果不手动进行commit操作,数据也不会写入数据库
            sqlSession.rollback();
            // 关闭SqlSession
            sqlSession.close();
        }
    }

右键单击测试类,在Run As选项中选择JUnit Test执行测试。测试通过,控制台将会打印如下日志:

DEBUG [main] - Opening JDBC Connection
DEBUG [main] - Created connection 640363654.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@262b2c86]
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, insert(String), 123456(String), mybatis@my.test(String), insert data(String), java.io.ByteArrayInputStream@462d5aee(ByteArrayInputStream), 2017-08-15 10:47:26.377(Timestamp)
DEBUG [main] - <==    Updates: 1
插入成功数据条数为:1
DEBUG [main] - Rolling back JDBC Connection [com.mysql.jdbc.JDBC4Connection@262b2c86]
DEBUG [main] - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@262b2c86]
DEBUG [main] - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@262b2c86]
DEBUG [main] - Returned connection 640363654 to pool.

通过上面的输出日志,我们可以看到
2017-08-15 10:47:26.377(Timestamp)
如果我们将XML中的INSERT的SQL语句修改如下

#{createTime,jdbcType=DATE}

再次执行测试,我们可以看到日志输出的日期字段的值会变成
2017-08-15(Date)
这个值就是我们设置的DATE,但是如果我们将jdbcType的值设置为TIME,再次进行测试,我们测试将会失败

### Error updating database.  Cause: com.mysql.jdbc.MysqlDataTruncation: Data truncation: Incorrect datetime value: '10:56:17' for column 'create_time' at row 1
### The error may involve defaultParameterMap
### The error occurred while setting parameters
### SQL: INSERT INTO sys_user (id,user_name, user_password,   user_email, user_info, head_img, create_time)   VALUES   (?,?,?,?,?,?,?)
### Cause: com.mysql.jdbc.MysqlDataTruncation: Data truncation: Incorrect datetime value: '10:56:17' for column 'create_time' at row 1

通过错误信息,我们可以知晓错误原因是数据库中的字段类型为datetime,但是这里的这里只有time部分的值。

数据库的datetime类型可以存储DATE(时间部分默认为00:00:00)和TIMESTAMP这两种类型的事件,不能存储TIME类型的时间。

通过上面的测试,我们应该可以理解jdbcType值得作用了。

返回主键值

之前我们进行INSERT操作时,返回的数据为影响的数据行数,但是很多时候我们并不需要这个返回数据,而是需要知道这条数据的主键,那么我们可以怎么获得这个主键的值呢?

使用JDBC方式返回主键自增的值

在使用主键自增(如MySQL、SQL Server数据库)时,插入数据库后科技能需要得到自增的主键值,然后使用这个值进行一些其他的操作。

现在增加一个insert2方法,首先在userMapper接口中增加insert2方法

public int insert2(SysUser sysUser);

然后在XML中新增一个insert2方法

    <insert id="insert2" 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>

比较insert和insert2,其中最主要的变化就是insert2比insert多添加了两个属性

useGeneratedKeys="true"
keyProperty="id"

useGeneratedKeys设置为true后,MyBatis会使用JDBC的getGeneratedKeys方法来取出由数据库内部生成的主键。获得主键后将其赋值给keyProperty配置的id属性。当需要配置多个属性时,使用逗号隔开,这种情况下通常还需要设置keyColumn属性,按顺序指定数据库的列,这里的值会和keyProperty配置的属性一一对应。

由于要使用数据库返回的主键,所有SQL中去掉了id列和#{id}属性。

然后写一个测试验证是否返回了SysUser的主键值,在UserMapperTest测试类中添加代码:

    @Test
    public void testInsert2(){
        // 获取SqlSession
        SqlSession sqlSession = getSqlSession();

        // 获取UserMapper接口
        try {
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
            //创建一个user对象
            SysUser user = new SysUser();
            user.setUserName("insert");
            user.setUserPassword("123456");
            user.setUserEmail("mybatis@my.test");
            user.setUserInfo("insert data");
            //正常情况下应该存入一张图片
            user.setHeadImg(new byte[]{1,2,3});
            user.setCreateTime(new Date());
            
            //将新建的对象插入数据库中,特别注意这里的返回值result是执行的SQL影响的行数
            int result = userMapper.insert2(user);
            System.out.println("插入成功数据条数为:" + result);
            System.out.println("主键id为:"+user.getId());
        }catch(Exception e){
            e.printStackTrace();
        } finally {
            //为了不对其他的测试造成影响,此处进行数据回滚
            //由于默认的sqlSessionFactory.openSession()是不会自动提交的,因此如果不手动进行commit操作,数据也不会写入数据库
            sqlSession.rollback();
            // 关闭SqlSession
            sqlSession.close();
        }
    }

右键单击测试类,在Run As选项中选择JUnit Test执行测试。测试通过,控制台将会打印如下日志:

DEBUG [main] - Opening JDBC Connection
DEBUG [main] - Created connection 99451533.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@5ed828d]
DEBUG [main] - ==>  Preparing: INSERT INTO sys_user (user_name, user_password, user_email, user_info, head_img, create_time) VALUES (?,?,?,?,?,?) 
DEBUG [main] - ==> Parameters: insert(String), 123456(String), mybatis@my.test(String), insert data(String), java.io.ByteArrayInputStream@462d5aee(ByteArrayInputStream), 2017-08-15 11:50:33.443(Timestamp)
DEBUG [main] - <==    Updates: 1
插入成功数据条数为:1
主键id为:7
DEBUG [main] - Rolling back JDBC Connection [com.mysql.jdbc.JDBC4Connection@5ed828d]
DEBUG [main] - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@5ed828d]
DEBUG [main] - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@5ed828d]
DEBUG [main] - Returned connection 99451533 to pool.

如果想知道这里打印的id的值是否正确,我们可以将测试方法中的rollback()方法修改为commit()方法,将结果提交到数据库(否则数据只存在session中,会随着程序的关闭而消失),然后查看数据库验证id是否一致。

使用selectKey返回主键的值

上面这种获取主键的方法只适用于支持主键自动增长的数据库。但是有些数据库(如Oracle)不提供主键自增的功能,而是使用序列得到一个值,然后讲这个值赋值给id,在将数据插入到数据库中,对于这种情况,我们就可以使用另一种方法:使用<selectKey>标签来获取主键的值,这种方式不仅是用于不提供主键自增功能的数据库,而且也适用于提供主键自增功能的数据库

我使用的是MySQL数据库(提供主键自增),当然如果你使用的是Oracel数据库(不提供主键自增),也是没有问题的。

在接口和XML中新增一个方法insert3,UserMapper接口中的方法如下:

public int insert3(SysUser sysUser);

然后添加UserMapper.xml中的代码:

    <insert id="insert3">
        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})
        
        <selectKey keyColumn="id" resultType="long" keyProperty="id" order="AFTER">
            SELECT LAST_INSERT_ID()
        </selectKey>
    </insert>

我们可以很明显的看出,这里的XML配置中除了我们常见的SQL语句外,还包含了一个selectKey标签。

selectKey标签的keyColumn、keyProperty和上一种方法中的useGengeratedKeys的用法含义相同,这里的resultType用于设置返回值类型。order属性的设置和使用的数据库有关。在MySQL数据库中,order属性设置的值是AFTER,因为当前记录的主键是在insert语句执行成功后才获取到的。而在Oracel数据库中,order属性的值要设置为BEFORE,这是因为Oracle数据库中需要先从序列获取值,然后将值作为主键插入到数据库中。

selectKey标签放置的位置可以是在INSERT语句之后,也可以放置在INSERT语句之前,这并不影响执行顺序,而影响执行顺序的主要因素是order属性的值

注意:
Oracle方式的INSERT语句中需要明确的列出id列和值#{id},因为执行selectKey中的语句后id就有值了,我们需要把这个序列作为主键插入到数据库中,所以必须制定id列,如果不指定这一列,数据库就会因为主键不能为空而抛出异常。所以我们使用selectKey方式获取主键id时,不管使用的是什么数据库,我们都可以添加上主键列和值(这里为id列和值#{id})

右键单击测试类,在Run As选项中选择JUnit Test执行测试。测试通过,控制台将会打印如下日志:

DEBUG [main] - Opening JDBC Connection
DEBUG [main] - Created connection 2050835901.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@7a3d45bd]
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, insert(String), 123456(String), mybatis@my.test(String), insert data(String), java.io.ByteArrayInputStream@757942a1(ByteArrayInputStream), 2017-08-15 13:42:11.322(Timestamp)
DEBUG [main] - <==    Updates: 1
DEBUG [main] - ==>  Preparing: SELECT LAST_INSERT_ID() 
DEBUG [main] - ==> Parameters: 
TRACE [main] - <==    Columns: LAST_INSERT_ID()
TRACE [main] - <==        Row: 9
DEBUG [main] - <==      Total: 1
插入成功数据条数为:1
主键id为:9
DEBUG [main] - Rolling back JDBC Connection [com.mysql.jdbc.JDBC4Connection@7a3d45bd]
DEBUG [main] - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@7a3d45bd]
DEBUG [main] - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@7a3d45bd]
DEBUG [main] - Returned connection 2050835901 to pool.

我们还需要注意一个问题,就是selectKey元素中的内容,它的内容是一个独立的SQL语句,MySQL中的语句是SELECT LAST_INSERT_ID()用于获取数据库中最后插入数据的ID值。

以下是一些数据库配置selectKey中回写主键的SQL:


项目源码下载地址:https://github.com/JFAlex/MyBatis/tree/master/MyBatis_No.3/alex


上一篇: 【MyBatis】 MyBatis修炼之四 MyBatis XML方式的基本用法(SELECT)

下一篇: 【MyBatis】 MyBatis修炼之六 MyBatis XML方式的基本用法(UPDATE、DELETE)

上一篇下一篇

猜你喜欢

热点阅读