安卓核心基础知识梳理之数据库部分

SQLite,是一款轻量型的数据库,是遵守ACID(原子性、一致性、隔离性、持久性)的关联式数据库管理系统,多用于嵌入式开发中。
Android平台中嵌入了一个关系型数据库SQLite,和其他数据库不同的是SQLite存储数据时不区分类型
例如一个字段声明为Integer类型,我们也可以将一个字符串存入,一个字段声明为布尔型,我们也可以存入浮点数。
除非是主键被定义为Integer,这时只能存储64位整数,SQLite,无需安装,是Android平台自带的一个数据库。
 创建数据库的表时可以不指定数据类型,例如:
CREATE TABLE person(id INTEGER PRIMARY KEY AUTOINCREMENT, name VARCHAR(20))
CREATE TABLE person(id INTEGER PRIMARY KEY AUTOINCREMENT, name)
 SQLite支持大部分标准SQL语句,增删改查语句都是通用的,分页查询语句和MySQL相同
SELECT FROM person LIMIT 20 OFFSET 10
SELECT
FROM person LIMIT 10,20
 SQLite与MySql的不同之处
主键自增长:SQLite是autoincrement,MySql是auto_increment
主键: SQLite主键一般定义为_id,在做查询时要求主键列名必须是_id(本身不是_id,可以起别名),不然拿不到主键值。
 获取可读数据库、可写数据库的区别
可读的数据库也有可能可以写,可读的数据库在获取实例时有可能拿到上一次可写的数据库
5.2. 创建数据库
下面通过一个案例演示SQLiteOpenHelper的用法。创建一个Android工程。为该工程添加Android Junit测试环境(注意:关于Android Junit的知识在本人的第二篇笔记中有详细的说明)。
 定义类继承SQLiteOpenHelper
 声明构造函数,4个参数
 重写onCreate()方法
 重写upGrade()方法

具体代码如下:
public class PersonOpenHelper extends SQLiteOpenHelper {
/**

  • @param context 上下文对象
  • @param name 数据库名称
  • @param factory 游标结果集工厂,如果需要使用则需要自定义结果集工厂,null值代表使用默认结果集工厂
  • @param version 数据库版本号,必须大于等于1
    */
    public PersonOpenHelper(Context context, String name, CursorFactory factory, int version) {
    super(context, name, factory, version);
    }
    /**
  • 数据库第一次被创建时调用该方法,这里面主要进行对数据库的初始化操作
    */
    public void onCreate(SQLiteDatabase db) {
    // 数据库第一次被创建的时候执行如下sql语句创建一个person表
    db.execSQL(“create table person(id integer primary key autoincrement, name varchar(20), phone varchar(20), money integer(20),age integer(10));”);
    }
    /**
  • 数据库更新的时候调用该方法
  • @param db 当前操作的数据库对象
  • @param oldVersion 老版本号
  • @param enwVersion 新版本号
    */
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    // 数据库的版本更新的时候执行
    if (oldVersion == 1 && newVersion == 2) {
    db.execSQL(“alter table person add column balance integer”);
    }
    }

}

创建一个PersonOpenHelperTest类,用于测试上面的代码:
public class PersonOpenHelperTest extends AndroidTestCase {

public SQLiteDatabase getDataBase(){
    PersonOpenHelper helper = new PersonOpenHelper(getContext(), "person.db", null, 1);
    SQLiteDatabase writableDatabase = helper.getWritableDatabase();
    return writableDatabase;
}

}
执行完上面代码后,通过DDMS,查看/data/data/com.itheima.sqlite/databases目录,发现产生了两个文件,person.db和person.db-journal。其中第一个文件就是我们的数据库文件。
第一次操作数据库时,person.db-journal文件会被自动创建,该文件是sqlite的一个临时的日志文件,主要用于sqlite数据库的事务回滚操作了。 但是Android系统中将该文件永久的保存在磁盘中,不会被自动清除的,如果没有操作异常或者不需要事务回滚时,此文件的大小为0。这种机制避免了每次生成和删除person.db-journal文件的开销。

在测试类中创建该类对象,调用getWritableDatabase()或者getReadableDatabase():

如果数据库不存在,创建数据库文件,执行onCreate()方法,并获取数据库对象。如果数据库存在,版本号没有发生改变,直接获取数据库对象。如果数据库存在,版本号提升,先执行onUpgrade()方法,再获取数据库对象。
数据库并不是初始化MyHelper时创建。
如果版本号降低,应用降级。并且不重写onDowngrade,就会调用父类的onDowngrade方法,抛出异常。一般不重写,没这个需求。

