第一行代码读书笔记——数据存储全方案,持久化技术

  • 三种方式简单实现数据持久化功能

    • 文件存储

      • 简介
      • 将数据存储到文件中
      • 从文件中读取数据
      • 扩展StringStringBuilderStringBuffer
    • SharePreferences存储
      • 注意点
      • 获取SharePreferences对象的三种方式
        • Context类中的 getSharedPreferences方法
        • Activity类中的 getPreferences方法
        • PreferenceManager类中的 getDefaultSharedPreferences方法
      • 向SharedPreferences文件中存储数据三步骤
      • 从Sharedpreferences中读取数据
      • 实践记住密码功能
    • SQLite 数据库存储一款轻量级的关系型数据库
      • 创建数据库SQLiteOpenHelper帮助类

        • SQLiteOpenHelper 抽象类
        • 用adb查看数据库
        • 升级数据库onUpgrade
        • 添加数据 insert
        • 更新数据 update
        • 删除数据 delete
        • 查询数据query
        • 使用SQL操作数据库dbexecSQL
      • 数据库的最佳实践
        • 使用事务
        • 升级数据库的最佳写法
      • 实际项目中可以考虑这样

三种方式简单实现数据持久化功能

  1. 文件存储
  2. SharedPreference存储
  3. 数据库存储

文件存储

简介

文件存储是 Android中最基本的一种数据存储方式,它不对存储的内容进行任何的格式

化处理,所有数据都是原封不动地保存到文件当中的,因而它比较适合用于存储一些简单的

文本数据或二进制数据。

将数据存储到文件中

Context类: openFileOutput ()

可以用于将数据存储到指定的文件中。

两个参数,第一个参数是文件名,指定的文件名不可以包含路径,因为所有的文件都是默认存到/data/data/package name/files/目录下的。

第二个参数是文件的操作模式,主要有两种模式可选,MODE_PRIVATE 和 MODE_APPEND。其中 MODE_PRIVATE是默认的操作模式,表示当指定同样文件名的时候,所写入的内容将会覆盖原文件中的内容,而 MODE_APPEND则表示如果该文件已存在就往文件里面追加内容,不存在就创建新文件。

其实文件的操作模式本来还有另外两种,MODE_WORLD_READABLE 和 MODE_WORLD_WRITEABLE,这两种模

式表示允许其他的应用程序对我们程序中的文件进行读写操作,不过由于这两种模式过于危

险,很容易引起应用的安全性漏洞,现已在 Android 4.2版本中被废弃。

openFileOutput ()方法返回的是一个 FileOutputStream对象,得到了这个对象之后就可以

使用 Java 流的方式将数据写入到文件中了。

public void save() {
    String data = "Data to save";
    FileOutputStream out = null;
    BufferedWriter writer = null;
    try {
        out = openFileOutput("data", Context.MODE_PRIVATE);
        writer = new BufferedWriter(new OutputStreamWriter(out));
        writer.write(data);
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
    try {
        if (writer != null) {
            writer.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
            }
        }
}

openFileOutput(name, mode)返回FileOutPutStream,

BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out));

writer.write(data);

以及最后要进行的关闭处理writer.close();

从文件中读取数据

Context类中还提供了一个 openFileInput()方法,

参数:文件名,系统会自动到/data/data//files/目录下去加载这个文件,并返回一个

FileInputStream对象

