Android SQLite

保存在内存中的数据是处于瞬时状态的,而保存在存储设备中的数据是处于持久状态的。
持久化技术提供了一种机制,可以让数据在瞬时状态和持久状态之间进行转换。
Android 系统中主要提供了 3 中方式用于简单地实现数据持久化功能:
文件存储、SharedPreferences 存储、数据库存储(SQlite存储,轻量级嵌入式数据库引擎,强大的增删改查功能)。

SQLite的使用

创建数据库和表

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
public class OrderDBHelper extends SQLiteOpenHelper {

private static final int DB_VERSION = 1;
private static final String DB_NAME = "myTest.db";
public static final String TABLE_NAME = "Orders";

public OrderDBHelper(Context context) {
super(context,DB_NAME,null,DB_VERSION);
}

@Override
public void onCreate(SQLiteDatabase sqLiteDatabase) {
// create table Orders(Id integer primary key, CustomName text, OrderPrice integer);
String sql = "create table if not exists " + TABLE_NAME + " (Id integer primary key, CustomName text, OrderPrice integer)";
sqLiteDatabase.execSQL(sql);
}

/**
* 更新
*/
@Override
public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {
String sql = "DROP TABLE IF EXISTS " + TABLE_NAME;
sqLiteDatabase.execSQL(sql);
onCreate(sqLiteDatabase);
}
}

定义方法:增、删、改、查

对于“增删改”这类对表内容变换的操作,需先调用getWritableDatabase(),
在执行的时候可以调用通用的execSQL(String sql)方法或对应的操作API:insert()、delete()、update()。

而对“查”,需要调用getReadableDatabase(),
这时就不能使用execSQL方法了,得使用query()或rawQuery()方法。
public Cursor rawQuery(String sql, String[] selectionArgs);(写法类似execSQL)
public Cursor query(String table,String[] columns,String selection,String[] selectionArgs,String groupBy,String having,String orderBy,String limit);

参数 说明
table 表名称
columns 列名称数组
selection 条件字句,相当于where
selectionArgs 条件字句,参数数组
groupBy 分组列
having 分组条件
orderBy 排序列
limit 分页查询限制

它们返回的类型都是Cursor(相当于结果集ResultSet),Cursor是一个游标接口,提供了遍历查询结果的方法,如移动指针方法move(),获得列值方法。
Cursor游标常用方法如下:

示例(Java)

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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
/**
* 用于处理所有得数据库操作方法
*/

