Mybatis源码学习

这篇具有很好参考价值的文章主要介绍了Mybatis源码学习。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、前言

憋了几个星期,终于憋出了这个源码学习的文章。

学习 Mybatis 源码,要有 Spring 源码的阅读基础,否则有些地方可能会不太明白。
本文源码来自于mybatis3.5.6

二、 用 Java 写个 JDBC 连接 MySQL 的例子

public static void main(String[] args) {
    Connection conn = null;
    PreparedStatement pstmt = null;
    ResultSet rs = null;

    try {
        // 加载驱动程序
        Class.forName("com.mysql.jdbc.Driver");

        // 获取数据库连接
        String url = "jdbc:mysql://localhost:3306/test";
        String user = "root";
        String password = "123456";
        conn = DriverManager.getConnection(url, user, password);

        // 执行 SQL 查询
        String sql = "SELECT * FROM student WHERE age >= ?";
        pstmt = conn.prepareStatement(sql);
        pstmt.setInt(1, 18);
        pstmt.execute();
        rs = pstmt.getResultSet();

        // 处理结果集
        while (rs.next()) {
            int id = rs.getInt("id");
            String name = rs.getString("name");
            int age = rs.getInt("age");
            System.out.println("id:" + id + ", name:" + name + ", age:" + age);
        }
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } catch (SQLException e) {
        e.printStackTrace();
    } finally {
        // 释放资源
        try {
            if (rs != null) rs.close();
            if (pstmt != null) pstmt.close();
            if (conn != null) conn.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

有几个比较关键的代码,我们可以先记住,之后的源码跟读中,可以找找在源码中对应的位置,到时候,我会标粗字体:

  • DriverManager.getConnection();
  • conn.prepareStatement();
  • pstmt.execute();
  • pstmt.getResultSet();
  • rs.getString();

三、 在SpringBoot启动时,Mybatis框架主要干了哪些事

在 SpringBoot 启动时,Mybatis 框架主要做了以下几个事情:

  1. 自动装配 MybatisAutoConfiguration 类。
  2. 扫描 Mapper 接口,并将其注册到 Mybatis 的 Configuration 中。

1、自动装配 MybatisAutoConfiguration 类

MybatisAutoConfiguration 是 Mybatis 框架的自动装配类,用于在 SpringBoot 启动时自动配置 Mybatis 相关的组件和依赖。MybatisAutoConfiguration 类上使用了 @Configuration 注解,表示该类是一个配置类。同时,它还使用了 @EnableConfigurationProperties(MybatisProperties.class) 注解来引入 MybatisProperties 属性类,以获取 Mybatis 相关的配置信息。
此外自动装配类中,自动注入了SqlSessionFactory类,源码如下:

//MybatisAutoConfiguration
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
    SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
    factory.setDataSource(dataSource);
    factory.setVfs(SpringBootVFS.class);
    if (StringUtils.hasText(this.properties.getConfigLocation())) {
        factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
    }
    applyConfiguration(factory);
    if (this.properties.getConfigurationProperties() != null) {
        factory.setConfigurationProperties(this.properties.getConfigurationProperties());
    }
    if (!ObjectUtils.isEmpty(this.interceptors)) {
        factory.setPlugins(this.interceptors);
    }
    if (this.databaseIdProvider != null) {
        factory.setDatabaseIdProvider(this.databaseIdProvider);
    }
    if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
        factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
    }
    if (this.properties.getTypeAliasesSuperType() != null) {
        factory.setTypeAliasesSuperType(this.properties.getTypeAliasesSuperType());
    }
    if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
        factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
    }
    if (!ObjectUtils.isEmpty(this.typeHandlers)) {
        factory.setTypeHandlers(this.typeHandlers);
    }
    if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
        factory.setMapperLocations(this.properties.resolveMapperLocations());
    }
    Set<String> factoryPropertyNames = Stream
            .of(new BeanWrapperImpl(SqlSessionFactoryBean.class).getPropertyDescriptors()).map(PropertyDescriptor::getName)
            .collect(Collectors.toSet());
    Class<? extends LanguageDriver> defaultLanguageDriver = this.properties.getDefaultScriptingLanguageDriver();
    if (factoryPropertyNames.contains("scriptingLanguageDrivers") && !ObjectUtils.isEmpty(this.languageDrivers)) {
        // Need to mybatis-spring 2.0.2+
        factory.setScriptingLanguageDrivers(this.languageDrivers);
        if (defaultLanguageDriver == null && this.languageDrivers.length == 1) {
            defaultLanguageDriver = this.languageDrivers[0].getClass();
        }
    }
    if (factoryPropertyNames.contains("defaultScriptingLanguageDriver")) {
        // Need to mybatis-spring 2.0.2+
        factory.setDefaultScriptingLanguageDriver(defaultLanguageDriver);
    }

    return factory.getObject();
}

