Spring JPA 關(guān)系映射系列教程:OneToOne 關(guān)系映射詳解

這是JPA 關(guān)系映射 系列教程的第一篇:JPA One-To-One 外鍵關(guān)系映射

JPA 關(guān)系映射系列(SPring Boot, Postgresql):

  1. JPA One-To-One 外鍵 關(guān)系映射
  2. JPA One-To-Many 關(guān)系映射
  3. JPA Many-To-Many 關(guān)系映射

為了完成這邊教程你所需要的工具如下:

Spring Data JPA
Spring Boot
Postgresql 數(shù)據(jù)庫

如何在ubuntu下安裝Postgresql以及圖形界面客戶端PgAdmin3践剂,請(qǐng)戳我

項(xiàng)目依賴

<?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>com.example</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>demo</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.6.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>


</project>

One To One 關(guān)系:
在這里 我們使用 book 以及 book_detail 來描述一對(duì)一的關(guān)系。

Selection_054.png

下面我們來看看如果使用 Spring Boot 來定義實(shí)體映射:

package com.example.demo;

import javax.persistence.*;

@Entity
public class Book {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;
    private String name;

    @OneToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "book_detail")
    private BookDetail bookDetail;

    public Book() {
    }

    public Book(String name, BookDetail bookDetail) {
        this.name = name;
        this.bookDetail = bookDetail;
    }

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public BookDetail getBookDetail() {
        return bookDetail;
    }

    public void setBookDetail(BookDetail bookDetail) {
        this.bookDetail = bookDetail;
    }
}

package com.example.demo;

import javax.persistence.*;

@Entity
public class BookDetail {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;


    private long numberOfPages;


   @OneToOne(cascade = CascadeType.ALL,mappedBy = "bookDetail")
    private Book book;

    public BookDetail() {
    }

    public BookDetail(long numberOfPages, Book book) {
        this.numberOfPages = numberOfPages;
     
    }

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public long getNumberOfPages() {
        return numberOfPages;
    }

    public void setNumberOfPages(long numberOfPages) {
        this.numberOfPages = numberOfPages;
    }

    public Book getBook() {
        return book;
    }

    public void setBook(Book book) {
        this.book = book;
    }
}

Spring Boot 中所有的實(shí)體類都需要用注解 @Entity 來標(biāo)記,被這個(gè)注解標(biāo)記的實(shí)體類將會(huì)在數(shù)據(jù)庫中生成一個(gè)對(duì)應(yīng)的 Table。表名通常情況下以類名作為默認(rèn)表名, 當(dāng)然你也可以指定一個(gè)已經(jīng)存在的表來對(duì)應(yīng)這個(gè)實(shí)體類硼瓣。比如:

            @Entity
            @Table(name ="book_specific")

@Id 標(biāo)記一個(gè)字段為 主鍵(Primary Key). 當(dāng)一個(gè)主鍵字段被定義的時(shí)候,主鍵的值將會(huì)被 ObjectDB 自動(dòng)注入到這個(gè)字段中。

@GeneratedValue 通常情況下,關(guān)系數(shù)據(jù)庫會(huì)為每一個(gè)數(shù)據(jù)庫維護(hù)一個(gè)特殊的全局?jǐn)?shù)字生成器置谦。這個(gè)數(shù)字生成器會(huì)自動(dòng)為每一個(gè)沒有定義主鍵字段的實(shí)體對(duì)象生成一個(gè) ID堂鲤。

    @Id
    @GeneratedValue
    private long id;

commit期間, AUTO策略使用全局?jǐn)?shù)字生成器為每個(gè)新的實(shí)體對(duì)象生成主鍵。這些生成的值在數(shù)據(jù)庫級(jí)別是唯一的,不會(huì)被
回收,這些主鍵值被多個(gè)表共享 媒峡。

簡(jiǎn)單的來解釋一下這里所謂的主鍵值被多個(gè)表共享是什么意思瘟栖,
    例如我們現(xiàn)在一個(gè)數(shù)據(jù)庫,里面有兩張表 A1 跟 A2谅阿。
A1里面有三條數(shù)據(jù) A11 A12 A13
A2里面有三條數(shù)據(jù) A21 A22 A23半哟,
每一個(gè)表的每一條數(shù)據(jù)的主鍵id通常情況是自增加的,
所以理論上的A11 的主鍵id=1签餐,A12的主鍵id=2寓涨,A13的主鍵id=3
所以理論上的A21 的主鍵id=1,A22的主鍵id=2氯檐,A23的主鍵id=3

