Android SQLite 数据库存储详解
SQLite 是一款轻量级的关系型数据库
Android为了让我们能够更加方便地管理数据库,专门提供了一个SQLiteOpenHelper帮助类,借助这个类就可以非常简单地对数据库进行创建和升级。
SQLiteOpenHelper是一个抽象类。
SQLiteOpenHelper中有两个抽象方法,分别是onCreate()和onUpgrade()。
SQLiteOpenHelper中还有两个非常重要的实例方法,getReadableDatabase()和getWritableDatabase()。两者的区别参考
Android SQLiteOpenHelper 实例方法getReadableDatabase()和getWritableDatabase() 区别
数据库文件存放在 : /data/data/
SQLite不像其他的数据库拥有众多繁杂的数据类型,它的数据类型很简单,integer表示整型,real表示浮点型,text表示文本类型,blob表示二进制类型。
SQLiteOpenHelper中有两个构造方法可供重写。
public SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version) {
this(context, name, factory, version, null);
}
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;
}
创建数据库
举个栗子:
创建一个BookStore.db的数据库。然后在这个数据库中新建一张Book表,表中有id(主键)、作者、价格、页数和书名等列。
建表语句
create table Book (
id integer primary key autoincrement,
author text,
price real,
pages integer,
name text)
升级数据库
再添加一张Category表用于记录书籍的分类。
Category表中有id(主键)、分类名和分类代码
create table Category (
id integer primary key autoincrement,
category_name text,
category_code integer)
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_BOOK);
db.execSQL(CREATE_CATEGORY);
Toast.makeText(mContext, "Create succeeded", Toast.LENGTH_SHORT). show();
}
因为此时BookStore.db数据库已经存在了,之后不管我们怎样点击Create database按钮,MyDatabaseHelper中的onCreate()方法都不会再次执行,因此新添加的表也就无法得到创建了。
需要更改update方法
@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);
}
更改数据库版本号后,运行项目。即可更新数据库。创建了Category表。
但是这种方法是相当粗暴。直接删表。在正常的产品项目中是不可能这么干的。
正确的更新数据库方法是:
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_BOOK);
db.execSQL(CREATE_CATEGORY);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
switch (oldVersion) {
case 1:
db.execSQL(CREATE_CATEGORY);
default:
}
}
在onCreate()方法里我们新增了一条建表语句,然后又在onUpgrade()方法中添加了一个switch判断,如果用户当前数据库的版本号是1,就只会创建一张Category表。这样当用户是直接安装的第二版的程序时,就会将两张表一起创建。而当用户是使用第二版的程序覆盖安装第一版的程序时,就会进入到升级数据库的操作中,此时由于Book表已经存在了,因此只需要创建一张Category表即可。
ADB 查询数据库
如果使用FileExplorer ,最多只能看到databases目录下有一个BookStore.db 文件。
要查询数据库数据,需要 使用 adb shell 。
adb.exe 存放在sdk的platform-tools目录下,如果想要在命令行中使用这个工具,就需要先把它的路径配置到环境变量里。
如果你使用的是Windows系统,可以右击我的电脑→属性→高级→环境变量,然后在系统变量里找到Path并点击编辑,将platform-tools目录配置进去。如果要使用真机调试程序,真机需要root权限。
配置好环境变量后,输入 adb shell 。如果出现如下格式。说明adb 指令权限不够。再输入 su 指令即可。
C:\Users\Tan>adb shell
shell@cancro:/ $
输入su 后,显示:
C:\Users\Tan>adb shell
shell@cancro:/ $ su
root@cancro:/ #
参考: adb shell 访问真机手机数据库文件
进入数据后,用sqlite3 指令增删改查数据时,会提示sqlite3 不是内部指令。这是因为手机中没有sqlite3 文件。
解决办法:参考如何设置Android手机的sqlite3命令环境
如果条件允许可以使用模拟器。模拟器是支持sqlite3指令集的。
也可以使用【RootExplorerRE文件管理器.apk】 ,安装到手机上直接查看手机data/data/目录下的文件。查看数据库。但是需要手机root权限。
下载地址:下载链接
也可以将 数据库文件导出,然后利用第三方插件查看。参考:Android开发工具---SQLiteManager插件
输入 adb shell
然后使用cd命令进行到 /data/data/com.example.databasetest/databases/目录下 (注意cd 后面有空格)
使用ls命令查看到该目录里的文件
接下来我们就要借助sqlite命令来打开数据库了,只需要键入sqlite3,后面加上数据库名即可。 sqlite3 BookStore.db
进入数据库后,
输入.table 查看数据库中的表
输入.schema 查看建表语句
键入.exit或.quit命令可以退出数据库的编辑,再键入exit命令就可以退出设备控制台了。
添加数据
我们可以对数据进行的操作也就无非四种,即CRUD。其中C代表添加(Create),R代表查询(Retrieve),U代表更新(Update),D代表删除(Delete)。每一种操作又各自对应了一种SQL命令,添加数据时使用insert,查询数据时使用select,更新数据时使用update,删除数据时使用delete。
方法一:
insert()方法
接收三个参数,第一个参数是表名,我们希望向哪张表里添加数据,这里就传入该表的名字。
第二个参数用于在未指定添加数据的情况下给某些可为空的列自动赋值NULL,一般我们用不到这个功能,直接传入null即可。
第三个参数是一个ContentValues对象,它提供了一系列的put()方法重载,用于向ContentValues中添加数据,只需要将表中的每个列名以及相应的待添加数据传入即可。
Button addData = (Button) findViewById(R.id.add_data);
addData.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
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); // 插入第二条数据
}
});
db.execSQL("insert into Book (name, author, pages, price) values(?, ?, ?, ?)",
new String[] { "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" });
db.execSQL 方法:
用adb 进入BookStores.db 数据库。输入查询语句 select * from Book ,即可查看表中的数据。
方法三:在adb 指令中,用sql 语句增加数据。
adb shell
cd /data/data/com.example.databasetest/databases/ //进入数据库目录
ls //查看目录下文件
sqlite3 BookStore.db // 利用sqlite3 指令进入数据库
.table //查看数据库中的表
select * from Book; //查询Book表中的数据
insert into Book ( name , author , pages , price) values ('diyihangdaima' , 'guolin', '550' ,'70.5');
//向Book表中插入一条数据
// 注意 ,用 insert into 指令 ,key 值和values 值 不能用 new String[] {"diyihsangdaima "……} 这种格式。并且,values中的值用单引号。
select * from Book; //查询Book表中的数据
更新数据
方法一:
update() ; 方法
方法接收四个参数,
第一个参数和insert()方法一样,也是表名,在这里指定去更新哪张表里的数据。
第二个参数是ContentValues对象,要把更新数据在这里组装进去。
第三、第四个参数用于去约束更新某一行或某几行中的数据,不指定的话默认就是更新所有行。
Button updateData = (Button) findViewById(R.id.update_data);
updateData.setOnClickListener(new OnClickListener() {
@Override
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" });
}
});
方法二:
db.execSQL("update Book set price = ? where name = ?", new String[] { "10.99", "The Da Vinci Code" });
在cmd中用sqlite3 指令更新数据;
sqlite> update Book set price = '9.99' where id = 1; //更新ID=1 的数据
sqlite> update Book set price = '50' where author = 'guolin'; // 更新author 为guolin的数据
删除数据
方法一:
delete()方法
方法接收三个参数,
第一个参数仍然是表名
第二、第三个参数又是用于去约束删除某一行或某几行的数据,不指定的话默认就是删除所有行
Button deleteButton = (Button) findViewById(R.id.delete_data);
deleteButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
db.delete("Book", "pages > ?", new String[] { "500" });
}
});
db.execSQL("delete from Book where pages > ?", new String[] { "500" });
sqlite> delete from Book where id = 1;
sqlite> delete from Book where author = 'guolin';
SQL的全称是Structured Query Language,翻译成中文就是结构化查询语言
方法一:
query();
query()方法用于对数据进行查询。这个方法的参数非常复杂,最短的一个方法重载也需要传入七个参数。那我们就先来看一下这七个参数各自的含义吧,
第一个参数不用说,当然还是表名,表示我们希望从哪张表中查询数据。
第二个参数用于指定去查询哪几列,如果不指定则默认查询所有列。
第三、第四个参数用于去约束查询某一行或某几行的数据,不指定则默认是查询所有行的数据。
第五个参数用于指定需要去group by的列,不指定则表示不对查询结果进行group by操作。
第六个参数用于对group by之后的数据进行进一步的过滤,不指定则表示不进行过滤。
第七个参数用于指定查询结果的排序方式,不指定则表示使用默认的排序方式。
query()方法参数 |
对应SQL部分 |
描述 |
table |
from table_name |
指定查询的表名 |
columns |
select column1, column2 |
指定查询的列名 |
selection |
where column = value |
指定where的约束条件 |
selectionArgs |
- |
为where中的占位符提供具体的值 |
groupBy |
group by column |
指定需要group by的列 |
having |
having column = value |
对group by后的结果进一步约束 |
orderBy |
order by column1, column2 |
指定查询结果的排序方式 |
Button queryButton = (Button) findViewById(R.id.query_data);
queryButton.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("MainActivity", "book name is " + name);
Log.d("MainActivity", "book author is " + author);
Log.d("MainActivity", "book pages is " + pages);
Log.d("MainActivity", "book price is " + price);
} while (cursor.moveToNext());
}
cursor.close(); // 不关闭会导致内存泄漏
}
});
方法二:
db.rawQuery("select * from Book", null);
方法三:
sqlite
select * from Book;
sqlite> select price from Book;
19.95
16.96
19.95
Button replaceData = (Button) findViewById(R.id.replace_data);
replaceData.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
db.beginTransaction(); // 开启事务
try {
db.delete("Book", null, null);
if (true) {
// 在这里手动抛出一个异常,让事务失败
throw new NullPointerException();
}
ContentValues values = new ContentValues();
values.put("name", "Game of Thrones");
values.put("author", "George Martin");
values.put("pages", 720);
values.put("price", 20.85);
db.insert("Book", null, values);
db.setTransactionSuccessful(); // 事务已经执行成功
} catch (Exception e) {
e.printStackTrace();
} finally {
db.endTransaction(); // 结束事务
}
}
});
数据库升级最佳写法
给Book表和Category表之间建立关联,需要在Book表中添加一个category_id的字段。
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, "
+ "category_id integer)";
public static final String CREATE_CATEGORY = "create table Category ("
+ "id integer primary key autoincrement, "
+ "category_name text, "
+ "category_code integer)";
public MyDatabaseHelper(Context context, String name,
CursorFactory factory, int version) {
super(context, name, factory, version);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_BOOK);
db.execSQL(CREATE_CATEGORY);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
switch (oldVersion) {
case 1:
db.execSQL(CREATE_CATEGORY);
case 2:
db.execSQL("alter table Book add column category_id integer");
default:
}
}
}
在onUpgrade()方法里,我们添加了一个新的case,如果当前数据库的版本号是2,就会执行alter命令来为Book表新增一个category_id列。
switch中每一个case的最后都是没有使用break的!