public class OrderDao {
private static final String TAG = "OrdersDao";
private final String[] ORDER_COLUMNS = new String[] {"Id", "CustomName","OrderPrice"};

private Context mContext;
private OrderDBHelper orderDBHelper;

public OrderDao(Context mContext){
this.mContext = mContext;
// 代码将在单独的线程中向数据库写入数据,
orderDBHelper = new OrderDBHelper(mContext);
DatabaseManager.initializeInstance(orderDBHelper);
}

/**
* 判断表中是否有数据
*/
public boolean isDataExist(){
int count = 0;

SQLiteDatabase db = null;
Cursor cursor = null;

try {
db = orderDBHelper.getReadableDatabase();
// select count(Id) from Orders
cursor = db.query(OrderDBHelper.TABLE_NAME, new String[]{"COUNT(Id)"}, null, null, null, null, null);

if (cursor.moveToFirst()) {
count = cursor.getInt(0);
}
if (count > 0) return true;
}
catch (Exception e) {
Log.e(TAG, "", e);
}
finally {
if (cursor != null) {
cursor.close();
}
if (db != null) {
db.close();
}
}
return false;
}

/**
* 查询数据库中所有数据
*/
public List<OrderBean> getAllDate(){
SQLiteDatabase db = null;
Cursor cursor = null;

try {
// 对于查询,需要调用getReadableDatabase()
db = orderDBHelper.getReadableDatabase();
// select * from Orders
cursor = db.query(OrderDBHelper.TABLE_NAME, ORDER_COLUMNS, null, null, null, null, null);

if (cursor.getCount() > 0) {
List<OrderBean> orderList = new ArrayList<>(cursor.getCount());
while (cursor.moveToNext()) {
orderList.add(parseOrder(cursor));
}
return orderList;
}
}
catch (Exception e) {
Log.e(TAG, "", e);
}
finally {
if (cursor != null) {
cursor.close();
}
if (db != null) {
db.close();
}
}

return null;
}

/**
* 初始化数据
*/
public void initTable(){
SQLiteDatabase db = null;

try{
// 对于“增删改”这类对表内容变换的操作,需先调用getWritableDatabase()
db = DatabaseManager.getInstance().openDatabase();
Log.e("TAG",db+"--=--=-=-=");
// 在android的使用数据库时,sqlite数据库默认情况下是“一个连接存在与一个事务”。
// 有时候会操作大批量的数据,比如批量的写操作,如何让批量的操作在一个事物中完成呢?
// 开始事务
db.beginTransaction();

db.execSQL("insert into " + OrderDBHelper.TABLE_NAME + "(Id, CustomName, OrderPrice) values (1,'one',10)");
db.execSQL("insert into " + OrderDBHelper.TABLE_NAME + "(Id, CustomName, OrderPrice) values (2,'two',20)");
db.execSQL("insert into " + OrderDBHelper.TABLE_NAME + "(Id, CustomName, OrderPrice) values (3,'three',20)");

// 设置事务标志为成功,当结束事务时就会提交事务
db.setTransactionSuccessful();
}catch (Exception e){
Log.e(TAG,"",e);
}finally {
if (db != null){
// 处理完成
db.endTransaction();
// db.close();
DatabaseManager.getInstance().closeDatabase();
}
}
}

/**
* 新增一条数据
*/
public boolean insertDate(){
SQLiteDatabase db = null;

try{
db = DatabaseManager.getInstance().openDatabase();
db.beginTransaction();

// insert into Orders(Id, CustomName, OrderPrice) values (47, "fou", 40);
// ContentValues内部实现就是HashMap,但是,
// ContenValues Key只能是String类型,Value只能存储基本类型的数据,像string,int之类的,不能存储对象这种东西:
ContentValues contentValues = new ContentValues();
contentValues.put("Id",4);
contentValues.put("CustomName","fou");
contentValues.put("OrderPrice",40);
db.insertOrThrow(OrderDBHelper.TABLE_NAME,null,contentValues);

db.setTransactionSuccessful();
return true;
}catch (SQLiteConstraintException e){
Toast.makeText(mContext,"主键重复",Toast.LENGTH_SHORT).show();
}catch (Exception e){
Log.e(TAG,"",e);
}finally{
if (db!=null){
db.endTransaction();
// db.close();
DatabaseManager.getInstance().closeDatabase();
}
}
return false;
}

/**
* 删除一条数据
*/
public boolean deleteOrder(){
SQLiteDatabase db = null;

try{
db = DatabaseManager.getInstance().openDatabase();
db.beginTransaction();

// delete from Orders where Id = 7
// 三个参数:表,删除条件,删除条件值数组
db.delete(OrderDBHelper.TABLE_NAME,"Id = ? ",new String[]{String.valueOf(4)});

db.setTransactionSuccessful();
return true;
}catch (Exception e){
Log.e(TAG,"",e);
}finally{
if (db!=null){
db.endTransaction();
// db.close();
DatabaseManager.getInstance().closeDatabase();
}
}
return false;
}

/**
* 修改一条数据
*/
public boolean updateOrder(){
SQLiteDatabase db = null;

try{
db = DatabaseManager.getInstance().openDatabase();
db.beginTransaction();

// update Orders set OrderPrice = 100 where Id = 1
ContentValues contentValues = new ContentValues();
contentValues.put("OrderPrice","100");
db.update(OrderDBHelper.TABLE_NAME,contentValues,"Id = ?",new String[]{String.valueOf(1)});

db.setTransactionSuccessful();
return true;
}catch (Exception e){
Log.e(TAG,"",e);
}finally{
if (db!=null){
db.endTransaction();
// db.close();
DatabaseManager.getInstance().closeDatabase();
}
}
return false;
}

/**
* 数据查询
* 此处将用户名为"one"的信息提取出来
*/
public List<OrderBean> getOrder(){
SQLiteDatabase db = null;
Cursor cursor = null;

try{
db = orderDBHelper.getReadableDatabase();

// select * from Orders where CustomName = 'one'
cursor = db.query(OrderDBHelper.TABLE_NAME,ORDER_COLUMNS,"CustomName = ?",new String[]{"one"},null,null,null);
if (cursor.getCount()>0){
List<OrderBean> orderList = new ArrayList<OrderBean>(cursor.getCount());
while (cursor.moveToNext()) {
OrderBean order = parseOrder(cursor);
orderList.add(order);
}
return orderList;
}
}catch (Exception e){
Log.e(TAG,"",e);
}finally{
if (cursor != null) {
cursor.close();
}
if (db != null) {
db.close();
}
}
return null;
}

/**
* 统计查询
* 此处查询 OrderPrice 为 20 的用户总数
*/
public int getOrderPriceCount(){
int count = 0;

SQLiteDatabase db = null;
Cursor cursor = null;

try {
db = orderDBHelper.getReadableDatabase();
// select count(Id) from Orders where OrderPrice = '20'
cursor = db.query(OrderDBHelper.TABLE_NAME,
new String[]{"COUNT(Id)"},
"OrderPrice = ?",
new String[] {"20"},
null, null, null);

if (cursor.moveToFirst()) {
count = cursor.getInt(0);
}
}
catch (Exception e) {
Log.e(TAG, "", e);
}
finally {
if (cursor != null) {
cursor.close();
}
if (db != null) {
db.close();
}
}
return count;
}

/**
* 比较查询
* 此处查询单笔数据中OrderPrice最高的
*/
public OrderBean getMaxOrderPrice(){
SQLiteDatabase db = null;
Cursor cursor = null;

try {
db = orderDBHelper.getReadableDatabase();
// select Id, CustomName, Max(OrderPrice) as OrderPrice from Orders
cursor = db.query(OrderDBHelper.TABLE_NAME, new String[]{"Id", "CustomName", "Max(OrderPrice) as OrderPrice"}, null, null, null, null, null);

if (cursor.getCount() > 0){
if (cursor.moveToFirst()) {
return parseOrder(cursor);
}
}
}
catch (Exception e) {
Log.e(TAG, "", e);
}
finally {
if (cursor != null) {
cursor.close();
}
if (db != null) {
db.close();
}
}
return null;
}

/**
* 将查找到的数据转换成Order类
*/
private OrderBean parseOrder(Cursor cursor){
OrderBean order = new OrderBean();
order.setId((cursor.getInt(cursor.getColumnIndex("Id"))));
order.setCustomName((cursor.getString(cursor.getColumnIndex("CustomName"))));
order.setOrderPrice((cursor.getInt(cursor.getColumnIndex("OrderPrice"))));
return order;
}

/**
* 执行自定义SQL语句
*/
public void execSQL(String sql) {
SQLiteDatabase db = null;

try {
if (sql.contains("select")){
Toast.makeText(mContext, "Sorry,还没处理select语句", Toast.LENGTH_SHORT).show();
}else if (sql.contains("insert") || sql.contains("update") || sql.contains("delete")){
db = DatabaseManager.getInstance().openDatabase();
db.beginTransaction();
db.execSQL(sql);
db.setTransactionSuccessful();
Toast.makeText(mContext, "执行SQL语句成功", Toast.LENGTH_SHORT).show();
}
} catch (Exception e) {
Toast.makeText(mContext, "执行出错,请检查SQL语句", Toast.LENGTH_SHORT).show();
Log.e(TAG, "", e);
} finally {
if (db != null) {
db.endTransaction();
// db.close();
DatabaseManager.getInstance().closeDatabase();
}
}
}
}

