SQLite 数据库存储——一款轻量级的关系型数据库
SQLite 数据库存储——一款轻量级的关系型数据库
文件存储和SharedPreferences存储毕竟只适用于去保存一些简单的数据和键值对,当需要存储大量复杂的关系型数据的时候,你就会发现以上两种存储方式很难应付得了。比如我们手机的短信程序中可能会有很多个会话,每个会话中又包含了很多条信息内容,并且大部分会话还可能各自对应了电话簿中的某个联系人。存储这些数据量大、结构性复杂的数据,使用数据库就可以做得到。
创建数据库(SQLiteOpenHelper帮助类)
创建数据库(SQLiteOpenHelper帮助类)
SQLiteOpenHelper 抽象类
SQLiteOpenHelper 抽象类
SQLiteOpenHelper 是一个抽象类,这意味着如果我们想要使用它的话,就需要创建一个自己的帮助类去继承它。SQLiteOpenHelper 中有两个抽象方法,分别是onCreate()和 onUpgrade(),我们必须在自己的帮助类里面重写这两个方法,然后分别在这两个方法中去实现创建、升级数据库的逻辑。
1、 getReadableDatabase() 和getWritableDatabase()
Android使用getWritableDatabase()和getReadableDatabase()方法都可以获取一个用于操作数据库的SQLiteDatabase实例。(getReadableDatabase()方法中会调用getWritableDatabase()方法)
SQLiteOpenHelper 中 还 有 两 个 非 常 重 要 的 实 例 方 法。两个方法都可以创建或打开一个现有的数据库(如果数据库已存在则直接打开,否则创建一个新的数据库),并返回一个可对数据库进行读写操作的对象。不同的是,当数据库不可写入的时候(如磁盘空间已满)getReadableDatabase()方法返回的对象将以只读的方式去打开数据库,而 getWritableDatabase()方法则将出现异常。
二者区别和对应的源码解析,可以参考:http://blog.youkuaiyun.com/alex_zhuang/article/details/7342840简单易懂。这两个方法中会调用抽象类实现之后的onCreate()和 onUpgrade()
2、SQLiteOpenHelper构造方法
有两个构造方法可供重写,一般使用参数少一点的那个构造方法即可。这个构造方法中接收四个参数,第一个参数是 Context。第二个参数是数据库名,创建数据库时使用的就是这里指定的名称。第三个参数允许我们在查询数据的时候返回一个自定义的 Cursor,一般都是传入 null。第四个参数表示当前数据库的版本号,可用于对数据库进行升级操作。
3、获取数据库
构建出SQLiteOpenHelper的实例之后,再调用它的 getReadableDatabase()或 getWritableDatabase()方法就能够创建数据库了,数据库文件会存放在/data/data//databases/目录下。此时,重写的 onCreate()方法也会得到执行,所以通常会在这里去处理一些创建表的逻辑。
package com.example.zzz.study; import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.widget.Toast; /** * Created by zzz on 2017/8/30. */ 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)"; private Context myContext; public MyDatabaseHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) { super(context, name, factory, version); myContext=context; } @Override public void onCreate(SQLiteDatabase sqLiteDatabase) { sqLiteDatabase.execSQL(CREATE_BOOK); Toast.makeText(myContext,"新建数据库成功",Toast.LENGTH_LONG).show(); } @Override public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) { } }
升级数据库——onUpgrade()
升级数据库——onUpgrade()
注意:一旦数据库创建成功,除非是你删掉应用重新装,否则数据库已经存在,再次获取数据库的时候不会再调用onCreate,所以不能直接在里面升级修改数据库。
最简单除暴的升级
public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) { sqLiteDatabase.execSQL("drop table if exists book"); onCreate(sqLiteDatabase); }
并在构造函数中传入一个更大的值,因为之前是1,这次我传入2
final MyDatabaseHelper myDatabaseHelper=new MyDatabaseHelper(this,"BookStore.db",null,2); createDatabase.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { myDatabaseHelper.getWritableDatabase(); } });
//也就是说每次使用构造函数时,都会调用onCreate()或onUpgrade();
添加数据—— insert()
添加数据—— insert()
CRUD: C 代表添加(Create),R 代表查询(Retrieve),U代表更新(Update),D代表删除(Delete)。每一种操作又各自对应了一种 SQL命令,添加数据时使用 insert,查询数据时使用 select,更新数据时使用 update,删除数据时使用 delete。
SQLiteDatabase 中提供了一个 insert()方法,这个方法就是专门用于添加数据的。它接收三个参数,第一个参数是表名,我们希望向哪张表里添加数据,这里就传入该表的名字。第二个参数用于在未指定添加数据的情况下给某些可为空的列自动赋值 NULL,一般我们用不到这个功能,直接传入 null 即可。第三个参数是一个 ContentValues 对象,它提供了一系列的 put()方法重载,用于向 ContentValues 中添加数据,只需要将表中的每个列名以及相应的待添加数据传入即可。
删除数据—— DELETE()
删除数据—— DELETE()
参数:接收三个参数,第一个参数仍然是表名,第二、第三个参数又是用于去约束删除某一
行或某几行的数据,不指定的话默认就是删除所有行。
Button deleteData=(Button)findViewById(R.id.button3); deleteData.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { SQLiteDatabase db=myDatabaseHelper.getWritableDatabase(); db.delete("book","id>?",new String[]{"4"}); } });
更新数据—— update()
更新数据—— update()
参数:第一个参数和 insert()方法一样,也是表名,指定去更新哪张表里的数
据。第二个参数是 ContentValues 对象,把更新数据在这里组装进去。第三、第四个参数
用于去约束更新某一行或某几行中的数据,不指定的话默认就是更新所有行Button updateButton=(Button)findViewById(R.id.button4); updateButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { SQLiteDatabase db=myDatabaseHelper.getWritableDatabase(); ContentValues contentValues=new ContentValues(); contentValues.put("name","HKT48"); db.update("book",contentValues,"name=?",new String[]{"AKB48"}); } });
查询数据——query()
查询数据——query()
参数:参数非常复杂,最短的一个方法重载也需要传入七个参数。第一个参数还是表名,第二个参数用于指定去查询哪几列,如果不指定则默认查询所有列。第三、第四个参数用于去约束查询某一行或某几行的数据,不指定则默认是查询所有行的数据。第五个参数用于指定需要去 group by的列,不指定则表示不对查询结果进行 group by操作。第六个参数用于对 group by之后的数据进行进一步的过滤,不指定则表示不进行过滤。第七个参数用于指定查询结果的排序方式,不指定则表示使用默认的排序方式。
Button queryButton = (Button)findViewById(R.id.button0); queryButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { TextView textView=(TextView)findViewById(R.id.textView); textView.setText(""); SQLiteDatabase db=myDatabaseHelper.getWritableDatabase(); Cursor cursor=db.query("book",null,null,null,null,null,null); if (cursor.moveToFirst()){ do { int id=cursor.getInt(cursor.getColumnIndex("id")); 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")); String word ="id: "+id+" name: "+name+" author: "+author+ " pages: "+pages+" price: "+price+"\n"; textView.append(word); }while (cursor.moveToNext()); } cursor.close(); } });
使用SQL操作数据库
1. 添加数据:db.execSQL("insert into Book (name, author, pages, price) values(?, ?, ?, ?)", new String[] { "The Lost Symbol", "Dan Brown", "510", "19.95" });
2. 更新数据:db.execSQL("update Book set price = ? where name = ?", new String[] { "10.99", "The Da Vinci Code" });
3. 删除数据:db.execSQL("delete from Book where pages > ?", new String[] { "500" });
4. 查询数据:db.rawQuery("select * from book",null);
SQLiteDatabase db= ....; Cursor cursor = db.rawQuery("select * from person",null); while (cursor.moveToNext()) { int personid = cursor.getInt(0); //获取第一列的值,第一列的索引从0开始 String name = cursor.getString(1);//获取第二列的值 int age = cursor.getInt(2);//获取第三列的值 } cursor.close(); db.close();
使用事务
db.beginTransaction(); //手动设置开始事务
db.setTransactionSuccessful(); //设置事务处理成功,不设置会自动回滚不提交。
db.endTransaction(); //处理完成
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(); // 结束事务 } }
升级数据库的最佳写法
升级数据库的最佳写法
前面数据库升级的方式会导致用户升级应用时候,数据全部丢失,太简单粗暴了。
比如,多增加了一个表,此时如果用户重新升级覆盖,不会create,所以,在升级中处理,用switch可以解决。
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: } } }
switch 中每一个 case 的最后都是没有使用 break的,为了保证在跨版本升级的时候,每一次的数据库修改都能被全部执行到。比如用户当前是从第二版程序升级到第三版程序的,那么 case 2中的逻辑就会执行。而如果用户是直接从第一版程序升级到第三版程序的,那么 case 1 和 case 2 中的逻辑都会执行。使用这种方式来维护数据库的升级,不管版本怎样更新,都可以保证数据库的表结构是最新的,而且表中的数据也完全不会丢失了。