mybatisplus oracle 数据库OracleKeyGenerator使用序列生成主键原理
目录
- 一、启动流程
- 二、执行流程
- 三、总结
一、启动流程
1、在mybaitsplus自动配置类MybatisPlusAutoConfiguration.java中创建SqlSessionFactory部分代码如下:
applySqlSessionFactoryBeanCustomizers(factory);GlobalConfig globalConfig = this.properties.getGlobalConfig();this.getBeanThen(MetaObjectHandler.class, globalConfig::setMetaObjectHandler);this.getBeanThen(AnnotationHandler.class, globalConfig::setAnnotationHandler);this.getBeanThen(PostInitTableInfoHandler.class, globalConfig::setPostInitTableInfoHandler);this.getBeansThen(IKeyGenerator.class, i -> globalConfig.getDbConfig().setKeyGenerators(i));this.getBeanThen(ISqlInjector.class, globalConfig::setSqlInjector);this.getBeanThen(IdentifierGenerator.class, globalConfig::setIdentifierGenerator);factory.setGlobalConfig(globalConfig);return factory.getObject();
可以看到 this.getBeansThen(IKeyGenerator.class, i -> globalConfig.getDbConfig().setKeyGenerators(i));会从IOC容器中获取IKeyGenerator组件放入到globalConfig中。下面是OracleKeyGenerator 类实现了IKeyGenerator接口。
public class OracleKeyGenerator implements IKeyGenerator {@Overridepublic String executeSql(String incrementerName) {return "SELECT " + incrementerName + ".NEXTVAL FROM DUAL";}@Overridepublic DbType dbType() {return DbType.ORACLE;}
}
要想让OracleKeyGenerator 生效,需要把OracleKeyGenerator 加入都Ioc容器。
@Beanpublic OracleKeyGenerator oracleKeyGenerator(){return new OracleKeyGenerator();}
实体类也需要使用注解@KeySequence配置序列的名称,@TableId中type是IdType.INPUT
@KeySequence(value = "SEQ_ORACLE_STRING_KEY",dbType = DbType.ORACLE)
public class MStudent implements Serializable {private static final long serialVersionUID = -57527430509435738L;@TableId(type = IdType.INPUT)private String id;private String name;private Integer age;
下面继续看mybatisplus启动流程如下图调用栈
MybatisSqlSessionFactoryBean.java中getObject()->afterPropertiesSet()->buildSqlSessionFactory()-> xmlMapperBuilder.parse()->MybatisXMLMapperBuilder中parse->bindMapperForNamespace()->
configuration.addMapper(boundType)->MybatisConfiguration中addMapper->
MybatisMapperAnnotationBuilder parser = new MybatisMapperAnnotationBuilder(config, type);
parser.parse();没有XML配置文件注入基础CRUD方法
在parse中会判断正在解析的mapper.java是不是 com.baomidou.mybatisplus.core.mapper.Mapper的子类。也就是说自己的Mapper.java需要实现com.baomidou.mybatisplus.core.mapper.Mapper接口,才能被mybatisplus扩展crud方法。
继续看parserInjector方法,其中看一下 List methodList = this.getMethodList(mapperClass, tableInfo);方法获取所有mybatisplus拓展crud SQL 默认注入器。
public void inspectInject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass) {Class<?> modelClass = ReflectionKit.getSuperClassGenericType(mapperClass, Mapper.class, 0);if (modelClass != null) {String className = mapperClass.toString();Set<String> mapperRegistryCache = GlobalConfigUtils.getMapperRegistryCache(builderAssistant.getConfiguration());if (!mapperRegistryCache.contains(className)) {TableInfo tableInfo = TableInfoHelper.initTableInfo(builderAssistant, modelClass);List<AbstractMethod> methodList = this.getMethodList(mapperClass, tableInfo);// 兼容旧代码if (CollectionUtils.isEmpty(methodList)) {methodList = this.getMethodList(builderAssistant.getConfiguration(), mapperClass, tableInfo);}if (CollectionUtils.isNotEmpty(methodList)) {// 循环注入自定义方法methodList.forEach(m -> m.inject(builderAssistant, mapperClass, modelClass, tableInfo));} else {logger.debug(className + ", No effective injection method was found.");}mapperRegistryCache.add(className);}}}
默认的sql注入器
public class DefaultSqlInjector extends AbstractSqlInjector {@Overridepublic List<AbstractMethod> getMethodList(Configuration configuration, Class<?> mapperClass, TableInfo tableInfo) {GlobalConfig.DbConfig dbConfig = GlobalConfigUtils.getDbConfig(configuration);Stream.Builder<AbstractMethod> builder = Stream.<AbstractMethod>builder().add(new Insert(dbConfig.isInsertIgnoreAutoIncrementColumn())).add(new Delete()).add(new Update()).add(new SelectCount()).add(new SelectMaps()).add(new SelectObjs()).add(new SelectList());if (tableInfo.havePK()) {builder.add(new DeleteById()).add(new DeleteByIds()).add(new UpdateById()).add(new SelectById()).add(new SelectByIds());} else {logger.warn(String.format("%s ,Not found @TableId annotation, Cannot use Mybatis-Plus 'xxById' Method.",tableInfo.getEntityType()));}return builder.build().collect(toList());}
}
如下图1是使用反射解析实体类,把实体中属性及注解解析到TableInfo中,其中包含属性对应数据字段和主键信息。2中是循环遍历sql注入器的inject方法,咱们重点看insert.java sql注入器.
跟踪inject方法进入insert中injectMappedStatement,通过实体中的注解配置会进入图片选中的行, keyGenerator = TableInfoHelper.genKeyGenerator(methodName, tableInfo, builderAssistant);
genKeyGenerator中会从GlobalConfig获取IKeyGenerator类型的组件,上面已经添加了OracleKeyGenerator,所以会获取到OracleKeyGenerator,下面会创建MappedStatement 其中掉用keyGenerator.executeSql(tableInfo.getKeySequence().value()))会调用OracleKeyGenerator中的public String executeSql(String incrementerName) {
return "SELECT " + incrementerName + “.NEXTVAL FROM DUAL”;
}方法。最终创建了id=com.ruoyi.system.mapper.MStudentMapper.insert!selectKey的MappedStatement 加入到configuration,创建了new SelectKeyGenerator(mappedStatement, true)放入了id=com.ruoyi.system.mapper.MStudentMapper.insert中的MappedStatement 的keyGenerator属性中。
public static KeyGenerator genKeyGenerator(String baseStatementId, TableInfo tableInfo, MapperBuilderAssistant builderAssistant) {List<IKeyGenerator> keyGenerators = GlobalConfigUtils.getKeyGenerators(builderAssistant.getConfiguration());if (CollectionUtils.isEmpty(keyGenerators)) {throw new IllegalArgumentException("not configure IKeyGenerator implementation class.");}IKeyGenerator keyGenerator = null;if (keyGenerators.size() > 1) {// 多个主键生成器KeySequence keySequence = tableInfo.getKeySequence();if (null != keySequence && DbType.OTHER != keySequence.dbType()) {keyGenerator = keyGenerators.stream().filter(k -> k.dbType() == keySequence.dbType()).findFirst().orElse(null);}}// 无法找到注解指定生成器,默认使用第一个生成器if (null == keyGenerator) {keyGenerator = keyGenerators.get(0);}Configuration configuration = builderAssistant.getConfiguration();String id = builderAssistant.getCurrentNamespace() + StringPool.DOT + baseStatementId + SelectKeyGenerator.SELECT_KEY_SUFFIX;ResultMap resultMap = new ResultMap.Builder(builderAssistant.getConfiguration(), id, tableInfo.getKeyType(), new ArrayList<>()).build();MappedStatement mappedStatement = new MappedStatement.Builder(builderAssistant.getConfiguration(), id,new StaticSqlSource(configuration, keyGenerator.executeSql(tableInfo.getKeySequence().value())), SqlCommandType.SELECT).keyProperty(tableInfo.getKeyProperty()).resultMaps(Collections.singletonList(resultMap)).build();configuration.addMappedStatement(mappedStatement);return new SelectKeyGenerator(mappedStatement, true);}
总结通过上面的流程,可以看到mybatisplus在解析实现了mapper接口的mapper时执行crud 的sql注入器,例如insert sql注入器,给当前解析的mapper添加了insert的MappedStatement。
现在咱们记住在id=com.ruoyi.system.mapper.MStudentMapper.insert中的MappedStatement 的属性keyGenerator中存放了SelectKeyGenerator(mappedStatement, true)其中MappedStatement是 id=com.ruoyi.system.mapper.MStudentMapper.insert!selectKey,这个MappedStatement中存入查询序列的sql。后面执行插入的时候会用到。
二、执行流程
mybatisplus中把mapper通过jdk代理生成MybatisMapperProxy.java,使用mybatisplus拓展的增删改查方法时,会调用MybatisMapperProxy中的invoke,最终调用MybatisMapperMethod.execute->org.mybatis.spring.SqlSessionTemplate.insert->org.apache.ibatis.session.defaults.DefaultSqlSession.insert跟踪到org.apache.ibatis.executor.SimpleExecutor.doUpdate
BaseStatementHandler构造器中执行generateKeys
这个地方就是从id=com.ruoyi.system.mapper.MStudentMapper.insert中MappedStatement 获取属性keyGenerator,上面介绍了是SelectKeyGenerator对象。
protected void generateKeys(Object parameter) {KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();ErrorContext.instance().store();keyGenerator.processBefore(executor, mappedStatement, null, parameter);ErrorContext.instance().recall();}
所以来到SelectKeyGenerator中的processBefore方法。
从创建SelectKeyGenerator执行构造器可以看到,类中的keyStatement就是
id=com.ruoyi.system.mapper.MStudentMapper.insert!selectKey的mappedStatement
获取了simpleExecutor,使用simpleExecutor执行了query方法,传入了查询序列的sql,执行查询序列sql,也就是执行了SELECT SEQ_ORACLE_STRING_KEY.NEXTVAL FROM DUAL,将返回值,设置到了参数里面。
三、总结
1、在mybatisplus启动时,解析mapper的逻辑里面添加了判断是否实现了com.baomidou.mybatisplus.core.mapper.Mapper,只有实现了com.baomidou.mybatisplus.core.mapper.Mapper接口mybatisplus才会自动添加crud注入器(使用sql注入器insert.java delete.java …)添加相应的mappedStatement到configuration中,BaseMapper中有相应的拓展出的方法。在执行insert注入器时,根据OracleKeyGenerator获取查询序列sql,创建了查询序列的mappedStatement添加到了configuration,并把包装后的类SelectKeyGenerator放入了insert的mappedStatement的keyGenerator属性。
2、执行insert,在处理参数时,会调用SelectKeyGenerator执行序列sql获取序列返回的主键设置到参数中为insert插入数据使用。