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