Android數(shù)據(jù)庫(kù)

Android數(shù)據(jù)庫(kù)

什么情況下我們才用數(shù)據(jù)庫(kù)做數(shù)據(jù)存儲(chǔ)?

大量數(shù)據(jù)結(jié)構(gòu)相同的數(shù)據(jù)需要存儲(chǔ)時(shí)了袁。Android內(nèi)置了sqlite坦仍,輕量級(jí)。

創(chuàng)建數(shù)據(jù)庫(kù)的方法

  1. 創(chuàng)建一個(gè)類繼承SqliteOpenHelper得糜,需要添加一個(gè)構(gòu)造方法敬扛,實(shí)現(xiàn)兩個(gè)方法oncreate ,onupgrade。
package com.example.databasedemo;


import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

public class MyDatabaseOpenHelper extends SQLiteOpenHelper {

    public static final String CREATE_BOOK = "create table book ("
            + "_id integer primary key autoincrement, name varchar(20), telephone varchar(11))";
  
    public static final String NEW_TABLE = "create table book ("
            + "_id integer primary key autoincrement, price real, pages integer)";
    private Context mContext;

    /**
     *
     * @param context 上下文
     * @param name    數(shù)據(jù)庫(kù)的名稱
     * @param factory 用來(lái)創(chuàng)建cursor對(duì)象朝抖,填入null使用默認(rèn)的
     * @param version version:數(shù)據(jù)庫(kù)的版本號(hào)啥箭,從1開(kāi)始,如果發(fā)生改變槽棍,onUpgrade方法將會(huì)調(diào)用,4.0之后只能升不能降
     */
    public MyDatabaseOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
        super(context, name, factory, version);
        mContext = context;
    }
    // 調(diào)用getReadableDatabase()或者getWritableDatabase()時(shí)會(huì)調(diào)用該方法
    // 第一次創(chuàng)建數(shù)據(jù)庫(kù)時(shí)才能執(zhí)行該方法捉蚤,特別適合做表結(jié)構(gòu)的初始化
    // 傳入的參數(shù)db可以用來(lái)執(zhí)行sql語(yǔ)句
    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(CREATE_BOOK);
        db.execSQL(NEW_TABLE); // 另外一個(gè)表
    }

    // version改變時(shí),調(diào)用這個(gè)方法炼七,version只能升不能降
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        // 先刪除缆巧,不刪除就onCreate發(fā)現(xiàn)表存在會(huì)報(bào)錯(cuò)。
      // 若是表被刪除了豌拙,或者oncreate里面又新建一個(gè)表陕悬。因?yàn)橹耙呀?jīng)創(chuàng)建了aa.db,onCreate()方法不會(huì)得到執(zhí)行按傅。則不能創(chuàng)建成功捉超。所以這里需要?jiǎng)h除后再重建
        db.execSQL("drop table if exists book");
        db.execSQL("drop table if exists people");
      // 強(qiáng)制執(zhí)行onCreate
        onCreate(db);
        db.execSQL("alter table book add author varchar(20)");
        db.execSQL("alter table people add age integer");

    }
}
 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mContext = this;
        // 1.創(chuàng)建一個(gè)幫助類的對(duì)象胧卤,調(diào)用getReadableDatabase方法,返回一個(gè)SqliteDatebase對(duì)象
        MyDatabaseOpenHelper dbHelper = new MyDatabaseOpenHelper(mContext, "demo.db", null, 15);
        // 創(chuàng)建數(shù)據(jù)庫(kù)拼岳,有則打開(kāi)枝誊,沒(méi)有則create
        dbHelper.getReadableDatabase();
    }

幫助類對(duì)象中的getWritableDatabase 和 getReadableDatabase都可以幫助我們獲取一個(gè)數(shù)據(jù)庫(kù)操作對(duì)象SqliteDatabase。

