死磕MybatisPlus系列:Mapper的奇妙之旅

news2024/11/26 22:28:40

Mybatis Plus源码解析系列篇之Mapper的奇妙之旅

一、MybatisPlus初体验

MybatisPlus是一个基于mybatis的开源orm框架,其内置的Mapper、Service让开发者仅需简单的配置,就能获得强大的CRUD能力;其强大的条件构造器,可以满足各类需求。所以越来越多的开发者使用MybatisPlus来替代基础的Mybatis。

和SpringBoot的集成

MybatisPlus可以无缝集成在SpringBoot中,常用方式如下:

1、引入MybatisPlus依赖

<dependency>
	<groupId>com.baomidou</groupId>
	<artifactId>mybatis-plus-boot-starter</artifactId>
	<version>3.4.3.1</version>
</dependency>

2、定义一个数据库实体

package cn.javayuli.demo.entity;

import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

/**
 * 实体类
 * @author GuiLin Han
 * @since 1.0.0
 */
@Data
@TableName("t_java_coder")
public class JavaCoder {

    private String name;

    private Integer age;
}

3、定义一个Mapper接口

package cn.javayuli.demo.mapper;

import cn.javayuli.demo.entity.JavaCoder;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;

/**
 * @author GuiLin Han
 * @since 1.0.0
 */
public interface JavaCoderMapper extends BaseMapper<JavaCoder> {
}

4、定义一个Service和实现类

package cn.javayuli.demo.service;

import cn.javayuli.demo.entity.JavaCoder;
import com.baomidou.mybatisplus.extension.service.IService;

/**
 * @author GuiLin Han
 * @since 1.0.0
 */
public interface JavaCoderService extends IService<JavaCoder> {
}

package cn.javayuli.demo.service.impl;

import cn.javayuli.demo.entity.JavaCoder;
import cn.javayuli.demo.mapper.JavaCoderMapper;
import cn.javayuli.demo.service.JavaCoderService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;

/**
 * @author GuiLin Han
 * @since 1.0.0
 */
@Service
public class JavaCoderServiceImpl extends ServiceImpl<JavaCoderMapper, JavaCoder> implements JavaCoderService {
}

5、提供一个Controller

package cn.javayuli.demo.controller;

import cn.javayuli.demo.entity.JavaCoder;
import cn.javayuli.demo.service.JavaCoderService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.List;

/**
 * @author GuiLin Han
 * @since 1.0.0
 */
@RestController
public class JavaCoderController {

    @Resource
    private JavaCoderService javaCoderService;

    @GetMapping("/java-coder/all")
    public List<JavaCoder> getList() {
        return javaCoderService.list();
    }
}

5、在SpringBoot启动类上使用Mapper扫描注解

package cn.javayuli.demo;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@MapperScan(basePackages = "cn.javayuli.demo.mapper")
@SpringBootApplication
public class MybatisDemoApplication {

	public static void main(String[] args) {
		SpringApplication.run(MybatisDemoApplication.class, args);
	}

}

那MybatisPlus是在哪里和SpringBoot进行集成的呢?

二、MybatisPlus集成原理

可以看到,启动类上加入了一个@MapperScan的注解,这个注解是Mybatis的,所以猜测这个注解大概率起到了关键作用,那我们就一起来看看:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {
	.....
}

这里有个@Import注解,引入的MapperScannerRegistrarImportBeanDefinitionRegistrar的子类,通过Spring中bean注册机制可以知道,@Import引入的ImportBeanDefinitionRegistrar子类,会在org.springframework.context.annotation.ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry中通过ImportBeanDefinitionRegistrar的addImportBeanDefinitionRegistrar方法,直接注册BeanDefinitionregistry中,从而可以生成bean。

MapperScannerRegistrar

撸下源码:

 public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
     .......
         
      @Override
      public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        // 获取MapperScan注解
        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) {
		// 创建一个MapperScannerConfigurer的BeanDefinitionBuilder
    	BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
        ......   
        // 向registry中注册BeanDefinition
        registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
           
     }
     
     ......
}

以上就是为了构造一个MapperScannerConfigurer对象的BeanDefinition,它的属性大部分都是从@MapperScan这个注解中取出来的,它的作用就是收集各种参数,方便后续扫描。

