【MyBatis】 MyBatis修炼之五 MyBatis XML方式的基本用法(INSERT)

和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:

  • ORACLE 使用 SELECT SEQ_ID.nextval from dual (SEQ_ID为主键对应的序列名称)
  • DB2 使用 VALUES IDENTITY_VAL_LOCAL()
  • MYSQL 使用 SELECT LAST_INSERT_ID()
  • SQLSERVER 使用 SELECT SCOPE_IDENTITY()
  • DERBY 使用 VALUES IDENTITY_VAL_LOCAL()
  • HSQLDB 使用 CALL IDENTITY()
  • SYBASE 使用 SELECT @@IDENTITY()
  • DB2_MF 使用 SELECT IDENTITY_VAL_LOCAL() FROM SYSIBM.SYSDUMMY1
  • CLOUDSCAPE 使用 VALUES IDENTITY_VAL_LOCAL()
    以上所列数据库除Oracle数据为不支持主键自增,其余数据库均支持主键自增,所以只有在使用Oracle是order属性配置为BEFORE,其余都配置order属性为AFTER。

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


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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容