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); }

如下可能的配置会使用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>
 useGeneratedKeys="true" keyProperty="id" keyColumn="id"

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

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

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

 @Override
  public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
    processBatch(ms, stmt, parameter);
  }
  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);
    }
  }

这里先介绍如何根据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();
        }
   
    }
}

从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);
    }
  }

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


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

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

然后再对应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>

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);
    
  }

实际上就是利用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>
  @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);
    }
  }


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);
  }
}

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

select uuid(),

通过查询数据库获取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) {
  }

}

你可能感兴趣的:(mybatis,mybatis,数据库,java)