MapperScannerConfigurer

image-20231023154115098

通过MapperScannerConfigurer的继承关系可以得出以下几个信息:

  • 实现了BeanDefinitionRegistryPostProcessor接口,Spring会主动调用其postProcessBeanDefinitionRegistrypostProcessBeanFactory方法。
  • 实现了InitializingBean接口,所以在初始化bean时,Spring会主动调用其afterPropertiesSet方法。

其中就postProcessBeanDefinitionRegistry起到了关键作用,来具体看看:

  @Override
  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));
  }

这里就是为了使用ClassPathMapperScanner的扫描功能,将Mybatis的mapper扫描出来,让Spring进行管理。

ClassPathMapperScanner

  @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 {
      // 处理扫描出来的beanDefinition
      processBeanDefinitions(beanDefinitions);
    }

    return beanDefinitions;
  }	

super.doScan调用的是父类ClassPathBeanDefinitionScanner的scan方法,大致就是通过basePackages路径,扫描本地文件中的.class文件,得到类元数据,再根据excludeFilter、includeFilter进行过滤后,最后组装成BeanDefinition。

在processBeanDefinitions中,最关键的步骤是:

definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
definition.setBeanClass(this.mapperFactoryBeanClass);

这个mapperFactoryBeanClass是ClassPathMapperScanner类中定义的一个常量,是一个FactoryBean

private Class<? extends MapperFactoryBean> mapperFactoryBeanClass = MapperFactoryBean.class;

也就是说,Context在实例化我们的Mapper的时候,解析到的需要实例化的类其实是MapperFactoryBean,最终初始化后得到的单例bean其实是MapperFactoryBean.getObject()的对象

MapperFactoryBean

既然知道了我们自己写的Mapper的实例是由MapperFactoryBean产生的,那我们来究其原因,它是怎么一个流程。

先来看下类图:

image-20231023154124351

顶层就两个接口,一个是FactoryBean,赋予其生产bean的能力,另一个是InitializingBean,肯定也是想利用afterPropertiesSet方法来做一些操作。那我们来分别对这两个接口进行分析。

1、InitializingBean

public abstract class DaoSupport implements InitializingBean {

   /** Logger available to subclasses. */
   protected final Log logger = LogFactory.getLog(getClass());


   @Override
   public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
      // Let abstract subclasses check their configuration.
      checkDaoConfig();

      // Let concrete implementations initialize themselves.
      try {
         initDao();
      }
      catch (Exception ex) {
         throw new BeanInitializationException("Initialization of DAO failed", ex);
      }
   }

   protected abstract void checkDaoConfig() throws IllegalArgumentException;

   protected void initDao() throws Exception {
   }

}

可以看到,就是定义了两个步骤,一个是检查dao(数据库操作对象,亦称mapper)的配置,另一个是初始化dao,具体操作由子类实现。

那继续往下走,看看SqlSessionDaoSupport

public abstract class SqlSessionDaoSupport extends DaoSupport {

  private SqlSessionTemplate sqlSessionTemplate;

  public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
    if (this.sqlSessionTemplate == null || sqlSessionFactory != this.sqlSessionTemplate.getSqlSessionFactory()) {
      this.sqlSessionTemplate = createSqlSessionTemplate(sqlSessionFactory);
    }
  }

  @SuppressWarnings("WeakerAccess")
  protected SqlSessionTemplate createSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
    return new SqlSessionTemplate(sqlSessionFactory);
  }
  
  public final SqlSessionFactory getSqlSessionFactory() {
    return (this.sqlSessionTemplate != null ? this.sqlSessionTemplate.getSqlSessionFactory() : null);
  }
  
  public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
    this.sqlSessionTemplate = sqlSessionTemplate;
  }

  public SqlSession getSqlSession() {
    return this.sqlSessionTemplate;
  }

  public SqlSessionTemplate getSqlSessionTemplate() {
    return this.sqlSessionTemplate;
  }

  @Override
  protected void checkDaoConfig() {
    notNull(this.sqlSessionTemplate, "Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required");
  }

}

