insert用法
和上一节的 select 相比,insert 要简单很多。只有让它返回主键值时,由于不同数据库的主键生成方式不同,这种情况下会有一些复杂。首先从最简单的 insert 方法开始学习。
简单的insert方法
在 UserMapper 中添加如下方法。
/**
* 新增用户
* @param sysUser 用户
*/
void 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>
先看 <insert> 元素,这个标签包含如下属性。
-
id:命名空间中的唯一标识符,可用来代表这条语句。
-
parameterType:即将传入的语句参数的完全限定类名或别名。这个属性是可选的,因为 MyBatis 可以推断出传入语句的具体参数,因此不建议配置该属性。
-
flushCache:默认值为 true,任何时候只要语句被调用,都会清空一级缓存和二级缓存。
-
timeout:设置在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。
-
statementType:对于 STATEMENT、PREPARED、CALLABLE,MyBatis 会分别使用对应的 Statement、PreparedStatement、CallableStatement,默认值为 PREPARED。
-
useGeneratedKeys:默认值为 false。如果设置为 true,MyBatis 会使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键。
-
keyProperty:MyBatis 通过 getGeneratedKeys 获取主键值后将要赋值的属性名。如果希望得到多个数据库自动生成的列,属性值也可以是以逗号分隔的属性名称列表。
-
keyColumn:仅对 INSERT 和 UPDATE 有用。通过生成的键值设置表中的列名,这个设置仅在某些数据库(如 PostgreSQL)中是必须的,当主键列不是表中的第一列时需要设置。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。
-
databaseId:如果配置了 databaseIdProvider(4.6 节有详细配置方法),MyBatis 会加载所有的不带 databaseId 的或匹配当前 databaseId 的语句。如果同时存在带 databaseId 和不带 databaseId 的语句,后者会被忽略。
此处 <insert> 中的 SQL 就是一个简单的 INSERT 语句,将所有的列都列举出来,在 values 中通过 #{property}
方式从参数中取出属性的值。
为了防止类型错误,对于一些特殊的数据类型,建议指定具体的 jdbcType 值。例如 headImg 指定 BLOB 类型,createTime 指定 TIMESTAMP 类型。
BLOB 对应的类型是 ByteArrayInputStream,就是二进制数据流。 由于数据库区分 date、time、datetime 类型,但是 Java 中一般都使用 java.util.Date 类型。因此为了保证数据类型的正确,需要手动指定日期类型,date、time、datetime 对应的 JDBC 类型分别为 DATE、TIME、TIMESTAMP。 |
现在在 UserMapperTest 测试类中增加一个方法来测试这个 insert 方法,代码如下。
@Test
public void testInsert(){
SqlSession sqlSession = getSqlSession();
try {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
// 创建一个user对象
SysUser user = new SysUser();
user.setId(123L);
user.setUserName("test1");
user.setUserPassword("123456");
user.setUserEmail("123456@qq.com");
user.setUserInfo("test info");
// 正常情况下应该读入一张图片存到byte数组中
user.setHeadImg(new byte[]{1,2,3});
user.setCreateTime(new Date());
// 将新建的对象插入数据库中,特别注意这里的返回值result是执行的SQL影响的行数
int result = userMapper.insert(user);
// 只插入 1 条数据
Assert.assertEquals(1, result);
// id为null,没有给id赋值,并且没有配置回写id的值
Assert.assertNull(user.getId());
// 需要手动提交事务
// sqlSession.commit();
}finally {
// 为了不影响其它测试,这里选择回滚
// 由于默认的 sqlSessionFactory.openSession()是不自动提交的
// 因此不手动执行 commit 也不会提交到数据库
sqlSession.rollback();
// 不要忘记关闭sqlSession
sqlSession.close();
}
}
执行这个测试,输出结果如下。
可以看到日期的值如下。
2016-04-07 22:11:03.121(Timestamp)
为了让大家理解指定具体的 jdbcType 值的作用,下面用时间类型来进行测试。
首先对 UserMapper.xml 中 insert 的 SQL 语句做出如下修改。
#{createTime,jdbcType=DATE}
然后再次执行上面的测试,看一下日期字段的值。
2016-04-07(Date)
这个值就是我们设置的 DATE,这个类型只有日期部分。再修改 insert 中的 SQL 如下。
#{createTime,jdbcType=TIME}
再次执行测试,会发现这次不像上次那么顺利,测试失败了,关键部分异常信息如下。

