Mybatis-Plus源码解析之@MapperScan(一)

group : com.baomidou

version:3.5.2.2-SNAPSHOT

baomidou官网可以从快速开始了解到,除了配置数据源,最重要的就是@MapperScan 注解,在 Spring Boot 启动类中添加 @MapperScan 注解,扫描 Mapper 文件夹。

@MapperScan

按照惯例,先看注释。在用java config的方式的时候使用@MapperScan注解来注册Mybatis mapper接口。然后他给了一个配置的示例。

	@Configuration@MapperScan("org.mybatis.spring.sample.mapper")public class AppConfig {@Beanpublic DataSource dataSource() {return new EmbeddedDatabaseBuilder().addScript("schema.sql").build();}@Beanpublic DataSourceTransactionManager transactionManager() {return new DataSourceTransactionManager(dataSource());}@Beanpublic SqlSessionFactory sqlSessionFactory() throws Exception {SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();sessionFactory.setDataSource(dataSource());return sessionFactory.getObject();}}

然后我们看下里面具体有些什么属性,大概了解一下。需要注意的是这个注解中@Import了MapperScannerRegistrar.class。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {String[] value() default {};String[] basePackages() default {};Class<?>[] basePackageClasses() default {};Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;Class<? extends Annotation> annotationClass() default Annotation.class;Class<?> markerInterface() default Class.class;String sqlSessionTemplateRef() default "";String sqlSessionFactoryRef() default "";Class<? extends MapperFactoryBean> factoryBean() default MapperFactoryBean.class;String lazyInitialization() default "";String defaultScope() default AbstractBeanDefinition.SCOPE_DEFAULT;
  • value(): 指定要扫描的包路径,用于指定需要扫描的包。可以指定多个包路径。basePackages() 属性的别名。 允许更简洁的注释声明,例如:@MapperScan(“org.my.pkg”) 而不是 @MapperScan(basePackages = “org.my.pkg”)}。

  • basePackages(): 同样是指定要扫描的包路径,与 value() 属性作用相同,可以指定多个包路径。Mybatis接口的扫描包路径。

  • basePackageClasses(): 指定要扫描的类,可以通过指定类来确定要扫描的包路径。可以指定多个类。basePackages的类型安全替代方法,用于指定要扫描components注解的包。将扫描每个指定类的包。

  • nameGenerator(): 指定自定义的 Bean 名称生成器,用于生成扫描到的 Mapper 接口对应的 Bean 的名称。

  • annotationClass(): 指定自定义的注解类型,用于标识被扫描的 Mapper 接口。默认为 Annotation.class,即不指定注解。

  • markerInterface(): 指定一个标记接口,用于限定被扫描的 Mapper 接口必须继承该标记接口。

  • sqlSessionTemplateRef(): 指定一个注入的 SqlSessionTemplate Bean 的名称,用于指定要使用的 SqlSessionTemplate

  • sqlSessionFactoryRef(): 指定一个注入的 SqlSessionFactory Bean 的名称,用于指定要使用的 SqlSessionFactory

  • factoryBean(): 指定一个自定义的 MapperFactoryBean 类型,用于创建 Mapper 接口对应的 Bean 实例。

  • lazyInitialization(): 指定是否启用延迟初始化。默认为空字符串,表示不启用延迟初始化。

  • defaultScope(): 指定扫描到的 Mapper 接口对应的 Bean 的默认作用域。默认为 AbstractBeanDefinition.SCOPE_DEFAULT

@Import(MapperScannerRegistrar.class)

引入了MapperScannerRegistrar,实现了ImportBeanDefinitionRegistrar。也就是说MapperScannerRegistrar的registerBeanDefinitions方法将会被执行。

spring专题有描述过ImportBeanDefinitionRegistrar的时机在invokeBeanFactoryPostProcessors阶段,有兴趣的朋友们也可以关注下。下面就是MapperScannerRegistrar的核心代码。

