飞道的博客

Mybatis系列第6篇:恕我直言,mybatis增删改你未必玩得转!

304人阅读  评论(0)

Mybatis系列目标:从入门开始开始掌握一个高级开发所需要的Mybatis技能。

这是mybatis系列第6篇。

主要内容

  • 建库建表

  • mybatis增删改返回值说明及源码解析

  • jdbc获取自增值的3种方式详解

  • mybatis获取自增值的3种方式详解

建库建表


   
  1. /*创建数据库javacode2018*/
  2. DROP DATABASE IF EXISTS  `javacode2018`;
  3. CREATE DATABASE  `javacode2018`;
  4. USE  `javacode2018`;
  5. /*创建表结构*/
  6. DROP TABLE IF EXISTS  `t_user`;
  7. CREATE TABLE t_user (
  8.   id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT  '主键,用户id,自动增长',
  9.    `name` VARCHAR( 32) NOT NULL DEFAULT  '' COMMENT '姓名 ',
  10.   `age` SMALLINT NOT NULL DEFAULT 1 COMMENT '年龄 ',
  11.   `salary` DECIMAL(12,2) NOT NULL DEFAULT 0 COMMENT '薪水 ',
  12.   `sex` TINYINT NOT NULL DEFAULT 0 COMMENT '性别, 0:未知, 1:男, 2:女 '
  13. ) COMMENT '用户表 ';
  14. SELECT * FROM t_user;

增删改返回值说明

mybatis中对db执行增删改操作,不管是新增、删除、还是修改,最后都会去调用jdbc中对应的方法,要么是调用java.sql.StatementexecuteUpdate的方法,要么是调用java.sql.PreparedStatementexecuteUpdate方法,这2个类的方法名称都是executeUpdate,他们的参数可能不一样,但是他们的返回值都是int,说明增删改的返回值都是int类型的,表示影响的行数,比如插入成功1行返回结果就是1,删除了10行记录,返回就是10,更新了5行记录,返回的就是5。

那么我们通过Mybatis中的Mapper接口来对db增删改的时候,mybatis的返回值支持哪些类型呢?

int类型那肯定是支持的,jdbc执行增删改默认返回int类型,那mybatis当然也支持这个类型。

但是mybatis的返回值比jdbc更强大,对于增删改还支持下面几种类型:


   
  1. int
  2. Integer
  3. long 
  4. Long
  5. boolean
  6. Boolean
  7. void

mapper的增删改方法返回值必须为上面的类型,mybatis内部将jdbc返回的int类型转换为上面列表中指定的类型,我们来看一下mybatis这块的源码,源码在下面的方法中:

org.apache.ibatis.binding.MapperMethod#rowCountResult

我们来看一下这个方法的源码:


   
  1. private Object rowCountResult( int rowCount) {
  2.     final Object result;
  3.      if (method.returnsVoid()) {
  4.       result = null;
  5.     }  else  if (Integer.class.equals(method.getReturnType()) || Integer.TYPE.equals(method.getReturnType())) {
  6.       result = rowCount;
  7.     }  else  if (Long.class.equals(method.getReturnType()) || Long.TYPE.equals(method.getReturnType())) {
  8.       result = (long)rowCount;
  9.     }  else  if (Boolean.class.equals(method.getReturnType()) || Boolean.TYPE.equals(method.getReturnType())) {
  10.       result = rowCount >  0;
  11.     }  else {
  12.       throw  new BindingException( "Mapper method '" + command.getName() +  "' has an unsupported return type: " + method.getReturnType());
  13.     }
  14.      return result;
  15.   }

mybatis中会使用上面这个方法最后会对jdbc 增删改返回的int结果进行处理,处理为mapper接口中增删改方法返回值的类型。

int、Integer、long、Long我们就不说了,主要说一下返回值是boolean、Boolean类型,如果影响的行数大于0了,将返回true。

下面我们来创建一个工程感受一下增删改各种返回值。

创建案例

整个mybatis系列的代码采用maven模块的方式管理的,可以在文章底部获取,本次我们还是在上一篇的mybatis-series中进行开发,在这个项目中新建一个模块chat04,模块坐标如下:


   
  1. <groupId>com.javacode2018</groupId>
  2. <artifactId>chat04</artifactId>
  3. <version> 1.0-SNAPSHOT</version>

下面我们通过mybatis快速来实现对t_user表增删改。

创建UserModel类

mybatis-series\chat04\src\main\java\com\javacode2018\chat04\demo1\model目录创建UserModel.java,如下:


   
  1. package com.javacode2018.chat04.demo1.model;
  2. import lombok.*;
  3. /**
  4.  * 公众号:路人甲Java,工作10年的前阿里P7分享Java、算法、数据库方面的技术干货!坚信用技术改变命运,让家人过上更体面的生活!
  5.  */
  6. @Getter
  7. @Setter
  8. @NoArgsConstructor
  9. @AllArgsConstructor
  10. @Builder
  11. @ToString
  12. public class UserModel {
  13.     private Long id;
  14.     private String name;
  15.     private Integer age;
  16.     private Double salary;
  17.     private Integer sex;
  18. }