可以看到,其中定义了SqlSessionTemplate这个变量,就说明SqlSessionDaoSupport的子类可以拿到SqlSessionTemplate进行一些操作,SqlSessionTemplate是对SqlSession操作定义的模板方法,可以执行数据库增删改查。

回到主流程,MapperFactoryBean重写了checkDaoConfig方法:

  @Override
  protected void checkDaoConfig() {
    // 调用父类的checkDaoConfig
    super.checkDaoConfig();

    notNull(this.mapperInterface, "Property 'mapperInterface' is required");

    Configuration configuration = getSqlSession().getConfiguration();
    // 如果需要添加到Configuration,且Configuration中还没有当前mapper
    if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
      try {
        // 添加当前mapper到Configuration中
        configuration.addMapper(this.mapperInterface);
      } catch (Exception e) {
        logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
        throw new IllegalArgumentException(e);
      } finally {
        ErrorContext.instance().reset();
      }
    }
  }

在MybatisPlus中,此处configuration是MybatisConfiguration,最终调用的是MybatisMapperRegistry的addMapper方法:

    @Override
    public <T> void addMapper(Class<T> type) {
        if (type.isInterface()) {
            if (hasMapper(type)) {
                return;
            }
            boolean loadCompleted = false;
            try {
                knownMappers.put(type, new MybatisMapperProxyFactory<>(type));
                MybatisMapperAnnotationBuilder parser = new MybatisMapperAnnotationBuilder(config, type);
                parser.parse();
                loadCompleted = true;
            } finally {
                if (!loadCompleted) {
                    knownMappers.remove(type);
                }
            }
        }
    }

可以看到,在MybatisMapperRegistry中,会为每一个mapper分配在一个MybatisMapperProxyFactory

InitializingBean这个接口这条路就分析完了,接下来对FactoryBean这边进行分析。

2、FactoryBean

@Override
public T getObject() throws Exception {
  return getSqlSession().getMapper(this.mapperInterface);
}

getSqlSession()返回的是SqlSessionTemplate

@Override
public <T> T getMapper(Class<T> type) {
  return getConfiguration().getMapper(type, this);
}

getConfiguration()返回的是MybatisConfiguration,

@Override
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mybatisMapperRegistry.getMapper(type, sqlSession);
}

MybatisMapperRegistry中:

@Override
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MybatisMapperProxyFactory<T> mapperProxyFactory = (MybatisMapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
        throw new BindingException("Type " + type + " is not known to the MybatisPlusMapperRegistry.");
    }
    try {
        return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
        throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
}

可以看到,mapper实例是从MybatisMapperProxyFactory创建来的。

MybatisMapperProxyFactory

public class MybatisMapperProxyFactory<T> {
    
    @Getter
    private final Class<T> mapperInterface;
    @Getter
    private final Map<Method, MybatisMapperProxy.MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();
    
    public MybatisMapperProxyFactory(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
    }

    @SuppressWarnings("unchecked")
    protected T newInstance(MybatisMapperProxy<T> mapperProxy) {
        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy);
    }

    public T newInstance(SqlSession sqlSession) {
        final MybatisMapperProxy<T> mapperProxy = new MybatisMapperProxy<>(sqlSession, mapperInterface, methodCache);
        return newInstance(mapperProxy);
    }
}

此factory中,是创建了一个jdk动态代理的对象,而jdk动态代理的灵魂就是InvocationHandlerMybatisMapperProxy就是InvocationHandler的子类,所以,搞清楚MybatisMapperProxy的动作,就知道了mapper接口能具体执行的重要步骤。

MybatisMapperProxy

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
        if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this, args);
        } else {
            return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
        }
    } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
    }
}

这里也没啥大的逻辑,就是将mapper的具体调用交给这个sqlSession去执行,而这个sqlSession,其实就是上面一直传递下来的SqlSessionTemplate对象。

三、总结

Mybatis通过@MapperScan指定mapper包路径,并据此扫描BeanDefinition,并设置其class为MapperFactoryBean。在实例化时,得到的是factoryBean生成的一个jdk动态代理对象。在使用mapper时,实际是使用SqlSessionTemplate的能力。

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

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

