在SQL数据库中保存数据Saving Data in SQL Databases


在数据库里面存储数据是数据复用和结构化数据的理想方案。这里默认你已经有一些数据库的基本知识了。重点在教你使用Andorid中的SQLite数据库。这里面的api在android.database.sqlite包中。




定义模式和约定


SQL数据库中的一个重要原则就是模式:关于数据库是如何组织的一个正式的定义。模式反映在SQL中你用来创建数据库的声明中。你可能会发现使用一个使用一个伴随的类十分有帮助,这个类叫做约定类,它以一个系统的,和自我文件的方式显示地说明了模式中的结构。




约定类是一个包含名字、URI等常量、表和栏目的容器。contract类允许你在同一个包中的其他的类中使用相同的变量。这使得你可以在修改了一个栏的名字的时候,你的代码的其他地方也会改变。




组织contract类的一个好办法就是把整个数据库的全局定义放在类的根层次下。然后给每一个列举栏目的表创建内部类。




*********提示*************


通过实现BaseColums接口,你的内部类可以继承一个自由的主键字段,叫做_ID,它会被一些例如cursor adaptors等的Android类需要。他不是必要的,但是可以让你的 数据库和android系统更加融洽地工作。


****************************


例如下面的代码片定义了一个表的表名和列名。


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";
        ...
    }
}

使用SQLHelper类创建一个数据库


一旦你定义好了你的数据库的样子,你就可以实现创建和维护数据库和表的方法了。下面是一些典型的创建和删除表的语句。


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;




在SQLiteOpenHelper中有一系列可以使用的api。当你使用这个类去获取一个数据库的引用的时候,系统会为你运行潜在的长时间运行的一些操作,比如说只在你需要的时候才进行数据库的创建和更新操作,而不是在应用启动的时候。你需要做的就是调用getWritableDatabase()或者getReadableDatabase()。


********提示*********


因为是长时间的运行,所以要确保你的调用是在后台线程当中。可以使用AnsyTask或者IntentService


**********************


使用SQLiteOpenHelper,请继承父类并重写onCreat,onUpgrade,onOpen等回调方法。你可以选择也重写onDowngrade(),但是不是必须的。




例如,下面是一个使用了上面几个命令的例子。


public class FeedReaderDbHelper extends SQLiteOpenHelper {
    // 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的子类。


FeedReaderDbHelper mDbHelper = new FeedReaderDbHelper(getContext());






向数据库中添加信息


通过insert()方法传入ContentValue对象来向数据库总插入信息:


// 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);




第一个参数是表名,第二个是指明一栏当ContentValue中的该项为null的时候是否还允许插入。如果设置为null,那么当没有数据的时候,就不会插入一行数据。




从数据库中读取数据


使用query()方法来读取数据库中的数据,传入你的选择条件和需要的栏。这个方法组合了insert()和update(),除了栏目的列表表示的是你想要获取的数据而不是你想要插入的数据。查询的结果会在一个返回一个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";

Cursor c = db.query(
    FeedEntry.TABLE_NAME,  // The table to query
    projection,                               // The columns to return
    selection,                                // The columns for the WHERE clause
    selectionArgs,                            // The values for the WHERE clause
    null,                                     // don't group the rows
    null,                                     // don't filter by row groups
    sortOrder                                 // The sort order
    );




cursor.moveToFirst();
long itemId = cursor.getLong(
    cursor.getColumnIndexOrThrow(FeedEntry._ID)
);//如果没有这栏就抛异常。

从数据库中删除数据


要删除数据库表中的一行数据,你需要提供用来确定这行的标准。数据库API提供了一种可以创建选择标准并防范SQL注入攻击的机制。这个机制将选择的说明分分割为选择的从句和选择的参数。从句定义了需要查找的栏目,同时可以组合栏目测试。参数就是从句中需要测试的值。因为对于结果的处理不同于一般的SQL语句,它对SQL注入攻击是免疫的。




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

更新数据库


当你需要修改数据库的一个子集的时候,使用update()函数。


更新表的语句融合了insert()的内容数值的语法,和delete()的where语法。


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);




(译者:说实话,官方开发文档保留了很多思想性的东西,包括东西构建的原理和联系,比如说这里面对于增删改查的语法的解析,我虽然也读过数据库的书但是当时并没有意识到该就是使用插入语法指定栏位置,然后用删除的语法来指定行。推荐大家多多阅读官方文档,体会设计者的思想,而不仅仅是只会用就行了,这样才能有长远的发展。)