Android数据存储—— SQL Database

原创 2015年11月29日 20:51:10

1. 概述

  对于一些结构非常明确的数据,或者是重复性比较高的数据,把它存储在数据库中是最好的选择。比方说是联系人,每一个联系人都会有姓名,手机号,电话,邮箱,住址等信息,当然可能会有一些字段存储的时候会是空的。对于每一个联系人来说,结构都是十分明确清晰的,这样,利用数据库来存储信息是比较合适,也是比较方便的。

1.1 SQLite数据库

1.1.1 SQLite简介

  Android系统中所采用的数据库是sqlite,这是一个轻量级的关系型数据库,主要是为嵌入式设备所设计的,所占的体积非常小,总共还不到500KiB,非常适合在移动设备上使用。sqlite并不是专门为Android专门设计的,其他的移动设备也是可以使用的,同时它还支持很多的操作系统,包括windows, linux, iOS等等,支持多种编程语言接口,跨平台性非常强。到2015年11月的时候,sqlite已经更新到v3.9.2了,所以一般在使用的时候,也叫它sqlite3,可能随着版本的更新,以后这个叫法也可能会随着更新。

1.1.2 SQLite的特点

  作为一个在移动设备上使用的数据库,sqlite有许多的优点,下面列出了它的一些特性:

  • 支持大多数SQL92标准
  • 所占空间小,可存储的数据量大(最多可以存储2TB的数据)
  • 无需用户名和密码即可连接
  • 不需要安装,可以随时拿过来用
  • 单文件,每个数据库的数据都存在一个文件中,保存在磁盘上
  • 支持事务,简化复杂的操作
  • 支持多语言(C/C++,Java,Objective-C,PHP,C#,Perl,python等)和和多平台
  • 弱类型,支持5种类型数据(NULL,INTEGER,REAL,TEXT,BLOB),但是可以存很多种数据
  • 等等等……

  关于sqlite的更多特性,可以去sqlite官网上了解。

2. 定义Schema和Contract

  这两个是数据库中的概念,Schema是数据库组织形式的声明,在SQL中反映为数据库的建表语句()。Contract,直译过来是合同的意思,可以把它理解成一种协议,从它的用法上可能会更容易理解一些。在使用数据库的时候,可以声明一个Contract类,这个类就相当于一个容器,里面放着一些常量,比如数据库的表名、属性名、Uri等等。这么做的一个好处是,可以对数据库进行统一的管理,如果有一天你想改变数据库表中的某一个字段名,你只需要在这里改就可以了,不用到代码中到处去改。
  那么怎么定义这个Contract类呢?官方推荐这么写:

public final class FeedReaderContract {
    // To prevent someone from accidentally instantiating the contract class, give it an empty constructor.(为了防止在程序中不小心实例化了这个类,这里写了一个空的构造方法)
    public FeedReaderContract() {}

    /* Inner class that defines the table contents (定义了一个静态抽象类,放一些跟表有关的属性)*/
    public static abstract class FeedEntry implements BaseColumns {
        public static final String TABLE_NAME = "entry";
        public static final String COLUMN_NAME_ENTRY_ID = "entryid";
        public static final String COLUMN_NAME_TITLE = "title";
        public static final String COLUMN_NAME_SUBTITLE = "subtitle";
        ...
    }
}

  这里定义了一个内部抽象类,它实现了BaseColumns接口,好像很厉害的样子。实际上这个接口里只有两个String类型的常量,一个_COUNT,一个_ID,这两个属性许多的Android类都有,当然了,实现这个接口并不是必须的,因为我们自己的数据库表不一定有这两个属性,为什么这么写呢,这里引用官方文档上的说法,是为了“让你的数据库和Android框架和谐地运作(help your database work harmoniously with the Android frame)”。
  看起来还是不太好理解,简单一点来解释一下。上面说到了SQLite数据库是单文件的,一个程序的数据库就是一个文件,那么这里呢,就写了一个Contract类来管理数据库常量,然后为数据库中的每一张表都定义一个内部抽象类,放一些常量,这样,数据库的结构就非常的明确了。

3. 操作数据库

   上面讲了一堆理论,接下来就是实际应用了。操作数据库,无非就是建数据库(表)以及增删改查。下面就进行一一介绍。

3.1 获取数据库对象

  由于sqlite数据库不需要安装,获取数据库也就比较简单,Android为我们提供了一个非常方便的工具类SQLiteOpenHelper,它有两个方法,getReadableDatabase()和getWritableDatabase(),这两个方法都返回一个SQLiteDatabase对象,可以获取具有“读”和“写”权限的数据库,在实际使用的时候,根据需要自己调用相应的方法,对数据库进行操作。
  关于这个SQLiteOpenHelper类,使用它可以很方便地操作数据库,在实际操作的时候,需要自己写一个类继承在SQLiteOpenHelper,然后在这个类中定义一些操作数据库的方法。

public class FeedReaderDbHelper extends SQLiteOpenHelper {
    private static final String TEXT_TYPE = " TEXT";
    private static final String COMMA_SEP = ",";
    private static final String SQL_CREATE_ENTRIES =
    "CREATE TABLE " + FeedEntry.TABLE_NAME + " (" +
    FeedEntry._ID + " INTEGER PRIMARY KEY," +
    FeedEntry.COLUMN_NAME_ENTRY_ID + TEXT_TYPE + COMMA_SEP +
    FeedEntry.COLUMN_NAME_TITLE + TEXT_TYPE + COMMA_SEP +
    ... // Any other options for the CREATE command
    " )";

private static final String SQL_DELETE_ENTRIES =
    "DROP TABLE IF EXISTS " + FeedEntry.TABLE_NAME;
    // If you change the database schema, you must increment the database version.
    public static final int DATABASE_VERSION = 1;
    public static final String DATABASE_NAME = "FeedReader.db";

    public FeedReaderDbHelper(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(SQL_CREATE_ENTRIES);
    }
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        // This database is only a cache for online data, so its upgrade policy is
        // to simply to discard the data and start over
        db.execSQL(SQL_DELETE_ENTRIES);
        onCreate(db);
    }
    public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        onUpgrade(db, oldVersion, newVersion);
    }
}

   注意到上面的写法,在写SQLiteOpenHelper的子类的时候,要重写onCreate(),onUpgrate(),onOpen()等方法,同时必须写一个构造方法。

  • 构造方法,必须实现,同时必须调用父类SQLiteOpenHelper的构造方法。SQLiteOpenHelper的构造方法有两个,一个4个参数的,一个5个参数的,4个参数的如下,可以看下源码,它调用了5个参数的构造方法:

    /**
     * Create a helper object to create, open, and/or manage a database.
     * This method always returns very quickly.  The database is not actually
     * created or opened until one of {@link #getWritableDatabase} or
     * {@link #getReadableDatabase} is called.
     *
     * @param context to use to open or create the database 打开或创建数据库的Context对象
     * @param name of the database file, or null for an in-memory database 数据库文件的名字,可以是null,数据库只在内存中
     * @param factory to use for creating cursor objects, or null for the default 用来创建一个cursor对象,传null则使用默认的cursor
     * @param version number of the database (starting at 1);数据库的版本号,从1开始,用来控制数据库的升级与降级 
     * if the database is older, {@link #onUpgrade} will be used to upgrade the database; 
     * if the database is newer, {@link #onDowngrade} will be used to downgrade the database
     */
    public SQLiteOpenHelper(Context context, String name,        CursorFactory factory, int version) {
        this(context, name, factory, version, null);
    }

    上面注释中提到了,如果第二个参数,也就是数据库的名,如果传一个null的话,会创建一个in-memery database,这种数据库不把文件存在磁盘上,而是把数据放在内存中。更多这种数据库的知识,wikipedia上有解释。
    5个参数的构造方法如下:

    public SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version,
            DatabaseErrorHandler errorHandler) {
        if (version < 1) throw new IllegalArgumentException("Version must be >= 1, was " + version);
    
        mContext = context;
        mName = name;
        mFactory = factory;
        mNewVersion = version;
        mErrorHandler = errorHandler;
    }
  • onCreate(),必须实现,用来建表,并进行一些初始化设置。

  • onUpgrade(),必须实现,用来升级数据库。一般,我们的app会升级,随着需求的变化,升级后的版本中,数据库表可能会增加字段,有可能增加表,也有可能删除表,这时候就会调用onUpgrade()方法。
  • onOpen(),这个方法是获取到一个只读的数据库。
  • onDowngrade(),和onUpgrade()方法相反,这个方法是数据库降级的时候调用的。不过,用到的一般比较少。
      关于数据库升级,实际用到的还比较多,这篇文章讲的比较清楚,http://www.cnblogs.com/a284628487/p/3345820.html
      SQLiteOpenHelper中还有很多方法,在开发文档上有详细的讲解。
      在实际使用的时候,需要先获取到SQLiteOpenHelper的对象,然后再调用它的getReadableDatabase()或getWritableDatabase()获取一个SQLiteDatabase对象,对数据库进行操作,用法如下:

    FeedReaderDbHelper mDbHelper = new  FeedReaderDbHelper(getContext());
    SQLiteDatabase db = mDbHelper.getReadableDatabase();

      这种new出一个对象的方法可以,实际上,我们也经常把DatabaseOpenHelper设置成单例模式,每次获取的都是同一个对象。
      除了使用DBHelper之外,直接通过Context对象的CreateOrOpenDatabase()也能得到一个SQLiteDatabase对象,这两种方法的效果是一样的,其实本质上也是一样的。

    注意:

    1. 拿到一个数据库实例db,操作完数据库之后,必须调用db.close()方法关闭数据库,否则的话,会抛异常,严重可以导致程序崩溃;
    2. 程序写的很大之后,获取到数据库的对象也是一个耗时的操作,最好把getWritableDatabase()和getReadableDatabase()放到非UI线程上操作,否则可能会因UI线程响应过慢导致ANR。

