超全面 hibernate 復(fù)習(xí)總結(jié)筆記

如有轉(zhuǎn)載瓶颠,請申明:
轉(zhuǎn)載自 IT天宇http://www.reibang.com/p/50964e92c5fb

前言

博客正式轉(zhuǎn)到簡書拟赊,實(shí)在忍受不了 CSDN 的模版,有廣告或界面難看還可以忍步清,但有些模版還有 bug要门,作為程序員忍無可忍,修個(gè) bug 真的有那么難嗎廓啊,這么多年了欢搜。

接著上篇,先說個(gè)段子谴轮。
三四年前如果有人問我 Android/Ios 如何入門炒瘟,我可能會(huì)推薦上百 G 的資料,但如果現(xiàn)在問我第步,我只會(huì)推薦一本書《app開發(fā)從上架到上吊》


你可能覺得我危言聳聽疮装,但今年的移動(dòng)開發(fā)行情真的很差,看到很多幾年經(jīng)驗(yàn)的一個(gè)月才收到幾個(gè)面試通知粘都,沒有經(jīng)驗(yàn)的就更絕望了廓推。

好吧,今天不是來討論行情的翩隧。
前段時(shí)間寫了一篇 struts2 的筆記樊展,有好心的老司機(jī)告訴我,struts2 已經(jīng)被拋棄了堆生。但話是這么說专缠,面試的時(shí)候,難免碰到問 struts2淑仆,如果這個(gè)時(shí)候表現(xiàn)得一臉懵逼涝婉,那估計(jì) offer 就沒有了。所以雖然現(xiàn)在 hibernate 用得不多了蔗怠,但還是得復(fù)習(xí)一下墩弯。

目錄

  1. 環(huán)境搭建
  2. 實(shí)體類映射
  3. 核心配置詳解
  4. 一級緩存
  5. 關(guān)系映射
  6. 抓取策略
  7. HQL
  8. QBC
  9. 其他配置
  10. 事務(wù)
  11. 二級緩存

正文

<a id="1"></a>

1.環(huán)境搭建

導(dǎo)包

根據(jù)需要選擇手動(dòng)導(dǎo)入 jar 包,或者用依賴管理工具寞射。

此外最住,為了連接數(shù)據(jù)庫,還需要數(shù)據(jù)庫的驅(qū)動(dòng)包怠惶。

配置

核心配置文件以 hibernate.cfg.xml 命名,放在類路徑下(Idea 放在 resources 下)

<!DOCTYPE hibernate-configuration PUBLIC
    "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
    "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
    <!-- SessionFactory轧粟,相當(dāng)于之前學(xué)習(xí)連接池配置 -->
    <session-factory>
        <!-- 1 基本4項(xiàng) -->
        <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
        <property name="hibernate.connection.url">jdbc:mysql:///db01</property>
        <property name="hibernate.connection.username">root</property>
        <property name="hibernate.connection.password">1234</property>
    
    </session-factory>

</hibernate-configuration>

<a id="2"> </a>

2.實(shí)體類映射

數(shù)據(jù)庫

create database db01;
use db01;

實(shí)體類

public class User {
    private Integer uid;
    private String username;
    private String password;
// 省略 get set 方法
}

映射實(shí)體類

可以用 xml 或者 注解來映射

xml

放在實(shí)體類同目錄下策治,名字和實(shí)體類相同脓魏,擴(kuò)展名為 .hbm.xml
例如: ser.hbm.xml

<!DOCTYPE hibernate-mapping PUBLIC 
    "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
    "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
    <class name="com.ittianyu.a_hello.User" table="t_user">
        <!-- 主鍵 -->
        <id name="uid">
            <generator class="native"></generator>
        </id>
        
        <!-- 普通屬性 -->
        <property name="username"></property>
        <property name="password"></property>
    
    </class>
</hibernate-mapping>

核心配置文件中加上映射文件位置

<mapping resource="com/ittianyu/hibernate/helloworld/User.hbm.xml"/>

注解

// name 對應(yīng)表名稱
@Entity
@Table(name = "t_user")
public class User {
    // 主鍵
    @Id
    @GeneratedValue
    private Integer uid;
    private String username;
    private String password;
// 省略 get set 方法
}

在核心配置文件中加上映射文件位置

<mapping class="com.ittianyu.hibernate.helloworld.User" />

測試

public class HelloWorld {
    @Test
    public void hello() {
        // username, password
        User user = new User("123456", "123");

        // 1.加載配置文件
        Configuration configure = new Configuration().configure();
        // 2.獲得session factory對象
        SessionFactory sessionFactory = configure.buildSessionFactory();
        // 3.創(chuàng)建session
        Session session = sessionFactory.openSession();
        // 4.開啟事務(wù)
        Transaction transaction = session.beginTransaction();
        // 5.保存并提交事務(wù)
        session.save(user);
        transaction.commit();
        // 6.釋放資源
        session.close();
        sessionFactory.close();
    }
}

<a id="3"> </a>

3.核心配置詳解

核心 api

Configuration

常用方法

  • 構(gòu)造方法:默認(rèn)加載 hibernate.properties
  • configure() 方法:默認(rèn)加載 hibernate.cfg.xml
  • configure(String) 方法:加載指定配置文件

手動(dòng)添加映射

// 手動(dòng)加載指定的配置文件
config.addResource("com/ittianyu/a_hello/User.hbm.xml");

// 手動(dòng)加載指定類,對應(yīng)的映射文件 User--> User.hbm.xml
config.addClass(User.class);

