概述
Android系统自带一个轻量级的关系型数据库:SQLite,支持基本的CRUD,事务等操作。
SQLite
多进程
可以配合ContentProvider实现多进程读写SQLite的操作。
事务
锁 |
概述 |
unlock(未加锁) |
1. 初始状态未加锁,在此状态下,连接还没有存取数据库。当连接到了一个数据库,甚至已经用BEGIN开始了一个事务时,连接都还处于未加锁状态。 |
shared(共享锁) |
2. 为了能够从数据库中读(不写)数据,连接必须首先进入共享状态,也就是说首先要获得一个共享锁。多个连接可以同时获得并保持共享锁,也就是说多个连接可以同时从同一个数据库中读数据但,哪怕只有一个共享锁还没有释放,也不允许任何连接写数据库 |
reserved(保留锁) |
3.如果一个连接想要写数据库,它必须首先获得一个保留锁。一个数据库上同时只能有一个保留锁。保留锁可以与共享锁共存,保留锁是写数据库的第1阶段。保留锁既不阻止其它拥有共享锁的连接继续读数据库,也不阻止其它连接获得新的共享锁。一旦一个连接获得了保留锁,它就可以开始处理数据库修改操作了,尽管这些修改只能在缓冲区中进行,而不是实际地写到磁盘。对读出内容所做的修改保存在内存缓冲区中。 |
pending(未决锁) |
4.当连接想要提交修改(或事务)时,需要将保留锁提升为排它锁。为了得到排它锁,还必须首先将保留锁提升为未决锁。获得未决锁之后,其它连接就不能再获得新的共享锁了,但已经拥有共享锁的连接仍然可以继续正常读数据库。此时,拥有未决锁的连接等待其它拥有共享锁的连接完成工作并释放其共享锁 |
exclusive(排它锁) |
|
事务可以开始于:DEFERRED、MMEDIATE或EXCLUSIVE。事务类型在BEGIN命令中指定:
1
| BEGIN [ DEFERRED | IMMEDIATE | EXCLUSIVE ] TRANSACTION;
|
一个DEFERRED事务不获取任何锁(直到它需要锁的时候),BEGIN语句本身也不会做什么事情——它开始于UNLOCK状态。默认情况下就 是这样的,如果仅仅用BEGIN开始一个事务,那么事务就是DEFERRED的,同时它不会获取任何锁;当对数据库进行第一次读操作时,它会获取 SHARED锁;同样,当进行第一次写操作时,它会获取RESERVED锁。
由BEGIN开始的IMMEDIATE事务会尝试获取RESERVED锁。如果成功,BEGIN IMMEDIATE保证没有别的连接可以写数据库。但是,别的连接可以对数据库进行读操作;但是,RESERVED锁会阻止其它连接的BEGIN IMMEDIATE或者BEGIN EXCLUSIVE命令,当其它连接执行上述命令时,会返回SQLITE_BUSY错误。这时你就可以对数据库进行修改操作了,但是你还不能提交,当你 COMMIT时,会返回SQLITE_BUSY错误,这意味着还有其它的读事务没有完成,得等它们执行完后才能提交事务。
EXCLUSIVE事务会试着获取对数据库的EXCLUSIVE锁。这与IMMEDIATE类似,但是一旦成功,EXCLUSIVE事务保证没有其它的连接,所以就可对数据库进行读写操作了。
死锁:
两个连接都在死锁中结束。B首先尝试写数据库,也就拥有了一个未决锁。A再试图写,但当其INSERT语句试图将共享锁提升为保留锁时失败。
为了讨论的方便,假设连接A和B都一直等待数据库可写。那么此时,其它的连接甚至都不能够再读数据库了,因为B拥有未决锁(它能阻止其它连接获得共享锁)。那么时此,不仅A和B不能工作,其它所有进程都不能再操作此数据库了。
这个例子的问题在于两个连接最终都想写数据库,但是它们都没有放弃各自原来的锁,最终,SHARED锁导致了问题的出现。如果两个连接都以 BEGIN IMMEDIATE开始事务,那么死锁就不会发生。在这种情况下,在同一时刻只能有一个连接进入BEGIN IMMEDIATE,其它的连接就得等待。BEGIN IMMEDIATE和BEGIN EXCLUSIVE通常被写事务使用。就像同步机制一样,它防止了死锁的产生。
基本的准则是:如果你正在使用的数据库没有其它的连接,用BEGIN就足够了。但是,如果你使用的数据库有其它的连接也会对数据库进行写操作,就得使用BEGIN IMMEDIATE或BEGIN EXCLUSIVE开始你的事务。
Android中的事物,有2种,一个是IMMEDIATE,一个是EXCLUSIVE,默认SQLiteDatabase的beginTransaction()是EXCLUSIVE事物,beginTransactionNonExclusive()是IMMEDIATE事务。
SQLiteOpenHelper
概述
SQLiteOpenHelper是一个数据库辅助操作的类,在Android中实现数据库的增删查改以及升级等的控制,在开发中通常会实现一个继承SQLiteOpenHelper的操作类,然后根据业务需求实现数据库的相关操作方法。
用法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| 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 mContext; private SQLiteDatabase mWritableDatabase = getWritableDatabase(); private SQLiteDatabase mReadableDatabase = getReadableDatabase(); public MyDatabaseHelper(Context context, String name, 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) { Toast.makeText(mContext, "onUpgrade oldVersion:" + oldVersion + " newVersion:" + newVersion, Toast.LENGTH_SHORT).show(); }
public int insert(Bean bean) { return 0; } }
|
GreenDAO
概述
GreenDAO 是一款开源的面向 Android 的轻便、快捷的 ORM 框架,它针对 Android 进行了高度优化,使用了最小的内存开销、且依赖体积小,同时还是支持数据库加密。
核心类
DaoMaster
greenDAO 的入口,DaoMaster 负责管理数据库对象(SQLiteDatabase)和 DAO 类(对象),我们可以通过它的内部类 OpenHelper 和 DevOpenHelper 创建不同模式的 SQLite 数据库。
DaoSession
管理指定模式下的所有 DAO 对象,DaoSession提供了一些通用的持久性方法比如插入、负载、更新、更新和删除实体。
XxxDAO
对于每个实体类,greenDAO 会生成一个与之对应DAO对象。
Entity
可持久化对象。通常,实体对象代表一个数据库行使用标准 Java 属性(如一个POJO 或 JavaBean)。
注解
@Entity
表明这个实体类会在数据库中生成一个与之相对应的表,属性如下:
- schema:告知GreenDao当前实体属于哪个schema
- schema active:标记一个实体处于活跃状态,活动实体有更新、删除和刷新方法
- nameInDb:在数据库中使用的别名,默认使用的是实体的类名
- indexes:定义索引,可以跨越多个列
- createInDb:标记创建数据库表(默认:true)
- generateConstructors 自动创建全参构造方法(同时会生成一个无参构造方法)(默认:true)
- generateGettersSetters 自动生成 getters and setters 方法(默认:true)
实例如下:
1 2 3 4 5 6 7 8 9 10 11
| @Entity( schema = "myschema", active = true, nameInDb = "AWESOME_USERS", indexes = { @Index(value = "name DESC", unique = true) }, createInDb = true, generateConstructors = false, generateGettersSetters = true )
|
@Id
对应数据表中的 Id 字段。
@Index
使用@Index作为一个属性来创建一个索引,默认是使用字段名。
1 2 3 4 5 6 7 8
| @Entity public class User { @Id private Long id;
@Index(unique = true) private String name; }
|
@Property
设置一个非默认关系映射所对应的列名,默认是使用字段名,例如:@Property(nameInDb = “userName”)。
@NotNull
设置数据库表当前列不能为空。
@Transient
添加此标记后不会生成数据库表的列。
@Unique
表名该属性在数据库中只能有唯一值。
1 2 3 4 5 6 7
| @Entity public class User { @Id private Long id; @Unique private String name; }
|
@ToOne
表示一对一关系。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| @Entity public class Order {
@Id private Long id;
private long customerId;
@ToOne(joinProperty = "customerId") private Customer customer; }
@Entity public class Customer { @Id private Long id; }
|
@OrderBy
更加某一字段排序,例如:@OrderBy(“date ASC”)。
@ToMany
定义一对多个实体对象的关系。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @Entity public class Customer { @Id private Long id;
@ToMany(referencedJoinProperty = "customerId") @OrderBy("date ASC") private List<Order> orders; }
@Entity public class Order { @Id private Long id; private Date date; private long customerId; }
|
集成GreenDAO
配置仓库/插件
在Project下的build.gradle中配置:
1 2 3 4 5 6 7 8 9 10
| buildscript { repositories { google() jcenter() } dependencies { classpath 'com.android.tools.build:gradle:3.5.3' classpath 'org.greenrobot:greendao-gradle-plugin:3.2.2' } }
|
添加依赖
在Module的build.gradle中配置:
1 2 3 4 5 6 7 8 9 10 11 12 13
| apply plugin: 'org.greenrobot.greendao'
greendao { schemaVersion 1 daoPackage 'com.hearing.dao.gen' targetGenDir 'src/main/java' }
dependencies { compile 'org.greenrobot:greendao:3.2.2' compile 'org.greenrobot:greendao-generator:3.2.2' }
|
Entity
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| @Entity public class ContractVideo { @Transient private static final long serialVersionUID = 1404519421023545593L;
@Id(autoincrement = true) private Long id; private String number; private String videoPath;
@Generated(hash = 1570774131) public ContractVideo() { }
@Generated(hash = 776974923) public ContractVideo(Long id, String number, String videoPath) { this.id = id; this.number = number; this.videoPath = videoPath; }
public Long getId() { return id; }
public void setId(Long id) { id = id; }
public String getNumber() { return number; }
public void setNumber(String number) { number = number; }
public String getVideoPath() { return videoPath; }
public void setVideoPath(String videoPath) { videoPath = videoPath; }
public Long getId() { return this.id; }
public void setId(Long id) { this.id = id; }
public String getNumber() { return this.number; }
public void setNumber(String number) { this.number = number; }
public String getVideoPath() { return this.videoPath; }
public void setVideoPath(String videoPath) { this.videoPath = videoPath; } }
|
在build工程后,会在指定包下生成管理类DaoMaster,DaoSession,BeanDao。
初始化GreenDAO
初始化通常在Application下进行,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public class CallApplication extends Application {
private static final String DB_NAME = "call.db";
private DaoSession mDaoSession;
@Override public void onCreate() { super.onCreate(); initDao(); }
private void initDao() { DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(this, DB_NAME); SQLiteDatabase db = helper.getWritableDatabase(); DaoMaster daoMaster = new DaoMaster(db); mDaoSession = daoMaster.newSession(); }
public DaoSession getDaoSession() { return mDaoSession; } }
|
获取BeanDao
1 2 3
| CallApplication myApp = (CallApplication) getApplication(); DaoSession daoSession = myApp.getDaoSession(); ContractVideoDao contractVideoDao = daoSession.getContractVideoDao();
|
增删查改
通过操作ContractVideoDao来进行记录的增删查改,其具体接口可参考ContractVideoDao类中的方法。