wenmo8 发布的文章

推荐一个介轻量ORM和全功能ORM的开源项目。

01 项目简介

RepoDB 提供了基本操作所需的方法,同时也提供了一些高级功能,如第二层缓存、跟踪、仓储、属性处理器和批量/大量操作。支持的数据库,包括SqlServer、SQLite、MySql和PostgreSql等。

02 关键特性

1、基础操作支持

RepoDB提供了执行基础数据库操作(如CRUD操作)所需的初步方法。

2、高级特性

第二层缓存:用于提高数据检索效率。

跟踪:用于监控和记录数据库操作。

仓储:用于封装数据库访问逻辑。

属性处理器:用于自定义属性映射和转换。

批量/大量操作:用于高效处理大量数据。

3、开发简单易用

使用RepoDB时,开发者用极少的代码调用高级操作。

4、批量操作与数据模型同步

在执行批量操作(如BulkInsert和BulkMerge)时,RepoDB会在执行后将生成的标识列(identity columns)值设置回数据模型,满足开发者重要用例需求。

5、多种执行方式支持

支持不同的执行方式,包括原子(atomic)、批处理(batch)和批量(bulk)操作。

03 使用方法

1、插入记录

//插入单条记录
var person = new Person
{
    Name = "John Doe",
    Age = 54,
    CreatedDateUtc = DateTime.UtcNow
};
using (var connection = new SqlConnection(ConnectionString))
{
    var id = connection.Insert(person);
}

//插入多条记录
var people = GetPeople(100);
using (var connection = new SqlConnection(ConnectionString))
{
    var rowsInserted = connection.InsertAll(people);
}

2、查询记录

//查询单条记录
using (var connection = new SqlConnection(ConnectionString))
{
    var person = connection.Query<Person>(e => e.Id == 10045);
}

//查询多条记录
using (var connection = new SqlConnection(ConnectionString))
{
    var people = connection.QueryAll<Person>();
}

3、更新记录

//更新单条记录
var person = new Person
{
    Id = 1,
    Name = "James Doe",
    Age = 55,
    DateInsertedUtc = DateTime.UtcNow
};
using (var connection = new SqlConnection(ConnectionString))
{
    var updatedRows = connection.Update<Person>(person);
}

//更新多条记录
var people = GetPeople(100);
people
    .AsList()
    .ForEach(p => p.Name = $"{p.Name} (Updated)");
using (var connection = new SqlConnection(ConnectionString))
{
    var updatedRows = connection.UpdateAll<Person>(people);
}

4、删除记录

//删除单条记录
using (var connection = new SqlConnection(ConnectionString))
{
    var deletedRows = connection.Delete<Person>(10045);
}

//删除多条记录
using (var connection = new SqlConnection(ConnectionString))
{
    var deletedRows = connection.DeleteAll<Person>();
}

04 项目地址

https://github.com/mikependon/RepoDB

- End -

更多开源项目请查看

一个专注推荐优秀.Net开源项目的榜单

推荐阅读

2个零基础入门框架教程!

一款可以替代Navicat的数据库管理工具

CSCore:一个.Net功能强大且灵活的开源音频处理库

Blazor开源UI简洁组件:10个热门.Net开源项目推荐!

ExcelDataReader:一个.Net高性能Excel开源读取器

IOC容器的初始化整体过程

Spring是如何实现将资源配置(以xml配置为例)通过加载,解析,生成BeanDefination并注册到IoC容器中的?这主要会经过以下 4 步:

  1. 从XML中读取配置文件,并将配置文件转换为Document
  2. 再将Document中的 bean标签解析成 BeanDefinition,如解析 property 元素, 并注入到 BeanDefinition 实例中。
  3. 将 BeanDefinition 注册到容器 BeanDefinitionMap 中。
  4. BeanFactory 根据 BeanDefinition 的定义信息创建实例化和初始化 bean。

启动的入口

对于xml配置的Spring应用,在main()方法中实例化ClasspathXmlApplicationContext即可创建一个IoC容器。可以从这个构造方法开始,探究一下IoC容器的初始化过程。

 // create and configure beans
ApplicationContext context = new ClassPathXmlApplicationContext("aspects.xml", "daos.xml", "services.xml");
public ClassPathXmlApplicationContext(String... configLocations) throws BeansException {
    this(configLocations, true, (ApplicationContext)null);
}

public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, @Nullable ApplicationContext parent) throws BeansException {
    // 设置Bean资源加载器
    super(parent);

    // 设置配置路径
    this.setConfigLocations(configLocations);

    // 初始化容器
    if (refresh) {
        this.refresh();
    }
}

这里的作用就是加载了一个解析配置文件路径的加载器,然后通过系统环境变量拿到这个配置文件,进行一些配置文件的去空格,转换表达式等等操作(注意,这里没有进行解析);最后通过 refresh方法 完成了几乎所有的工作。

设置资源解析器和环境

调用父类容器AbstractApplicationContext的构造方法(
super(parent)
方法)为容器设置好Bean资源加载器

public AbstractApplicationContext(@Nullable ApplicationContext parent) {
    // 默认构造函数初始化容器id, name, 状态 以及 资源解析器
    this();

    // 将父容器的Environment合并到当前容器
    this.setParent(parent);
}

通过AbstractApplicationContext默认构造函数初始化容器id, name, 状态 以及 资源解析器

public AbstractApplicationContext() {
    this.logger = LogFactory.getLog(this.getClass());
    this.id = ObjectUtils.identityToString(this);
    this.displayName = ObjectUtils.identityToString(this);
    this.beanFactoryPostProcessors = new ArrayList();
    this.active = new AtomicBoolean();
    this.closed = new AtomicBoolean();
    this.startupShutdownMonitor = new Object();
    this.applicationStartup = ApplicationStartup.DEFAULT;
    this.applicationListeners = new LinkedHashSet();
    this.resourcePatternResolver = this.getResourcePatternResolver();
}
// Spring资源加载器
protected ResourcePatternResolver getResourcePatternResolver() {
    return new PathMatchingResourcePatternResolver(this);
}

通过AbstractApplicationContext的
setParent(parent)
方法将父容器的Environment合并到当前容器

public void setParent(@Nullable ApplicationContext parent) {
    this.parent = parent;
    if (parent != null) {
        Environment parentEnvironment = parent.getEnvironment();
        if (parentEnvironment instanceof ConfigurableEnvironment) {
            this.getEnvironment().merge((ConfigurableEnvironment)parentEnvironment);
        }
    }
}

设置配置路径

在设置容器的资源加载器之后,接下来FileSystemXmlApplicationContet执行setConfigLocations方法通过调用其父类AbstractRefreshableConfigApplicationContext的方法进行对Bean定义资源文件的定位

public void setConfigLocations(@Nullable String... locations) {
    if (locations != null) {
        Assert.noNullElements(locations, "Config locations must not be null");
        this.configLocations = new String[locations.length];

        for(int i = 0; i < locations.length; ++i) {
            // 解析配置路径
            this.configLocations[i] = this.resolvePath(locations[i]).trim();
        }
    } else {
        this.configLocations = null;
    }
}
protected String resolvePath(String path) {
    // 从上一步Environment中解析
    return this.getEnvironment().resolveRequiredPlaceholders(path);
}

启动的主体流程

Spring IoC容器对Bean定义资源的载入是从refresh()函数开始的,refresh()是一个
模板方法

refresh()方法的作用是:在创建IoC容器前,如果已经有容器存在,则需要把已有的容器销毁和关闭,以保证在refresh之后使用的是新建立起来的IoC容器。refresh的作用类似于对IoC容器的重启,在新建立好的容器中对容器进行初始化,对Bean定义资源进行载入。

@Override
public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");

        //① 容器刷新前的准备工作
        prepareRefresh();

        //② 调用子类容器的refreshBeanFactory()方法,启动容器载入Bean定义资源文件的过程
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

        //③ 准备Bean工厂
        prepareBeanFactory(beanFactory);

        try {
            //④ 子类扩展BeanFactory
            postProcessBeanFactory(beanFactory);

            StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
            //⑤ 执行增强方法
            invokeBeanFactoryPostProcessors(beanFactory);

            //⑥ 注册BeanPostProcessors,但没执行
            registerBeanPostProcessors(beanFactory);
            beanPostProcess.end();

            //⑦ 执行国际化内容
            initMessageSource();

            //⑧ 创建了一个多播器,为添加`Listener`提供支持。
            initApplicationEventMulticaster();

            //⑨ 子类扩展
            onRefresh();

            //⑩ Check for listener beans and register them.
            registerListeners();

            //⑪ Instantiate all remaining (non-lazy-init) singletons.
            finishBeanFactoryInitialization(beanFactory);

            // Last step: publish corresponding event.
            finishRefresh();
        }

        catch (BeansException ex) {
            if (logger.isWarnEnabled()) {
                logger.warn("Exception encountered during context initialization - " +
                        "cancelling refresh attempt: " + ex);
            }

            // Destroy already created singletons to avoid dangling resources.
            destroyBeans();

            // Reset 'active' flag.
            cancelRefresh(ex);

            // Propagate exception to caller.
            throw ex;
        }

        finally {
            // Reset common introspection caches in Spring's core, since we
            // might not ever need metadata for singleton beans anymore...
            resetCommonCaches();
            contextRefresh.end();
        }
    }
}@Override
public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");

        //① 容器刷新前的准备工作
        prepareRefresh();

        //② 调用子类容器的refreshBeanFactory()方法,启动容器载入Bean定义资源文件的过程
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

        //③ 准备Bean工厂
        prepareBeanFactory(beanFactory);

        try {
            //④ 子类扩展BeanFactory
            postProcessBeanFactory(beanFactory);

            StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
            //⑤ 执行增强方法
            invokeBeanFactoryPostProcessors(beanFactory);

            //⑥ 注册BeanPostProcessors,但没执行
            registerBeanPostProcessors(beanFactory);
            beanPostProcess.end();

            //⑦ 执行国际化内容
            initMessageSource();

            //⑧ 创建了一个多播器,为添加`Listener`提供支持。
            initApplicationEventMulticaster();

            //⑨ 子类扩展
            onRefresh();

            //⑩ Check for listener beans and register them.
            registerListeners();

            //⑪ Instantiate all remaining (non-lazy-init) singletons.
            finishBeanFactoryInitialization(beanFactory);

            // Last step: publish corresponding event.
            finishRefresh();
        }

        catch (BeansException ex) {
            if (logger.isWarnEnabled()) {
                logger.warn("Exception encountered during context initialization - " +
                        "cancelling refresh attempt: " + ex);
            }

            // Destroy already created singletons to avoid dangling resources.
            destroyBeans();

            // Reset 'active' flag.
            cancelRefresh(ex);

            // Propagate exception to caller.
            throw ex;
        }

        finally {
            // Reset common introspection caches in Spring's core, since we
            // might not ever need metadata for singleton beans anymore...
            resetCommonCaches();
            contextRefresh.end();
        }
    }
}

这里的设计上是一个非常典型的资源类加载处理型的思路,头脑中需要形成如下图的
顶层思路
(而不是只停留在流水式的方法上面):

  • 模板方法设计模式
    ,模板方法中使用典型的
    钩子方法

  • 具体的初始化加载方法
    插入到钩子方法之间
  • 将初始化的阶段封装,用来记录当前初始化到什么阶段;常见的设计是xxxPhase/xxxStage;
  • 资源加载初始化有失败等处理,必然是
    try/catch/finally
    ...

① prepareRefresh 准备上下文环境

protected void prepareRefresh() {
    //1、设置启动时间、一些标志位
    
    //设置容器的启动时间
    this.startupDate = System.currentTimeMillis();
    //设置容器的启动时间
    this.closed.set(false);
    //设置容器的激活标志位
    this.active.set(true);
    if (this.logger.isDebugEnabled()) {
        if (this.logger.isTraceEnabled()) {
            this.logger.trace("Refreshing " + this);
        } else {
            this.logger.debug("Refreshing " + this.getDisplayName());
        }
    }
    
    //2、初始化占位符属性源,留给子类实现
    this.initPropertySources();
    
    //3、验证并获取环境对象,验证需要的属性是否都已经放入环境对象中
    this.getEnvironment().validateRequiredProperties();
    
    //4、判断刷新前的应用程序监听器集合是否为空,如果为空,则将监听器添加到此集合中
    if (this.earlyApplicationListeners == null) {
        this.earlyApplicationListeners = new LinkedHashSet(this.applicationListeners);
    } else {
        //如果不为空,清空集合
        this.applicationListeners.clear();
        this.applicationListeners.addAll(this.earlyApplicationListeners);
    }

    this.earlyApplicationEvents = new LinkedHashSet();
}

② obtainFreshBeanFactory 创建工厂

这个方法主要就是创建了一个工厂
BeanFactory
,并且解析了配置文件,加载了
Bean
定义信息

AbstractApplicationContext的obtainFreshBeanFactory()方法调用子类容器的refreshBeanFactory()方法,启动容器载入Bean定义资源文件的过程,代码如下:

protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
    // 这里使用了委派设计模式,父类定义了抽象的refreshBeanFactory()方法,具体实现调用子类容器的refreshBeanFactory()方法
    refreshBeanFactory();
    return getBeanFactory();
}

AbstractApplicationContext类中只抽象定义了refreshBeanFactory()方法,如下:

//org.springframework.context.support.AbstractApplicationContext#refreshBeanFactory
protected abstract void refreshBeanFactory() throws BeansException, IllegalStateException;

容器真正调用的是其子类AbstractRefreshableApplicationContext实现的refreshBeanFactory()方法;在创建IoC容器前,如果已经有容器存在,则需要把已有的容器销毁和关闭,以保证在refresh之后使用的是新建立起来的IoC容器。方法的源码如下:

protected final void refreshBeanFactory() throws BeansException {
    // 如果已经有容器存在,则需要把已有的容器销毁和关闭,以保证在refresh之后使用的是新建立起来的IoC容器
    if (hasBeanFactory()) {
        destroyBeans();
        closeBeanFactory();
    }
    try {
        // 创建DefaultListableBeanFactory,并调用loadBeanDefinitions(beanFactory)装载bean定义
        DefaultListableBeanFactory beanFactory = createBeanFactory();
        beanFactory.setSerializationId(getId());
        customizeBeanFactory(beanFactory); // 对IoC容器进行定制化,如设置启动参数,开启注解的自动装配等 
        loadBeanDefinitions(beanFactory); // 调用载入Bean定义的方法,主要这里又使用了一个委派模式,在当前类中只定义了抽象的loadBeanDefinitions方法,具体的实现调用子类容器  
        this.beanFactory = beanFactory;
    }
    catch (IOException ex) {
        throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
    }
}

loadBeanDefinitions

AbstractRefreshableApplicationContext 中只定义了抽象的 loadBeanDefinitions 方法,容器真正调用的是其子类 AbstractXmlApplicationContext 对该方法的实现,AbstractXmlApplicationContext 的主要源码如下:

protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
    // 创建XmlBeanDefinitionReader,即创建Bean读取器,并通过回调设置到容器中去,容器使用该读取器读取Bean定义资源  
    XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

    // 配置上下文的环境,资源加载器、解析器
    beanDefinitionReader.setEnvironment(this.getEnvironment());
    beanDefinitionReader.setResourceLoader(this);
    beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this)); // 为Bean读取器设置SAX xml解析器

    // 允许子类自行初始化(比如校验机制),并提供真正的加载方法
    initBeanDefinitionReader(beanDefinitionReader); // 当Bean读取器读取Bean定义的Xml资源文件时,启用Xml的校验机制  
    loadBeanDefinitions(beanDefinitionReader);
}

protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
    // 加载XML配置方式里的Bean定义的资源
    Resource[] configResources = getConfigResources();
    if (configResources != null) {
        reader.loadBeanDefinitions(configResources);
    }
    // 加载构造函数里配置的Bean配置文件,即{"aspects.xml", "daos.xml", "services.xml"}
    String[] configLocations = getConfigLocations();
    if (configLocations != null) {
        reader.loadBeanDefinitions(configLocations);
    }
}

Xml Bean读取器(XmlBeanDefinitionReader)调用其父类AbstractBeanDefinitionReader的 reader.loadBeanDefinitions方法读取Bean定义资源。

由于这里使用 ClassPathXmlApplicationContext 作为例子分析,因此 getConfigResources 的返回值为null,因此程序执行reader.loadBeanDefinitions(configLocations)分支。

AbstractBeanDefinitionReader读取Bean定义资源

AbstractBeanDefinitionReader的loadBeanDefinitions方法源码如下:

@Override
public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
    return loadBeanDefinitions(location, null);
}

public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
    ResourceLoader resourceLoader = getResourceLoader();
    if (resourceLoader == null) {
        throw new BeanDefinitionStoreException(
                "Cannot load bean definitions from location [" + location + "]: no ResourceLoader available");
    }

    // 模式匹配类型的解析器,这种方式是加载多个满足匹配条件的资源
    if (resourceLoader instanceof ResourcePatternResolver) {
        try {
            // 获取到要加载的资源
            Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
            int count = loadBeanDefinitions(resources); // 委派调用其子类XmlBeanDefinitionReader的方法,实现加载功能  
            if (actualResources != null) {
                Collections.addAll(actualResources, resources);
            }
            if (logger.isTraceEnabled()) {
                logger.trace("Loaded " + count + " bean definitions from location pattern [" + location + "]");
            }
            return count;
        }
        catch (IOException ex) {
            throw new BeanDefinitionStoreException(
                    "Could not resolve bean definition resource pattern [" + location + "]", ex);
        }
    }
    else {
        // 只能通过绝对路径URL加载单个资源.
        Resource resource = resourceLoader.getResource(location);
        int count = loadBeanDefinitions(resource);
        if (actualResources != null) {
            actualResources.add(resource);
        }
        if (logger.isTraceEnabled()) {
            logger.trace("Loaded " + count + " bean definitions from location [" + location + "]");
        }
        return count;
    }
}

从对AbstractBeanDefinitionReader的loadBeanDefinitions方法源码分析可以看出该方法做了以下两件事:

  • 首先,调用资源加载器的获取资源方法resourceLoader.getResource(location),获取到要加载的资源。
  • 其次,真正执行加载功能是其子类XmlBeanDefinitionReader的loadBeanDefinitions方法。

