在
中存储数据的方式有很多种,其中使用
- Android
数据库是存储结构化数据的最佳选择。幸运的是,
- SQLite
中默认提供了对
- Android
的支持,这就使得在
- SQLite
中使用
- Android
数据库变得格外方便。
- SQLite
SQLite 是一款轻量级的数据库,其支持的数据类型也很简单,主要有以下几种:
数据库的使用始于
- SQLite
这个抽象类。我们需要自定义类去继承
- SQLiteOpenHelper
,并重写它的
- SQLiteOpenHelper
。实际上,有两个构造方法可以重写,一般我们选择重写参数较少的那个方法。
- 构造方法
此外,我们还需要实现
和
- onCreate
这两个抽象方法。在
- onUpgrade
方法中,我们对数据库进行创建。而在
- onCreate
方法中,我们则对数据库进行升级、更新。基本代码如下:
- onUpgrade
- public class BookOpenHelper extends SQLiteOpenHelper{
- private static final String CREATE_BOOK="create table Book(" +
- "id integer primary key autoincrement," +
- "name text," +
- "price text," +
- "author text)";
- public BookOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
- super(context, name, factory, version);
- }
- @Override
- public void onCreate(SQLiteDatabase db) {
- db.execSQL(CREATE_BOOK);
- }
- @Override
- public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
- }
- }
可以看到,在构造方法中,我们只是简单地调用了父类的构造方法。在构造方法的参数列表中,第一个参数是
对象,第二个参数是数据库名称,第三个参数一般传入
- Context
,第四个参数是数据库的版本号。
- null
此外,我们定义了一个字符串常量。很明显,这是一条建表语句,用于创建名为
的表。在这个表中,我们定义了四个字段,分别是
- Book
。其中,
- id、name、price、author
由
- id
和
- primary key
修饰,代表它是表的主键且自增。在
- autoincrement
方法中,我们调用了
- onCreate
的
- SQLiteDatabase
方法,执行
- execSQL
表的建表语句。
- Book
在实际使用中,我们需要先构建
的实例,然后调用它的
- SQLiteOpenHelper
或者
- getReadableDatabase
方法,如果数据库还未创建,系统就会创建数据库并调用
- getWritableDatabase
的
- SQLiteOpenHelper
方法。实例代码如下:
- onCreate
- BookOpenHelper dbOpenHelper=new BookOpenHelper(
- MainActivity.this,"SQLiteDemo.db",null,1);
- dbOpenHelper.getReadableDatabase();
和
- getReadableDatabase
方法都会返回一个可以对数据库进行增删改查操作的
- getWritableDatabase
对象。它们的区别在于,当手机存储空间已满的时候,前者会返回 null,而后者会抛出异常。
- SQLiteDatabase
在实际开发中,常常需要对数据库进行修改,比如添加新的表或是增加表字段等。为了实现对数据库的更新,我们需要在构建
实例的时候传入一个比当前版本号更大的
- SQLiteOpenHelper
参数,这将导致
- version
的
- SQLiteOpenHelper
方法被调用。在这个方法中,我们就可以进行数据库的更新操作了。以下代码演示在之前的数据库中添加名为
- onUpgrade
的表:
- Author
- public class BookOpenHelper extends SQLiteOpenHelper{
- .......
- //创建Author表
- private static final String CREATE_AUTHOR="create table Author(" +
- "id integer primary key autoincrement," +
- "name text," +
- "age integer)";
- public BookOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
- super(context, name, factory, version);
- }
- @Override
- public void onCreate(SQLiteDatabase db) {
- db.execSQL(CREATE_BOOK);
- db.execSQL(CREATE_AUTHOR);
- }
- @Override
- public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
- switch (oldVersion){
- case 1:
- db.execSQL(CREATE_AUTHOR);
- default:
- break;
- }
- }
- }
我们定义了一个新的字符串常量
,用于创建
- CREATE_AUTHOR
r 表。同时,我们也在
- Autho
方法中执行了
- onCreate
。如果应用是首次安装,就可以正常地创建两张表。但是,如果应用之前已经被安装了。由于数据库已经存在,
- db.execSQL(CREATE_AUTHOR);
方法就不会被执行了。在这种情况下,我们将补救措施写在了
- onCreate
方法中。通过对
- onUpgrade
、即旧版本号的判断,我们执行了后续的建表语句。只要在创建
- oldVersion
的时候传入了比当前版本号更大的
- SQLiteOpenHelper
参数,
- version
方法就会被调用,
- onUpgrade
表也就会被创建了。
- Author
如果现在我们又想要为
表添加一个新的字段
- Author
,用于记录作者的名字,又该如何操作呢?实际上做法和前面一样,只需要在
- country
方法中执行更新操作即可,具体代码如下:
- onUpgrade
- public class BookOpenHelper extends SQLiteOpenHelper{
- .......
- //为Author表添加新的字段
- private static final String ALTER_AUTHOR="alter table Author add column country text";
- public BookOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
- super(context, name, factory, version);
- }
- @Override
- public void onCreate(SQLiteDatabase db) {
- db.execSQL(CREATE_BOOK);
- db.execSQL(CREATE_AUTHOR);
- db.execSQL(ALTER_AUTHOR);
- }
- @Override
- public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
- switch (oldVersion){
- case 1:
- db.execSQL(CREATE_AUTHOR);
- case 2:
- db.execSQL(ALTER_AUTHOR);
- default:
- break;
- }
- }
- }
在
方法中,我们依次执行了各个
- onCreate
操作,这样当应用首次安装时就可以正常地创建所有的表。而在
- SQL
方法中,我们通过判断当前数据库的版本号,来决定应该做哪些更新操作。若当前版本号为
- onUpgrade
,则只需要为
- 2
表添加新的字段;若当前版本号为
- Author
,则需要先创建
- 1
表,再为
- Author
表添加新的字段。这也是为什么每个
- Author
都不加
- case
的原因,这样才能保证应用跨版本升级的时候能够依次执行所有的更新操作。这一点尤其需要注意。
- break
解决了如何创建数据库和更新数据库的问题,接下来就可以正式接触
中的各项数据操作了。一般可以简称为
- SQLite
操作,即增删改查。首先,我们需要获得一个
- CRUD
对象,它封装了多种数据操作方法。可以通过
- SQLiteDatabase
实例的
- SQLiteOpenHelper
或
- getReadableDatabase
方法获得这个对象。
- getWritableDatabase
- SQLiteDatabase database=dbOpenHelper.getWritableDatabase();
- SQLiteDatabase database=dbOpenHelper.getReadableDatabase();
通过
- SQLiteDatabase
方法实现数据的添加,其方法原型如下:
- insert
- public long insert(String table, String nullColumnHack, ContentValues values)
方法需要传入三个参数,第一个参数是表名,第二个参数一般传入
- insert
,第三个参数是一个
- null
对象。
- ContentValues
通过键值对的方式存储需要写入表中的数据。插入完成后,这个方法还会返回插入数据在表中的
- ContentValues
号,如果插入失败,则返回
- id
。示例代码如下:
- -1
- SQLiteDatabase database= dbOpenHelper.getWritableDatabase();
- ContentValues values=new ContentValues();
- values.put("name","Android");
- values.put("author","Bill");
- values.put("price","60");
- database.insert("Book",null,values);
- values.clear();
- values.put("name","Java");
- values.put("author","Tom");
- values.put("price","50");
- database.insert("Book",null,values);
可以看到,我们通过同一个
对象向
- ContentValues
表添加了两条数据。需要注意的是,在添加第二条数据之前应该先调用
- Book
的
- ContentValues
方法清除旧数据。
- clear
通过
- SQLiteDatabase
方法实现数据的添加,常用版本的方法原型如下:
- query
- public Cursor query(String table, String[] columns, String selection,
- String[] selectionArgs, String groupBy, String having,
- String orderBy)
方法需要传入
- query
个参数。第一个参数是表名。第二个参数是需要查询的列名,如果传入
- 7
则代表查询所有列。第三个参数和第四个参数是查询条件,用于限制查询的行,相当于
- null
中的
- SQL
部分,如果都传入
- where
表示查询所有行。第五参数是分组的列名,相当于
- null
中的
- SQL
部分,如果传入
- group by
表示不对结果进行分组。第六个参数则相当于
- null
中的
- SQL
部分。第七个参数是排序的列名,传入
- having
表示对查询结果进行默认排序。
- null
这个方法在查询完成后会返回
对象,这是一个游标,可以通过遍历的方式去取出查询到的数据。示例代码如下:
- Cursor
- SQLiteDatabase database= dbOpenHelper.getReadableDatabase();
- Cursor cursor=database.query("Book",null,null,null,null,null,null);
- if(cursor.moveToFirst()){
- do {
- Log.d(TAG,cursor.getString(cursor.getColumnIndex("id")));
- Log.d(TAG,cursor.getString(cursor.getColumnIndex("name")));
- Log.d(TAG,cursor.getString(cursor.getColumnIndex("author")));
- Log.d(TAG,cursor.getString(cursor.getColumnIndex("price")));
- }
- while(cursor.moveToNext());
- }
- cursor.close();
我们仅为
方法传入了一个表名,其他参数均传入
- query
,表示查询
- null
表中的所有数据。对于这个方法返回的
- Book
对象,我们先调用
- Cursor
方法移动到数据的第一行。随后,在一个
- moveToFirst
循环中判断
- do-while
的
- cursor
方法是否为
- moveToNext
,并通过
- true
方法取出表中的数据。
- getString
方法用于获取表中指定列名对应的索引号。这是一个最简单的查询操作,现在我们再尝试一次限制条件更多的查询:
- getColumnIndex
- SQLiteDatabase database = dbOpenHelper.getReadableDatabase();
- Cursor cursor = database.query("Book", new String[] {
- "name",
- "author"
- },
- "author=?", new String[] {
- "Tom"
- },
- null, null, "name");
- if (cursor.moveToFirst()) {
- do {
- Log.d(TAG, cursor.getString(cursor.getColumnIndex("name")));
- Log.d(TAG, cursor.getString(cursor.getColumnIndex("author")));
- } while ( cursor . moveToNext ());
- }
- cursor.close();
以上代码的目的是查询
表中
- Book
为
- author
的数据,并且只查询
- Tom
和
- name
这两列的数据,最后将返回结果根据
- author
进行排序。需要注意的是,在完成查询后,我们还应该调用
- name
的
- Cursor
方法,释放资源。
- close
通过
- SQLiteDatabase
方法实现数据的添加,其方法原型如下:
- query
- public int update(String table, ContentValues values, String whereClause, String[] whereArgs)
方法需要传入
- update
个参数。第一个参数是表名。第二个参数是
- 4
对象,存储更新后的数据。第三个参数和第四个参数是限制条件,都传入
- ContentValues
就代表更新表中的所有数据。更新完成后,这个方法会返回受影响的行数。示例代码如下:
- null
- SQLiteDatabase database = dbOpenHelper.getWritableDatabase();
- ContentValues values = new ContentValues();
- values.put("name", "Android Plus");
- database.update("Book", values, "name=?", new String[] {
- "Android"
- });
以上代码查询
表中所有
- Book
为
- name
的数据,并将
- Android
字段更新为
- name
。需要注意的是,我们只需要在
- Android Plus
中存储需要更新的数据,不需要存储其他不变的数据。
- ContentValues
通过
- SQLiteDatabase
方法实现数据的删除,其方法原型如下:
- delete
- public int delete(String table, String whereClause, String[] whereArgs)
方法需要传入三个参数。第一个参数是表名。第二个和第三个参数是限制条件,如果都传入
- delete
就表示删除表中所有的数据。示例代码如下:
- null
- SQLiteDatabase database = dbOpenHelper.getWritableDatabase();
- database.delete("Book", "name=?", new String[] {
- "Java"
- });
以上代码将会删除
表中所有
- Book
为
- name
的数据。
- Java
除了使用
封装好的
- SQLiteDatabase
方法对数据库进行增删改查,我们还可以直接使用
- insert、delete、update、query
语句对数据进行操作。当然,前提是
- SQL
基础要足够好。
- SQL
提供了
- SQLiteDatabase
和
- execSQL
这两个方法用于直接执行
- rawQuery
语句。不同之处在于,后者用于执行查询操作,前者则执行其他操作。这两个方法常用版本的原型如下:
- SQL
- public void execSQL(String sql, Object[] bindArgs)
- public Cursor rawQuery(String sql, String[] selectionArgs)
示例代码如下:
- //插入数据
- database.execSQL("insert into Book(name,price,author) values(?,?,?)", new String[] {
- "book",
- "110",
- "Bill"
- });
- //查询数据
- Cursor cursor = database.rawQuery("select * from Book", null);
在 SQLiteDatabase 中,Android 还为开发者集成了事务功能。那么什么是事务呢?想象这样一个场景,由于应用的版本更新,我们需要删除一部分数据,然后插入新的数据。但是要求删除操作和插入操作必须一起成功,否则就一起失败。这样做可以避免在删除数据后,由于某些原因应用出错,导致插入操作没能完成,而影响用户的正常使用。使用事务,就可以顺利地完成这一要求。示例代码如下:
- SQLiteDatabase database = dbOpenHelper.getWritableDatabase();
- database.beginTransaction();
- try {
- database.delete("Book", "name=?", new String[] {
- "Android"
- });
- if (true) { //手动抛出异常演示在执行事务的过程中操作失败
- throw new Exception("手动抛出异常");
- }
- ContentValues values = new ContentValues();
- values.put("name", "Java");
- values.put("author", "Jimmy");
- values.put("price", "500");
- database.insert("Book", null, values);
- database.setTransactionSuccessful();
- } catch(Exception e) {
- Log.d(TAG, "执行事务过程中出现错误!");
- e.printStackTrace();
- } finally {
- database.endTransaction();
- }
可以看到,我们首先调用了
的
- SQLiteDatabase
方法开启事务。然后在
- beginTransaction
语句块中,我们先是删除了
- try
表中所有
- Book
为
- name
的数据,随后向
- Android
中插入了一条新的数据。在
- Book
语句块的最后,我们调用了
- try
的
- SQLiteDatabase
方法,表示事务成功执行。但是在删除数据后,我们手动抛出了一个异常,这将会导致后续的添加操作失败。如果是在普通情况下,最后的结果就是数据被删除了,但是新的数据却没有添加成功。而在这里使用了事务,出现异常后,也将会导致前面的删除操作失败。需要注意的是,我们还需要在
- setTransactionSuccessful
语句块中调用
- finally
方法,表示事务结束。
- endTransaction
总结一下,使用事务的关键就是下面这三句,需要使用事务的时候参照上述写法即可。
- database.beginTransaction();
- database.setTransactionSuccessful();
- database.endTransaction();
除了通过本文讲解的方式操作
数据库外,我们还可以借助开源库的力量。比如 LitePal、GreenDAO、ActiveAndroid、realm-java、sqlbrite 等。对于这些开源库的使用,可以参考它们的
- SQLite
主页,也可以参考下列博客:
- Github
使用 LitePal 操作数据库(先挖坑,随后填)
下面给出上述例子的
下载地址:SQLiteDemo
- demo
来源: http://blog.csdn.net/codingending/article/details/72403894