public String load() {
    FileInputStream in = null;
    BufferedReader reader = null;
    StringBuilder content = new StringBuilder();
    try {
        in = openFileInput("data");
        reader = new BufferedReader(new InputStreamReader(in));
        String line = "";
        while ((line = reader.readLine()) != null) {
            content.append(line);
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if (reader != null) {
            try {
                reader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    return content.toString();
}

openFileInput(name)->FileInputStream

StringBuilder content = new StringBuilder();

BufferedReader reader = new BufferedReader(new InputStreamReader(in));

String line = “”;

while ((line = reader.readLine()) != null) {

content.append(line);

}

reader.close();

content.toString();

扩展String、StringBuilder、StringBuffer

1.如果要操作少量的数据用 = String

2.单线程操作字符串缓冲区 下操作大量数据 = StringBuilder

3.多线程操作字符串缓冲区 下操作大量数据 = StringBuffer

StringBuffer与StringBuilder的区别主要是前者是线程安全的,就是说它是同步的;后者不安全,不是同步的,其它的区别不大。当你的程序不需要线程同步,一般都用StringBuilder.

StringBuffer与StringBuilder,他们是字符串变量,是可改变的对象,每当我们用它们对字符串做操作时,实际上是在一个对象上操作的,不像String一样创建一些对象进行操作,所以速度就快了。

String s = new String(“zzz”);
当你要改变s的时候,比如改为s =s+”dsdsdsd”;
系统会重新创建一个字符串变量它的值为”zzzdsdsdsd”,然后把该字符串赋值给s.
当你这样的改变s的行为在一个循环里面,那么将会创建大量的中间变量,影响程序的运行效率:
如:for(int i=0;i<100000;i++){
        s=”a”;
}

StringBuilder sb = new StringBuilder();
一次性给sb分配一个固定长度大小的内存空间,当你改变的时候会在此空间后面加上,不够的时候,内存空间自动增加.比如初始分配的内存大小为10字节,
那么.Sb.append(“as”);它占据内存空间10字节,此时sb.toString().equals(“as”)为true;
Sb.append.(“qqq”),它占据的内存空间还是10字节,sb.toString().equals(“asqqq”)为true.
当内存空间不够的时候,自动加长,加入一次增加10字节,那么:
Sb.append(“ppppppp”),它占据的内存空间为20字节.
StringBuilder允许设定它的初始长度和每次增加的长度。

SharePreferences存储

注意点

  1. 文件存储在:/data/data/包名/shared_prefs /目录下
  2. 以键值对的方式进行存储。

获取SharePreferences对象的三种方式

Context类中的 getSharedPreferences()方法

参数:文件名称、mode

1、文件名称:第一个参数用于指定 SharedPreferences 文件的名称,如果指

定的文件不存在则会创建一个,SharedPreferences 文件都是存放在/data/data/package name/shared_prefs/目录下的。

2、指定操作模式:MODE_PRIVATE (默认,和0等值)和 MODE_MULTI_PROCESS。

MODE_PRIVATE 表示只有当前的应用程序才可以对这个SharedPreferences文件进行读写。MODE_MULTI_PROCESS则一般是用于会有多个进程中对同一个 SharedPreferences文件进行读写的情况。

=》MODE_WORLD_READABLE和 MODE_WORLD_WRITEABLE 这两种模式已在 Android 4.2 版本中被废弃(安全问题)

Activity类中的 getPreferences()方法

和 Context 中的 getSharedPreferences()方法很相似,不过它只接收一个操作模式参数,因为使用这个方法时会自动将当前活动的类名作为 SharedPreferences的文件名。

PreferenceManager类中的 getDefaultSharedPreferences()方法

这是一个静态方法,它接收一个 Context 参数,并自动使用当前应用程序的包名作为前缀来命名 SharedPreferences文件。

向SharedPreferences文件中存储数据三步骤

  1. 调用 SharedPreferences对象的 edit()方法来获取一个 SharedPreferences.Editor 对象。
  2. 向 SharedPreferences.Editor 对象中添加数据,比如添加一个布尔型数据就使用

    putBoolean 方法,添加一个字符串则使用 putString()方法,以此类推。

  3. 调用 commit()方法将添加的数据提交,从而完成数据存储操作。

从Sharedpreferences中读取数据

SharedPreferences pref = getSharedPreferences("data", MODE_PRIVATE);
String name = pref.getString("name", "");

实践:记住密码功能

自己之前项目里面的登录代码,还包括了记住之前登录过的账户,模仿QQ那种,记住的方式正好是用文件,等于结合上面两点,直接从项目拿出来,参考。

protected SharedPreferences sp;
protected SharedPreferences.Editor editor;
@Override
    protected void onCreate(Bundle savedInstanceState) {
        // TODO Auto-generated method stub
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        initView();
        initDate();
    }

    private Handler myHandler = new Handler(){

        @Override
        public void handleMessage(Message msg) {
            // TODO Auto-generated method stub
            super.handleMessage(msg);
            warnDialog.dismissWarnDialog();
        }

    };

    private void initDate(){
        ObjectInputStream in = null;
        try {
            InputStream is = openFileInput("account.obj");
            in = new ObjectInputStream(is);
            mList = (ArrayList<String>) in.readObject();
            if (mList.size() > 0) {
            nameText.setText(mList.get(0));
            if(sp.getBoolean("isRemember", false)){
                pwdText.setText(sp.getString(nameText.getText().toString().trim(), ""));
                remPwd.setChecked(true);
            }
            }
        } catch (Exception e) {
                e.printStackTrace();
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
    }

    private void initPopup(){
        mAdapter = new NameListAdapter(LoginActivity.this, mList);
        mListView = new ListView(this);
        mListView.setAdapter(mAdapter);
        mListView.setOnItemClickListener(new OnItemClickListener() {

            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                // TODO Auto-generated method stub
                nameText.setText(mList.get(position));
                pwdText.setText("");
                mPopup.dismiss();
            }
        });  

        int height = ViewGroup.LayoutParams.WRAP_CONTENT;
        int width = pwdText.getWidth();
        System.out.println(width);
        mPopup = new PopupWindow(mListView, width, height, true);
        mPopup.setOutsideTouchable(true);
        mPopup.setBackgroundDrawable(getResources().getDrawable(
                R.drawable.rect_cycle_white_bg));
        mPopup.setOnDismissListener(new OnDismissListener() {

            @Override
            public void onDismiss() {
                // TODO Auto-generated method stub
                mShowing = false;
            }
        });
    }

    private void rememberName(){
        String input = nameText.getText().toString();
        mList.remove(input);
        mList.add(0,input);
        if (mList.size() > 5) {
            mList.remove(0);
        }
        ObjectOutputStream out = null;
        try {
            FileOutputStream os = openFileOutput("account.obj",
                    MODE_PRIVATE);
            out = new ObjectOutputStream(os);
            out.writeObject(mList);
        } catch (Exception e) {  

        } finally {
            if (out != null) {
                try {
                    out.close();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
    }

    @Override
    protected void showIndexDialog() {
        // TODO Auto-generated method stub
        dialog = new IndexDialog(LoginActivity.this);
        dialog.showDialog(R.layout.layout_index_button,  LoginActivity.this.findViewById(R.id.login_layout), LoginActivity.this, true);

    }

    @Override
    protected void quickStartClicked() {
        // TODO Auto-generated method stub

    }

    @Override
    protected void initView() {
        // TODO Auto-generated method stub

        nameEditLayout = (LinearLayout)findViewById(R.id.sr_username_edit_layout);
        pwdText = (EditText)findViewById(R.id.sr_password);
        nameText = (EditText)findViewById(R.id.sr_username);

        initDropBtn();
        initLoginBtn();

        nameText.setOnFocusChangeListener(new OnFocusChangeListener() {

            @Override
            public void onFocusChange(View v, boolean hasFocus) {
                // TODO Auto-generated method stub
                if(hasFocus){
                    nameEditLayout.setBackgroundResource(R.drawable.set_button_cir_white_border);
                }else{
                    nameEditLayout.setBackgroundResource(R.drawable.rect_cycle_edit_white_gray);
                }
            }
        });

        remPwd = (CheckBox)findViewById(R.id.sr_remember);

        findPwd = (TextView)findViewById(R.id.sr_reget_psw);
        findPwd.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub
                gotoActivity(LoginActivity.this, ForgotPwdActivity.class);
                LoginActivity.this.finish();
            }
        });

        rgBtn = (Button)findViewById(R.id.sr_register);
        rgBtn.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub
                gotoActivity(LoginActivity.this, RegisterActivity.class);
                LoginActivity.this.finish();
            }
        });

    }

    private void initDropBtn(){
        showDrowBtn = (ImageView)findViewById(R.id.show_drop);
        showDrowBtn.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub
                if(mList!=null && mList.size()>0 && !mInitPopup){
                    mInitPopup = true;
                    initPopup();
                }
                if (mPopup != null) {
                    if (!mShowing) {
                        mPopup.showAsDropDown(nameText,0,-5);
                        mShowing  = true;
                    } else {
                        mPopup.dismiss();
                    }
                }
            }
        });
    }

    private void initLoginBtn(){
        loginBtn = (Button)findViewById(R.id.sr_login);
        loginBtn.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub
                if(checkLogin()){
                    rememberName();
                    gotoActivity(LoginActivity.this, QuickStartRunActivity.class);
                    LoginActivity.this.finish();
                }else{
                    warnDialog = new WarnDialog(LoginActivity.this);
                    warnDialog.showWarnDialog(R.layout.layout_dialog_error, R.string.login_error, true);
                    myHandler.sendEmptyMessageDelayed(0, 3000);
                }
            }
        });
    }

    private boolean checkLogin(){
        db = MainActivity.db;
        String name = nameText.getText().toString().trim();
        String pwd = pwdText.getText().toString().trim();
        Cursor cursor = db.rawQuery("select * from TBL_PERSONAL_INFO where name = ?", new String[] { name });
        if(cursor.moveToNext()){
            if(pwd.equals(cursor.getString(3))){
                if(remPwd.isChecked()){
                    editor.putBoolean("isRemember", true);
                    editor.putString(name, pwd);

                }
                else{
                    editor.putBoolean("isRemember", false);
                }
                editor.putString("name", name);
                editor.commit();
                cursor.close();
                return true;
            }
        }
        editor.putBoolean("isLogin", false);
        editor.commit();
        cursor.close();
        return false;
    }