但是如果你使用AUTO的策略的話戒良,主鍵的增加是根據(jù)你將數(shù)值插入數(shù)據(jù)庫的順序來決定的。
如果你先在A1表中插入 A11 A12 A13三條數(shù)據(jù)冠摄,然后在A2表中插入A21 A22 A23三條數(shù)據(jù)
那們的主鍵的id就會(huì)變成 
A11 的主鍵id=1糯崎,A12的主鍵id=2,A13的主鍵id=3
A21 的主鍵id=4河泳,A22的主鍵id=5沃呢,A23的主鍵id=6
如果你兩個(gè)表是相互交替的插入數(shù)據(jù),那么主鍵的生成也是相互的交替的拆挥,
    這就導(dǎo)致了每一張表的主鍵ID都不一定是從1開始增加的
    這取決你插入數(shù)據(jù)的順序

這就是所謂的主鍵值被多個(gè)表共享

所以通常情況下我們?yōu)榱吮WC每一個(gè)表的主鍵ID都從一開始增加薄霜,我們需要設(shè)置
@GeneratedValue(strategy=GenerationType.IDENTITY

關(guān)于JPA中主鍵的生成策略,一共有四種模式:
@GeneratedValue(strategy=GenerationType.AUTO)
@GeneratedValue(strategy=GenerationType.IDENTITY)
@GeneratedValue(strategy=GenerationType.SEQUENCE)
@GeneratedValue(strategy=GenerationType.TABLE)

如果大家對(duì)這方面感興趣的話,請(qǐng)點(diǎn)擊關(guān)注樓主黄锤,或者給樓主留言搪缨,樓主會(huì)考慮寫一篇專門的文章來講解JPA中主鍵的生成策略。

@OneToOne 在兩個(gè)實(shí)體之間定義了一對(duì)一的關(guān)系,這里Book與BookDetail是一對(duì)一的關(guān)系鸵熟。
mappedBy=”bookDetail” 定義了兩張表之間由誰來維護(hù)彼此的關(guān)系副编。這里表示由Book這張表來維護(hù)兩者之間的關(guān)系。這里
BookDetail類中的mappeBy的值 bookDetail 是實(shí)體類Book中對(duì)BookDetail的引用字段名流强。當(dāng)我們定義了mappedBy屬性之后, JPA就會(huì)在Book表中新增加一列bookDetail,它的值就是BookDetail表中的主鍵值痹届。
@JoinColumn 通常情況下,如果不指定這個(gè)屬性打月,那么JPA會(huì)默認(rèn)幫你在Book類中新增一列以bookDetail為默認(rèn)名字队腐,如果你指定了這個(gè)屬性,那么JPA會(huì)按照你給的屬性命為新增的一列命名奏篙。

當(dāng)然了關(guān)于@JoinColumn以及mappendBy這兩個(gè)屬性的用法不僅僅與此柴淘,事實(shí)上他們是JPA中關(guān)系映射非常重要的注解方法。
當(dāng)然本文不在討論范圍之類秘通。在之后的OneToMany以及 ManyToMany 教程中中我會(huì)詳細(xì)的討論這兩個(gè)屬性

當(dāng)然了如果你這么定義:

class Book(){
    
@OneToOne(cascade = CascadeType.ALL,mappedBy = "book")

private BookDetail bookDetail; 

}

class BookDetail(){

@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "book_id")
private Book book;

}

也是可以的为严,只不過這回在BookDetail表中新增加的一列,他的值就是Book表中的主鍵值肺稀。 這一次有BookDetail來維護(hù)兩個(gè)表之間的關(guān)系第股。

OneToOne關(guān)系中,誰來維護(hù)關(guān)系并不是特別重要话原,因?yàn)閮烧呤堑葍r(jià)的夕吻,你可以引用我的主鍵,同樣我也可以引用你的主鍵繁仁。 因此這跟OneToManyManyToMany是不一樣的涉馅。 例如在OneToMany中,關(guān)系的維護(hù)方永遠(yuǎn)是Many的那一方改备。比如班級(jí)跟學(xué)生就是一對(duì)多的關(guān)系控漠,這個(gè)時(shí)候就必須要在學(xué)生表中引用班級(jí)的主鍵作為一列。 因?yàn)槲覀兊谋硎遣荒鼙4婕系年P(guān)系悬钳。 在班級(jí)表中盐捷,每一個(gè)班級(jí)都有很多學(xué)生,這是一個(gè)集合默勾,這沒有辦法用數(shù)據(jù)庫在保存碉渡。 但是在學(xué)生表中,每一個(gè)學(xué)生都對(duì)應(yīng)唯一一個(gè)班級(jí)母剥。 所以我們可以引用班級(jí)的主鍵作為學(xué)生表的外鍵滞诺。 這就是為什么在一對(duì)多一方形导,關(guān)系的維護(hù)者永遠(yuǎn)是Many的那一方了。