创建UserMapper接口

mybatis-series\chat04\src\main\java\com\javacode2018\chat04\demo1\mapper目录创建UserMapper.java,如下:


   
  1. package com.javacode2018.chat04.demo1.mapper;
  2. import com.javacode2018.chat04.demo1.model.UserModel;
  3. /**
  4.  * 公众号:路人甲Java,工作10年的前阿里P7分享Java、算法、数据库方面的技术干货!坚信用技术改变命运,让家人过上更体面的生活!
  5.  */
  6. public  interface UserMapper {
  7.      /**
  8.      * 插入用户信息,返回影响行数
  9.      *
  10.      * @param model
  11.      * @return
  12.      */
  13.      int insertUser(UserModel model);
  14.      /**
  15.      * 更新用户信息,返回影响行数
  16.      *
  17.      * @param model
  18.      * @return
  19.      */
  20.     long updateUser(UserModel model);
  21.      /**
  22.      * 根据用户id删除用户信息,返回删除是否成功
  23.      *
  24.      * @param userId
  25.      * @return
  26.      */
  27.     boolean deleteUser(Long userId);
  28. }

注意上面3个操作的返回类型,我们体验一下int、long、boolean类型的返回值。

创建UserMapper.xml文件

mybatis-series\chat04\src\main\resources\demo1目录创建,UserMapper.xml,如下:


   
  1. <?xml version= "1.0" encoding= "UTF-8" ?>
  2. <!DOCTYPE mapper PUBLIC  "-//mybatis.org//DTD Mapper 3.0//EN"
  3.          "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  4. <mapper namespace= "com.javacode2018.chat04.demo1.mapper.UserMapper">
  5.     <insert id= "insertUser" parameterType= "com.javacode2018.chat04.demo1.model.UserModel">
  6.         <![CDATA[
  7.         INSERT INTO t_user (id,name,age,salary,sex) VALUES (#{id},#{name},#{age},#{salary},#{sex})
  8.          ]]>
  9.     </insert>
  10.     <update id= "updateUser" parameterType= "com.javacode2018.chat04.demo1.model.UserModel">
  11.         <![CDATA[
  12.         UPDATE t_user SET name = #{name},age = #{age},salary = #{salary},sex = #{sex} WHERE id = #{id}
  13.         ]]>
  14.     </update>
  15.     <update id= "deleteUser" parameterType= "java.lang.Long">
  16.         <![CDATA[
  17.         DELETE FROM t_user WHERE id = #{id}
  18.         ]]>
  19.     </update>
  20. </mapper>

创建属性配置文件

mybatis-series\chat04\src\main\resources目录中创建jdbc.properties,如下:


   
  1. jdbc.driver=com.mysql.jdbc.Driver
  2. jdbc.url=jdbc:mysql: //localhost:3306/javacode2018?characterEncoding=UTF-8
  3. jdbc.username=root
  4. jdbc.password=root123

创建mybatis全局配置文件

mybatis-series\chat04\src\main\resources\demo1目录创建,mybatis-config.xml,如下:


   
  1. <?xml version= "1.0" encoding= "UTF-8" ?>
  2. <!DOCTYPE configuration
  3.         PUBLIC  "-//mybatis.org//DTD Config 3.0//EN"
  4.          "http://mybatis.org/dtd/mybatis-3-config.dtd">
  5. <configuration>
  6.     <!-- 引入外部jdbc配置 -->
  7.     <properties resource= "jdbc.properties"/>
  8.     <!-- 环境配置,可以配置多个环境 -->
  9.     <environments  default= "demo4">
  10.         <environment id= "demo4">
  11.             <!-- 事务管理器工厂配置 -->
  12.             <transactionManager  type= "JDBC"/>
  13.             <!-- 数据源工厂配置,使用工厂来创建数据源 -->
  14.             <dataSource  type= "POOLED">
  15.                 <property name= "driver" value= "${jdbc.driver}"/>
  16.                 <property name= "url" value= "${jdbc.url}"/>
  17.                 <property name= "username" value= "${jdbc.username}"/>
  18.                 <property name= "password" value= "${jdbc.password}"/>
  19.             </dataSource>
  20.         </environment>
  21.     </environments>
  22.     <mappers>
  23.         <mapper resource= "demo1/UserMapper.xml" />
  24.     </mappers>
  25. </configuration>

引入logback日志支持

chat04\src\main\resources目录创建logback.xml,如下:


   
  1. <?xml version= "1.0" encoding= "UTF-8"?>
  2. <configuration>
  3.     <appender name= "STDOUT" class= "ch.qos.logback.core.ConsoleAppender">
  4.         <encoder>
  5.             <pattern>%d{mm:ss.SSS} [%thread] % -5level %logger{ 36} - %msg%n</pattern>
  6.         </encoder>
  7.     </appender>
  8.     <logger name= "com.javacode2018" level= "debug" additivity= "false">
  9.         <appender-ref ref= "STDOUT" />
  10.     </logger>
  11. </configuration>

创建测试用例Demo1Test

mybatis-series\chat04\src\test\java\com\javacode2018\chat04目录创建Demo1Test.java,如下:


   
  1. package com.javacode2018.chat04;
  2. import com.javacode2018.chat04.demo1.mapper.UserMapper;
  3. import com.javacode2018.chat04.demo1.model.UserModel;
  4. import lombok.extern.slf4j.Slf4j;
  5. import org.apache.ibatis.io.Resources;
  6. import org.apache.ibatis.session.SqlSession;
  7. import org.apache.ibatis.session.SqlSessionFactory;
  8. import org.apache.ibatis.session.SqlSessionFactoryBuilder;
  9. import org.junit.Before;
  10. import org.junit.Test;
  11. import java.io.IOException;
  12. import java.io.InputStream;
  13. /**
  14.  * 公众号:路人甲Java,工作10年的前阿里P7分享Java、算法、数据库方面的技术干货!坚信用技术改变命运,让家人过上更体面的生活!
  15.  */
  16. @Slf4j
  17. public class Demo1Test {
  18.     private SqlSessionFactory sqlSessionFactory;
  19.     @Before
  20.     public void before() throws IOException {
  21.          //指定mybatis全局配置文件
  22.         String resource =  "demo1/mybatis-config.xml";
  23.          //读取全局配置文件
  24.         InputStream inputStream = Resources.getResourceAsStream(resource);
  25.          //构建SqlSessionFactory对象
  26.         SqlSessionFactory sqlSessionFactory =  new SqlSessionFactoryBuilder().build(inputStream);
  27.         this.sqlSessionFactory = sqlSessionFactory;
  28.     }
  29.     @Test
  30.     public void insertUser() {
  31.         try (SqlSession sqlSession = this.sqlSessionFactory.openSession( true);) {
  32.             UserMapper mapper = sqlSession.getMapper(UserMapper.class);
  33.              //创建UserModel对象
  34.             UserModel userModel = UserModel.builder().id( 1L).name( "路人甲Java").age( 30).salary( 50000D).sex( 1).build();
  35.              //执行插入操作
  36.              int insert = mapper.insertUser(userModel);
  37.             log.info( "影响行数:{}", insert);
  38.         }
  39.     }
  40.     @Test
  41.     public void updateUser() {
  42.         try (SqlSession sqlSession = this.sqlSessionFactory.openSession( true);) {
  43.             UserMapper mapper = sqlSession.getMapper(UserMapper.class);
  44.              //创建UserModel对象
  45.             UserModel userModel = UserModel.builder().id( 1L).name( "路人甲Java,你好").age( 18).salary( 5000D).sex( 0).build();
  46.              //执行更新操作
  47.             long result = mapper.updateUser(userModel);
  48.             log.info( "影响行数:{}", result);
  49.         }
  50.     }
  51.     @Test
  52.     public void deleteUser() {
  53.         try (SqlSession sqlSession = this.sqlSessionFactory.openSession( true);) {
  54.             UserMapper mapper = sqlSession.getMapper(UserMapper.class);
  55.              //定义需要删除的用户id
  56.             Long userId =  1L;
  57.              //执行删除操作
  58.             boolean result = mapper.deleteUser(userId);
  59.             log.info( "第1次删除:id={},返回值:{}", userId, result);
  60.             result = mapper.deleteUser(userId);
  61.             log.info( "第2次删除:id={},返回值:{}", userId, result);
  62.         }
  63.     }
  64. }

项目结构如下图

注意项目结构如下图,跑起来有问题的可以对照一下。

运行测试用例

测试int类型返回值

运行com.javacode2018.chat04.Demo1Test#insertUser,插入一条用户信息,输出如下:


   
  1. 16: 35.821 [main] DEBUG c.j.c.d.mapper.UserMapper.insertUser - ==>  Preparing: INSERT INTO t_user (id,name,age,salary,sex) VALUES (?,?,?,?,?) 
  2. 16: 35.858 [main] DEBUG c.j.c.d.mapper.UserMapper.insertUser - ==> Parameters:  1(Long), 路人甲Java(String),  30(Integer),  50000.0(Double),  1(Integer)
  3. 16: 35.865 [main] DEBUG c.j.c.d.mapper.UserMapper.insertUser - <==    Updates:  1
  4. 16: 35.865 [main] INFO com.javacode2018.chat04.Demo1Test - 影响行数: 1
测试long类型返回值

运行com.javacode2018.chat04.Demo1Test#updateUser,通过用户id更新用户信息,输出如下:


   
  1. 17: 49.084 [main] DEBUG c.j.c.d.mapper.UserMapper.updateUser - ==>  Preparing: UPDATE t_user SET name = ?,age = ?,salary = ?,sex = ? WHERE id = ? 
  2. 17: 49.127 [main] DEBUG c.j.c.d.mapper.UserMapper.updateUser - ==> Parameters: 路人甲Java,你好(String),  18(Integer),  5000.0(Double),  0(Integer),  1(Long)
  3. 17: 49.135 [main] DEBUG c.j.c.d.mapper.UserMapper.updateUser - <==    Updates:  1
  4. 17: 49.135 [main] INFO com.javacode2018.chat04.Demo1Test - 影响行数: 1
测试boolean类型返回值

运行com.javacode2018.chat04.Demo1Test#deleteUser,根据用户id删除用户信息,删除2次,输出如下:


   
  1. 20: 37.745 [main] DEBUG c.j.c.d.mapper.UserMapper.deleteUser - ==>  Preparing: DELETE FROM t_user WHERE id = ? 
  2. 20: 37.785 [main] DEBUG c.j.c.d.mapper.UserMapper.deleteUser - ==> Parameters:  1(Long)
  3. 20: 37.790 [main] DEBUG c.j.c.d.mapper.UserMapper.deleteUser - <==    Updates:  0
  4. 20: 37.791 [main] INFO  com.javacode2018.chat04.Demo1Test - 第 1次删除:id= 1,返回值: false
  5. 20: 37.793 [main] DEBUG c.j.c.d.mapper.UserMapper.deleteUser - ==>  Preparing: DELETE FROM t_user WHERE id = ? 
  6. 20: 37.794 [main] DEBUG c.j.c.d.mapper.UserMapper.deleteUser - ==> Parameters:  1(Long)
  7. 20: 37.795 [main] DEBUG c.j.c.d.mapper.UserMapper.deleteUser - <==    Updates:  0
  8. 20: 37.795 [main] INFO  com.javacode2018.chat04.Demo1Test - 第 2次删除:id= 1,返回值: false

第一次删除成功,再次删除数据已经不存在了,返回false

jdbc获取主键的几种方式

上面的案例中inserUser会向t_user表插入数据,t_user表的id是自动增长的,插入数据的时候我们不指定id的值,看看插入成功之后userModel对象和db中插入的记录是什么样的。

com.javacode2018.chat04.Demo1Test#insertUser代码改成下面这样:


   
  1. @Test
  2. public void insertUser() {
  3.     try (SqlSession sqlSession = this.sqlSessionFactory.openSession( true);) {
  4.         UserMapper mapper = sqlSession.getMapper(UserMapper.class);
  5.          //创建UserModel对象
  6.         UserModel userModel = UserModel.builder().name( "郭富城").age( 30).salary( 50000D).sex( 1).build();
  7.          //执行插入操作
  8.          int insert = mapper.insertUser(userModel);
  9.         log.info( "影响行数:{}", insert);
  10.         log.info( "{}", userModel);
  11.     }
  12. }

执行一下,输出:


   
  1. 36: 10.673 [main] DEBUG c.j.c.d.mapper.UserMapper.insertUser - ==>  Preparing: INSERT INTO t_user (id,name,age,salary,sex) VALUES (?,?,?,?,?) 
  2. 36: 10.715 [main] DEBUG c.j.c.d.mapper.UserMapper.insertUser - ==> Parameters: null, 郭富城(String),  30(Integer),  50000.0(Double),  1(Integer)
  3. 36: 10.721 [main] DEBUG c.j.c.d.mapper.UserMapper.insertUser - <==    Updates:  1
  4. 36: 10.722 [main] INFO com.javacode2018.chat04.Demo1Test - 影响行数: 1
  5. 36: 10.723 [main] INFO  com.javacode2018.chat04.Demo1Test - UserModel(id=null, name=郭富城, age= 30, salary= 50000.0, sex= 1)

输出中插入成功1行,最后一行日志中输出了userModel对象所有属性信息,id是null的,我们去db中看一下这条记录:


   
  1. mysql> SELECT * FROM t_user;
  2. +----+-----------+-----+----------+-----+
  3. | id | name      | age | salary   | sex |
  4. +----+-----------+-----+----------+-----+
  5. |   2 | 郭富城    |   30 |  50000.00 |    1 |
  6. +----+-----------+-----+----------+-----+
  7. 1 row in set ( 0.00 sec)

db中插入的这条郭富城的id是2,当我们没有指定id,或者指定的id为null的时候,mysql会自动生成id的值。

那么我们如何mysql中获取这个自动增长的值呢?我们先看看jdbc是如何实现的

方式1:jdbc内置的方式

用法

jdbc的api中为我们提供了获取自动生成主键的值,具体看这个方法:

java.sql.Statement#getGeneratedKeys

看一下这个方法的定义:


   
  1. /**
  2. * Retrieves any auto-generated keys created as a result of executing this
  3. * <code>Statement</code> object. If this <code>Statement</code> object did
  4. * not generate any keys, an empty <code>ResultSet</code>
  5. * object is returned.
  6. *
  7. *<p><B>Note:</B>If the columns which represent the auto-generated keys were not specified,
  8. * the JDBC driver implementation will determine the columns which best represent the auto-generated keys.
  9. *
  10. * @return a <code>ResultSet</code> object containing the auto-generated key(s)
  11. *         generated by the execution of this <code>Statement</code> object
  12. * @exception SQLException if a database access error occurs or
  13. * this method is called on a closed <code>Statement</code>
  14. * @throws SQLFeatureNotSupportedException  if the JDBC driver does not support this method
  15. * @since 1.4
  16. */
  17. ResultSet getGeneratedKeys() throws SQLException;

这个方法会返回一个结果集,从这个结果集中可以获取自增主键的值。

不过使用这个方法有个前提,执行sql的时候需要做一个设置。

如果是通过java.sql.Statement执行sql,需要调用下面这个方法:

int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException

注意上面这个方法的第二个参数需要设置为java.sql.Statement.RETURN_GENERATED_KEYS,表示需要返回自增列的值。

不过多数情况下,我们会使用java.sql.PreparedStatement对象来执行sql,如果想获取自增值,创建这个对象需要设置第2个参数的值,如下:

PreparedStatement preparedStatement = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);