SQLite 数据库存储——一款轻量级的关系型数据库

文件存储SharedPreferences存储毕竟只适用于去保存一些简单的数据键值对,当需要存储大量复杂的关系型数据的时候,你就会发现以上两种存储方式很难应付得了。比如我们手机的短信程序中可能会有很多个会话,每个会话中又包含了很多条信息内容,并且大部分会话还可能各自对应了电话簿中的某个联系人。存储这些数据量大、结构性复杂的数据,使用数据库就可以做得到。

创建数据库(SQLiteOpenHelper帮助类)

SQLiteOpenHelper 抽象类

SQLiteOpenHelper 是一个抽象类,这意味着如果我们想要使用它的话,就需要创建一个自己的帮助类去继承它。SQLiteOpenHelper 中有两个抽象方法,分别是onCreate()和 onUpgrade(),我们必须在自己的帮助类里面重写这两个方法,然后分别在这两个方法中去实现创建、升级数据库的逻辑。

1、 getReadableDatabase() 和getWritableDatabase()

Android使用getWritableDatabase()和getReadableDatabase()方法都可以获取一个用于操作数据库的SQLiteDatabase实例。(getReadableDatabase()方法中会调用getWritableDatabase()方法)

SQLiteOpenHelper 中 还 有 两 个 非 常 重 要 的 实 例 方 法。两个方法都可以创建或打开一个现有的数据库(如果数据库已存在则直接打开,否则创建一个新的数据库),并返回一个可对数据库进行读写操作的对象。不同的是,当数据库不可写入的时候(如磁盘空间已满)getReadableDatabase()方法返回的对象将以只读的方式去打开数据库,而 getWritableDatabase()方法则将出现异常。