XmlBeanDefinitionReader加载Bean定义资源

XmlBeanDefinitionReader的loadBeanDefinitions方法主要是调用了 loadBeanDefinitions(Resource …) 方法,可以看到代表bean文件的资源定义以后的载入过程。

/**
 * 本质上是加载XML配置的Bean。
 * @param inputSource the SAX InputSource to read from
 * @param resource the resource descriptor for the XML file
 */
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
        throws BeanDefinitionStoreException {

    try {
        Document doc = doLoadDocument(inputSource, resource); // 将Bean定义资源转换成Document对象
        int count = registerBeanDefinitions(doc, resource);
        if (logger.isDebugEnabled()) {
            logger.debug("Loaded " + count + " bean definitions from " + resource);
        }
        return count;
    }
    catch (BeanDefinitionStoreException ex) {
        throw ex;
    }
    catch (SAXParseException ex) {
        throw new XmlBeanDefinitionStoreException(resource.getDescription(),
                "Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);
    }
    catch (SAXException ex) {
        throw new XmlBeanDefinitionStoreException(resource.getDescription(),
                "XML document from " + resource + " is invalid", ex);
    }
    catch (ParserConfigurationException ex) {
        throw new BeanDefinitionStoreException(resource.getDescription(),
                "Parser configuration exception parsing XML from " + resource, ex);
    }
    catch (IOException ex) {
        throw new BeanDefinitionStoreException(resource.getDescription(),
                "IOException parsing XML document from " + resource, ex);
    }
    catch (Throwable ex) {
        throw new BeanDefinitionStoreException(resource.getDescription(),
                "Unexpected exception parsing XML document from " + resource, ex);
    }
}

// 使用配置的DocumentLoader加载XML定义文件为Document.
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
    return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
            getValidationModeForResource(resource), isNamespaceAware());
}

通过源码分析,载入Bean定义资源文件的最后一步是将Bean定义资源转换为Document对象,该过程由documentLoader实现

DocumentLoader将Bean定义资源转换为Document对象

DocumentLoader将Bean定义资源转换成Document对象的源码如下:

// 使用标准的JAXP将载入的Bean定义资源转换成document对象
@Override
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
        ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {

    // 创建文件解析器工厂
    DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
    if (logger.isTraceEnabled()) {
        logger.trace("Using JAXP provider [" + factory.getClass().getName() + "]");
    }
    // 创建文档解析器
    DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
    return builder.parse(inputSource); // 解析
}

protected DocumentBuilderFactory createDocumentBuilderFactory(int validationMode, boolean namespaceAware)
        throws ParserConfigurationException {

    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    factory.setNamespaceAware(namespaceAware);

    // 设置解析XML的校验
    if (validationMode != XmlValidationModeDetector.VALIDATION_NONE) {
        factory.setValidating(true);
        if (validationMode == XmlValidationModeDetector.VALIDATION_XSD) {
            // Enforce namespace aware for XSD...
            factory.setNamespaceAware(true);
            try {
                factory.setAttribute(SCHEMA_LANGUAGE_ATTRIBUTE, XSD_SCHEMA_LANGUAGE);
            }
            catch (IllegalArgumentException ex) {
                ParserConfigurationException pcex = new ParserConfigurationException(
                        "Unable to validate using XSD: Your JAXP provider [" + factory +
                        "] does not support XML Schema. Are you running on Java 1.4 with Apache Crimson? " +
                        "Upgrade to Apache Xerces (or Java 1.5) for full XSD support.");
                pcex.initCause(ex);
                throw pcex;
            }
        }
    }

    return factory;
}

该解析过程调用JavaEE标准的JAXP标准进行处理。

至此Spring IoC容器根据定位的Bean定义资源文件,将其加载读入并转换成为Document对象过程完成。

接下来继续分析Spring IoC容器将载入的Bean定义资源文件转换为Document对象之后,是如何将其解析为Spring IoC管理的Bean对象并将其注册到容器中的。

XmlBeanDefinitionReader解析载入的Bean定义资源文件

XmlBeanDefinitionReader类中的doLoadBeanDefinitions方法是从特定XML文件中实际载入Bean定义资源的方法,该方法在载入Bean定义资源之后将其转换为Document对象,接下来调用registerBeanDefinitions启动Spring IoC容器对Bean定义的解析过程,registerBeanDefinitions方法源码如下:

// 按照Spring的Bean语义要求将Bean定义资源解析并转换为容器内部数据结构 
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
    BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
    int countBefore = getRegistry().getBeanDefinitionCount();
    // 解析过程入口,这里使用了委派模式,具体的解析实现过程由实现类DefaultBeanDefinitionDocumentReader完成  
    documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
    return getRegistry().getBeanDefinitionCount() - countBefore;  // 返回此次解析了多少个对象
}

// 创建BeanDefinitionDocumentReader对象,解析Document对象  
protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader() {
    return BeanUtils.instantiateClass(this.documentReaderClass);
}

/**
 * Create the {@link XmlReaderContext} to pass over to the document reader.
 */
public XmlReaderContext createReaderContext(Resource resource) {
    return new XmlReaderContext(resource, this.problemReporter, this.eventListener,
            this.sourceExtractor, this, getNamespaceHandlerResolver());
}

Bean定义资源的载入解析分为以下两个过程:

  • 首先,通过调用XML解析器将Bean定义资源文件转换得到Document对象,但是这些Document对象并没有按照Spring的Bean规则进行解析。这一步是载入的过程
  • 其次,在完成通用的XML解析之后,按照Spring的Bean规则对Document对象进行解析。

在这个方法中很好地应用了面向对象中单一职责的原则,将逻辑处理委托给单一的类进行处理,而这个逻辑处理类就是BeanDefinitionDocumentReader。BeanDefinitionDocumentReader是一个接口,而实例化的工作是在createBeanDefinitionDocumentReader()中完成的,而通过此方法,BeanDefinitionDocumentReader真正的类型其实已经是DefaultBeanDefinitionDocumentReader了。按照Spring的Bean规则对Document对象解析的过程是在接口BeanDefinitionDocumentReader的实现类DefaultBeanDefinitionDocumentReader中实现的。

DefaultBeanDefinitionDocumentReader对Bean定义的Document对象解析

BeanDefinitionDocumentReader接口通过registerBeanDefinitions方法调用其实现类DefaultBeanDefinitionDocumentReader对Document对象进行解析,解析的代码如下:

@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
    this.readerContext = readerContext;
    doRegisterBeanDefinitions(doc.getDocumentElement());
}

// 注册<beans/>配置的Beans
@SuppressWarnings("deprecation")  // for Environment.acceptsProfiles(String...)
protected void doRegisterBeanDefinitions(Element root) {
    // Any nested <beans> elements will cause recursion in this method. In
    // order to propagate and preserve <beans> default-* attributes correctly,
    // keep track of the current (parent) delegate, which may be null. Create
    // the new (child) delegate with a reference to the parent for fallback purposes,
    // then ultimately reset this.delegate back to its original (parent) reference.
    // this behavior emulates a stack of delegates without actually necessitating one.
    BeanDefinitionParserDelegate parent = this.delegate;
    this.delegate = createDelegate(getReaderContext(), root, parent);

    if (this.delegate.isDefaultNamespace(root)) {
        //专门对 profile 标签进行解析
        String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
        if (StringUtils.hasText(profileSpec)) {
            String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
                    profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
            // We cannot use Profiles.of(...) since profile expressions are not supported
            // in XML config. See SPR-12458 for details.
            //
            if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec +
                            "] not matching: " + getReaderContext().getResource());
                }
                return;
            }
        }
    }

    
    preProcessXml(root);//解析前处理,留给子类实现
    parseBeanDefinitions(root, this.delegate); // 从Document的根元素开始进行Bean定义的Document对象  
    postProcessXml(root);//解析后处理,留给子类实现

    this.delegate = parent;
}

这里我们注意到在注册Bean的最开始是对PROFILE_ATTRIBUTE属性的解析,有了 profile 这个特性我们就可以同时在配置文件中部署两套配置来适用于生产环境和开发环境,这样可以方便的进行切换开发、部署环境,最常用的就是更换不同的数据库。

首先程序会获取beans节点是否定义了profile属性,如果定义了则会需要到环境变量中去寻找,因为profile是可以同时指定多个的,需要程序对其拆分,并解析每个profile是都符合环境变量中所定义的,不定义则不会浪费性能去解析。

注意:跟进 preProcessXml(root) 和 postProcessXml(root) 后发现代码是空的。
记住,一个类要么是面向继承设计,要么是final修饰的。而这个类并不是final修饰的,那么就是面向继承设计的,显然这里是用到了
模板方法设计模式
,如果继承自DefaultBeanDefinitionDocumentReader的子类需要在Bean解析前后做一些处理的话,那么只需要重写这两个方法即可

BeanDefinitionParserDelegate解析Bean定义资源文件生成BeanDefinition

处理了profile后就可以进行XML的读取了,跟踪代码进入parseBeanDefinitions(root, this.delegate)。

/**
    * Parse the elements at the root level in the document:
    * "import", "alias", "bean".
    * @param root the DOM root element of the document
    */
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
    if (delegate.isDefaultNamespace(root)) {
        NodeList nl = root.getChildNodes();
        for (int i = 0; i < nl.getLength(); i++) {
            Node node = nl.item(i);
            if (node instanceof Element) {
                Element ele = (Element) node;
                if (delegate.isDefaultNamespace(ele)) {
                    //对默认标签解析
                    parseDefaultElement(ele, delegate);
                }
                else {
                    //对默认自定义标签的解析
                    delegate.parseCustomElement(ele);
                }
            }
        }
    }
    else {
        delegate.parseCustomElement(root);
    }
}

上面的代码看起来逻辑还是蛮清晰的,因为在Spring的XML配置里面有两大类Bean声明,一个是默认的,如:

<bean id="test" class="test.TestBean"/>

另一类就是自定义的,如:

<tx:annotation-driven/>

而两种方式的读取及解析差别是非常大的,如果采用Spring默认的配置,Spring当然知道该怎么做,但是如果是自定义的,那么就需要用户实现一些接口及配置了。对于根节点或者子节点如果是默认命名空间的话则采用parseDefaultElement方法进行解析,否则使用delegate.parseCustomElement方法对自定义命名空间进行解析。而判断是否默认命名空间还是自定义命名空间的办法其实是使用node.getNamespaceURI()获取命名空间,并与Spring中固定的命名空间
http://www.Springframework.org/schema/beans
进行比对。如果一致则认为是默认,否则就认为是自定义。

默认标签的解析
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
      
    // 如果元素节点是<Import>导入元素,进行导入解析
    if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
        importBeanDefinitionResource(ele);
    }
    // 如果元素节点是<Alias>别名元素,进行别名解析 
    else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
        processAliasRegistration(ele);
    }
    // 如果元素节点<Bean>元素, 按照Spring的Bean规则解析元素  
    else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
        processBeanDefinition(ele, delegate);
    }
    // 如果元素节点<Beans>元素,即它是嵌套类型的
    else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
        // 递归解析
        doRegisterBeanDefinitions(ele);
    }
}

委托BeanDefinitionDelegate类的parseBeanDefinitionElement方法进行元素解析,返回BeanDefinitionHolder类型的实例bdHolder,经过这个方法后,bdHolder实例已经包含我们配置文件中配置的各种属性了,例如class、name、id、alias之类的属性。

/**
* Process the given bean element, parsing the bean definition
* and registering it with the registry.
*/
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
    BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
    if (bdHolder != null) {
        bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
        try {
            // 注册最终的装饰实例
            BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
        }
        catch (BeanDefinitionStoreException ex) {
            getReaderContext().error("Failed to register bean definition with name '" +
                    bdHolder.getBeanName() + "'", ele, ex);
        }
        // Send registration event.
        getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
    }
}

parseBeanDefinitionElement的解析方法 就不一一展开了,无非就是解析XML各种元素,来生成BeanDefinition。解析的过程与mybatis解析xml文件同理,详情可以看
这篇文章

自定义标签的解析

在实际项目中,较少进行自定义标签,因此这里不展开描述了。

解析过后的BeanDefinition在IoC容器中的注册

Document对象的解析后得到封装 BeanDefinition 的 BeanDefinitionHold 对象,然后调用 BeanDefinitionReaderUtils 的 registerBeanDefinition 方法向IoC容器注册解析的Bean,BeanDefinitionReaderUtils的注册的源码如下:

// 通过BeanDefinitionRegistry将BeanDefinitionHolder注册到BeanFactory
public static void registerBeanDefinition(
        BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
        throws BeanDefinitionStoreException {

    // Register bean definition under primary name.
    String beanName = definitionHolder.getBeanName();
    registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

    // Register aliases for bean name, if any.
    String[] aliases = definitionHolder.getAliases();
    if (aliases != null) {
        for (String alias : aliases) {
            registry.registerAlias(beanName, alias);
        }
    }
}

当调用BeanDefinitionReaderUtils向IoC容器注册解析的BeanDefinition时,真正完成注册功能的是DefaultListableBeanFactory。

DefaultListableBeanFactory向IoC容器注册解析后的BeanDefinition

IOC容器本质上就是一个beanDefinitionMap, 注册即将BeanDefinition put到map中

/** Map of bean definition objects, keyed by bean name. */
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);

/** Map from bean name to merged BeanDefinitionHolder. */
private final Map<String, BeanDefinitionHolder> mergedBeanDefinitionHolders = new ConcurrentHashMap<>(256);


@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
        throws BeanDefinitionStoreException {

    Assert.hasText(beanName, "Bean name must not be empty");
    Assert.notNull(beanDefinition, "BeanDefinition must not be null");

    if (beanDefinition instanceof AbstractBeanDefinition) {
        try {
            ((AbstractBeanDefinition) beanDefinition).validate();
        }
        catch (BeanDefinitionValidationException ex) {
            throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
                    "Validation of bean definition failed", ex);
        }
    }

    BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
    // 如果已经注册
    if (existingDefinition != null) {
        // 检查是否可以覆盖
        if (!isAllowBeanDefinitionOverriding()) {
            throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
        }
        else if (existingDefinition.getRole() < beanDefinition.getRole()) {
            // e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
            if (logger.isInfoEnabled()) {
                logger.info("Overriding user-defined bean definition for bean '" + beanName +
                        "' with a framework-generated bean definition: replacing [" +
                        existingDefinition + "] with [" + beanDefinition + "]");
            }
        }
        else if (!beanDefinition.equals(existingDefinition)) {
            if (logger.isDebugEnabled()) {
                logger.debug("Overriding bean definition for bean '" + beanName +
                        "' with a different definition: replacing [" + existingDefinition +
                        "] with [" + beanDefinition + "]");
            }
        }
        else {
            if (logger.isTraceEnabled()) {
                logger.trace("Overriding bean definition for bean '" + beanName +
                        "' with an equivalent definition: replacing [" + existingDefinition +
                        "] with [" + beanDefinition + "]");
            }
        }
        // 覆盖
        this.beanDefinitionMap.put(beanName, beanDefinition);
    }
    else {
        if (hasBeanCreationStarted()) {
            // Cannot modify startup-time collection elements anymore (for stable iteration)
            synchronized (this.beanDefinitionMap) {
                this.beanDefinitionMap.put(beanName, beanDefinition);
                List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
                updatedDefinitions.addAll(this.beanDefinitionNames);
                updatedDefinitions.add(beanName);
                this.beanDefinitionNames = updatedDefinitions;
                removeManualSingletonName(beanName);
            }
        }
        else {
            // Still in startup registration phase
            this.beanDefinitionMap.put(beanName, beanDefinition);
            this.beanDefinitionNames.add(beanName);
            removeManualSingletonName(beanName);
        }
        //重置所有已经注册过的BeanDefinition的缓存  
        this.frozenBeanDefinitionNames = null;
    }

    if (existingDefinition != null || containsSingleton(beanName)) {
        resetBeanDefinition(beanName);
    }
    else if (isConfigurationFrozen()) {
        clearByTypeCache();
    }
}

至此,Bean定义资源文件中配置的Bean被解析过后,已经注册到IoC容器中,被容器管理起来,真正完成了IoC容器初始化所做的全部工作。现在IoC容器中已经建立了整个Bean的配置信息,这些 BeanDefinition 信息已经可以使用,并且可以被检索,IoC容器的作用就是对这些注册的Bean定义信息进行处理和维护。这些的注册的Bean定义信息是IoC容器控制反转的基础,正是有了这些注册的数据,容器才可以进行依赖注入。

小结

现在通过上面的代码,总结一下IOC容器初始化的基本步骤:

  • 初始化的入口在容器实现中的 refresh()调用来完成
  • 对 bean 定义载入 IOC 容器使用的方法是 loadBeanDefinition,其中的大致过程如下:
    • 通过 ResourceLoader 来完成资源文件位置的定位,DefaultResourceLoader 是默认的实现,同时上下文本身就给出了 ResourceLoader 的实现,可以从类路径,文件系统, URL 等方式来定为资源位置。如果是 XmlBeanFactory作为 IOC 容器,那么需要为它指定 bean 定义的资源,也就是说 bean 定义文件时通过抽象成 Resource 来被 IOC 容器处理的
    • 通过 BeanDefinitionReader来完成定义信息的解析和 Bean 信息的注册, 往往使用的是XmlBeanDefinitionReader 来解析 bean 的 xml 定义文件 — 实际的处理过程是委托给 BeanDefinitionParserDelegate 来完成的,从而得到 bean 的定义信息,这些信息在 Spring 中使用 BeanDefinition 对象来表示
    • 容器解析得到 BeanDefinition 以后,需要把它在 IOC 容器中注册,这由 IOC 实现 BeanDefinitionRegistry 接口来实现。注册过程就是在 IOC 容器内部维护的一个HashMap 来保存得到的 BeanDefinition 的过程。这个 HashMap 是 IoC 容器持有 bean 信息的场所,以后对 bean 的操作都是围绕这个HashMap 来实现的.
  • 最后可以通过 BeanFactory 和 ApplicationContext 来享受到 Spring IOC 的服务了,在使用 IOC 容器的时候,除了少量粘合代码,绝大多数以正确 IoC 风格编写的应用程序代码完全不用关心如何到达工厂,因为容器将把这些对象与容器管理的其他对象钩在一起。基本的策略是把工厂放到已知的地方,最好是放在对预期使用的上下文有意义的地方,以及代码将实际需要访问工厂的地方。 Spring 本身提供了对声明式载入 web 应用程序用法的应用程序上下文,并将其存储在ServletContext 中的框架实现。