获取可读数据库、可写数据库的区别:可读的数据库也有可能可以写,可读的数据库在获取实例时有可能拿到上一次可写的数据库。

5.3. 使用SQLiteDatabase操作数据库
5.3.1. SQLiteDatabase(★★★)
 Android提供了一个名为SQLiteDatabase的类,该类封装了一些操作数据库的API,使用该类可以完成对数据进行添加(Create)、查询(Retrieve)、更新(Update)和删除(Delete)操作(这些操作简称为CRUD)。
 和JDBC访问数据库不同,操作SQLite数据库无需加载驱动,不用获取连接,直接可以使用
 常用方法:
获取SQLiteDatabase对象之后通过该对象直接可以执行SQL语句。sqliteDatabase.execSQL():可以执行insert、delete、update和CREATE TABLE
之类有更改行为的SQL语句sqliteDatabase.rawQuery():用于执行select查询语句。
 getReadableDatabase()和getWritableDatabase()的区别
查看源代码后我们发现getReadableDatabase()在通常情况下返回的就是getWritableDatabase()拿到的数据库。只有在抛出异常的时候才会以只读方式打开
 数据库对象缓存
getWritableDatabase()方法最后会使用一个成员变量记住这个数据库对象,下次打开时判断是否重用
 SQLiteDatabase封装了insert()、delete()、update()、query()四个方法也可以对数据库进行操作,详情参照7.3.4
5.3.2. 执行SQL语句来操作数据库
执行SQL语句来操作数据库有两种方式,拼串和使用占位符”?。使用占位符”?”来执行SQL语句能够防止SQL注入攻击。
 拼串方式使用的方法:execSQL(String sql):增、删、改。
Cursor rawQuery(String sql, String[] selectionArgs): 查询(拼串方式,第二个参数传null即可)。
 占位符”?”使用的方法:void execSQL(String sql, Object[] bindArgs)。Cursor rawQuery(String sql, String[] selectionArgs)。
第二个参数对应的是第一个参数中占位符”?”对应的数据数组
 下面通过一个案例来演示通过执行SQL实现对数据的增删改查操作,这里使用本文档中7.2章节中的工程(★★★★)。