3.2 向数据库中写入数据

  写入数据,或者说插入数据,其实就是执行insert语句,也很简单。一般有两种方法可以使用。

3.2.1 直接执行sql语句

  这种方法比较粗暴,或者说是比较原始,直接写sql语句,然后调用SQLiteDatabase的exeSQL(String sql)方法插入数据。

String sql = "insert into "+ FeedReaderContract.FeedEntry.TABLE_NAME + " ("+          FeedReaderContract.FeedEntry.COLUMN_NAME_ENTRY_ID+"," + DatabaseContract.StudentEntry.COLUMN_NAME_ENTRY_TITLE + DatabaseContract.StudentEntry.COLUMN_NAME_ENTRY_SUBTITLE + ") values (1, 'title', 'subtitle')";
        db.execSQL(sql);

  每一种方法都有其优点和缺点,这里简单说一下。
优点:

  • 灵活,可以随时随地定义自己的sql语句

缺点:

  • 极易出错
  • 程序结构不清晰,不易于阅读

   个人还是觉得这么写缺点大于优点,自己写事情了语句是在太容易出错了,刚才写demo的时候,sql少写了一个空格,一直报错,然后看了半天才发现。可见,如果程序代码一多,sql结构在复杂一点,这样的语句就更容易出错了。
   事实上,除了插入数据,下面讲到的查询,删除,更新数据都可以用这种方法,不过因为这个方法的返回值是void,查询语句是拿不到查询结果的。