③ prepareBeanFactory 准备Bean工厂


BeanFactory
准备一些环境,方便在实例化的时候使用,同时添加容器自己的
BeanPostProcessor

// org.springframework.context.support.AbstractApplicationContext#prepareBeanFactory
protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
    //设置bean工厂使用上下文的类加载器
    beanFactory.setBeanClassLoader(this.getClassLoader());
    //设置bean表达式解析器,默认可以使用#{bean.xxx}的形式来调用相关属性值
    beanFactory.setBeanExpressionResolver(new StandardBeanExpressionResolver(beanFactory.getBeanClassLoader()));
    //设置属性编辑器
    beanFactory.addPropertyEditorRegistrar(new ResourceEditorRegistrar(this, this.getEnvironment()));
    
    //添加BeanPostProcessor -> Applicat ionContextAwareProcessor
    // AppLicationContextAwareProcessor在 postProcessBeforeInitialization()方法调用invokeAwareInterfaces(bean)
    // 主要外理实现Aware接的bean在被初始化之前能够获取一些对应的资源,如ApplicationContext. ResourceLoader、 Environment
    beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));
    
    //忽略以下接口的自动装配依赖,因为前面的ApplicationContextAwareProcessor已经实现了类似功能
    beanFactory.ignoreDependencyInterface(EnvironmentAware.class);
    beanFactory.ignoreDependencyInterface(EmbeddedValueResolverAware.class);
    beanFactory.ignoreDependencyInterface(ResourceLoaderAware.class);
    beanFactory.ignoreDependencyInterface(ApplicationEventPublisherAware.class);
    beanFactory.ignoreDependencyInterface(MessageSourceAware.class);
    beanFactory.ignoreDependencyInterface(ApplicationContextAware.class);
    beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory);
    beanFactory.registerResolvableDependency(ResourceLoader.class, this);
    beanFactory.registerResolvableDependency(ApplicationEventPublisher.class, this);
    beanFactory.registerResolvableDependency(ApplicationContext.class, this);
    
    //添加BeanPostProcessor
    beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(this));
    if (beanFactory.containsBean("loadTimeWeaver")) {
        beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
        beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
    }
    //注册默认环境bean
    if (!beanFactory.containsLocalBean("environment")) {
        beanFactory.registerSingleton("environment", this.getEnvironment());
    }

    if (!beanFactory.containsLocalBean("systemProperties")) {
        beanFactory.registerSingleton("systemProperties", this.getEnvironment().getSystemProperties());
    }

    if (!beanFactory.containsLocalBean("systemEnvironment")) {
        beanFactory.registerSingleton("systemEnvironment", this.getEnvironment().getSystemEnvironment());
    }

}

④ postProcessBeanFactory 子类扩展BeanFactory

protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
}	

⑤ invokeBeanFactoryPostProcessors 执行增强的方法

这个类,涉及到了两个接口。

  • BeanFactoryPostProcessor
  • BeanDefinitionRegistryPostProcessor
    接口,这个接口是
    BeanFactoryPostProcessor
    的子接口,
    它的优先级比
    BeanFactoryPostProcessor
    更高

它的总体执行流程是:先执行
BeanDefinitionRegistryPostProcessor

BeanFactoryPostProcessor
,然后再执行
BeanFactoryPostProcessor

下图是
BeanDefinitionRegistryPostProcessor
接口的处理过程:

BeanFactoryPostProcessor的处理逻辑

总逻辑就是先分类,已经处理过的直接跳过,没有处理过的,分类处理,逻辑和上面的相同。

执行BeanFactoryPostProcessor后置处理器的postProcessBeanFactory()增强方法

protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
    // 1.拿到当前应用上下文beanFactoryPostProcessors变量中的值
    // 2.实例化并调用所有已注册的BeanFactoryPostProcessor
    PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, this.getBeanFactoryPostProcessors());
    if (beanFactory.getTempClassLoader() == null && beanFactory.containsBean("loadTimeWeaver")) {
        beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
        beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
    }
}

⑥ registerBeanPostProcessors

这个方法的逻辑和上面的一样,只不过上面是直接执行了BeanFactoryPostProcessor,而这个仅仅注册没执行。

首先拿到工厂中所有的
BeanPostProcessor
类型的
Bean
,然后分类处理,排序注册。

⑦ initMessageSource()

执行国际化内容

⑧ initApplicationEventMulticaster

创建了一个多播器,为添加
Listener
提供支持。

主要逻辑:

  • 容器中是否存在
    applicationEventMulticaster
    ,如果存在直接注册
  • 如果不存在,创建一个
    SimpleApplicationEventMulticaster
    ,注册到容器中。

⑨ onRefresh()

子类扩展

⑩ registerListeners()

观察者模式的实现

protected void registerListeners() {
    // 拿到当前容器中的监听器,注册到多播器中
    for (ApplicationListener <? > listener: getApplicationListeners()) {
        getApplicationEventMulticaster().addApplicationListener(listener);
    }

    //拿到容器中为监听器的Bean,注册
    String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
    for (String listenerBeanName: listenerBeanNames) {
        getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
    }

    // 清空开始的事件,到广播器中
    Set < ApplicationEvent > earlyEventsToProcess = this.earlyApplicationEvents;
    this.earlyApplicationEvents = null;
    if (earlyEventsToProcess != null) {
        for (ApplicationEvent earlyEvent: earlyEventsToProcess) {
            getApplicationEventMulticaster().multicastEvent(earlyEvent);
        }
    }
}

⑪ finishBeanFactoryInitialization

 /**
   * Finish the initialization of this context's bean factory,
   * initializing all remaining singleton beans.
     在上下文工厂中完成所有Bean 的初始化
   */
 protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
     // 初始化上下文转换服务Bean
     if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) &&
         beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) {
         beanFactory.setConversionService(
             beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class));
     }


     //如果不存在前入值解析器,则注册一个默认的嵌入值解析器,主要是注解属性解析
     if (!beanFactory.hasEmbeddedValueResolver()) {
         beanFactory.addEmbeddedValueResolver(strVal - > getEnvironment().resolvePlaceholders(strVal));
     }

     // 初始化LoadTimeWeaverAware
     String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false);
     for (String weaverAwareName: weaverAwareNames) {
         getBean(weaverAwareName);
     }

     // Stop using the temporary ClassLoader for type matching.
     beanFactory.setTempClassLoader(null);

     // Allow for caching all bean definition metadata, not expecting further changes.
     beanFactory.freezeConfiguration();

     // Instantiate all remaining (non-lazy-init) singletons.
     //实例化,重点
     beanFactory.preInstantiateSingletons();
 }

下图是创建Bean的主要流程

按照途中的序号一个一个说:

  1. BeanDefinition
    是否需要合并。
    BeanDefinition
    根据不同类型的配置文件信息,会将
    Bean
    封装到不同的
    Bean
    信息定义类中。比如我们常用的配置文件版的
    GenericBeanDefinition
    ;注解扫描版的
    ScannedGenericBeanDefinition
    等等。

而在这个过程中就出现了,
父定义和子定义
,我们需要在实际处理定义信息的时候进行合并处理,主要有一下三个方面

  • 存在父定义信息,使用父定义信息创建一个
    RootBeanDefinition
    ,然后将自定义信息作为参数传入。
  • 不存在父定义信息,并且当前
    BeanDefinition

    RootBeanDefintion
    类型的,直接返回一份
    RootBeanDefintion
    的克隆
  • 不存在父定义信息,并且当前
    BeanDefintion
    不是
    RootBeanDefintiton
    类型的,直接通过该
    BeanDefintion
    构建一个
    RootBeanDefintion
    返回

上面的流程也是源码中的执行流程

  1. isFactoryBean
    。判断是否为
    FactoryBean

简单介绍一下:
FactoryBean
是让开发者创建自己需要
Bean
接口。内部提供了三个方法

T getObject() throws Exception;//返回的Bean信息
Class<?> getObjectType();//返回的Bean类型
default boolean isSingleton() {return true;}//是否单例

当我们通过
GetBean
直接该
Bean
的时候,获取到的是该工厂指定返回的
Bean
类型。如果想要获取该
Bean
本身,需要通过一个前缀获得
&

@Override
public boolean isFactoryBean(String name) throws NoSuchBeanDefinitionException {
    String beanName = transformedBeanName(name); //解析真正的BeanName
    Object beanInstance = getSingleton(beanName, false); //获取容器中的bean
    if (beanInstance != null) { //如果容器中存在,直接返回该Bean是否为FactoryBea类型
        return (beanInstance instanceof FactoryBean);
    }
    //没有Bean信息,检查这个Bean信息
    if (!containsBeanDefinition(beanName) && getParentBeanFactory() instanceof ConfigurableBeanFactory) {
        // 从父工厂中获取
        return ((ConfigurableBeanFactory) getParentBeanFactory()).isFactoryBean(name);
    }
    //MergedBeanDefinition来检查beanName对应的Bean是否为FactoryBean
    return isFactoryBean(beanName, getMergedLocalBeanDefinition(beanName));
}

