mybatis primary key acquisition

Disclaimer: This article is a blogger original article, welcome to reprint. https://blog.csdn.net/guo_xl/article/details/88040696

mybatis configuration in the auto generation

<table tableName="RPTDEF" domainObjectName="RptDef">   
    <generatedKey column="RPTDEFKY" sqlStatement="Mysql" identity="true" />
    <columnOverride column="ISDELETE" javaType="boolean" />
</table>

Generated out of the mapper in

<insert id="insert" parameterType="com.xx..module.reports.entities.SSMRptInst">
    <!--
      WARNING - @mbg.generated
      This element is automatically generated by MyBatis Generator, do not modify.
    -->
    <selectKey keyProperty="ssmrptinstky" order="AFTER" resultType="java.lang.Integer">
      SELECT LAST_INSERT_ID()
    </selectKey>
    insert into ssmrptinst (RPTNAME, RPTTYPE, STATUS, 
      ISDELETE, AFFILIATE, CORPID, 
      UPDATEDTTM, UPDATEUSER, VERSIONSTAMP, 
      JSONDATA)
    values (#{rptname,jdbcType=VARCHAR}, #{rpttype,jdbcType=CHAR}, #{status,jdbcType=CHAR}, 
      #{isdelete,jdbcType=BIT}, #{affiliate,jdbcType=VARCHAR}, #{corpid,jdbcType=VARCHAR}, 
      #{updatedttm,jdbcType=TIMESTAMP}, #{updateuser,jdbcType=VARCHAR}, #{versionstamp,jdbcType=SMALLINT}, 
      #{jsondata,jdbcType=LONGVARCHAR})
  </insert>

The above xml in the node, I always thought it was mybatis generate the primary key is inserted into the table, so when the establishment of the table, the primary key is not set AUTO_INCREMENT. as follows
RPTINSTKY int(11) NOT NULL primary key

The results have been reported insert the primary key is empty mistakes. Node in the end is what to do with? With this question
just simple code to mybatis over again.
Here is the process of tracking the source.

MybatisAutoConfiguration

Due to the use of springboot integrated mybatis. So there is no doubt that in the MybatisAutoConfiguration in the springboot

public class MybatisAutoConfiguration {
	  //new出SqlSessionFactory这个bean
	  @ConditionalOnMissingBean
	  public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
	    SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
	    factory.setDataSource(dataSource);
	    factory.setVfs(SpringBootVFS.class);
	    
	    //如果有在application.properties里的配置mybatis.configLocation=xxx.xml
	    if (StringUtils.hasText(this.properties.getConfigLocation())) {
	      factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
	    }
	    Configuration configuration = this.properties.getConfiguration();
	    
	    //如果没有配置的话,就new一个configuration
	    if (configuration == null && !StringUtils.hasText(this.properties.getConfigLocation())) {
	      configuration = new Configuration();
	    }
	    if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) {
	      for (ConfigurationCustomizer customizer : this.configurationCustomizers) {
	        customizer.customize(configuration);
	      }
	    }
	    factory.setConfiguration(configuration);
	    if (this.properties.getConfigurationProperties() != null) {
	      factory.setConfigurationProperties(this.properties.getConfigurationProperties());
	    }
	    if (!ObjectUtils.isEmpty(this.interceptors)) {
	      factory.setPlugins(this.interceptors);
	    }
	    if (this.databaseIdProvider != null) {
	      factory.setDatabaseIdProvider(this.databaseIdProvider);
	    }
	    if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
	      factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
	    }
	    if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
	      factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
	    }
	    //加入mapper的xml
	    if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
	      factory.setMapperLocations(this.properties.resolveMapperLocations());
	    }
	
	    return factory.getObject();
	  }
  }

The above code is very easy to draw in the configuration can be application.properties

mybatis.mapper-locations=classpath*:/mappers/*.xml,classpath:/mappers/**/*Mapper.xml

There is a section of the code org.apache.ibatis.session.Configuration, this is a very important container classes, all the mapper parses the xml will be put there. Behind the code you will see the process of how to put

Fancy piece of code in the last factory.getObject ()

public SqlSessionFactory getObject() throws Exception {
    if (this.sqlSessionFactory == null) {
      afterPropertiesSet();
    }

    return this.sqlSessionFactory;
  }
  public void afterPropertiesSet() throws Exception {
  ...
    this.sqlSessionFactory = buildSqlSessionFactory();
  }

 protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
  ...
  //this.mapperLocations就是配置文件里mybatis.mapper-locations的配置
 if (!isEmpty(this.mapperLocations)) {
 //循环所有的mapper加入到configuration
      for (Resource mapperLocation : this.mapperLocations) {
        if (mapperLocation == null) {
          continue;
        }

        try {
          XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
              configuration, mapperLocation.toString(), configuration.getSqlFragments());
         //parse的过程会把mapper的xml打散后存到Configuration里
          xmlMapperBuilder.parse();
        } catch (Exception e) {
          throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
        } finally {
          ErrorContext.instance().reset();
        }

        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Parsed mapper file: '" + mapperLocation + "'");
        }
      }
    } else {
      if (LOGGER.isDebugEnabled()) {
        LOGGER.debug("Property 'mapperLocations' was not specified or no matching resources found");
      }
    }
}