SessionFactory

  • SessionFactory 相當(dāng)于java web連接池通惫,用于管理所有session
  • 獲得方式:config.buildSessionFactory();
  • sessionFactory hibernate緩存配置信息 (數(shù)據(jù)庫配置信息茂翔、映射文件,預(yù)定義HQL語句 等)
  • SessionFactory線程安全履腋,可以是成員變量珊燎,多個(gè)線程同時(shí)訪問時(shí),不會(huì)出現(xiàn)線程并發(fā)訪問問題
  • 開啟一個(gè) session:factory.openSession();
  • 獲取和當(dāng)前線程綁定的會(huì)話(需要配置):factory.getCurrentSession();
    <property name="hibernate.current_session_context_class">thread</property>
    

Session

  • Session 相當(dāng)于 JDBC的 Connection -- 會(huì)話
  • 通過session操作PO對象 --增刪改查
  • session單線程遵湖,線程不安全悔政,不能編寫成成員變量。
  • Api:
    save 保存
    update 更新
    delete 刪除
    get 通過id查詢延旧,如果沒有 null
    load 通過id查詢谋国,如果沒有拋異常
    createQuery("hql")  獲得Query對象
    createCriteria(Class) 獲得Criteria對象
    

Transaction

開啟事務(wù) beginTransaction()
獲得事務(wù) getTransaction()

提交事務(wù):commit()
回滾事務(wù):rollback()

和 spring 整合后,無需手動(dòng)管理

Query

  • hibernate執(zhí)行hql語句
  • hql語句:hibernate提供面向?qū)ο蟛樵冋Z句迁沫,使用對象(類)和屬性進(jìn)行查詢芦瘾。區(qū)分大小寫。
  • 獲得 session.createQuery("hql")
  • 方法:
    list()  查詢所有
    uniqueResult() 獲得一個(gè)結(jié)果集畅。如果沒有查詢到返回null近弟,如果查詢多條拋異常。
    
    setFirstResult(int) 分頁挺智,開始索引數(shù)startIndex
    setMaxResults(int) 分頁祷愉,每頁顯示個(gè)數(shù) pageSize
    

Criteria

  • QBC(query by criteria),hibernate提供純面向?qū)ο蟛樵冋Z言逃贝,提供直接使用PO對象進(jìn)行操作谣辞。
  • 獲得方式:Criteria criteria = session.createCriteria(User.class);
  • 條件
    criteria.add(Restrictions.eq("username", "tom"));
    Restrictions.gt(propertyName, value)        大于
    Restrictions.ge(propertyName, value)    大于等于
    Restrictions.lt(propertyName, value)    小于
    Restrictions.le(propertyName, value)    小于等于
    Restrictions.like(propertyName, value)  模糊查詢,注意:模糊查詢值需要使用 % _
    

工具類

public class HibernateUtils {
    private static SessionFactory sessionFactory;
    static {
        Configuration configuration = new Configuration().configure();
        sessionFactory = configuration.buildSessionFactory();

        Runtime.getRuntime().addShutdownHook(new Thread(){
            @Override
            public void run() {
                sessionFactory.close();
            }
        });
    }

    public static Session openSession() {
        return sessionFactory.openSession();
    }

    public static Session getCurrentSession() {
        return sessionFactory.getCurrentSession();
    }

    public static void main(String[] args) {
        Session session = openSession();
        System.out.println(session);
        session.close();

    }
}

核心配置

基本配置

<!-- SessionFactory沐扳,相當(dāng)于之前學(xué)習(xí)連接池配置 -->
<session-factory>
    <!-- 1 基本4項(xiàng) -->
    <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
    <property name="hibernate.connection.url">jdbc:mysql:///h_day01_db</property>
    <property name="hibernate.connection.username">root</property>
    <property name="hibernate.connection.password">1234</property>

    <!-- 2 與本地線程綁定 -->
    <property name="hibernate.current_session_context_class">thread</property>

        <!-- 3 方言:為不同的數(shù)據(jù)庫泥从,不同的版本,生成sql語句(DQL查詢語句)提供依據(jù) 
            * mysql 字符串 varchar
            * orcale 字符串 varchar2
        -->
        <property name="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</property>
    
    <!-- 4 sql語句 -->
    <!-- 顯示sql語句 -->
    <property name="hibernate.show_sql">true</property>
    <property name="hibernate.format_sql">true</property>

    <!-- 5 自動(dòng)創(chuàng)建表(了解) 沪摄,學(xué)習(xí)中使用躯嫉,開發(fā)不使用的。
        * 開發(fā)中DBA 先創(chuàng)建表杨拐,之后根據(jù)表生產(chǎn) PO類
        * 取值:
        update:【】
            如果表不存在祈餐,將創(chuàng)建表。
            如果表已經(jīng)存在哄陶,通過hbm映射文件更新表(添加)帆阳。(映射文件必須是數(shù)據(jù)庫對應(yīng))
                表中的列可以多,不負(fù)責(zé)刪除屋吨。
        create :如果表存在蜒谤,先刪除山宾,再創(chuàng)建。程序結(jié)束時(shí)鳍徽,之前創(chuàng)建的表不刪除资锰。【】
        create-drop:與create幾乎一樣阶祭。如果factory.close()執(zhí)行绷杜,將在JVM關(guān)閉同時(shí),將創(chuàng)建的表刪除了濒募。(測試)
        validate:校驗(yàn) hbm映射文件 和 表的列是否對應(yīng)鞭盟,如果對應(yīng)正常執(zhí)行,如果不對應(yīng)拋出異常萨咳。(測試)
    -->
    <property name="hibernate.hbm2ddl.auto">create</property>
    
    <!-- 6 java web 6.0 存放一個(gè)問題
        * BeanFactory 空指針異常
            異常提示:org.hibernate.HibernateException: Unable to get the default Bean Validation factory
        * 解決方案:取消bean校驗(yàn)
    -->
    <property name="javax.persistence.validation.mode">none</property>

    <!-- 添加映射文件 
        <mapping >添加映射文件
            resource 設(shè)置 xml配置文件 (addResource(xml))
            class 配置類 (addClass(User.class)) 配置的是全限定類名
    -->
    <mapping  resource="com/ittianyu/a_hello/User.hbm.xml"/>
