SQLite数据库存储
SQLite是一款轻量级的关系型数据库,它的运算速度非常快, 占用资源很少,通常只需要几百 K的内存就足够了,因而特别适合在移动设备上使用
SQLite 不仅支持标准的 SQL语法,还遵循了数据库的 ACID 事务,所以只要你以前使用过其他的 关系型数据库,就可以很快地上手 SQLite
SQLite又比一般的数据库要简单得多,它甚 至不用设置用户名和密码就可以使用
Android 正是把这个功能极为强大的数据库嵌入到了 系统当中,使得本地持久化的功能有了一次质的飞跃
前面我们所学的文件存储和 SharedPreferences 存储毕竟只适用于去保存一些简单的数据 和键值对,当需要存储大量复杂的关系型数据的时候,你就会发现以上两种存储方式很难应 付得了
比如我们手机的短信程序中可能会有很多个会话,每个会话中又包含了很多条信息 内容,并且大部分会话还可能各自对应了电话簿中的某个联系人。很难想象如何用文件或者 SharedPreferences来存储这些数据量大、结构性复杂的数据吧?但是使用数据库就可以做得 到
创建数据库
Android为了让我们能够更加方便地管理数据库,专门提供了一个 SQLiteOpenHelper
帮助类
首先你要知道 SQLiteOpenHelper
是一个抽象类,这意味着如果我们想要使用它的话, 就需要创建一个自己的帮助类去继承它
SQLiteOpenHelper
中有两个抽象方法,分别是 onCreate()
和 onUpgrade()
,我们必须在自己的帮助类里面重写这两个方法,然后分别在这两 个方法中去实现创建、升级数据库的逻辑
SQLiteOpenHelper
中还有两个非常重要的实例方法 , getReadableDatabase()
和 getWritableDatabase()
。这两个方法都可以创建或打开一个现有的数据库(如果数据库已存在则直接打开,否则创建一个新的数据库),并返回一个可对数据库进行读写操作的对象。不 同的是,当数据库不可写入的时候(如磁盘空间已满)getReadableDatabase()
方法返回的对象将以只读的方式去打开数据库,而 getWritableDatabase()
方法则将出现异常
SQLiteOpenHelper中有两个构造方法可供重写,一般使用参数少一点的那个构造方法即可。这个构造方法中接收四个参数,第一个参数是 Context。第二个参数是数据库名,创建数据库时使用的就是这里指定的名称。第三个参数允许我们在查询数据的时候返回一个自定义的 Cursor,一般都是传入 null。 第四个参数表示当前数据库的版本号,可用于对数据库进行升级操作
构建出 SQLiteOpenHelper 的实例之后,再调用它的 getReadableDatabase()
或 getWritableDatabase()
方法就能够创建数据库了,数据库文件会存放在/data/data/包名/databases/
目录下。 此时,重写的 onCreate()
方法也会得到执行,所以通常会在这里去处理一些创建表的逻辑
栗子
这里我们希望创建一个名为 BookStore.db
的数据库,然后在这个数据库中新建一张Book
表,表中有 id(主键)、作者、价格、页数和书名等列
创建数据库表当然还是需要用建表 语句的,Book
表的建表语句如下所示
create table Book
( id integer primary key autoincrement,
author text,
price real,
pages integer,
name text)
SQLite的数据类型很简单,integer
表示整型,real
表示浮点型,text
表示文本类型,blob`表示二进制类型
上述建表语句中我们还 使用了 primary key
将 id
列设为主键,并用 autoincrement
关键字表示id
列是自增长的
新建 MyDatabaseHelper 类继承自 SQLiteOpenHelper
class MyDatabaseHelper extends SQLiteOpenHelper {
private static final String CREATE_BOOK = "create table book (id integer primary key autoincrement,author text,price real,pages integer,name text)";
private Context mContext;
public MyDatabaseHelper(@Nullable Context context, @Nullable String name, @Nullable SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
mContext = context;
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_BOOK);
Toast.makeText(mContext, "Create succeeded", Toast.LENGTH_SHORT).show();
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
activity_main.xml
<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"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Create database" />
</LinearLayout>
MainActivity.java
public class MainActivity extends AppCompatActivity {
private MyDatabaseHelper myDatabaseHelper;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
myDatabaseHelper = new MyDatabaseHelper(this,"BookStore.db",null,1);
Button create_database = findViewById(R.id.create_database);
create_database.setOnClickListener(v -> {
myDatabaseHelper.getWritableDatabase();
});
}
}
onCreate()
方法中构建了一个 MyDatabaseHelper 对象,并且通过构造函数的 参数将数据库名指定为 BookStore.db,版本号指定为 1
然后在 Create database按钮的点击 事件里调用了getWritableDatabase()
方法
这样当第一次点击 Createdatabase 按钮时,就会检测 到当前程序中并没有BookStore.db这个数据库,于是会创建该数据库并调用MyDatabaseHelper中的 onCreate()
方法,这样 Book表也就得到了创建,然后会弹出一个 Toast提示创建成功
再次点击 Createdatabase按钮时,会发现此时已经存在 BookStore.db数据库了,因此不会再 创建一次
此时 BookStore.db数据库和 Book表应该都已经创建成功了,因为当你再次点击 Create database按钮时不会再有 Toast弹出。在 /data/data/packagename/databases 下多了 BookStore.db,还有一个 BookStore.db-journal 文件,这是一个为了让数据库能够支持事务而产生的临时日志文件,通常情况下这个文件大小是 0 字节
下面使用 Kotlin 完成
新建 MyDatabaseHelper
class MyDatabaseHelper extends SQLiteOpenHelper {
private static final String CREATE_BOOK = "create table book (id integer primary key autoincrement,author text,price real,pages integer,name text)";
private Context mContext;
public MyDatabaseHelper(@Nullable Context context, @Nullable String name, @Nullable SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
mContext = context;
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_BOOK);
Toast.makeText(mContext, "Create succeeded", Toast.LENGTH_SHORT).show();
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
MainActivity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val helper = MyDatabaseHelper(this, "BookStore.db", 1)
create_database.setOnClickListener {
helper.writableDatabase
}
}
}
使用 Database Navigator 查看
把 BookStore.db 右键 Save as 保存到本机
安装插件 Database Navigator,重启 AS 后,侧边栏会多出 DB Browser
打开的页面中选中 刚才的 BookStore.db
确定之后,可以看到确实有一个 Book 表,我们创建成功了
升级数据库
如果我们想 再添加一张 Category 表用于记录书籍的分类该怎么做呢? 比如 Category 表中有 id(主键)、分类名和分类代码这几个列,那么建表语句就可以 写成
create table Category ( id integer primary key autoincrement, category_name text, category_code integer)
如果单纯的把这个语句像创建 Book 表一样,加入到 MyDatabaseHelper 中执行,是不能成功创建表的,因为此时 BookStore.db 数据库已经存在了,之后不 管我们怎样点击 Create database 按钮,MyDatabaseHelper 中的 onCreate()
方法都不会再次执 行,因此新添加的表也就无法得到创建了。 解决这个问题的办法也相当简单,只需要先将程序卸载掉,然后重新运行,这时 BookStore.db 数据库已经不存在了,如果再点击 Create database 按钮,MyDatabaseHelper 中 的 onCreate()
方法就会执行,这时 Category表就可以创建成功了
不过通过卸载程序的方式来新增一张表是很极端的做法,其实我们只需要运用 MyDatabaseHelper 的升级功能,就可以轻松解决这个问题
MyDatabaseHelper
class MyDatabaseHelper extends SQLiteOpenHelper {
private static final String CREATE_BOOK = "create table book (id integer primary key autoincrement,author text,price real,pages integer,name text)";
private static final String CREATE_CATEGORY ="create table Category ( id integer primary key autoincrement, category_name text, category_code integer) ";
private Context mContext;
public MyDatabaseHelper(@Nullable Context context, @Nullable String name, @Nullable SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
mContext = context;
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_BOOK);
db.execSQL(CREATE_CATEGORY);
Toast.makeText(mContext, "Create succeeded", 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);
}
}
我们在 onUpgrade()
方法中执行了两条 DROP 语句,如果发现数据库中已经 存在 Book 表或 Category 表,就将这两张表删除掉,然后再调用 onCreate()
方法去重新创建。这里先将已经存在的表删除掉,是因为如果在创建表时发现这张表已经存在了,就会直接报错
接下来的问题就是如何让 onUpgrade()
方法能够执行了,还记得 SQLiteOpenHelper的构 造方法里接收的第四个参数吗?它表示当前数据库的版本号,之前我们传入的是 1,现在只 要传入一个比 1大的数,就可以让 onUpgrade()
方法得到执行了。修改 MainActivity中的代码
public class MainActivity extends AppCompatActivity {
private MyDatabaseHelper myDatabaseHelper;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
myDatabaseHelper = new MyDatabaseHelper(this,"BookStore.db",null,2);
Button create_database = findViewById(R.id.create_database);
create_database.setOnClickListener(v -> {
myDatabaseHelper.getWritableDatabase();
});
}
}
这里将数据库版本号指定为 2,表示我们对数据库进行升级
现在重新运行程序,并 点击 Createdatabase 按钮,这时就会再次弹出创建成功的提示。为了验证一下 Category表是 不是已经创建成功了,我们重新把 Book.db 导出到计算机,在 DB Browser 中重新导入
使用Kotlin实现
class MyDatabaseHelper(val context: Context,name:String,version:Int):SQLiteOpenHelper(context,name,null,version) {
private val createBook = "create table Book( id integer primary key autoincrement,"+
"author text,price real,pages integer,name text)"
private val creatCategory = "create table Category ( id integer primary key autoincrement, category_name text, category_code integer) "
override fun onCreate(db: SQLiteDatabase) {
db.execSQL(createBook)
db.execSQL(creatCategory)
Toast.makeText(context,"Create succeeded",Toast.LENGTH_SHORT).show()
}
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
db.execSQL("drop table if exists Book")
db.execSQL("drop table if exists Category")
onCreate(db)
}
}