这个方法,通过 @ConditionalOnMissingBean 注解, 限定了 SqlSessionFactory 类型的 Bean 只有在容器中不存在时才会被创建,如果用户自己配置了 SqlSessionFactory ,这段代码就不会执行。
这个方法主要就是自动配置 SqlSessionFactoryBean 类和 Configuration 类,分别用于创建 SqlSessionFactoryConfiguration 对象。

2、扫描 Mapper 接口,并将其注册到 Mybatis 的 Configuration 中

@MapperScan 注解通过 @Import(MapperScannerRegistrar.class) 引入扫描注册的类MapperScannerRegistrar

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

MapperScannerRegistrar 实现了 ImportBeanDefinitionRegistrar 接口并重写 registerBeanDefinitions() 方法,在方法中注册了 MapperScannerConfigurer

//MapperScannerRegistrar
@Override
public 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));

    registry.registerBeanDefinition(beanName, builder.getBeanDefinition());

}

MapperScannerConfigurer 类是 一个 Bean 后置处理器,实现了 BeanDefinitionRegistryPostProcessor 接口,其 postProcessBeanDefinitionRegistry 方法在容器启动时会调用,作用是完成 Mapper 的扫描注册

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

scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass); 设置了 MapperFactoryBean 的 Class 类型, 默认为 MapperFactoryBean.class ,这个 this.mapperFactoryBeanClass 必须是 MapperFactoryBean 的子类,这边是方便扩展的,不展开说了,重点记住 MapperFactoryBean 这个类,然后走 scanner.scan 往下看

//ClassPathBeanDefinitionScanner
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);
}

继续看 doScan,这个 doScan 由子类 ClassPathMapperScanner 重写了,所以要看 ClassPathMapperScanner 中的 doScan 方法

//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 {
        processBeanDefinitions(beanDefinitions);
    }

    return beanDefinitions;
}

这个方法,先调用了父类的 doScan 方法完成 beanDefinition 的注册,如果注册成功 beanDefinitions 集合里有内容,就调用 processBeanDefinitions 方法,重点是这个方法

//ClassPathMapperScanner
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 MapperFactoryBean
        definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
        definition.setBeanClass(this.mapperFactoryBeanClass);

        definition.getPropertyValues().add("addToConfig", this.addToConfig);

        // Attribute for MockitoPostProcessor
        // https://github.com/mybatis/spring-boot-starter/issues/475
        definition.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());
        }

    }
}

这个方法重点就是,definition.setBeanClass(this.mapperFactoryBeanClass); 这一句,把 beanClass 替换成了 MapperFactoryBean 类型,MapperFactoryBean 实现了 FactoryBean 接口,这里简单介绍下 FactoryBean

FactoryBean 是 Spring 框架中的一个工厂 Bean 接口,使得用户可以自定义 Bean 的创建过程,并且可以将创建的 Bean 注册到 Spring 容器中。
FactoryBean 接口定义了一个 getObject() 方法,用于返回一个对象实例。用户通过实现该接口并重写该方法来实现定制化的 Bean 创建逻辑。
当 Spring 容器在初始化 Bean 时遇到一个 FactoryBean 类型的 Bean 定义时,它会自动调用该 FactoryBean 的 getObject() 方法从而获得一个对象实例,而不是直接返回该 FactoryBean 本身。