</session-factory>

主鍵種類

  • 自然主鍵: 在業(yè)務(wù)中,某個(gè)屬性符合主鍵的三個(gè)要求.那么該屬性可以作為主鍵列.
  • 代理主鍵: 在業(yè)務(wù)中,不存符合以上3個(gè)條件的屬性,那么就增加一個(gè)沒有意義的列.作為主鍵.

類型對應(yīng)

Java數(shù)據(jù)類型 Hibernate數(shù)據(jù)類型 標(biāo)準(zhǔn)SQL數(shù)據(jù)類型(不同DB有差異)
byte懊缺、java.lang.Byte byte TINYINT
short、java.lang.Short short SMALLINT
int培他、java.lang.Integer integer INGEGER
long鹃两、java.lang.Long long BIGINT
float、java.lang.Float float FLOAT
double舀凛、java.lang.Double double DOUBLE
java.math.BigDecimal big_decimal NUMERIC
char俊扳、java.lang.Character character CHAR(1)
boolean、java.lang.Boolean boolean BIT
java.lang.String string VARCHAR
boolean猛遍、java.lang.Boolean yes_no CHAR(1)('Y'或'N')
boolean馋记、java.lang.Boolean true_false CHAR(1)('Y'或'N')
java.util.Date、java.sql.Date date DATE
java.util.Date懊烤、java.sql.Time time TIME
java.util.Date梯醒、java.sql.Timestamp timestamp TIMESTAMP
java.util.Calendar calendar TIMESTAMP
java.util.Calendar calendar_date DATE
byte[] binary VARBINARY、BLOB
java.lang.String text CLOB
java.io.Serializable serializable VARBINARY腌紧、BLOB
java.sql.Clob clob CLOB
java.sql.Blob blob BLOB
java.lang.Class class VARCHAR
java.util.Locale locale VARCHAR
java.util.TimeZone timezone VARCHAR
java.util.Currency currency VARCHAR

普通屬性

<hibernate-mapping> 
            package 用于配置PO類所在包
                例如: package="com.ittianyu.d_hbm"
        <class> 配置 PO類 和 表 之間對應(yīng)關(guān)系
            name:PO類全限定類名
                例如:name="com.ittianyu.d_hbm.Person"
                如果配置 package茸习,name的取值可以是簡單類名 name="Person"
            table : 數(shù)據(jù)庫對應(yīng)的表名
            dynamic-insert="false" 是否支持動(dòng)態(tài)生成insert語句
            dynamic-update="false" 是否支持動(dòng)態(tài)生成update語句
                如果設(shè)置true,hibernate底層將判斷提供數(shù)據(jù)是否為null壁肋,如果為null号胚,insert或update語句將沒有此項(xiàng)。
        普通字段
            <property>
                name : PO類的屬性
                column : 表中的列名浸遗,默認(rèn)name的值相同
                type:表中列的類型猫胁。默認(rèn)hibernate自己通過getter獲得類型,一般情況不用設(shè)置
                    取值1: hibernate類型
                        string 字符串
                        integer 整形
                    取值2: java類型 (全限定類名)
                        java.lang.String 字符串
                    取值3:數(shù)據(jù)庫類型
                        varchar(長度) 字符串
                        int 整形
                        <property name="birthday">
                            <column name="birthday" sql-type="datetime"></column>
                        </property>
                        javabean 一般使用類型 java.util.Date
                        jdbc規(guī)范提供3中
                            java類型              mysql類型
                            java.sql.Date       date
                            java.sql.time       time
                            java.sql.timestamp  timestamp
                            null                datetime
                            
                            以上三個(gè)類型都是java.util.Date子類
                            
                length : 列的長度跛锌。默認(rèn)值:255
                not-null : 是否為null
                unique : 是否唯一
                access:設(shè)置映射使用PO類屬性或字段
                    property : 使用PO類屬性弃秆,必須提供setter、getter方法
                    field : 使用PO類字段,一般很少使用菠赚。
                insert 生成insert語句時(shí)盼樟,是否使用當(dāng)前字段。
                update 生成update語句時(shí)锈至,是否使用當(dāng)前字段。
                    默認(rèn)情況:hibernate生成insert或update語句译秦,使用配置文件所有項(xiàng)
        注意:配置文件如果使用關(guān)鍵字峡捡,列名必須使用重音符    

主鍵

<id>配置主鍵
name:屬性名稱
access="" 設(shè)置使用屬性還是字段
column=""  表的列名
length=""  長度
type="" 類型
<generator> class屬性用于設(shè)置主鍵生成策略
1.increment 由hibernate自己維護(hù)自動(dòng)增長
    底層通過先查詢max值,再+1策略
    不建議使用筑悴,存在線程并發(fā)問題
2.identity hibernate底層采用數(shù)據(jù)庫本身自動(dòng)增長列
    例如:mysql auto_increment
3.sequence hibernate底層采用數(shù)據(jù)庫序列
    例如:oracle 提供序列
4.hilo 
    
    </generator>
5.native 根據(jù)底層數(shù)據(jù)庫的能力選擇 identity们拙、sequence 或者 hilo 中的一個(gè)「罅撸【】
##以上策略使用整形砚婆,long, short 或者 int 類型
6.uuid 采用字符串唯一值【】
##以上策略 代理主鍵,有hibernate維護(hù)突勇。
7.assigned 自然主鍵装盯,由程序自己維護(hù)〖撞觯【】

