Android 数据存储之 SQLite数据库存储
----------------------------------------SQLite数据库----------------------------------------------
SQLite是一款轻量级的关系型数据库,它的运算速度非常快,占用资源很少,通常只需要几百K的内存就足够了,因此特别适合在移动设备上使用。
SQLite不仅支持标准的SQL语法,还遵守了数据库的 ACID 事务,只要你以前使用过其他的关系型数据库,就可以很快的上手SQLite。
而SQLite又比一般的数据库要简单的多,它甚至不用设置用户名和密码就可以使用。
Android 正是把这个功能极为强大的数据库嵌入到了系统当中,使得本地持久化的功能有了一次质的飞跃。
-------------------------------------------创建数据库-----------------------------------------------
Android 提供了一个SQLiteOpenHelper 帮助类,借助这个类就可以非常简单的对数据库进行创建和升级。
SQLiteOpenHelper 是一个抽象类,如果需要使用它的话,就需要创建一个帮助类去继承它。
SQLiteOpenHelper 中有两个抽象方法,分别是 onCreate() 和 onUpdate(),需要在帮助类里重写这两个方法,然后分别在这两个方法中去实现创建和升级数据库的逻辑。
SQLiteOpenHelper 中还有两个非常重要的实例方法,getReadableDatabase() 和 getWritableDatabase()。这两种方法都可以创建或打开一个现有的数据库(如果数据库已存在则直接打开,否则创建一个新的数据库),并返回一个可对数据库进行读写操作的对象。不同的是,当数据库不可写入的时候(如磁盘空间已满)getReadableDatabase() 方法返回的对象将以只读的方式去打开数据库,而getWritableDatabase() 方法将抛出异常。
SQLiteOpenHelper 的构造方法接收四个参数,第一个参数是 Context,必须要有Context对象才能对数据库进行操作。第二个参数是数据库名,创建数据库时使用的就是这里指定的名称。第三个参数允许在查询数据库的时候返回一个自定义的 Cursor,一般传入null。第四个参数表示当前数据库的版本号,可用于对数据库进行升级操作。
构建出 SQLiteOpenHelper 的实例之后,再调用它的 getReadableDatabase() 或 getWritableDatabase() 方法就能够创建数据库了,数据库文件会存放在 /data/data/<包名>/database/ 目录下。
新建 MyDatabaseHelper 类继承自 SQLiteOpenHelper:
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 )"; /** * integer:整形 * real:浮点型 * text:文本类型 * blob:二进制类型 * PRIMARY KEY将id列设置为主键 * AutoIncrement关键字表示id列是自动增长的 */ private Context myContent; public MyDatabaseHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) { super(context, name, factory, version); myContent = context; } @Override public void onCreate(SQLiteDatabase db) { //创建数据库的同时创建Book表 db.execSQL(CREATE_BOOK); //提示数据库创建成功 Toast.makeText(myContent, "数据库创建成功", Toast.LENGTH_SHORT).show(); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { } }
在 MainActivity 中进行测试:
public class MainActivity extends AppCompatActivity { private MyDatabaseHelper dbHelper; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //构建一个 MyDatabaseHelper 对象,通过构造函数将数据库名指定为 BookStore.db dbHelper = new MyDatabaseHelper(this,"BookStore.db",null,1); Button createDatabase = (Button)findViewById(R.id.create_database); createDatabase.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { /** *调用getWritableDatabase() 方法 * 自动检测当前程序中 BookStore.db 这个数据库 * 如果不存在则创建该数据库并调用 onCreate() 方法 * 同时Book表也会被创建 */ dbHelper.getWritableDatabase(); } }); } }
点击按钮 BookStore.db 数据库和 Book 表就已经创建成功了,再次点击不会再有Toast弹出。
关于如何查看SQLite数据库,请参考:Android 中 SQLite 数据库的查看
-------------------------------------------升级数据库-----------------------------------------------
onUpdate() 方法是用于对数据库进行升级的,它在整个数据库的管理工作中担当着非常重要的作用。
目前 BookStore.db 中已经有一张 Book 表用于存放书的各种详细数据,接下来再添加一张 Category 表用于记录书籍的分类。
将建表语句添加到 MyDatabaseHelper 中,代码如下:
public static final String CREATE_CATEGORY = "CREATE TABLE category (" + "id integer PRIMARY KEY Autoincrement , " + "category_name text , " + "category_code integer )";
并在 onCreate()方法中添加:db.execSQL( CREATE_CATEGORY); 这条语句,运行程序,并不会弹出创建成功的提示。因为此时 BookStore.db 数据库已经存在了,之后不论怎样点击创建按钮,MyDatabaseHelper 中的 onCreate() 方法都不会再次执行,因此新添加的表也就无法得到创建了。
只需要巧妙的运用 SQLiteOpenHelper 的升级功能就可以很轻松的解决这个问题。
修改 MyDatabaseHelper 中的代码,如下所示:
@Override public void onCreate(SQLiteDatabase db) { //创建Book表和Category表 db.execSQL(CREATE_BOOK); db.execSQL(CREATE_CATEGORY); //提示数据库创建成功 Toast.makeText(myContent, "数据库创建成功", Toast.LENGTH_SHORT).show(); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { /** * 如果发现数据库中已经存在 Book 表或 Category 表 * 就将这两张表删除掉,然后调用 onCreate() 方法重新创建 * 如果在创建表时发现表已经存在,就会直接报错 */ db.execSQL("DROP TABLE IF EXISTS Book"); db.execSQL("DROP TABLE IF EXISTS Category"); onCreate(db); }
接下来的问题就是如何让 onUpgrade() 方法能够得到执行了,还记得 SQLiteOpenHelper 的构造方法里接受的第四个参数吗?它表示当前数据库的版本号,之前我们传入的是1,现在只要传入一个比1大的数,就可以让 onUpgrade() 方法得到执行了,修改MainActivity 中的代码:
dbHelper = new MyDatabaseHelper(this,"BookStore.db",null,3);
这里将数据库版本号指定为3,表示我们对数据库进行升级了。重新运行程序,并点击创建数据库按钮,这时就会再次弹出创建成功的提示。
升级数据库的最佳方式:
粗暴的删除当前所有的表来达到更新的效果,对于用户来说是非常糟糕的,因为以前程序中存储的本地数据全部丢失了。其实只需进行一些合理的控制,就可以保证升级数据库的时候数据并不会丢失了。
每一个数据库版本都会对应一个版本号,当指定的数据库版本号大于当前数据库版本号的时候,就会进入到 onUpgrade()方法中去执行更新操作。这里需要为每一个版本号赋予它各自改变的内容,然后在 onUpgrade()方法中对当前数据库的版本进行判断,再执行相应的改变就可以了。
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { switch (oldVersion) { case 1: db.execSQL(CREATE_CATEGORY); default: } }
注意,switch 中每一个 case 的最后都是没有使用 break 的,这是为了保证跨版本升级的时候,每一次的数据库修改都能被全部执行。
使用这种方式来维护数据库的升级,不管版本怎样更新,都可以保证数据库的表结构是最新的,而且表中的数据也完全不会丢失。
--------------------------------------使用SQL操作数据库-------------------------------------------
现在你已经掌握了创建和升级数据库的方法,接下来就该学习一下如何对表中的数据进行操作了。
其实我们可以对数据库进行的操作也就无非四种,添加(Create)、查询(Rectrieve)、更新(Update),删除(Delete)。
每一种操作又各自对应了一种SQL命令,添加数据时使用 insert,查询数据时使用 select,更新数据时使用 update,删除数据时使用 delete。
而作为一个开发者熟练使用SQL语句是非常有必要的,Android 提供了一系列方法,使得可以直接通过SQL来操作数据库。
下面简略演示一下,如何直接使用SQL来完成增删改查的操作。
添加数据的方法如下:
/** * SQL插入语句: * INSERT INTO Book(name,author,pages,price) VALUES * ("The Da Vinci Code" ,"Dan Brown",454,16.96); */ db.execSQL("INSERT INTO Book(name,author,pages,price) VALUES(?,?,?,?", new String[]{"The Lost Symbol", "Dan Brown", "510", "19.95"});
删除数据的方法如下:
/** *SQL删除语句: * DELETE FROM Book WHERE pages > 500; */ db.execSQL("DELETE FROM Book WHERE pages > ?",new String[]{"500"});
更新数据的方法如下:
/** * SQL更新语句: * UPDATE Book SET price = 10.99 WHERE name = "The Da Vinci Code" ; */ db.execSQL("UPDATE Book SET price = ? WHERE name = ?", new String[]{"10.99", "The Da Vinci Code"});
查询数据的方法如下:
/** * SQL查询语句: * SELECT * FROM BOOK ; */ db.rawQuery("SELECT * FROM BOOK", null);
注意:除了查询数据的时候调用的是 SQLiteDatabase 的 rawQuery()方法,其他的操作都是调用的 execSQL()方法。
--------------------------------------------添加数据------------------------------------------------
并非所有开发者都能非常熟练度使用SQL语言,因此 Android 也提供了一系列的辅助性方法,使得在 Android 中即使不去编写 SQL 语句,也能轻松完成所有的增删改查操作。
SQLiteDatabase 中提供了一个 insert() 方法,这个方法就是专门用于添加数据的。
insert() 方法接收三个参数,第一个参数是表名,我们希望向哪张表里添加数据,这里就传入该表的名字。第二个参数用于在未指定添加数据的情况下给某些可为空的列自动赋值NULL,一般用不到这个功能,直接传入 null 即可。第三个参数是一个 ContentValues 对象,它提供了一系列的 put() 方法重载,用于向 ContentValues 中添加数据,只需要将表中的每个列名及相对应的待添加数据传入即可。
public void onClick(View v) { //获取 SQLiteDatabase 对象 SQLiteDatabase db = dbHelper.getWritableDatabase(); //使用ContentValues 对数据进行组装 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); }
这里只对Book表里其中四列的数据进行了组装,id并没有赋值。因为创建表的时候就将 id 列设置为自动增长了,它的值会在入库的时候自动生成,不需要手动赋值。
--------------------------------------------更新数据------------------------------------------------
SQLiteDatabase 中提供了一个非常好用的 update() 方法用于对数据进行更新,这个方法接收四个参数,第一个参数和 insert()方法一样,也是表名,在这里指定去更新哪张表里的数据。第二个参数是 ContentValues 对象,把要更新的数据在这里组装进去。第三、第四个参数用于去约束更新某一行的数据,不指定的话默认就是更新所有行。
public void onClick(View v) { SQLiteDatabase db = dbHelper.getWritableDatabase(); ContentValues values = new ContentValues(); values.put("price", 10.99); //?是一个占位符,通过字符串数组为每个占位符指定相应的内容 db.update("Book", values, "name = ?", new String[]{"The Da Vinci Code"}); }
--------------------------------------------删除数据------------------------------------------------
SQLiteDatabase 中提供了一个 delete()方法专门用于删除数据,这个方法接收三个参数,第一个参数仍然是表名,第二、第三个参数用于约束删除某一行或某几行的数据,不指定的话默认就是删除所有行。
public void onClick(View v) { SQLiteDatabase db = dbHelper.getWritableDatabase(); db.delete("Book", "pages > ?", new String[]{"500"}); }
--------------------------------------------查询数据------------------------------------------------
SQLiteDatabase 中还提供了一个 query() 方法用于对数据进行查询。这个方法的参数非常复杂,最短的一个方法重载也需要传入七个参数。
query()方法参数及对应SQL:
table:指定查询的表名,对应 from table_name
columns:指定查询的列名,对应 select column1,column2 ...
selection:指定 where 的约束条件,where column = value
selectionArgs:为 where 中的占位符提供具体的值
groupBy:指定需要分组的列,group by column
having:对分组后的结果进一步约束,having column = value
orderBy:指定查询结果的排序方式,order by column
虽然 query()方法的参数非常多,但是不必每条查询语句都指定上所有的参数,多数情况下只需传入少数几个参数就可以完成查询操作了。
调用 query()方法后会返回一个 Cursor 对象,查询到的所有数据都将从这个对象中取出。
public void onClick(View v) { SQLiteDatabase db = dbHelper.getWritableDatabase(); //查询Book表中的所有数据 Cursor cursor = db.query("Book", null, null, null, null, null, null, null); //遍历Curosr对象,取出数据并打印 while (cursor.moveToNext()) { String name = cursor.getString(cursor.getColumnIndex("name")); String author = cursor.getString(cursor.getColumnIndex("author")); int pages = cursor.getInt(cursor.getColumnIndex("pages")); double price = cursor.getDouble(cursor.getColumnIndex("price")); Log.d("woider", "Book Name:" + name + " Author:" + author + " Pages:" + pages + " Price:" + price); } //关闭Cursor cursor.close(); }
执行 query()方法之后会得到一个 Cursor对象,然后通过 Cursor 对象的 moveToNext()方法去遍历查询到的每一行数据。在这个循环中可以通过 Cursor 的 getColumnIndex() 方法获取到某一列在表中对应位置的索引,然后将这个索引传入到相应的取值方法中,就可以得到从数据库中读取到的数据了。最后别忘了调用 close()方法来关闭 Cursor。