二者区别和对应的源码解析,可以参考:http://blog.csdn.net/alex_zhuang/article/details/7342840简单易懂。这两个方法中会调用抽象类实现之后的onCreate()和 onUpgrade()

2、SQLiteOpenHelper构造方法

有两个构造方法可供重写,一般使用参数少一点的那个构造方法即可。这个构造方法中接收四个参数,第一个参数是 Context。第二个参数是数据库名,创建数据库时使用的就是这里指定的名称。第三个参数允许我们在查询数据的时候返回一个自定义的 Cursor,一般都是传入 null。第四个参数表示当前数据库的版本号,可用于对数据库进行升级操作。

3、获取数据库

构建出SQLiteOpenHelper的实例之后,再调用它的 getReadableDatabase()或 getWritableDatabase()方

法就能够创建数据库了,数据库文件会存放在/data/data//databases/目录下。此时,重写的 onCreate()方法也会得到执行,所以通常会在这里去处理一些创建表的逻辑。

数据库建表语句
create table Book (
    id integer primary key autoincrement,
    author text,
    price real,
    pages integer,
    name text)
转换成android中使用
public static final String CREATE_BOOK = "create table book ("
    + "id integer primary key autoincrement, "
    + "author text, "
    + "price real, "
    + "pages integer, "
    + "name text)";
    +
@Override
public void onCreate(SQLiteDatabase db) {
    db.execSQL(CREATE_BOOK);
}

实际应用中,比较好的方式:

创建一个单独的类,把表名、表内参数以及参数类型分三个变量。

public class SQLInfo {

    private static String TableNames[] = {
            "TBL_PERSONAL_INFO",
            "TBL_RUN_DATA"
        };//表名

        private static String FieldNames[][] = {
                {"ID","NAME","REAL_NAME","PWD","SEX","AGE","HEIGHT","WEIGHT","QUESTION_ONE","ANSWER_ONE","QUESTION_TWO","ANSWER_TWO","QUESTION_THREE","ANSWER_THREE"},
                {"ID","NAME","USED_TIME","FINISH_DISTANCE","CALORIES","AVG_SPEED","AVG_INCLINE"}

        };//字段名