<a id="4"> </a>

4.一級緩存

對象狀態(tài)

三種狀態(tài)

  • 瞬時(shí)態(tài):transient埂奈,session沒有緩存對象,數(shù)據(jù)庫也沒有對應(yīng)記錄定躏。
    OID特點(diǎn):沒有值
  • 持久態(tài):persistent账磺,session緩存對象,數(shù)據(jù)庫最終會(huì)有記錄痊远。(事務(wù)沒有提交)
    OID特點(diǎn):有值
  • 脫管態(tài):detached垮抗,session沒有緩存對象,數(shù)據(jù)庫有記錄碧聪。
    OID特點(diǎn):有值

轉(zhuǎn)換

三種狀態(tài)轉(zhuǎn)換圖

一級緩存

一級緩存:又稱為session級別的緩存冒版。當(dāng)獲得一次會(huì)話(session),hibernate在session中創(chuàng)建多個(gè)集合(map)矾削,用于存放操作數(shù)據(jù)(PO對象)壤玫,為程序優(yōu)化服務(wù),如果之后需要相應(yīng)的數(shù)據(jù)哼凯,hibernate優(yōu)先從session緩存中獲取欲间,如果有就使用;如果沒有再查詢數(shù)據(jù)庫断部。當(dāng)session關(guān)閉時(shí)猎贴,一級緩存銷毀。

@Test
public void demo02(){
    //證明一級緩存
    Session session = factory.openSession();
    session.beginTransaction();
    
    //1 查詢 id = 1
    User user = (User) session.get(User.class, 1);
    System.out.println(user);
    //2 再查詢 -- 不執(zhí)行select語句,將從一級緩存獲得
    User user2 = (User) session.get(User.class, 1);
    System.out.println(user2);
    
    session.getTransaction().commit();
    session.close();
}

可以調(diào)用方法清除一級緩存

//清除
//session.clear();
session.evict(user);

快照

與一級緩存一樣的存放位置她渴,對一級緩存數(shù)據(jù)備份达址。保證數(shù)據(jù)庫的數(shù)據(jù)與 一級緩存的數(shù)據(jù)必須一致。如果一級緩存修改了趁耗,在執(zhí)行commit提交時(shí)沉唠,將自動(dòng)刷新一級緩存,執(zhí)行update語句苛败,將一級緩存的數(shù)據(jù)更新到數(shù)據(jù)庫满葛。

當(dāng)緩存和數(shù)據(jù)庫數(shù)據(jù)不一樣且在提交之前,可以調(diào)用 refresh 強(qiáng)制刷新緩存罢屈。

<a id="5"> </a>

5.關(guān)系映射

一對一

一對一關(guān)系一般是可以整合成一張表嘀韧,也可以分成兩張表。
維護(hù)兩張表的關(guān)系可以選擇外鍵也可以選擇讓主鍵同步缠捌。

實(shí)體類

Address.java

public class Address {
    private Integer id;
    private String name;

    private Company company;
    // 省略 get set
}

Company.java

public class Company {
    private Integer id;
    private String name;

    private Address address;
    // 省略 get set
}

外鍵維護(hù)關(guān)系

Address.hbm.xml

<!--
dynamic-insert 和 dynamic-update 為 true 時(shí)锄贷,sql語句中只有值變化或者不為空的屬性才會(huì)加上,用于更新部分屬性
-->
<class name="Address" table="t_address_sync" dynamic-insert="true" dynamic-update="true">
    <id name="id" column="id">
        <!--
        主鍵與外鍵表的主鍵同步
        -->
        <generator class="foreign">
            <param name="property">company</param>
        </generator>
    </id>
    <property name="name" column="name"/>

    <!--
        需要在同步主鍵的一方加上 constrained="true" 使用給主鍵加上外鍵約束
    -->
    <one-to-one name="company" class="Company" constrained="true" />
</class>

Company.hbm.xml

<!--
dynamic-insert 和 dynamic-update 為 true 時(shí)曼月,sql語句中只有值變化或者不為空的屬性才會(huì)加上谊却,用于更新部分屬性
-->
<class name="Company" table="t_company_ref" dynamic-insert="true" dynamic-update="true">
    <id name="id" column="id">
        <generator class="native"/>
    </id>
    <property name="name" column="name"/>

    <!--
        one-to-one 中使用了 property-ref :當(dāng)前類哪個(gè)屬性是引用外鍵
        放棄維護(hù)外鍵
    -->
    <one-to-one name="address" class="Address" property-ref="company" />
</class>

主鍵同步關(guān)系

Address.hbm.xml

<!--
dynamic-insert 和 dynamic-update 為 true 時(shí),sql語句中只有值變化或者不為空的屬性才會(huì)加上十嘿,用于更新部分屬性
-->
<class name="Address" table="t_address_sync" dynamic-insert="true" dynamic-update="true">
    <id name="id" column="id">
        <!--
        主鍵與外鍵表的主鍵同步
        -->
        <generator class="foreign">
            <param name="property">company</param>
        </generator>
    </id>
    <property name="name" column="name"/>

    <!--
        需要在同步主鍵的一方加上 constrained="true" 使用給主鍵加上外鍵約束
    -->
    <one-to-one name="company" class="Company" constrained="true" />
</class>

Company.hbm.xml

<!--
dynamic-insert 和 dynamic-update 為 true 時(shí)因惭,sql語句中只有值變化或者不為空的屬性才會(huì)加上,用于更新部分屬性
-->
<class name="Company" table="t_company_sync" dynamic-insert="true" dynamic-update="true">
    <id name="id" column="id">
        <generator class="native"/>
    </id>
    <property name="name" column="name"/>

    <!--
    在另一個(gè)表需要修改主鍵生成策略為 外鍵
    -->
    <one-to-one name="address" class="Address" />