产生错误的原因是,数据库中的字段类型为 datetime,但是这里只有 time 部分的值。
通过上面成功的测试实例,说明数据库的 datetime 类型可以存储 DATE(时间部分默认为 00:00:00)和 TIMESTAMP 这两种类型的时间,不能存储 TIME 类型的时间。将数据库的字段类型修改为 time 时,这个测试就可以正常通过了。做完这些尝试后,将上面的方法还原。
接下来看一下接口中对应的方法 int insert(SysUser sysUser)。很多人会把这个 int 类型的返回值当作数据库返回的主键的值,它其实是执行的 SQL 影响的行数,这个值和日志中的 Updates:1 是一致的。也就是说,这个 INSERT 语句影响了数据库中的 1 行数据。如果是批量插入、批量更新、批量删除,这里的数字会是插入的数据个数、更新的数据个数、删除的数据个数。一般在数据库管理软件中,执行 SQL 语句时,这些工具都会显示影响的行数。
既然这个返回值不是主键的值,那么该如何获得主键的值呢?下面提供两种方法,基本上可以涵盖所有数据库的不同情况。
使用JDBC方式返回主键自增的值
在使用主键自增(如 MySQL、SQL Server 数据库)时,插入数据库后可能需要得到自增的主键值,然后使用这个值进行一些其他的操作。现在,增加一个 insert2 方法,复制 insert 并稍加修改,首先在 UserMapper 接口中增加 insert2 方法。
/**
* 新增用户-使用 useGeneratedKeys 方式
*/
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>
将 insert2 和 insert 比较,主要的变化是在 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 = getSqlSession();
try {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
SysUser user = new SysUser();
user.setUserName("test1");
user.setUserPassword("123456");
user.setUserEmail("123456@qq.com");
user.setUserInfo("test info");
user.setHeadImg(new byte[]{1,2,3});
user.setCreateTime(new Date());
int result = userMapper.insert2(user);
// 只插入 1 条数据
Assert.assertEquals(1, result);
// 因为id回写,所以id不为null
Assert.assertNotNull(user.getId());
} finally {
sqlSession.rollback();
// 不要忘记关闭 sqlSession
sqlSession.close();
}
}
执行该测试,该测试正确通过。如果想看 id 的值是否正确,可以在测试中输出 id 的值,并且将 rollback() 方法修改为 commit() 方法,将结果提交给数据库(否则数据只存在于 session 中,会随着程序的关闭而消失),然后查看数据库验证 id 是否一致(验证后记得删除刚刚插入的数据,否则会影响后续测试的结果)。
使用selectKey返回主键的值
上面这种回写主键的方法只适用于支持主键自增的数据库。有些数据库(如 Oracle)不提供主键自增的功能,而是使用序列得到一个值,然后将这个值赋给 id,再将数据插入数据库。对于这种情况,可以采用另外一种方式:使用 <selectKey> 标签来获取主键的值,这种方式不仅适用于不提供主键自增功能的数据库,也适用于提供主键自增功能的数据库。
为了让大家能看到这种方法的效果,分别用 MySQL 和 Oracle 数据库举两个例子。先来看一下 MySQL 的例子。
在接口和 XML 中再新增一个 insert3 方法,UserMapper 接口的代码如下。
/**
* 新增用户-使用 selectKey 方式
*/
int insert3(SysUser sysUser);
上面的代码和前两个接口方法仍然一样,不同的还是 UserMapper.xml 中的代码,具体如下。
<insert id="insert3">
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})
<selectKey keyColumn="id" resultType="long" keyProperty="id" order="AFTER">
SELECT LAST_INSERT_ID()
</selectKey>
</insert>
注意看下面这段代码,和 insert 相比增加了 selectKey 标签。
<selectKey keyColumn="id" resultType="long" keyProperty="id" order="AFTER">
SELECT LAST_INSERT_ID()
</selectKey>
selectKey 标签的 keyColumn、keyProperty 和上面 useGeneratedKeys 的用法含义相同,这里的 resultType 用于设置返回值类型。order 属性的设置和使用的数据库有关。在 MySQL 数据库中,order 属性设置的值是 AFTER,因为当前记录的主键值在 insert 语句执行成功后才能获取到。而在 Oracle 数据库中,order 的值要设置为 BEFORE,这是因为 Oracle 中需要先从序列获取值,然后将值作为主键插入到数据库中。
下面是一个 Oracle 的 XML 文件中写法的简单示例。
<insert id="insert3">
<selectKey keyColumn="id" resultType="long" keyProperty="id" order="BEFORE">
SELECT SEQ_ID.nextval from dual
</selectKey>
insert into sys_user (
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>
可以发现,selectKey 元素放置的位置和之前 MySQL 例子中的不同,其实这个元素放置的位置不会影响 selectKey 中的方法在 insert 前面或者后面执行的顺序,影响执行顺序的是 order 属性,这么写仅仅是为了符合实际的执行顺序,看起来更直观而已。
Oracle 方式的 INSERT 语句中明确写出了 id 列和值 #{id},因为执行 selectKey 中的语句后 id 就有值了,我们需要把这个序列值作为主键值插入到数据库中,所以必须指定 id 列,如果不指定这一列,数据库就会因为主键不能为空而抛出异常。 |
以上是对 selectKey 标签中属性的介绍,接着看一下 selectKey 元素中的内容。它的内容就是一个独立的 SQL 语句,在 Oracle 示例中,SELECT SEQ_ID.nextval from dual 是一个获取序列的 SQL 语句。MySQL 中的 SQL 语句 SELECT LAST_INSERT_ID() 用于获取数据库中最后插入的数据的 ID 值。以下是其他一些支持主键自增的数据库配置 selectKey 中回写主键的 SQL。
-
DB2 使用 VALUES IDENTITY_VAL_LOCAL()。
-
MYSQL 使用 SELECT LAST_INSERT_ID()。
-
SQLSERVER 使用 SELECT SCOPE_IDENTITY()。
-
CLOUDSCAPE 使用 VALUES IDENTITY_VAL_LOCAL()。
-
DERBY 使用 VALUES IDENTITY_VAL_LOCAL()。
-
HSQLDB 使用 CALL IDENTITY()。
-
SYBASE 使用 SELECT @@IDENTITY。
-
DB2_MF 使用 SELECT IDENTITY_VAL_LOCAL() FROM SYSIBM.SYSDUMMY1。
-
INFORMIX 使用 select dbinfo('sqlca.sqlerrd1') from systables where tabid=1。
以上就是 insert 的基本用法,后面介绍动态 SQL 的时候还会涉及更多的 insert 用法。下一节继续学习和 insert 用法相似的 update 用法。