對(duì)于OneToManyManyToMany的關(guān)系映射习霹,是這一系列教程的后兩篇文章朵耕。 稍后我會(huì)詳細(xì)解釋這兩種映射關(guān)系

下一步我們需要給Spring Boot配置數(shù)據(jù)庫,以便它能夠連接到我們的數(shù)據(jù)庫:
在Spring Boot的application.properties中添加以下語句:

# ===============================
# = DATA SOURCE
# ===============================
# Set here configurations for the database connection
spring.datasource.url=jdbc:postgresql://localhost:5432/jpa1
spring.datasource.username=postgres
spring.datasource.password=9145190618
spring.datasource.driver-class-name=org.postgresql.Driver
# Keep the connection alive if idle for a long time (needed in production)
spring.datasource.testWhileIdle=true
spring.datasource.validationQuery=SELECT 1
# ===============================
# = JPA / HIBERNATE
# ===============================
# Show or not log for each sql query
spring.jpa.show-sql=true
# Hibernate ddl auto (create, create-drop, update): with "create-drop" the database
# schema will be automatically created afresh for every start of application
spring.jpa.hibernate.ddl-auto=create-drop

# Naming strategy
spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyHbmImpl
spring.jpa.hibernate.naming.physical-strategy=org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy

# Allows Hibernate to generate SQL optimized for a particular DBMS
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect


#There is also a /shutdown endpoint, but its only visible by default via JMX. To enable it as an HTTP endpoint, add endpoints.shutdown.enabled=true to your application.properties file.
endpoints.shutdown.enabled=true

如果你沒有實(shí)現(xiàn)創(chuàng)建數(shù)據(jù)庫,那么你需要手動(dòng)用圖形GUI或者在命令行中創(chuàng)建數(shù)據(jù)庫

pring.datasource.url=jdbc:postgresql://localhost:5432/jpa 指明了數(shù)據(jù)庫為jpa,監(jiān)聽端口為5432,這是Postgresql數(shù)據(jù)庫的默認(rèn)監(jiān)聽端口。

這里pring.jpa.hibernate.ddl-auto可以是none,update,create,create-drop,有關(guān)詳細(xì)信息,請(qǐng)參考Hibernate文檔淋叶。

        none: 這的默認(rèn)值,不改變數(shù)據(jù)庫結(jié)構(gòu)阎曹。(一般在部署階段,可以使用這個(gè)模式)
        update: Hibernate根據(jù)給定的實(shí)體結(jié)構(gòu)更改數(shù)據(jù)庫。
        create: 每次創(chuàng)建數(shù)據(jù)庫,但不要在關(guān)閉時(shí)丟棄煞檩。
        create-drop: 創(chuàng)建數(shù)據(jù)庫,然后在SessionFactory關(guān)閉時(shí)將其刪除(在開發(fā)階段,可以使用這個(gè)方式,因?yàn)槊看纬绦騿?dòng)時(shí)候   都會(huì)刪除已有的數(shù)據(jù),在重新創(chuàng)建)处嫌。我們這里從創(chuàng)建開始,因?yàn)槲覀冞€沒有數(shù)據(jù)庫結(jié)構(gòu)。第一次運(yùn)行后,我們可以根據(jù)程序要求將其切換為更新或無更新斟湃。當(dāng)您想對(duì)數(shù)據(jù)庫結(jié)構(gòu)進(jìn)行一些更改時(shí),請(qǐng)使用更新熏迹。    

我們運(yùn)行程序的時(shí)候,我們可以看到:數(shù)據(jù)庫中已經(jīng)自動(dòng)創(chuàng)建了兩張表了:

Book 表
BookDetail 表

最右邊是外建,其值為book表中主鍵的id值

Spring Data JPA 包含了一些內(nèi)置的Repository實(shí)現(xiàn)了一些常用的操作數(shù)據(jù)庫的功能凝赛, 比如:findOne注暗, findAllsave 等等哄酝。下面根據(jù)上面兩個(gè)實(shí)體類友存,我們?cè)谕ㄟ^JPA提供給我們的Repository來簡(jiǎn)單的操作我們的數(shù)據(jù)庫。

我們所需要做的事情就是為我們的實(shí)體類 Book 和 BookDetail 建立對(duì)應(yīng)的Repository接口:

package com.example.demo;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface BookRepository extends JpaRepository<Book, Long> {
}

package com.example.demo;

import org.springframework.data.jpa.repository.JpaRepository;

public interface BookDetailRepository extends JpaRepository<BookDetail, Long> {
}

下面我們運(yùn)行APP來使用Repository新建數(shù)據(jù)庫數(shù)據(jù):

package com.example.demo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import java.util.ArrayList;
import java.util.List;

@SpringBootApplication
public class DemoApplication implements CommandLineRunner {

    @Autowired
    BookRepository bookRepository;

    @Autowired
    BookDetailRepository bookDetailRepository;

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

    @Override
    public void run(String... strings) throws Exception {


        List<Book> books = new ArrayList<>();
        books.add(new Book("Book A", new BookDetail(49)));
        books.add(new Book("Book B", new BookDetail(59)));
        books.add(new Book("Book C", new BookDetail(69)));
        bookRepository.save(books);

    }
}

運(yùn)行結(jié)果:


Selection_060.png
Selection_061.png

如果喜歡這篇文章陶衅,請(qǐng)給樓主點(diǎn)個(gè)贊或者加個(gè)關(guān)注。下次給大家?guī)淼氖荍PA One-To-Many 關(guān)系映射 以 及JPA Many-To-Many 關(guān)系映射

源碼已經(jīng)分享在Github

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末直晨,一起剝皮案震驚了整個(gè)濱河市搀军,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌勇皇,老刑警劉巖罩句,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異敛摘,居然都是意外死亡门烂,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門兄淫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來屯远,“玉大人,你說我怎么就攤上這事捕虽】ぃ” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵泄私,是天一觀的道長(zhǎng)房揭。 經(jīng)常有香客問我备闲,道長(zhǎng),這世上最難降的妖魔是什么捅暴? 我笑而不...
    開封第一講書人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任恬砂,我火速辦了婚禮,結(jié)果婚禮上蓬痒,老公的妹妹穿的比我還像新娘觉既。我一直安慰自己,他們只是感情好乳幸,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開白布瞪讼。 她就那樣靜靜地躺著,像睡著了一般粹断。 火紅的嫁衣襯著肌膚如雪符欠。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評(píng)論 1 297
  • 那天瓶埋,我揣著相機(jī)與錄音希柿,去河邊找鬼。 笑死养筒,一個(gè)胖子當(dāng)著我的面吹牛曾撤,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播晕粪,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼挤悉,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了巫湘?” 一聲冷哼從身側(cè)響起装悲,我...
    開封第一講書人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎尚氛,沒想到半個(gè)月后诀诊,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡阅嘶,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年属瓣,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片讯柔。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡抡蛙,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出磷杏,到底是詐尸還是另有隱情溜畅,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布极祸,位于F島的核電站慈格,受9級(jí)特大地震影響怠晴,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜浴捆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一蒜田、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧选泻,春花似錦冲粤、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至窝撵,卻和暖如春傀顾,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背碌奉。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來泰國打工短曾, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人赐劣。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓嫉拐,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親魁兼。 傳聞我的和親對(duì)象是個(gè)殘疾皇子婉徘,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)自:jpa-內(nèi)心求法 JPA概要摘要:JPA定義了Java ORM及實(shí)體操作API的標(biāo)準(zhǔn)。本文摘錄了JPA的一些...
    誰在烽煙彼岸閱讀 1,169評(píng)論 0 4
  • 本文中我們介紹并比較兩種最流行的開源持久框架:iBATIS和Hibernate,我們還會(huì)討論到Java Persi...
    大同若魚閱讀 4,310評(píng)論 4 27
  • 作為一種輕量級(jí)的關(guān)系映射工具碉考,Hibernate支持各種關(guān)系映射,例如:多對(duì)一挺身、一對(duì)多和一對(duì)一的數(shù)據(jù)庫表關(guān)系侯谁,通過...
    Ystrator閱讀 528評(píng)論 0 1
  • 終于到了數(shù)據(jù)庫操作部分了,通常我們對(duì)于數(shù)據(jù)庫的操作無非是增刪改查章钾,對(duì)于單表操作而言墙贱,SQL語句大都是類似的,同時(shí)時(shí)...
    藍(lán)色的咖啡閱讀 3,626評(píng)論 0 9
  • 并發(fā)包 ConcurrentHashMap(類HashMap的并發(fā)類) CopyOnWriteArrayList(...
    小魚游兒閱讀 289評(píng)論 0 1