• Mybatis KeyGenerator生成主键


    通过KeyGenerator回写数据库自增主键

    //KeyGenerator.java
    /**
     * 主键生成器接口,有三个实现类:
     * 1、 {@link Jdbc3KeyGenerator}
    * 2、{@link NoKeyGenerator}
    * 3、{@link SelectKeyGenerator}
    * @author Clinton Begin */
    public interface KeyGenerator { /** * 针对Sequence主键而言,在执行insert sql前必须指定一个主键值给要插入的记录, * 如Oracle、DB2,KeyGenerator提供了processBefore()方法。 * @param executor * @param ms * @param stmt * @param parameter */ void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter); /** * 针对自增主键的表,在插入时不需要主键,而是在插入过程自动获取一个自增的主键, * 比如MySQL,Postgresql,KeyGenerator提供了processAfter()方法 * @param executor * @param ms * @param stmt * @param parameter */ void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter); }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    如下可能的配置会使用Jdbc3KeyGenerator:

     <insert id="insert" parameterType="com.mybatis.cache.mybatiscache.domain.User" useGeneratedKeys="true" keyProperty="id" >
        insert into user (id, user_name, pass_word, 
          real_name, name)
        values (#{id,jdbcType=INTEGER}, #{userName,javaType=java.lang.String, jdbcType=VARCHAR}, #{passWord,jdbcType=VARCHAR},
          #{realName,jdbcType=VARCHAR}, #{name,jdbcType=VARCHAR})
      </insert>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
     useGeneratedKeys="true" keyProperty="id" keyColumn="id"
    
    • 1

    这里使用了useGeneratedKeys="true"代表我们希望在插入数据库之后获取生成的主键值,因为是在操作数据库之后获取主键,因此processBefore方法是不需要实现的,因此给出的是空实现,源码如下:

    org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator#processBefore
    public void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
      // do nothing
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    因此我们只需要看下procesAfter的源码就行了,如下:

     @Override
      public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
        processBatch(ms, stmt, parameter);
      }
    
    • 1
    • 2
    • 3
    • 4
      public void processBatch(MappedStatement ms, Statement stmt, Object parameter) {
        final String[] keyProperties = ms.getKeyProperties();
        if (keyProperties == null || keyProperties.length == 0) {
          return;
        }
              // 获取JDBC的ResultSet对象,封装有数据库生成的主键信息(因此,mybaits最终还是要依赖于JDBC的机制)
        try (ResultSet rs = stmt.getGeneratedKeys()) {
          final ResultSetMetaData rsmd = rs.getMetaData();
          final Configuration configuration = ms.getConfiguration();
          if (rsmd.getColumnCount() < keyProperties.length) {
            // Error?
          } else {
            assignKeys(configuration, rs, rsmd, keyProperties, parameter);
          }
        } catch (Exception e) {
          throw new ExecutorException("Error getting generated key or setting result to parameter object. Cause: " + e, e);
        }
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    这里先介绍如何根据jdbc获取到自增主键,看一个demo

    mport java.sql.Connection;
    import java.sql.DriverManager;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    import java.sql.Statement;
       
    public class TestJDBC {
       
        public static void main(String[] args) {
            try {
                Class.forName("com.mysql.jdbc.Driver");
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
             String sql = "insert into hero values(null,?,?,?)";
            try (Connection c = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/how2java?characterEncoding=UTF-8","root", "admin");
                    PreparedStatement ps = c.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);          
                    ) {
      
                ps.setString(1, "盖伦");
                ps.setFloat(2, 616);
                ps.setInt(3, 100);
       
                // 执行插入语句
                ps.execute();
       
                // 在执行完插入语句后,MySQL会为新插入的数据分配一个自增长id
                // JDBC通过getGeneratedKeys获取该id
                ResultSet rs = ps.getGeneratedKeys();
                if (rs.next()) {
                    int id = rs.getInt(1);
                    System.out.println(id);
                }
       
            } catch (SQLException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
       
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    从jdbc获取到GeneratedKeys之后
    进入assignKeys

      private void assignKeys(Configuration configuration, ResultSet rs, ResultSetMetaData rsmd, String[] keyProperties,
          Object parameter) throws SQLException {
        if (parameter instanceof ParamMap || parameter instanceof StrictMap) {
          // Multi-param or single param with @Param
          assignKeysToParamMap(configuration, rs, rsmd, keyProperties, (Map<String, ?>) parameter);
        } else if (parameter instanceof ArrayList && !((ArrayList<?>) parameter).isEmpty()
            && ((ArrayList<?>) parameter).get(0) instanceof ParamMap) {
          // Multi-param or single param with @Param in batch operation
          assignKeysToParamMapList(configuration, rs, rsmd, keyProperties, (ArrayList<ParamMap<?>>) parameter);
        } else {
          // Single param without @Param
          assignKeysToParam(configuration, rs, rsmd, keyProperties, parameter);
        }
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    对于不同的类型有不同设置的办法,paramMap,集合ParamMap


    题外话ParamMap
    实际上就是执行查询时,传入多个参数需要指定

        int insertParamMap(@Param("user") User record);
    
    • 1

    然后再对应sql中写上就好了

     <insert id="insertParamMap" useGeneratedKeys="true" keyProperty="id">
        insert into user (id, user_name, pass_word,
          real_name, name)
        values (#{user.id,jdbcType=INTEGER}, #{user.userName,javaType=java.lang.String, jdbcType=VARCHAR}, #{user.passWord,jdbcType=VARCHAR},
          #{user.realName,jdbcType=VARCHAR}, #{user.name,jdbcType=VARCHAR})
      </insert>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator.KeyAssigner#assign

     protected void assign(ResultSet rs, Object param) {
       
          MetaObject metaParam = configuration.newMetaObject(param);
         
        Class<?> propertyType = metaParam.getSetterType(propertyName);
            typeHandler = typeHandlerRegistry.getTypeHandler(propertyType,
                    JdbcType.forCode(rsmd.getColumnType(columnPosition)));
       
        Object value = typeHandler.getResult(rs, columnPosition);
        metaParam.setValue(propertyName, value);
        
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    实际上就是利用jdbc获取主键,,用过属性类型,和jdbc类型获取typeHandler然后利用typeHandler获取匹配类型的数据,设置到对应的parameter中,完成主键的设置,将数据库生成的主键设置回对象

    SelectKeyGenerator

    processBefore

    如下配置会执行该方法,在操作数据库之前,生成主键值,最终使用该主键值进行数据库操作:

    <insert id="insert" parameterType="com.mybatis.cache.mybatiscache.domain.User">
        <selectKey keyProperty="name" keyColumn="name"
                   resultType="java.lang.String" order="BEFORE">
          select uuid()
        </selectKey>
        insert into user (id, user_name, pass_word,
          real_name, name)
        values (#{id,jdbcType=INTEGER}, #{userName,javaType=java.lang.String, jdbcType=VARCHAR}, #{passWord,jdbcType=VARCHAR},
          #{realName,jdbcType=VARCHAR}, #{name,jdbcType=VARCHAR})
      </insert>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
      @Override
      public void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
        if (executeBefore) {
          processGeneratedKeys(executor, ms, parameter);
        }
      }
    
      @Override
      public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
        if (!executeBefore) {
          processGeneratedKeys(executor, ms, parameter);
        }
      }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    org.apache.ibatis.executor.keygen.SelectKeyGenerator#processGeneratedKeys
    private void processGeneratedKeys(Executor executor, MappedStatement ms, Object parameter) {
      try {
        if (parameter != null && keyStatement != null && keyStatement.getKeyProperties() != null) {
          // 获取配置的属性值,即标签中配置的keyProperty="id"
          String[] keyProperties = keyStatement.getKeyProperties();
          // 获取全局配置文件对应的Configuration对象
          final Configuration configuration = ms.getConfiguration();
          // 将传入参数封装为MetaObject对象,方便操作,如设置属性
          final MetaObject metaParam = configuration.newMetaObject(parameter);
          if (keyProperties != null) {
            // 获取执行器,用于执行在标签中设置的生成主键的sql语句
            Executor keyExecutor = configuration.newExecutor(executor.getTransaction(), ExecutorType.SIMPLE);
            // keyStatement:是对标签的封装
            // 执行对应的MappedStatement,生成主键
            // 如select UUID()结果为db6fbe60-07af-11ec-978a-1b6197d25533
            List<Object> values = keyExecutor.query(keyStatement, parameter, RowBounds.DEFAULT, Executor.NO_RESULT_HANDLER);
            // 判断长度只能为1,这里使用List接收,是因为底层如此
            // 这里只能通过判断元素个数来进行控制
            if (values.size() == 0) {
              throw new ExecutorException("SelectKey returned no data.");            
            } else if (values.size() > 1) {
              throw new ExecutorException("SelectKey returned more than one value.");
            } else {
              MetaObject metaResult = configuration.newMetaObject(values.get(0));
              // 这里只考虑有一个属性的情况
              if (keyProperties.length == 1) {
                // 如果是传入的对象的当前属性有getter方法,一般获取的是
                // 字符串,所有肯定没有
                if (metaResult.hasGetter(keyProperties[0])) {
                  setValue(metaParam, keyProperties[0], metaResult.getValue(keyProperties[0]));
                } else {
                  // 设置属性值
                  // metaParam:用户传入的参数对象
                  // keyProperties[0]:要设置的属性值
                  // values.get(0):生成的主键值
                  // 将生成的主键值设置到传入参数对象的属性中
                  // 如下是设置前,设置后的值:
                  // 设置前:{"myage":19,"myname":"379bff59-eb6d-4b03-95d4-571379e9d13f"}
                  // 设置后:{"id":"db6fbe60-07af-11ec-978a-1b6197d25533","myage":19,"myname":"379bff59-eb6d-4b03-95d4-571379e9d13f"}
                  setValue(metaParam, keyProperties[0], values.get(0));
                }
              } else {
                handleMultipleProperties(keyProperties, metaParam, metaResult);
              }
            }
          }
        }
      } catch (ExecutorException e) {
        throw e;
      } catch (Exception e) {
        throw new ExecutorException("Error selecting key or setting result to parameter object. Cause: " + e, e);
      }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    实现也很简单就是新建我们配置的查询

    select uuid(),
    
    • 1

    通过查询数据库获取uuid进行设置

    如果生成uuid后需要插入数据库,order="BEFORE"
    如果配置成了 order=“AFTER”,那么不会将对应uuid插入到数据库,只会将参数设置uuid返回

    NoKeyGenerator

    不设置主键值,也不获取数据库生成的主键值的情况,为了保持代码的通用性,这里也给出一个实现类处理这种情况,源码如下:

    org.apache.ibatis.executor.keygen.NoKeyGenerator
    public class NoKeyGenerator implements KeyGenerator {
    
      public void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
      }
    
      public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
      }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
  • 相关阅读:
    [附源码]计算机毕业设计springboot春晓学堂管理系统
    Acer宏碁掠夺者战斧700笔记本PH717-71原装出厂Win10系统1903工厂模式镜像
    Coremail城市沙龙活动|齐聚古城西安,护航信息安全
    按关键字搜索商品 (淘宝)
    相机标定和双目相机标定标定原理推导及效果展示
    (利用IDEA+Maven)定制属于自己的jar包
    天池2023智能驾驶汽车虚拟仿真视频数据理解--baseline
    浅入G2O
    【力扣】单调栈:901. 股票价格跨度
    【Java 进阶篇】使用 JDBC 更新数据详解
  • 原文地址:https://blog.csdn.net/qq_37436172/article/details/127557902