assets下的db文件

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
70
71
72
73
74
75
76
77
78
/**
* 将assets文件夹下的数据库写入SD卡中
**/
public class WriteToSD {

private Context context;
/**
* 写入到sd卡中
*/
// public static String SDCARD_PATH = android.os.Environment
// .getExternalStorageDirectory().getAbsolutePath() + "/dbfile/";

/**
* 写入到data目录下
*/
public static String SDCARD_PATH ;

public WriteToSD(Context context){
this.context = context;
SDCARD_PATH = context.getFilesDir() + File.separator;
}

public String assetsWriteToSD(String fileName){
String fileP = "";
// 判断文件是否存在
if(!isExist(fileName)){
fileP = write(fileName);
}else{
fileP = SDCARD_PATH + fileName;
}
return fileP;
}

private String write(String fileName){
String fileAllPath = "";
InputStream inputStream;
try {
inputStream = context.getResources().getAssets().open(fileName);
File file = new File(SDCARD_PATH);
if(!file.exists()){
file.mkdirs();
}
fileAllPath = SDCARD_PATH + fileName;
FileOutputStream fileOutputStream = new FileOutputStream(SDCARD_PATH + fileName);
byte[] buffer = new byte[512];
int count = 0;
while((count = inputStream.read(buffer)) > 0){
fileOutputStream.write(buffer, 0 ,count);
}
fileOutputStream.flush();
fileOutputStream.close();
inputStream.close();

} catch (IOException e) {
e.printStackTrace();
}
return fileAllPath;
}

private boolean isExist(String fileName){
File file = new File(SDCARD_PATH + fileName);
if(file.exists()){
return true;
}else{
return false;
}
}

/**
* 获取sdcard路径
*/
public static boolean isExitsSdcard() {
if (android.os.Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED))
return true;
else
return false;
}
}