</class>

一對多

實(shí)體類

Customer.java

public class Customer {
    private Integer id;
    private String name;

    private Set<Order> orders = new HashSet<>();
    // 省略 get set
}

Order.java

public class Order {
    private Integer id;
    private String name;

    private Customer customer;
    // 省略 get set
}

映射文件

Customer.hbm.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.ittianyu.hibernate.onetomany">
    <!--
    dynamic-insert 和 dynamic-update 為 true 時(shí)绩衷,sql語句中只有值變化或者不為空的屬性才會(huì)加上蹦魔,用于更新部分屬性
    -->
    <class name="Customer" table="t_customer" dynamic-insert="true" dynamic-update="true">
        <id name="id" column="id">
            <generator class="native"/>
        </id>
        <property name="name" column="name"/>
        <!--
        inverse 為 true 表示放棄維護(hù)關(guān)系,留給對方來維護(hù)咳燕,
        一般是一對多中 一的一方放棄勿决,由多的一放維護(hù),
        這個(gè)時(shí)候刪除對象時(shí)招盲,需要手動(dòng)將關(guān)聯(lián)的對象外鍵引用移除
        -->
        <set name="orders" inverse="true">
            <key column="cid"></key>
            <one-to-many class="Order" />
        </set>
    </class>
</hibernate-mapping>

Order.hbm.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.ittianyu.hibernate.onetomany">
    <!--
    dynamic-insert 和 dynamic-update 為 true 時(shí)畜份,sql語句中只有值變化或者不為空的屬性才會(huì)加上温眉,用于更新部分屬性
    -->
    <class name="Order" table="t_order" dynamic-insert="true" dynamic-update="true">
        <id name="id" column="id">
            <generator class="native"/>
        </id>
        <property name="name" column="name"/>
        <many-to-one name="customer" column="cid" class="Customer" />
    </class>
</hibernate-mapping>

多對多

實(shí)體類

Course.java

public class Course {
    private Integer id;
    private String name;
    private Set<Student> students = new HashSet<>();
    // 省略 get set
}

Student.java

public class Student {
    private Integer id;
    private String name;
    private Set<Course> courses = new HashSet<>();
    // 省略 get set
}

映射文件

Course.hbm.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.ittianyu.hibernate.manytomany">
    <!--
    dynamic-insert 和 dynamic-update 為 true 時(shí),sql語句中只有值變化或者不為空的屬性才會(huì)加上,用于更新部分屬性
    -->
    <class name="Course" table="t_course" dynamic-insert="true" dynamic-update="true">
        <id name="id" column="id">
            <generator class="native"/>
        </id>
        <property name="name" column="name"/>

        <!--
            many to many 中船响,需要給 set 加上 table 名
            放棄維護(hù)外鍵
        -->
        <set name="students" table="t_student_course" inverse="true">
            <key column="cid"></key>
            <many-to-many class="Student" column="sid" />
        </set>
    </class>
</hibernate-mapping>

Student.hbm.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.ittianyu.hibernate.manytomany">
    <!--
    dynamic-insert 和 dynamic-update 為 true 時(shí)雁仲,sql語句中只有值變化或者不為空的屬性才會(huì)加上昔字,用于更新部分屬性
    -->
    <class name="Student" table="t_studennt" dynamic-insert="true" dynamic-update="true">
        <id name="id" column="id">
            <generator class="native"/>
        </id>
        <property name="name" column="name"/>
        <!--
            many to many 中高每,需要給 set 加上 table 名
            外鍵表由 student 維護(hù),并啟用級聯(lián)礼饱,所以 course 中要放棄維護(hù)
        -->
        <set name="courses" table="t_student_course" cascade="save-update">
            <key column="sid"></key>
            <many-to-many class="Course" column="cid" />
        </set>
    </class>
</hibernate-mapping>

級聯(lián)

cascade 表示指定級聯(lián)操作的類型坏为。

  • save-update : 增加或更新 A 時(shí)究驴,自動(dòng)增加或更新 B。
  • delete : 刪除 A 時(shí)匀伏,自動(dòng)刪除 B
  • all : 上面兩項(xiàng)效果疊加
  • delete-orphan (孤兒刪除) : 刪除所有和當(dāng)前對象解除關(guān)聯(lián)關(guān)系的對象
  • all-delete-orphan : all + delete-orphan 效果疊加
<set name="courses" table="t_student_course" cascade="save-update">
    <key column="sid"></key>
    <many-to-many class="Course" column="cid" />
</set>

<a id="6"> </a>

6.抓取策略

檢索方式

  • 立即檢索:立即查詢洒忧,在執(zhí)行查詢語句時(shí),立即查詢所有的數(shù)據(jù)够颠。
  • 延遲檢索:延遲查詢熙侍,在執(zhí)行查詢語句之后,在需要時(shí)在查詢履磨。(懶加載)

檢索策略

  • 類級別檢索:當(dāng)前的類的屬性獲取是否需要延遲核行。
  • 關(guān)聯(lián)級別的檢索:當(dāng)前類 關(guān)聯(lián) 另一個(gè)類是否需要延遲。

類級別檢索

  • get:立即檢索蹬耘。get方法一執(zhí)行,立即查詢所有字段的數(shù)據(jù)减余。
  • load:延遲檢索综苔。默認(rèn)情況,load方法執(zhí)行后位岔,如果只使用OID的值不進(jìn)行查詢如筛,如果要使用其他屬性值將查詢∈闾В可以配置是否延遲檢索:
    <class  lazy="true | false">
    lazy 默認(rèn)值true杨刨,表示延遲檢索,如果設(shè)置false表示立即檢索擦剑。
    