所以,在初始化这些 Mapper 的时候,会通过 MapperFactoryBeangetObject() 方法来获取一个对象实例,另外 MapperFactoryBean 还继承了 SqlSessionDaoSupport 抽象类,SqlSessionDaoSupport 类继承自 DaoSupport 类 ,这个类实现了 InitializingBean 接口,这里再简单介绍下 InitializingBean 接口

InitializingBean是Spring框架中的一个接口,如果一个Bean实现了该接口,那么在该Bean被实例化并依赖注入之后,Spring容器会自动调用InitializingBean接口中定义的afterPropertiesSet方法,执行一些初始化操作。

贴个类图看,更直观一点
图片.png
我们看下 DaoSupport 类的 afterPropertiesSet 方法

//DaoSupport
public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
    this.checkDaoConfig();

    try {
        this.initDao();
    } catch (Exception var2) {
        throw new BeanInitializationException("Initialization of DAO failed", var2);
    }
}

重点看 checkDaoConfig 方法,后面的 initDao 是空方法,没有子类实现,不用管;checkDaoConfig 是抽象方法,看其子类实现,MapperFactoryBeancheckDaoConfig 方法

//MapperFactoryBean
@Override
protected void checkDaoConfig() {
    super.checkDaoConfig();

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

    Configuration configuration = getSqlSession().getConfiguration();
    if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
        try {
            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();
        }
    }
}

这个方法,调用父类的 checkDaoConfig 方法校验 sqlSessionTemplate 是不是空的,然后重点是 configuration.addMapper(this.mapperInterface); 这个方法,这个方法,最终会调用 MapperRegistryaddMapper 方法,把 Mapper 封装成 MapperProxyFactory 类型放到 MapperRegistry 内部的一个 map (knownMappers) 里缓存起来

//MapperRegistry
public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {
        if (hasMapper(type)) {
            throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
        }
        boolean loadCompleted = false;
        try {
            knownMappers.put(type, new MapperProxyFactory<>(type));
            // It's important that the type is added before the parser is run
            // otherwise the binding may automatically be attempted by the
            // mapper parser. If the type is already known, it won't try.
            MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
            parser.parse();
            loadCompleted = true;
        } finally {
            if (!loadCompleted) {
                knownMappers.remove(type);
            }
        }
    }
}

其中的 parser.parse(); 方法会读取 XML 配置文件中定义的 SQL 映射语句,并存到 Configuration 里缓存起来。
下面我们再看 MapperFactoryBeangetObject() 方法是怎么获取 Mapper

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

这里的 getSqlSession() 返回的是 SqlSessionTemplate ,然后这个 getMapper 最终调用 MapperRegistrygetMapper 方法

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

可以看到返回的是 mapperProxyFactory.newInstance,再进去看下

//MapperProxyFactory
protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

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

最终返回的是 MapperProxy 代理对象
这个方法主要干了以下几件事:

  1. @MapperScan 注解通过 @Import(MapperScannerRegistrar.class) 引入扫描注册的类MapperScannerRegistrar;
  2. MapperScannerRegistrarregisterBeanDefinitions() 方法中注册了 MapperScannerConfigurer 类;
  3. MapperScannerConfigurer 类的 postProcessBeanDefinitionRegistry 方法中扫描 Mapper 并将其 BeanClass 改为 MapperFactoryBean 类型;
  4. 在初始化 Mapper 的时候封装成 MapperProxyFactory 对象缓存起来;
  5. 在获取 Mapper 实例的时候,从缓存中取出 MapperProxyFactory 对象并创建代理对象 MapperProxy 返回;

之后在实际调用 Mapper 的 sql 的时候,其实就会走代理对象的 invoke 方法,这也是 Mapper 不需要实现类的原因。

四、Mapper是怎么调用sql语句的

接着上面,我们在 MapperProxyinvoke 方法上打断点,然后随便调用下自己项目的一个会调用数据库的接口,我这边调用了一个查询接口

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