然后我们就可以通过getGeneratedKeys返回的ResultSet对象获取自动增长的值了,如下:


   
  1. ResultSet generatedKeys = preparedStatement.getGeneratedKeys();
  2. if (generatedKeys!=null && generatedKeys.next()) {
  3.     log.info( "自增值为:{}", generatedKeys.getInt( 1));
  4. }
案例

com.javacode2018.chat04.Demo1Test中新增一个测试用例,如下代码:


   
  1. private String jdbcDriver =  "com.mysql.jdbc.Driver";
  2. private String jdbcUrl =  "jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8";
  3. private String jdbcUserName =  "root";
  4. private String jdbcPassword =  "root123";
  5. @Test
  6. public void jdbcInsertUser1() throws Exception {
  7.     Connection connection = null;
  8.     PreparedStatement preparedStatement = null;
  9.     ResultSet generatedKeys = null;
  10.     try {
  11.         UserModel userModel = UserModel.builder().name( "黎明").age( 30).salary( 50000D).sex( 1).build();
  12.          //执行jdbc插入数据操作
  13.         Class.forName(jdbcDriver);
  14.         connection = DriverManager.getConnection(jdbcUrl, jdbcUserName, jdbcPassword);
  15.          //注意创建PreparedStatement的时候,使用prepareStatement方法的第二个参数需要指定Statement.RETURN_GENERATED_KEYS
  16.         preparedStatement = connection.prepareStatement( "INSERT INTO t_user (name,age,salary,sex) VALUES (?,?,?,?)", Statement.RETURN_GENERATED_KEYS);
  17.          int parameterIndex =  1;
  18.         preparedStatement.setString(parameterIndex++, userModel.getName());
  19.         preparedStatement.setInt(parameterIndex++, userModel.getAge());
  20.         preparedStatement.setDouble(parameterIndex++, userModel.getSalary());
  21.         preparedStatement.setInt(parameterIndex++, userModel.getSex());
  22.          int count = preparedStatement.executeUpdate();
  23.         log.info( "影响行数:{}", count);
  24.          //获取自增值
  25.         generatedKeys = preparedStatement.getGeneratedKeys();
  26.          if (generatedKeys != null && generatedKeys.next()) {
  27.             log.info( "自增值为:{}", generatedKeys.getInt( 1));
  28.         }
  29.     } finally {
  30.          if (generatedKeys != null && generatedKeys.isClosed()) {
  31.             generatedKeys. close();
  32.         }
  33.          if (preparedStatement != null && preparedStatement.isClosed()) {
  34.             preparedStatement. close();
  35.         }
  36.          if (connection != null && connection.isClosed()) {
  37.             connection. close();
  38.         }
  39.     }
  40. }