在本文档7.2章节中,我们继续使用PersonOpenHelperTest类,在该测试类中添加如下方法:
//插入一条数据
public void insert(){
//获取数据库对象
SQLiteDatabase dataBase = getDataBase();
String sql = “insert into person(name,age,phone) values(?,?,?)”;
//执行sql语句
dataBase.execSQL(sql,new Object[]{“lisi”,”22″,”13240217764″});
//关闭数据库
dataBase.close();
}
//查询单个数据
public void query(){
SQLiteDatabase dataBase = getDataBase();
String sql = “select name,age,phone from person where name=?”;
//执行rawQuery查询,返回Cursor对象
Cursor cursor = dataBase.rawQuery(sql , new String[]{“zhangsan”});
Person person = new Person();
//如果游标还有下一个元素,跟我们集合中Iterator中hasNext()方法类似
while(cursor.moveToNext()){
//获取当前游标的第0个元素,元素是从0开始的,而不是1
String name = cursor.getString(0);
//也可以通过列名来查询该字段在游标中的位置
int age = cursor.getInt(cursor.getColumnIndex(“age”));
String phone = cursor.getString(2);
person.setName(name);
person.setAge(age);
person.setPhone(phone);
}
//关闭游标
cursor.close();
System.out.println(person);
}
//更新数据
public void update(){
SQLiteDatabase dataBase = getDataBase();
String sql = “update person set age=? where name=?”;
//将zhangsan的年龄修改为18
dataBase.execSQL(sql,new String[]{“18″,”zhangsan”});
dataBase.close();
}
//查询所有数据
public void queryAll(){
SQLiteDatabase dataBase = getDataBase();
List<Person> persons = new ArrayList<Person>();
String sql = “select name,age,phone from person”;
Cursor cursor = dataBase.rawQuery(sql , null);
while(cursor.moveToNext()){
String name = cursor.getString(0);
int age = cursor.getInt(1);
String phone = cursor.getString(2);
Person p = new Person();
p.setAge(age);
p.setName(name);
p.setPhone(phone);
persons.add(p);
}
//关闭游标
cursor.close();
//输出集合中的数据
for(Person p : persons){
System.out.println(p);
}
}
在上面例子各个方法中,为了方便Android Junit测试,因此我把数据“写死”在代码里了,真正开发中是不会这么设计的。
5.3.3. 游标结果集Cursor(★★★)
Cursor是结果集游标,用于对结果集进行随机访问。Cursor与JDBC中的ResultSet作用很相似。
Cursor中维护一个行索引一个列索引,游标中本身没有数据,它只是指向数据库的索引,模拟一个行、列的表结构。其起始位置是在-1的位置上的。
常用方法:
moveToNext():将游标从当前行移动到下一行,如果已经移过了结果集的最后一行,返回结果为false,否则为true。
moveToPrevious():用于将游标从当前行移动到上一行,如果已经移过了结果集的第一行,返回值为false,否则为true。
moveToFirst():用于将游标移动到结果集的第一行,如果结果集为空,返回值为false,否则为true。
moveToLast():用于将游标移动到结果集的最后一行,如果结果集为空,返回值为false,否则为true。
5.3.4. SQLiteDataBase自带的增删改查
SQLiteDatabase专门提供了对应于添加(insert)、删除(delete)、更新(update)、查询(query)的操作方法。
这些方法封装了部分SQL语句,通过参数进行拼接,这些方法实际上是给那些不太了解SQL语法的开发者使用的。对于熟悉SQL语法的程序员而言,直接使用execSQL()和rawQuery()方法执行SQL语句就能完成数据的添加、删除、更新、查询操作。
这四个方法主要是在使用内容提供者时使用,因为contentprovider中提供的增删改查方法,与这一套一样,方便调用传参。(等学完ContentProvider自然明白)
 insert(“表名”,nullColumnHack,””,contentValue)
 nullColumnHack:如果写null,就无法插入一条空数据(2.3会出异常,4.0之后可以写null)
 如果想插入空数据,第二个参数必须写一个列名(任意列)
 这个列名是用来拼接sql语句的,如果contentValue为空,则后台不知道表的列名,无法构建sql语句
 ContentValue:键值对:键对应列明,值对应插入的值
 返回值 long id,插入的主键id
 delete(“表名”,条件,条件值),返回受影响的行数。
 update(“表名”,contentValues(更新列、值),条件,条件值),返回受影响的行数
 query(“表名”, 查询的字段, 条件, 条件值);
下面通过代码来演示SQLiteDatabase操作数据库的过程,我们这里直接使用本文档7.2中工程,只需修改PersonOpenHelperTest类中方法即可。
//测试添加数据
public void insert() {
SQLiteDatabase dataBase = getDataBase();
ContentValues values = new ContentValues();
values.put(“name”, “heima”);
values.put(“age”, 5);
values.put(“phone”, “010-82826816”);
/*

  • 第一个参数 table,代表要将数据插入哪家表 第二个参数
  • nullColumnHack,字符串类型,指明如果某一字段没有值,那么会将该字段的值设为NULL
  • ,一般给该参数传递null就行如果没有特殊要求
  • ,在这里我传递了phone字符串,也就是说当我的ContentValues中phone字段为空的时候系统自动给其值设置为NULL
  • 第三个参数ContentValues 类似一个Map<key,value>的数据结构,key是表中的字段,value是值
    */
    dataBase.insert(“person”, “phone”, values);
    }
    //测试删除数据

    public void delete() {
    SQLiteDatabase database = getDataBase();
    /*

  • 第一个参数 table,代表要删除的表名
  • 第二个参数 whereClause ,选择的条件选项,如果为null则删除表中的所有数据
    • 第三个参数 whereArgs ,如果有条件选项,对应的条件选项的具体参数,没有写null
  • 删除名字为”heima”的记录
    /
    database.delete(“person”, “name=?”, new String[]{“heima”});
    }
    //测试修改数据
    public void update() {
    SQLiteDatabase database = getDataBase();
    ContentValues values = new ContentValues();
    values.put(“age”, “100”);
    /
  • 第一个参数table,要更新的表名
  • 第二个参数ContentValues 设置要修改的字段的新值,没有涉及到的字段则默认不修改
  • 第三、四个参数的含义同方法delete
    /
    database.update(“person”, values , “name=?”, new String[]{“heima”});
    }
    //测试查询单个数据
    public void query() {
    SQLiteDatabase database = getDataBase();
    /
  • 第一个参数 table,查询的表名
  • 第二个参数 columns,要查询的字段
  • 第三个参数selection 过滤字段
  • 第四个参数selectionArgs 过滤字段的值
  • 第五个参数groupBy 分组字段,null代表不分组
  • 第六个参数having
  • A filter declare which row groups to include in the cursor,
  • if row grouping is being used, formatted as an SQL HAVING clause
  • (excluding the HAVING itself). Passing null will cause all row groups
  • to be included, and is required when row grouping is not being used.
  • 第七个参数orderBy 排序字段,asc正序,desc倒序,null代表自然顺序
    /
    Cursor cursor = database.query(“person”, new String[]{“name,age,phone”}, “name=?”, new String[]{“heima”}, null, null, null);
    int id = cursor.getInt(0);
    String name = cursor.getString(1);
    String phone = cursor.getString(2);
    System.out.println(id + “” + name + “” + phone);
    }
    //测试查询所有数据
    public void queryAll(){
    SQLiteDatabase database = getDataBase();
    List<Person> persons = new ArrayList<Person>();
    /
  • 该方法跟query()方法是完全一样的,因此参数的含义不在介绍
    */
    Cursor cursor = database.query(“person”, new String[]{“name,age,phone”}, null, null, null, null, null);
    while(cursor.moveToNext()){
    Person p = new Person();
    String name = cursor.getString(0);
    int age = cursor.getInt(1);
    String phone = cursor.getString(2);
    p.setName(name);
    p.setAge(age);
    p.setPhone(phone);
    persons.add(p);
    }
    }