相关文章

Walrus 0.4发布:单一配置、多态运行,体验下一代应用交付模型

今天&#xff0c;我们高兴地宣布云原生统一应用平台 Walrus 0.4 正式发布&#xff0c;这是一个里程碑式的版本更新。新版本采用了全新的应用模型——仅需进行单一配置&#xff0c;即可在多种模态的基础设施及环境中运行包括应用服务及周边依赖资源在内的完整应用系统。“You bu…

keil5下使用RAM运行程序的配置过程

本用例是展示HC32F4A0片上2M flash的擦除和读写功能&#xff0c;由于默认配置是程序写入flash中&#xff0c;并从flash中运行程序&#xff0c;所以需要将程序配置为从RAM中运行&#xff0c;这样才能正确运行此程序。默认配置如下&#xff1a; 可以看到MCU的内部flash为2M&#…

Arraylist案例

Arraylist是使用最频繁的一个集合&#xff0c;它与数组类似&#xff0c;不同之处在于它可以动态改变长度&#xff0c;不够了可以扩容。 案例&#xff1a; 我的思考&#xff1a; 首先多个菜品信息可以用Arraylist 来存储&#xff0c;那我们需要再创建一个菜品类Food&#xff0…

uni-app+ts----微信小程序锚点定位 、自动吸顶、滚动自动选择对应的锚点(点击tab跳转对应的元素位置)

uni-app----微信小程序锚点定位 、自动吸顶、滚动自动选择对应的锚点&#xff08;点击tab跳转对应的元素位置&#xff09; html代码部分 重点是给元素加入【 :id“‘item’ item.id”】 <view class"radiusz bg-white pt-[30rpx] z-[999]"><u-tabs:list&q…

长期用台灯影响视力吗?备考专用护眼台灯推荐

大家都知道台灯作为一种小范围的桌面照明灯具&#xff0c;在夜晚能给我们带来很大的帮助&#xff0c;不管是办公、还是学习、阅读都需要它提供照明。那么长期使用台灯会影响视力吗&#xff1f;其实台灯一般都眼睛都是没有伤害的&#xff0c;真正对眼睛有伤害的是不正确的使用台…

MySQL(免密登录)

简介: MySQL免密登录是一种允许用户在没有输入密码的情况下直接登录到MySQL服务器的配置。这通常是通过在登录时跳过密码验证来实现的。 1、修改MySQL的配置文件 使用vi /etc/my.cnf&#xff0c;添加到【mysqld】后面 skip-grant-tables #配置项告诉mysql跳过权限验证&#…

win10屏幕录制神器,让你轻松上手!

屏幕录制成为了人们日常生活中越来越重要的一部分&#xff0c;无论是游戏录制、在线会议记录&#xff0c;还是教程演示&#xff0c;屏幕录制都能够有效地帮助人们捕捉并分享关键信息。随着windows 10系统的普及&#xff0c;许多用户已经开始探索这个系统中的屏幕录制功能。接下…

CRM的智能招投标对企业有什么意义?

如今CRM系统的生态系统越来越壮大&#xff0c;这些工具的集成极大地丰富了CRM系统的应用场景&#xff0c;例如CRM系统集成企业微信等社交媒体为获客提供便利&#xff1b;再比如CRM集成ChatGPT提高邮件内容质量&#xff0c;对于经常接触招投标项目的业务人员来说&#xff0c;在C…

企业营销管理能够实现自动化吗?怎么做?

当今企业面临着越来越多的营销难题&#xff1a;如何有效培育潜在客户、如何提高营销活动的效果、如何优化营销资源的分配......企业的营销管理怎么做&#xff1f;或许CRM系统营销自动化会起到作用。 客户细分&#xff1a; 企业可以通过CRM的客户细分功能&#xff0c;根据客户…

C#中openFileDialog控件的使用方法

目录 一、OpenFileDialog基本属性 二、使用 OpenFile 从筛选的选择中打开文件 1.示例源码 2.生成效果 3. 其它示例 三、使用 StreamReader 以流的形式读取文件 1.示例源码 2.生成效果 四、一种新颖的Windows窗体应用文件设计方法 在C#中&#xff0c;OpenFileDialog控件…