關(guān)聯(lián)級別檢索

容器<set> 提供兩個(gè)屬性:fetch妖胀、lazy,用于控制關(guān)聯(lián)檢索惠勒。

  • fetch:確定使用sql格式
    • join:底層使用迫切左外連接
    • select:使用多個(gè)select語句(默認(rèn)值)
    • subselect:使用子查詢
  • lazy:關(guān)聯(lián)對象是否延遲赚抡。
    • false:立即
    • true:延遲(默認(rèn)值)
    • extra:極其懶惰,調(diào)用 size 時(shí)纠屋,sql 查詢 count涂臣。(用于只需要獲取個(gè)數(shù)的時(shí)候)

批量查詢

一次加載多行數(shù)據(jù),用于減少 sql 語句數(shù)量
<set batch-size="5">

比如: 當(dāng)客戶關(guān)聯(lián)查詢訂單時(shí)售担,默認(rèn)給每一個(gè)客戶生產(chǎn)一個(gè)select語句查詢訂單赁遗。開啟批量查詢后,使用in語句減少查詢訂單語句個(gè)數(shù)族铆。

默認(rèn):select * from t_order where customer_id = ?
批量:select * from t_order where customer_id in (?,?,?,?)

檢索總結(jié)

檢索策略| 優(yōu)點(diǎn)| 缺點(diǎn)| 優(yōu)先考慮使用的場合
----| ----| ----
立即檢索| 對應(yīng)用程序完全透明岩四,不管對象處于持久化狀態(tài)還是游離狀態(tài),應(yīng)用程序都可以從一個(gè)對象導(dǎo)航到關(guān)聯(lián)的對象| (1)select語句多
(2)可能會(huì)加載應(yīng)用程序不需要訪問的對象骑素,浪費(fèi)許多內(nèi)存空間炫乓。| (1)類級別
(2)應(yīng)用程序需要立即訪問的對象
(3)使用了二級緩存
延遲檢索| 由應(yīng)用程序決定需要加載哪些對象刚夺,可以避免執(zhí)行多余的select語句,以及避免加載應(yīng)用程序不需要訪問的對象末捣。因此能提高檢索性能侠姑,并節(jié)省內(nèi)存空間。| 應(yīng)用程序如果希望訪問游離狀態(tài)的代理類實(shí)例箩做,必須保證她在持久化狀態(tài)時(shí)已經(jīng)被初始化莽红。| (1)一對多或者多對多關(guān)聯(lián)
(2)應(yīng)用程序不需要立即訪問或者根本不會(huì)訪問的對象
表連接檢索| (1)對應(yīng)用程序完全透明,不管對象處于持久化狀態(tài)還是游離狀態(tài)邦邦,都可從一個(gè)對象導(dǎo)航到另一個(gè)對象安吁。
(2)使用了外連接,select語句少|(zhì) (1)可能會(huì)加載應(yīng)用程序不需要訪問的對象燃辖,浪費(fèi)內(nèi)存鬼店。
(2)復(fù)雜的數(shù)據(jù)庫表連接也會(huì)影響檢索性能。| (1)多對一或一對一關(guān)聯(lián)
(2)需要立即訪問的對象
(3)數(shù)據(jù)庫有良好的表連接性能黔龟。

<a id="7"> </a>

7.HQL

查詢所有

//1  使用簡單類名 妇智, 存在自動(dòng)導(dǎo)包
// * Customer.hbm.xml <hibernate-mapping auto-import="true">
//  Query query = session.createQuery("from Customer");
//2 使用全限定類名
Query query = session.createQuery("from com.ittianyu.bean.Customer");
// 獲取結(jié)果
List<Customer> allCustomer = query.list();

條件查詢

//1 指定數(shù)據(jù),cid OID名稱
//  Query query = session.createQuery("from Customer where cid = 1");
//2 如果使用id氏身,也可以(了解)
//  Query query = session.createQuery("from Customer where id = 1");
//3 對象別名 ,格式: 類 [as] 別名
//  Query query = session.createQuery("from Customer as c where c.cid = 1");
//4 查詢所有項(xiàng)巍棱,mysql--> select * from...
Query query = session.createQuery("select c from Customer as c where c.cid = 1");

Customer customer = (Customer) query.uniqueResult();

投影查詢

//1 默認(rèn)
//如果單列 ,select c.cname from蛋欣,需要List<Object>
//如果多列航徙,select c.cid,c.cname from ,需要List<Object[]>  ,list存放每行陷虎,Object[]多列
//  Query query = session.createQuery("select c.cid,c.cname from Customer c");
//2 將查詢部分?jǐn)?shù)據(jù)到踏,設(shè)置Customer對象中
// * 格式:new Customer(c.cid,c.cname)
// * 注意:Customer必須提供相應(yīng)的構(gòu)造方法。
// * 如果投影使用oid尚猿,結(jié)果脫管態(tài)對象夭禽。
Query query = session.createQuery("select new Customer(c.cid,c.cname) from Customer c");

List<Customer> allCustomer = query.list();

排序

Query query = session.createQuery("from Customer order by cid desc");
List<Customer> allCustomer = query.list();

分頁

Query query = session.createQuery("from Customer");
// * 開始索引 , startIndex 算法: startIndex = (pageNum - 1) * pageSize;
// *** pageNum 當(dāng)前頁(之前的 pageCode)
query.setFirstResult(0);
// * 每頁顯示個(gè)數(shù) , pageSize
query.setMaxResults(2);

List<Customer> allCustomer = query.list();

綁定參數(shù)

Integer cid = 1;

