mybatis-plus获取雪花算法或者替换雪花算法
介绍
前面说到雪花算法的原理,可能说的不是很具体,但是网上解释雪花算法原理一大堆。mybatis-plus(下面统称MP)现在越来越多的公司和个人在使用,有时候我们会有这样的需求。我们想获取MP的雪花算法来生成一个id,或者修改自定义他的雪花算法。这样怎么办?
先说结论
- 你可以实现MP提供的
IdentifierGenerator
接口然后注入到IOC容器中(具体实现可以使用hutool的雪花算法工具类等...) - 你可以通过MP提供的
IdWorker.getId()
IdWorker工具类来通过MP提供的雪花算法获取id
源码解析
我们知道springboot加载一个组件到IOC中对应的组件都有一个XXXAutoConfiguration
,所以我们直接看 MybatisPlusAutoConfiguration
这个类
看public SqlSessionFactory sqlSessionFactory(DataSource dataSource)
这个方法,他向IOC容器中注入了一个ibatis(mybatis)的SqlSessionFactory
,我们知道,在学习mybatis的时候就是通过这个SqlSessionFactory来获取SqlSession的,但是这不是我们这篇文章的目的。
找到这一行this.getBeanThen(IdentifierGenerator.class, globalConfig::setIdentifierGenerator);
这个getBeanThen看一下:
private <T> void getBeanThen(Class<T> clazz, Consumer<T> consumer) {
if (this.applicationContext.getBeanNamesForType(clazz, false, false).length > 0) {
consumer.accept(this.applicationContext.getBean(clazz));
}
}
这就是说在容器上下文中查找目标类如果查找到了执行consumer函数。那么说回来这句的意思就是说在容器中查找到IdentifierGenerator.class这个类如果找到了就设置给globalConfig这个对象。所以到这里我们的第一个结论就得出来啦。
在这个方法开始前他创建了一个MybatisSqlSessionFactoryBean
对象,这个是用来获取SqlSessionFactory 对象的,所以我们看最后一行的factory.getObject();
(按住你的Ctrl点进去!)
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
this.afterPropertiesSet();
}
return this.sqlSessionFactory;
}
我们进到了MybatisSqlSessionFactoryBean中,这个方法进行了一次判空如果没有执行afterPropertiesSet()
方法。gogogo去看看:
public void afterPropertiesSet() throws Exception {
Assert.notNull(this.dataSource, "Property 'dataSource' is required");
Assert.state(this.configuration == null && this.configLocation == null || this.configuration == null || this.configLocation == null, "Property 'configuration' and 'configLocation' can not specified with together");
SqlRunner.DEFAULT.close();
this.sqlSessionFactory = this.buildSqlSessionFactory();
}
这里对数据源和配置类进行了一次校验后执行了buildSqlSessionFactory()
方法
对了,如果看过mybatis的源码的小伙伴应该知道,他有一个全局的Configuration而MP这里继承了这个类创建了一个MybatisConfiguration,那mybatis的配置如何和MP的配置产生关系呢?MP有一个GlobalConfig还有一个工具类
GlobalConfigUtils
,他将MybatisConfiguration和GlobalConfig关联了起来,都在这个方法里。
这个方法比较长我就不粘了,我们看主要的:
SqlSessionFactory sqlSessionFactory = (new MybatisSqlSessionFactoryBuilder()).build((Configuration)targetConfiguration);
这里通过MybatisSqlSessionFactoryBuilder这个类建造了一个sqlSessionFactory对象,我们点击build进去看看:
public SqlSessionFactory build(Configuration configuration) {
//找到MP的globalConfig
GlobalConfig globalConfig = GlobalConfigUtils.getGlobalConfig(configuration);
Object identifierGenerator;
// 判断是否之前有设置identifierGenerator
if (null == globalConfig.getIdentifierGenerator()) {
// 没有重新创建
identifierGenerator = new DefaultIdentifierGenerator();
globalConfig.setIdentifierGenerator((IdentifierGenerator)identifierGenerator);
} else {
// 有就直接设置
identifierGenerator = globalConfig.getIdentifierGenerator();
}
// 设置给IdWorker
IdWorker.setIdentifierGenerator((IdentifierGenerator)identifierGenerator);
if (globalConfig.isEnableSqlRunner()) {
(new SqlRunnerInjector()).inject(configuration);
}
// 调用父类的build,这里其实就在调用mybatis的构建sqlSessionFactory方法了,
// 因为这个MybatisSqlSessionFactoryBuilder继承了mybatis的SqlSessionFactoryBuilder方法,这个类我们在使用mybatis的时候很熟悉吧!
SqlSessionFactory sqlSessionFactory = super.build(configuration);
globalConfig.setSqlSessionFactory(sqlSessionFactory);
return sqlSessionFactory;
}
已经对上面代码进行了注释,在第15行代码中我们看到了我们的第二个结论,你可以点进IdWorker这个工具类,这个代码很简单稍微看一下就知道了。
MP何时给主键设置的id?
这个问题需要调用save方法时debug代码,还是比较深的,跟的比较久。但是我们想啊,MP是通过IdentifierGenerator
接口的实现来的nextId
方法生成id,那么他在给主键设置id时一定会调用这个方法,你可以debug这里,或者按住Ctrl+鼠标左键会有一个MybatisParameterHandler#populateKeys
方法(这个需要下载源码哦不然你点不进去的我试了)。
protected void populateKeys(TableInfo tableInfo, MetaObject metaObject, Object entity) {
final IdType idType = tableInfo.getIdType();
final String keyProperty = tableInfo.getKeyProperty();
if (StringUtils.isNotBlank(keyProperty) && null != idType && idType.getKey() >= 3) {
// 获取id的生成器
final IdentifierGenerator identifierGenerator = GlobalConfigUtils.getGlobalConfig(this.configuration).getIdentifierGenerator();
// 获取当前id字段对应的值
Object idValue = metaObject.getValue(keyProperty);
// 判断是否分配id 如果id为空就会为id生成一个id
if (identifierGenerator.assignId(idValue)) {
if (idType.getKey() == IdType.ASSIGN_ID.getKey()) {
Class<?> keyType = tableInfo.getKeyType();
if (Number.class.isAssignableFrom(keyType)) {
// 通过id生成器生成id
// 下面是通过id字段不同的类型为id设置值
Number id = identifierGenerator.nextId(entity);
if (keyType == id.getClass()) {
metaObject.setValue(keyProperty, id);
} else if (Integer.class == keyType) {
metaObject.setValue(keyProperty, id.intValue());
} else if (Long.class == keyType) {
metaObject.setValue(keyProperty, id.longValue());
} else if (BigDecimal.class.isAssignableFrom(keyType)) {
metaObject.setValue(keyProperty, new BigDecimal(id.longValue()));
} else if (BigInteger.class.isAssignableFrom(keyType)) {
metaObject.setValue(keyProperty, new BigInteger(id.toString()));
} else {
throw new MybatisPlusException("Key type '" + keyType + "' not supported");
}
} else {
metaObject.setValue(keyProperty, identifierGenerator.nextId(entity).toString());
}
} else if (idType.getKey() == IdType.ASSIGN_UUID.getKey()) {
metaObject.setValue(keyProperty, identifierGenerator.nextUUID(entity));
}
}
}
}
上面设置值的metaObject对象,可以理解为实例类的承载类,这里用到了反射,所以对他的操作进行了封装。到这里基本解决了我的疑问了。
最后
本篇文章并没有过多的说mybatis的源码,默认了读者看过mybatis的源码,其实有springboot的经验还是可以看懂。
最后,笔者能力有限!如果有错误,或者有疑问可以联系我哦!qq:1126184155。欢迎来撩~