一文解析 MyBatis Generator 的使用及配置

Deecyn發表於2020-04-06

MyBatis-Generator 是 MyBatis 提供的一個程式碼生成工具,可以幫助我們生成資料庫表對應的持久化物件(也稱作 Model、PO)、運算元據庫的介面(dao)、簡單 SQL 的 mapper(XML 形式或註解形式)。

MyBatis-Generator (常簡寫為 MBG 或 mbg)是一個獨立工具,你可以下載它的 jar 包來執行,也可以在 Ant 和 Maven 中執行。其官方網址為:mybatis.org/generator/

一、引入 MyBatis-Generator 及環境

對於這篇博文,我是在基於 SpringBoot 的 Maven 專案環境中配置、使用和講解的,使用的 IDE 是 IntelliJ IDEA。

既然需要使用 MyBatis-Generator,那麼在專案中就一定使用了 MyBatis 和某一種資料庫,並且這些依賴應該已經在 Maven 中配置好了。例如 pom 檔案中的配置:

<dependencies>
  <!-- 為了方便,不展示其它配置... -->
  <dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
  </dependency>
  <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>${mysql.version}</version>
  </dependency>
</dependencies>
複製程式碼

對於在 Maven 專案中引入 MyBatis-Generator,這裡介紹兩種方式,具體需要用哪種方式取決於你使用哪種方式來執行 MyBatis-Generator 工具:(對於如何執行 MyBatis-Generator 工具,參見本文第三節)

1. 方式一:在 Maven 中匯入依賴

這種方式適用於:用 Java 程式碼來執行 MyBatis-Generator 工具。在 pom 檔案中引入 mybatis-generator-core 依賴:

<dependencies>
  <!-- 為了方便,不展示其它配置... -->
  <dependency>
    <groupId>org.mybatis.generator</groupId>
    <artifactId>mybatis-generator-core</artifactId>
    <version>1.3.7</version>
  </dependency>
</dependencies>
複製程式碼

2. 方式二:在 Maven 中引入外掛

這種方式適用於:通過 Maven 專案的 Plugins 或 Maven 的命令列來執行 MyBatis-Generator 工具。在 pom 檔案中引入 mybatis-generator-maven-plugin 外掛:

<build>
    <!-- 為了方便,不展示其它配置... -->
	<plugins>
  	<plugin>
      <groupId>org.mybatis.generator</groupId>
      <artifactId>mybatis-generator-maven-plugin</artifactId>
      <version>1.3.7</version>
    </plugin>
  </plugins>
</build>
複製程式碼

二、MyBatis-Generator 配置檔案

MyBatis-Generator 需要一個 xml 配置檔案,來詳細配置生成程式碼的各種細節。例如,在專案的 resources 目錄下新建一個 mybatis-generator-config.xml 配置檔案:

mbg-config-目錄路徑

其內容如下:

<?xml version="1.0" encoding="UTF-8"?>
<!-- MyBatis-Generator 相關配置 -->
<!DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">

<!-- 所有的配置均在根元素 generatorConfiguration 下 -->
<generatorConfiguration>
  ...
  ...
</generatorConfiguration>
複製程式碼