上面代码中我们插入了一条用户的信息,没有指定用户的id,执行输出:


   
  1. 21: 22.410 [main] INFO com.javacode2018.chat04.Demo1Test - 影响行数: 1
  2. 21: 22.414 [main] INFO com.javacode2018.chat04.Demo1Test - 自增值为: 5

我们去db中看一下这个记录的id,如下,确实是5:


   
  1. mysql> SELECT * FROM t_user;
  2. +----+--------+-----+----------+-----+
  3. | id | name   | age | salary   | sex |
  4. +----+--------+-----+----------+-----+
  5. |   5 | 黎明   |   30 |  50000.00 |    1 |
  6. +----+--------+-----+----------+-----+
  7. 1 row in set ( 0.00 sec)

方式2:插入之后查询获取

用法

mysql中插入一条数据之后,可以通过下面的sql获取最新插入记录的id的值:

SELECT LAST_INSERT_ID()

那么我们可以在插入之后,立即使用当前连接发送上面这条sql去获取自增列的值就可以。

案例

创建测试用例com.javacode2018.chat04.Demo1Test#jdbcInsertUser2,代码如下:


   
  1. @Test
  2. public void jdbcInsertUser2() throws Exception {
  3.     Connection connection = null;
  4.     PreparedStatement preparedStatement = null;
  5.     ResultSet rs = null;
  6.     try {
  7.         UserModel userModel = UserModel.builder().name( "梁朝伟").age( 30).salary( 50000D).sex( 1).build();
  8.          //执行jdbc插入数据操作
  9.         Class.forName(jdbcDriver);
  10.         connection = DriverManager.getConnection(jdbcUrl, jdbcUserName, jdbcPassword);
  11.          //注意创建PreparedStatement的时候,使用prepareStatement方法的第二个参数需要指定Statement.RETURN_GENERATED_KEYS
  12.         preparedStatement = connection.prepareStatement( "INSERT INTO t_user (name,age,salary,sex) VALUES (?,?,?,?)", Statement.RETURN_GENERATED_KEYS);
  13.          int parameterIndex =  1;
  14.         preparedStatement.setString(parameterIndex++, userModel.getName());
  15.         preparedStatement.setInt(parameterIndex++, userModel.getAge());
  16.         preparedStatement.setDouble(parameterIndex++, userModel.getSalary());
  17.         preparedStatement.setInt(parameterIndex++, userModel.getSex());
  18.          int count = preparedStatement.executeUpdate();
  19.         log.info( "影响行数:{}", count);
  20.          //通过查询获取自增值
  21.         rs = connection.prepareStatement( "SELECT LAST_INSERT_ID()").executeQuery();
  22.          if (rs != null && rs.next()) {
  23.             log.info( "自增值为:{}", rs.getInt( 1));
  24.         }
  25.     } finally {
  26.          if (rs != null && rs.isClosed()) {
  27.             rs. close();
  28.         }
  29.          if (preparedStatement != null && preparedStatement.isClosed()) {
  30.             preparedStatement. close();
  31.         }
  32.          if (connection != null && connection.isClosed()) {
  33.             connection. close();
  34.         }
  35.     }
  36. }