//方式1 索引 從 0 開始
//  Query query = session.createQuery("from Customer where cid = ?");
//  query.setInteger(0, cid);
//方式2 別名引用 (:別名)
Query query = session.createQuery("from Customer where cid = :xxx");
//  query.setInteger("xxx", cid);
query.setParameter("xxx", cid);

Customer customer = (Customer) query.uniqueResult();

聚合函數(shù)和分組

//1 
//  Query query = session.createQuery("select count(*) from Customer");
//2 別名
//  Query query = session.createQuery("select count(c) from Customer c");
//3 oid
Query query = session.createQuery("select count(cid) from Customer");

Long numLong = (Long) query.uniqueResult();

連接查詢

//左外連接
//  List list = session.createQuery("from Customer c left outer join c.orderSet ").list();
//迫切左外鏈接 (默認(rèn)數(shù)據(jù)重復(fù))
//  List list = session.createQuery("from Customer c left outer join fetch c.orderSet ").list();
//迫切左外鏈接 (去重復(fù))
List list = session.createQuery("select distinct c from Customer c left outer join fetch c.orderSet ").list();

命名查詢

Custom.hbm.xml

...
    <!--局部 命名查詢-->
    <query name="findAll"><![CDATA[from Customer ]]></query>
</class>
<!--全局 命名查詢-->
<query name="findAll"><![CDATA[from Customer ]]></query>

測試

//全局
//List list = session.getNamedQuery("findAll").list();
//局部
List list = session.getNamedQuery("com.ittianyu.a_init.Customer.findAll").list();

<a id="8"> </a>

8.QBC

查詢所有

List<Customer> list = session.createCriteria(Customer.class).list();

分頁查詢

Criteria criteria = session.createCriteria(Order.class);
criteria.setFirstResult(10);
criteria.setMaxResults(10);
List<Order> list = criteria.list();

排序

Criteria criteria = session.createCriteria(Customer.class);
//      criteria.addOrder(org.hibernate.criterion.Order.asc("age"));
criteria.addOrder(org.hibernate.criterion.Order.desc("age"));
List<Customer> list = criteria.list();

條件查詢

// 按名稱查詢:
/*Criteria criteria = session.createCriteria(Customer.class);
criteria.add(Restrictions.eq("cname", "tom"));
List<Customer> list = criteria.list();*/

// 模糊查詢;
/*Criteria criteria = session.createCriteria(Customer.class);
criteria.add(Restrictions.like("cname", "t%"));
List<Customer> list = criteria.list();*/

// 條件并列查詢
Criteria criteria = session.createCriteria(Customer.class);
criteria.add(Restrictions.like("cname", "t%"));
criteria.add(Restrictions.ge("age", 35));
List<Customer> list = criteria.list();

離線查詢

// service 層 封裝與 session 無關(guān)的 criteria
DetachedCriteria detachedCriteria = DetachedCriteria.forClass(Customer.class);
detachedCriteria.add(Restrictions.eq("id", 4));

// dao 層
Session session = HibernateUtils.openSession();
Criteria criteria = detachedCriteria.getExecutableCriteria(session);
List list = criteria.list();

<a id="9"> </a>

9.其他配置

c3p0(spring 整合后直接配 dataSource)

  1. 導(dǎo)入 c3p0 包
  2. hibernate.cfg.xml 配置
    <property name="hibernate.connection.provider_class">org.hibernate.connection.C3P0ConnectionProvider</property>
    

log4j

  1. 導(dǎo)入包
    • log4j 核心包:log4j-1.2.17.jar
    • 過渡jar:slf4j-log4j12-1.7.5.jar
  2. 導(dǎo)入配置文件
    • log4j.properties 谊路,此配置文件通知log4j 如何輸出日志

<a id="10"> </a>

10.事務(wù)

隔離級別

  • read uncommittd讹躯,讀未提交。存在3個(gè)問題缠劝。
  • read committed潮梯,讀已提交。解決:臟讀惨恭。存在2個(gè)問題秉馏。
  • repeatable read ,可重復(fù)讀脱羡。解決:臟讀萝究、不可重復(fù)讀免都。存在1個(gè)問題。
  • serializable帆竹,串行化绕娘。單事務(wù)。沒有問題栽连。

hibernate 中配置

<property name="hibernate.connection.isolation">4</property>

對照上面的分別是 1 2 4 8险领,0表示沒有事務(wù)級別

悲觀鎖

采用數(shù)據(jù)庫鎖機(jī)制。丟失更新肯定會(huì)發(fā)生秒紧。

  • 讀鎖:共享鎖绢陌。
    select .... from  ... lock in share mode;
    
  • 寫鎖:排他鎖。(獨(dú)占)
    select ... from  ....  for update
    

Hibernate 中使用

Customer customer = (Customer) session.get(Customer.class, 1 ,LockMode.UPGRADE);

樂觀鎖

在表中提供一個(gè)字段(版本字段)熔恢,用于標(biāo)識(shí)記錄脐湾。如果版本不一致,不允許操作叙淌。丟失更新肯定不會(huì)發(fā)生

Hibernate 中使用

  1. 在PO對象(javabean)提供字段沥割,表示版本字段。
    ...
    private Integer version;
    ...
    
  2. 在配置文件中增加 version
    <class ...>
        ...
        <version name="version" />
        ...
    

<a id="11"> </a>

11.二級緩存

sessionFactory 級別緩存凿菩,整個(gè)應(yīng)用程序共享一個(gè)會(huì)話工廠,共享一個(gè)二級緩存帜讲。

由4部分構(gòu)成:

  • 類級別緩存
  • 集合級別緩存
  • 時(shí)間戳緩存
  • 查詢緩存(二級緩存的第2大部分,三級緩存)

并發(fā)訪問策略

