mybatis的主键获取

版权声明:本文为博主原创文章,欢迎转载。 https://blog.csdn.net/guo_xl/article/details/88040696

mybatis的auto generation的配置里

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

生成出来的mapper里

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

上面的xml里的节点,我一直以为是mybatis生成主键插入到表里,所以在建立表的时候,主键并没有设AUTO_INCREMENT。如下
RPTINSTKY int(11) NOT NULL primary key

结果插入一直报主键为空的错误。节点到底是做什么用的?带着这个问题
刚好把mybatis的代码简单的过了一遍。
下面就是跟踪源码的过程。

MybatisAutoConfiguration

由于使用springboot集成的mybatis.所以毫无疑问就是在springboot里的MybatisAutoConfiguration里

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

上面的代码里很容易得出application.properties里的配置可以是

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

上段代码里有个org.apache.ibatis.session.Configuration,这是一个很重要的容器类,所有的mapper的xml解析后都会放到这里面。后面的代码里会看到如何放的过程

看上段代码里最后的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");
      }
    }
}

分析上面的xmlMapperBuilder.parse()
xmlMapperBuilder 从名字就可以看出对应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);
    }
  }

上面代码里就是解析mapper的xml文件里各种节点,重点是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里的keyGenerators 是

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

主键是如何生成的

BaseStatementHandler

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

因此

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

结论

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

mysql里的sql 还是必须要有主键,这个selectKey 就是说可以回填到ssmrptinst.ssmrptinstky。

还有另种配置等同

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

参考文档

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

猜你喜欢

转载自blog.csdn.net/guo_xl/article/details/88040696