美文网首页MyBatis修炼MyBatis我爱编程
【MyBatis】 MyBatis修炼之五 MyBatis

【MyBatis】 MyBatis修炼之五 MyBatis

作者: 开心跳蚤 | 来源:发表于2017-08-15 15:20 被阅读227次

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

    相关文章

      网友评论

      本文标题:【MyBatis】 MyBatis修炼之五 MyBatis

      本文链接:https://www.haomeiwen.com/subject/ussurxtx.html