示例(Kotlin)

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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
/**
* 第一行代码第三版
*
* SQLite 是一款轻量级的关系型数据库,适合存储大量复杂的关系型数据。
* 它的运算速度非常快,占用资源很少,通常只需要几百 KB 的内存就足够了,因而特别适合在移动设备上使用。
* -------------------------------------------------------------------------------
* SQLite 不仅支持标准的 SQL 语法,还遵循了数据库的 ACID 事务。
*/
class SQLiteActivity:BaseActivity() {

private val TAG = "TAG_SQLiteActivity"
private lateinit var dbHelper: MyDatabaseHelper

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_sqlite)

initData()
}

/**
// Android 专门提供了一个 SQLiteOpenHelper 帮助类用来更加方便地管理数据库。
// SQLiteOpenHelper 是一个抽象类,需要创建一个自己的帮助类去继承它,
----------------------------------------------------------------------------------------------------------------------
// 有两个抽象方法,并且必须重写。onCreate() 和 onUpgrade(),用来实现创建和升级数据库的逻辑。
----------------------------------------------------------------------------------------------------------------------
// 还有两个非常重要的实例方法,getReadableDatabase() 和 getWritableDatabase(),
// 它们都可以创建或打开一个现有的数据库(如果数据库已存在则直接打开,否则要创建一个新的数据库),并返回一个可对数据库进行读写操作的对象。
// 不同的是,当数据库不可写入时(如磁盘空间已满),getReadableDatabase() 返回的对象将以只读的方式打开数据库,而 getWritableDatabase() 则将出现异常。
----------------------------------------------------------------------------------------------------------------------
// SQLiteOpenHelper 中有两个构造方法可供重写,一般使用参数少的那一个即可,它接收 4 个参数:
// 1. Context,必须有它才能对数据库进行操作。
// 2. 数据库名,创建数据库时使用的就是这里指定的名称。
// 3. 允许在查询数据库时返回一个自定义的 Cursor,一般传 null 即可。
// 4. 当前数据库的版本号,可用于对数据库进行升级操作。
----------------------------------------------------------------------------------------------------------------------
// 构建出 SQLiteOpenHelper 的实例之后,再调用它的 getReadableDatabase() 或 getWritableDatabase() 就能够创建数据库了,
// 数据库文件存在在 /data/data/<package name>/database/目录下。(安装插件:AS->Preferences->Plugins->搜索 Database Navigator)
// 此时,重写的 onCreate() 也会得到执行,所以通常会在这里处理一些创建表的逻辑。
*/
private fun initData() {
// 构建 MyDatabaseHelper 对象,将版本号升级,就会执行 onUpgrade()
dbHelper = MyDatabaseHelper(this, "BookStore.db", 3)
createTable()
dataCRUD()
transaction()
}

