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

和select相比,insert要簡單的多。只有讓他返回主鍵時,由于不同數(shù)據(jù)庫的主鍵生成方式不同,這種情況下會有一些復雜。

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 構(gòu)建工具


項目源碼下載地址:https://github.com/JFAlex/MyBatis/tree/master/MyBatis_No.3/alex


簡單的insert用法

現(xiàn)在我們向用戶表中添加一個新用戶,在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:即將傳入的語句參數(shù)的完全限定類名或別名。這個屬性是可選的,因為MyBatis可以推斷出傳入語句的具體參數(shù),因此不建議配置該屬性。
  • flushCache:默認值為true,任何時候只要語句被調(diào)用,都會清空一級緩存和二級緩存。
  • timeout:設(shè)置在拋出異常之前,驅(qū)動程序等待數(shù)據(jù)庫返回請求結(jié)果的秒數(shù)。
  • statementType:對于STATEMENT、CALLABLE、PREPARED,MyBatis會分別使用對應的Statement、CallableStatement、PreparedStatement,默認值為PREPARED。
  • useGeneratedKeys:默認值為false。如果設(shè)置為true,MyBati會使用JDBC的getGeneratedKeys方法來取出有數(shù)據(jù)庫內(nèi)部生成的主鍵。
  • keyProperty:用于設(shè)置MyBatis通過getGeneratedKeys獲取主鍵值后,將要賦值給哪個屬性(屬性名)。
  • keyColumn:僅對INSERT和UPDATE有用。通過生成的鍵值設(shè)置表中的列名,這個設(shè)置僅在某些數(shù)據(jù)庫(如PostgreSQL)中是必須的,當主鍵列不是表中的第一列時需要設(shè)置。如果希望得到多個生成的列,也可以是逗號分隔的屬性名稱列表。
  • databaseId:如果配置了databaseIdProvider,MyBatis會加載所有的不帶databaseId的或匹配當前databaseId的語句。如果同時存在帶databaseId和不帶databaseId的語句,則不帶databaseId的語句被忽略。

在XML配置SQL語句時我們可以看到,最后兩個字段(#{headImg和 jdbcType=BLOB},#{createTime,jdbcType=TIMESTAMP})中添加了jdbcType,這里的作用是為了防止類型錯誤,對于一些特殊的數(shù)據(jù)類型,需要指定具體的jdbcType值。

由于數(shù)據(jù)庫區(qū)分date、time、datetime類型,但是Java中一般都使用java.util.Date類型,因此為了保證數(shù)據(jù)庫類型的正確,需要手動指定日期類型,date、time、datetime對應的JDBC類型分別為DATE、TIME、TIMESTAMP。

現(xiàn)在在UserMapperTest測試類中添加一個方法來測試insert方法:

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

        // 獲取UserMapper接口
        try {
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
            //創(chuàng)建一個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());
            
            //將新建的對象插入數(shù)據(jù)庫中,特別注意這里的返回值result是執(zhí)行的SQL影響的行數(shù)
            int result = userMapper.insert(user);
            System.out.println("插入成功數(shù)據(jù)條數(shù)為:" + result);
        }catch(Exception e){
            e.printStackTrace();
        } finally {
            //為了不對其他的測試造成影響,此處進行數(shù)據(jù)回滾
            //由于默認的sqlSessionFactory.openSession()是不會自動提交的,因此如果不手動進行commit操作,數(shù)據(jù)也不會寫入數(shù)據(jù)庫
            sqlSession.rollback();
            // 關(guān)閉SqlSession
            sqlSession.close();
        }
    }

右鍵單擊測試類,在Run As選項中選擇JUnit Test執(zhí)行測試。測試通過,控制臺將會打印如下日志:

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
插入成功數(shù)據(jù)條數(shù)為: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}

再次執(zhí)行測試,我們可以看到日志輸出的日期字段的值會變成
2017-08-15(Date)
這個值就是我們設(shè)置的DATE,但是如果我們將jdbcType的值設(shè)置為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

通過錯誤信息,我們可以知曉錯誤原因是數(shù)據(jù)庫中的字段類型為datetime,但是這里的這里只有time部分的值。

數(shù)據(jù)庫的datetime類型可以存儲DATE(時間部分默認為00:00:00)和TIMESTAMP這兩種類型的事件,不能存儲TIME類型的時間。

通過上面的測試,我們應該可以理解jdbcType值得作用了。

返回主鍵值

之前我們進行INSERT操作時,返回的數(shù)據(jù)為影響的數(shù)據(jù)行數(shù),但是很多時候我們并不需要這個返回數(shù)據(jù),而是需要知道這條數(shù)據(jù)的主鍵,那么我們可以怎么獲得這個主鍵的值呢?

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

在使用主鍵自增(如MySQL、SQL Server數(shù)據(jù)庫)時,插入數(shù)據(jù)庫后科技能需要得到自增的主鍵值,然后使用這個值進行一些其他的操作。