再来看一个点,这个就是从容器中获取Bean的主要方法,也是解决循环依赖的逻辑,这部分内容详情看
这篇文章

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    //查看当前容器中是否存在该Bean
    Object singletonObject = this.singletonObjects.get(beanName);
    //如果不存在,且当前Bean正在被创建
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized(this.singletonObjects) {
            //从早期的容器中获取Bean
            singletonObject = this.earlySingletonObjects.get(beanName);
            //如果早期容器也没有且允许创建早期引用
            if (singletonObject == null && allowEarlyReference) {
                //获取该Bean的ObjectFactory工厂
                ObjectFactory <? > singletonFactory = this.singletonFactories.get(beanName);
                //如果当前工厂不为空
                if (singletonFactory != null) {
                    //创建一个对象实例,此时处于半初始化状态
                    singletonObject = singletonFactory.getObject();
                    //添加到早期引用中
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    //移除创建早期引用的工厂,因为该Bean已经创建且添加到了早期容器中,不需要再次进行创建了。
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return singletonObject;
}

⑫ finishRefresh

这个方法进行了一系列的资源清理

protected void finishRefresh() {
  // 清空上下文资源缓存
  clearResourceCaches();

  // 初始化生命周期处理器
  initLifecycleProcessor();

  // 将已经刷新完毕的处理器传播(扔到)生命周期处理器中
  getLifecycleProcessor().onRefresh();

  // 推送上下文刷新完毕的时间到相应的监听器
  publishEvent(new ContextRefreshedEvent(this));

  // Participate in LiveBeansView MBean, if active.
  LiveBeansView.registerApplicationContext(this);
 }

initLifecycleProcessor,这个方法极具简单,就看一下当前Bean中是否存在生命周期处理器,如果存在直接使用这个,如果不存在则创建一个默认的,并且注册为一个单例的扔到容器中。

面试题专栏

Java面试题专栏
已上线,欢迎访问。

  • 如果你不知道简历怎么写,简历项目不知道怎么包装;
  • 如果简历中有些内容你不知道该不该写上去;
  • 如果有些综合性问题你不知道怎么答;

那么可以私信我,我会尽我所能帮助你。

背景

hi 大家好,我是三合,在过往的岁月中,我曾经想过要写以下这些工具

  1. 写一个通过拦截业务系统所有sql,然后根据这些sql自动分析表与表,字段与字段之间是如何关联的工具,即sql血缘分析工具
  2. 想着动态改写sql,比如给where动态添加一个条件。
  3. 写一个sql格式化工具
  4. 写一个像mycat那样的分库分表中间件
  5. 写一个sql防火墙,防止出现where 1=1后没有其他条件导致查询全表
  6. 写一个数据库之间的sql翻译工具,比如把sqlserver的sql自动翻译为oracle的sql

但是无一例外,都失败了,因为要实现以上这些需求,都需要一个核心的类库,即sql解析引擎,遗憾的是,我没有找到合适的,这是我当初寻找的轨迹

  1. 我发现了
    tsql-parser
    ,但他只支持sql server,所以只能pass。
  2. 然后我又发现了
    SqlParser-cs
    ,
    他的语法树解析出来像这样,
JsonConvert.SerializeObject(statements.First(), Formatting.Indented)
// Elided for readability
{
   "Query": {
      "Body": {
         "Select": {
            "Projection": [
               {
                  "Expression": {
                     "Ident": {
                        "Value": "a",
                        "QuoteStyle": null
                     }
                  }
               }
	...

额,怎么说呢,这语法树也太丑了点吧,同时非常难以理解,跟我想象中的完全不一样啊,于是也只能pass。

  1. 接下来我又发现了另外一些基于antlr来解析sql的类库,比如
    SQLParser
    ,因为代码是antlr自动生成的,比较难以进行手动优化,所以还是pass。

  2. 最后我还发现了另外一个
    gsp的sqlparser
    ,但它是收费的,而且巨贵无比,也pass。

找了一圈下来,我发现符合我要求的类库并不存在,所以我上面的那些想法,也一度搁浅了,但每一次的搁浅,都会使我内心的不甘加重一分,终于有一天,我下定决心,自己动手,丰衣足食,所以最近花了大概3个月时间,从头开始写了一个sql解析引擎,包括词法解析器到语法分析器,不依赖任何第三方组件,纯c#代码,在通过了156个各种各样场景的单元测试以及各种真实的业务环境验证后,今天它
SqlParser.Net
1.0.0正式发布了,本项目基于MIT协议开源,有以下优点,

  1. 支持5大数据库,oracle,sqlserver,mysql,pgsql以及sqlite。
  2. 极致的速度,解析普通sql,时间基本在0.3毫秒以下,当然了,sql越长,解析需要的时间就越长。
  3. 文档完善,众所周知,我三合的开源项目,一向是文档齐全且简单易懂,做到看完就能上手,同时,我也会根据用户的反馈不断的补充以及完善文档。
  4. 代码简洁易懂

SqlParser.Net存在的意义

SqlParser.Net是一个免费,功能全面且高性能的sql解析引擎类库,它可以帮助你简单快速高效的解析和处理sql。

Getting Started

接下来,我将介绍
SqlParser.Net
的用法

通过Nuget安装

你可以运行以下命令在你的项目中安装 SqlParser.Net 。

PM> Install-Package SqlParser.Net

支持框架

netstandard2.0

从最简单的demo开始

让我们一起看一个最简单的select语句是如何解析的

var sql = "select * from test";
var sqlAst = DbUtils.Parse(sql, DbType.Oracle);

解析结果如下:

var expect = new SqlSelectExpression()
{
    Query = new SqlSelectQueryExpression()
    {
        Columns = new List<SqlSelectItemExpression>()
        {
            new SqlSelectItemExpression()
            {
                Body = new SqlAllColumnExpression()
            }
        },
        From = new SqlTableExpression()
        {
            Name = new SqlIdentifierExpression()
            {
                Value = "test"
            }
        }
    }
};

以上面为例子,抽象语法树的所有叶子节点均为sqlExpression的子类,且各种sqlExpression节点可以互相嵌套,组成一颗复杂无比的树,其他sql解析引擎还分为statement和expression,我认为过于复杂,所以全部统一为sqlExpression,顶级的sqlExpression总共分为4种,

  1. 查询语句(SqlSelectExpression)
  2. 插入语句(SqlInsertExpression)
  3. 删除语句(SqlDeleteExpression)
  4. 更新语句(SqlUpdateExpression)

这4种顶级语句中,我认为最复杂的是查询语句,因为查询组合非常多,要兼容各种各样的情况,其他3种反而非常简单。现阶段,sqlExpression的子类一共有38种,我将在下面的演示中,结合例子给大家讲解一下。

1. Select查询语句

如上例子,SqlSelectExpression代表一个查询语句,SqlSelectQueryExpression则是真正具体的查询语句,他包括了

  1. 要查询的所有列(Columns字段)
  2. 数据源(From字段)
  3. 条件过滤语句(Where字段)
  4. 分组语句(GroupBy字段)
  5. 排序语句(OrderBy字段)
  6. 分页语句(Limit字段)
  7. Into语句(sql server专用,如SELECT id,name into test14 from TEST t)
  8. ConnectBy语句(oracle专用,如SELECT LEVEL l FROM DUAL CONNECT BY NOCYCLE LEVEL<=100)
  9. WithSubQuerys语句,公用表表达式,即CTE

其中Columns是一个列表,他的每一个子项都是一个SqlSelectItemExpression,他的body代表一个逻辑子句,逻辑子句的值,可以为以下这些

  1. 字段,如name,
  2. 二元表达式,如t.age+3
  3. 函数调用,如LOWER(t.NAME)
  4. 一个完整的查询语句,如SELECT name FROM TEST2 t2

包括order by,partition by,group by,between,in,case when后面跟着的都是逻辑子句,这个稍后会演示,在这个例子中,因为是要查询所有列,所以仅有一个SqlSelectItemExpression,他的body是SqlAllColumnExpression(代表所有列),From代表要查询的数据源,在这里仅单表查询,所以From的值为SqlTableExpression(代表单表),表名是一个SqlIdentifierExpression,即标识符表达式,表示这是一个标识符,在SQL中,标识符(Identifier)是用于命名数据库对象的名称。这些对象可以包括表、列、索引、视图、模式、数据库等。标识符使得我们能够引用和操作这些对象,在这里,标识符的值为test,表示表名为test。

1.1 查询返回列的各种情形

1.1.1 查询指定字段

var sql = "select id AS bid,t.NAME testName  from test t";
var sqlAst = DbUtils.Parse(sql, DbType.Oracle);

解析结果如下:

var expect = new SqlSelectExpression()
{
    Query = new SqlSelectQueryExpression()
    {
        Columns = new List<SqlSelectItemExpression>()
        {
            new SqlSelectItemExpression()
            {
                Body = new SqlIdentifierExpression()
                {
                    Value = "id",
                },
                Alias = new SqlIdentifierExpression()
                {
                    Value = "bid",
                },
            },
            new SqlSelectItemExpression()
            {
                Body = new SqlPropertyExpression()
                {
                    Name = new SqlIdentifierExpression()
                    {
                        Value = "NAME",
                    },
                    Table = new SqlIdentifierExpression()
                    {
                        Value = "t",
                    },
                },
                Alias = new SqlIdentifierExpression()
                {
                    Value = "testName",
                },
            },
        },
        From = new SqlTableExpression()
        {
            Name = new SqlIdentifierExpression()
            {
                Value = "test",
            },
            Alias = new SqlIdentifierExpression()
            {
                Value = "t",
            },
        },
    },
};


在上面这个例子中,我们指定了要查询2个字段,分别是id和t.NAME,此时Columns列表里有2个值,
第一个SqlSelectItemExpression包含了

  1. 主体,即body字段,在本例子中他的值是一个SqlIdentifierExpression表达式,值为id,表示列名为id,
  2. 别名,即Alias字段,在本例子中他也是一个SqlIdentifierExpression,值为bid,代表列别名为bid,

第二个SqlSelectItemExpression的body里是一个SqlPropertyExpression,代表这是一个属性表达式,SqlPropertyExpression它包含了

  1. 表名,即Table字段,值为t,即表名为t
  2. 属性名,即Name字段,值为Name,即属性名为name

合起来则代表t表的name字段,而第二个SqlSelectItemExpression也有列别名,即testName,这个查询也是单表查询,但SqlTableExpression他多了一个Alias别名字段,即表示,表别名为t。

1.1.2 查询列为二元表达式的情况

var sql = "select 1+2 from test";
var sqlAst = DbUtils.Parse(sql, DbType.Oracle);

解析结果如下:

var expect = new SqlSelectExpression()
{
    Query = new SqlSelectQueryExpression()
    {
        Columns = new List<SqlSelectItemExpression>()
        {
            new SqlSelectItemExpression()
            {
                Body = new SqlBinaryExpression()
                {
                    Left = new SqlNumberExpression()
                    {
                        Value = 1M,
                    },
                    Operator = SqlBinaryOperator.Add,
                    Right = new SqlNumberExpression()
                    {
                        Value = 2M,
                    },
                },
            },
        },
        From = new SqlTableExpression()
        {
            Name = new SqlIdentifierExpression()
            {
                Value = "test",
            },
        },
    },
};

在这个例子中,要查询的字段的值为一个二元表达式SqlBinaryExpression,他包含了

  1. 左边部分,即Left字段,值为一个SqlNumberExpression,即数字表达式,它的值为1
  2. 右边部分,即Right字段,值为一个SqlNumberExpression,即数字表达式,它的值为2
  3. 中间符号,即Operator字段,值为add,即加法

这个例子证明了,SqlSelectItemExpression代表一个逻辑子句,而不仅仅是某个字段。

1.1.3 查询列为字符串/数字/布尔值的情况

var sql = "select ''' ''',3,true FROM test";
var sqlAst = DbUtils.Parse(sql, DbType.MySql);

解析结果如下:

var expect = new SqlSelectExpression()
{
    Query = new SqlSelectQueryExpression()
    {
        Columns = new List<SqlSelectItemExpression>()
        {
            new SqlSelectItemExpression()
            {
                Body = new SqlStringExpression()
                {
                    Value = "' '"
                },
            },
            new SqlSelectItemExpression()
            {
                Body = new SqlNumberExpression()
                {
                    Value = 3M,
                },
            },
            new SqlSelectItemExpression()
            {
                Body = new SqlBoolExpression()
                {
                    Value = true
                },
            },
        },
        From = new SqlTableExpression()
        {
            Name = new SqlIdentifierExpression()
            {
                Value = "test",
            },
        },
    },
};


在这个例子中,要查询的3个字段为字符串,数字和布尔值,字符串表达式即SqlStringExpression,body里即字符串的值' ',数字表达式即SqlNumberExpression,值为3,布尔表达式即SqlBoolExpression,值为true;

1.1.4 查询列为函数调用的情况

1.1.4.1 简单的函数调用
var sql = "select LOWER(name)  FROM test";
var sqlAst = DbUtils.Parse(sql, DbType.Oracle);

解析结果如下:

var expect = new SqlSelectExpression()
{
    Query = new SqlSelectQueryExpression()
    {
        Columns = new List<SqlSelectItemExpression>()
        {
            new SqlSelectItemExpression()
            {
                Body = new SqlFunctionCallExpression()
                {
                    Name = new SqlIdentifierExpression()
                    {
                        Value = "LOWER",
                    },
                    Arguments = new List<SqlExpression>()
                    {
                        new SqlIdentifierExpression()
                        {
                            Value = "name",
                        },
                    },
                },
            },
        },
        From = new SqlTableExpression()
        {
            Name = new SqlIdentifierExpression()
            {
                Value = "test",
            },
        },
    },
};


在这个例子中,要查询的表达式是一个函数调用,函数调用表达式即SqlFunctionCallExpression,它包含了

  1. 函数名,即Name字段,值为LOWER,
  2. 函数参数列表,即Arguments字段,列表里只有一个值,即函数只有一个参数,且参数的值为name
1.1.4.2 带有over子句的函数调用
var sql = "SELECT t.*, ROW_NUMBER() OVER ( PARTITION BY t.ID  ORDER BY t.NAME,t.ID) as rnum FROM TEST t";
var sqlAst = DbUtils.Parse(sql, DbType.Oracle);

解析结果如下:

var expect = new SqlSelectExpression()
{
    Query = new SqlSelectQueryExpression()
    {
        Columns = new List<SqlSelectItemExpression>()
        {
            new SqlSelectItemExpression()
            {
                Body = new SqlPropertyExpression()
                {
                    Name = new SqlIdentifierExpression()
                    {
                        Value = "*",
                    },
                    Table = new SqlIdentifierExpression()
                    {
                        Value = "t",
                    },
                },
            },
            new SqlSelectItemExpression()
            {
                Body = new SqlFunctionCallExpression()
                {
                    Name = new SqlIdentifierExpression()
                    {
                        Value = "ROW_NUMBER",
                    },
                    Over = new SqlOverExpression()
                    {
                        PartitionBy = new SqlPartitionByExpression()
                        {
                            Items = new List<SqlExpression>()
                            {
                                new SqlPropertyExpression()
                                {
                                    Name = new SqlIdentifierExpression()
                                    {
                                        Value = "ID",
                                    },
                                    Table = new SqlIdentifierExpression()
                                    {
                                        Value = "t",
                                    },
                                },
                            },
                        },
                        OrderBy = new SqlOrderByExpression()
                        {
                            Items = new List<SqlOrderByItemExpression>()
                            {
                                new SqlOrderByItemExpression()
                                {
                                    Body = new SqlPropertyExpression()
                                    {
                                        Name = new SqlIdentifierExpression()
                                        {
                                            Value = "NAME",
                                        },
                                        Table = new SqlIdentifierExpression()
                                        {
                                            Value = "t",
                                        },
                                    },
                                },
                                new SqlOrderByItemExpression()
                                {
                                    Body = new SqlPropertyExpression()
                                    {
                                        Name = new SqlIdentifierExpression()
                                        {
                                            Value = "ID",
                                        },
                                        Table = new SqlIdentifierExpression()
                                        {
                                            Value = "t",
                                        },
                                    },
                                },
                            },
                        },
                    },
                },
                Alias = new SqlIdentifierExpression()
                {
                    Value = "rnum",
                },
            },
        },
        From = new SqlTableExpression()
        {
            Name = new SqlIdentifierExpression()
            {
                Value = "TEST",
            },
            Alias = new SqlIdentifierExpression()
            {
                Value = "t",
            },
        },
    },
};



在这个例子中,SqlFunctionCallExpression它除了常规字段外,还包含了Over子句,具体有以下这些

  1. 函数名,即Name字段,值为ROW_NUMBER,
  2. 函数参数列表,即Arguments字段,值为null,即无参数
  3. Over子句,即Over字段,他的值为一个SqlOverExpression表达式,SqlOverExpression本身又包含了以下内容
    1. PartitionBy分区子句,值为一个SqlPartitionByExpression表达式,表达式的内容也非常简单,只有一个Items,即一个分区表达式的列表,在这个例子中,列表里只有一个值SqlPropertyExpression,即根据t.id分区
    2. OrderBy排序子句,值为SqlOrderByExpression表达式,表达式的内容也非常简单,只有一个Items,即一个排序表达式的列表,列表里的值为SqlOrderByItemExpression,即排序子项表达式,排序子项表达式里又包含了以下内容
      1. 排序依据,即Body字段,在这个例子中,排序依据是2个SqlPropertyExpression表达式,即根据t.NAME,t.ID排序
      2. 排序类型,即OrderByType字段,值为Asc或者Desc,默认为asc,在这2个例子中,默认排序类型都是asc
1.1.4.3 带有within group子句的函数调用
var sql = "select name,PERCENTILE_CONT(0.5) within group(order by \"number\") from TEST5 group by name";
var sqlAst = DbUtils.Parse(sql, DbType.Pgsql);

解析结果如下:

var expect = new SqlSelectExpression()
{
    Query = new SqlSelectQueryExpression()
    {
        Columns = new List<SqlSelectItemExpression>()
        {
            new SqlSelectItemExpression()
            {
                Body = new SqlIdentifierExpression()
                {
                    Value = "name",
                },
            },
            new SqlSelectItemExpression()
            {
                Body = new SqlFunctionCallExpression()
                {
                    Name = new SqlIdentifierExpression()
                    {
                        Value = "PERCENTILE_CONT",
                    },
                    WithinGroup = new SqlWithinGroupExpression()
                    {
                        OrderBy = new SqlOrderByExpression()
                        {
                            Items = new List<SqlOrderByItemExpression>()
                            {
                                new SqlOrderByItemExpression()
                                {
                                    Body = new SqlIdentifierExpression()
                                    {
                                        Value = "number",
                                        LeftQualifiers = "\"",
                                        RightQualifiers = "\"",
                                    },
                                },
                            },
                        },
                    },
                    Arguments = new List<SqlExpression>()
                    {
                        new SqlNumberExpression()
                        {
                            Value = 0.5M,
                        },
                    },
                },
            },
        },
        From = new SqlTableExpression()
        {
            Name = new SqlIdentifierExpression()
            {
                Value = "TEST5",
            },
        },
        GroupBy = new SqlGroupByExpression()
        {
            Items = new List<SqlExpression>()
            {
            new SqlIdentifierExpression()
            {
                Value = "name",
            },
            },
        },
    },
};

在这个例子中,SqlFunctionCallExpression它除了常规字段外,还包含了within group子句,具体有以下这些

  1. 函数名,即Name字段,值为PERCENTILE_CONT,
  2. 函数参数列表,即Arguments字段,列表里只有一项,表示只有1个参数,参数是SqlNumberExpression表达式,值为0.5
  3. within group子句,即WithinGroup字段,他的值为一个SqlWithinGroupExpression表达式,SqlWithinGroupExpression又包含了OrderBy排序子句,这里根据number字段排序

1.1.5 查询列为子查询的情况

var sql = "select c.*, (select a.name as province_name from portal_area a where a.id = c.province_id) as province_name, (select a.name as city_name from portal_area a where a.id = c.city_id) as city_name, (CASE WHEN c.area_id IS NULL THEN NULL ELSE (select a.name as area_name from portal_area a where a.id = c.area_id)  END )as area_name  from portal.portal_company c";
var sqlAst = DbUtils.Parse(sql, DbType.Oracle);

解析结果如下:

var expect = new SqlSelectExpression()
{
    Query = new SqlSelectQueryExpression()
    {
        Columns = new List<SqlSelectItemExpression>()
        {
            new SqlSelectItemExpression()
            {
                Body = new SqlPropertyExpression()
                {
                    Name = new SqlIdentifierExpression()
                    {
                        Value = "*",
                    },
                    Table = new SqlIdentifierExpression()
                    {
                        Value = "c",
                    },
                },
            },
            new SqlSelectItemExpression()
            {
                Body = new SqlSelectExpression()
                {
                    Query = new SqlSelectQueryExpression()
                    {
                        Columns = new List<SqlSelectItemExpression>()
                        {
                            new SqlSelectItemExpression()
                            {
                                Body = new SqlPropertyExpression()
                                {
                                    Name = new SqlIdentifierExpression()
                                    {
                                        Value = "name",
                                    },
                                    Table = new SqlIdentifierExpression()
                                    {
                                        Value = "a",
                                    },
                                },
                                Alias = new SqlIdentifierExpression()
                                {
                                    Value = "province_name",
                                },
                            },
                        },
                        From = new SqlTableExpression()
                        {
                            Name = new SqlIdentifierExpression()
                            {
                                Value = "portal_area",
                            },
                            Alias = new SqlIdentifierExpression()
                            {
                                Value = "a",
                            },
                        },
                        Where = new SqlBinaryExpression()
                        {
                            Left = new SqlPropertyExpression()
                            {
                                Name = new SqlIdentifierExpression()
                                {
                                    Value = "id",
                                },
                                Table = new SqlIdentifierExpression()
                                {
                                    Value = "a",
                                },
                            },
                            Operator = SqlBinaryOperator.EqualTo,
                            Right = new SqlPropertyExpression()
                            {
                                Name = new SqlIdentifierExpression()
                                {
                                    Value = "province_id",
                                },
                                Table = new SqlIdentifierExpression()
                                {
                                    Value = "c",
                                },
                            },
                        },
                    },
                },
                Alias = new SqlIdentifierExpression()
                {
                    Value = "province_name",
                },
            },
            new SqlSelectItemExpression()
            {
                Body = new SqlSelectExpression()
                {
                    Query = new SqlSelectQueryExpression()
                    {
                        Columns = new List<SqlSelectItemExpression>()
                        {
                            new SqlSelectItemExpression()
                            {
                                Body = new SqlPropertyExpression()
                                {
                                    Name = new SqlIdentifierExpression()
                                    {
                                        Value = "name",
                                    },
                                    Table = new SqlIdentifierExpression()
                                    {
                                        Value = "a",
                                    },
                                },
                                Alias = new SqlIdentifierExpression()
                                {
                                    Value = "city_name",
                                },
                            },
                        },
                        From = new SqlTableExpression()
                        {
                            Name = new SqlIdentifierExpression()
                            {
                                Value = "portal_area",
                            },
                            Alias = new SqlIdentifierExpression()
                            {
                                Value = "a",
                            },
                        },
                        Where = new SqlBinaryExpression()
                        {
                            Left = new SqlPropertyExpression()
                            {
                                Name = new SqlIdentifierExpression()
                                {
                                    Value = "id",
                                },
                                Table = new SqlIdentifierExpression()
                                {
                                    Value = "a",
                                },
                            },
                            Operator = SqlBinaryOperator.EqualTo,
                            Right = new SqlPropertyExpression()
                            {
                                Name = new SqlIdentifierExpression()
                                {
                                    Value = "city_id",
                                },
                                Table = new SqlIdentifierExpression()
                                {
                                    Value = "c",
                                },
                            },
                        },
                    },
                },
                Alias = new SqlIdentifierExpression()
                {
                    Value = "city_name",
                },
            },
            new SqlSelectItemExpression()
            {
                Body = new SqlCaseExpression()
                {
                    Items = new List<SqlCaseItemExpression>()
                    {
                        new SqlCaseItemExpression()
                        {
                            Condition = new SqlBinaryExpression()
                            {
                                Left = new SqlPropertyExpression()
                                {
                                    Name = new SqlIdentifierExpression()
                                    {
                                        Value = "area_id",
                                    },
                                    Table = new SqlIdentifierExpression()
                                    {
                                        Value = "c",
                                    },
                                },
                                Operator = SqlBinaryOperator.Is,
                                Right = new SqlNullExpression()
                            },
                            Value = new SqlNullExpression()
                        },
                    },
                    Else = new SqlSelectExpression()
                    {
                        Query = new SqlSelectQueryExpression()
                        {
                            Columns = new List<SqlSelectItemExpression>()
                            {
                                new SqlSelectItemExpression()
                                {
                                    Body = new SqlPropertyExpression()
                                    {
                                        Name = new SqlIdentifierExpression()
                                        {
                                            Value = "name",
                                        },
                                        Table = new SqlIdentifierExpression()
                                        {
                                            Value = "a",
                                        },
                                    },
                                    Alias = new SqlIdentifierExpression()
                                    {
                                        Value = "area_name",
                                    },
                                },
                            },
                            From = new SqlTableExpression()
                            {
                                Name = new SqlIdentifierExpression()
                                {
                                    Value = "portal_area",
                                },
                                Alias = new SqlIdentifierExpression()
                                {
                                    Value = "a",
                                },
                            },
                            Where = new SqlBinaryExpression()
                            {
                                Left = new SqlPropertyExpression()
                                {
                                    Name = new SqlIdentifierExpression()
                                    {
                                        Value = "id",
                                    },
                                    Table = new SqlIdentifierExpression()
                                    {
                                        Value = "a",
                                    },
                                },
                                Operator = SqlBinaryOperator.EqualTo,
                                Right = new SqlPropertyExpression()
                                {
                                    Name = new SqlIdentifierExpression()
                                    {
                                        Value = "area_id",
                                    },
                                    Table = new SqlIdentifierExpression()
                                    {
                                        Value = "c",
                                    },
                                },
                            },
                        },
                    },
                },
                Alias = new SqlIdentifierExpression()
                {
                    Value = "area_name",
                },
            },
        },
        From = new SqlTableExpression()
        {
            Name = new SqlIdentifierExpression()
            {
                Value = "portal_company",
            },
            Schema = new SqlIdentifierExpression()
            {
                Value = "portal",
            },
            Alias = new SqlIdentifierExpression()
            {
                Value = "c",
            },
        },
    },
};


在这个例子中,要查询的列值为一个SqlSelectExpression表达式,即要查询的列是一个子查询

1.2 Where条件过滤语句

1.2.1 二元表达式

var sql = "SELECT * FROM test WHERE ID =1";
var sqlAst = DbUtils.Parse(sql, DbType.Oracle);

解析结果如下:

var expect = new SqlSelectExpression()
{
    Query = new SqlSelectQueryExpression()
    {
        Columns = new List<SqlSelectItemExpression>()
        {
            new SqlSelectItemExpression()
            {
                Body = new SqlAllColumnExpression()
            },
        },
        From = new SqlTableExpression()
        {
            Name = new SqlIdentifierExpression()
            {
                Value = "test",
            },
        },
        Where = new SqlBinaryExpression()
        {
            Left = new SqlIdentifierExpression()
            {
                Value = "ID",
            },
            Operator = SqlBinaryOperator.EqualTo,
            Right = new SqlNumberExpression()
            {
                Value = 1M,
            },
        },
    },
};


在这个例子中,where字段的值是一个二元表达式SqlBinaryExpression,他包含了

  1. 左边部分,即Left字段,值为一个SqlIdentifierExpression,即标识符表达式,它的值为ID
  2. 右边部分,即Right字段,值为一个SqlNumberExpression,即数字表达式,它的值为1
  3. 中间符号,即Operator字段,值为EqualTo,即等号,当然了,还可以是大于号,小于号,大于等于号,小于等于号,不等号等

二元表达式的两边可以非常灵活,可以是各种其他表达式,同时也可以自我嵌套另一个二元表达式,组成一个非常复杂的二元表达式

1.2.2 between/not between子句

var sql = "SELECT * FROM test WHERE ID BETWEEN 1 AND 2";
var sqlAst = DbUtils.Parse(sql, DbType.Oracle);

解析结果如下:

var expect = new SqlSelectExpression()
{
    Query = new SqlSelectQueryExpression()
    {
        Columns = new List<SqlSelectItemExpression>()
        {
            new SqlSelectItemExpression()
            {
                Body = new SqlAllColumnExpression()
            },
        },
        From = new SqlTableExpression()
        {
            Name = new SqlIdentifierExpression()
            {
                Value = "test",
            },
        },
        Where = new SqlBetweenAndExpression()
        {
            Body = new SqlIdentifierExpression()
            {
                Value = "ID",
            },
            Begin = new SqlNumberExpression()
            {
                Value = 1M,
            },
            End = new SqlNumberExpression()
            {
                Value = 2M,
            },
        },
    },
};


between子句包含了

  1. Begin部分,即Begin字段,在这个例子中,值为一个SqlNumberExpression,,它的值为1
  2. End部分,即End字段,在这个例子中,值为一个SqlNumberExpression,它的值为2
  3. Body主体部分,即Body字段,值为SqlIdentifierExpression,即标识符表达式,值为id
  4. 取反部分,即IsNot字段,如果是not between,则IsNot=true

1.2.3 is null/is not null子句

var sql = "select * from test rd where rd.name is null";
var sqlAst = DbUtils.Parse(sql, DbType.Oracle);

解析结果如下:

var expect = new SqlSelectExpression()
{
    Query = new SqlSelectQueryExpression()
    {
        Columns = new List<SqlSelectItemExpression>()
        {
            new SqlSelectItemExpression()
            {
                Body = new SqlAllColumnExpression()
            },
        },
        From = new SqlTableExpression()
        {
            Name = new SqlIdentifierExpression()
            {
                Value = "test",
            },
            Alias = new SqlIdentifierExpression()
            {
                Value = "rd",
            },
        },
        Where = new SqlBinaryExpression()
        {
            Left = new SqlPropertyExpression()
            {
                Name = new SqlIdentifierExpression()
                {
                    Value = "name",
                },
                Table = new SqlIdentifierExpression()
                {
                    Value = "rd",
                },
            },
            Operator = SqlBinaryOperator.Is,
            Right = new SqlNullExpression()
        },
    },
};



is null/is not null子句主要体现在二元表达式里,Operator字段为Is/IsNot,right字段为SqlNullExpression,即null表达式,代表值为null

1.2.4 exists/not exists子句

var sql = "select * from TEST t where EXISTS(select * from TEST2 t2)";
var sqlAst = DbUtils.Parse(sql, DbType.Oracle);

解析结果如下:

var expect = new SqlSelectExpression()
{
    Query = new SqlSelectQueryExpression()
    {
        Columns = new List<SqlSelectItemExpression>()
        {
            new SqlSelectItemExpression()
            {
                Body = new SqlAllColumnExpression()
            },
        },
        From = new SqlTableExpression()
        {
            Name = new SqlIdentifierExpression()
            {
                Value = "TEST",
            },
            Alias = new SqlIdentifierExpression()
            {
                Value = "t",
            },
        },
        Where = new SqlExistsExpression()
        {
            Body = new SqlSelectExpression()
            {
                Query = new SqlSelectQueryExpression()
                {
                    Columns = new List<SqlSelectItemExpression>()
                    {
                        new SqlSelectItemExpression()
                        {
                            Body = new SqlAllColumnExpression()
                        },
                    },
                    From = new SqlTableExpression()
                    {
                        Name = new SqlIdentifierExpression()
                        {
                            Value = "TEST2",
                        },
                        Alias = new SqlIdentifierExpression()
                        {
                            Value = "t2",
                        },
                    },
                },
            },
        },
    },
};




exists/not exists子句,主要体现为SqlExistsExpression表达式,

  1. 主体,即body字段,本例子中值为一个SqlSelectExpression表达式
  2. 取反部分,即IsNot字段,如果是not exists,则IsNot=true

1.2.5 like/not like子句

var sql = "SELECT * from TEST t WHERE name LIKE '%a%'";
var sqlAst = DbUtils.Parse(sql, DbType.Oracle);

解析结果如下:

var expect = new SqlSelectExpression()
{
    Query = new SqlSelectQueryExpression()
    {
        Columns = new List<SqlSelectItemExpression>()
        {
            new SqlSelectItemExpression()
            {
                Body = new SqlAllColumnExpression()
            },
        },
        From = new SqlTableExpression()
        {
            Name = new SqlIdentifierExpression()
            {
                Value = "TEST",
            },
            Alias = new SqlIdentifierExpression()
            {
                Value = "t",
            },
        },
        Where = new SqlBinaryExpression()
        {
            Left = new SqlIdentifierExpression()
            {
                Value = "name",
            },
            Operator = SqlBinaryOperator.Like,
            Right = new SqlStringExpression()
            {
                Value = "%a%"
            },
        },
    },
};


like子句,主要体现在二元表达式里,Operator字段为Like/NotLike,本例子中right字段为字符串表达式,即SqlStringExpression表达式,值为%a%。

1.2.6 all/any子句

var sql = "select * from customer c where c.Age >all(select o.Quantity  from orderdetail o)";
var sqlAst = DbUtils.Parse(sql, DbType.MySql);

解析结果如下:

var expect = new SqlSelectExpression()
{
    Query = new SqlSelectQueryExpression()
    {
        Columns = new List<SqlSelectItemExpression>()
        {
            new SqlSelectItemExpression()
            {
                Body = new SqlAllColumnExpression()
            },
        },
        From = new SqlTableExpression()
        {
            Name = new SqlIdentifierExpression()
            {
                Value = "customer",
            },
            Alias = new SqlIdentifierExpression()
            {
                Value = "c",
            },
        },
        Where = new SqlBinaryExpression()
        {
            Left = new SqlPropertyExpression()
            {
                Name = new SqlIdentifierExpression()
                {
                    Value = "Age",
                },
                Table = new SqlIdentifierExpression()
                {
                    Value = "c",
                },
            },
            Operator = SqlBinaryOperator.GreaterThen,
            Right = new SqlAllExpression()
            {
                Body = new SqlSelectExpression()
                {
                    Query = new SqlSelectQueryExpression()
                    {
                        Columns = new List<SqlSelectItemExpression>()
                        {
                            new SqlSelectItemExpression()
                            {
                                Body = new SqlPropertyExpression()
                                {
                                    Name = new SqlIdentifierExpression()
                                    {
                                        Value = "Quantity",
                                    },
                                    Table = new SqlIdentifierExpression()
                                    {
                                        Value = "o",
                                    },
                                },
                            },
                        },
                        From = new SqlTableExpression()
                        {
                            Name = new SqlIdentifierExpression()
                            {
                                Value = "orderdetail",
                            },
                            Alias = new SqlIdentifierExpression()
                            {
                                Value = "o",
                            },
                        },
                    },
                },
            },
        },
    },
};



all/any子句,主要体现在SqlAllExpression/SqlAnyExpression表达式,它的body里是另一个SqlSelectExpression表达式

1.2.7 in/ not in子句

var sql = "SELECT  * from TEST t WHERE t.NAME IN ('a','b','c')";
var sqlAst = DbUtils.Parse(sql, DbType.Oracle);

解析结果如下:

var expect = new SqlSelectExpression()
{
    Query = new SqlSelectQueryExpression()
    {
        Columns = new List<SqlSelectItemExpression>()
        {
            new SqlSelectItemExpression()
            {
                Body = new SqlAllColumnExpression()
            },
        },
        From = new SqlTableExpression()
        {
            Name = new SqlIdentifierExpression()
            {
                Value = "TEST",
            },
            Alias = new SqlIdentifierExpression()
            {
                Value = "t",
            },
        },
        Where = new SqlInExpression()
        {
            Body = new SqlPropertyExpression()
            {
                Name = new SqlIdentifierExpression()
                {
                    Value = "NAME",
                },
                Table = new SqlIdentifierExpression()
                {
                    Value = "t",
                },
            },
            TargetList = new List<SqlExpression>()
            {
                new SqlStringExpression()
                {
                    Value = "a"
                },
                new SqlStringExpression()
                {
                    Value = "b"
                },
                new SqlStringExpression()
                {
                    Value = "c"
                },
            },
        },
    },
};



in/not in子句,主要体现在SqlInExpression表达式,它包含了

  1. body字段,即in的主体,在这里是SqlPropertyExpression,值为t.NAME
  2. TargetList字段,即in的目标列表,在这里是一个SqlExpression的列表,里面包括3个SqlStringExpression,即字符串表达式,a,b,c
  3. 取反部分,即IsNot字段,如果是not in,则IsNot=true

当然了,in也有另一种子查询的类型,即

var sql = "select * from TEST5  WHERE NAME IN (SELECT NAME  FROM TEST3)";
var sqlAst = DbUtils.Parse(sql, DbType.Oracle);

解析结果如下:

var expect = new SqlSelectExpression()
{
    Query = new SqlSelectQueryExpression()
    {
        Columns = new List<SqlSelectItemExpression>()
        {
            new SqlSelectItemExpression()
            {
                Body = new SqlAllColumnExpression()
            },
        },
        From = new SqlTableExpression()
        {
            Name = new SqlIdentifierExpression()
            {
                Value = "TEST5",
            },
        },
        Where = new SqlInExpression()
        {
            Body = new SqlIdentifierExpression()
            {
                Value = "NAME",
            },
            SubQuery = new SqlSelectExpression()
            {
                Query = new SqlSelectQueryExpression()
                {
                    Columns = new List<SqlSelectItemExpression>()
                    {
                        new SqlSelectItemExpression()
                        {
                            Body = new SqlIdentifierExpression()
                            {
                                Value = "NAME",
                            },
                        },
                    },
                    From = new SqlTableExpression()
                    {
                        Name = new SqlIdentifierExpression()
                        {
                            Value = "TEST3",
                        },
                    },
                },
            },
        },
    },
};

在这里的SqlInExpression表达式中,它包含了

  1. body字段,即in的主体,在这里是SqlIdentifierExpression,值为NAME
  2. SubQuery字段,即子查询,值为一个SqlSelectExpression
  3. IsNot字段,如果是not in,则IsNot=true

1.2.8 case when子句

var sql = "SELECT CASE WHEN t.name='1' THEN 'a' WHEN t.name='2' THEN 'b' ELSE 'c' END AS v from TEST t";
var sqlAst = DbUtils.Parse(sql, DbType.Oracle);

解析结果如下:

var expect = new SqlSelectExpression()
{
    Query = new SqlSelectQueryExpression()
    {
        Columns = new List<SqlSelectItemExpression>()
        {
            new SqlSelectItemExpression()
            {
                Body = new SqlCaseExpression()
                {
                    Items = new List<SqlCaseItemExpression>()
                    {
                        new SqlCaseItemExpression()
                        {
                            Condition = new SqlBinaryExpression()
                            {
                                Left = new SqlPropertyExpression()
                                {
                                    Name = new SqlIdentifierExpression()
                                    {
                                        Value = "name",
                                    },
                                    Table = new SqlIdentifierExpression()
                                    {
                                        Value = "t",
                                    },
                                },
                                Operator = SqlBinaryOperator.EqualTo,
                                Right = new SqlStringExpression()
                                {
                                    Value = "1"
                                },
                            },
                            Value = new SqlStringExpression()
                            {
                                Value = "a"
                            },
                        },
                        new SqlCaseItemExpression()
                        {
                            Condition = new SqlBinaryExpression()
                            {
                                Left = new SqlPropertyExpression()
                                {
                                    Name = new SqlIdentifierExpression()
                                    {
                                        Value = "name",
                                    },
                                    Table = new SqlIdentifierExpression()
                                    {
                                        Value = "t",
                                    },
                                },
                                Operator = SqlBinaryOperator.EqualTo,
                                Right = new SqlStringExpression()
                                {
                                    Value = "2"
                                },
                            },
                            Value = new SqlStringExpression()
                            {
                                Value = "b"
                            },
                        },
                    },
                    Else = new SqlStringExpression()
                    {
                        Value = "c"
                    },
                },
                Alias = new SqlIdentifierExpression()
                {
                    Value = "v",
                },
            },
        },
        From = new SqlTableExpression()
        {
            Name = new SqlIdentifierExpression()
            {
                Value = "TEST",
            },
            Alias = new SqlIdentifierExpression()
            {
                Value = "t",
            },
        },
    },
};



case when子句,主要体现在SqlCaseExpression表达式里,他包含了

  1. 各种case when键值对的列表,即Items字段,列表里的每一个元素都是SqlCaseItemExpression表达式,SqlCaseItemExpression表达式,又包含了
    1. 条件,即Condition字段,在本例子中是二元表达式,即SqlBinaryExpression表达式,值为t.name ='1'
    2. 值,即value字段,在本例子中值为字符串a
  2. Else字段,即默认值,本例子中为字符串c

case when还有另外一种句式,如下:

var sql = "select case t.name when 'a' then 1 else 2 end  from test t ";
var sqlAst = DbUtils.Parse(sql, DbType.Oracle);

解析结果如下:

var expect = new SqlSelectExpression()
{
    Query = new SqlSelectQueryExpression()
    {
        Columns = new List<SqlSelectItemExpression>()
        {
            new SqlSelectItemExpression()
            {
                Body = new SqlCaseExpression()
                {
                    Items = new List<SqlCaseItemExpression>()
                    {
                        new SqlCaseItemExpression()
                        {
                            Condition = new SqlStringExpression()
                            {
                                Value = "a"
                            },
                            Value = new SqlNumberExpression()
                            {
                                Value = 1M,
                            },
                        },
                    },
                    Else = new SqlNumberExpression()
                    {
                        Value = 2M,
                    },
                    Value = new SqlPropertyExpression()
                    {
                        Name = new SqlIdentifierExpression()
                        {
                            Value = "name",
                        },
                        Table = new SqlIdentifierExpression()
                        {
                            Value = "t",
                        },
                    },
                },
            },
        },
        From = new SqlTableExpression()
        {
            Name = new SqlIdentifierExpression()
            {
                Value = "test",
            },
            Alias = new SqlIdentifierExpression()
            {
                Value = "t",
            },
        },
    },
};



在这种SqlCaseExpression表达式里,他包含了

  1. case条件的主体变量,即Value字段,本例子中值为SqlPropertyExpression,它的值为t.name
  2. 各种when then键值对的列表,即Items字段,列表里的每一个元素都是SqlCaseItemExpression表达式,SqlCaseItemExpression表达式,又包含了
    1. 条件,即Condition字段,在本例子中是字符串表达式SqlStringExpression,它的值为a
    2. 值,即Value字段,在本例子中值为SqlNumberExpression,它的值为1
  3. Else字段,即默认值,本例子中为数字2

1.2.9 not子句

var sql = "select * from TEST t WHERE not t.NAME ='abc'";
var sqlAst = DbUtils.Parse(sql, DbType.Oracle);

解析结果如下:

var expect = new SqlSelectExpression()
{
    Query = new SqlSelectQueryExpression()
    {
        Columns = new List<SqlSelectItemExpression>()
        {
            new SqlSelectItemExpression()
            {
                Body = new SqlAllColumnExpression()
            },
        },
        From = new SqlTableExpression()
        {
            Name = new SqlIdentifierExpression()
            {
                Value = "TEST",
            },
            Alias = new SqlIdentifierExpression()
            {
                Value = "t",
            },
        },
        Where = new SqlNotExpression()
        {
            Body = new SqlBinaryExpression()
            {
                Left = new SqlPropertyExpression()
                {
                    Name = new SqlIdentifierExpression()
                    {
                        Value = "NAME",
                    },
                    Table = new SqlIdentifierExpression()
                    {
                        Value = "t",
                    },
                },
                Operator = SqlBinaryOperator.EqualTo,
                Right = new SqlStringExpression()
                {
                    Value = "abc"
                },
            },
        },
    },
};



not子句,主要体现在SqlNotExpression表达式里,它只有一个body字段,即代表否定的部分

1.2.10 变量子句

var sql = "select * from TEST t WHERE not t.NAME =:name";
var sqlAst = DbUtils.Parse(sql, DbType.Oracle);

解析结果如下:

var expect = new SqlSelectExpression()
{
    Query = new SqlSelectQueryExpression()
    {
        Columns = new List<SqlSelectItemExpression>()
        {
            new SqlSelectItemExpression()
            {
                Body = new SqlAllColumnExpression()
            },
        },
        From = new SqlTableExpression()
        {
            Name = new SqlIdentifierExpression()
            {
                Value = "TEST",
            },
            Alias = new SqlIdentifierExpression()
            {
                Value = "t",
            },
        },
        Where = new SqlNotExpression()
        {
            Body = new SqlBinaryExpression()
            {
                Left = new SqlPropertyExpression()
                {
                    Name = new SqlIdentifierExpression()
                    {
                        Value = "NAME",
                    },
                    Table = new SqlIdentifierExpression()
                    {
                        Value = "t",
                    },
                },
                Operator = SqlBinaryOperator.EqualTo,
                Right = new SqlVariableExpression()
                {
                    Name = "name",
                    Prefix = ":",
                },
            },
        },
    },
};


变量子句,主要体现在SqlVariableExpression表达式里,它包括以下部分:

  1. 变量名,即字段Name,这里值为name
  2. 变量前缀,这里值为:

1.3 From数据源

在sql中,From关键字后面有多种形式来指定数据源。主要有以下几种

1.3.1 表名或者视图

select * from test

这个解析结果上面已经演示了。

1.3.2 子查询(子表)

var sql = "select * from (select * from test) t";
var sqlAst = DbUtils.Parse(sql, DbType.Oracle);

解析结果如下:

var expect = new SqlSelectExpression()
{
    Query = new SqlSelectQueryExpression()
    {
        Columns = new List<SqlSelectItemExpression>()
        {
            new SqlSelectItemExpression()
            {
                Body = new SqlAllColumnExpression()
            },
        },
        From = new SqlSelectExpression()
        {
            Alias = new SqlIdentifierExpression()
            {
                Value = "t",
            },
            Query = new SqlSelectQueryExpression()
            {
                Columns = new List<SqlSelectItemExpression>()
                {
                    new SqlSelectItemExpression()
                    {
                        Body = new SqlAllColumnExpression()
                    },
                },
                From = new SqlTableExpression()
                {
                    Name = new SqlIdentifierExpression()
                    {
                        Value = "test",
                    },
                },
            },
        },
    },
};

在这个例子中,数据源From的值为一个SqlSelectExpression,即SqlSelectExpression中可以嵌套SqlSelectExpression,同时我们注意到内部的SqlSelectExpression有一个表别名的字段Alias,标识符的值为t,表示表别名为t;

1.3.3 连表查询(JOIN)

var sql = "select t1.id from test t1 left join test2 t2 on t1.id=t2.id right join test3 t3 on t2.id=t3.id";
var sqlAst = DbUtils.Parse(sql, DbType.Oracle);

解析结果如下:

var expect = new SqlSelectExpression()
{
    Query = new SqlSelectQueryExpression()
    {
        Columns = new List<SqlSelectItemExpression>()
        {
            new SqlSelectItemExpression()
            {
                Body = new SqlPropertyExpression()
                {
                    Name = new SqlIdentifierExpression()
                    {
                        Value = "id",
                    },
                    Table = new SqlIdentifierExpression()
                    {
                        Value = "t1",
                    },
                },
            },
        },
        From = new SqlJoinTableExpression()
        {
            Left = new SqlJoinTableExpression()
            {
                Left = new SqlTableExpression()
                {
                    Name = new SqlIdentifierExpression()
                    {
                        Value = "test",
                    },
                    Alias = new SqlIdentifierExpression()
                    {
                        Value = "t1",
                    },
                },
                JoinType = SqlJoinType.LeftJoin,
                Right = new SqlTableExpression()
                {
                    Name = new SqlIdentifierExpression()
                    {
                        Value = "test2",
                    },
                    Alias = new SqlIdentifierExpression()
                    {
                        Value = "t2",
                    },
                },
                Conditions = new SqlBinaryExpression()
                {
                    Left = new SqlPropertyExpression()
                    {
                        Name = new SqlIdentifierExpression()
                        {
                            Value = "id",
                        },
                        Table = new SqlIdentifierExpression()
                        {
                            Value = "t1",
                        },
                    },
                    Operator = SqlBinaryOperator.EqualTo,
                    Right = new SqlPropertyExpression()
                    {
                        Name = new SqlIdentifierExpression()
                        {
                            Value = "id",
                        },
                        Table = new SqlIdentifierExpression()
                        {
                            Value = "t2",
                        },
                    },
                },
            },
            JoinType = SqlJoinType.RightJoin,
            Right = new SqlTableExpression()
            {
                Name = new SqlIdentifierExpression()
                {
                    Value = "test3",
                },
                Alias = new SqlIdentifierExpression()
                {
                    Value = "t3",
                },
            },
            Conditions = new SqlBinaryExpression()
            {
                Left = new SqlPropertyExpression()
                {
                    Name = new SqlIdentifierExpression()
                    {
                        Value = "id",
                    },
                    Table = new SqlIdentifierExpression()
                    {
                        Value = "t2",
                    },
                },
                Operator = SqlBinaryOperator.EqualTo,
                Right = new SqlPropertyExpression()
                {
                    Name = new SqlIdentifierExpression()
                    {
                        Value = "id",
                    },
                    Table = new SqlIdentifierExpression()
                    {
                        Value = "t3",
                    },
                },
            },
        },
    },
};

在上面这个例子中,我们演示了连表查询是如何解析的,From字段的值为一个SqlJoinTableExpression,即连表查询表达式,他包含了

  1. 左边部分,即Left字段
  2. 右边部分,即Right字段
  3. 连接方式,即JoinType字段,值包括InnerJoin,LeftJoin,RightJoin,FullJoin,CrossJoin,CommaJoin
  4. 表关联条件,即Conditions字段。在这里,Conditions字段的值为一个二元表达式SqlBinaryExpression

在这个例子中,总共3张表联查,SqlJoinTableExpression中得left字段又是一个SqlJoinTableExpression,即SqlJoinTableExpression中可以嵌套SqlJoinTableExpression,无限套娃。

1.3.4 公用表表达式(CTE)

var sql = "with c1 as (select name from test t) , c2(name) AS (SELECT name FROM TEST2 t3 ) select *from c1 JOIN c2 ON c1.name=c2.name";
var sqlAst = DbUtils.Parse(sql, DbType.Oracle);

解析结果如下:

var expect = new SqlSelectExpression()
{
    Query = new SqlSelectQueryExpression()
    {
        WithSubQuerys = new List<SqlWithSubQueryExpression>()
        {
            new SqlWithSubQueryExpression()
            {
                Alias = new SqlIdentifierExpression()
                {
                    Value = "c1",
                },
                FromSelect = new SqlSelectExpression()
                {
                    Query = new SqlSelectQueryExpression()
                    {
                        Columns = new List<SqlSelectItemExpression>()
                        {
                            new SqlSelectItemExpression()
                            {
                                Body = new SqlIdentifierExpression()
                                {
                                    Value = "name",
                                },
                            },
                        },
                        From = new SqlTableExpression()
                        {
                            Name = new SqlIdentifierExpression()
                            {
                                Value = "test",
                            },
                            Alias = new SqlIdentifierExpression()
                            {
                                Value = "t",
                            },
                        },
                    },
                },
            },
            new SqlWithSubQueryExpression()
            {
                Alias = new SqlIdentifierExpression()
                {
                    Value = "c2",
                },
                FromSelect = new SqlSelectExpression()
                {
                    Query = new SqlSelectQueryExpression()
                    {
                        Columns = new List<SqlSelectItemExpression>()
                        {
                            new SqlSelectItemExpression()
                            {
                                Body = new SqlIdentifierExpression()
                                {
                                    Value = "name",
                                },
                            },
                        },
                        From = new SqlTableExpression()
                        {
                            Name = new SqlIdentifierExpression()
                            {
                                Value = "TEST2",
                            },
                            Alias = new SqlIdentifierExpression()
                            {
                                Value = "t3",
                            },
                        },
                    },
                },
                Columns = new List<SqlIdentifierExpression>()
                {
                    new SqlIdentifierExpression()
                    {
                        Value = "name",
                    },
                },
            },
        },
        Columns = new List<SqlSelectItemExpression>()
        {
            new SqlSelectItemExpression()
            {
                Body = new SqlAllColumnExpression()
            },
        },
        From = new SqlJoinTableExpression()
        {
            Left = new SqlTableExpression()
            {
                Name = new SqlIdentifierExpression()
                {
                    Value = "c1",
                },
            },
            JoinType = SqlJoinType.InnerJoin,
            Right = new SqlTableExpression()
            {
                Name = new SqlIdentifierExpression()
                {
                    Value = "c2",
                },
            },
            Conditions = new SqlBinaryExpression()
            {
                Left = new SqlPropertyExpression()
                {
                    Name = new SqlIdentifierExpression()
                    {
                        Value = "name",
                    },
                    Table = new SqlIdentifierExpression()
                    {
                        Value = "c1",
                    },
                },
                Operator = SqlBinaryOperator.EqualTo,
                Right = new SqlPropertyExpression()
                {
                    Name = new SqlIdentifierExpression()
                    {
                        Value = "name",
                    },
                    Table = new SqlIdentifierExpression()
                    {
                        Value = "c2",
                    },
                },
            },
        },
    },
};

公用表表达式(CTE),主要体现在SqlSelectQueryExpression的WithSubQuerys字段,他是一个SqlWithSubQueryExpression表达式列表,即公用表列表,它里面的每一个元素都是SqlWithSubQueryExpression表达式,此表达式,包含了

  1. 公共表的来源部分,即FromSelect字段,在本例子中,他的值是一个SqlSelectExpression表达式,即一个查询
  2. 公共表的表别名,即Alias字段,在本例子中,他的值是c1
  3. 公共表的列部分,即Columns字段,在本例子中只有一个列名,即name

1.3.5 函数返回的结果集

特定数据库支持从返回结果集的函数中查询,比如oracle中添加一个自定义函数splitstr,他的作用是将一个字符串根据;号进行分割,返回多行数据

var sql = "SELECT * FROM TABLE(splitstr('a;b',';'))";
var sqlAst = DbUtils.Parse(sql, DbType.Oracle);

解析结果如下:

var expect = new SqlSelectExpression()
{
    Query = new SqlSelectQueryExpression()
    {
        Columns = new List<SqlSelectItemExpression>()
        {
            new SqlSelectItemExpression()
            {
                Body = new SqlAllColumnExpression()
            },
        },
        From = new SqlReferenceTableExpression()
        {
            FunctionCall = new SqlFunctionCallExpression()
            {
                Name = new SqlIdentifierExpression()
                {
                    Value = "TABLE",
                },
                Arguments = new List<SqlExpression>()
                {
                    new SqlFunctionCallExpression()
                    {
                        Name = new SqlIdentifierExpression()
                        {
                            Value = "splitstr",
                        },
                        Arguments = new List<SqlExpression>()
                        {
                            new SqlStringExpression()
                            {
                                Value = "a;b"
                            },
                            new SqlStringExpression()
                            {
                                Value = ";"
                            },
                        },
                    },
                },
            },
        }
    },
};

函数返回的结果集主要体现在SqlReferenceTableExpression表达式,他的内部包含了一个FunctionCall字段,值为SqlFunctionCallExpression表达式,代表从函数调用的结果集中进行查询。

1.4 OrderBy排序语句

var sql = "select fa.FlowId  from FlowActivity fa order by fa.FlowId desc,fa.Id asc";
var sqlAst = DbUtils.Parse(sql, DbType.SqlServer);

解析结果如下:

var expect = new SqlSelectExpression()
{
    Query = new SqlSelectQueryExpression()
    {
        Columns = new List<SqlSelectItemExpression>()
        {
            new SqlSelectItemExpression()
            {
                Body = new SqlPropertyExpression()
                {
                    Name = new SqlIdentifierExpression()
                    {
                        Value = "FlowId"
                    },
                    Table = new SqlIdentifierExpression()
                    {
                        Value = "fa"
                    },
                },
            },
        },
        From = new SqlTableExpression()
        {
            Name = new SqlIdentifierExpression()
            {
                Value = "FlowActivity"
            },
            Alias = new SqlIdentifierExpression()
            {
                Value = "fa"
            },
        },
        OrderBy = new SqlOrderByExpression()
        {
            Items = new List<SqlOrderByItemExpression>()
            {
                new SqlOrderByItemExpression()
                {
                    Body =
                        new SqlPropertyExpression()
                        {
                            Name = new SqlIdentifierExpression()
                            {
                                Value = "FlowId"
                            },
                            Table = new SqlIdentifierExpression()
                            {
                                Value = "fa"
                            },
                        },
                    OrderByType = SqlOrderByType.Desc
                },
                new SqlOrderByItemExpression()
                {
                    Body =
                        new SqlPropertyExpression()
                        {
                            Name = new SqlIdentifierExpression()
                            {
                                Value = "Id"
                            },
                            Table = new SqlIdentifierExpression()
                            {
                                Value = "fa"
                            },
                        },
                    OrderByType = SqlOrderByType.Asc
                },
            },
        },
    },
};


OrderBy排序子句,值为SqlOrderByExpression表达式,表达式的内容也非常简单,只有一个Items,即一个排序子项表达式的列表,列表里的值为SqlOrderByItemExpression,即排序子项表达式,排序子项表达式里又包含了以下内容

  1. 排序依据,即Body字段,在这个例子中,排序依据是2个SqlPropertyExpression表达式,即根据fa.FlowId,fa.Id排序
  2. 排序类型,即OrderByType字段,值为Asc或者Desc,默认为asc,在这2个例子中,有asc和Desc
  3. 决定null排在前面或后面的NullsType字段,在oracle,pgsql,sqlite中我们可以指定null在排序中的位置,如以下sql
select * from TEST5 t order by t.NAME  desc nulls FIRST,t.AGE ASC NULLS  last 

那么我们的NullsType字段,他的值有SqlOrderByNullsType.First和SqlOrderByNullsType.Last,与之对应。

1.5 GroupBy分组语句

var sql = "select fa.FlowId  from FlowActivity fa group by fa.FlowId,fa.Id HAVING count(fa.Id)>1";
var sqlAst = DbUtils.Parse(sql, DbType.SqlServer);

解析结果如下:

var expect = new SqlSelectExpression()
{
    Query = new SqlSelectQueryExpression()
    {
        Columns = new List<SqlSelectItemExpression>()
        {
            new SqlSelectItemExpression()
            {
                Body = new SqlPropertyExpression()
                {
                    Name = new SqlIdentifierExpression()
                    {
                        Value = "FlowId"
                    },
                    Table = new SqlIdentifierExpression()
                    {
                        Value = "fa"
                    },
                },
            },
        },
        From = new SqlTableExpression()
        {
            Name = new SqlIdentifierExpression()
            {
                Value = "FlowActivity"
            },
            Alias = new SqlIdentifierExpression()
            {
                Value = "fa"
            },
        },
        GroupBy = new SqlGroupByExpression()
        {
            Items = new List<SqlExpression>()
            {
                new SqlPropertyExpression()
                {
                    Name = new SqlIdentifierExpression()
                    {
                        Value = "FlowId"
                    },
                    Table = new SqlIdentifierExpression()
                    {
                        Value = "fa"
                    },
                },
                new SqlPropertyExpression()
                {
                    Name = new SqlIdentifierExpression()
                    {
                        Value = "Id"
                    },
                    Table = new SqlIdentifierExpression()
                    {
                        Value = "fa"
                    },
                },
            },
            Having = new SqlBinaryExpression()
            {
                Left = new SqlFunctionCallExpression()
                {
                    Name = new SqlIdentifierExpression()
                    {
                        Value = "count"
                    },
                    Arguments = new List<SqlExpression>()
                    {
                        new SqlPropertyExpression()
                        {
                            Name = new SqlIdentifierExpression()
                            {
                                Value = "Id"
                            },
                            Table = new SqlIdentifierExpression()
                            {
                                Value = "fa"
                            },
                        },
                    },
                },
                Operator = SqlBinaryOperator.GreaterThen,
                Right = new SqlNumberExpression()
                {
                    Value = 1M
                },
            },
        },
    },
};


GroupBy分组语句,值为SqlGroupByExpression表达式,他的内容如下

  1. 分组子项表达式的列表,即Items字段,列表里的值为SqlExpression,他的值是一个逻辑子句
  2. 分组过滤子句,即Having字段,他的值是一个逻辑子句,在本例子中,逻辑子句的值为一个SqlBinaryExpression

1.5 Limit分页子句

1.5.1 mysql,sqlite

var sql = "select * from test t limit 1,5";
var sqlAst = DbUtils.Parse(sql, DbType.MySql);

解析结果如下:

var expect = new SqlSelectExpression()
{
    Query = new SqlSelectQueryExpression()
    {
        Columns = new List<SqlSelectItemExpression>()
        {
            new SqlSelectItemExpression()
            {
                Body = new SqlAllColumnExpression()
            },
        },
        From = new SqlTableExpression()
        {
            Name = new SqlIdentifierExpression()
            {
                Value = "test",
            },
            Alias = new SqlIdentifierExpression()
            {
                Value = "t",
            },
        },
        Limit = new SqlLimitExpression()
        {
            Offset = new SqlNumberExpression()
            {
                Value = 1M,
            },
            RowCount = new SqlNumberExpression()
            {
                Value = 5M,
            },
        },
    },
};


Limit分页子句,值为SqlLimitExpression表达式,他的内容如下

  1. 每页数量,即RowCount字段,这本例子中,值为5
  2. 跳过数量,即Offset字段,本例子中,值为1

1.5.2 oracle

var sql = "SELECT * FROM TEST3 t  ORDER BY t.NAME  DESC FETCH FIRST 2 rows ONLY";
var sqlAst = DbUtils.Parse(sql, DbType.Oracle);

解析结果如下:

var expect = new SqlSelectExpression()
{
    Query = new SqlSelectQueryExpression()
    {
        Columns = new List<SqlSelectItemExpression>()
        {
            new SqlSelectItemExpression()
            {
                Body = new SqlAllColumnExpression()
            }
        },
        From = new SqlTableExpression()
        {
            Alias = new SqlIdentifierExpression()
            {
                Value = "t"
            },
            Name = new SqlIdentifierExpression()
            {
                Value = "TEST3"
            }
        },

        OrderBy = new SqlOrderByExpression()
        {
            Items = new List<SqlOrderByItemExpression>()
            {
                new SqlOrderByItemExpression()
                {
                    OrderByType = SqlOrderByType.Desc,
                    Body = new SqlPropertyExpression()
                    {
                        Name = new SqlIdentifierExpression() { Value = "NAME" },
                        Table = new SqlIdentifierExpression()
                        {
                            Value = "t"
                        }
                    }
                }
            }
        },
        Limit = new SqlLimitExpression()
        {
            RowCount = new SqlNumberExpression()
            {
                Value = 2
            }
        }
    }
};

1.5.3 pgsql

var sql = "select * from test5   t order by t.name limit 1 offset 10;";
var sqlAst = DbUtils.Parse(sql, DbType.Pgsql);

解析结果如下:

var expect = new SqlSelectExpression()
{
    Query = new SqlSelectQueryExpression()
    {
        Columns = new List<SqlSelectItemExpression>()
        {
            new SqlSelectItemExpression()
            {
                Body = new SqlAllColumnExpression()
            },
        },
        From = new SqlTableExpression()
        {
            Name = new SqlIdentifierExpression()
            {
                Value = "test5",
            },
            Alias = new SqlIdentifierExpression()
            {
                Value = "t",
            },
        },
        OrderBy = new SqlOrderByExpression()
        {
            Items = new List<SqlOrderByItemExpression>()
            {
                new SqlOrderByItemExpression()
                {
                    Body = new SqlPropertyExpression()
                    {
                        Name = new SqlIdentifierExpression()
                        {
                            Value = "name",
                        },
                        Table = new SqlIdentifierExpression()
                        {
                            Value = "t",
                        },
                    },
                },
            },
        },
        Limit = new SqlLimitExpression()
        {
            Offset = new SqlNumberExpression()
            {
                Value = 10M,
            },
            RowCount = new SqlNumberExpression()
            {
                Value = 1M,
            },
        },
    },
};


1.5.4 sqlServer

var sql = "select * from test t order by t.name OFFSET 5 ROWS FETCH NEXT 10 ROWS ONLY";
var sqlAst = DbUtils.Parse(sql, DbType.SqlServer);

解析结果如下:

var expect = new SqlSelectExpression()
{
    Query = new SqlSelectQueryExpression()
    {
        Columns = new List<SqlSelectItemExpression>()
        {
            new SqlSelectItemExpression()
            {
                Body = new SqlAllColumnExpression()
            }
        },
        From = new SqlTableExpression()
        {
            Alias = new SqlIdentifierExpression()
            {
                Value = "t"
            },
            Name = new SqlIdentifierExpression()
            {
                Value = "test"
            }
        },

        OrderBy = new SqlOrderByExpression()
        {
            Items = new List<SqlOrderByItemExpression>()
            {
                new SqlOrderByItemExpression()
                {
                    Body = new SqlPropertyExpression()
                    {
                        Name = new SqlIdentifierExpression() { Value = "name" },
                        Table = new SqlIdentifierExpression()
                        {
                            Value = "t"
                        }
                    }
                }
            }
        },
        Limit = new SqlLimitExpression()
        {
            Offset = new SqlNumberExpression()
            {
                Value = 5
            },
            RowCount = new SqlNumberExpression()
            {
                Value = 10
            }
        }
    }
};


1.6 ConnectBy层次查询语句(oracle专用)

var sql = "SELECT EMPLOYEEID , MANAGERID , LEVEL FROM EMPLOYEE e START WITH MANAGERID IS NULL CONNECT BY NOCYCLE PRIOR EMPLOYEEID = MANAGERID ORDER SIBLINGS BY EMPLOYEEID ";
var sqlAst = DbUtils.Parse(sql, DbType.Oracle);

解析结果如下:

var expect = new SqlSelectExpression()
{
    Query = new SqlSelectQueryExpression()
    {
        Columns = new List<SqlSelectItemExpression>()
        {
            new SqlSelectItemExpression()
            {
                Body = new SqlIdentifierExpression()
                {
                    Value = "EMPLOYEEID",
                },
            },
            new SqlSelectItemExpression()
            {
                Body = new SqlIdentifierExpression()
                {
                    Value = "MANAGERID",
                },
            },
            new SqlSelectItemExpression()
            {
                Body = new SqlIdentifierExpression()
                {
                    Value = "LEVEL",
                },
            },
        },
        From = new SqlTableExpression()
        {
            Name = new SqlIdentifierExpression()
            {
                Value = "EMPLOYEE",
            },
            Alias = new SqlIdentifierExpression()
            {
                Value = "e",
            },
        },
        ConnectBy = new SqlConnectByExpression()
        {
            StartWith = new SqlBinaryExpression()
            {
                Left = new SqlIdentifierExpression()
                {
                    Value = "MANAGERID",
                },
                Operator = SqlBinaryOperator.Is,
                Right = new SqlNullExpression()
            },
            Body = new SqlBinaryExpression()
            {
                Left = new SqlIdentifierExpression()
                {
                    Value = "EMPLOYEEID",
                },
                Operator = SqlBinaryOperator.EqualTo,
                Right = new SqlIdentifierExpression()
                {
                    Value = "MANAGERID",
                },
            },
            IsNocycle = true,
            IsPrior = true,
            OrderBy = new SqlOrderByExpression()
            {
                Items = new List<SqlOrderByItemExpression>()
                {
                    new SqlOrderByItemExpression()
                    {
                        Body = new SqlIdentifierExpression()
                        {
                            Value = "EMPLOYEEID",
                        },
                    },
                },
                IsSiblings = true,
            },
        },
    },
};

ConnectBy层次查询子句,值为SqlConnectByExpression表达式,他的内容如下

  1. 指定层次查询的根节点条件,即StartWith字段,本例子中他的值为SqlBinaryExpression二元表达式
  2. 主体关联条件子句,即Body字段,本例子中他的值是一个SqlBinaryExpression二元表达式
  3. IsPrior字段用来指示层次结构中哪个列是父节点,如果sql中存在Prior关键字,则值为true
  4. IsNocycle字段,用来防止循环引用导致无限递归,如果sql中存在Nocycle则为true
  5. order by子句,用于排序

1.7 Into子句(sql server专用)

var sql = "SELECT name into test14 from TEST as t ";
var sqlAst = DbUtils.Parse(sql, DbType.SqlServer);

解析结果如下:

var expect = new SqlSelectExpression()
{
    Query = new SqlSelectQueryExpression()
    {
        Columns = new List<SqlSelectItemExpression>()
        {
            new SqlSelectItemExpression()
            {
                Body = new SqlIdentifierExpression()
                {
                    Value = "name"
                },
            },
        },
        Into = new SqlTableExpression()
        {
            Name = new SqlIdentifierExpression()
            {
                Value = "test14"
            },
        },
        From = new SqlTableExpression()
        {
            Name = new SqlIdentifierExpression()
            {
                Value = "TEST"
            },
            Alias = new SqlIdentifierExpression()
            {
                Value = "t"
            },
        },
    },
};

into子句,在本例子中值为SqlTableExpression,即into到某张表里。

2. Insert插入语句

2.1 插入单个值

var sql = "insert into test11(name,id) values('a1','a2')";
var sqlAst = DbUtils.Parse(sql, DbType.SqlServer);

解析结果如下:

var expect = new SqlInsertExpression()
{
    Columns = new List<SqlExpression>()
    {
        new SqlIdentifierExpression()
        {
            Value = "name"
        },
        new SqlIdentifierExpression()
        {
            Value = "id"
        },
    },
    ValuesList = new List<List<SqlExpression>>()
    {
        new List<SqlExpression>()
        {
            new SqlStringExpression()
            {
                Value = "a1"
            },
            new SqlStringExpression()
            {
                Value = "a2"
            },
        },
    },
    Table = new SqlTableExpression()
    {
        Name = new SqlIdentifierExpression()
        {
            Value = "test11"
        },
    },
};

如上例子,插入语句表现为一个SqlInsertExpression,他包含了

  1. 要插入的字段列表,即Columns字段,值为一个SqlExpression的列表,本例子中值为2个SqlIdentifierExpression,它们的值为name和id,即插入name和id字段
  2. 值列表,即ValuesList字段,值为一个List<SqlExpression>的列表,即列表里的元素是一个列表,每个元素代表一组待插入的数据,本例子中列表里只有一个List
    ,并且子列表里的值为a1和a2,即待插入的值为a1和a2。
  3. 待插入数据的表,即Table字段,本例子中值为test11

为什么ValuesList字段是列表里嵌套列表呢?主要是因为可以插入多个值列表,让我们继续往下看

2.2 插入多个值

var sql = "insert into test11(name,id) values('a1','a2'),('a3','a4')";
var sqlAst = DbUtils.Parse(sql, DbType.MySql);

解析结果如下:

var expect = new SqlInsertExpression()
{
    Columns = new List<SqlExpression>()
    {
        new SqlIdentifierExpression()
        {
            Value = "name"
        },
        new SqlIdentifierExpression()
        {
            Value = "id"
        },
    },
    ValuesList = new List<List<SqlExpression>>()
    {
        new List<SqlExpression>()
        {
            new SqlStringExpression()
            {
                Value = "a1"
            },
            new SqlStringExpression()
            {
                Value = "a2"
            },
        },
        new List<SqlExpression>()
        {
            new SqlStringExpression()
            {
                Value = "a3"
            },
            new SqlStringExpression()
            {
                Value = "a4"
            },
        },
    },
    Table = new SqlTableExpression()
    {
        Name = new SqlIdentifierExpression()
        {
            Value = "test11"
        },
    },
};

在本例子中,ValuesList字段中有2个子元素,即2个List
列表,代表插入2组数据,值分别为('a1','a2')和('a3','a4')

2.3 待插入的值为一个子查询

var sql = "INSERT INTO TEST2(name) SELECT name AS name2 FROM TEST t";
var sqlAst = DbUtils.Parse(sql, DbType.MySql);

解析结果如下:

var expect = new SqlInsertExpression()
{
    Columns = new List<SqlExpression>()
    {
        new SqlIdentifierExpression()
        {
            Value = "name"
        },
    },
    Table = new SqlTableExpression()
    {
        Name = new SqlIdentifierExpression()
        {
            Value = "TEST2"
        },
    },
    FromSelect = new SqlSelectExpression()
    {
        Query = new SqlSelectQueryExpression()
        {
            Columns = new List<SqlSelectItemExpression>()
            {
                new SqlSelectItemExpression()
                {
                    Body = new SqlIdentifierExpression()
                    {
                        Value = "name"
                    },
                    Alias = new SqlIdentifierExpression()
                    {
                        Value = "name2"
                    },
                },
            },
            From = new SqlTableExpression()
            {
                Name = new SqlIdentifierExpression()
                {
                    Value = "TEST"
                },
                Alias = new SqlIdentifierExpression()
                {
                    Value = "t"
                },
            },
        },
    },
};

如上例子,插入语句表现为一个SqlInsertExpression,他包含了

  1. 要插入的字段列表,即Columns字段,值为一个SqlExpression的列表,本例子中值为name
  2. 子查询来源,即FromSelect字段,值为一个SqlSelectExpression,即一个子查询
  3. 待插入数据的表,即Table字段,本例子中值为TEST2

3. Update更新语句

var sql = "update test set name ='4',d='2024-11-22 08:19:47.243' where name ='1'";
var sqlAst = DbUtils.Parse(sql, DbType.MySql);

解析结果如下:

var expect = new SqlUpdateExpression()
{
    Table = new SqlTableExpression()
    {
        Name = new SqlIdentifierExpression()
        {
            Value = "test"
        },
    },
    Where = new SqlBinaryExpression()
    {
        Left = new SqlIdentifierExpression()
        {
            Value = "name"
        },
        Operator = SqlBinaryOperator.EqualTo,
        Right = new SqlStringExpression()
        {
            Value = "1"
        },
    },
    Items = new List<SqlExpression>()
    {
        new SqlBinaryExpression()
        {
            Left = new SqlIdentifierExpression()
            {
                Value = "name"
            },
            Operator = SqlBinaryOperator.EqualTo,
            Right = new SqlStringExpression()
            {
                Value = "4"
            },
        },
        new SqlBinaryExpression()
        {
            Left = new SqlIdentifierExpression()
            {
                Value = "d"
            },
            Operator = SqlBinaryOperator.EqualTo,
            Right = new SqlStringExpression()
            {
                Value = "2024-11-22 08:19:47.243"
            },
        },
    },
};

如上例子,更新语句表现为一个SqlUpdateExpression,他包含了

  1. 要更新的(字段-值)的列表,即Items字段,值为一个SqlExpression的列表,本例子中值为2个SqlBinaryExpression,即name='4'和d='2024-11-22 08:19:47.243'
  2. 条件过滤子句,即Where字段,代表过滤条件,本例子中值为一个SqlBinaryExpression,即name ='1'
  3. 待更新数据的表,即Table字段,本例子中值为test

4. Delete删除语句

var sql = "delete from test where name=4";
var sqlAst = DbUtils.Parse(sql, DbType.MySql);

解析结果如下:

var expect = new SqlDeleteExpression()
{
    Table = new SqlTableExpression()
    {
        Name = new SqlIdentifierExpression()
        {
            Value = "test"
        },
    },
    Where = new SqlBinaryExpression()
    {
        Left = new SqlIdentifierExpression()
        {
            Value = "name"
        },
        Operator = SqlBinaryOperator.EqualTo,
        Right = new SqlNumberExpression()
        {
            Value = 4M
        },
    },
};

如上例子,删除语句表现为一个SqlDeleteExpression,他包含了

  1. 条件过滤子句,即Where字段,代表过滤条件,本例子中值为一个SqlBinaryExpression,即name=4
  2. 待删除数据的表,即Table字段,本例子中值为test

5. 注释处理

5.1 单行注释

var sql = @"select *--abc from test lbu WHERE a ='1'--aaaaaa
FROM test";
var sqlAst = DbUtils.Parse(sql, DbType.SqlServer);

解析结果如下:

var expect = new SqlSelectExpression()
{
    Query = new SqlSelectQueryExpression()
    {
        Columns = new List<SqlSelectItemExpression>()
        {
            new SqlSelectItemExpression()
            {
                Body = new SqlAllColumnExpression()
            },
        },
        From = new SqlTableExpression()
        {
            Name = new SqlIdentifierExpression()
            {
                Value = "test",
            },
        },
    },
};

如上例子,单行注释被正确忽视,解析正确。

5.2 多行注释

var sql = @"/*这
            是
            顶部*/
            select *--abc
            FROM test/*这
            是
            底部*/";
var sqlAst = DbUtils.Parse(sql, DbType.Oracle);

解析结果如下:

var expect = new SqlSelectExpression()
{
    Query = new SqlSelectQueryExpression()
    {
        Columns = new List<SqlSelectItemExpression>()
        {
            new SqlSelectItemExpression()
            {
                Body = new SqlAllColumnExpression()
            },
        },
        From = new SqlTableExpression()
        {
            Name = new SqlIdentifierExpression()
            {
                Value = "test",
            },
        },
    },
};

如上例子,多行注释被正确忽视,解析正确。

6. 如何解析ast抽象语法树

当我们通过

var sql = @"select * from test";
var sqlAst = DbUtils.Parse(sql, DbType.Oracle);

解析sql获取到抽象语法树以后,我们就要对这颗抽象语法树进行解析,获取我们想要的数据,此时就要用上访问者模式(visitor) .

6.1 访问者模式

访问者模式最大的特点就是结构与算法分离,结合本项目理解,就是ast抽象语法树这个结构已经解析出来了,你可以根据自己的需要写算法去任意解析这颗语法树。这是一个1-N的操作,即一个抽象语法树,可以对应N个解析算法,当我们要自定义算法去解析抽象语法树时,我们需要自定义一个Visitor类,并且实现IAstVisitor接口

public class CustomVisitor : IAcceptVisitor
{
    
}

但是实现这个接口要实现接口里的很多个方法,并且有些数据并不是我们关心的,所以我提供了一个实现了IAcceptVisitor接口的抽象类BaseAstVisitor用来简化操作,我们只需要继承这个抽象类,然后重写我们感兴趣的方法即可

public class CustomVisitor : BaseAstVisitor
{
    
}

在本项目中,我提供了2个基本的vistor供大家使用,UnitTestAstVisitor和SqlGenerationAstVisitor,大家可以参考这2个visitor去写自己的算法来解析抽象语法树。接下来,我将介绍这2个visitor的用法。

6.2 UnitTestAstVisitor

当我们通过

var sql = @"select * from test";
var sqlAst = DbUtils.Parse(sql, DbType.Oracle);

解析sql获取到抽象语法树之后,sqlAst其实还是一个数据结构,我们可以通过vs监视这个变量来查看内部的结构,但是如果是非常复杂的sql,那这颗树会巨大无比,要靠我们手动去慢慢点开查看得累死,没错!写单元测试的时候,我刚开始都是用手写的结果去对比引擎解析出来的结果,后来我就被累死了,该说不说,这活真不是人干的,所以痛定思痛之后我就写了这个UnitTestAstVisitor来替我生成ast的结构字符串,接下来让我们看看用法

var sql = @"select * from test";
var sqlAst = DbUtils.Parse(sql, DbType.Oracle);
var unitTestAstVisitor = new UnitTestAstVisitor();
sqlAst.Accept(unitTestAstVisitor);
var result = unitTestAstVisitor.GetResult();

其中的result就是解析抽象语法树生成的字符串,如下:

var expect = new SqlSelectExpression()
{
    Query = new SqlSelectQueryExpression()
    {
        Columns = new List<SqlSelectItemExpression>()
        {
            new SqlSelectItemExpression()
            {
                Body = new SqlAllColumnExpression()
            },
        },
        From = new SqlTableExpression()
        {
            Name = new SqlIdentifierExpression()
            {
                Value = "test",
            },
        },
    },
};

然后把这个生成的字符串黏贴到vs里去和引擎生成的结果进行对比,

Assert.True(sqlAst.Equals(expect));

至此,我写单元测试的工作量大大减轻,同时对于生成的sqlAst语法树的结构也更加一目了然了。

6.2 SqlGenerationAstVisitor

我们通过解析sql生成了抽象语法树之后,如果我们想要给这颗抽象语法树添加一个where条件,比如添加test.name ='a'

var sql = @"select * from test";
var sqlAst = DbUtils.Parse(sql, DbType.Oracle);
if (sqlAst is SqlSelectExpression sqlSelectExpression && sqlSelectExpression.Query is SqlSelectQueryExpression sqlSelectQueryExpression)
{
    sqlSelectQueryExpression.Where = new SqlBinaryExpression()
    {
        Left = new SqlPropertyExpression()
        {
            Table = new SqlIdentifierExpression()
            {
                Value = "test"
            },
            Name = new SqlIdentifierExpression()
            {
                Value = "name"
            }
        },
        Operator = SqlBinaryOperator.EqualTo,
        Right = new SqlStringExpression()
        {
            Value = "a"
        }
    };
}

好了,现在我们添加完了,接下来我们肯定是想着把抽象语法树转化为sql语句,此时,就需要用上SqlGenerationAstVisitor了,他就是负责把抽象语法树转化为sql的

var sqlGenerationAstVisitor = new SqlGenerationAstVisitor(DbType.Oracle);
sqlAst.Accept(sqlGenerationAstVisitor);
var newSql = sqlGenerationAstVisitor.GetResult();

我们获取到的newSql就是新的sql了,他的值为

select * from test where(test.name = 'a')

至此,我们的目的就达到了。

7. sql解析的理论基础

sql之所以能被我们解析出来,主要是因为sql是一种形式语言,自然语言和形式语言的一个重要区别是,自然语言的一个语句,可能有多重含义,而形式语言的一个语句,只能有一个语义;形式语言的语法是人为规定的,有了一定的语法规则,语法解析器就能根据语法规则,解析出一个语句的唯一含义。

项目感悟

  1. 解决嵌套问题的唯一方案,就是用递归
  2. 对于基础项目,单元测试非常非常重要,因为开发的过程中可能会不断地重构,那以前跑过的测试案例就有可能失败,如果此时需要靠人手工去回归测试验证的话,那工作量是天量的,做不完,根本做不完,所以正确的解决方案是写单元测试,新添加一个功能后,为这个功能写1-N个单元测试,确保新功能对各种情况都有覆盖到,然后再跑一遍所有单元测试,确保没有影响到旧的功能。当然了,跑单元测试最让我崩溃的是,跑一遍所有单元测试,红了(即失败)几十个,天都塌了。

开源地址,欢迎star

本项目基于MIT协议开源,地址为
https://github.com/TripleView/SqlParser.Net

同时感谢以下项目

  1. 阿里巴巴开源的druid

写在最后

如果各位靓仔觉得这个项目不错,欢迎一键三连(推荐,star,关注),同时欢迎加入三合的开源交流群,QQ群号:799648362

QQ群799648362

上一篇:《人工智能大语言模型起源篇,低秩微调(LoRA)》

(14)Rae 和同事(包括78位合著者!)于2022年发表的《Scaling Language Models: Methods, Analysis & Insights from Training Gopher》,
https://arxiv.org/abs/2112.11446

《Gopher》是篇特别好的论文,包含了大量分析来帮助理解大型语言模型(LLM)的训练过程。在这篇论文中,研究人员训练了一个包含2800亿参数、80层的模型,使用了3000亿个标记(tokens)。其中包括一些有趣的架构修改,比如使用RMSNorm(均方根归一化)代替LayerNorm(层归一化)。LayerNorm和RMSNorm通常比BatchNorm更受欢迎,因为它们不依赖于批量大小,也不需要同步,在使用较小批量大小的分布式环境下具有优势。不过,通常认为RMSNorm能稳定更深层架构的训练。

除了像上述的有趣细节外,这篇论文的主要焦点是对不同规模模型任务表现的分析。在152个不同任务上的评估结果显示,增加模型规模对理解、事实核查和有害语言识别等任务的提升最大。不过,涉及逻辑和数学推理的任务,从架构扩展中获益较少。

                                              来源:图片来自https://arxiv.org/abs/2112.11446

(15)Hoffmann、Borgeaud、Mensch、Buchatskaya、Cai、Rutherford、de Las Casas、Hendricks、Welbl、Clark、Hennigan、Noland、Millican、van den Driessche、Damoc、Guy、Osindero、Simonyan、Elsen、Rae、Vinyals 和 Sifre 于2022年发表的《Training Compute-Optimal Large Language Models》,
https://arxiv.org/abs/2203.15556

这篇论文介绍了70亿参数的Chinchilla模型,它在生成建模任务上超越了流行的175亿参数的GPT-3模型。然而,论文的核心观点是,现代的大型语言模型“明显训练不足”。

论文定义了大语言模型训练的线性扩展规律。例如,尽管Chinchilla的规模只有GPT-3的一半,但它在训练了1.4万亿(而不是仅仅3000亿)标记后,超越了GPT-3。换句话说,训练标记的数量和模型规模一样重要。


来源:
https://arxiv.org/abs/2203.15556

(16)Biderman、Schoelkopf、Anthony、Bradley、O'Brien、Hallahan、Khan、Purohit、Prashanth、Raff、Skowron、Sutawika 和 van der Wal 于2023年发表的《Pythia: A Suite for Analyzing Large Language Models Across Training and Scaling》,
https://arxiv.org/abs/2304.01373

Pythia是一套开源的大型语言模型(70M到12B参数),用于研究大型语言模型在训练过程中如何演变。

它的架构类似于GPT-3,但包括了一些改进,例如,Flash Attention(类似LLaMA)和旋转位置嵌入(类似PaLM)。Pythia使用The Pile数据集(825GB)进行训练,训练了300B标记(在常规的PILE上约训练了1个epoch,在去重的PILE上训练了约1.5个epoch)。

                              pytaiya 模型套件来自 https://arxiv.org/abs/2304.01373。

Pythia研究的主要发现如下:

  1. 在重复数据上训练(由于大型语言模型的训练方式,这意味着训练超过一个epoch)不会对性能产生正面或负面影响。

  2. 训练顺序不会影响记忆效果。这很遗憾,因为如果情况相反,我们可以通过重新排序训练数据来缓解不希望的逐字记忆问题。

  3. 预训练的词频会影响任务性能。例如,较常见的词汇通常会提高少样本准确率。

  4. 将批量大小加倍,可以将训练时间减少一半,但不会影响收敛性。

对齐——引导大型语言模型朝向预期目标和利益

近年来,我们看到许多相对强大的大型语言模型,它们可以生成真实的文本(比如GPT-3和Chinchilla等)。似乎我们在使用常见的预训练范式上已经达到了瓶颈。

为了让语言模型更有帮助,并减少错误信息和有害语言,研究人员设计了额外的训练范式来微调预训练的基础模型。

(17) 《训练语言模型以遵循指令并结合人类反馈》(2022年),作者:Ouyang、Wu、Jiang、Almeida、Wainwright、Mishkin、Zhang、Agarwal、Slama、Ray、Schulman、Hilton、Kelton、Miller、Simens、Askell、Welinder、Christiano、Leike、Lowe,网址:
https://arxiv.org/abs/2203.02155。

在这篇所谓的InstructGPT论文中,研究人员使用了一个带有人类在环的强化学习机制(RLHF)。他们从一个预训练的GPT-3基础模型开始,使用人类生成的提示-回应对进行监督学习,进一步微调模型(步骤1)。接下来,他们让人类对模型输出进行排名,以训练一个奖励模型(步骤2)。最后,他们使用奖励模型,通过近端策略优化(PPO)强化学习更新预训练并已微调的GPT-3模型(步骤3)。

顺便提一下,这篇论文也被认为是描述ChatGPT背后思想的论文——根据最近的传闻,ChatGPT是InstructGPT的扩展版,已经在更大的数据集上进行了微调。

                                              来源:https://arxiv.org/abs/2203.02155

近期,我在致力于打造自己的小程序产品时,迎来了一项关键性的进展——
微信相关授权流程
的完整实现。从用户登录到权限获取,我们细致入微地梳理并实现了每一项授权机制,确保了用户体验的流畅与安全。

微信小程序授权

image

image

授权流程:

  1. 用户在小程序中点击登录按钮,触发
    wx.login()
    获取
    code
  2. 小程序将
    code
    发送到后端服务器。
  3. 后端通过微信接口
    jscode2session
    使用
    code
    获取
    session_key

    openid
  4. 后端返回
    session_key

    openid
    给前端。
  5. 前端获取
    session_key

    openid
    ,使用
    wx.getUserProfile()
    获取用户信息(如昵称、头像等)。
  6. 如果需要,可以将用户信息(如昵称、头像等)发送到后端进行存储或处理。
wx.login({
  success: function(res) {
    if (res.code) {
      // 将 code 发送到服务器
      wx.request({
        url: 'https://localhost:8080/api/login',
        method: 'POST',
        data: {
          code: res.code
        },
        success: function(response) {
          // 处理服务器返回的数据
          console.log(response.data);
        }
      });
    }
  }
});

下面是Nest 伪代码实现

import { Injectable } from '@nestjs/common';
import { HttpService } from '@nestjs/axios';
import { firstValueFrom } from 'rxjs';

interface MiniProgramLoginResponse {
  openid: string;
  session_key: string;
  unionid?: string;
  errcode?: number;
  errmsg?: string;
}

@Injectable()
  export class MiniProgramAuthService {
    constructor(private readonly httpService: HttpService) {}

    async login(code: string): Promise<MiniProgramLoginResponse> {
      const url = 'https://api.weixin.qq.com/sns/jscode2session';

      try {
        const response = await firstValueFrom(
          this.httpService.get(url, {
            params: {
              appid: process.env.MINIPROGRAM_APPID,
              secret: process.env.MINIPROGRAM_APPSECRET,
              js_code: code,
              grant_type: 'authorization_code'
            }
          })
        );

        return response.data;
      } catch (error) {
        throw new Error('小程序登录失败');
      }
    }

    // 解密用户敏感信息
    async decryptUserInfo(sessionKey: string, encryptedData: string, iv: string) {
      // 实现微信小程序用户信息解密逻辑
      // 通常需要使用第三方加密库如 crypto-js
    }
  }

微信网页授权(OAuth 2.0)

网页授权是通过微信官方提供的OAuth2.0认证方式,使第三方网站或应用能够获取用户基本信息,实现用户身份识别。

image

image

授权流程

  1. 发起授权
    • 用户点击登录/授权按钮
    • 生成授权链接
    • 跳转至微信授权页面
  2. 用户确认
    • 用户选择是否授权
    • 确认后获取临时授权码
      code
  3. 换取 Access Token
    • 服务端使用
      code
      换取
      access_token
    • 获取用户的
      openid

      access_token
  4. 获取用户信息
    • 使用
      access_token

      openid
    • 调用微信接口获取用户详细信息
  5. 系统内部处理
    • 创建或更新用户信息
    • 生成系统内部登录态
import { Injectable } from '@nestjs/common';
import { HttpService } from '@nestjs/axios';
import { firstValueFrom } from 'rxjs';

// 用户授权信息接口定义
interface WechatUserInfo {
  openid: string;      // 用户唯一标识
  nickname: string;    // 用户昵称
  sex: number;         // 用户性别
  province: string;    // 省份
  city: string;        // 城市
  country: string;     // 国家
  headimgurl: string;  // 头像地址
  privilege: string[]; // 用户特权信息
  unionid?: string;    // 开放平台唯一标识
}

@Injectable()
export class WebAuthService {
  constructor(private readonly httpService: HttpService) {}

  // 生成授权链接
  generateAuthUrl(redirectUri: string, scope: 'snsapi_base' | 'snsapi_userinfo' = 'snsapi_userinfo') {
    const baseUrl = 'https://open.weixin.qq.com/connect/oauth2/authorize';
    const params = new URLSearchParams({
      appid: process.env.WECHAT_APPID,
      redirect_uri: redirectUri,
      response_type: 'code',
      scope: scope,
      state: 'STATE#wechat_redirect'  // 自定义参数,用于回传
    });
    
    return `${baseUrl}?${params}#wechat_redirect`;
  }

  // 获取 Access Token
  async getAccessToken(code: string) {
    const url = 'https://api.weixin.qq.com/sns/oauth2/access_token';
    
    try {
      const response = await firstValueFrom(
        this.httpService.get(url, {
          params: {
            appid: process.env.WECHAT_APPID,
            secret: process.env.WECHAT_APPSECRET,
            code: code,
            grant_type: 'authorization_code'
          }
        })
      );

      return response.data;
    } catch (error) {
      throw new Error('获取 Access Token 失败');
    }
  }

  // 获取用户信息
  async getUserInfo(accessToken: string, openid: string): Promise<WechatUserInfo> {
    const url = 'https://api.weixin.qq.com/sns/userinfo';
    
    try {
      const response = await firstValueFrom(
        this.httpService.get(url, {
          params: {
            access_token: accessToken,
            openid: openid,
            lang: 'zh_CN'
          }
        })
      );

      return response.data;
    } catch (error) {
      throw new Error('获取用户信息失败');
    }
  }
}

微信开放平台授权

image

特点:

  • 适用于第三方应用
  • 支持移动应用、网站应用等
  • 需要开发者资质认证
import { Injectable } from '@nestjs/common';
import { HttpService } from '@nestjs/axios';
import { firstValueFrom } from 'rxjs';

// 定义开放平台授权响应接口
interface OpenPlatformAuthResponse {
  access_token: string;    // 接口调用凭证
  expires_in: number;      // access_token 过期时间
  refresh_token: string;   // 刷新 token
  openid: string;          // 授权用户唯一标识
  scope: string;           // 用户授权的作用域
  unionid: string;         // 开放平台唯一标识
}

@Injectable()
export class OpenPlatformAuthService {
  constructor(private readonly httpService: HttpService) {}

  /**
   * 获取 access_token
   * @param code 授权码
   * @returns 授权响应信息
   */
  async getAccessToken(code: string): Promise<OpenPlatformAuthResponse> {
    // 微信获取 access_token 的接口地址
    const url = 'https://api.weixin.qq.com/sns/oauth2/access_token';
    
    try {
      // 使用授权码换取 access_token
      const response = await firstValueFrom(
        this.httpService.get(url, {
          params: {
            // 从环境变量读取开放平台 AppID
            appid: process.env.OPEN_PLATFORM_APPID,
            // 从环境变量读取开放平台密钥
            secret: process.env.OPEN_PLATFORM_APPSECRET,
            // 授权码
            code: code,
            // 授权类型,固定值
            grant_type: 'authorization_code'
          }
        })
      );

      return response.data;
    } catch (error) {
      // 捕获并抛出授权失败的错误
      throw new Error('开放平台授权失败');
    }
  }

  /**
   * 刷新 access_token
   * @param refreshToken 刷新 token
   * @returns 新的授权信息
   */
  async refreshAccessToken(refreshToken: string) {
    // 微信刷新 access_token 的接口地址
    const url = 'https://api.weixin.qq.com/sns/oauth2/refresh_token';
    
    try {
      // 使用 refresh_token 换取新的 access_token
      const response = await firstValueFrom(
        this.httpService.get(url, {
          params: {
            // 开放平台 AppID
            appid: process.env.OPEN_PLATFORM_APPID,
            // 授权类型,固定值
            grant_type: 'refresh_token',
            // 用于刷新的 token
            refresh_token: refreshToken
          }
        })
      );

      return response.data;
    } catch (error) {
      // 捕获并抛出刷新 Token 失败的错误
      throw new Error('刷新 Token 失败');
    }
  }
}

企业微信授权

企业微信授权是针对企业内部应用和员工的身份认证机制,提供更严格和精细的权限控制。

image

image

特点:

  • 主要面向企业内部应用
  • 更强的权限控制
  • 安全性更高

授权流程

  1. 发起授权
    • 员工访问企业内部应用
    • 触发登录机制(扫码/输入)
    • 生成企业微信授权链接
  2. 身份验证
    • 跳转企业微信登录页
    • 员工确认身份
    • 获取临时授权码
  3. 换取用户信息
    • 服务端使用
      code
      换取用户标识
    • 获取
      userid
    • 调用接口获取用户详细信息
  4. 系统内部处理
    • 验证员工身份
    • 检查权限状态
    • 生成系统内部登录态
import { Injectable } from '@nestjs/common';
import { HttpService } from '@nestjs/axios';
import { firstValueFrom } from 'rxjs';

// 定义企业微信授权响应接口
interface EnterpriseWechatAuthResponse {
  access_token: string;    // 企业接口调用凭证
  expires_in: number;      // access_token 过期时间
  user_ticket?: string;    // 用户票据(可选)
  user_info?: {
    userid: string;        // 企业成员 ID
    name: string;          // 成员名称
    department: number[];  // 部门 ID 列表
  };
}

@Injectable()
export class EnterpriseWechatAuthService {
  constructor(private readonly httpService: HttpService) {}

  /**
   * 获取企业微信用户信息
   * @param code 临时授权码
   * @returns 用户信息和 access_token
   */
  async getUserInfo(code: string): Promise<EnterpriseWechatAuthResponse> {
    // 获取企业 access_token 的接口地址
    const tokenUrl = 'https://qyapi.weixin.qq.com/cgi-bin/gettoken';
    // 获取用户信息的接口地址
    const userInfoUrl = 'https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo';

    // 第一步:获取企业 access_token
    // 需要使用企业 ID 和应用的秘钥
    const tokenResponse = await firstValueFrom(
      this.httpService.get(tokenUrl, {
        params: {
          // 从环境变量读取企业 ID
          corpid: process.env.ENTERPRISE_CORPID,
          // 从环境变量读取企业应用秘钥
          corpsecret: process.env.ENTERPRISE_CORPSECRET
        }
      })
    );

    // 从响应中提取 access_token
    const accessToken = tokenResponse.data.access_token;

    // 第二步:使用 access_token 和临时授权码获取用户信息
    const userInfoResponse = await firstValueFrom(
      this.httpService.get(userInfoUrl, {
        params: {
          // 企业 access_token
          access_token: accessToken,
          // 临时授权码
          code: code
        }
      })
    );

    return userInfoResponse.data;
  }
}

各个平台授权小结

授权类型 个人是否可用 是否收费 主要适用场景
网页授权 免费 网站、H5应用
小程序授权 免费 小程序登录、开放平台
公众号授权 部分可用 免费+增值服务 公众号相关应用(服务号、订阅号)
企业微信 有费用 企业内部协作

image