/**
* SQLite 数据库是支持事务的,
* 事务的特性可以保证让一系列的操作要么全部完成,要么一个都不会完成。
* 避免中途出现异常,导致数据丢失。
*/
private fun transaction() {
btnReplace.setOnClickListener{
val db = dbHelper.writableDatabase
db.beginTransaction() // 开启事务
try {
db.delete("Book",null,null)
if (true){
// 手动抛出一个异常,用来测试,
// 由于事务的存在,中途出现异常会导致事务的失败,那么旧数据应该是删除不掉的。
throw NullPointerException()
}

// val values = ContentValues()
// values.put("name","Game of Thrones")
// values.put("author","George Martin")
// values.put("pages",720)
// values.put("price",20.85)

// 使用 apply 函数简化写法
// val values = ContentValues().apply {
// put("name","Game of Thrones")
// put("author","George Martin")
// put("pages",720)
// put("price",20.85)
// }

// 使用高阶函数来简化用法
// val values = cvOf("name" to "Game of Thrones","author" to "George Martin",
// "pages" to 720,"price" to 20.85)

// 实际上,KTX 库中也提供了一个同样功能的方法
val values = contentValuesOf("name" to "Game of Thrones","author" to "George Martin",
"pages" to 720,"price" to 20.85)

db.insert("Book",null,values)
db.setTransactionSuccessful() // 事务已经执行成功
}catch (e: Exception){
e.printStackTrace()
}finally {
db.endTransaction() // 结束事务
}
}
}

/**
* 数据操作无非 4 种:C(create 添加)R(retrieve 查询)U(update 更新)D(delete 删除)
* 每一种操作都对应了一种 SQL 命令:insert,select,update,delete。
* ------------------------------------------------------------------------------------------------
* 并且 Android 还提供了一系列辅助性方法,即使不编写 SQL 语句,也能完成所有的 CRUD 操作。
* getReadableDatabase() 或 getWritableDatabase() 会返回一个 SQLiteDatabase 对象,用来对数据进行 CRUD 操作。
*
*/
private fun dataCRUD() {
insertData()
updateData()
deleteData()
queryData()
}

private fun queryData() {
btnQuery.setOnClickListener{
val db = dbHelper.writableDatabase
// 查询 Book 表中所有数据
// 最短的重载方法,也有 7 个参数:
// 第一个参数,表名
// 第二个参数,指定查询哪几列,不指定则默认查询所有列。
// 第三个参数,用于约束查询某一行或某几行的数据,不指定则默认查询所有行的数据。
// 第四个参数,用于约束查询某一行或某几行的数据,不指定则默认查询所有行的数据。
// 第五个参数,指定需要去 group by 的列,不指定则表示不对查询结果进行 group by 操作。
// 第六个参数,用于对 group by 之后的数据进行进一步的过滤,不指定则表示不进行过滤。
// 第七个参数,用于指定查询结果的排列方式,不执行则表示使用默认的排列方式。
// 调用 query() 会返回一个 Cursor 对象,查询到的所有数据都将从这个对象中取出。
val cursor = db.query("Book",null,null,null,null,null,null)
// 将数据指针移动到第一行的位置
if (cursor.moveToFirst()){
do {
// 遍历 Cursor 对象,取出数据并打印
// getColumnIndex() 获取某一列在表中对应的位置索引
val name = cursor.getString(cursor.getColumnIndex("name"))
val author = cursor.getString(cursor.getColumnIndex("author"))
val pages = cursor.getInt(cursor.getColumnIndex("pages"))
val price = cursor.getDouble(cursor.getColumnIndex("price"))
Log.d(TAG,"book name is $name")
Log.d(TAG,"book author is $author")
Log.d(TAG,"book pages is $pages")
Log.d(TAG,"book price is $price")
}while (cursor.moveToNext())
}
// 关闭 Cursor
cursor.close()

// SQL 语句
// val cursor1 = db.rawQuery("select * from Book",null)
}
}