运行输出:


   
  1. 26: 55.407 [main] INFO com.javacode2018.chat04.Demo1Test - 影响行数: 1
  2. 26: 55.414 [main] INFO com.javacode2018.chat04.Demo1Test - 自增值为: 6

db中我们去看一下,梁朝伟的id是6,如下:


   
  1. mysql> SELECT * FROM t_user;
  2. +----+-----------+-----+----------+-----+
  3. | id | name      | age | salary   | sex |
  4. +----+-----------+-----+----------+-----+
  5. |   5 | 黎明      |   30 |  50000.00 |    1 |
  6. |   6 | 梁朝伟    |   30 |  50000.00 |    1 |
  7. +----+-----------+-----+----------+-----+
  8. 2 rows in set ( 0.00 sec)

方式3:插入之前获取

oracle不知道大家有没有玩过,oracle中没有mysql中自动增长列,但是oracle有个功能可以实现自动增长,这个功能就是序列,序列就相当于一个自增器一样,有个初始值,每次递增的步长,当然这个序列提供了一些功能给我们使用,可以获取序列的当前值、下一个值,使用方式如下:


   
  1. 1.先定义一个序列
  2. 2.获取下一个值:SELECT 序列名.NEXTVAL FROM dual;

这个案例我只说一下具体步骤,代码就不写了,步骤:


   
  1. 1.通过jdbc执行 `SELECT 序列名.NEXTVAL FROM dual`获取序列的下一个值,如nextId
  2. 2.在代码中使用nextId的值

