MyBatis基礎-ORM、全局配置、mapper配置、使用

目錄

 

基礎概念

ORM

MyBatis

MyBatis功能架構

MyBatis框架架構

MyBatis 與 JDBC、Hibernate 的區別

基本術語

基本使用

安裝

基礎核心配置文件

核心映射文件Mapper

過程概述

1.在pom.xml中導入依賴並設置

2.配置主文件,並在開發過程中不斷添加typeAlias、mapper等

 

3.寫映射,即接口與xml映射文件

4.使用映射完成sql語句的執行


基礎概念

ORM

ORM(Object Relational Mapping),即對象關係映射,它完成面向對象的編程語言到關係數據庫的映射。ORM 工具的唯一作用是:把持久化對象的保存、修改、刪除等操作,轉換成對數據庫的操作。

ORM 基本映射關係:

  • 數據表映射類
  • 數據表的行映射對象(實例)
  • 數據表的列(字段)映射對象的屬性

MyBatis

MyBatis 是支持定製化 SQL、存儲過程以及高級映射的優秀的持久層框架。MyBatis 避免了幾乎所有的 JDBC 代碼和手動設置參數以及對結果集的檢索封裝。MyBatis 可以對配置和原生 Map 使用簡單的 XML 或註解,將接口和 Java 的 POJO(Plain Old Java Objects,普通的 Java 對象)映射成數據庫中的記錄。

MyBatis 的主要思想是將程序中的大量 SQL 語句抽取出來,配置在配置文件中,以實現 SQL 的靈活配置。

MyBatis 並不完全是一種 ORM 框架,它的設計思想和 ORM 相似,只是它允許直接編寫 SQL 語句,使得數據庫訪問更加靈活。因此,準確地說,MyBatis 提供了一種“半自動化”的 ORM 實現,是一種 "SQL Mapping" 框架。

MyBatis功能架構

分爲三層

API接口層:提供給外部使用的接口 API,開發人員通過這些本地 API 來操縱數據庫。接口層一接收到調用請求就會調用數據處理層來完成具體的數據處理。

數據處理層:負責具體的 SQL 查找、SQL 解析、SQL 執行和執行結果映射處理等。它主要的目的是根據調用的請求完成一次數據庫操作。

基礎支撐層:負責最基礎的功能支撐,包括連接管理、事務管理、配置加載和緩存處理,這些都是共用的東西,將他們抽取出來作爲最基礎的組件,爲上層的數據處理層提供最基礎的支撐。

MyBatis框架架構

加載配置:MyBatis 應用程序根據XML配置文件加載運行環境,創建 SqlSessionFactory,SqlSessionFactory可來源於xml配置文件或者註解,將 SQL 的配置信息加載成爲一個個 MappedStatement 對象(包括了傳入參數映射配置、執行的SQL語句、結果映射配置),存儲在內存中。

SQL 解析:當 API 接口層接收到調用請求時,會接收到傳入 SQL 的 ID 和傳入對象(可以是 Map、JavaBean 或者基本數據類型),Mybatis 會根據 SQL 的 ID 找到對應的 MappedStatement,然後根據傳入參數對象對 MappedStatement 進行解析,解析後可以得到最終要執行的 SQL 語句和參數。

SQL 執行:SqlSession 將最終得到的 SQL 和參數拿到數據庫進行執行,得到操作數據庫的結果。

結果映射:將操作數據庫的結果按照映射的配置進行轉換,可以轉換成 HashMap、JavaBean 或者基本數據類型,並將最終結果返回,用完之後關閉 SqlSession。

更具體的:

1.SqlSessionFactory

sql會話工廠,每個基於 MyBatis 的應用都是以一個 SqlSessionFactory 的實例爲核心的。SqlSessionFactory 是單個數據庫映射關係經過編譯後的內存映像(類似於JDBC中加載完驅動,創建連接)。SqlSessionFactory 的實例可以通過 SqlSessionFactoryBuilder 獲得。而 SqlSessionFactoryBuilder 則可以從 XML 配置文件或一個預先定製的 Configuration 的實例構建出 SqlSessionFactory 的實例。SqlSessionFactory 是創建 SqlSession 的工廠。

2.SqlSession

SqlSession 是執行持久化操作的對象,它完全包含了面向數據庫執行 SQL 命令所需的所有方法,可以通過 SqlSession 實例來直接執行已映射的 SQL 語句(類似於JDBC中通過連接獲取Statement/PreparedStatement,然後執行SQL)。在使用完 SqlSession 後我們應該使用 finally 塊來確保關閉它。