        private static String FieldTypes[][] = {
                {"INTEGER PRIMARY KEY AUTOINCREMENT","TEXT","TEXT","TEXT","INTEGER","INTEGER","INTEGER","REAL","INTEGER","TEXT","INTEGER","TEXT","INTEGER","TEXT"},
                {"INTEGER PRIMARY KEY AUTOINCREMENT","TEXT","INTEGER","REAL","INTEGER","REAL","INTEGER"}
        };//字段类型

        public static String[] getTableNames() {
            return TableNames;
        }

        public static String[][] getFieldNames() {
            return FieldNames;
        }

        public static String[][] getFieldTypes() {
            return FieldTypes;
        }
}

在自己创建的帮助类(继承SQLiteOpenHelper)中
public SQLDbHelper(Context context) {
            super(context, myDBName, null, version);
            // TODO Auto-generated constructor stub
        }

        @Override
        public void onCreate(SQLiteDatabase db) {
            // TODO Auto-generated method stub
            if(TableNames == null){
                message = NO_CREATE_TABLES;
                return;
            }
            for(int i = 0; i < TableNames.length; i++){
                String sql = "CREATE TABLE " + TableNames[i] + " (";
                for (int j = 0; j < FieldNames[i].length; j++)
                {
                    sql += FieldNames[i][j] + " " + FieldTypes[i][j] + ",";
                }
                sql = sql.substring(0, sql.length() - 1);
                sql += ")";
                db.execSQL(sql);
            }
        }
    }

用adb查看数据库

adb是 Android SDK中自带的一个调试工具,使用这个工具可以直接对连接在电脑上的手机或模拟器进行调试操作。它存放在 sdk的 platform-tools 目录下,想直接使用记得配置路径到环境变量。

使用步骤:

(1)打开命令行界面,输入 adb shell,

(2)cd 进行到/data/data/com.example.databasetest/databases/目录下,ls

(3)这个目录下出现了两个数据库文件,一个正是我们创建的 BookStore.db,而另一个BookStore.db-journal 则是为了让数据库能够支持事务而产生的临时日志文件,通常情况下这个文件的大小都是 0 字节。

(4)借助 sqlite 命令来打开数据库了,只需要键入 sqlite3,后面加上数据库

名。

(5)此时数据库中会多一个android_metadata 表,每个数据库中都会自动生成

的。

升级数据库——onUpgrade()

注意:一旦数据库创建成功,除非是你删掉应用重新装,否则数据库已经存在,再次获取数据库的时候不会再调用onCreate,所以不能直接在里面升级修改数据库。

最简单除暴的升级

@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    db.execSQL("drop table if exists Book");
    db.execSQL("drop table if exists Category");
    onCreate(db);
}

onUpgrade()方法的执行

SQLiteOpenHelper 的构造方法里第四个参数,当前数据库的版本号,只要传入一个比 之前版本大的数,就可以让 onUpgrade()方法得到执行了。

比如
dbHelper = new MyDatabaseHelper(this, "BookStore.db", null, 2);

添加数据—— insert()

CRUD: C 代表添加(Create),R 代表查询(Retrieve),U代表更新(Update),D代表删除(Delete)。每一种操作又各自对应了一种 SQL命令,添加数据时使用 insert,查询数据时使用 select,更新数据时使用 update,删除数据时使用 delete。

SQLiteDatabase 中提供了一个 insert()方法,这个方法就是专门用于添加数据的。它接收三个

参数,第一个参数是表名,我们希望向哪张表里添加数据,这里就传入该表的名字。第二个

参数用于在未指定添加数据的情况下给某些可为空的列自动赋值 NULL,一般我们用不到这

个功能,直接传入 null 即可。第三个参数是一个 ContentValues 对象,它提供了一系列的 put()

方法重载,用于向 ContentValues 中添加数据,只需要将表中的每个列名以及相应的待添加

数据传入即可。

Button addData = (Button) findViewById(R.id.add_data);
addData.setOnClickListener(new OnClickListener() {
    @Override
    public void onClick(View v) {
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        ContentValues values = new ContentValues();
        //  开始组装第一条数据
        values.put("name", "The Da Vinci Code");
        values.put("author", "Dan Brown");
        values.put("pages", 454);
        values.put("price", 16.96);
        db.insert("Book", null, values); //  插入第一条数据
        values.clear();
        //  开始组装第二条数据
        values.put("name", "The Lost Symbol");
        values.put("author", "Dan Brown");
        values.put("pages", 510);
        values.put("price", 19.95);
        db.insert("Book", null, values); //  插入第二条数据
    }
});