这个方法中的 cachedInvoker 方法会将 Mapper 接口的方法信息包装成 MapperMethodInvoker 对象并缓存到一个 ConcurrentMap 中,这样是为了避免在接口方法被多次调用时重复解析方法信息所带来的性能开销。invoke 方法会走到 MapperMethodInvoker 的实现类 PlainMethodInvokerinvoke 方法中,然后调用 MapperMethod 对象的 execute 方法,我们跟进去看下

//MapperMethod
public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
        case INSERT: {
            Object param = method.convertArgsToSqlCommandParam(args);
            result = rowCountResult(sqlSession.insert(command.getName(), param));
            break;
        }
        case UPDATE: {
            Object param = method.convertArgsToSqlCommandParam(args);
            result = rowCountResult(sqlSession.update(command.getName(), param));
            break;
        }
        case DELETE: {
            Object param = method.convertArgsToSqlCommandParam(args);
            result = rowCountResult(sqlSession.delete(command.getName(), param));
            break;
        }
        case SELECT:
            if (method.returnsVoid() && method.hasResultHandler()) {
                executeWithResultHandler(sqlSession, args);
                result = null;
            } else if (method.returnsMany()) {
                result = executeForMany(sqlSession, args);
            } else if (method.returnsMap()) {
                result = executeForMap(sqlSession, args);
            } else if (method.returnsCursor()) {
                result = executeForCursor(sqlSession, args);
            } else {
                Object param = method.convertArgsToSqlCommandParam(args);
                result = sqlSession.selectOne(command.getName(), param);
                if (method.returnsOptional()
                        && (result == null || !method.getReturnType().equals(result.getClass()))) {
                    result = Optional.ofNullable(result);
                }
            }
            break;
        case FLUSH:
            result = sqlSession.flushStatements();
            break;
        default:
            throw new BindingException("Unknown execution method for: " + command.getName());
    }
    if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
        throw new BindingException("Mapper method '" + command.getName()
                + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    return result;
}

该方法通过判断不同的 SQL 命令类型, 调用对应的 SqlSession 方法来执行 SQL 语句,因为我这边调用的是查询列表,所以走到了 executeForMany 方法里

//MapperMethod
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
    List<E> result;
    Object param = method.convertArgsToSqlCommandParam(args);
    if (method.hasRowBounds()) {
        RowBounds rowBounds = method.extractRowBounds(args);
        result = sqlSession.selectList(command.getName(), param, rowBounds);
    } else {
        result = sqlSession.selectList(command.getName(), param);
    }
    // issue #510 Collections & arrays support
    if (!method.getReturnType().isAssignableFrom(result.getClass())) {
        if (method.getReturnType().isArray()) {
            return convertToArray(result);
        } else {
            return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
        }
    }
    return result;
}

最终走到了 sqlSession.selectList 方法中,之前我们知道 MapperProxy 里的 sqlSession 类型是 SqlSessionTemplate,继续跟

//SqlSessionTemplate
@Override
public <E> List<E> selectList(String statement, Object parameter) {
	return this.sqlSessionProxy.selectList(statement, parameter);
}

这个 sqlSessionProxy 是什么,我们看下 SqlSessionTemplate 的构造函数
图片.png
这个 sqlSessionProxySqlSession 的代理对象,并委托给了 SqlSessionInterceptor 处理器处理,所以我们继续跟,就会来到 SqlSessionInterceptorinvoke 方法

//SqlSessionTemplate -> SqlSessionInterceptor
private class SqlSessionInterceptor implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
                SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
        try {
            Object result = method.invoke(sqlSession, args);
            if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
                // force commit even on non-dirty sessions because some databases require
                // a commit/rollback before calling close()
                sqlSession.commit(true);
            }
            return result;
        } catch (Throwable t) {
            Throwable unwrapped = unwrapThrowable(t);
            if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
                // release the connection to avoid a deadlock if the translator is no loaded. See issue #22
                closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
                sqlSession = null;
                Throwable translated = SqlSessionTemplate.this.exceptionTranslator
                        .translateExceptionIfPossible((PersistenceException) unwrapped);
                if (translated != null) {
                    unwrapped = translated;
                }
            }
            throw unwrapped;
        } finally {
            if (sqlSession != null) {
                closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
            }
        }
    }
}