@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {AnnotationAttributes mapperScanAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));if (mapperScanAttrs != null) {registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry,generateBaseBeanName(importingClassMetadata, 0));}}void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs,BeanDefinitionRegistry registry, String beanName) {BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);builder.addPropertyValue("processPropertyPlaceHolders", true);Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");if (!Annotation.class.equals(annotationClass)) {builder.addPropertyValue("annotationClass", annotationClass);}Class<?> markerInterface = annoAttrs.getClass("markerInterface");if (!Class.class.equals(markerInterface)) {builder.addPropertyValue("markerInterface", markerInterface);}Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");if (!BeanNameGenerator.class.equals(generatorClass)) {builder.addPropertyValue("nameGenerator", BeanUtils.instantiateClass(generatorClass));}Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {builder.addPropertyValue("mapperFactoryBeanClass", mapperFactoryBeanClass);}String sqlSessionTemplateRef = annoAttrs.getString("sqlSessionTemplateRef");if (StringUtils.hasText(sqlSessionTemplateRef)) {builder.addPropertyValue("sqlSessionTemplateBeanName", annoAttrs.getString("sqlSessionTemplateRef"));}String sqlSessionFactoryRef = annoAttrs.getString("sqlSessionFactoryRef");if (StringUtils.hasText(sqlSessionFactoryRef)) {builder.addPropertyValue("sqlSessionFactoryBeanName", annoAttrs.getString("sqlSessionFactoryRef"));}List<String> basePackages = new ArrayList<>();basePackages.addAll(Arrays.stream(annoAttrs.getStringArray("value")).filter(StringUtils::hasText).collect(Collectors.toList()));basePackages.addAll(Arrays.stream(annoAttrs.getStringArray("basePackages")).filter(StringUtils::hasText).collect(Collectors.toList()));basePackages.addAll(Arrays.stream(annoAttrs.getClassArray("basePackageClasses")).map(ClassUtils::getPackageName).collect(Collectors.toList()));if (basePackages.isEmpty()) {basePackages.add(getDefaultBasePackage(annoMeta));}String lazyInitialization = annoAttrs.getString("lazyInitialization");if (StringUtils.hasText(lazyInitialization)) {builder.addPropertyValue("lazyInitialization", lazyInitialization);}String defaultScope = annoAttrs.getString("defaultScope");if (!AbstractBeanDefinition.SCOPE_DEFAULT.equals(defaultScope)) {builder.addPropertyValue("defaultScope", defaultScope);}builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));// for spring-nativebuilder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);registry.registerBeanDefinition(beanName, builder.getBeanDefinition());}

此段源码其实非常简单,就是读取注解信息,然后注册了一个MapperScannerConfigurer类的BeanDefinition。

MapperScannerConfigurer

MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor、InitializingBean、ApplicationContextAware、BeanNameAware。比较重要的是BeanDefinitionRegistryPostProcessor中的postProcessBeanDefinitionRegistry方法将在invokeBeanFactoryPostProcessors中的后续被执行。

public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {if (this.processPropertyPlaceHolders) {processPropertyPlaceHolders();}ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);scanner.setAddToConfig(this.addToConfig);scanner.setAnnotationClass(this.annotationClass);scanner.setMarkerInterface(this.markerInterface);scanner.setSqlSessionFactory(this.sqlSessionFactory);scanner.setSqlSessionTemplate(this.sqlSessionTemplate);scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);scanner.setResourceLoader(this.applicationContext);scanner.setBeanNameGenerator(this.nameGenerator);scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);if (StringUtils.hasText(lazyInitialization)) {scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));}if (StringUtils.hasText(defaultScope)) {scanner.setDefaultScope(defaultScope);}scanner.registerFilters();scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));}

源码分析:

this.processPropertyPlaceHolders这个属性是在创建的时候设置的,默认就是true

BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);builder.addPropertyValue("processPropertyPlaceHolders", true);