更新数据—— update()

参数:第一个参数和 insert()方法一样,也是表名,指定去更新哪张表里的数

据。第二个参数是 ContentValues 对象,把更新数据在这里组装进去。第三、第四个参数

用于去约束更新某一行或某几行中的数据,不指定的话默认就是更新所有行。

SQLiteDatabase db = dbHelper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put("price", 10.99);
db.update("Book", values, "name = ?", new String[] { "The Da
Vinci Code" });

删除数据—— delete()

参数:接收三个参数,第一个参数仍然是表名,第二、第三个参数又是用于去约束删除某一

行或某几行的数据,不指定的话默认就是删除所有行。

SQLiteDatabase db = dbHelper.getWritableDatabase();
db.delete("Book", "pages > ?", new String[] { "500" });

查询数据——query()

参数:参数非常复杂,最短的一个方法重载也需要传入七个参数。第一个参数还是表名,第二个参数用于指定去查询哪几列,如果不指定则默认查询所有列。第三、第四个参数用于去约束查询某一行或某几行的数据,不指定则默认是查询所有行的数据。第五个参数用于指定需要去 group by的列,不指定则表示不对查询结果进行 group by操作。第六个参数用于对 group by之后的数据进行进一步的过滤,不指定则表示不进行过滤。第七个参数用于指定查询结果的排序方式,不指定则表示使用默认的排序方式。

SQLiteDatabase db = dbHelper.getWritableDatabase();
//  查询Book 表中所有的数据
Cursor cursor = db.query("Book", null, null, null, null, null, null);

Cursor   query  (String table, String[] columns, String selection, String[] selectionArgs,
String groupBy, String having, String orderBy, String limit)

使用SQL操作数据库——db.execSQL

1、 添加数据

db.execSQL("insert into Book (name, author, pages, price) values(?, ?, ?, ?)",
new String[] { "The Lost Symbol", "Dan Brown", "510", "19.95" });

2、 更新数据

db.execSQL("update Book set price = ? where name = ?", new String[] { "10.99",
"The Da Vinci Code" });

3、 删除数据

db.execSQL("delete from Book where pages > ?", new String[] { "500" });

4、 查询数据

db.rawQuery("select * from Book", null);

数据库的最佳实践

使用事务

SQLite 数据库是支持事务的,事务的特性可以保证让某一系列的操作要么全部完成,要么一个都不会完成。

SQLiteDatabase db = dbHelper.getWritableDatabase();
db.beginTransaction(); //  开启事务
try {
    db.delete("Book", null, null);
    if (true) {
    //  在这里手动抛出一个异常,让事务失败
    throw new NullPointerException();
    }
    ContentValues values = new ContentValues();
    values.put("name", "Game of Thrones");
    values.put("author", "George Martin");
    values.put("pages", 720);
    values.put("price", 20.85);
    db.insert("Book", null, values);
    db.setTransactionSuccessful(); //  事务已经执行成功
} catch (Exception e) {
    e.printStackTrace();
} finally {
    db.endTransaction(); //  结束事务
}
}

上述代码就是Android中事务的标准用法,首先调用SQLiteDatabase的beginTransaction()

方法来开启一个事务,然后在一个异常捕获的代码块中去执行具体的数据库操作,当所有的

操作都完成之后,调用 setTransactionSuccessful()表示事务已经执行成功了,最后在 finally

代码块中调用 endTransaction()来结束事务。注意观察,我们在删除旧数据的操作完成后手动

抛出了一个 NullPointerException,这样添加新数据的代码就执行不到了。不过由于事务的存

在,中途出现异常会导致事务的失败,此时旧数据应该是删除不掉的。

升级数据库的最佳写法

前面数据库升级的方式会导致用户升级应用时候,数据全部丢失,太简单粗暴了。

比如,多增加了一个表,此时如果用户重新升级覆盖,不会creat,所以,在升级中处理

public class MyDatabaseHelper extends SQLiteOpenHelper {
    public static final String CREATE_BOOK = "create table Book ("
        + "id integer primary key autoincrement, "
        + "author text, "
        + "price real, "
        + "pages integer, "
        + "name text)";
    public static final String CREATE_CATEGORY = "create table Category ("
        + "id integer primary key autoincrement, "
        + "category_name text, "
        + "category_code integer)";
    public MyDatabaseHelper(Context context, String name,CursorFactory factory, int version) {
        super(context, name, factory, version);
    }
    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(CREATE_BOOK);
        db.execSQL(CREATE_CATEGORY);
    }
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        switch (oldVersion) {
        case 1:
            db.execSQL(CREATE_CATEGORY);
        default:
        }
    }
}