現(xiàn)在增加一個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設(shè)置為true后,MyBatis會使用JDBC的getGeneratedKeys方法來取出由數(shù)據(jù)庫內(nèi)部生成的主鍵。獲得主鍵后將其賦值給keyProperty配置的id屬性。當需要配置多個屬性時,使用逗號隔開,這種情況下通常還需要設(shè)置keyColumn屬性,按順序指定數(shù)據(jù)庫的列,這里的值會和keyProperty配置的屬性一一對應。

由于要使用數(shù)據(jù)庫返回的主鍵,所有SQL中去掉了id列和#{id}屬性。

然后寫一個測試驗證是否返回了SysUser的主鍵值,在UserMapperTest測試類中添加代碼:

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

        // 獲取UserMapper接口
        try {
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
            //創(chuàng)建一個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());
            
            //將新建的對象插入數(shù)據(jù)庫中,特別注意這里的返回值result是執(zhí)行的SQL影響的行數(shù)
            int result = userMapper.insert2(user);
            System.out.println("插入成功數(shù)據(jù)條數(shù)為:" + result);
            System.out.println("主鍵id為:"+user.getId());
        }catch(Exception e){
            e.printStackTrace();
        } finally {
            //為了不對其他的測試造成影響,此處進行數(shù)據(jù)回滾
            //由于默認的sqlSessionFactory.openSession()是不會自動提交的,因此如果不手動進行commit操作,數(shù)據(jù)也不會寫入數(shù)據(jù)庫
            sqlSession.rollback();
            // 關(guān)閉SqlSession
            sqlSession.close();
        }
    }

右鍵單擊測試類,在Run As選項中選擇JUnit Test執(zhí)行測試。測試通過,控制臺將會打印如下日志:

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
插入成功數(shù)據(jù)條數(shù)為: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()方法,將結(jié)果提交到數(shù)據(jù)庫(否則數(shù)據(jù)只存在session中,會隨著程序的關(guān)閉而消失),然后查看數(shù)據(jù)庫驗證id是否一致。

使用selectKey返回主鍵的值

上面這種獲取主鍵的方法只適用于支持主鍵自動增長的數(shù)據(jù)庫。但是有些數(shù)據(jù)庫(如Oracle)不提供主鍵自增的功能,而是使用序列得到一個值,然后講這個值賦值給id,在將數(shù)據(jù)插入到數(shù)據(jù)庫中,對于這種情況,我們就可以使用另一種方法:使用<selectKey>標簽來獲取主鍵的值,這種方式不僅是用于不提供主鍵自增功能的數(shù)據(jù)庫,而且也適用于提供主鍵自增功能的數(shù)據(jù)庫。

我使用的是MySQL數(shù)據(jù)庫(提供主鍵自增),當然如果你使用的是Oracel數(shù)據(jù)庫(不提供主鍵自增),也是沒有問題的。

在接口和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用于設(shè)置返回值類型。order屬性的設(shè)置和使用的數(shù)據(jù)庫有關(guān)。在MySQL數(shù)據(jù)庫中,order屬性設(shè)置的值是AFTER,因為當前記錄的主鍵是在insert語句執(zhí)行成功后才獲取到的。而在Oracel數(shù)據(jù)庫中,order屬性的值要設(shè)置為BEFORE,這是因為Oracle數(shù)據(jù)庫中需要先從序列獲取值,然后將值作為主鍵插入到數(shù)據(jù)庫中。

selectKey標簽放置的位置可以是在INSERT語句之后,也可以放置在INSERT語句之前,這并不影響執(zhí)行順序,而影響執(zhí)行順序的主要因素是order屬性的值

注意:
Oracle方式的INSERT語句中需要明確的列出id列和值#{id},因為執(zhí)行selectKey中的語句后id就有值了,我們需要把這個序列作為主鍵插入到數(shù)據(jù)庫中,所以必須制定id列,如果不指定這一列,數(shù)據(jù)庫就會因為主鍵不能為空而拋出異常。所以我們使用selectKey方式獲取主鍵id時,不管使用的是什么數(shù)據(jù)庫,我們都可以添加上主鍵列和值(這里為id列和值#{id})

右鍵單擊測試類,在Run As選項中選擇JUnit Test執(zhí)行測試。測試通過,控制臺將會打印如下日志:

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
插入成功數(shù)據(jù)條數(shù)為: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元素中的內(nèi)容,它的內(nèi)容是一個獨立的SQL語句,MySQL中的語句是SELECT LAST_INSERT_ID()用于獲取數(shù)據(jù)庫中最后插入數(shù)據(jù)的ID值。

以下是一些數(shù)據(jù)庫配置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()
    以上所列數(shù)據(jù)庫除Oracle數(shù)據(jù)為不支持主鍵自增,其余數(shù)據(jù)庫均支持主鍵自增,所以只有在使用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)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,963評論 6 542
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,348評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 178,083評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,706評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,442評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,802評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,795評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,983評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,542評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,287評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,486評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,030評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,710評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,116評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,412評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,224評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,462評論 2 378

推薦閱讀更多精彩內(nèi)容