MyBatis从入门到精通(第2章):MyBatis XML方式的基本用法【insert用法、update用法、delete用法】
2.4 insert 用法
2.4.1 简单的 insert方法
在接口 UserMapper.java 中添加如下方法。
/** * 新增用户 * @param sysUser * @return */ 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>
先看 <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 的语句,后者会被忽略。
在values中通过 #{ property } 方式从(实体类对象的)参数中取出属性的值。为了防止类型错误,对于一些特殊的数据类型,建议指定具体的 jdbcType 值。例如 headImg 指定 BLOB 类型, createTime 指定 TIMESTAMP 类型。
特别说明:
1 )BLOB对应的类型是 ByteArrayInputStream,就是二进制数据流。
2 )由于数据库区分date、time、datetime类型,但是在Java中一般都使用java.util.Date类型。因此为了保证数据类型的正确,需要手动指定日期类型。date、time、datetime对应的JDBC类型分别为DATE、TIME、TIMESTAMP。
BaseMapperTest
import java.io.IOException; import java.io.Reader; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import org.junit.BeforeClass; /** * 基础测试类 */ public class BaseMapperTest { private static SqlSessionFactory sqlSessionFactory; @BeforeClass public static void init() { try { Reader reader = Resources.getResourceAsReader("mybatis-config.xml"); sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader); reader.close(); } catch (IOException ignore) { ignore.printStackTrace(); } } public SqlSession getSqlSession(){ return sqlSessionFactory.openSession(); } }
现在在 UserMapperTest 测试类中增加一个方法来测试这个 insert 方法,代码如下。现在在 UserMapperTest 测试类中增加一个方法来测试这个 insert 方法,代码如下。
@Test public void testInsert(){ SqlSession sqlSession = getSqlSession(); try { UserMapper userMapper = sqlSession.getMapper(UserMapper.class); //创建一个 user 对象 SysUser user = new SysUser(); user.setUserName("test1"); user.setUserPassword("123456"); user.setUserEmail("test@mybatis.tk"); 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()); //引用数据类型未赋值默认为null } finally { //为了不影响数据库中的数据导致其他测试失败,这里选择回滚 //由于默认的 sqlSessionFactory.openSession() 是不自动提交的, //因此不手动执行 commit 也不会提交到数据库 sqlSession.rollback(); //不要忘记关闭 sqlSession sqlSession.close(); } }
数据库的datatime类型可以存储DATE(时间部分默认为00:00:00)和 TIMESTAMP两种类型的时间。
上面接口中对应的方法 int insert( SysUser sysUser) ,这个int类型返回值是执行的SQL影响的行数,这个值和日志中的 Updatas:1 是一致的。
如何获得主键的值呢?下面提供两种方法,基本上可以涵盖所有数据库的不同情况。
2.4.2 使用JDBC方式返回主键自增的值
在使用主键自增(如 MySQL、 SQL Server 数据库)时,插入数据库后可能需要得到自增的主键值,然后使用这个值进行一些其他的操作。
现在添加一个insert2方法,首先在UserMapper接口中增加insert2方法。
/** * 新增用户 - 使用 useGeneratedKeys 方式 * * @param sysUser * @return */ 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>
useGeneratedKeys="true" 后,当需要设置多个属性值时,使用逗号隔开,还需要设置 keyColumn属性,按顺序指定数据库的列 ,这里列的值会和 keyProperty配置的属性一一对应。
由于要使用数据库返回的主键值,所以 SQL 上下两部分的列中去掉了 id 列和对应的#{id}属性。
下面写一个测试,验证是否返回了SysUser的主键值,在测试类 UserMapperTest中添加如下代码。
@Test public void testInsert2(){ SqlSession sqlSession = getSqlSession(); try { UserMapper userMapper = sqlSession.getMapper(UserMapper.class); //创建一个 user 对象 SysUser user = new SysUser(); user.setUserName("test1"); user.setUserPassword("123456"); user.setUserEmail("test@mybatis.tk"); user.setUserInfo("test info"); 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(); } }
2.4.3 使用 selectKey 返回主键的值
有些数据库(如 Oracle)不提供主键自增的功能,而是使用序列得到一个值,然后将这个值赋给 id,再将数据插入数据库。对于这种情况,可以采用另外一种方式:使用 <selectKey> 标签来获取主键的值,这种方式不仅适用于不提供主键自增功能的数据库,也适用于提供主键自增功能的数据库。
先来看一下MySQL的例子。
在接口和XML中再新增一个insert3方法, UserMapper接口的代码如下。
/** * 新增用户 - 使用 selectKey 方式 * * @param sysUser * @return */ int insert3(SysUser sysUser);
不同之处在 UserMapper.xml 文件中的代码:新添加了<selectKey>标签。
<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>
注意看下面这段代码,和 insert2 相比增加了 selectKey 标签。
这里的 resultType 用于设置返回值类型。order 属性的设置和使用的数据库有关。在 MySQL 数据库中,order 属性设置的值是 AFTER,因为当前记录的主键值在 insert 语句执行成功后才能获取到。
而在 Oracle 数据库中,order 的值要设置为 BEFORE,这是因为 Oracle 中需要先从序列获取值,然后将值作为主键插入到数据库中。
selectKey元素放置的上、下位置不会影响最终执行的结果。这么写仅仅是为了符合实际的执行顺序,看起来更直观而已。
在 Oracle 示例中, SELECT SEQ _ ID.nextval from dual 是一个获取序列的 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.SYSDUMMY 1。 · INFORMIX 使用 select dbinfo ( 'sqlca.sqlerrd 1') from systables where tabid= 1。
2.5 update 用法
先来看一个简单的通过主键更新数据的 update 方法的例子。在 UserMapper 接口中添加以下方法。
1 /** 2 * 根据主键更新 3 * 4 * @param sysUser 5 * @return 6 * */ 7 int updateById(SysUser sysUser); //这里的参数 sysUser是要更新的数据
在接口对应的 UserMapper.xml中添加如下代码。
<update id="updateById"> update sys_user set user_name = #{userName}, user_password = #{userPassword}, user_email = #{userEmail}, user_info = #{userInfo}, head_img = #{headImg, jdbcType=BLOB}, create_time = #{createTime, jdbcType=TIMESTAMP} where id = #{id} </update>
这个方法的 SQL 很简单,下面写一个简单的测试来验证一下。在 UserMapperTest 中添加如下代码。
@Test public void testUpdateById(){ SqlSession sqlSession = getSqlSession(); try { UserMapper userMapper = sqlSession.getMapper(UserMapper.class); //从数据库查询 1 个 user 对象 SysUser user = userMapper.selectById(1L); //当前 userName 为 admin Assert.assertEquals("admin", user.getUserName()); //修改用户名 user.setUserName("admin_test"); //修改邮箱 user.setUserEmail("test@mybatis.tk"); //更新数据,特别注意,这里的返回值 result 是执行的 SQL 影响的行数 int result = userMapper.updateById(user); //只更新 1 条数据 Assert.assertEquals(1, result); //根据当前 id 查询修改后的数据 user = userMapper.selectById(1L); //修改后的名字 admin_test Assert.assertEquals("admin_test", user.getUserName()); } finally { //为了不影响数据库中的数据导致其他测试失败,这里选择回滚 //由于默认的 sqlSessionFactory.openSession() 是不自动提交的, //因此不手动执行 commit 也不会提交到数据库 sqlSession.rollback(); //不要忘记关闭 sqlSession sqlSession.close(); } }
还可以通过修改 UPDATE 语句中的 WHERE 条件来更新一条或一批数据。基本的 update 用法就这么简单,更复杂的情况在后面的动态 SQL 章节中会进行讲解。
2.6 delete用法
delete 同 update 类似,下面也用一个简单的例子说明。在 UserMapper 中添加一个简单的例子,代码如下。
/** * 通过主键删除 * * @param id * @return */ int deleteById(Long id);
根据主键删除数据的时候,如果主键只有一个字段,那么就可以和这个方法一样使用一个参数id ,这个方法对应UserMapper.xml中的代码如下。
<delete id="deleteById"> delete from sys_user where id = #{id } </delete>
注意接口中 int deleteById (Long id);方法的参数类型为 Long id,如果将参数类型修改如下,也是正确的。
对于以上的接口,在 UserMapperTest中编写一个测试方法,代码如下。
@Test public void testDeleteById(){ SqlSession sqlSession = getSqlSession(); try { UserMapper userMapper = sqlSession.getMapper(UserMapper.class); //从数据库查询 1 个 user 对象,根据 id = 1 查询 SysUser user1 = userMapper.selectById( 1L); //现在还能查询出 user 对象 Assert.assertNotNull(user1); //调用方法删除 Assert.assertEquals(1, userMapper.deleteById(user1)); //再次查询,这时应该没有值,为 null Assert.assertNull(userMapper.selectById(1L)); /* //使用 SysUser 参数再做一遍测试,根据 id = 1001 查询 SysUser user2 = userMapper.selectById(1001L); //现在还能查询出 user 对象 Assert.assertNotNull(user2); //调用方法删除,注意这里使用参数为 user2 Assert.assertEquals(1, userMapper.deleteById(user2)); //再次查询,这时应该没有值,为 null Assert.assertNull(userMapper.selectById(1001L)); //使用 SysUser 参数再做一遍测试*/ } finally { //为了不影响数据库中的数据导致其他测试失败,这里选择回滚 //由于默认的 sqlSessionFactory.openSession() 是不自动提交的, //因此不手动执行 commit 也不会提交到数据库 sqlSession.rollback(); //不要忘记关闭 sqlSession sqlSession.close(); } }
下面给出deleteById方法输出的操作日志。
以上是一个简单的 delete 方法示例,在动态 SQL 章节中还会介绍一些更复杂的用法。
2.7 多个接口参数的用法
上述CURD案例中的方法的参数只有一个:一种是基本类型,另一种是JavaBean。
在实际应用中经常会遇到使用多个参数的情况。前面几节的例子中,我们将多个参数合并到一个 JavaBean 中,并使用这个 JavaBean 作为接口方法的参数。这种方法用起来很方便,但并不适合全部的情况,因为不能只为了两三个参数去创建新的 JavaBean 类,因此对于参数比较少的情况,还有两种方式可以采用:使用 Map 类型作为参数或使用@Param 注解。
现在,在接口方法的参数前添加 @Param注解,代码如下。
/** * 根据用户 id 和 角色的 enabled 状态获取用户的角色 * * @param userId * @param enabled * @return */ List<SysRole> selectRolesByUserIdAndRoleEnabled( @Param("userId")Long userId, @Param("enabled")Integer enabled );
这个接口方法对应的 UserMapper.xml中的代码如下。
<select id="selectRolesByUserIdAndRoleEnabled" resultType="SysRole"> select r.id, r.role_name roleName, r.enabled, r.create_by createBy, r.create_time createTime from sys_user u inner join sys_user_role ur on u.id = ur.user_id inner join sys_role r on ur.role_id = r.id where u.id = #{userId} and r.enabled = #{enabled} </select>
在 UserMapperTest 中添加如下代码进行测试。
@Test public void testSelectRolesByUserIdAndRoleEnabled(){ SqlSession sqlSession = getSqlSession(); try { UserMapper userMapper = sqlSession.getMapper(UserMapper.class); //调用 selectRolesByUserIdAndRoleEnabled 方法查询用户的角色 List<SysRole> roleList = userMapper.selectRolesByUserIdAndRoleEnabled(1L, null); //结果不为空 Assert.assertNotNull(roleList); //角色数量大于 0 个 Assert.assertTrue(roleList.size() > 0); } finally { //不要忘记关闭 sqlSession sqlSession.close(); } }
/** * 根据用户 id 和 角色的 enabled 状态获取用户的角色 * * @param user * @param role * @return */ List<SysRole> selectRolesByUserIdAndRoleEnabledBean( @Param("user") SysUser user, @Param("role") SysRole role );
<select id="selectRolesByUserIdAndRoleEnabledBean" resultType="SysRole"> select r.id, r.role_name roleName, r.enabled, r.create_by createBy, r.create_time createTime from sys_user u inner join sys_user_role ur on u.id = ur.user_id inner join sys_role r on ur.role_id = r.id where u.id = #{user.id} and r.enabled = #{role.enabled} </select>
@Test public void testSelectRolesByUserIdAndRoleEnabledBean(){ SqlSession sqlSession = getSqlSession(); try { UserMapper userMapper = sqlSession.getMapper(UserMapper.class); //调用 selectRolesByUserIdAndRoleEnabled 方法查询用户的角色 SysUser user = new SysUser(); user.setId(1L); SysRole role = new SysRole(); role.setEnabled(1); List<SysRole> roleList = userMapper.selectRolesByUserIdAndRoleEnabledBean(user, role); //结果不为空 Assert.assertNotNull(roleList); //角色数量大于 0 个 Assert.assertTrue(roleList.size() > 0); } finally { //不要忘记关闭 sqlSession sqlSession.close(); } }
=================================================================================================
end