没过多久,新的需求又来了,这次要给 Book表和 Category表之间建立关联,需要在 Book表中添加一个 category_id 的字段。再次修改 MyDatabaseHelper中的代码,如下所示:

public class MyDatabaseHelper extends SQLiteOpenHelper {
    public static final String CREATE_BOOK = "create table Book ("
        + "id integer primary key autoincrement, "
        + "author text, "
        + "price real, "
        + "pages integer, "
        + "name text, "
        + "category_id integer)";
    public static final String CREATE_CATEGORY = "create table Category ("
        + "id integer primary key autoincrement, "
        + "category_name text, "
        + "category_code integer)";
    public MyDatabaseHelper(Context context, String name,CursorFactory factory, int version) {
        super(context, name, factory, version);
    }
    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(CREATE_BOOK);
        db.execSQL(CREATE_CATEGORY);
    }
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        switch (oldVersion) {
        case 1:
            db.execSQL(CREATE_CATEGORY);
        case 2:
            db.execSQL("alter table Book add column category_id integer");
        default:
        }
    }
}

switch 中每一个 case 的最后都是没有使用 break的,为了保证在跨版本升级的时候,每一次的数据库修改都能被全部执行到。比如用户当前是从第二版程序升级到第三版程序的,那么 case 2中的逻辑就会执行。而如果用户是直接从第一版程序升级到第三版程序的,那么 case 1 和 case 2 中的逻辑都会执行。使用这种方式来维护数据库的升级,不管版本怎样更新,都可以保证数据库的表结构是最新的,而且表中的数据也完全不会丢失了。

实际项目中可以考虑这样

/**打开数据库*/
    public SQLiteHelper open() throws SQLException {
        mDbHelper = new SQLDbHelper(mCtx);
        mDb = mDbHelper.getWritableDatabase();
        return this;
    }

    /**关闭数据库*/
    public void close() {
        mDbHelper.close();
    }

    public void execSQL(String sql) throws java.sql.SQLException
    {
        mDb.execSQL(sql);
    }

    /**sql语句查询数据*/
    public Cursor rawQuery(String sql,String[] selectionArgs)
    {
        Cursor cursor = mDb.rawQuery(sql, selectionArgs);
        return cursor;
    }

    /**查询数据*/
    public Cursor select(String table, String[] columns,
            String selection, String[] selectionArgs, String groupBy,
            String having, String orderBy)
    {
        Cursor cursor = mDb.query
        (
                table, columns, selection, selectionArgs,
                groupBy, having, orderBy
        );
        return cursor;
    }

    /**添加数据*/
    public long insert(String table, String fields[], String values[])
    {
        ContentValues cv = new ContentValues();
        for (int i = 0; i < fields.length; i++)
        {
            cv.put(fields[i], values[i]);
        }
        return mDb.insert(table, null, cv);
    }

    /**删除数据*/
    public int delete(String table, String where, String[] whereValue)
    {
        return mDb.delete(table, where, whereValue);
    }

    /**更新数据*/
    public int update(String table, String updateFields[],
            String updateValues[], String where, String[] whereValue)
    {
        ContentValues cv = new ContentValues();
        for (int i = 0; i < updateFields.length; i++)
        {
            cv.put(updateFields[i], updateValues[i]);
        }
        return mDb.update(table, cv, where, whereValue);
    }
时间: 2024-11-02 01:34:16

第一行代码读书笔记——数据存储全方案,持久化技术的相关文章

第一行代码读书笔记1+常见错误分析

1.eclipse里面的视图在 windows ---- show views ---- other ----- Android 2.需要掌握Logcat的使用 Logcat是你在茫茫人海中寻找到一片绿洲的地方,你需要灵活运用之,然后我们可以打印出我们需要的信息,而不用担心找不到. 我们也可以自己添加滤波器,比如下面的滤波器选项: 3.在建立菜单文件的时候,文件头需要改变 以下是错误的,这样的文件<?xml version="1.0" encoding="uft-8&q

第一行代码读书笔记2+常见错误分析