所以processPropertyPlaceHolders()方法默认的时候一定会执行的。

首先看注释,BeanDefinitionRegistries在应用启动早期被调用,且早于BeanFactoryPostProcessors。这就意味着PropertyResourceConfigurer将不会被加载,因为其实现了BeanFactoryPostProcessor;所以这个类的属性将会加载失败,为了避免这类情况,所以在此处找到所有PropertyResourceConfigurers的定义信息,且运行他们的postProcessBeanFactory方法并更新他们的值。

/** BeanDefinitionRegistries are called early in application startup, before BeanFactoryPostProcessors. This means that* PropertyResourceConfigurers will not have been loaded and any property substitution of this class' properties will* fail. To avoid this, find any PropertyResourceConfigurers defined in the context and run them on this class' bean* definition. Then update the values.*/private void processPropertyPlaceHolders() {Map<String, PropertyResourceConfigurer> prcs = applicationContext.getBeansOfType(PropertyResourceConfigurer.class,false, false);if (!prcs.isEmpty() && applicationContext instanceof ConfigurableApplicationContext) {BeanDefinition mapperScannerBean = ((ConfigurableApplicationContext) applicationContext).getBeanFactory().getBeanDefinition(beanName);// PropertyResourceConfigurer does not expose any methods to explicitly perform// property placeholder substitution. Instead, create a BeanFactory that just// contains this mapper scanner and post process the factory.DefaultListableBeanFactory factory = new DefaultListableBeanFactory();factory.registerBeanDefinition(beanName, mapperScannerBean);for (PropertyResourceConfigurer prc : prcs.values()) {prc.postProcessBeanFactory(factory);}PropertyValues values = mapperScannerBean.getPropertyValues();this.basePackage = getPropertyValue("basePackage", values);this.sqlSessionFactoryBeanName = getPropertyValue("sqlSessionFactoryBeanName", values);this.sqlSessionTemplateBeanName = getPropertyValue("sqlSessionTemplateBeanName", values);this.lazyInitialization = getPropertyValue("lazyInitialization", values);this.defaultScope = getPropertyValue("defaultScope", values);}this.basePackage = Optional.ofNullable(this.basePackage).map(getEnvironment()::resolvePlaceholders).orElse(null);this.sqlSessionFactoryBeanName = Optional.ofNullable(this.sqlSessionFactoryBeanName).map(getEnvironment()::resolvePlaceholders).orElse(null);this.sqlSessionTemplateBeanName = Optional.ofNullable(this.sqlSessionTemplateBeanName).map(getEnvironment()::resolvePlaceholders).orElse(null);this.lazyInitialization = Optional.ofNullable(this.lazyInitialization).map(getEnvironment()::resolvePlaceholders).orElse(null);this.defaultScope = Optional.ofNullable(this.defaultScope).map(getEnvironment()::resolvePlaceholders).orElse(null);}

这个方法的作用是确保在应用启动时,处理所有的属性占位符,使得配置的值能够正确的诸如到当前类的属性中。这对于需要在应用程序启动时读取配置信息并进行相应处理的情况非常有用。

在方法内部创建了ClassPathMapperScanner对象。并设置了属性值,注册了过滤器(包含过滤器和排除过滤器),执行了scan方法。

package-info结尾的类名将会被排除加载。默认所有的类都会被加载。

ClassPathMapperScanner

ClassPathMapperScanner类继承了ClassPathBeanDefinitionScanner类。

scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));

所以此处将会调用父类ClassPathBeanDefinitionScanner的Scan方法。

public int scan(String... basePackages) {int beanCountAtScanStart = this.registry.getBeanDefinitionCount();doScan(basePackages);// Register annotation config processors, if necessary.if (this.includeAnnotationConfig) {AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);}return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);}

ClassPathBeanDefinitionScanner的scan方法会执行doScan方法,也就是子类的实现。

ClassPathMapperScanner#doScan

@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);if (beanDefinitions.isEmpty()) {LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages)+ "' package. Please check your configuration.");} else {processBeanDefinitions(beanDefinitions);}return beanDefinitions;
}

