简介
SQLite是一款轻量级的关系型数据库,运算速度快,占用资源少,通常只需要几百K的内存就足够了,因而特别适合在移动设备上使用。SQLite不仅支持标准的SQL语法,还遵循了数据库的ACID事务。
创建数据库
Android专门提供了一个SQLiteOpenHelper帮助类让我们能够更加方便地管理数据库,借助这个类就可以非常简单地对数据库进行创建和升级。
SQLiteOpenHelper是一个抽象类,如果我们要使用它,就需要创建一个自己的帮助类去继承它。SQLiteOpenHelper中有两个抽象方法,分别是onCreate()和onUpgrade(),我们必须在自己的帮助类里面重写这两个方法,然后分别在这两个方法中去实现创建、升级数据库的逻辑。
SQLiteOpenHelper中还有两个非常重要的实例方法,getReadableDatabase()和getWritableDatabase()。这两个方法都可以创建或打开一个现有的数据库(如果数据库已存在则直接打开,否则创建一个新的数据库),并返回一个可对数据库进行读写操作的对象。不同的是,当数据库不可写入的时候(如磁盘空间已满)getReadableDatabase()方法返回的对象将以只读的方式去打开数据库,而getWritableDatabase()方法则将出现异常。
SQLiteOpenHelper中有两个构造方法可供重写,一般使用参数少一点的那个构造方法即可。这个构造方法中接收四个参数:
- 参数1:Context
- 参数2:数据库名
- 参数3:允许我们在查询数据的时候返回一个自定义的Cursor,一般都是传入null
- 参数4:当前数据库的版本号,可用于对数据库进行升级操作
构建出SQLiteOpenHelper的实例之后,再调用它的getReadableDatabase()或getWritableDatabase()方法就能够创建数据库了,数据库文件会存放在/data/data//databases/目录下。此时,重写的onCreate()方法也会得到执行,所以通常会在这里去处理一些创建表的逻辑。
现在,我新建一个Android项目,其中:
- 数据库名称:BookStore
- 表名称:Book
- 字段:id(主键)、作者、价格、页数和书名等列
SQLite数据类型很简单,integer表示整型,real表示浮点型,text表示文本类型,blob表示二进制类型。
新建MyDatabaseHelper类继承自SQLiteOpenHelper,代码如下:
public class MyDatabaseHelper extends SQLiteOpenHelper {
public static final String CRERTE_BOOK = "create table book ("
+ "id integer primary key autoincrement, "
+ "author text, "
+ "price real, "
+ "pages integer, "
+ "name text)";
private Context mContext;
public MyDatabaseHelper(Context context, String name,
CursorFactory factory, int version) {
super(context, name, factory, version);
mContext = context;
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CRERTE_BOOK);
Toast.makeText(mContext, "Create succeeded", Toast.LENGTH_SHORT).show();
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
可以看到,我们把建表语句定义成了一个字符串常量,然后在onCreate()方法中又调用了SQLiteDatabase的execSQL()方法去执行这条建表语句,并弹出一个Toast提示创建成功,这样就可以保证在数据库创建完成的同时还能成功创建Book表。
新建activity_sqlite_storage.xml,代码如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<Button
android:id="@+id/create_database_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="创建数据库" />
</LinearLayout>
新建SqliteStorageActivity,代码如下:
public class SqliteStorageActivity extends Activity {
private MyDatabaseHelper dbHelper;
private Button mCreateDatabaseButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sqlite_storage);
dbHelper = new MyDatabaseHelper(this, "BookStore.db", null, 1);
mCreateDatabaseButton = (Button) findViewById(R.id.create_database_button);
mCreateDatabaseButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
dbHelper.getWritableDatabase();
}
});
}
}
在onCreate()方法中构建了一个MyDatabaseHelper对象,并且通过构造函数的参数将数据库名指定为BookStore.db,版本号指定为1,然后在”创建数据库“按钮的点击事件里调用了getWritableDatabase()方法。这样当第一次点击Create database按钮时,就会检测到当前程序中并没有BookStore.db这个数据库,于是会创建该数据库并调用MyDatabaseHelper中的onCreate()方法,这样Book表也就得到了创建,弹出一个Toast提示创建成功。再次点击”创建数据库“按钮时,会发现此时已经存在BookStore.db数据库了,因此不会再创建一次。
查看/data/data/com.knittedschool/databases/目录,会有两个数据库文件,一个正是我们创建的BookStore.db,而另一个BookStore.db-journal则是为了让数据库能够支持事务而产生的临时日志文件,通常情况下这个文件的大小都是0字节。如下图:
将BookStore.db保存到本地,使用SQLiteExpertPers打开,此时数据库中有两张表,android_metadata表是每个数据库中都会自动生成的,而另外一张Book表就是我们在MyDatabaseHelper中创建的,如下图:
升级数据库
onUpgrade()方法是用于对数据库进行升级的,现在我们添加一张Category表用于记录书籍的分类。修改MyDatabaseHelper,代码如下:
public class MyDatabaseHelper extends SQLiteOpenHelper {
public static final String CRERTE_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)";
private Context mContext;
public MyDatabaseHelper(Context context, String name,
CursorFactory factory, int version) {
super(context, name, factory, version);
mContext = context;
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CRERTE_BOOK);
db.execSQL(CREATE_CATEGORY);
Toast.makeText(mContext, "创建数据库成功", Toast.LENGTH_SHORT).show();
}
@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);
}
}
可以看到,首先增加CREATE_CATEGORY用于创建表Category,然后在onCreate()中执行创建语句,然后在onUpgrade()中执行两条DROP语句,如果发现数据库中已经存在Book表或Category表,就将这两张表删除,最后再调用onCreate()方法去重新创建。
修改SqliteStorageActivity中的代码,将SQLiteOpenHelper构造方法里接收的第四个参数(我们之前传入的是1),现在只要传入一个比1大的数,就可以让onUpgrade()方法得到执行了,代码如下:
dbHelper = new MyDatabaseHelper(this, "BookStore.db", null, 2);
重新运行程序,并点击”成将数据库“按钮,然后使用SQLiteExpertPers打开BookStore.db,可以看到Category表已经创建成功了,同时也说明我们的升级功能实现了,如下图:
添加数据
SQLiteDatabase中提供了一个insert()方法,这个方法就是专门用于添加数据的。它接收三个参数:
- 参数1:表名
- 参数2:在未指定添加数据的情况下给某些可为空的列自动赋值NULL,一般我们用不到这个功能,直接传入null即可
- 参数3:ContentValues对象,它提供了一系列的put()方法重载,用于向ContentValues中添加数据,只需要将表中的每个列名以及相应的待添加数据传入即可。
修改activity_sqlite_storage.xml,如下代码:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<Button
android:id="@+id/create_database_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="创建数据库" />
<Button
android:id="@+id/add_data_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="添加数据" />
</LinearLayout>
修改SqliteStorageActivity,如下代码:
mAddDataButton = (Button) findViewById(R.id.add_data_button);
mAddDataButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
ContentValues values = new ContentValues();
// 开始组装第一条数据
values.put("name", "平凡的世界");
values.put("author", "路遥");
values.put("pages", 1264);
values.put("price", 55.10);
db.insert("Book", null, values); // 插入第一条数据
values.clear();
// 开始组装第二条数据
values.put("name", "追风筝的人");
values.put("author", "卡勒德·胡赛尼");
values.put("pages", 372);
values.put("price", 18.30);
db.insert("Book", null, values); // 插入第二条数据
}
});
在添加数据按钮的点击事件里面,我们先获取到了SQLiteDatabase对象,然后使用ContentValues来对要添加的数据进行组装。接下来调用了insert()方法将数据添加到表当中,上述代码中使用ContentValues分别组装了两次不同的内容,并调用了两次insert()方法。
点击一下”添加数据“按钮,此时两条数据已经添加成功了,打开数据库,如下图:
更新数据
SQLiteDatabase中提供了一个update()方法用于对数据进行更新,接收四个参数:
- 参数1:表名
- 参数2:ContentValues对象
- 参数3、4:用于去约束更新某一行或某几行中的数据,不指定的话默认就是更新所有行
在activity_sqlite_storage.xml增加一个按钮,如下代码:
<Button
android:id="@+id/update_data_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="更新数据" />
修改SqliteStorageActivity,代码如下:
mUpdteDataButton = (Button) findViewById(R.id.update_data_button);
mUpdteDataButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put("price", 25.50);
db.update("Book", values, "name = ?", new String[] { "追风筝的人" });
}
});
在更新数据按钮的点击事件里面构建了一个ContentValues对象,并且只给它指定了一组数据,说明我们只是想把价格这一列的数据更新成25.50。然后调用了SQLiteDatabase的update()方法去执行具体的更新操作,可以看到,这里使用了第三、第四个参数来指定具体更新哪几行。第三个参数对应的是SQL语句的where部分,表示去更新所有name等于?的行,而?是一个占位符,可以通过第四个参数提供的一个字符串数组为第三个参数中的每个占位符指定相应的内容。因此上述代码想表达的意图就是,将名字是”追风筝的人“的这本书的价格改成25.50。
运行程序,打开数据库文件,如下图:
删除数据
SQLiteDatabase中提供了一个delete()方法专门用于删除数据,这个方法接收三个参数:
- 参数1:表名
- 参数2、3:用于去约束删除某一行或某几行的数据,不指定的话默认就是删除所有行
在activity_sqlite_storage.xml增加一个删除按钮,如下代码:
<Button
android:id="@+id/delete_data_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="删除数据" />
修改SqliteStorageActivity,代码如下:
mDeleteDataButton = (Button) findViewById(R.id.delete_data_button);
mDeleteDataButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
db.delete("Book", "pages > ?", new String[] { "500" });
}
});
可以看到,我们在删除按钮的点击事件里指明去删除Book表中的数据,并且通过第二、第三个参数来指定仅删除那些页数超过500页的书籍。
运行程序,打开数据库文件,如下图:
查询数据
SQLiteDatabase中提供了一个query()方法用于对数据进行查询,这个方法的参数非常复杂,最短的一个方法重载也需要传入七个参数。那我们就先来看一下这七个参数各自的含义:
- 参数1:表名
- 参数2:指定查询哪几列,如果不指定则默认查询所有列
- 参数3、4:去约束查询某一行或某几行的数据,不指定则默认是查询所有行的数据
- 参数5:指定需要去group by的列,不指定则表示不对查询结果进行group by操作
- 参数6:对group by之后的数据进行进一步的过滤,不指定则表示不进行过滤
- 参数7:定查询结果的排序方式,不指定则表示使用默认的排序方式
调用query()方法后会返回一个Cursor对象,查询到的所有数据都将从这个对象中取出。
在activity_sqlite_storage.xml增加一个查询按钮,如下代码:
<Button
android:id="@+id/query_data_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="查询数据" />
然后修改SqliteStorageActivity,如下代码:、
mQueryDataButton = (Button) findViewById(R.id.query_data_button);
mQueryDataButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
// 查询Book表中所有的数据
Cursor cursor = db.query("Book", null, null, null, null, null, null);
if (cursor.moveToFirst()) {
do {
// 遍历Cursor对象,取出数据并打印
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(TAG, "Name:" + name);
Log.d(TAG, "Author:" + author);
Log.d(TAG, "Pages:" + pages);
Log.d(TAG, "Price:" + price);
} while (cursor.moveToNext());
}
cursor.close();
}
});
首先在查询按钮的点击事件里面调用了SQLiteDatabase的query()方法查询数据。这里的query()方法只使用了第一个参数指明去查询Book表,后面的参数全部为null。这就表示希望查询这张表中的所有数据,虽然这张表中目前只剩下一条数据了。查询完之后就得到了一个Cursor对象,接着调用它的moveToFirst()方法将数据的指针移动到第一行的位置,然后进入了一个循环当中,去遍历查询到的每一行数据。在这个循环中可以通过Cursor的getColumnIndex()方法获取到某一列在表中对应的位置索引,然后将这个索引传入到相应的取值方法中,就可以得到从数据库中读取到的数据了。接着使用Log的方式将取出的数据打印出来,借此来检查一下读取工作有没有成功完成。最后调用close()方法来关闭Cursor。
运行程序,点击“查询数据”按钮,如下图:
使用SQL操作数据库
Android充分考虑到了你们的编程习惯,同样提供了一系列的方法,可以直接通过SQL来操作数据库。
添加数据
db.execSQL("insert into Book (name, author, pages, price) values(?, ?, ?, ?)", new String[] { "平凡的世界", "路遥", "1264", "55.10" });
db.execSQL("insert into Book (name, author, pages, price) values(?, ?, ?, ?)", new String[] { "追风筝的人", "卡勒德·胡赛尼", "372", "18.30" });
更新数据
db.execSQL("update Book set price = ? where name = ?", new String[] { "25.50", "追风筝的人" });
删除数据
db.execSQL("delete from Book where pages > ?", new String[] { "500" });
查询数据
db.rawQuery("select * from Book", null);
可以看到,除了查询数据的时候调用的是SQLiteDatabase的rawQuery()方法,其他的操作都是调用的execSQL()方法。
实践案例
现在我们使用事务操作数据,事务的特性可以保证让某一系列的操作要么全部完成,要么一个都不会完成。
接下来,使用delete()方法将Book表中的数据删除,然后再使用insert()方法将新的数据添加到表中。要保证删除旧数据和添加新数据的操作必须一起完成,否则就还要继续保留原来的旧数据。修改activity_sqlite_storage.xml中的代码,如下所示:
<Button
android:id="@+id/replace_data_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="替换数据" />
修改SqliteStorageActivity中的代码,如下所示:
mReplaceDataButton = (Button) findViewById(R.id.replace_data_button);
mReplaceDataButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
db.beginTransaction(); // 开启事务
try {
db.delete("Book", null, null);
ContentValues values = new ContentValues();
values.put("name", "何以笙箫默");
values.put("author", "顾漫");
values.put("pages", 247);
values.put("price", 12.5);
db.insert("Book", null, values);
db.setTransactionSuccessful(); // 事务已经执行成功
} catch (Exception e) {
e.printStackTrace();
} finally {
db.endTransaction(); // 结束事务
}
}
});
上述代码就是Android中事务的标准用法,首先调用SQLiteDatabase的beginTransaction()方法来开启一个事务,然后在一个异常捕获的代码块中去执行具体的数据库操作,当所有的操作都完成之后,调用setTransactionSuccessful()表示事务已经执行成功了,最后在finally代码块中调用endTransaction()来结束事务。注意观察,我们在删除旧数据的操作完成后手动抛出了一个NullPointerException,这样添加新数据的代码就执行不到了。不过由于事务的存在,中途出现异常会导致事务的失败,此时旧数据应该是删除不掉的。
现在运行程序,并点击“替换数据”按钮,打开数据库,如下图: