搜索
您的当前位置:首页正文

mybatis入门基础(三)----SqlMapConfig.xml全局配置文件解析

2023-11-09 来源:哗拓教育
  • settings(全局配置参数)
  • typeAiases(类型别名)
  • typeHandlers(类型处理器)
  • objectFactory(对象工厂)
  • plugins(插件)
  • environments(环境集合属性对象)mappers(映射器)
    1. environment(环境子属性对象)
    2. transactionManager(事物管理)
    3. datesource(数据源
  • mappers(映射器)
  • 二:properties属性

    作用:将数据连接单独配置在db.properties中,只需要在SqlMapConfig.xml中加载db.properties的属性值,在SqlMapConfig.xml中就不需要对数据库连接参数进行硬编码。数据库连接参数只配置在db.properties中,方便对参数进行统一管理,其它xml可以引用该db.properties。

    db.properties的内容:

    jdbc.driver=com.mysql.jdbc.Driverjdbc.url=jdbc:mysql://localhost:3306/mybatisjdbc.username=rootjdbc.password=root

    在SqlMapConfig.xml中加载db.properties

     <!-- 加载数据库文件db.properties --><properties resource="db.properties"> <!-- properties中还可以配置一些属性名和属性值,此处的优先加载 --> <!-- <property name="driver" value=""/> --> </properties> <!-- 和spring整合后 environments配置将废除--> <environments default="development"> <environment id="development"> <!-- 使用jdbc事务管理,事务控制由mybatis管理--> <transactionManager type="JDBC" /> <!-- 数据库连接池,由mybatis管理--> <dataSource type="POOLED"> <property name="driver" value="${jdbc.driver}" /> <property name="url" value="${jdbc.url}" /> <property name="username" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> </dataSource> </environment> </environments>

    properties特性:

    注意:

  • 在properties元素体内定义的属性优先读取。
  • 然后读取properties元素中resource或url加载的属性,它会覆盖已读取的同名属性。
  • 最后读取parameterType传递的属性,它会覆盖已读取的同名属性
  • 建议:

    不要在properties元素体内添加任何属性值,只将属性值定义在properties文件中。

    在properties文件中定义属性名要有一定的特殊性,如xxxx.xxxx(jdbc.driver)

    三:settings全局参数配置

    mybatis框架运行时可以调整一些运行参数。比如,开启二级缓存,开启延迟加载等等。全局参数会影响mybatis的运行行为。

    mybatis-settings的配置属性以及描述

    setting(设置)Description(描述)valid Values(验证值组)Default(默认值)
    cacheEnabled在全局范围内启用或禁用缓存配置 任何映射器在此配置下。true | falseTRUE
    lazyLoadingEnabled在全局范围内启用或禁用延迟加载。禁用时,所有相关联的将热加载。true | falseTRUE
    aggressiveLazyLoading启用时,有延迟加载属性的对象将被完全加载后调用懒惰的任何属性。否则,每一个属性是按需加载。true | falseTRUE
    multipleResultSetsEnabled允许或不允许从一个单独的语句(需要兼容的驱动程序)要返回多个结果集。true | falseTRUE
    useColumnLabel使用列标签,而不是列名。在这方面,不同的驱动有不同的行为。参考驱动文档或测试两种方法来决定你的驱动程序的行为如何。true | falseTRUE
    useGeneratedKeys允许JDBC支持生成的密钥。兼容的驱动程序是必需的。此设置强制生成的键被使用,如果设置为true,一些驱动会不兼容性,但仍然可以工作。true | falseFALSE
    autoMappingBehavior指定MyBatis的应如何自动映射列到字段/属性。NONE自动映射。 PARTIAL只会自动映射结果没有嵌套结果映射定义里面。 FULL会自动映射的结果映射任何复杂的(包含嵌套或其他)。

    NONE,PARTIAL,FULL

    PARTIAL
    defaultExecutorType配置默认执行人。SIMPLE执行人确实没有什么特别的。 REUSE执行器重用准备好的语句。 BATCH执行器重用语句和批处理更新。

    SIMPLE,REUSE,BATCH

    SIMPLE
    safeRowBoundsEnabled允许使用嵌套的语句RowBounds。true | falseFALSE
    mapUnderscoreToCamelCase从经典的数据库列名A_COLUMN启用自动映射到骆驼标识的经典的Java属性名aColumn。true | falseFALSE
    localCacheScopeMyBatis的使用本地缓存,以防止循环引用,并加快反复嵌套查询。默认情况下(SESSION)会话期间执行的所有查询缓存。如果localCacheScope=STATMENT本地会话将被用于语句的执行,只是没有将数据共享之间的两个不同的调用相同的SqlSession。

    SESSION

    STATEMENT

    SESSION
    dbcTypeForNull指定为空值时,没有特定的JDBC类型的参数的JDBC类型。有些驱动需要指定列的JDBC类型,但其他像NULL,VARCHAR或OTHER的工作与通用值。JdbcType enumeration. Most common are: NULL, VARCHAR and OTHEROTHER
    lazyLoadTriggerMethods指定触发延迟加载的对象的方法。A method name list separated by commasequals,clone,hashCode,toString
    defaultScriptingLanguage指定所使用的语言默认为动态SQL生成。A type alias or fully qualified class name.

    org.apache.ibatis.scripting.xmltags

    .XMLDynamicLanguageDriver

    callSettersOnNulls指定如果setter方法??或地图的put方法时,将调用检索到的值是null。它是有用的,当你依靠Map.keySet()或null初始化。注意原语(如整型,布尔等)不会被设置为null。true | falseFALSE
    logPrefix指定的前缀字串,MyBatis将会增加记录器的名称。Any StringNot set
    logImpl指定MyBatis的日志实现使用。如果此设置是不存在的记录的实施将自动查找。SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGINGNot set
    proxyFactory指定代理工具,MyBatis将会使用创建懒加载能力的对象。CGLIB | JAVASSIST CGLIB

    官方文档settings的例子

    技术分享
    <setting name="cacheEnabled" value="true"/> <setting name="lazyLoadingEnabled" value="true"/> <setting name="multipleResultSetsEnabled" value="true"/> <setting name="useColumnLabel" value="true"/> <setting name="useGeneratedKeys" value="false"/> <setting name="autoMappingBehavior" value="PARTIAL"/> <setting name="defaultExecutorType" value="SIMPLE"/> <setting name="defaultStatementTimeout" value="25"/> <setting name="safeRowBoundsEnabled" value="false"/> <setting name="mapUnderscoreToCamelCase" value="false"/> <setting name="localCacheScope" value="SESSION"/> <setting name="jdbcTypeForNull" value="OTHER"/> <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/></settings>
    View Code四:typeAiases(别名)--重点掌握

    在mapper.xml中,定义很多的statement,statement需要parameterType指定输入参数的类型、需要resultType指定输出结果的映射类型。

         如果在指定类型时输入类型全路径,不方便进行开发,可以针对parameterType或resultType指定的类型定义一些别名,在mapper.xml中通过别名定义,方便开发。

    4.1.mybatis默认支持的别名

    别名

    映射的类型

    _byte 

    byte 

    _long 

    long 

    _short 

    short 

    _int 

    int 

    _integer 

    int 

    _double 

    double 

    _float 

    float 

    _boolean 

    boolean 

    string 

    String 

    byte 

    Byte 

    long 

    Long 

    short 

    Short 

    int 

    Integer 

    integer 

    Integer 

    double 

    Double 

    float 

    Float 

    boolean 

    Boolean 

    date 

    Date 

    decimal 

    BigDecimal 

    bigdecimal 

    BigDecimal 

     

    4.2.自定义别名

    4.2.1:单个别名定义(在SqlMapConfig.xml)  

    <!-- 别名定义:针对单个别名定义 type:类型的路径; alias:别名 --> <typeAliases><typeAlias type="com.mybatis.entity.User" alias="user"/></typeAliases>

    UserMapper.xml引用别名:

    <select id="findUserById" parameterType="int" resultType="user" >   select * from t_user where id=#{id}</select>

    4.2.2:批量定义别名(常用) 

    <!-- 批量别名的定义: package:指定包名,mybatis会自动扫描包中的pojo类,自动定义别名,别名就是类名(首字母大写或小写都可以) --> <typeAliases> <package name="com.mybatis.entity"/> <package name="其它包"/> </typeAliases>
    五:typeHandlers(类型处理器)

    mybatis中通过typeHandlers完成jdbc类型和java类型的转换。

    通常情况下,mybatis提供的类型处理器满足日常需要,不需要自定义.

    mybatis支持类型处理器:

    类型处理器

    Java类型

    JDBC类型

    BooleanTypeHandler 

    Boolean,boolean 

    任何兼容的布尔值

    ByteTypeHandler 

    Byte,byte 

    任何兼容的数字或字节类型

    ShortTypeHandler 

    Short,short 

    任何兼容的数字或短整型

    IntegerTypeHandler 

    Integer,int 

    任何兼容的数字和整型

    LongTypeHandler 

    Long,long 

    任何兼容的数字或长整型

    FloatTypeHandler 

    Float,float 

    任何兼容的数字或单精度浮点型

    DoubleTypeHandler 

    Double,double 

    任何兼容的数字或双精度浮点型

    BigDecimalTypeHandler 

    BigDecimal 

    任何兼容的数字或十进制小数类型

    StringTypeHandler 

    String 

    CHAR和VARCHAR类型

    ClobTypeHandler 

    String 

    CLOB和LONGVARCHAR类型

    NStringTypeHandler 

    String 

    NVARCHAR和NCHAR类型

    NClobTypeHandler 

    String 

    NCLOB类型

    ByteArrayTypeHandler 

    byte[] 

    任何兼容的字节流类型

    BlobTypeHandler 

    byte[] 

    BLOB和LONGVARBINARY类型

    DateTypeHandler 

    Date(java.util)

    TIMESTAMP类型

    DateOnlyTypeHandler 

    Date(java.util)

    DATE类型

    TimeOnlyTypeHandler 

    Date(java.util)

    TIME类型

    SqlTimestampTypeHandler 

    Timestamp(java.sql)

    TIMESTAMP类型

    SqlDateTypeHandler 

    Date(java.sql)

    DATE类型

    SqlTimeTypeHandler 

    Time(java.sql)

    TIME类型

    ObjectTypeHandler 

    任意

    其他或未指定类型

    EnumTypeHandler 

    Enumeration类型

    VARCHAR-任何兼容的字符串类型,作为代码存储(而不是索引)。

    六:mappers(映射配置)

    6.1:通过resource加载单个映射文件

    <!-- 加载映射文件 --> <mappers> <!--通过resource方法一次加载一个映射文件 --> <mapper resource="sqlmap/User.xml"/> <mapper resource="mapper/UserMapper.xml"/> </mappers>

    6.2:通过mapper接口加载单个映射文件

     <!-- 通过mapper接口加载单个映射配置文件 遵循一定的规范:需要将mapper接口类名和mapper.xml映射文件名称保持一致,且在一个目录中; 上边规范的前提是:使用的是mapper代理方法; --> <mapper class="com.mybatis.mapper.UserMapper"/> 

    按照上边的规范,将mapper.java和mapper.xml放在一个目录 ,且同名。

    技术分享

    6.3:批量加载mapper(推荐使用)

     <!-- 批量加载映射配置文件,mybatis自动扫描包下面的mapper接口进行加载 遵循一定的规范:需要将mapper接口类名和mapper.xml映射文件名称保持一致,且在一个目录中; 上边规范的前提是:使用的是mapper代理方法; --> <package name="com.mybatis.mapper"/> 

     

    mybatis入门基础(三)----SqlMapConfig.xml全局配置文件解析

    标签:

    小编还为您整理了以下内容,可能对您也有帮助:

    Mybatis的基础和高级查询应用实践

    将分为3部分介绍:

    #java# #数据库# #开发# #后端#

    快速入门查看官网 :http://www.mybatis.org/mybatis-3

    1.添加Mybatis的坐标(添加pom.xml依赖)

    2.创建数据库表 + 编写实体

    表设计和实体根据自己业务实际编写即可

    3.编写映射文件Mapper.xml与标签说明

    4.编写核心文件SqlMapConfig.xml

    5.使用

    : 数据库环境的配置,支持多种环境配置

    : 环境变量

    : 事务管理器,有两种类型:

    : 数据源,有三种类型:

    : 属性

    : 属性该标签可以加载额外配置的properties文件,如resource文件夹下的jdbc.properties。使用方式为:${jdbc.driver} 可以引用对应的值

    : 映射器,作用是加载sql的映射,有四种方式

    从用户角度出发,查询一对多结果;

    用户和角色的关系,就是多对多查询;

    代替了标签,是多表查询的关键,在注解中用来指定子查询返回单一对象。有以下属性:

    代替了标签,是多表查询的关键,再注解中用来指定子查询返回的对象集合。有以下属性:

    以上是对mybatis的基础使用和高级用法,实际项目中,可以结合情况,使用xml查询或者使用两者结合查询都是可行的。

    快捷访问:Mybatis一级缓存和二级缓存

    MyBatis解析

    从命名上可以看出,这个是一个 Builder 模式的,用于创建 SqlSessionFactory 的类。SqlSessionFactoryBuilder 根据配置来构造 SqlSessionFactory。其中配置方式有两种:

    mybatis-config.xml 就是我们的配置文件:

    Java Config 相比较 XML 文件的方式而言,会有一些。比如修改了配置文件需要重新编译,注解方式没有 XML 配置项多等。所以,业界大多数情况下是选择 XML 文件的方式。但到底选择哪种方式,这个要取决与自己团队的需要。比如,项目的 SQL 语句不复杂,也不需要一些高级的 SQL 特性,那么 Java Config 则会更加简洁一点;反之,则可以选择 XML 文件的方式。

    创建配置文件解析器XMLConfigBuilder

    解析mybatis-config.xml里的配置为Configuration对象,Mybatis的全局配置对象。

    XMLConfigBuilder#parseConfiguration解析mapper下的xml

    XMLMapperBuilder#bindMapperForNamespace,根据xml里的 namespace 反射出 mapper接口 的 class,如果有mapper接口,则把该mapper接口的class添加到Configuration的mapperRegistry里。

    如果该接口已经注册,则抛出已经绑定的异常。

    为该接口注册MapperProxyFactory,但这里只是注册其创建MapperProxy的工厂,并不是创建MapperProxy。

    如果Mapper对应的xml资源未加载,触发xml的绑定操作,将xml中的sql语句与Mapper建立关系。

    addMapper方法,只是为**Mapper创建对应对应的MapperProxyFactory。

    根据Mapper接口与SqlSession创建MapperProxy对象。

    根据接口类获取MapperProxyFactory。

    调用MapperProxyFactory的newInstance创建MapperProxy对象。

    SqlSessionFactory 顾名思义,是用于生产 SqlSession 的工厂。 通过如下的方式来获取 SqlSession 实例:

    SqlSession 包含了执行 SQL 的所有的方法。以下是示例:

    当然,下面的方式可以做到类型安全:

    MapperProxy是MapperProxyFactory使用SqlSession创建出来的。所以MapperProxy中包含SqlSession。

    可以看到MapperProxy调用invoke方法,进而调用MapperMethod的execute(),这些MapperMethod就是和你要执行的命令相关,比如执行select语句,则会通过SqlSession的select()方法,最终调用到Executor的query方法。Executor会再协调另外三个核心组件。

    MapperProxy:

    MapperMethod:

    插件的构建:

    谈原理首先要知道StatementHandler,ParameterHandler,Result Handler都是代理,他们是Configuration创建,在创建过程中会调用interceptorChain.pluginAll()方法,为四大组件组装插件(再底层是通过Plugin.wrap(target,XX, new Plugin( interceptor))来来创建的)。

    插件链是何时构建的:

    在执行SqlSession的query或者update方法时,SqlSession会通过Configuration创建Executor代理,在创建过程中就调用interceptor的pluginAll方法组装插件。然后executor在调用doQuery()方法的时候,也会调用Configuration的newStatementHandler方法创建StatemenHandler(和上面描述的一样,这个handler就是个代理,也是通过interceptorChain的pluginAll方法构建插件)

    插件如何执行:

    以statementhandler的prepare方法的插件为例,正如前面所说,statementhandler是一个proxy,执行他的prepare方法,将调用invokeHandler的invoke方法,而invokeHandler就是Plugin.wrap(target, xxx, new Plugin(interceptor))中的第三个参数,所以很自然invokeHanlder的invoke的方法最终就会调用interceptor对象的intercept方法。

    Mybatis的插件配置在configuration内部,初始化时,会读取这些插件,保存于Configuration对象的InterceptorChain中。

    org.apache.ibatis.plugin.InterceptorChain.java源码。

    上面的for循环代表了只要是插件,都会以责任链的方式逐一执行,所谓插件,其实就类似于。

    插件的编写

    插件必须实现org.apache.ibatis.plugin.Interceptor接口。

    -intercept()方法:执行拦截内容的地方,拦截目标对象的目标方法的执行

    -plugin()方法:决定是否触发intercept()方法。 作用:包装目标对象,包装就是为目标对象创建一个代理对象

    -setProperties()方法:给自定义的传递xml配置的属性参数。将插件注册时的property属性设置进来

    下面自定义一个:

    为什么要写Annotation注解?注解都是什么含义?

    Mybatis规定插件必须编写Annotation注解,是必须,而不是可选。@Intercepts注解:装载一个@Signature列表,一个@Signature其实就是一个需要拦截的方法封装。那么,一个要拦截多个方法,自然就是一个@Signature列表。

    type = Executor.class, method = "query", args = { MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class }

    解释:要拦截Executor接口内的query()方法,参数类型为args列表。

    Plugin.wrap(target, this)是干什么的?

    使用JDK的动态代理,给target对象创建一个delegate代理对象,以此来实现方法拦截和增强功能,它会回调intercept()方法。

    Mybatis可以拦截哪些接口对象?

    Mybatis只能拦截ParameterHandler、ResultSetHandler、StatementHandler、Executor共4个接口对象内的方法。

    重新审视interceptorChain.pluginAll()方法:该方法在创建上述4个接口对象时调用,其含义为给这些接口对象注册功能,注意是注册,而不是执行拦截。

    执行时机:plugin()方法注册后,那么,在执行上述4个接口对象内的具体方法时,就会自动触发的执行,也就是插件的执行。

    Invocation

    可以通过invocation来获取拦截的目标方法,以及执行目标方法。

    分页插件原理

    由于Mybatis采用的是逻辑分页,而非物理分页,那么,市场上就出现了可以实现物理分页的Mybatis的分页插件。 要实现物理分页,就需要对String sql进行拦截并增强,Mybatis通过BoundSql对象存储String sql,而BoundSql则由StatementHandler对象获取。

    因此,就需要编写一个针对StatementHandler的query方法,然后获取到sql,对sql进行重写增强。

    mybatismap怎么根据缓存查询mybatis的缓存

    如何使用mybatisL2缓存?

    了解更多关于MyBatisL2缓存的信息

    首先,创建缓存的整个过程

    我们从SqlSessionFactoryBuilder解析mybatis-config.xml配置文件开始:

    readerreader=resources.getresourceasreader(mybatis-config.XML);

    SqlSessionFactorySqlSessionFactory=newSqlSessionFactorybuilder()。构建(阅读器);

    然后:

    XMLConfigBuilderparser=newXMLConfigBuilder(inputStream,environment,properties);

    返回build(parser.parse());

    看看parser.parse()方法:

    parseconfiguration(parser.evalnode(/配置

    查看处理Mapper.xml文件的位置:

    mapperelement(root.evalnode(地图绘制者

    看看处理Mapper.xml的XMLMapperBuilder:

    XMLmapperbuildermapperparser=newXMLmapperbuilder(inputStream,configuration,

    resource,configuration.getsqlfragments());

    mapperparser.parse();

    继续看看解析方法:

    configurationelement(parser.evalnode/mapper));

    转到此处:

    stringnamespace=context.getstringattribute(命名空间

    if(namespace.equals(映射器的命名空间不能为空);

    }

    builderassistant.setcurrentnamespace(命名空间);

    cacheRefElement(context.evalnode(cache-ref));

    cacheElement(context.evalnode(缓存

    看这里,名称空间是

    看看我的巴蒂斯如何处理

    私有voidcacheElement(XNode上下文)引发异常{

    如果(上下文!=null){

    stringtype=context.getstringattribute(键入

    Class

    stringeviction=context.getstringattribute(驱逐);

    Class

    longflushinterval=context.getlongattribute(flushInterval

    integersize=context.getintattribute(尺寸

    布尔读写=!context.getbooleanattribute(只读,假);

    booleanblocking=context.getbooleanattribute(阻止

    propertiesprops=context.getchildrenaspproperties();

    builderasistant.usenewcache(typeclass,evictionClass,

    flushInterval、size、readWrite、blocking、props);

    }

    }

    从源代码中可以看到MyBatis已经读取了那些属性,很容易就可以得到这些属性的默认值。

    Java的cache对象方法是builderAssistant.useNewCache我们来看看这段代码:

    公共缓存useNewCache(Class马鞭延伸缓存

    evictionclass=valueOrDefault(evictionclass,lrucache.class);

    Cachecache=新的CacheBuilder(当前命名空间)

    .实现(类型类)

    .addDecorator(evictionClass)

    .清除时间间隔

    .尺寸(大小)

    .读写(读写)

    .阻断(阻断)

    .属性(道具)

    .build();

    configuration.addCache(缓存);

    currentCache=cache

    返回缓存;

    }

    从调用这个方法的地方我们可以看到,并没有使用返回值缓存,而是在后面的过程中创建MappedStatement时使用了currentCache。

    第二,使用缓存过程

    在系统中,使用缓存的地方是在CachingExecutor中:

    @覆盖

    公共

    获取缓存后,先确定是否有二级缓存。

    仅通过,

    然后根据xml配置的属性useCache判断是否使用缓存(resultHandler常用的默认值很少为null)。

    if(ms.isusecache()

    如果没有问题,将根据键从缓存中获取值:

    @suppresswarnings(未选中E

    如果没有缓存,将执行查询,并将查询结果放入缓存中。

    if(list==null){

    列表=委托。

    tcm.putObject(缓存、键、列表);//问题#578和#116

    }

    返回结果

    退货单;

    }

    }

    没有缓存时,直接执行查询。

    返回委托。

    }

    在上面的代码中,tcm.putObject(cache,key,list);这段代码缓存结果。但实际上,直到sqlsession关闭,MyBatis才以序列化的形式保存在Map(默认缓存配置)中。

    三、使用缓存时的注意事项

    1.缓存只能用于具有单表操作的表。

    只需要保证这个表在整个系统中只有一个表操作,所有和这个表相关的操作都必须在一个命名空间中。

    2.当您可以保证查询远远大于插入、更新和删除操作时,请使用缓存。

    这不用我说,大家应该都知道。记住,这需要在1的前提下保证!

    第四,避免使用L2缓存。

    这里可能有很多人不理解。L2缓存带来的好处远远小于隐患。

    缓存在命名空间中,不同命名空间中的操作互不影响。

    插入、更新和删除操作将清除空所在的名称空间中的所有缓存。

    通常在MyBatisGenerator生成的代码中,每个表都是的,每个表都有自己的命名空间。

    为什么避免使用L2缓存?

    符合【缓存使用注意事项

    mybatiscollection的三种用法?

    mybatis使用foreach中collection的三种用法

    foreach的主要用在构建in条件中,它可以在SQL语句中进行迭代一个集合。

    foreach元素的属性主要有item,index,collection,open,separator,close。

    item表示集合中每一个元素进行迭代时的别名,

    index指定一个名字,用于表示在迭代过程中,每次迭代到的位置,

    open表示该语句以什么开始,

    separator表示在每次进行迭代之间以什么符号作为分隔符,

    close表示以什么结束。

    在使用foreach的时候最关键的也是最容易出错的就是collection属性,该属性是必须指定的,但是在不同情况下,该属性的值是不一样的,主要有一下3种情况:

    1.如果传入的是单参数且参数类型是一个List的时候,collection属性值为list

    2.如果传入的是单参数且参数类型是一个array数组的时候,collection的属性值为array

    3.如果传入的参数是多个的时候,我们就需要把它们封装成一个Map了,当然单参数也可

    以封装成map,实际上如果你在传入参数的时候,在breast里面也是会把它封装成一个Map的,map的key就是参数名,所以这个时候collection属性值就是传入的List或array对象在自己封装的map里面的key下面分别来看看上述三种情况的示例代码

    mybatis怎么将一个表中符合查询条件的多条记录,返回为一个list+map的形式?

    xml中配置好pojo对象然后将properties和数据库字段匹配即可

    Top