然后执行父类的doscan方法,返回的是BeanDefinitionHolder集合,也就是把包路径中的类扫描注册到容器中。

如果扫描为空,就提示一下,如果返回有值的话就进入processBeanDefinitions方法。这个就比较重要了。

private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {AbstractBeanDefinition definition;BeanDefinitionRegistry registry = getRegistry();for (BeanDefinitionHolder holder : beanDefinitions) {definition = (AbstractBeanDefinition) holder.getBeanDefinition();boolean scopedProxy = false;if (ScopedProxyFactoryBean.class.getName().equals(definition.getBeanClassName())) {definition = (AbstractBeanDefinition) Optional.ofNullable(((RootBeanDefinition) definition).getDecoratedDefinition()).map(BeanDefinitionHolder::getBeanDefinition).orElseThrow(() -> new IllegalStateException("The target bean definition of scoped proxy bean not found. Root bean definition[" + holder + "]"));scopedProxy = true;}String beanClassName = definition.getBeanClassName();LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName+ "' mapperInterface");// the mapper interface is the original class of the bean// but, the actual class of the bean is MapperFactoryBeandefinition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59try {// for spring-nativedefinition.getPropertyValues().add("mapperInterface", Resources.classForName(beanClassName));} catch (ClassNotFoundException ignore) {// ignore}definition.setBeanClass(this.mapperFactoryBeanClass);definition.getPropertyValues().add("addToConfig", this.addToConfig);// Attribute for MockitoPostProcessor// https://github.com/mybatis/spring-boot-starter/issues/475definition.setAttribute(FACTORY_BEAN_OBJECT_TYPE, beanClassName);boolean explicitFactoryUsed = false;if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {definition.getPropertyValues().add("sqlSessionFactory",new RuntimeBeanReference(this.sqlSessionFactoryBeanName));explicitFactoryUsed = true;} else if (this.sqlSessionFactory != null) {definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);explicitFactoryUsed = true;}if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {if (explicitFactoryUsed) {LOGGER.warn(() -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");}definition.getPropertyValues().add("sqlSessionTemplate",new RuntimeBeanReference(this.sqlSessionTemplateBeanName));explicitFactoryUsed = true;} else if (this.sqlSessionTemplate != null) {if (explicitFactoryUsed) {LOGGER.warn(() -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");}definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);explicitFactoryUsed = true;}if (!explicitFactoryUsed) {LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);}definition.setLazyInit(lazyInitialization);if (scopedProxy) {continue;}if (ConfigurableBeanFactory.SCOPE_SINGLETON.equals(definition.getScope()) && defaultScope != null) {definition.setScope(defaultScope);}if (!definition.isSingleton()) {BeanDefinitionHolder proxyHolder = ScopedProxyUtils.createScopedProxy(holder, registry, true);if (registry.containsBeanDefinition(proxyHolder.getBeanName())) {registry.removeBeanDefinition(proxyHolder.getBeanName());}registry.registerBeanDefinition(proxyHolder.getBeanName(), proxyHolder.getBeanDefinition());}}
}

代码非常长哈,里面也就一些issue的记录注释,有兴趣的可以看一看,拓展一下。

其实里面做的事情就是BeanDefinition的属性设置,需要注意的是扫描的这些Mapper类的BeanDefinition的beanClass都是MapperFactoryBean.class。因为我们使用的Mapper类都是接口。

写在最后

@MapperScan注解,实际上做的事情就是扫描Mapper类到容器中。引入的类也是为了加载和处理Mapper类。但是除了@MapperScan之外有个一个SPI机制引入的类MybatisPlusAutoConfiguration,将在下一章进行分析。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/213587.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

angular form 组件、双向绑定;反应式表单