MyBatis 與 JDBC、Hibernate 的區別

        Mybatis也是基於JDBC的。Java與數據庫操作僅能通過JDBC完成。 Mybatis也要通過JDBC完成數據查詢、更新這些動作。Mybatis僅僅是在JDBC基礎上做了,OO化、封裝事務管理接口這些東西。 Mybatis 和 Hibernate 都屏蔽JDBC API 的底層方問細節,使我們不用於JDBC API打交道就可以訪問數據庫。但是,Hibernate是全自動的ORM映射工具,可以自動生成SQL語句,Mybatis需要在xml配置文件中寫SQL語句;因爲Hibernate是自動生成SQL語句的,在寫複雜查詢時,Hibernate實現比Mybatis複雜的多。

基本術語

表示層(視圖層)、業務層(業務邏輯層)、持久層(數據訪問層),注:嚴格來說持久層包括數據訪問層

表示層負責接收用戶請求、轉發請求、顯示數據等;業務層負責組織業務邏輯;持久層負責持久化業務對象。

這三個分層,每一層都有不同的模式,就是架構模式。表示層最常用的架構模式就是MVC

DAL:data access layout數據訪問層

DAO:data access object 數據訪問對象

基本使用

安裝

要使用 MyBatis, 只需在項目中將 mybatis-x.x.x.jar 文件導入即可。

如果使用 Maven 來構建項目,則需將下面的 dependency 代碼置於 pom.xml 文件中:

<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis</artifactId>
  <version>x.x.x</version>
</dependency>

(框架如何使用,可直接查看中文官方文檔:|------傳送門------|)

基礎核心配置文件

XML 配置文件中包含了對 MyBatis 系統的核心設置,包含獲取數據庫連接實例的數據源(DataSource)和決定事務作用域和控制方式的事務管理器(TransactionManager)。一般放在src/main/resources下。

先看一個實例:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
  <environments default="development">
    <environment id="development">
      <transactionManager type="JDBC"/>
      <dataSource type="POOLED">
        <property name="driver" value="${driver}"/>
        <property name="url" value="${url}"/>
        <property name="username" value="${username}"/>
        <property name="password" value="${password}"/>
      </dataSource>
    </environment>
  </environments>
  <mappers>
    <mapper resource="org/mybatis/example/BlogMapper.xml"/>
  </mappers>
</configuration>

MyBatis 配置文件的 configuration 標籤主要包括:

  • configuration 配置
    • properties 屬性
    • settings 設置
    • typeAliases 類型命名
    • typeHandlers 類型處理器
    • objectFactory 對象工廠
    • plugins 插件
    • environments 環境
      • environment 環境變量
      • transactionManager 事務管理器
    • databaseIdProvider 數據庫廠商標識
    • mappers 映射器

 properties 屬性

這個標籤就是用來進行聲明name/value的,相當於頭部設置變量,下面的配置可直接拿來用,避免冗餘。

使用下級標籤property聲明key/value,如:

<properties>
      <property name="username" value="root"/>
      <property name="password" value="123"/>
</properties>

也可以使用resource或url進行外部配置且動態替換,例如建立 Java 屬性文件 config.properties ,用於配置一些數據庫的信息,內容如下:

driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC
username=root
password=xxx

在配置文件中配置 <properties.../> 屬性中就可直接引入

<properties resource="config.properties"/>

然後在其他標籤裏就可以通過 properties 元素的子元素來傳遞值,如在這裏設置 username 和 password 的值:

<dataSource type="POOLED">
     <!--直接拿properties中的值過來用即可-->
      <property name="driver" value="${driver}"/>
      <property name="url" value="${url}"/>
      <property name="username" value="${username}"/>
      <property name="password" value="${password}"/>
</dataSource>

如果重名,則優先級是這樣的:通過方法參數傳遞的屬性具有最高優先級(即其他標籤內部自定義的property優先級最高),resource/url 屬性中指定的配置文件次之,最低優先級的是 properties 屬性中指定的屬性。

 settings 設置

該標籤內部設置MyBatis運行時的一些操作設置(如自動生成主鍵,設置超時時間等),如果不指定設置就使用默認值。

詳情參看官方文檔:http://www.mybatis.org/mybatis-3/zh/configuration.html#settings

typeAliases 類型別名 

類型別名是爲 Java 類型設置一個短的名字。 它只和 XML 配置有關,存在的意義僅在於用來減少類完全限定名的冗餘。例如:

