小白到大神,你需要了解的 sqlite 最佳实践
本文微信公众号「AndroidTraveler」首发。
背景
本文是对一篇英文文档的翻译,原文请见文末链接。
并发数据库访问
假设你实现了自己的 SQLiteOpenHelper。
public class DatabaseHelper extends SQLiteOpenHelper { ... }
现在你想要在多个线程中对数据库写入数据。
// Thread 1
Context context = getApplicationContext();
DatabaseHelper helper = new DatabaseHelper(context);
SQLiteDatabase database = helper.getWritableDatabase();
database.insert(…);
database.close();
// Thread 2
Context context = getApplicationContext();
DatabaseHelper helper = new DatabaseHelper(context);
SQLiteDatabase database = helper.getWritableDatabase();
database.insert(…);
database.close();
你将会在你的 logcat 中发现下面信息,并且你的其中一个改变不会写入数据库:
android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5)
产生这个错误的原因是因为,每次你创建新的 SQLiteOpenHelper
对象,实际上你创建了新的数据库连接。如果你尝试从不同的连接同时对数据库写入数据,其中一个会失败。
为了在多线程使用数据库,我们要确保只使用一个数据库连接。
让我们构造单例类 DatabaseManager
,它会持有并返回单个 SQLiteOpenHelper
对象。
public class DatabaseManager {
private static DatabaseManager instance;
private static SQLiteOpenHelper mDatabaseHelper;
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 initialize(..) method first.");
}
return instance;
}
public synchronized SQLiteDatabase getDatabase() {
return mDatabaseHelper.getWritableDatabase();
}
}
在多个线程中对数据库写入数据,修改后的代码如下所示。
// In your application class
DatabaseManager.initializeInstance(new DatabaseHelper());
// Thread 1
DatabaseManager manager = DatabaseManager.getInstance();
SQLiteDatabase database = manager.getDatabase()
database.insert(…);
database.close();
// Thread 2
DatabaseManager manager = DatabaseManager.getInstance();
SQLiteDatabase database = manager.getDatabase()
database.insert(…);
database.close();
这会带来另一个奔溃。
java.lang.IllegalStateException: attempt to re-open an already-closed object: SQLiteDatabase
由于我们只使用了一个数据库连接,Thread1 和 Thread2 的 getDatabase()
方法都会返回同一个 SQLiteDatabase
对象实例。可能发生的场景是 Thread1 关闭了数据库,然而 Thread2 还在使用它。这也就是为什么我们会有 IllegalStateException
的奔溃的原因。
我们需要确保没有人正在使用数据库,这个时候我们才可以关闭它。stackoveflow 上有人推荐永远不要关闭你的 SQLiteDatabase。这会让你看到下面的 logcat 信息。所以我一点也不认为这是一个好的想法。
Leak found
Caused by: java.lang.IllegalStateException: SQLiteDatabase created and never closed
实战例子
一种可能的解决方案是使用计数器跟踪打开/关闭的数据库连接。
public class DatabaseManager {
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() {
if(mOpenCounter.incrementAndGet() == 1) {
// Opening new database
mDatabase = mDatabaseHelper.getWritableDatabase();
}
return mDatabase;
}
public synchronized void closeDatabase() {
if(mOpenCounter.decrementAndGet() == 0) {
// Closing database
mDatabase.close();
}
}
}
然后如下所示来使用。
SQLiteDatabase database = DatabaseManager.getInstance().openDatabase();
database.insert(...);
// database.close(); Don't close it directly!
DatabaseManager.getInstance().closeDatabase(); // correct way
每当你需要使用数据库的时候你应该调用 DatabaseManager
类的 openDatabase()
方法。在这个方法里面,我们有一个计数器,用来表明数据库打开的次数。如果计数为 1,意味着我们需要创建新的数据库连接,否则,数据库连接已经建立。
对于 closeDatabase()
方法来说也是一样的。每次我们调用这个方法的时候,计数器在减少,当减为 0 的时候,我们关闭数据库连接。
现在你能够使用你的数据库并且确保是线程安全的。
由于本人翻译水平有限,如果你有更好的翻译文案,欢迎在 GitHub 提 PR。
这边建了一个仓库,欢迎提 PR 投稿一些好的文章翻译。
并发数据库访问
小白到大神,你需要了解的 sqlite 最佳实践的更多相关文章
- 【同行说技术】iOS程序员从小白到大神必读资料汇总
在文章<iOS程序员从小白到大神必读资料汇总(一)>里面介绍了很多iOS入门学习的资料,今天小编就发几篇技术进阶的文章,快来看看吧! 一.iOS后台模式开发指南 这个教程会教你在什么时候怎 ...
- 老杜告诉你java小白到大神是怎么炼成的(转载)
老杜告诉你java小白到大神是怎么炼成的 1. 学习前的准备 一个好的学习方法(应该怎么学习更高效): 一个合格的程序员应该具备两个能力 有一个很好的指法速度(敲代码快) 有一个很好的编程思想(编程思 ...
- linux下鼠标穿透和取消穿透--linux小白,大神无视
最近在用qt写一个跨平台的软件,因为设置了无边框,并且我自己给程序窗口加了阴影,阴影范围又比较大 所以必须给阴影区域加上鼠标穿透才能有更好的体验. 上网查了一下,在windows下使用SetWindo ...
- mui初级入门教程(四)— 再谈webview,从小白变“大神”!
文章来源:小青年原创发布时间:2016-06-05关键词:mui,html5+,webview转载需标注本文原始地址: http://zhaomenghuan.github.io/#!/blog/20 ...
- 小白到大神,Python 密集知识点汇总
Python 基础 1. 变量 你可以把变量想象成一个用来存储值的单词.我们看个例子. Python 中定义一个变量并为它赋值是很容易的.假如你想存储数字 1 到变量 "one" ...
- AtCoder从小白到大神的进阶攻略
前言 现在全球最大的编程比赛记分网站非CodeForces和AtCoder莫属了,@ezoixx130大佬已经在去年介绍过CodeForces了(传送门),那么现在我们主要谈一下AtCoder. 简介 ...
- 自学python,从小白到大神,需要多久?
2020年10月 TIOBE 排行榜超过了 Java, 历史上首次 Python 超越了 Java ,再次让许多朋友对 Python 产生了兴趣,今天我们来梳理下学习 Python 几个阶段或者级别, ...
- 【同行说技术】Python程序员小白变大神必读资料汇总( 三)
在文章<Python开发.调试.爬虫类工具大全>里面向大家总结了各种实用工具和爬虫技术,今天小编收集了5篇带有实例干货的资料,赶紧来看看吧!另外,喜欢写博客的博主可以申请加工程师博主交流群 ...
- 通过开发工具发布web应用到tomcat服务器中--对于小白,大神可以忽略不看,勿喷,谢谢
需要的工具 MyEclipse和TomCat 本人用的是MyEclipse2014和TomCat7 TomCat结构图 第一步:在MyEclipse中配置TomCat 如图所示: 第二步:创建Web项 ...
- React 生态系统:从小白到大神
http://mp.weixin.qq.com/s/Epx46lznpnvgrIsbmAIZBA
随机推荐
- luogu P2345 奶牛集会 |排序+树状数组
题目描述 约翰的N 头奶牛每年都会参加"哞哞大会".哞哞大会是奶牛界的盛事.集会上的活动很多,比如堆干草,跨栅栏,摸牛仔的屁股等等.它们参加活动时会聚在一起,第i 头奶牛的坐标为X ...
- luogu P2824 [HEOI2016/TJOI2016]排序
题目描述 在2016年,佳媛姐姐喜欢上了数字序列.因而他经常研究关于序列的一些奇奇怪怪的问题,现在他在研究一个难题,需要你来帮助他.这个难题是这样子的:给出一个1到n的全排列,现在对这个全排列序列进行 ...
- JavaScript基础5
字符串操作方法和属性 length 获取字符串的长度 str.chartAt(指定字符的索引) 获取指定位置的字符 返回string类型的值 str.concat(字符串) 链接多个字符串 ...
- CoderForces999B- Reversing Encryption
time limit per test 1 second memory limit per test 256 megabytes input standard input output standar ...
- SpringBoot系列之集成Mybatis教程
SpringBoot系列之集成Mybatis教程 环境准备:IDEA + maven 本博客通过例子的方式,介绍Springboot集成Mybatis的两种方法,一种是通过注解实现,一种是通过xml的 ...
- protobuf-rpm
############################################################## # http://www.rpm.org/max-rpm/ch-rpm-i ...
- Win32_Processor CPU 参数说明
转载自:https://blog.csdn.net/yeyingss/article/details/49385421 AddressWidth --在32位操作系统,该值是32,在64位操作系统是 ...
- 超简单!asp.net core前后端分离项目使用gitlab-ci持续集成到IIS
现在好多使用gitlab-ci的持续集成的教程,大部分都是发布到linux系统上的,但是目前还是有很大一部分企业使用的都是windows系统使用IIS在部署.NET应用程序.这里写一下如何使用gitl ...
- 刷新.NET
.NET Core 发布的那一天起,它在完成自我刷新的过程,一切为了适应未来,云原生.不仅仅跨平台那么简单. .NET Core 未来发展路线 我们发现跳过了.NET Core 4.X 避免了和目前. ...
- 【NodeJS】跨域
[NodeJS]跨域 转载:https://www.cnblogs.com/yangchongxing/p/10635480.html var express = require('express') ...