3.2.2 使用ContentValues

  ContentValus是android.content中的一个类,它是以key-value的形式存储数据的。来看一下源码的一部分:

public static final String TAG = "ContentValues";

    /** Holds the actual values */
    private HashMap<String, Object> mValues;

    /**
     * Creates an empty set of values using the default initial size
     */
    public ContentValues() {
        // Choosing a default size of 8 based on analysis of typical
        // consumption by applications.
        mValues = new HashMap<String, Object>(8);
    }

  可以看到ContentValues本质上就是一个HashMap,在new一个对象的时候,如果一个参数都没给,就默认new出一个size为8的HashMap。
  有了ContentValues之后,可以调用SQLiteDatabase的insert(String table, String nullColumnHack, ContentValues values)插入数据,这里有必要解释一下这几个参数。

  • table 这个很容易理解,就是表名了;
  • nullColumnHack 这其实是表中其中一个允许值为null的属性名。使用的时候,可以传一个null值。由于SQL不允许插入一条全部属性都为空,而且还没有指定任何一个属性的纪录,比如说这样”insert into my_table values;”一条语句,但是我们传入的ContentValues也就是第三个参数,有可能是未初始化的,就出现了这种情况。为了避免出现这种情况,就可以在传参的时候,这个参数放一个属性名,当第三个参数没初始化的时候,就会把这个属性的值设为空,于是就出现了这样的语句,”insert into my_table (nullColumnHack) values (null);”, 这样就是完全合理的;
  • values 这就是要插入的值了。

那么,具体的用法就是下面这样;

// Gets the data repository in write mode
SQLiteDatabase db = mDbHelper.getWritableDatabase();

// Create a new map of values, where column names are the keys
ContentValues values = new ContentValues();
values.put(FeedEntry.COLUMN_NAME_ENTRY_ID, id);
values.put(FeedEntry.COLUMN_NAME_TITLE, title);
values.put(FeedEntry.COLUMN_NAME_CONTENT, content);

// Insert the new row, returning the primary key value of the new row
long newRowId;
newRowId = db.insert(
         FeedEntry.TABLE_NAME,
         FeedEntry.COLUMN_NAME_NULLABLE,
         values);

3.3 从数据库中读取信息

  从数据库读取信息需要调用SQLiteDatabase的query方法,结果会放在一个Cursor对象中。用法如下:

SQLiteDatabase db = mDbHelper.getReadableDatabase();

// Define a projection that specifies which columns from the database 定义要查找的列
// you will actually use after this query.
String[] projection = {
    FeedEntry._ID,
    FeedEntry.COLUMN_NAME_TITLE,
    FeedEntry.COLUMN_NAME_UPDATED,
    ...
    };

// How you want the results sorted in the resulting Cursor 查询结果的排列顺序
String sortOrder =
    FeedEntry.COLUMN_NAME_UPDATED + " DESC";