區(qū)別:

  • getReadableDatabase: 先嘗試以讀寫方式打開(kāi)數(shù)據(jù)庫(kù)惜纸,如果磁盤空間滿了叶撒,他會(huì)重新嘗試以只讀方式打開(kāi)數(shù)據(jù)庫(kù)。
  • getWritableDatabase: 直接以讀寫方式打開(kāi)數(shù)據(jù)庫(kù)耐版,如果磁盤空間滿了祠够,就直接報(bào)錯(cuò)。

數(shù)據(jù)庫(kù)的CURD - 1

  • 對(duì)上面建的book表進(jìn)行CURD
  • 使用上面的MyDatabaseOpenHelper創(chuàng)建數(shù)據(jù)庫(kù)和表粪牲。
  • 封裝一個(gè)InfoBean來(lái)存儲(chǔ)表的信息古瓤。
  • 封裝一個(gè)InfoDao來(lái)返回一個(gè)MyDatabaseOpenHelper,以及執(zhí)行增刪改查操作腺阳。

bean用來(lái)封裝表的數(shù)據(jù)落君。

package com.example.databasedemo.dao;
// 這些變量名和建表時(shí)候字段對(duì)應(yīng)
public class InfoBean {
    public int _id;
    public String name;
    public int age;
    public String telephone;
}

封裝好的執(zhí)行增刪改查的類,注意db不要隨便db.close亭引,容易引發(fā)錯(cuò)誤叽奥。

package  com.example.databasedemo.dao;

import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;

import com.example.databasedemo.MyDatabaseOpenHelper;

public class InfoDao {

    private MyDatabaseOpenHelper myDatabaseOpenHelper;
    private SQLiteDatabase db;
  
    public InfoDao(Context context ,String dbName , int version){
        //創(chuàng)建一個(gè)幫助類對(duì)象
        myDatabaseOpenHelper = new MyDatabaseOpenHelper(context, dbName, null, version);
        db = myDatabaseOpenHelper.getReadableDatabase();
    }

    public void add(InfoBean bean){
        //sql:sql語(yǔ)句,  bindArgs:sql語(yǔ)句中占位符的值
        db.execSQL("insert into people(name,telephone) values(?,?);", new Object[]{bean.name,bean.telephone});
    }

    public void del(String name){
        //sql:sql語(yǔ)句痛侍,  bindArgs:sql語(yǔ)句中占位符的值
        db.execSQL("delete from people where name=?;", new Object[]{name});
    }
    public void update(InfoBean bean){
        //sql:sql語(yǔ)句朝氓,  bindArgs:sql語(yǔ)句中占位符的值
        db.execSQL("update people set telephone=? where name=?;", new Object[]{bean.telephone, bean.name});

    }
    public void query(String name){
        //原始查詢 --> sql:sql語(yǔ)句,  selectionArgs:查詢條件占位符的值,返回一個(gè)cursor對(duì)象
        Cursor cursor = db.rawQuery("select _id, name, telephone from people where name = ?;", new String []{name});
        //解析Cursor中的數(shù)據(jù)
        if(cursor != null && cursor.getCount() >0){//判斷cursor中是否存在數(shù)據(jù)

            //循環(huán)遍歷結(jié)果集主届,獲取每一行的內(nèi)容
            while(cursor.moveToNext()){ //條件赵哲,游標(biāo)能否定位到下一行
                //獲取數(shù)據(jù)
                int id = cursor.getInt(cursor.getColumnIndex("_id"));
                String name_str = cursor.getString(cursor.getColumnIndex("name"));
                String phone = cursor.getString(cursor.getColumnIndex("telephone"));
                Log.d("query result", "[ _id:"+id+" ,name:"+name_str+" ,phone:"+phone+" ]");
            }
            cursor.close();//關(guān)閉結(jié)果集
        }
    }
}