上面就是jdbc获取值增值的几种方式,jdbc中的这3中方式,mybatis中都提供了对应的 支持,下面我们来看mybatis中是如何实现的。

mybatis获取主键的3种方式

方式1:内部使用jdbc内置的方式

用法

mybatis这个方式内部采用的是上面说的jdbc内置的方式。

我们需要在Mapper xml中进行配置,如:


   
  1. <insert id= "insertUser1" parameterType= "com.javacode2018.chat04.demo1.model.UserModel" useGeneratedKeys= "true" keyProperty= "id">
  2.     <![CDATA[
  3.     INSERT INTO t_user (name,age,salary,sex) VALUES (#{name},#{age},#{salary},#{sex})
  4.      ]]>
  5. </insert>

有2个关键参数必须要设置:

  • useGeneratedKeys:设置为true

  • keyProperty:参数对象中的属性名称,最后插入成功之后,mybatis会通过反射将自增值设置给keyProperty指定的这个属性

案例

mybatis-series\chat04\src\main\resources\demo1\UserMapper.xml中新增代码:


   
  1. <!-- 插入的时候获取值增值,必须需指定 2个属性
  2.     useGeneratedKeys:设置为 true
  3.     keyProperty:参数对象中的属性,插入成功之后会将值增值设置给这个属性
  4.  -->
  5. <insert id= "insertUser1" parameterType= "com.javacode2018.chat04.demo1.model.UserModel" useGeneratedKeys= "true" keyProperty= "id">
  6.     <![CDATA[
  7.     INSERT INTO t_user (name,age,salary,sex) VALUES (#{name},#{age},#{salary},#{sex})
  8.      ]]>
  9. </insert>

Mapper接口中也新增代码,com.javacode2018.chat04.demo1.mapper.UserMapper中新增一个方法,如下:

int insertUser1(UserModel userModel);

创建测试用例方法com.javacode2018.chat04.Demo1Test#insertUser1,如下:


   
  1. @Test
  2. public void insertUser1() {
  3.     try (SqlSession sqlSession = this.sqlSessionFactory.openSession( true);) {
  4.         UserMapper mapper = sqlSession.getMapper(UserMapper.class);
  5.          //创建UserModel对象
  6.         UserModel userModel = UserModel.builder().name( "陈宝国").age( 30).salary( 50000D).sex( 1).build();
  7.          //执行插入操作
  8.          int insert = mapper.insertUser1(userModel);
  9.         log.info( "影响行数:{}", insert);
  10.         log.info( "{}", userModel);
  11.     }
  12. }

注意上面的userModel对象,id没有设置值,运行输出:


   
  1. 59: 44.412 [main] DEBUG c.j.c.d.m.UserMapper.insertUser1 - ==>  Preparing: INSERT INTO t_user (name,age,salary,sex) VALUES (?,?,?,?) 
  2. 59: 44.444 [main] DEBUG c.j.c.d.m.UserMapper.insertUser1 - ==> Parameters: 陈宝国(String),  30(Integer),  50000.0(Double),  1(Integer)
  3. 59: 44.451 [main] DEBUG c.j.c.d.m.UserMapper.insertUser1 - <==    Updates:  1
  4. 59: 44.453 [main] INFO com.javacode2018.chat04.Demo1Test - 影响行数: 1
  5. 59: 44.455 [main] INFO  com.javacode2018.chat04.Demo1Test - UserModel(id= 8, name=陈宝国, age= 30, salary= 50000.0, sex= 1)

看上面最后一行输出,id的值为8,去db中看一下,如下:


   
  1. mysql> SELECT * FROM t_user;
  2. +----+-----------+-----+----------+-----+
  3. | id | name      | age | salary   | sex |
  4. +----+-----------+-----+----------+-----+
  5. |   5 | 黎明      |   30 |  50000.00 |    1 |
  6. |   6 | 梁朝伟    |   30 |  50000.00 |    1 |
  7. |   7 | 陈宝国    |   30 |  50000.00 |    1 |
  8. |   8 | 陈宝国    |   30 |  50000.00 |    1 |
  9. +----+-----------+-----+----------+-----+
  10. 4 rows in set ( 0.00 sec)

方式2:插入后查询获取主键

用法

这个方式和上面介绍的jdbc的第二种方式一样,插入之后通过查询获取主键的值然后填充给指定的属性,mapper xml配置如下:


   
  1. <insert id= "insertUser2" parameterType= "com.javacode2018.chat04.demo1.model.UserModel">
  2.     <selectKey keyProperty= "id" order= "AFTER" resultType= "long">
  3.     <![CDATA[
  4.     SELECT LAST_INSERT_ID()
  5.      ]]>
  6.     </selectKey>
  7.     <![CDATA[
  8.     INSERT INTO t_user (name,age,salary,sex) VALUES (#{name},#{age},#{salary},#{sex})
  9.      ]]>
  10. </insert>

关键代码是selectKey元素包含的部分,这个元素内部可以包含一个sql,这个sql可以在插入之前或者插入之后运行(之前还是之后通过order属性配置),然后会将sql运行的结果设置给keyProperty指定的属性,selectKey元素有3个属性需要指定:

  • keyProperty:参数对象中的属性名称,最后插入成功之后,mybatis会通过反射将自增值设置给keyProperty指定的这个属性

  • order:指定selectKey元素中的sql是在插入之前运行还是插入之后运行,可选值(BEFORE|AFTER),这种方式中我们选择AFTER

  • resultType:keyProperty指定的属性对应的类型,如上面的id对应的类型是java.lang.Long,我们直接写的是别名long

案例

mybatis-series\chat04\src\main\resources\demo1\UserMapper.xml中新增代码:


   
  1. <insert id= "insertUser2" parameterType= "com.javacode2018.chat04.demo1.model.UserModel">
  2.     <selectKey keyProperty= "id" order= "AFTER" resultType= "long">
  3.     <![CDATA[
  4.     SELECT LAST_INSERT_ID()
  5.      ]]>
  6.     </selectKey>
  7.     <![CDATA[
  8.     INSERT INTO t_user (name,age,salary,sex) VALUES (#{name},#{age},#{salary},#{sex})
  9.      ]]>
  10. </insert>

Mapper接口中也新增代码,com.javacode2018.chat04.demo1.mapper.UserMapper中新增一个方法,如下:

int insertUser2(UserModel userModel);

创建测试用例方法com.javacode2018.chat04.Demo1Test#insertUser2,如下:


   
  1. @Test
  2. public void insertUser2() {
  3.     try (SqlSession sqlSession = this.sqlSessionFactory.openSession( true);) {
  4.         UserMapper mapper = sqlSession.getMapper(UserMapper.class);
  5.          //创建UserModel对象
  6.         UserModel userModel = UserModel.builder().name( "周润发").age( 30).salary( 50000D).sex( 1).build();
  7.          //执行插入操作
  8.          int insert = mapper.insertUser2(userModel);
  9.         log.info( "影响行数:{}", insert);
  10.         log.info( "{}", userModel);
  11.     }
  12. }

注意上面的userModel对象,id没有设置值,运行输出:


   
  1. 22: 18.140 [main] DEBUG c.j.c.d.m.UserMapper.insertUser2 - ==>  Preparing: INSERT INTO t_user (name,age,salary,sex) VALUES (?,?,?,?) 
  2. 22: 18.173 [main] DEBUG c.j.c.d.m.UserMapper.insertUser2 - ==> Parameters: 周润发(String),  30(Integer),  50000.0(Double),  1(Integer)
  3. 22: 18.180 [main] DEBUG c.j.c.d.m.UserMapper.insertUser2 - <==    Updates:  1
  4. 22: 18.183 [main] DEBUG c.j.c.d.m.U.insertUser2!selectKey - ==>  Preparing: SELECT LAST_INSERT_ID() 
  5. 22: 18.183 [main] DEBUG c.j.c.d.m.U.insertUser2!selectKey - ==> Parameters: 
  6. 22: 18.197 [main] DEBUG c.j.c.d.m.U.insertUser2!selectKey - <==      Total:  1
  7. 22: 18.198 [main] INFO com.javacode2018.chat04.Demo1Test - 影响行数: 1
  8. 22: 18.200 [main] INFO  com.javacode2018.chat04.Demo1Test - UserModel(id= 11, name=周润发, age= 30, salary= 50000.0, sex= 1)

上面输出中执行了2条sql,先执行的插入,然后执行了一个查询获取自增值id,最后一行输出的id为11.

去db中看一下,如下:


   
  1. mysql> SELECT * FROM t_user order by id desc limit  1;
  2. +----+-----------+-----+----------+-----+
  3. | id | name      | age | salary   | sex |
  4. +----+-----------+-----+----------+-----+
  5. 11 | 周润发    |   30 |  50000.00 |    1 |
  6. +----+-----------+-----+----------+-----+
  7. 1 row in set ( 0.00 sec)

方式2:插入前查询获取主键

用法

这个方式和上面介绍的jdbc的第3种方式一样,会在插入之前先通过一个查询获取主键的值然后填充给指定的属性,然后在执行插入,mapper xml配置如下:


   
  1. <insert id= "insertUser3" parameterType= "com.javacode2018.chat04.demo1.model.UserModel">
  2.     <selectKey keyProperty= "id" order= "BEFORE" resultType= "long">
  3.     <![CDATA[ 获取主键的 select语句 ]]>
  4.     </selectKey>
  5.     <![CDATA[
  6.     INSERT INTO t_user (name,age,salary,sex) VALUES (#{name},#{age},#{salary},#{sex})
  7.      ]]>
  8. </insert>

关键代码是selectKey元素包含的部分,这个元素内部可以包含一个sql,这个sql可以在插入之前或者插入之后运行(之前还是之后通过order属性配置),然后会将sql运行的结果设置给keyProperty指定的属性,selectKey元素有3个属性需要指定:

  • keyProperty:参数对象中的属性名称,最后插入成功之后,mybatis会通过反射将自增值设置给keyProperty指定的这个属性

  • order:指定selectKey元素中的sql是在插入之前运行还是插入之后运行,可选值(BEFORE|AFTER),这种方式中我们选择BEFORE

  • resultType:keyProperty指定的属性对应的类型,如上面的id对应的类型是java.lang.Long,我们直接写的是别名long

案例

这个案例我就不写了,大家可以拿oracle的序列去练习一下这个案例。

源码

mybatis处理自动生产主键值的代码,主要看下面这个接口:

org.apache.ibatis.executor.keygen.KeyGenerator

看一下这个接口的定义:


   
  1. public  interface KeyGenerator {
  2.   void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter);
  3.   void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter);
  4. }

有2个方法,根据方法名称就可以知道,一个是插入sql执行之前调用的,一个是之后调用的,通过这2个方法mybatis完成了获取主键的功能。

这个接口默认有3个实现类:


   
  1. org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator
  2. org.apache.ibatis.executor.keygen.SelectKeyGenerator
  3. org.apache.ibatis.executor.keygen.NoKeyGenerator

mybatis中获取主键的第一种方式就是在Jdbc3KeyGenerator类中实现的,其他2种方式是在第2个类中实现的,大家可以去看一下代码,设置断点感受一下,第3个类2个方法是空实现。

案例代码获取方式

扫码添加微信备注:mybatis案例,即可获取

MyBatis系列

  1. MyBatis系列第1篇:MyBatis未出世之前我们那些痛苦的经历

  2. MyBatis系列第2篇:入门篇,带你感受一下mybatis独特的魅力!

  3. MyBatis系列第3篇:Mybatis使用详解(1)

  4. MyBatis系列第4篇:Mybatis使用详解(2)

  5. Mybatis系列第5篇:Mapper接口多种方式传参详解、原理、源码解析

更多好文章

  1. Java高并发系列(共34篇)

  2. MySql高手系列(共27篇)

  3. Maven高手系列(共10篇)

  4. 聊聊db和缓存一致性常见的实现方式

  5. 接口幂等性这么重要,它是什么?怎么实现?

感谢大家的阅读,也欢迎您把这篇文章分享给更多的朋友一起阅读!谢谢!

路人甲java

▲长按图片识别二维码关注

路人甲Java:工作10年的前阿里P7分享Java、算法、数据库方面的技术干货!坚信用技术改变命运,让家人过上更体面的生活!


转载:https://blog.csdn.net/likun557/article/details/103501907
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场