小白到大神,你需要了解的 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
随机推荐
- SpringBoot添加热部署
一.导入依赖 <!--热部署--> <dependency> <groupId>org.springframework.boot</groupId> & ...
- oracle逻辑存储结构
oracle数据库管理系统有三个重要的概念:实例.数据库.数据库服务器.oracle数据库的存储结构可以分为逻辑存储结构和物理存储结构.逻辑存储结构用于描绘Oracle内部组织和管理数据的方式,而物理 ...
- [开源] .Net 使用 ORM 访问 达梦数据库
前言 武汉达梦数据库有限公司成立于2000年,为中国电子信息产业集团(CEC)旗下基础软件企业,专业从事数据库管理系统的研发.销售与服务,同时可为用户提供大数据平台架构咨询.数据技术方案规划.产品部署 ...
- 学习go语言第二天-变量、常量
编写测试程序 源码文件以_test结尾;例如:xxx_test.go 测试方法名以Test开头;例如:func TestXXXXX(t *testing.T){} 实现斐波那且数列 package f ...
- Python爬虫技术:爬虫时如何知道是否代理ip伪装成功?
前言本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理. python爬虫时如何知道是否代理ip伪装成功: 有时候我们的爬虫程序添加了 ...
- tensorflow SavedModelBuilder用法
训练代码: # coding: utf-8 from __future__ import print_function from __future__ import division import t ...
- python中bitmap的参数
Tkinter Bitmaps: 你会使用这个属性显示一个位图.有以下类型的可用位图. 你会使用这个属性显示一个位图.有以下类型的可用位图.: "error" "gray ...
- Applet和普通的Java应用程序有什么区别?
(1)运行方式不同.Java Applet程序不能单独运行,它必须依附于一个用HTML语言编写的网页并嵌入其中,通过与Java兼容的浏览器来控制执行.Java Application是完整的程序,可以 ...
- [系列] Go 使用 defer 函数 要注意的几个点
概述 defer 函数大家肯定都用过,它在声明时不会立刻去执行,而是在函数 return 后去执行的. 它的主要应用场景有异常处理.记录日志.清理数据.释放资源 等等. 这篇文章不是分享 defer ...
- tune kubernetes eviction parameter
Highlight 本文会介绍kubernetes中关于集群驱逐的相关参数, 合理设置驱逐速率的考虑因素, 但是不会涉及node层面资源的驱逐阈值的设置. Basic 在kubernetes中, 如果 ...