布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_margin="16dp"
    tools:context="com.example.databasedemo.MainActivity">

    <Button
        android:id="@+id/bt_add"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/add"/>
    <Button
        android:id="@+id/bt_update"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/update"/>
    <Button
        android:id="@+id/bt_del"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/del"/>
    <Button
        android:id="@+id/bt_query"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/query"/>

</LinearLayout>

MainActivity

package com.example.databasedemo;

import android.content.Context;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;

import  com.example.databasedemo.dao.InfoBean;
import  com.example.databasedemo.dao.InfoDao;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private Context mContext;
    private InfoDao infoDao;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mContext = this;
        // 創(chuàng)建數(shù)據(jù)庫(kù),有則打開(kāi)君丁,沒(méi)有則create
        infoDao = new InfoDao(mContext, "test.db", 3);
      
        Button btAdd = (Button) findViewById(R.id.bt_add);
        Button btDel = (Button) findViewById(R.id.bt_del);
        Button btUpdate = (Button) findViewById(R.id.bt_update);
        Button btQuery = (Button) findViewById(R.id.bt_query);

        btAdd.setOnClickListener(this);
        btDel.setOnClickListener(this);
        btUpdate.setOnClickListener(this);
        btQuery.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        InfoBean bean = null;
        InfoBean bean1 = null;
        switch (v.getId()) {
            case R.id.bt_add:

                bean = new InfoBean();
                bean.name = "張三";
                bean.telephone = "119";
                infoDao.add(bean);

                bean1 = new InfoBean();
                bean1.name = "李四";
                bean1.telephone = "120";
                infoDao.add(bean1);
                break;

            case R.id.bt_del:

                infoDao.del("張三");
                break;

            case R.id.bt_update:

                bean = new InfoBean();
                bean.name = "張三";
                bean.telephone = "110";
                infoDao.update(bean);
                break;

            case R.id.bt_query:
                infoDao.query("張三");
                infoDao.query("李四");
                break;

            default:
                break;
        }
    }
}

上面的方法基本是手寫sql語(yǔ)句枫夺,容易寫錯(cuò)。還有一種更便捷的方法 绘闷。

數(shù)據(jù)庫(kù)的CURD -2

使用db.insert()橡庞、db.delete()、db.update()印蔗、db.query()

package dao;


import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;

import com.example.databasedemo.MyDatabaseOpenHelper;

public class InfoDao {

    private MyDatabaseOpenHelper myDatabaseOpenHelper;
    private SQLiteDatabase db;
    
    public InfoDao(Context context ,String dbName , int version){
        //創(chuàng)建一個(gè)幫助類對(duì)象
        myDatabaseOpenHelper = new MyDatabaseOpenHelper(context, dbName, null, version);
        db = myDatabaseOpenHelper.getReadableDatabase();
    }

    public boolean add(InfoBean bean){

        //執(zhí)行sql語(yǔ)句需要sqliteDatabase對(duì)象
        ContentValues values = new ContentValues(); // 是用Map封裝的對(duì)象
        values.put("name", bean.name);
        values.put("telephone", bean.telephone);
        // 第二個(gè)參數(shù)可以為空,返回值表示新增的行號(hào)扒最,-1表示添加失敗
        long result =  db.insert("people", null, values);

        return result != -1;
    }

    public int del(String name){
        //執(zhí)行sql語(yǔ)句需要sqliteDatabase對(duì)象
  
        int count = db.delete("people", "name = ?", new String[]{name});
        return count;
    }