这个方法其实就是在执行 SqlSession 接口方法之前,首先获取当前线程绑定的 SqlSession 对象,如果不存在,则会通过 SqlSessionFactory 创建一个新的 SqlSession 对象,并将其绑定到当前线程上。然后再将方法调用委托给 SqlSession 对象进行实际的操作。在执行完 SqlSession 接口方法之后,又会检查 SqlSession 对象是否已经与当前线程解除绑定,如果尚未解除,则解除绑定,从而避免内存泄漏问题。
获取 SqlSession 的方法,可以进 getSession 方法里看,具体创建 SqlSession 的方法在 ConfigurationnewExecutor 方法里

//Configuration
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    if (ExecutorType.BATCH == executorType) {
        executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
        executor = new ReuseExecutor(this, transaction);
    } else {
        executor = new SimpleExecutor(this, transaction);
    }
    if (cacheEnabled) {
        executor = new CachingExecutor(executor);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
}

创建 Executor 是根据不同的 ExecutorType 类型,创建不同的 Executor,这里是 SimpleExecutor,然后根据是否开启缓存,再包一层 CachingExecutor,这里用到了装饰器模式,cacheEnabled 默认就是 true,最后加上拦截器链,这个拦截器链会依次调用所有拦截器的 plugin 方法,给 executor 创建代理类,这里就不展开了。
最后把 CachingExecutor 封装到 DefaultSqlSession 里返回,所以最终 method.invoke() 调用的是 DefaultSqlSessionselect 方法,我们跟进去看下

//DefaultSqlSession
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      	MappedStatement ms = configuration.getMappedStatement(statement);
      	return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
      	throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      	ErrorContext.instance().reset();
    }
}

getMappedStatement 方法从 configuration 里的缓存 map 里获取 MappedStatement ,然后执行 CachingExecutorquery 方法,这时如果有拦截器,会先走到拦截器里,最后会走到 CachingExecutorquery 方法里,这个方法会做一些缓存的判断,如果没有开启缓存,会直接调用 BaseExecutorquery 方法

//BaseExecutor
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    if (closed) {
        throw new ExecutorException("Executor was closed.");
    }
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
        clearLocalCache();
    }
    List<E> list;
    try {
        queryStack++;
        list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
        if (list != null) {
            handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
        } else {
            list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
        }
    } finally {
        queryStack--;
    }
    if (queryStack == 0) {
        for (BaseExecutor.DeferredLoad deferredLoad : deferredLoads) {
            deferredLoad.load();
        }
        // issue #601
        deferredLoads.clear();
        if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
            // issue #482
            clearLocalCache();
        }
    }
    return list;
}

然后会走到17行的 queryFromDatabase

//BaseExecutor
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
        list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
        localCache.removeObject(key);
    }
    localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
        localOutputParameterCache.putObject(key, parameter);
    }
    return list;
}

然后走到第5行的 doQuery 方法

//SimpleExecutor
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
        Configuration configuration = ms.getConfiguration();
        StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
        stmt = prepareStatement(handler, ms.getStatementLog());
        return handler.query(stmt, resultHandler);
    } finally {
        closeStatement(stmt);
    }
}

这里的 newStatementHandler 和 前面的 newExecutor 差不多,创建对象,然后加上拦截器,返回的是 RoutingStatementHandler 对象,在创建 RoutingStatementHandler 对象的时候,会根据不同的 statementType 创建不同的 StatementHandler,我这边是 PreparedStatementHandler 类型。

//RoutingStatementHandler
public class RoutingStatementHandler implements StatementHandler{

    private final StatementHandler delegate;
    