Analysis of the above xmlMapperBuilder.parse()
xmlMapperBuilder can be seen from the name of the corresponding xmlMapper

public void parse() {
    
    if (!configuration.isResourceLoaded(resource)) {
    //这里从xml里的mapper节点进行配置
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      bindMapperForNamespace();
    }

    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
  }
 private void configurationElement(XNode context) {
    try {
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.equals("")) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      builderAssistant.setCurrentNamespace(namespace);
      cacheRefElement(context.evalNode("cache-ref"));
      cacheElement(context.evalNode("cache"));
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      sqlElement(context.evalNodes("/mapper/sql"));
      //我要分析的主键在这些CRUD的节点中
            buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
    }
  }

The above code is to parse xml file of mapper in various nodes, the focus is buildStatementFromContext

private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
    for (XNode context : list) {
      final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
      try {
      //进入这个类
        statementParser.parseStatementNode();
      } catch (IncompleteElementException e) {
        configuration.addIncompleteStatement(statementParser);
      }
    }
  }
public void parseStatementNode() {
   ...
    // Parse selectKey after includes and remove them.
    //如果insert里有<SelectKey>节点,这里则需要处理它。
    processSelectKeyNodes(id, parameterTypeClass, langDriver);
    
    // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
    String resultSets = context.getStringAttribute("resultSets");
    String keyProperty = context.getStringAttribute("keyProperty");
    String keyColumn = context.getStringAttribute("keyColumn");
    KeyGenerator keyGenerator;
    String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
    keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
    //如果上面的processSelectKeyNodes已经处理好了,比如加入了selectkey,那么这里就自然有。也就是返回的Select
    if (configuration.hasKeyGenerator(keyStatementId)) {
      keyGenerator = configuration.getKeyGenerator(keyStatementId);
    } else {
      keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
          configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
          ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
    }

    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered, 
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
  }
private void parseSelectKeyNodes(String parentId, List<XNode> list, Class<?> parameterTypeClass, LanguageDriver langDriver, String skRequiredDatabaseId) {
    for (XNode nodeToHandle : list) {
     生成的规则是genertor的规则是parentId + SelectKeyGenerator.SELECT_KEY_SUFFIX
      String id = parentId + SelectKeyGenerator.SELECT_KEY_SUFFIX;
      String databaseId = nodeToHandle.getStringAttribute("databaseId");
      if (databaseIdMatchesCurrent(id, databaseId, skRequiredDatabaseId)) {
        parseSelectKeyNode(id, nodeToHandle, parameterTypeClass, langDriver, databaseId);
      }
    }
  }
 private void parseSelectKeyNode(String id, XNode nodeToHandle, Class<?> parameterTypeClass, LanguageDriver langDriver, String databaseId) {
   ...
   //最终的结果就是加入keyGenerator,放到Configuration.keyGenerators
   configuration.addKeyGenerator(id, new SelectKeyGenerator(keyStatement, executeBefore));
  }

Configuration is in the keyGenerators

 protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<KeyGenerator>("Key Generators collection");

How the primary key is generated

Base Statement Trades

  protected void generateKeys(Object parameter) {
    KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
    ErrorContext.instance().store();
    keyGenerator.processBefore(executor, mappedStatement, null, parameter);
    ErrorContext.instance().recall();
  }

public class PreparedStatementHandler extends BaseStatementHandler {

  @Override
  public int update(Statement statement) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    int rows = ps.getUpdateCount();
    Object parameterObject = boundSql.getParameterObject();
    KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
    //这步就是回填key值到对象里
    keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
    return rows;
  }
 }

therefore

RPTInstMapper.insertSelective(inst);
//这就是为什么这里能取到值
int rptInstKy = inst.getRptinstky();

in conclusion

<selectKey keyProperty="ssmrptinstky" order="AFTER" resultType="java.lang.Integer">
      SELECT LAST_INSERT_ID()
    </selectKey>

mysql in the sql or must have a primary key, this means that you can backfill selectKey to ssmrptinst.ssmrptinstky.

There is another kind of configuration equivalent

 <insert id="insert" keyProperty="ssmrptinstky" useGeneratedKeys="true" parameterType="com.xxx.ssmrpt">

Reference Documents

https://blog.csdn.net/wagcy/article/details/32965349

Guess you like

Origin blog.csdn.net/guo_xl/article/details/88040696