<typeAliases>
  <typeAlias alias="Author" type="domain.blog.Author"/>
  <typeAlias alias="Blog" type="domain.blog.Blog"/>
  <typeAlias alias="Comment" type="domain.blog.Comment"/>
  <typeAlias alias="Post" type="domain.blog.Post"/>
  <typeAlias alias="Section" type="domain.blog.Section"/>
  <typeAlias alias="Tag" type="domain.blog.Tag"/>
</typeAliases>

當這樣配置時,Blog 可以用在任何使用 domain.blog.Blog 的地方。

也可以指定一個包名,MyBatis 會在包名下面搜索需要的 Java Bean,比如:

<typeAliases>
  <package name="domain.blog"/>
</typeAliases>

每一個在包 domain.blog 中的 Java Bean,在沒有註解的情況下,會使用 Bean 的首字母小寫的非限定類名來作爲它的別名。 比如 domain.blog.Author 的別名爲 author;若有註解,則別名爲其註解值。見下面的例子:

@Alias("author")
public class Author {
    ...
}

這是一些爲常見的 Java 類型內建的相應的類型別名。它們都是不區分大小寫的,注意對基本類型名稱重複採取的特殊命名風格。

別名 映射的類型
_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
object Object
map Map
hashmap HashMap
list List
arraylist ArrayList
collection Collection
iterator Iterator

environments 環境

MyBatis 的環境配置實際是數據源的配置。MyBatis 可以配置多個環境,幫助你將 SQL 映射對應到多種數據庫。environments下的environment環境就相當於一個數據庫連接配置,所以每個 SqlSessionFactory 實例只能選擇一種環境。

environments下有多個environment,每個environment有多個子標籤

環境元素定義瞭如何配置環境。

<environments default="development">
  <environment id="development">
    <transactionManager type="JDBC">
      <property name="..." value="..."/>
    </transactionManager>
    <dataSource type="POOLED">
      <property name="driver" value="${driver}"/>
      <property name="url" value="${url}"/>
      <property name="username" value="${username}"/>
      <property name="password" value="${password}"/>
    </dataSource>
  </environment>
</environments>

注意這裏的關鍵點:

  • 默認使用的環境 ID(比如:default="development")。
  • 每個 environment 元素定義的環境 ID(比如:id="development")。
  • 事務管理器的配置(比如:type="JDBC")。
  • 數據源的配置(比如:type="POOLED")。

默認的環境和環境 ID 是自解釋的,因此一目瞭然。

關於environment的子標籤:

    transactionManager  事務管理器

在 MyBatis 中有兩種類型的事務管理器(也就是 type=”[JDBC|MANAGED]”):

  • JDBC – 這個配置就是直接使用了 JDBC 的提交和回滾設置,它依賴於從數據源得到的連接來管理事務作用域。
  • MANAGED – 這個配置幾乎沒做什麼。它從來不提交或回滾一個連接,而是讓容器來管理事務的整個生命週期(比如 JEE 應用服務器的上下文)。 默認情況下它會關閉連接,然而一些容器並不希望這樣,因此需要將 closeConnection 屬性設置爲 false 來阻止它默認的關閉行爲。例如:
    <transactionManager type="MANAGED">
      <property name="closeConnection" value="false"/>
    </transactionManager>

如果你正在使用 Spring + MyBatis,則沒有必要配置事務管理器, 因爲 Spring 模塊會使用自帶的管理器來覆蓋前面的配置。

這兩種事務管理器類型都不需要設置任何屬性。它們其實是類型別名。

    dataSource 數據源

dataSource 元素使用標準的 JDBC 數據源接口來配置 JDBC 連接對象的資源。MyBatis 三種內建的數據源類型,即 type="[UNPOOLED|POOLED|JNDI]",數據源規定了可以配置哪些屬性。

(1)UNPOOLED

UNPOOLED 不支持 JDBC 數據源連接池,實現的只是每次被請求時打開和關閉連接。其包含的屬性:

  • driver:JDBC 驅動的 Java 類的完全限定名,如 MySQL 的 com.mysql.jdbc.Driver
  • url:數據庫的 JDBC URL 地址
  • username:數據庫的用戶名
  • password:數據庫的密碼
  • defaultTransactionIsolationLevel:默認的連接事務隔離級別。

(2)POOLED