5.4. 事务管理
5.4.1. 事务简介
跟MySql、Oracle等常用数据库一样,SQLite数据库也对事物有较好的支持。使用方法:
 beginTransaction(): 开启一个事务
 setTransactionSuccessful():设置事务成功标记
 endTransaction(): 结束事务,包括提交和回滚,需要放在finally中执行,否则事务只有到超时的时候才自动结束,会降低数据库并发效率
执行过程:
使用beginTransaction开启一个事务,程序执行到endTransaction方法时会检查事务的标志是否为成功,如果程序执行到endTransaction之前调用了setTransactionSuccessful方法设置事务的标志为成功,则提交事务;如果没有调用setTransactionSuccessful方法则回滚事务。
那么下面通过一个案例演示SQLite操作事务的过程。为了便于直奔主题,我们直接使用本文档7.2章节中的Android工程中的PersonOpenHelperTest类即可。
 案例:银行转账,需求:客户lisi向zhangsan的账户上转了100块。
public void testTransaction(){
//这里的最后一个参数(数据库版本号)设置为2,那么会执行PersonOpenHelper类中的onUpgrade方法
PersonOpenHelper helper = new PersonOpenHelper(getContext(), “person”, null, 2);
SQLiteDatabase database = helper.getWritableDatabase();
try {
//开启事务
database.beginTransaction();
database.execSQL(“update person set balance = balance-100 where name=?”,new String[]{“lisi”});
//当把int a=1/0;放开的时候,发现抛出异常,那么事务就会回滚,上面的扣除lsii的100元钱不会被真正执行
//如果把int a = 1/0;注释掉,才发现事务成功了,lisi的钱被扣除了100元,同时zhangsan的钱也多了100元。
int a=1/0;
database.execSQL(“update person set balance = balance+100 where name=?”,new String[]{“zhangsan”});
//设置事务成功,也就是只有当代码执行到此行,才代表事务已经成功
database.setTransactionSuccessful();
} finally{
//提交事务,如果setTransactionSuccessful()方法已经执行,则beginTransaction()后的语句执行成功
//否则,事务回滚到开启事务前的状态
database.endTransaction();
}
}