private fun deleteData() {
btnDelete.setOnClickListener{
val db = dbHelper.writableDatabase
// 删除页数超过 500 的数据,不指定会删除所有行
db.delete("Book","pages>?", arrayOf("500"))

// SQL 语句
// db.execSQL("delete from Book where pages > ?", arrayOf("500"))

}
}

private fun updateData() {
btnUpdate.setOnClickListener{
val db = dbHelper.writableDatabase
val values = ContentValues()
values.put("price",10.99)
// 第一个参数是表名
// 第二个参数 ContentValues 对象
// 第三、四个参数用于约束更新某一行或某几行中的数据,不指定的话默认会更新所有行。
db.update("Book",values,"name=?", arrayOf("The Da Vinci Code"))

// SQL 语句
// db.execSQL("update Book set price = ? where name = ? ", arrayOf("10.99","The Da Vinci Code"))
}
}

private fun insertData() {
btnAdd.setOnClickListener{
val db = dbHelper.writableDatabase
val values1 = ContentValues().apply {
// 开始组装第一条数据
put("name","The Da Vinci Code")
put("author","Dan Brown")
put("pages",454)
put("price",16.99)
}
// 插入第一条数据
// 第一个参数是表名,
// 第二个参数用于在未指定添加数据的情况下给某些可为空的列自动赋值 NULL,一般用不到这个功能,直接传入 null 即可,
// 第三个参数是一个 ContentValues 对象,它提供了一系列的 put() 重载,用于向 ContentValues 中添加数据。
db.insert("Book",null,values1)
val values2 = ContentValues().apply {
// 开始组装第一条数据
put("name","The Da Lost Symbol")
put("author","Dan Brown")
put("pages",510)
put("price",19.95)
}
// 插入第二条数据
db.insert("Book",null,values2)

// SQL 语句
// db.execSQL("insert into Book(name,author,pages,price) values(?,?,?,?)",
// arrayOf("The Da Vinci Code","Dan Brown","454","16.69"))
// db.execSQL("insert into Book(name,author,pages,price) values(?,?,?,?)",
// arrayOf("The Da Lost Symbol","Dan Brown","510","19.95"))

}
}

/**
* 创建成功之后,可将 BookStore.db 文件右键选择 Save AS,将它从模拟器导出到计算机上。
* (BookStore.db-journal 文件是为了让数据库能够支持事务而产生的临时日志文件,通常大小为 0 字节,暂时不管它。)
* 这里我没有使用 Database Navigator 插件,因为安装失败了。我使用的是 Navicat Premium。
*/
private fun createTable() {
// 第一次点击会创建数据库,再次点击则不会再执行 onCreate(),除非卸载程序重新安装。
btnCreateDatabase.setOnClickListener{
// 调用 getWritableDatabase()
dbHelper.writableDatabase
}
}

