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。欢迎来撩~

posted @ 2022-08-21 22:53  敬敬不想造轮子  阅读(2336)  评论(0编辑  收藏  举报