5.4.2. 事务对效率的提高
在批量修改数据的时候,由于事务是在进行事务提交时将要执行的SQL操作一次性打开数据库连接执行,其执行速度比逐条执行SQL语句的速度快了很多倍。因此当我们开发中遇到对数据库的批量操作那么,使用事务是提高效率的重要原则。
下面通过一个案例,来演示批量操作的情况下,使用事务和不使用事务在效率上的差异。同样的该案例依然使用本文档7.2中的Android工程。
案例:插入一万条数据到数据库,比较使用事务和不使用事务各自所需的时间。
public void testTransactionEfficient(){
PersonOpenHelper helper = new PersonOpenHelper(getContext(), “person”, null, 2);
SQLiteDatabase database = helper.getWritableDatabase();
// ——测试不使用事务时插入1w条数据耗时——————–
long beginTime = System.currentTimeMillis();
for(int i=0;i<10000;i++){
database.execSQL(“insert into person(name,age,phone) values(‘text’+”+i+”,”+i+”,'”+(1320000+i)+””+”‘)”);
}
long endTime = System.currentTimeMillis();
System.out.println(“不使用事务插入1w条数据耗时:”+(endTime-beginTime)+”毫秒”);
// ———测试使用事务时耗时———————–
beginTime = System.currentTimeMillis();
database.beginTransaction();
for(int i=0;i<10000;i++){
database.execSQL(“insert into person(name,age,phone) values(‘text’+”+i+”,”+i+”,'”+(1320000+i)+””+”‘)”);
}
database.setTransactionSuccessful();
database.endTransaction();
endTime = System.currentTimeMillis();
System.out.println(“使用事务插入1w条数据耗时:”+(endTime-beginTime)+”毫秒”);
}

执行上面代码,查看控制台,发现不使用事务耗时19397毫秒,使用事务耗时3404毫秒,性能差别还是相当的明显。

5.4.3. 查看SQLite数据库文件
查看SQLite数据库有多种方法,上面章节的演示过程其实就是通过Android API的方式查看。那么下面要介绍的是非常常用的两种方式:1、通过SQLite Expert工具2、通过Android sqlite3工具。
1、 通过SQLite Expert工具
SQLite Expert(http://www.sqliteexpert.com/)是一款强大的SQLite数据库管理工具。从官网上下载的版本只能免费试用30天,试用期过后如果继续使用需要支付一定的费用购买许可。该工具的安装过程很简单,在这里就不再演示。只给大家演示如果利用该工具打开我们在上一个章节中生成的测试数据
在DDMS视图中打开/data/data/com.itheima.sqlite/databases/person文件,点击右上角的导出按钮,然后在弹出的文件对话框中选择需要保存的位置,然后点击确定即可将模拟器中的数据库文件导出到本地。

然后打开SQLite Expert软件,将person数据拖拽到如下图的左侧区域即可。

2、 通过Android sqlite3工具
Android提供了一个sqlite3.exe程序,位于sdk的tools目录下,用于操作SQLite数据库,其常用命令为:
 sqlite3 数据库名称:进入数据库操作模式 eg: sqlite3 contacts.db
 tables:查看所有的表 eg: .tables
 schema:查看查看库中所有表的DDL语句 eg: .schema
 help : 查看帮助  eg: .help
 headers on/off :显示表头,默认off   eg: headers on
 mode list|column|insert|line|tabs|tcl|csv:改变输出格式。eg: .mode column
 nullValue: NULL空值数据显示问题 eg: .nullValue NULL
 dump表名 : 生成形成表的SQL脚本 eq: .dump person
 dump : 生成整个数据库的SQL脚本 eq: .dump
 exit : 退出sqlite操作模式 eq: .exit
操作步骤:
1、 在命令行界面使用adb shell命令进入linux内核
2、 使用cd命令进入数据库所在目录(数据库的路径为”/data/data/应用包名/databases/数据库”)
3、 使用”sqlite3 数据库名”进入数据库操作模式
4、 直接使用各种命令操作数据库

5.5. 结合工作和面试

  1. 面试中
     SQLite特点?
    不区分数据类型等等,参照上边的章节即可。
     数据库如何维护?
    面试中有可能会被问到,你们公司之前应用的数据库是如何维护的?
    其实意思就是你们的数据库表、表字段是怎么做增删修改的,你可以说如果数据库要修改,就改下数据库版本号,然后在onUpgrade里调用即可。
     数据库用事务么?
    这个就可以说,多表操作,或者是循环操作一个表时会用,其他情况不用。
    多表操作,例如转账。循环操作,比如缓存listview数据,要循环插入listview的每一个item对象。
  2. 工作中
     SQLiteDataBase
    这个比较重要,每个应用都会用到数据库,用到数据库就要用到这个类。
    会使用增删改查方法,事务,以及execSQL即可。
     SQLiteOpenHelper
    维护数据库升级,需要用到这个类。熟悉核心方法onCreate方法以及onUpgrade方法有什么作用,什么时候调用即可。
     事务
    上边也提到了,多表操作,循环操作数据库都需要用到事务。所以这个也比较重要。会用就行。
     SQL语句 重点
    做开发的,不管是web端还是客户端,都会接触到sql语句。作为两年工作经验的开发人员,sql语句是必须要会的,所以大家一定要注意。多加练习!!!
    如果sql语句写的好,会提高性能,会少些很多逻辑代码。
点赞