    public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    
        switch (ms.getStatementType()) {
            case STATEMENT:
                delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
                break;
            case PREPARED:
                delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
                break;
            case CALLABLE:
                delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
                break;
            default:
                throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
        }
    
    }

    ...
}

这个 RoutingStatementHandler 类是 StatementHandler 接口的实现,然后又持有一个 StatementHandler 的成员变量,又是装饰器模式。

这里不论是创建什么类型的 StatementHandler 都会创建它们的父类 BaseStatementHandler,而这个类的构造方法里创建了 ParameterHandlerResultSetHandler 这两个重要的组件,我们以后再讲。

再回过头来看 prepareStatement 方法

//SimpleExecutor
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);
    stmt = handler.prepare(connection, transaction.getTimeout());
    handler.parameterize(stmt);
    return stmt;
}

先获取链接,然后调用 handlerprepare 方法,上面我们知道 handlerRoutingStatementHandler,跟进去看下

//RoutingStatementHandler
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
    return delegate.prepare(connection, transactionTimeout);
}

根据上面知道这个 deleagtePreparedStatementHandler 类型的,跟进去看,会来到抽象父类 BaseStatementHandler

//BaseStatementHandler
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
    ErrorContext.instance().sql(boundSql.getSql());
    Statement statement = null;
    try {
        statement = instantiateStatement(connection);
        setStatementTimeout(statement, transactionTimeout);
        setFetchSize(statement);
        return statement;
    } catch (SQLException e) {
        closeStatement(statement);
        throw e;
    } catch (Exception e) {
        closeStatement(statement);
        throw new ExecutorException("Error preparing statement.  Cause: " + e, e);
    }
} 

然后 instantiateStatement 是抽象方法,实现在子类里,跟子类 PreparedStatementHandlerinstantiateStatement 方法

//PreparedStatementHandler
@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
    String sql = boundSql.getSql();
    if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
        String[] keyColumnNames = mappedStatement.getKeyColumns();
        if (keyColumnNames == null) {
            return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
        } else {
            return connection.prepareStatement(sql, keyColumnNames);
        }
    } else if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
        return connection.prepareStatement(sql);
    } else {
        return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
    }
}

我这里走到了第12行,connection.prepareStatement(sql)
然后一直往下,回到 SimpleExecutordoQuery 方法

//SimpleExecutor
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
        Configuration configuration = ms.getConfiguration();
        StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
        stmt = prepareStatement(handler, ms.getStatementLog());
        return handler.query(stmt, resultHandler);
    } finally {
        closeStatement(stmt);
    }
}

继续看第8行的 handler.query,最终会走到 PreparedStatementHandler 类的 query 方法里

//PreparedStatementHandler
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    return resultSetHandler.handleResultSets(ps);
}

可以看到 ps.execute 就是执行 sql 语句,然后 resultSetHandler.handleResultSets(ps) 处理返回结果,继续跟进

//DefaultResultSetHandler
@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
    ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

    final List<Object> multipleResults = new ArrayList<>();

    int resultSetCount = 0;
    ResultSetWrapper rsw = getFirstResultSet(stmt);

    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    validateResultMapsCount(rsw, resultMapCount);
    while (rsw != null && resultMapCount > resultSetCount) {
        ResultMap resultMap = resultMaps.get(resultSetCount);
        handleResultSet(rsw, resultMap, multipleResults, null);
        rsw = getNextResultSet(stmt);
        cleanUpAfterHandlingResultSet();
        resultSetCount++;
    }

    String[] resultSets = mappedStatement.getResultSets();
    if (resultSets != null) {
        while (rsw != null && resultSetCount < resultSets.length) {
            ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
            if (parentMapping != null) {
                String nestedResultMapId = parentMapping.getNestedResultMapId();
                ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
                handleResultSet(rsw, resultMap, null, parentMapping);
            }
            rsw = getNextResultSet(stmt);
            cleanUpAfterHandlingResultSet();
            resultSetCount++;
        }
    }

    return collapseSingleResultList(multipleResults);
}