    public int update(InfoBean bean){
        ContentValues values = new ContentValues();
        values.put("telephone", bean.telephone);
        int count = db.update("people", values, "name = ?", new String[]{bean.name});
        return count;
    }
    public void query(String name){
        // 查詢people表中的name為參數(shù)指定的"_id", "name", "telephone"字段,按照id遞減
        Cursor cursor = db.query("people", new String[]{"_id", "name", "telephone"}, "name = ?", new String[]{name}, null, null, "_id desc");

        //解析Cursor中的數(shù)據(jù)
        if(cursor != null && cursor.getCount() >0){//判斷cursor中是否存在數(shù)據(jù)

            //循環(huán)遍歷結(jié)果集华嘹,獲取每一行的內(nèi)容
            while(cursor.moveToNext()){ //條件吧趣,游標(biāo)能否定位到下一行
                //獲取數(shù)據(jù)
                int id = cursor.getInt(cursor.getColumnIndex("_id"));
                String name_str = cursor.getString(cursor.getColumnIndex("name"));
                String phone = cursor.getString(cursor.getColumnIndex("telephone"));
                Log.d("query result", "[ _id:"+id+" ,name:"+name_str+" ,phone:"+phone+" ]");
            }
            cursor.close();//關(guān)閉結(jié)果集
        }
    }
}
@Override
public void onClick(View v) {
    InfoBean bean = null;
    int count = 0;
    switch (v.getId()) {
        case R.id.bt_add:

            bean = new InfoBean();
            bean.name = "張三";
            bean.telephone = 119;
            boolean result = infoDao.add(bean);
            if (result) {
                Toast.makeText(mContext, "添加成功", Toast.LENGTH_SHORT).show();
            }
            break;
        case R.id.bt_del:

            count = infoDao.del("張三");
            Toast.makeText(mContext, "刪除"+count+"行", Toast.LENGTH_SHORT).show();
            break;

        case R.id.bt_update:
            bean = new InfoBean();
            bean.name = "張三";
            bean.telephone = 110;
            count = infoDao.update(bean);
            Toast.makeText(mContext, "更新"+count+"行", Toast.LENGTH_SHORT).show();
            break;

        case R.id.bt_query:
            infoDao.query("張三");
            break;

        default:
            break;
    }
}

使用以上方法不容易寫錯(cuò)sql語(yǔ)句,而且其返回值能方便地知道數(shù)據(jù)變化了幾條。

使用第二種方法更簡(jiǎn)單一些强挫,但是不能多表查詢(傳參時(shí)候只能傳入一個(gè)table)岔霸。而第一種手寫rawQuery()的方法可以實(shí)現(xiàn)「┎常可謂各有利弊呆细。

數(shù)據(jù)庫(kù)中的事務(wù)

執(zhí)行多條sql語(yǔ)句,要么同時(shí)執(zhí)行成功八匠,要么同時(shí)執(zhí)行失敗侦鹏。不能有的成功,有的失敗臀叙。失敗了則會(huì)回滾。

舉個(gè)銀行轉(zhuǎn)賬的例子价卤。因?yàn)楦鞣N原因比如在轉(zhuǎn)賬過(guò)程中突然斷電斷網(wǎng)劝萤,不能使得資金流失。李四個(gè)張三轉(zhuǎn)200慎璧,張三要么收到兩百床嫌。要么退還給李四(回滾到未轉(zhuǎn)錢的時(shí)候)。

還是需要一個(gè)繼承自SQLiteOpenHelper的類胸私,不過(guò)這次的比較簡(jiǎn)單了厌处。

package com.example.trancaction;


import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

public class MyDatabaseOpenHelper extends SQLiteOpenHelper {

    /**
     * @param context 上下文
     * @param name    數(shù)據(jù)庫(kù)的名稱
     * @param factory 用來(lái)創(chuàng)建cursor對(duì)象,填入null使用默認(rèn)的
     * @param version version:數(shù)據(jù)庫(kù)的版本號(hào)岁疼,從1開(kāi)始阔涉,如果發(fā)生改變,onUpgrade方法將會(huì)調(diào)用,4.0之后只能升不能降
     */
    public MyDatabaseOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
        super(context, name, factory, version);
    }
    
  // 直接在創(chuàng)建表的時(shí)候就添加數(shù)據(jù)
    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL("create table account (_id integer primary key autoincrement,name varchar(20),money varchar(20))");
        db.execSQL("insert into account ('name','money') values ('張三','2000')");
        db.execSQL("insert into account ('name','money') values ('李四','5000')");
    }
  
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        // TODO: 2017/4/11
    }
}

