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