companion object{
fun actionStart(context: Context){
val intent = Intent(context, SQLiteActivity::class.java)
context.startActivity(intent)
}
}
}
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
70
71
72
73
74
75
class MyDatabaseHelper(val context: Context, name:String, version:Int):
SQLiteOpenHelper(context,name,null,version){

/**
* integer 表示整型
* real 表示浮点型
* text 表示文本类型
* blob 表示二进制类型
* primary key 将 id 列设为主键,并用 autoincrement 关键字表示 id 列是自增长的。
*/

/**
* 第二版
*/
// private val createBook = "create table Book(" +
// "id integer primary key autoincrement," +
// "author text," +
// "price real," +
// "pages integer," +
// "name text)"

/**
* 第三版,新加字段,用来和 Category 表建立关联。
*/
private val createBook = "create table Book(" +
"id integer primary key autoincrement," +
"author text," +
"price real," +
"pages integer," +
"name text," +
"category_id integer)"

private val createCategory = "create table Category(" +
"id integer primary key autoincrement," +
"category_name text," +
"category_code integer)"

override fun onCreate(db: SQLiteDatabase) {
db.execSQL(createBook)
db.execSQL(createCategory)

// 跨进程访问时不能直接使用 Toast
// Toast.makeText(context,"Create succeeded",Toast.LENGTH_SHORT).show()
}


/**
* 升级数据库的最佳写法
*
* 注意,每当升级一个数据库版本时,onUpgrade() 里都一定要写一个相应的 if 判断语句,保证 App 在跨版本升级时,每一次的数据库修改都能被全部执行。
*/
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
Log.d("TAG","oldVersion is $oldVersion")
// 现版本 version 为 3,oldVersion 为 2.
// 如果直接安装最新版程序,则会走 onCreate(),不会走 onUpgrade()。
// 如果是从 version 2 升级为 version 3 版本,则会走 onUpgrade(),并且执行下面的语句来升级数据库。

// 当用户直接安装第二版程序时,就会进入 onCreate(),将两张表一起创建,
// 如果用户使用第二版程序覆盖第一版的程序时,就会进入升级数据库的操作中,只需创建一个表就可以。
if (oldVersion <= 1){
db.execSQL(createCategory)
}

if (oldVersion <= 2){
db.execSQL("alter table Book add column category_id integer")
}


// 暴力写法,开发阶段还可以,上线不行。
// 执行了两条 DROP 语句,如果发现数据库中已经存在 Book 表或 Category 表,就将它们删除,然后调用 onCreate() 重新创建。
// db.execSQL("drop table if exists Book")
// db.execSQL("drop table if exists Category")
// onCreate(db)
}
}
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
/**
* 虽然从功能性上面看好像用不到高阶函数的知识,但从代码实现上,却可以结合高阶函数来进行进一步的优化
* apply 函数的返回值就是它的调用对象本身,因此这里可以使用单行代码函数的语法糖,用等号替代返回值的声明。
* 另外 ,apply 函数的 Lambda 表达式中会自动拥有 ContentValues 的上下文,所以可直接调用 put 方法。
*/
fun cvOf(vararg pairs: Pair<String, Any?>) = ContentValues().apply{
for (pair in pairs){
val key = pair.first
val value = pair.second
when(value){
// 这里还使用了 Kotlin 中的 Smart Cast 功能。
// 比如 when 语句进入 Int 条件分支后,这个条件下面的 value 会被自动转换成 Int 类型,而不再是 Any? 类型,
// 这样就不需要像 Java 中那样再额外进行一次向下转型了,这个功能在 if 语句中也同样适用。
is Int -> put(key, value)
is Long -> put(key, value)
is Short -> put(key, value)
is Float -> put(key, value)
is Double -> put(key, value)
is Boolean -> put(key, value)
is String -> put(key, value)
is Byte -> put(key, value)
is ByteArray -> put(key, value)
null -> putNull(key)
}
}
}

/**
* 这个方法的作用是构建一个 ContentValues 对象。
*
* mapOf() 函数允许使用 "Apple" to 1 这样的语法结构快速创建一个键值对。
* 在 Kotlin 中使用 A to B 这样的语法结构会创建一个 Pair 对象。
*
* 方法接收一个 Pair 参数,vararg 关键字对应的是 Java 中的可变参数列表,
* 允许向这个方法传入 0 个、1 个甚至任意多个 Pair 类型的参数,
* 这些参数都会被赋值到使用 vararg 声明的这一个变量上面,然后使用 for-in 循环可以将传入的所有参数遍历出来。
*
* Pair 是一种键值对的数据结构,因此需要通过泛型来指定它的键和值分别对应什么类型的数据。
* ContentValues 的键都是字符串类型,所以可直接将 Pair 键的泛型指定成 String,
* 但 ContentValues 的值可以有多种类型(字符串型、整型、浮点型、甚至是 null),所以要指定成 Any,
* Any 是 Kotlin 中所有类的共同基类,相当于 Java 的 Object,而 Any?表示允许传入空值。
*/
//fun cvOf(vararg pairs: Pair<String, Any?>): ContentValues{
// // 创建 ContentValues 对象
// val cv = ContentValues()
// // 遍历 pairs 参数列表,取出其中的数据并填入 ContentValues 中,最终将 ContentValues 对象返回.
// for (pair in pairs){
// val key = pair.first
// val value = pair.second
// // 使用 when 语句一一进行条件判断,并覆盖 ContentValues 所支持的所有数据类型。
// // (因为 Pair 参数的值是 Any?类型)
// when(value){
// // 这里还使用了 Kotlin 中的 Smart Cast 功能。
// // 比如 when 语句进入 Int 条件分支后,这个条件下面的 value 会被自动转换成 Int 类型,而不再是 Any? 类型,
// // 这样就不需要像 Java 中那样再额外进行一次向下转型了,这个功能在 if 语句中也同样适用。
// is Int -> cv.put(key, value)
// is Long -> cv.put(key, value)
// is Short -> cv.put(key, value)
// is Float -> cv.put(key, value)
// is Double -> cv.put(key, value)
// is Boolean -> cv.put(key, value)
// is String -> cv.put(key, value)
// is Byte -> cv.put(key, value)
// is ByteArray -> cv.put(key, value)
// null -> cv.putNull(key)
// }
// }
// return cv
//}