String section = FeedEntry.COLUMN_NAME_TITLE + "like ?";
String[] selectionArgs = new String[]{"android%"};
 Cursor c = db.query(
    FeedEntry.TABLE_NAME,  // The table to query 表名
    projection,                               // The columns to return 要查询哪些属性的值
    selection,                                // The columns for the WHERE clause  /where子句中的属性名
    selectionArgs,                            // The values for the WHERE clause  /where子句中的属性值
    null,                                     // don't group the rows /group by子句
    null,                                     // don't filter by row groups  /having字句
    sortOrder                                 // The sort order 结果排序
    );

  上面说了,查询出的结果是放在一个Cursor对象中的,那么获取查询到的数据,应该这么做,先调用moveToFirst(),把cursor移动到最开始的位置,然后调用getLong(),getString()等方法获取到相应的值。用法见代码:

cursor.moveToFirst();
while(cursor.moveToNext()){
    long itemId = cursor.getLong(
        cursor.getColumnIndexOrThrow(FeedEntry._ID)
    );
}
cursor.close();

  除了query这个方法。SQLiteDatabase还有一个查询的方法,叫rawQuery(String sql, String[] selectionArgs),这个方法只有两个参数,一个是sql语句,一个是值,这相当于是条件变少了的query语句。用法还是见代码:

    /** use raw query **/
    String selection = "select * from "+ FreeReaderContract.FreeEntry.TABLE_NAME +
                " where " + FreeReaderContract.FeedEntry.COLUMN_NAME_TITLE +" like ?";
        String[] selectionValue = new String[]{"android%"};

        Cursor cursor = db.rawQuery(selection, selectionValue);

  不管使用哪个query,拿到数据之后,随手写上cursor.close()都是一个好习惯。

3.4 从数据库中删除信息

  删除信息只需调用SQLiteDatabase的delete(String table, String whereClause, String[] whereArgs)方法,三个参数很容易理解,分别是表名,条件语句,条件值,用法直接看代码:

// Define 'where' part of query. 定义where语句
String selection = FeedEntry.COLUMN_NAME_ENTRY_ID + " LIKE ?";
// Specify arguments in placeholder order. 确定where语句的值
String[] selectionArgs = { String.valueOf(rowId) };
// Issue SQL statement.
db.delete(table_name, selection, selectionArgs);

3.4 从数据库中更新信息

  从数据库更新数据调用SQLiteDatabase的update(String table, ContentValues values, String whereClause, String[] whereArgs)方法,4个参数,分别是表名,新值,where条件,where条件值。update是insert和delete的复合语句,任何一条update语句都可以用一条insert语句和一条delete语句来代替,如果你愿意的话,可以这么干。
  update语句的用法,还是见代码:

SQLiteDatabase db = mDbHelper.getReadableDatabase();

// New value for one column
ContentValues values = new ContentValues();
values.put(FeedEntry.COLUMN_NAME_TITLE, title);

// Which row to update, based on the ID
String selection = FeedEntry.COLUMN_NAME_ENTRY_ID + " LIKE ?";
String[] selectionArgs = { String.valueOf(rowId) };

int count = db.update(
    FeedReaderDbHelper.FeedEntry.TABLE_NAME,
    values,
    selection,
    selectionArgs);
版权声明:本文为博主原创文章,转载请注明出处。博文如有错误,欢迎批评指正。

Android sql的数据存储

实现效果:输入姓名,密码,年龄。点击增加,查询,显示到下拉列表中。                    点击删除,此行信息消失。点击修改,修改内容直接进入下拉列表中。...

Android数据存储SQLite-使用sql操作数据库

SQLiteDatabase db=new SQLiteDatabase(); public static final String CREATE_BOOK = "create table Book...

android数据存储_SQL数据库

//继承SQLiteOpenHelper类, public class DictionaryOpenHelper extends SQLiteOpenHelper{ public stati...

Android开发之路十一-----SharePreferences进行数据存储和SQL数据库

SharedPreferences进行数据存储   很多时候我们开发的软件需要向用户提供软件参数设置功能,例如我们常用的QQ,用户可以设置是否允许陌生人添加自己为好友。对于软件配置参数的保存,如果...

Android数据存储(七) SQLite使用注意和SQL语句

1、数据类型的问题 SQLite内部只支持NULL、INTEGER、REAL(浮点数)、TEXT(文本)和BLOB(大二进制对象)这5种数据类型,但实际上SQLite完全可以接受varchar(n)...

Android数据存储之SQLite中常用的SQL语句

数据查询语句SQL是一种查询功能很强的语言,它使用select语句进行数据库的查询操作,其语言的一般格式如下。 select 字段1[,字段2,······] from 表名 [where 限制条...

NI-NINews——labview数据存储

  • 2015年09月23日 10:46
  • 1.29MB
  • 下载
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Android数据存储—— SQL Database
举报原因:
原因补充:

(最多只允许输入30个字)