总结下:?一个应用程序中,多个不同的activity之间,以及一个activity多个不同的实例间,又是怎样的通讯机制? 通常有四种: Intent用于组件之间的消息传递,可以跨进程与线程.但是跨进程需要和其他机制捆绑(比如binder). (注意:Binder机制是android中实现的进程间通信的架构) Handle 一般用于主线程(UI线程)界面的更新,通过消息传递机制来实现.(需要使用Looper).此机制一般用于线程通讯. Broadcast 一般和intent一起用,主要用于进程间通

第一行代码读书笔记1

activity 需要了解activity的启动和传递数据,以及activity的四种启动模式,还有就是activity配置时候的action和category的用法. 还有就是activity的生命周期-----7种. UI开发 常用控件:TextView EditView ProgressBar ImageView AlertDialog Button ProgreeDialog 常用布局:LinearLayout RelativeLayout FrameLayout TableLayout

第6章 数据存储全方案,详解持久化技术

第6章 数据存储全方案,详解持久化技术 所有的App都可以说是与数据打交道的,离开数据它们什么都不是.那么平时我们怎么存储一些关键的数据呢? 1 持久化技术简介 数据持久化就是指将那些内存中的瞬时数据保存到存储设备中,保证即使在手机或电脑关机的情况下,这些数据仍然不会丢失.保存在内存中的数据是处于瞬时状态的,而保存在存储设备中的数据是处于持久状态的,持久化技术则是提供了一种机制可以让数据在瞬时状态和持久状态之间进行转换. Android系统中主要提供了三种方式用于简单地实现数据持久化功能,即文件

《Android第一行代码》笔记

学习Android开发差不多有两年时间了,期间也做了大大小小的一些项目.近来抽出闲暇想把Android基础强化一下,之前在网上看到了郭霖郭大神的几篇博客,从中受益不少.于是花了近一周时间看完了郭神的一本Android教材--<Android第一行代码>.这本书相比其他教材个人感觉更为基础,内容很实用.看完之后我也有一些收获,学到了一些可以很好的运用到实际中的Android小技巧,下面从中选出我认为很有价值的地方做个记录.同时欢迎各位指正补充~~ 1.查看当前界面处于哪个Activity. 很多

阅读郭林《第一行代码》的笔记——第6章 数据存储全方案,详解持久化技术

瞬时数据是指那些存储在内存当中,有可能会因为程序关闭或其他原因导致内存被回收而丢失的数据.这对于一些关键性的数据信息来说是绝对不能容忍的,谁都不希望自己刚发出去的一条微博,刷新一下就没了吧.那么怎样才能保证让一些关键性的数据不会丢失呢?这就需要用到数据持久化技术了. 持久化技术简介 数据持久化就是指将那些内存中的瞬时数据保存到存储设备中,保证即使在手机或电脑关机的情况下,这些数据仍然不会丢失.保存在内存中的数据是处于瞬时状态的,而保存在存储设备中的数据是处于持久状态的,持久化技术则是提供了一种机

第一行代码阅读笔记

2017年12月5号: 第二章: 1.Android Studio新建一个工程,以及各个文件夹.文件所包含的内容和含义: 2.使用Toast提醒的方式: Toast.makeText(MainActivity.this, "hello world",Toast.LENGTH_LONG).show(); 3.添加menu: (1)添加menu-main.xml,增加item:(2)在Activity中重写 onCreateOptionsMenu(Menu menu) 和 onOption

Android第一行代码学习笔记六---Intent向活动传递数据

@1.向下一个活动传递数据: Intent提供了一系列putExtra()方法的重载,可以把我们想要传递的数据暂存在Intent中,启动了另一个活动后,只需把这些数据再从Intent中取出就可以了,比如firstActivity中有一个字符串要传递到secondActivity中,修改firstActivity中按钮点击事件,代码可以这样编写: button.setOnClickListener(new View.OnClickListener() { public void onClick(V

Android第一行代码学习笔记七---活动的生命周期

@1.返回栈 Android中的活动是可以层叠的,我们每启动一个新的活动,就会覆盖在原活动之上,然后点击Back键就会销毁最上面的活动,下面一个活动就会重新显示出来. Android是使用任务(Task)来管理活动的,一个任务就是一组存放在栈里的活动的集合,这个栈也被称为返回栈(Back Stack).栈是一种后进先出的数据结构,在默认情况下,每当我们启动了一个新的活动,它会在返回栈中入栈,并处于栈顶的位置.而当我们按下Back键或调用finish()方法去销毁一个活动时,处于栈顶的活动会出栈,