package com.example.trancaction;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity {

    private Context mContext;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mContext = this;
        
        Button btTransfer = (Button) findViewById(R.id.bt_transfer);
        btTransfer.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                MyDatabaseOpenHelper dbHelper = new MyDatabaseOpenHelper(mContext, "account.db", null, 1);
                SQLiteDatabase db = dbHelper.getReadableDatabase();
                //3.轉(zhuǎn)賬,將李四的錢減200捷绒,張三加200
                db.beginTransaction();//開(kāi)啟一個(gè)數(shù)據(jù)庫(kù)事務(wù)
                try {
                    // 如果沒(méi)有事務(wù)瑰排,這里只會(huì)執(zhí)行李四的錢轉(zhuǎn)出,張三收不到暖侨。
                    db.execSQL("update account set money= money-200 where name=?", new String[]{"李四"});
                    int i = 100 / 0;//模擬一個(gè)異常
                    db.execSQL("update account set money= money+200 where name=?", new String[]{"張三"});

                    db.setTransactionSuccessful();//能運(yùn)行到最后這兒椭住,就標(biāo)記事務(wù)中的sql語(yǔ)句全部成功執(zhí)行
                } finally {
                    db.endTransaction();//判斷事務(wù)的標(biāo)記是否成功,如果不成功字逗,回滾錯(cuò)誤之前執(zhí)行的sql語(yǔ)句
                }
            }
        });
    }
}


by @sunhaiyu

2017.4.13

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末京郑,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子葫掉,更是在濱河造成了極大的恐慌些举,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,743評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件俭厚,死亡現(xiàn)場(chǎng)離奇詭異金拒,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門绪抛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)资铡,“玉大人,你說(shuō)我怎么就攤上這事幢码◇孕荩” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 157,285評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵症副,是天一觀的道長(zhǎng)店雅。 經(jīng)常有香客問(wèn)我,道長(zhǎng)贞铣,這世上最難降的妖魔是什么闹啦? 我笑而不...
    開(kāi)封第一講書人閱讀 56,485評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮辕坝,結(jié)果婚禮上窍奋,老公的妹妹穿的比我還像新娘。我一直安慰自己酱畅,他們只是感情好琳袄,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,581評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著纺酸,像睡著了一般窖逗。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上餐蔬,一...
    開(kāi)封第一講書人閱讀 49,821評(píng)論 1 290
  • 那天碎紊,我揣著相機(jī)與錄音,去河邊找鬼樊诺。 笑死矮慕,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的啄骇。 我是一名探鬼主播痴鳄,決...
    沈念sama閱讀 38,960評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼缸夹!你這毒婦竟也來(lái)了痪寻?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 37,719評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤虽惭,失蹤者是張志新(化名)和其女友劉穎橡类,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體芽唇,經(jīng)...
    沈念sama閱讀 44,186評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡顾画,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,516評(píng)論 2 327
  • 正文 我和宋清朗相戀三年取劫,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片研侣。...
    茶點(diǎn)故事閱讀 38,650評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡谱邪,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出庶诡,到底是詐尸還是另有隱情惦银,我是刑警寧澤,帶...
    沈念sama閱讀 34,329評(píng)論 4 330
  • 正文 年R本政府宣布末誓,位于F島的核電站扯俱,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏喇澡。R本人自食惡果不足惜迅栅,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,936評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望晴玖。 院中可真熱鬧读存,春花似錦、人聲如沸窜醉。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,757評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)榨惰。三九已至,卻和暖如春静汤,著一層夾襖步出監(jiān)牢的瞬間琅催,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,991評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工虫给, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留藤抡,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,370評(píng)論 2 360
  • 正文 我出身青樓抹估,卻偏偏與公主長(zhǎng)得像缠黍,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子药蜻,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,527評(píng)論 2 349

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