||
---|---
transactional| 可以防止臟讀和不可重復(fù)讀衅谷,性能低
read-write| 可以防止臟讀,更新緩存時(shí)鎖定緩存數(shù)據(jù)
nonstrict-read-write| 不保證緩存和數(shù)據(jù)庫一致似将,為緩存設(shè)置短暫的過期時(shí)間获黔,減少臟讀
read-only| 適用于不會(huì)被修改的數(shù)據(jù),并發(fā)性能高

應(yīng)用場景

  • 適合放入二級緩存中的數(shù)據(jù):
    很少被修改
    不是很重要的數(shù)據(jù), 允許出現(xiàn)偶爾的并發(fā)問題
  • 不適合放入二級緩存中的數(shù)據(jù):
    經(jīng)常被修改
    財(cái)務(wù)數(shù)據(jù), 絕對不允許出現(xiàn)并發(fā)問題
    與其他應(yīng)用數(shù)據(jù)共享的數(shù)據(jù)

二級緩存提供商

  • EHCache: 可作為進(jìn)程(單機(jī))范圍內(nèi)的緩存, 存放數(shù)據(jù)的物理介質(zhì)可以是內(nèi)存或硬盤, 對 Hibernate 的查詢緩存提供了支持在验。--支持集群玷氏。
  • OpenSymphony `:可作為進(jìn)程范圍內(nèi)的緩存, 存放數(shù)據(jù)的物理介質(zhì)可以是內(nèi)存或硬盤, 提供了豐富的緩存數(shù)據(jù)過期策略, 對 Hibernate 的查詢緩存提供了支持
  • SwarmCache: 可作為集群范圍內(nèi)的緩存, 但不支持 Hibernate 的查詢緩存
  • JBossCache:可作為集群范圍內(nèi)的緩存, 支持 Hibernate 的查詢緩存

開啟二級緩存

  1. 導(dǎo)包 hibernate-ehcache-5.2.8.Final.jar
  2. 配置
    <!--二級緩存
    #hibernate.cache.region.factory_class org.hibernate.cache.internal.EhCacheRegionFactory
    -->
    <property name="hibernate.cache.use_second_level_cache">true</property>
    <property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property>
    

使用二級緩存

類緩存

<!--
類緩存
-->
<class-cache class="com.ittianyu.hibernate.onetomany.Order" usage="read-only"/>
<class-cache class="com.ittianyu.hibernate.onetomany.Customer" usage="read-only"/>

集合緩存

<collection-cache collection="com.ittianyu.hibernate.onetomany.Customer.orders" usage="read-only" />

查詢緩存

將HQL語句 與 查詢結(jié)果進(jìn)行綁定。通過HQL相同語句可以緩存內(nèi)容腋舌。

  1. 配置

    #hibernate.cache.use_query_cache true 啟用 HQL查詢緩存
    <property name="hibernate.cache.use_query_cache">true</property>
    
  2. 使用

    Query query = session.createQuery("from Customer");
    query.setCacheable(true);// 標(biāo)記為緩存
    
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末盏触,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子块饺,更是在濱河造成了極大的恐慌赞辩,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件授艰,死亡現(xiàn)場離奇詭異辨嗽,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)淮腾,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門糟需,熙熙樓的掌柜王于貴愁眉苦臉地迎上來屉佳,“玉大人,你說我怎么就攤上這事洲押∥浠ǎ” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵诅诱,是天一觀的道長髓堪。 經(jīng)常有香客問我,道長娘荡,這世上最難降的妖魔是什么干旁? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮炮沐,結(jié)果婚禮上争群,老公的妹妹穿的比我還像新娘。我一直安慰自己大年,他們只是感情好换薄,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著翔试,像睡著了一般轻要。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上垦缅,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天冲泥,我揣著相機(jī)與錄音,去河邊找鬼壁涎。 笑死凡恍,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的怔球。 我是一名探鬼主播嚼酝,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼竟坛!你這毒婦竟也來了闽巩?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤担汤,失蹤者是張志新(化名)和其女友劉穎又官,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體漫试,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡六敬,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了驾荣。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片外构。...
    茶點(diǎn)故事閱讀 38,161評論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡普泡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出审编,到底是詐尸還是另有隱情撼班,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布垒酬,位于F島的核電站砰嘁,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏勘究。R本人自食惡果不足惜矮湘,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望口糕。 院中可真熱鬧缅阳,春花似錦、人聲如沸景描。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽超棺。三九已至向族,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間棠绘,已是汗流浹背件相。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留弄唧,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓霍衫,卻偏偏與公主長得像候引,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子敦跌,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評論 2 344

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

  • Hibernate: 一個(gè)持久化框架 一個(gè)ORM框架 加載:根據(jù)特定的OID,把一個(gè)對象從數(shù)據(jù)庫加載到內(nèi)存中OID...
    JHMichael閱讀 1,958評論 0 27
  • 本文中我們介紹并比較兩種最流行的開源持久框架:iBATIS和Hibernate柠傍,我們還會(huì)討論到Java Persi...
    大同若魚閱讀 4,302評論 4 27
  • 目錄 1. Hibernate框架的概述 1.1 Hibernate簡介 1.2 為什么要學(xué)習(xí)Hibernate ...
    深海魚Q閱讀 1,015評論 0 14
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法麸俘,類相關(guān)的語法,內(nèi)部類的語法惧笛,繼承相關(guān)的語法从媚,異常的語法,線程的語...
    子非魚_t_閱讀 31,587評論 18 399
  • 暮 睡在技頭的那片綠葉夏 時(shí)間的長河把季節(jié)輪回 那片綠葉在命運(yùn)輪回里 生生息息患整,誰也說不清楚 它輪回了多少個(gè)日日夜...
    像我一樣帥閱讀 279評論 0 0