1.使用双向绑定&#xff0c;以及angular的表单提交功能 app.moudle中引入 双向绑定 [(ngModel)]"text" ​​​​​​​ 效果 提交表单 2.反应式表单 在app.module.ts中引入在组件中引入&#xff0c;并放在一个变量里 在初始化时实列化这个module 定义规则 在html…

Linux:环境变量

目录 1.基本变量 2.通过代码获取环境变量 2.1 main传参 2.2 全局变量environ 2.3 系统调用getenv() 3.在脚本文件中添加环境变量 4.环境变量通常是具有全局属性 1.基本变量 环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数…

商用中央空调市场分析:预计2028年将达到628亿元

商用空调一直以来都没有一个相对比较明确的概念&#xff0c;一直以来被认为是制冷空调市场的一个细分子行业。现在比较一致的观点是&#xff0c;可以纳入商用空调范畴的产品可以包括户式中央空调产品、部分传统中央空调产品以及部分家用空调。商用空调已普遍采用直流变频领先技…

网络计算机模拟实现

今天给大家说说前几天完成的一个模拟的网络计算机吧&#xff0c;虽然计算机的模拟实现的原理很简单&#xff0c;但是如果要想写乘网络的&#xff0c;个人认为是不简单的。基本上算是包涵了套接字编程的三分之一的知识点&#xff0c;此处的套接字编程指的是在理解TCP/IP五层协议…

泡沫玻璃市场分析:预计2028年将达到14亿美元

泡沫玻璃最早是由美国匹兹堡康宁公司发明的&#xff0c;是由碎玻璃、发泡剂、改性添加剂和发泡促进剂等&#xff0c;经过细粉碎和均匀混合后&#xff0c;再经过高温熔化&#xff0c;发泡、退火而制成的无机非金属玻璃材料。它是由大量直径为1~2毫米的均匀气泡结构组成。其中吸声…

Linux 常用命令----mktemp 命令

文章目录 基本用法实例演示高级用法注意事项 mktemp 命令用于创建一个临时文件或目录&#xff0c;这在需要处理临时数据或进行安全性测试时非常有用。使用 mktemp 可以保证文件名的唯一性&#xff0c;避免因文件名冲突而导致的问题。 基本用法 创建临时文件: 命令 mktemp 默认…

Go语言基础知识学习(一)

Go基本数据类型 bool bool型值可以为true或者false,例子&#xff1a; var b bool true数值型 类型表示范围int8有符号8位整型-128 ~ 127int16有符号16位整型-32768 ~ 32767int32有符号32位整型-2147783648 ~ 2147483647int64有符号64位整型uint8无符号8位整型0 ~ 255uint16…

优思学院|如何建立公司运营指标体系?如何推行六西格玛改进运营指标?

关键绩效指标 (KPI) 是测量您团队或组织朝重要商业目标进展表现如何的量化指标&#xff0c;组织会在多个层面使用 KPI&#xff0c;这视乎您想要追踪何指标而定&#xff0c;您可以设定全组织的、特定团队的、或甚至是个人 KPI。 良好的KPI能让公司管理者掌握组织的营运是否进度…

使用React 18、Echarts和MUI实现温度计

关键词 React 18 Echarts和MUI 前言 在本文中&#xff0c;我们将结合使用React 18、Echarts和MUI&#xff08;Material-UI&#xff09;库&#xff0c;展示如何实现一个交互性的温度计。我们将使用Echarts绘制温度计的外观&#xff0c;并使用MUI创建一个漂亮的用户界面。 本文…

点评项目——分布式锁

2023.12.10 集群模式下的并发安全问题及解决 随着现在分布式系统越来越普及&#xff0c;一个应用往往会部署在多台机器上&#xff08;多节点&#xff09;&#xff0c;通过加锁可以解决在单机情况下的一人一单安全问题&#xff0c;但是在集群模式下就不行了。见下图&#xff1a…

在 Android WebView 中实现和 JavaScript 的互操作