核密度估计法(KDE)的概念,应用,优点,缺点,以及与正态分布(高斯分布)的区别,以及与概率分布的区别联系。看完你就真正捋清这些概念了

文章目录 前言一、核密度估计法&#xff08;KDE&#xff09;是什么&#xff1f;二、核密度估计法的步骤如下&#xff1a;三、核密度的应用&#xff1a;四、核密度估计法的优点&#xff1a;五、核密度估计法的缺点&#xff1a;六、核密度估计法和正态分布的区别在于&#xff1a;…

UE4/UE5 c++绘制编辑器场景直方图(源码包含场景中的像素获取、菜单添加ToolBar)

UE4/UE5 c场景直方图 UE4/UE5 C绘制编辑器场景直方图绘制原理&#xff1a;元素绘制坐标轴绘制 源码处理 UE4/UE5 C绘制编辑器场景直方图 注&#xff1a;源码包含场景中的像素获取、菜单添加ToolBar 实现效果&#xff1a; 这个是用于美术统计场景中像素元素分布&#xff0c;类…

fastjson和jackson序列化的使用案例

简单记录一下一个fastjson框架和jackson进行序列化的使用案例&#xff1a; 原json字符串&#xff1a; “{“lockCount”:”{1:790,113:1,2:0,211:0,101:1328,118:8,137:0,301:0,302:0}“,“inventoryCount”:”{1:25062,113:2,2:10000,211:2,101:11034,118:9,137:40,301:903914…

如何查看电脑版Office的有效期

有时候点击Office账户看不到有效期信息&#xff0c;那么如何查看呢&#xff0c;其实用一条命令就可以查看。 首选WinR运行&#xff0c;输入cmd回车&#xff0c;然后输入下面的命令&#xff1a; cscript “C:\Program Files\Microsoft Office\Office16\ospp.vbs” /dstatus当然…

文章解读与仿真程序复现思路——电力自动化设备EI\CSCD\北大核心《考虑氢储一体化协同的综合能源系统低碳优化》

这个标题涉及到考虑了多个方面的能源系统优化&#xff0c;其中关键的关键词包括"氢储一体化"、"协同"、"综合能源系统"和"低碳优化"。以下是对这些关键词的解读&#xff1a; 氢储一体化&#xff1a; 氢储存&#xff1a; 指的是氢气的存…

【开源】基于Vue+SpringBoot的企业项目合同信息系统

项目编号&#xff1a; S 046 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S046&#xff0c;文末获取源码。} 项目编号&#xff1a;S046&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 数据中心模块2.2 合同审批模块2.3 合…

LeetCode [简单]118. 杨辉三角

给定一个非负整数 numRows&#xff0c;生成「杨辉三角」的前 numRows 行。 在「杨辉三角」中&#xff0c;每个数是它左上方和右上方的数的和。 public class Solution {public IList<IList<int>> Generate(int numRows) {List<IList<int>> res new …

c# 简单web api接口实例源码分析

CreateHostBuilder(args).Build().Run();这句语句处于c#webapi程序的第一句&#xff0c;它的作用是&#xff1a;启动接口的三个步骤&#xff1a; 创建一个HostBuilder对象。执行IHostBuilder.Build()方法创建IHost对象。执行IHost.Run()方法启动。 创建和配置Host&#xff08;…

sklearn中tfidf的计算与手工计算不同详解

sklearn中tfidf的计算与手工计算不同详解 引言&#xff1a;本周数据仓库与数据挖掘课程布置了word2vec的课程作业&#xff0c;要求是手动计算corpus中各个词的tfidf&#xff0c;并用sklearn验证自己计算的结果。但是博主手动计算的结果无论如何也与sklearn中的结果无法对应&…

ChatGLM 6B 部署及微调 【干货】

代码地址、模型地址、安装环境&#xff1a;Ubuntu20.04&#xff0c;RTX3060 12G 一、部署 1.1 下载代码 cd /opt git clone https://github.com/THUDM/ChatGLM2-6B1.2 下载模型 将模型下载后放到项目文件夹内 git lfs install # 确认安装了lfs&#xff0c;或者直接到项目地…