第9行的 getFirstResultSet 里可以找到 stmt.getResultSet() ,15行 handleResultSet 跟进

//DefaultResultSetHandler
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
    try {
        if (parentMapping != null) {
            handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
        } else {
            if (resultHandler == null) {
                DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
                handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
                multipleResults.add(defaultResultHandler.getResultList());
            } else {
                handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
            }
        }
    } finally {
        // issue #228 (close resultsets)
        closeResultSet(rsw.getResultSet());
    }
}

第8行 handleRowValues 跟进

//DefaultResultSetHandler
public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
    if (resultMap.hasNestedResultMaps()) {
        ensureNoRowBounds();
        checkResultHandler();
        handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    } else {
        handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    }
}

第7行 handleRowValuesForSimpleResultMap 跟进

//DefaultResultSetHandler
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
            throws SQLException {
    DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
    ResultSet resultSet = rsw.getResultSet();
    skipRows(resultSet, rowBounds);
    while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
        ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
        Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
        storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
    }
}

第8行 getRowValue 跟进(这里也是 rowBounds 处理分页的地方,顺便提一下,后面有机会再展开)

//DefaultResultSetHandler
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
    final ResultLoaderMap lazyLoader = new ResultLoaderMap();
    Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
    if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
        final MetaObject metaObject = configuration.newMetaObject(rowValue);
        boolean foundValues = this.useConstructorMappings;
        if (shouldApplyAutomaticMappings(resultMap, false)) {
            foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
        }
        foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
        foundValues = lazyLoader.size() > 0 || foundValues;
        rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
    }
    return rowValue;
}

第8行 applyAutomaticMappings 跟进

//DefaultResultSetHandler
private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
    List<DefaultResultSetHandler.UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
    boolean foundValues = false;
    if (!autoMapping.isEmpty()) {
        for (DefaultResultSetHandler.UnMappedColumnAutoMapping mapping : autoMapping) {
            final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
            if (value != null) {
                foundValues = true;
            }
            if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) {
                // gcode issue #377, call setter on nulls (value is not 'found')
                metaObject.setValue(mapping.property, value);
            }
        }
    }
    return foundValues;
}

这个方法里的 createAutomaticMappings 方法会根据 resultSet 的元数据信息获取列名和列类型等信息。然后会将数据库表中的列名转化为 Java 对象属性名,然后调用 getResult 方法从 resultSet 中获取值,然后调用 metaObject.setValue 方法把值填充到属性中,我们跟进第6行 getResult

//BaseTypeHandler
@Override
public T getResult(ResultSet rs, String columnName) throws SQLException {
    try {
        return getNullableResult(rs, columnName);
    } catch (Exception e) {
        throw new ResultMapException("Error attempting to get column '" + columnName + "' from result set.  Cause: " + e, e);
    }
}

继续跟进

//StringTypeHandler
@Override
public String getNullableResult(ResultSet rs, String columnName)
        throws SQLException {
    return rs.getString(columnName);
}

最终在 StringTypeHandler 中找到了 rs.getString(columnName)

五、总结

本篇文章只是简单的带大家过一下整个 Mybatis 的流程,先有个初步的认识,后续有机会,再针对性的剖析细节文章来源地址https://www.toymoban.com/news/detail-492618.html