在該檔案中,所有的配置均在根元素 generatorConfiguration 下。根元素 generatorConfiguration 有 3 個子元素可供配置,這 3 個子元素必須按照下面給出的次數和順序進行配置:(沒錯,MyBatis-Generator 對配置的順序也有嚴格的要求

  1. properties (0 or 1),可出現 0 次或 1 次。
  2. classPathEntry (0...N),可出現 0 次或多次。
  3. context (1...N),至少出現 1 次。

其中,properties 和 classPathEntry 元素用於引入外部配置或檔案;context 是核心元素,裡面包含有各種詳細配置。

1. 引入外部配置檔案

在配置 MyBatis-Generator 時,是可以引入外部配置或檔案的。

properties 元素

元素 properties 可以用於載入外部配置項或配置檔案,該元素有兩個屬性,均用來指定外部配置檔案的地址:

  • resource:使 MBG 從 classpath 開始查詢;一般可以使用相對於當前 xml 配置檔案的相對路徑。
  • url:採用 URL 的方式;例如可以使用 file 協議 file:///Users/deecyn/Files/mybatis-generator.properties 從計算機本地查詢,也可以使用 http 協議在網際網路上查詢,等等。

注意,這兩個屬性只能選擇一個使用。例如,引入下圖中的 mybatis-generator.properties 配置檔案:

mbg-properties
用於引入檔案的程式碼如下:

<!-- 引入外部配置檔案 -->
<properties resource="mybatis-generator.properties"/>
複製程式碼

之後在整個 xml 配置檔案中就可以通過 ${propertyKey} 的方式來引用配置項。

classPathEntry 元素

使用 classPathEntry 元素,可以在 MBG 工作的時候,載入額外需要的依賴包。其中,location 屬性指明需要載入的 jar/zip 包的全路徑。例如:

<!--  載入需要的額外的依賴包 -->
<classPathEntry location="/Users/deecyn/Files/db2java.zip"/>
複製程式碼

2. 配置 context 核心元素

在 generationConfiguration 的子元素中,context 是核心元素,用於配置生成一組物件的環境。元素 context 有 4 個屬性可供配置:

  • id,必填,上下文 id,用於在生成錯誤時提示;保證多個 context 的 id 不重複就行。
  • targetRuntime,選填項,這個配置會影響生成的 dao 和 mapper.xml 的內容。常見值為:
    1. MyBatis3,預設值,生成基於 MyBatis 3.x 以上版本的內容,包括很多類似 XxxByExample 的 dao 方法。
    2. MyBatis3Simple,類似 MyBatis3,只是不生成類似 XxxByExample 的 dao 方法,一般選擇不生成這些繁雜的方法
    3. 還有其它可配置的值,詳情見 官網
  • defaultModelType,選填項,用於指定生成物件的樣式。其值為:
    1. conditional,預設值,類似於 hierarchical。區別是,不會為只有一個欄位的資料庫表生成一個單獨的類。
    2. hierarchical,主鍵生成一個 XxxKey 物件(key class),Blob 等欄位單獨生成一個物件,其它簡單屬性在一個物件中(record class)。
    3. flat,所有欄位(主鍵、blob 等)全部生成在一個物件中。
  • introspectedColumnImpl,選填項,類全限定名,用於 擴充套件 MBG

示例配置如下:

<context id="MySqlContext" targetRuntime="MyBatis3" defaultModelType="flat">
  ...
</context>
複製程式碼

context 的子元素

元素 context 中,有多個子元素需要配置。同樣的,context 的子元素必須按照下面給出的次數和順序進行配置:

  1. property (0...N)
  2. plugin (0...N)
  3. commentGenerator (0 or 1)
  4. connectionFactoryjdbcConnection,二選一進行配置
  5. javaTypeResolver (0 or 1)
  6. javaModelGenerator(有且僅有 1 次)
  7. sqlMapGenerator (0 or 1)
  8. javaClientGenerator (0 or 1)
  9. table (1...N)

可以看出,javaModelGenerator、table 以及 connection 元素的配置是必需的。

property 元素

用於為程式碼生成指定屬性,或為其它元素指定屬性。可以配置零個或多個,常見的 property 配置如下:

<!-- 自動識別資料庫關鍵字,預設為 false,一般保留預設值,遇到資料庫關鍵字(Java關鍵字)時,按照 table 元素中 columnOverride 屬性的配置進行覆蓋;
  如果設定為 true, 則需按照 SqlReservedWords 中定義的關鍵字列表,對關鍵字進行定界(分隔);
  定界符(分隔符)參見 beginningDelimiter 和 endingDelimiter 的設定-->
<property name="autoDelimitKeywords" value="false"/>

<!-- beginningDelimiter 和 endingDelimiter,定界符(分隔符),指明用於標記資料庫關鍵字的符號,預設為為雙引號 (");
  在 oracle 中是雙引號 ("),在 MySQL 中需配置為反引號 (`)  -->
<property name="beginningDelimiter" value="`"/>
<property name="endingDelimiter" value="`"/>

<!-- 生成的 Java 檔案的編碼   -->
<property name="JavaFileEncoding" value="UTF-8"/>

<!-- 格式化 Java 程式碼 -->
<property name="javaFormatter" value="org.mybatis.generator.api.dom.DefaultJavaFormatter"/>
<!-- 格式化 XML 程式碼 -->
<property name="xmlFormatter" value="org.mybatis.generator.api.dom.DefaultXmlFormatter"/>
複製程式碼

注:SqlReservedWords 關鍵字列表

plugin 元素

配置外掛,可以有零個或多個,常見的 plugin 配置有:

<!-- 使生成的 Model 實現 Serializable 介面  -->
<plugin type="org.mybatis.generator.plugins.SerializablePlugin"/>
<!--  為生成的 Model 覆寫 toString() 方法 -->
<plugin type="org.mybatis.generator.plugins.ToStringPlugin"/>
<!--  為生成的 Model 覆寫 equals() 和 hashCode() 方法 -->
<plugin type="org.mybatis.generator.plugins.EqualsHashCodePlugin"/>
複製程式碼

commentGenerator 元素

可以配置 0 個或 1 個,用來配置生成的註釋,預設是生成註釋的,並且會在註釋中新增時間等資訊。如果想沿用預設的註釋配置的話,可以不用配置 commentGenerator 元素。否則,可以進行如下配置:

<commentGenerator>
  <!--  不生成所有註釋,預設為 false  -->
  <property name="suppressAllComments" value="true"/>

  <!--  生成的註釋中不包含時間資訊,預設為 false -->
  <property name="suppressDate" value="true"/>
  <!--  生成的註釋中,時間的顯示格式 -->
  <property name="dateFormat" value="yyyy/MM/dd"/>
  <!-- 是否新增資料庫表中欄位的註釋,預設為 false  -->
  <property name="addRemarkComments" value="true"/>
</commentGenerator>
複製程式碼

當然,你也可以 自定註釋生成器

jdbcConnection 元素

配置資料庫連線,具體如下:

<!--  配置資料庫連線  -->
<jdbcConnection driverClass="${jdbc.driverClass}"
                connectionURL="${jdbc.connectionURL}"
                userId="${jdbc.userId}"
                password="${jdbc.password}">

<!-- 若為 8.0 版本以上的 mysql-connector-java 驅動,需要設定 nullCatalogMeansCurrent = true -->
  <!-- <property name="nullCatalogMeansCurrent" value="true"/> -->
</jdbcConnection>
複製程式碼

其中,${propertyKey} 裡面是引用的外部配置檔案中的 propertyValue:

mbg-properties

你也可以寫死,那麼就不用在 <properties resource=""/> 中引入此檔案了。

這裡面值得注意的是 <property name="nullCatalogMeansCurrent" value="true"/> ,當 mysql-connector-java 驅動在 8.0 版本以上時,如果不配置這一項為 true,會不生成指定資料庫中表的 Mapper。具體原因可參考文章:MyBatis Generator踩坑與自救

javaTypeResolver 元素

可以配置 0 或 1 個,用來配置 JDBC 到 Java 中的型別轉換規則,如果不進行配置,則使用預設的轉換規則,預設使用 org.mybatis.generator.internal.types.JavaTypeResolverDefaultImpl

就算要配置,也只能配置 BigDecimal 和時間型別的轉換:

<javaTypeResolver>
  <!-- 是否強制使用 BigDecimal;
			預設為 false,把 JDBC 的 DECIMAL 和 NUMERIC 型別解析為 Integer;
			設定為 true 時,把 JDBC 的 DECIMAL 和 NUMERIC 型別解析為 java.math.BigDecimal
 		-->
  <property name="forceBigDecimals" value="true"/>

  <!-- 設定時間型別的轉換,
			預設 false,將所有 JDBC 的時間型別解析為 java.util.Date;
      設定為 true 時,將 JDBC 的時間型別按如下規則解析:
        DATE	    -> java.time.LocalDate
        TIME	    -> java.time.LocalTime
        TIMESTAMP      -> java.time.LocalDateTime
        TIME_WITH_TIMEZONE  	-> java.time.OffsetTime
        TIMESTAMP_WITH_TIMEZONE 	-> java.time.OffsetDateTime
     -->
  <property name="useJSR310Types" value="true"/>
</javaTypeResolver>
複製程式碼

javaModelGenerator 元素

Java 模型生成器,有且僅能配置一個,負責 key 類(見 context 元素的 defaultModelType 屬性)、Java Bean 實體類、查詢類的生成。

元素 javaModelGenerator 有兩個屬性:

  • targetPackage:生成的類要放的包,具體的包受子元素 enableSubPackages 影響;
  • targetProject:目標專案,指定一個已存在的目錄。(targetProject 的路徑配置,對於不同的 MBG 啟動方式會有一些區別,詳情見第三節:執行 MyBatis-Generator)

在 javaModelGenerator 元素中還可以配置多個 property 子元素,具體程式碼如下:

<!--  配置 Java 模型生成器 -->
<javaModelGenerator targetPackage="deecyn.shop_02.mbg.model" targetProject="src/main/java">
  <!-- 自動為每一個生成的類建立一個構造方法,構造方法包含了所有的 field,而不是使用 setter;
     預設值為 false -->
  <property name="constructorBased" value="false"/>

  <!-- 在 targetPackage 的基礎上,根據資料庫的 schema 再生成一層 package,
     最終生成的類放在這個package下;預設為false -->
  <property name="enableSubPackages" value="false"/>

  <!-- 是否建立一個不可變的類:如果為true,那麼 MBG 生成的類會沒有 setter 方法,
     採用建構函式的方式來接收和設定每個欄位的值,此時會忽略 constructorBased 屬性的設定;
     預設值為 false  -->
  <property name="immutable" value="false"/>

  <!-- 設定在 getter 方法中,是否對 String 型別的欄位呼叫 trim() 方法;預設為 false -->
  <property name="trimStrings" value="true"/>
</javaModelGenerator>
複製程式碼

sqlMapGenerator 元素

可以配置 0 或 1 個,生成 SQL Map 的 xml 檔案生成器。在 MyBatis3 之後,我們可以使用 mapper.xml 檔案 + Mapper 介面,或者只使用 Mapper 介面 + Annotation;所以,如果 javaClientGenerator 元素中配置了需要生成 xml 的話,這個元素就必須配置。

該元素有 targetPackage 和 targetProject 兩個屬性,原理與 javaModelGenerator 元素的相同,只不過這裡指的是 resource 目錄下存放 mapper.xml 檔案的路徑。具體程式碼如下:

<!-- SQL Map 的 xml 檔案生成器 -->
<sqlMapGenerator targetPackage="mbg-mapper" targetProject="src/main/resources">
  <!-- 同 javaModelGenerator 元素中的配置  -->
  <property name="enableSubPackages" value="false"/>
</sqlMapGenerator>
複製程式碼

javaClientGenerator 元素

可以配置 0 或 1 個,用於配置關於 Mapper 介面的生成,如果沒有配置該元素,那麼預設不會生成 Mapper 介面。

元素 javaClientGenerator 有 3 個屬性,其中 targetPackage 和 targetProject 屬性的配置與 javaModelGenerator 元素的原理相同,只不過這裡指的是 java 目錄下存放 Mapper 介面的路徑。關於 type 屬性,有 3 個可選值:

  • ANNOTATEDMAPPER,按照使用 Mapper 介面 + Annotation 的方式生成檔案,SQL 生成在對應的 Annotation 中,不會生成 xml 檔案。
  • MIXEDMAPPER,使用混合配置,會生成 Mapper 介面,並適當新增合適的 Annotation,也會有 SQL 生成在 XML 檔案中。
  • XMLMAPPER,會生成 Mapper 介面,介面完全依賴 XML 檔案。

注意,如果 context 元素的 defaultModelType 屬性設定為 MyBatis3Simple,那麼就只支援 ANNOTATEDMAPPER 和 XMLMAPPER 的方式。一般建議將 type 設定成 XMLMAPPER

<!--  關於 Mapper 介面的生成 -->
<javaClientGenerator type="XMLMAPPER" targetPackage="deecyn.shop_02.mbg.mapper"
                     targetProject="src/main/java">
  <!-- 同 javaModelGenerator 元素中的配置  -->
  <property name="enableSubPackages" value="false"/>
</javaClientGenerator>
複製程式碼

table 元素

一個 table 元素對應一張資料庫表,如果想同時為多張表生成程式碼,需要配置多個 table 元素;或者可以將 tableName 設定為 % 來為全部表生成程式碼。

元素 table 除開一個必須的屬性 tableName(資料庫表名稱)需要設定外,還有很多可選的屬性,部分屬性如下:

  1. schema,資料庫的 schema;
  2. catalog,資料庫的 catalog;
  3. domainObjectName:生成的 domain 類的名字,如果不設定,直接使用表名的駝峰命名作為 domain 類的名字;可以設定為 somepackage.domainName,那麼會自動把 domainName 類再放到 somepackage 包裡面;
  4. enableSelectByExample,預設 true,MyBatis3Simple 為 false,指定是否生成動態查詢語句;
  5. enableUpdateByPrimaryKey,預設 true,指定是否生成按照主鍵修改物件的語句(即 update);
  6. enableDeleteByExample,預設 true,MyBatis3Simple 為 false,指定是否生成動態刪除語句;
  7. enableCountByExample,預設 true,MyBatis3Simple 為 false,指定是否生成動態查詢總條數語句(用於分頁的總條數查詢);
  8. enableUpdateByExample,預設 true,MyBatis3Simple為false,指定是否生成動態修改語句(只修改物件中不為空的屬性);
  9. modelType,參考 context 元素的 defaultModelType,相當於對其進行覆蓋。

此外,table 元素中還可以配置多個 property 和 columnOverride 等子元素。示例程式碼如下:

<!-- 配置需要生成程式碼的資料庫表 -->
<table tableName="pms_brand" domainObjectName="PmsBrand"
       enableCountByExample="false" enableUpdateByExample="false"
       enableDeleteByExample="false" enableSelectByExample="false"
       selectByExampleQueryId="false">

  <!-- 指定是否只生成 domain 類,預設為 false;
     如果設定為 true,則只生成 domain 類,如果還配置了sqlMapGenerator,那麼
     在 mapper.xml 檔案中,只生成 resultMap 元素 -->
  <property name="modelOnly" value="false"/>

  <!-- 預設為 false;如果設定為 true,生成的 model 類會直接使用 column 本身的名字,而不會再使用駝峰命名方法。比如 CREATE_DATE,生成的屬性名字就是 CREATE_DATE,而不會是 createDate -->
  <property name="useActualColumnNames" value="false"/>

  <!-- 生成主鍵的方法,如果設定了該元素,MBG 會在生成的 <insert> 元素中生成一條正確的 <selectKey> 元素 -->
  <generatedKey column="id" sqlStatement="MySql" identity="true"/>

  <!-- 用來修改表中某個列的屬性,MBG 會根據修改後的配置來生成 domain 的屬性;
     column:要重新設定的列名;一個 table 元素中可以定義多個 columnOverride 元素哈 -->
  <columnOverride column="show_status">
    <!-- 使用 property 屬性來指定列要生成的屬性名稱 -->
    <property name="property" value="showStatus"/>

    <!-- javaType 用於指定生成的 domain 的屬性型別,使用型別的全限定名-->
    <property name="javaType" value="java.lang.Integer"/>

    <!-- jdbcType用於指定該列的JDBC型別
    <property name="jdbcType" value=""/>
    -->
  </columnOverride>
</table>
複製程式碼

三、執行 MyBatis-Generator

對於 MyBatis-Generator,不同的執行方式,對專案和檔案的配置會有一些區別,本文介紹兩種執行 MBG 的方式。

方式一:使用 Java 程式碼程式設計執行

通過這種方式執行 MBG,在本文的第一節,引入 MyBatis-Generator 時,需要按照方式一在 Maven 的 pom 檔案中引入依賴:

<dependencies>
  <!-- 為了方便,不展示其它配置... -->
  <dependency>
    <groupId>org.mybatis.generator</groupId>
    <artifactId>mybatis-generator-core</artifactId>
    <version>1.3.7</version>
  </dependency>
</dependencies>
複製程式碼

然後在專案中新建一個 Java 類,程式碼類似下面:

package deecyn.shop_02.mbg;

import org.mybatis.generator.api.MyBatisGenerator;
import org.mybatis.generator.config.Configuration;
import org.mybatis.generator.config.xml.ConfigurationParser;
import org.mybatis.generator.internal.DefaultShellCallback;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

public class Generator {
    public static void main(String[] args) throws Exception {
        // MBG 執行過程中的警告資訊
        List<String> warnings = new ArrayList<String>();
        // 當生成的程式碼重複時,覆蓋原始碼
        boolean overwrite = true;
        // 讀取我們的 MBG 配置檔案
        InputStream is = Generator.class.getResourceAsStream("/mybatis-generator-config.xml");
        ConfigurationParser cp = new ConfigurationParser(warnings);
        Configuration config = cp.parseConfiguration(is);
        is.close();

        DefaultShellCallback callback = new DefaultShellCallback(overwrite);
        //建立 MBG
        MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings);
        //執行生成程式碼
        myBatisGenerator.generate(null);
        //輸出警告資訊
        for (String warning : warnings) {
            System.out.println(warning);
        }
    }
}
複製程式碼

執行類中的 main() 方法即可在相應的目錄下生成對應的程式碼。

需要注意的是,當你的專案中有多個 Module 時,在配置 javaModelGenerator、sqlMapGenerator 和 javaClientGenerator 元素的 targetProject 屬性時,需要在前面加上當前的 Module 名稱。例如當前的 Module 名稱為 shop_02 時:

<javaModelGenerator targetPackage="deecyn.shop_02.mbg.model" targetProject="shop_02/src/main/java"/>
<sqlMapGenerator targetPackage="mbg-mapper" targetProject="shop_02/src/main/resources"/>
<javaClientGenerator type="XMLMAPPER" targetPackage="deecyn.shop_02.mbg.mapper"
                     targetProject="shop_02/src/main/java"/>
複製程式碼

否則會提示找不到對應的 java 和 resource 目錄。

方式二:通過 Maven 外掛執行

通過這種方式執行 MBG,在本文的第一節,引入 MyBatis-Generator 時,需要按照方式二在 Maven 的 pom 檔案中引入外掛,此外還需要進行一些配置:

<build>
    <!-- 為了方便,不展示其它配置... -->
  <plugin>
    <groupId>org.mybatis.generator</groupId>
    <artifactId>mybatis-generator-maven-plugin</artifactId>
    <version>1.3.7</version>

    <configuration>
      <!--  引入 MyBatis-Generator 的配置檔案 -->
      <configurationFile>./src/main/resources/generatorConfig.xml</configurationFile>
      <!--  允許 MBG 將構建訊息寫入日誌中  -->
      <verbose>true</verbose>
      <!--  再次執行 MBG 時,允許覆蓋已生成的檔案,但是不會覆蓋 xml 檔案  -->
      <overwrite>true</overwrite>
    </configuration>

    <dependencies>
      <!--  引入 mysql 的 JDBC 驅動,否則會報錯找不到驅動  -->
      <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.48</version>
      </dependency>
    </dependencies>
  </plugin>
</build>
複製程式碼

配置好後,雙擊 Maven --> Plugins 中的 MyBatis-Generator 執行:

mbg-run

即可在相應的目錄下生成對應的程式碼。

需要注意的是,此時,在配置 javaModelGenerator、sqlMapGenerator 和 javaClientGenerator 元素的 targetProject 屬性時,其路徑都是相對於當前 Project 或 Module 的,不需要加字首。例如當前的 Module 名稱為 shop_02 時:

<javaModelGenerator targetPackage="deecyn.shop_02.mbg.model" targetProject="src/main/java"/>
<sqlMapGenerator targetPackage="mbg-mapper" targetProject="src/main/resources"/>
<javaClientGenerator type="XMLMAPPER" targetPackage="deecyn.shop_02.mbg.mapper"
                     targetProject="src/main/java"/>
複製程式碼

否則會提示找不到對應的 java 和 resource 目錄。

四、完整配置檔案參考

1. 完整的 pom 配置檔案

參考連結:Notes: mybatis-generator-pom

2. 完整的 MyBatis-Generator 配置檔案

參考連結:Notes: mybatis-generator-config

五、參考


(完)如有問題,歡迎交流~

相關文章