和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)
网友评论