SQLite是否线程安全

第一个问题

要将数据库与多个线程一起使用,需要确保使用的是一个数据库连接。

这是因为每次创建新的SQLiteOpenHelper对象时,实际上都在建立新的数据库连接。
如果试图同时从实际的不同连接写入数据库,
则会失败(android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5))。

第二个问题

由于我们只使用一个数据库连接,getDatabase()方法为Thread1和Thread2返回相同的SQLiteDatabase对象实例。
发生的情况是,Thread1可能会关闭数据库,而Thread2仍然在使用它。
这样也会有IllegalStateException崩溃(java.lang.IllegalStateException: attempt to re-open an already-closed object: SQLiteDatabase)。

一种可能的解决方案是让计数器跟踪打开/关闭数据库连接。

  • 每次需要数据库时,都应该调用DatabaseManager类的openDatabase()方法。
    在这个方法中,我们有一个计数器,它指示打开数据库的次数。
    如果等于1,则意味着需要创建新的数据库连接;如果不等于1,则表示已经建立了数据库连接。
  • 在closeDatabase()方法中也发生了同样的情况。每次调用这个方法时,计数器都会减少,当计数器趋近于0时,就关闭数据库连接。

示例

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
/**
* 创建一个单例类DatabaseManager,
* 它将保存并返回一个SQLiteOpenHelper对象。
*/

public class DatabaseManager {
/**
* AtomicInteger是一个提供原子操作的Integer类,通过线程安全的方式操作加减。
* AtomicInteger是在使用非阻塞算法实现并发控制,在一些高并发程序中非常适合
*/
private AtomicInteger mOpenCounter = new AtomicInteger();

private static DatabaseManager instance;
private static SQLiteOpenHelper mDatabaseHelper;
private SQLiteDatabase mDatabase;

public static synchronized void initializeInstance(SQLiteOpenHelper helper) {
if (instance == null) {
instance = new DatabaseManager();
mDatabaseHelper = helper;
}
}

public static synchronized DatabaseManager getInstance() {
if (instance == null) {
throw new IllegalStateException(DatabaseManager.class.getSimpleName() +
" is not initialized, call initializeInstance(..) method first.");
}
return instance;
}

public synchronized SQLiteDatabase openDatabase() {
int incrementAndGet = mOpenCounter.incrementAndGet();
Log.e("TAG","openDatabase->"+incrementAndGet);
if( incrementAndGet == 1) {
// Opening new database
mDatabase = mDatabaseHelper.getWritableDatabase();
}
return mDatabase;
}

public synchronized void closeDatabase() {
int decrementAndGet = mOpenCounter.decrementAndGet();
Log.e("TAG","closeDatabase->"+decrementAndGet);
if(decrementAndGet == 0) {
// Closing database
mDatabase.close();
}
}
}

备注

参考资料:
Android SQLite详解
sqlite是否线程安全

传送门GitHub