到了这里,关于Mybatis源码学习的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • FPGA学习实践之旅——前言及目录

    很早就有在博客中记录技术细节,分享一些自己体会的想法,拖着拖着也就到了现在。毕业至今已经半年有余,随着项目越来越深入,感觉可以慢慢进行总结工作了。趁着2024伊始,就先开个头吧,这篇博客暂时作为汇总篇,记录在这几个月以及之后从FPGA初学者到也算有一定

    2024年02月03日
    浏览(58)
  • 大数据、人工智能、机器学习、深度学习关系联系前言

    1.大数据和人工智能关系 2.机器学习、深度学习、人工智能关系 3.监督学习、无监督学习、半监督学习、强化学习、迁移学习关系 4.机器学习具体内容 1.数据驱动的人工智能 :人工智能系统需要大量的数据来进行训练和学习。大数据提供了海量的信息,可以用于训练机器学习

    2024年02月12日
    浏览(62)
  • 【C++初阶(一)】学习前言以及命名空间

    💓博主CSDN主页:杭电码农-NEO💓   ⏩专栏分类:C++初阶之路⏪   🚚代码仓库:NEO的学习日记🚚   🌹关注我🫵带你学习排序知识   🔝🔝 对于复杂的问题,规模较大的程序 需要高度的抽象和建模时 C语言不再适合应用于这种场景 于是在1982年 C++创始人 Bjarne Stroustrup 在C语言

    2024年02月11日
    浏览(53)
  • 【C++初阶(一)】学习前言 命名空间与IO流

    本专栏内容为:C++学习专栏,分为初阶和进阶两部分。 通过本专栏的深入学习,你可以了解并掌握C++。 💓博主csdn个人主页:小小unicorn ⏩专栏分类:C++ 🚚代码仓库:小小unicorn的代码仓库🚚 🌹🌹🌹关注我带你学习编程知识 C++是基于C语言而产生的,它既可以进行C语言的

    2024年02月08日
    浏览(97)
  • Linux tcp/ip 网路协议栈学习-00 前言

    Linux tcp/ip 网路协议栈学习-00 前言 目录 Linux  tcp/ip 网路协议栈学习-00 前言 (1)预备知识  (2)前置知识 (3)学习目标 (4)总结     (1)预备知识  好工具事半功倍,做任何事情都需要有方法和工具,同样,阅读 Linux 内核源码也是如此。由于当前内核源码非常庞大,学习上,不能一

    2024年04月26日
    浏览(45)
  • 【C++初阶】前言——C++的发展简述及学习方法分享

     ========================================================================= 主页点击直达: 个人主页 我的小仓库: 代码仓库 C语言偷着笑: C语言专栏 数据结构挨打小记: 初阶数据结构专栏 Linux被操作记: Linux专栏 LeetCode刷题掉发记: LeetCode刷题 算法: 算法专栏  C++头疼记: C++专栏 ====

    2024年02月08日
    浏览(63)
  • 学习Opencv(蝴蝶书/C++)——1. 前言 和 第1章.概述

    注,整体学习过程参考的内容: 从零学习 OpenCV4 2022年唐宇迪新全【OpenCV入门到实战】课程分享!原来学习OpenCV可以这么简单,超级通俗易懂!(附配套学习资料)-人工智能图像处理计算机视觉 《OpenCV轻松入门面向python》 细致理解 OpenCV opencv的全名:Open Source Computer Vision

    2024年02月03日
    浏览(52)
  • go语言:腾讯终于开源trpc框架——对trpc-go源码分析

    根据以上,我们对trpc框架进行三个方面的讲解 trpc配置文件采用yaml格式,文件默认目录在 ./trpc_go.yaml下,所有自定义配置都需要写在这个yaml文件中,所有支持用户自定义的配置都可以参考结构体config 简单来说干了以下几件事情 1.获取配置文件的默认目录,并且解析目录到

    2024年02月03日
    浏览(51)
  • 学习Transformer前言(Self Attention Multi head self attention)

    一直在做项目,也比较懒没有挤出时间去学习新的东西,感觉停滞很久了,好长一段时间都没有新的知识输入,早就需要就去学习transformer了,因此先来学习注意力机制,本文为个人的一个笔记总结。主要是基于李宏毅老师的一个课程视频笔记,论文原文,加上B站UP主的霹雳

    2024年02月02日
    浏览(54)
  • 推荐几个值得收藏的源码资源网站找源码必备

    CSDN下载-IT资源大本营 CSDN下载是一个提供学习资源、源码、在线学习视频、IT电子书、各类免费软件等下载服务的IT资源大本营,致力于为软件开发者提供知识传播、资源共享、共同学习的优质学习资源平台 Github 作为开源代码库以及版本控制系统,Github拥有超过900万开发者用户

    2024年02月13日
    浏览(46)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包