POOLED 支持 JDBC 數據源連接池,利用“池”的概念將 JDBC 連接對象組織起來,避免了創建新的連接實例時所必需的初始化和認證時間。除了有 UNPOOLED 的屬性外還有包括 poolMaximumActiveConnections 、 poolMaximumIdleConnections 等屬性。

(3)JNDI

JNDI 支持外部數據源連接池,它的實現是爲了能在如 EJB 或應用服務器這類容器中使用,容器可以集中或在外部配置數據源,然後放置一個 JNDI 上下文的引用。其包含的屬性:

  • initial_context:用來在 InitialContext 中尋找上下文
  • data_source:引用數據源實例位置的上下文的路徑

mappers 映射器

        數據訪問對象DAO定義好了,數據表設定好了,如何進行數據的curd操作,即sql操作呢?對於DAO,要定義相應的接口,該接口提供操控DAO的一系列方法(增刪查改啥的),同時在設置一個xml配置文件與該接口對應,該xml與DAO得對應在主配置文件的mappers標籤下進行配置。

        說白了就是要定義 SQL 映射語句。 但是首先我們需要告訴 MyBatis 到哪裏去找到這些語句。 Java 在自動查找這方面沒有提供一個很好的方法,所以最佳的方式是告訴 MyBatis 到哪裏去找映射文件。 你可以使用相對於類路徑的資源引用, 或完全限定資源定位符(包括 file:/// 的 URL),或類名和包名等。例如:

<!-- 使用相對於類路徑的資源引用 -->
<mappers>
  <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
  <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
  <mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
<!-- 使用完全限定資源定位符(URL) -->
<mappers>
  <mapper url="file:///var/mappers/AuthorMapper.xml"/>
  <mapper url="file:///var/mappers/BlogMapper.xml"/>
  <mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>
<!-- 使用映射器接口實現類的完全限定類名 -->
<mappers>
  <mapper class="org.mybatis.builder.AuthorMapper"/>
  <mapper class="org.mybatis.builder.BlogMapper"/>
  <mapper class="org.mybatis.builder.PostMapper"/>
</mappers>
<!-- 將包內的映射器接口實現全部註冊爲映射器 -->
<mappers>
  <package name="org.mybatis.builder"/>
</mappers>

這些配置會告訴了 MyBatis 去哪裏找映射文件,剩下的細節就應該是每個 SQL 映射文件

核心映射文件Mapper

使用JDBC操控數據庫寫sql時,每次都要手動去執行sql獲取數據,映射文件是專門用來放置sql語句的地方,執行的sql如何對應到DAO上?答案是使用接口,接口提供操控DAO的方法,配置文件使用sql實現與這些方法對應的映射,然後在主配置文件進行配置(mappers標籤下配置mapper),完成了解耦。

即寫接口,然後寫xml配置文件。

映射文件(mapper)包含的頂級元素:

  • cache:給定命名空間的緩存配置。
  • cache-ref:其他命名空間緩存配置的引用。
  • resultMap:描述如何從數據庫結果集中來加載對象。
  • sql:可被其他語句引用的可重用語句塊。
  • insert:映射插入語句
  • update:映射更新語句
  • delete:映射刪除語句
  • select:映射查詢語句

屬性namespace="xxx" 其中xxx必須是某個接口名,這樣才能完成綁定

select

select映射查詢語句,簡單查詢的 select 元素是非常簡單的。比如:

<select id="selectPerson" parameterType="int" resultType="hashmap">
  SELECT * FROM PERSON WHERE ID = #{id}
</select>

select查詢到的結果會自動映射到resultType或者resultMap指定的類型上。

如:

<select id="selectUsers" resultType="com.someapp.model.User">
  select id, username, hashedPassword
  from some_table
  where id = #{id}
</select>

select內置了多個屬性,常用的就是id,parameterType,resultType,resultMap。如果resultType的類型是某個自己寫的Java Bean,那麼MyBatis會自動創建對應的resultMap,當然手動配置resultMap也可以。

resultType:

1、基本類型  :resultType=基本類型

2、List類型:   resultType=List中元素的類型

3、Map類型     resultType =map

即select *自動返回list,xml的  resultType=List中元素的類型即可。

resultMap

resultMap相當於結果集,可在外部設置,如果使用resultType,則會進行自動配置,如果進行的是比較複雜的配置,那麼可以在外部設置resultMap。主要配置的就是屬性與列的對應。

<resultMap id="userResultMap" type="User">
  <id property="id" column="user_id" />
  <result property="username" column="user_name"/>
  <result property="password" column="hashed_password"/>
</resultMap>

而在引用它的語句中使用 resultMap 屬性就行了(注意我們去掉了 resultType 屬性)。比如:

<select id="selectUsers" resultMap="userResultMap">
  select user_id, user_name, hashed_password
  from some_table
  where id = #{id}
</select>

如果是複雜的sql語句,那麼映射也會變得複雜,resultMap提供多種子標籤解決這些事情。

id:主鍵映射,即DAO字段與數據庫表的主鍵映射

result:非主鍵映射

id與result常見的屬性是property(指定DAO字段),column(指定映射的列),javaType(指定映射字段的類型)

constructor:構造器映射,如果在DAO中定義了構造器,那麼resultMap中進行相應配置即可

            例:

public User(Integer id,String username,String password,String sex,String address){
    this.id = id;
    this.username = username;
    this.password = password;
    this.sex = sex;
    this.address = address;
}

配置爲:

<resultMap id="userMap" type="User">
    <constructor>  
        <idArg column="id" javaType="int"/>
        <arg column="username" javaType="String"/>
        <arg column="password" javaType="String"/>
        <arg column="sex" javaType="String"/>
        <arg column="address" javaType="String"/>
    </constructor>  
</resultMap>

指定參數與類型即可。

discriminator:鑑別器,用於動態鑑別列的值並映射到對應的結果集,即通過列的值分配不同的字段給DAO,很像 Java 語言中的 switch 語句。

update/insert/delete

與select類似,想要什麼功能直接查看官方文檔

過程概述

1.在pom.xml中導入依賴並設置<build>

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>lsl</groupId>
    <artifactId>lsl.desigin.db</artifactId>
    <version>1.0-SNAPSHOT</version>

    <name>springJdbc</name>
    <url>http://maven.apache.org</url>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>5.1.8.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.1.8.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.1.8.RELEASE</version>
        </dependency>


        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.17</version>
        </dependency>

        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.2</version>
        </dependency>
    </dependencies>


    <!--指定執行時的掃描選項-->
    <build>
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.xml</include>
                </includes>
            </resource>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.*</include>
                </includes>
            </resource>
        </resources>
    </build>

</project>

 

2.配置主文件,並在開發過程中不斷添加typeAlias、mapper等

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">


<configuration>
    <properties resource="myBatis.configure.properties" />

    <typeAliases>
        <typeAlias alias="Student" type="dao.Student"  />
        <typeAlias alias="Elder" type="dao.Elder"/>
    </typeAliases>

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${driver}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <package name="mapper"/>
    </mappers>

</configuration>

 

3.寫映射,即接口與xml映射文件

package mapper;

import dao.Student;

public interface StudentMapper {
    Student selectStudentById(Integer id) throws Exception;
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org/DTD Mapper 3.0" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="mapper.StudentMapper">

    <select id="selectStudentById" parameterType="int" resultType="Student">
        select * from students where id=#{id}
    </select>

</mapper>

4.使用映射完成sql語句的執行

使用過程爲:

Resources.getResourceAsStream(configResource)獲取總配置文件流

new SqlSessionFactoryBuilder().build(resourceInputStream);通過總配置文件流獲取會話工廠

sqlSessionFactory.openSession();開啓會話

sqlSession.getMapper(StudentMapper.class);通過反射、動態代理等獲取自動生成的實現對應接口的對象

Student student=studentMapper.selectStudentById(1); sqlSession.commit();執行sql,會話進行提交執行

package study;

import dao.Student;
import mapper.StudentMapper;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.InputStream;

public class MyBatisStudy {
    private static String configResource="MyBatis.config.xml";
    private static InputStream resourceInputStream=null;

    public static void main(String[] args){
        try {
            resourceInputStream= Resources.getResourceAsStream(configResource);//獲取文件配置流
        }catch (Exception e){
            e.printStackTrace();
        }

        SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(resourceInputStream);//創建工廠,傳入信息
        SqlSession sqlSession=sqlSessionFactory.openSession();//開啓會話
        StudentMapper studentMapper=sqlSession.getMapper(StudentMapper.class);//獲取映射1,返回相關實例

        //可以執行sql語句了,對應的映射由MyBatis幫助完成
        try{
            Student student=studentMapper.selectStudentById(1);
            sqlSession.commit();//會話進行提交執行
            System.out.println(student.toString());
        }catch (Exception e){
            e.printStackTrace();
        }

        sqlSession.close();//關閉會話即可
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章