前言 在 APP 中内嵌一个 H5 来实现特定的业务功能已经是非常成熟且常用的方案了。 虽然 H5 已经能够实现大多数的需求&#xff0c;但是对于某些需求还是得依靠原生代码来实现然后与 JavaScript 进行交互&#xff0c;例如我目前所负责的项目就是一个 “智能硬件” 设备&#x…

【PyTorch】卷积神经网络

文章目录 1. 理论介绍1.1. 从全连接层到卷积层1.1.1. 背景1.1.2. 从全连接层推导出卷积层 1.2. 卷积层1.2.1. 图像卷积1.2.2. 填充和步幅1.2.3. 多通道 1.3. 池化层&#xff08;又称汇聚层&#xff09;1.3.1. 背景1.3.2. 池化运算1.3.3. 填充和步幅1.3.4. 多通道 1.4. 卷积神经…

实现React18加TS,解决通用后台管理系统,实战方案落地有效实践经验

随着前端技术的不断发展和更新&#xff0c;使用React 18结合TypeScript&#xff08;TS&#xff09;来构建通用后台管理系统已成为一种常见的选择。本文将介绍如何在项目中应用React 18和TS&#xff0c;并分享一些实战方案的有效实践经验。 一、搭建React 18 TS项目 首先&…

12.2每日一题(1无穷型幂指函数:二倍角公式+三部曲+等价无穷小代换(只有整体的因子不为0才能先算出来))

注意&#xff1a;求极限不能想先算哪里就先算哪里&#xff0c;只有整体的因子不为0才能先算出来&#xff0c;部分不为0不可以先算

外贸老业务也棘手的一个问题

这几天有2个老业务都被一个类同的问题缠住了。 客户定购了三台车&#xff0c;由于是非常规要求所以我建议收取全款或者最少收50%的定金。但是业务员为了当月业绩或者为了拿到就收了客户20% 或者30% &#xff0c;定金收到了&#xff0c;我也不好再逼着业务员去加收定金。 订单就…

记录 | ubuntu上安装fzf

在 ubuntu 上采用命令行安装 fzf 的方式行不通 指的是采用下面的方式行不通&#xff1a; sudo apt install fzf # 行不通 sudo snap install fzf --classic # 行不通正确的安装方式是&#xff1a; ● 到 fzf 的 git 仓库&#xff1a;https://github.com/junegunn/fzf/re…

在高数据量中如何优化MySQL的Group by语句?

在实际开发环境中&#xff0c;MySQL的GROUP BY操作的优化需要结合具体的业务场景和数据特点。以下是一些建议&#xff0c;可以帮助你在实际开发中优化GROUP BY查询&#xff1a; 使用合适的索引&#xff1a; 确保GROUP BY和ORDER BY中的列上存在索引。这有助于加速分组和排序操作…

计算机毕业设计 基于SpringBoot的电动车租赁系统的设计与实现 Java实战项目 附源码+文档+视频讲解

博主介绍&#xff1a;✌从事软件开发10年之余&#xff0c;专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精…

场景示例:有赞商城 × 微盛企微管家,助力零售企业,实现私域运营自动化

1 场景描述 在零售行业内&#xff0c;线上渠道已经是零售行业的主要销售渠道&#xff0c;大多数零售企业都会将产品上架到有赞商城&#xff0c;并使用微盛企微管家系统进行客户管理和服务&#xff0c;希望能对客户画像进行精细化管理&#xff0c;以提升销售和服务效率。 然而&a…

2023年最新prometheus + grafana搭建和使用+gmail邮箱告警配置

一、安装prometheus 1.1 安装 prometheus官网下载地址 sudo -i mkdir -p /opt/prometheus #移动解压后的文件名到/opt/,并改名prometheus mv prometheus-2.45 /opt/prometheus/ #创建一个专门的prometheus用户&#xff1a; -M 不创建家目录&#xff0c; -s 不让登录 useradd…