【Android】SQLite实例(多线程下安全访问数据库)

1.前言

  最近有个刚接触开发的朋友在使用SQLite进行数据存储时出现各种问题,所以特意写了个DEMO发给他用于参考,以规避掉那些容易发生的突发状况。以下先附出代码部分,然后标出并解释对方在阅读Demo时产生的疑问。

2.代码部分

1.DBHelper类

package com.example.sqlitetest.db;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

import com.example.sqlitetest.db.constant.MyDBConstant;

/**
 * @ClassName:DBHelper
 * @Description:TODO<数据库帮助类--用于数据库的创建and版本管理>
 * @author:zihao
 * @date:2014年04月18日 上午11:00:11
 * @version:v1.0 构建一个基本的框架
 */
public class DBHelper extends SQLiteOpenHelper {

    /** 数据库名称 **/
    private static final String DB_NAME = "test.db";
    /** 数据库版本号 **/
    private static final int DB_VERSION = 1001;

    /**
     * 构造方法
     * 
     * @param context
     *            // 上下文对象
     */
    public DBHelper(Context context) {
        super(context, DB_NAME, null, DB_VERSION);// 调用父类方法进行数据库的创建、打开或管理
        // TODO Auto-generated constructor stub
    }

    /**
     * Called when the database is created for the first time. This is where the
     * creation of tables and the initial population of the tables should
     * happen. 首次创建数据库时调用.
     */
    @Override
    public void onCreate(SQLiteDatabase db) {
        // TODO Auto-generated method stub
        // 进行数据库表的创建
        createTable(db);
    }

    /**
     * 当数据库发生更新操作时触发--即当前版本号DB_VERSION比原版本号大时
     */
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        // TODO Auto-generated method stub
        // 用于处理版本更新(数据库表结构发生变更时,修改DB_VERSION时触发更新)
        // 在更新处理中,包含两种处理方式:1.保留原始数据(1.取出数据,删除旧表;2.创建新表;3.插入数据);2.不保留原始数据(1.删除旧表;2.创建新表)--这里默认用第二种.

        // 这里默认用第二种处理方式
        // 1.删除旧表
        deleteAllTable(db);
        // 2.调用onCreate创建新表
        onCreate(db);
    }

    /**
     * TODO<创建表格>
     * 
     * @return void
     */
    private void createTable(SQLiteDatabase db) {
        // 所有表格的创建都在这里进行
        db.execSQL(MyDBConstant.CREATE_TB_USER_SQL);
    }

    /**
     * TODO<删除所有表格>
     * 
     * @return void
     */
    private void deleteAllTable(SQLiteDatabase db) {
        // 所有表格的删除都在这里进行
        db.execSQL("DROP TABLE IF EXISTS " + MyDBConstant.TB_NAME_USERINFO);
    }

}

2.DBManager类

package com.example.sqlitetest.db;

import java.util.concurrent.atomic.AtomicInteger;

import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;

/**
 * @ClassName:DBManager
 * @Description:TODO<数据库管理类>
 * @author:zihao
 * @date:2014年04月18日 上午11:02:23
 * @version:v1.0 构建一个基本的框架
 */
public class DBManager {
    private static final String TAG = DBManager.class.getSimpleName();

    private AtomicInteger mOpenCounter = new AtomicInteger();// 用于操作计数
    private static DBManager mInstance;
    private static DBHelper mDbHelper;
    private SQLiteDatabase mDatabase;

    /**
     * TODO<获取一个数据库管理实例>
     * 
     * @return DBManager
     */
    public static synchronized DBManager getInstance(Context context) {

        if (mInstance == null) {
            mInstance = new DBManager();
            mDbHelper = new DBHelper(context);
        }

        return mInstance;
    }

    /**
     * TODO<获得一个读写数据库操作对象>
     * 
     * @return SQLiteDatabase
     */
    public synchronized SQLiteDatabase getWriteDatabase() {
        if (mOpenCounter.incrementAndGet() == 1) {// 每次获取该实例mOpenCounter都通过自增来进行标记
            mDatabase = mDbHelper.getWritableDatabase();
        }
        return mDatabase;
    }

    /**
     * TODO<获取一个只写的数据库操作对象>
     * 
     * @return SQLiteDatabase
     */
    public synchronized SQLiteDatabase getReadDatabase() {
        if (mOpenCounter.incrementAndGet() == 1) {// 每次获取该实例mOpenCounter都通过自增来进行标记
            mDatabase = mDbHelper.getReadableDatabase();
        }
        return mDatabase;
    }

    /**
     * TODO<关闭数据库>
     * 
     * @return SQLiteDatabase
     */
    public synchronized void closeDatabase() {
        if (mOpenCounter.decrementAndGet() == 0) {// 自减判断计数值是否为0,如果不为0,表明其它位置还在引用该实例,不能关闭数据库
            mDatabase.close();
        }
    }
}

3.MyDBConstant类

package com.example.sqlitetest.db.constant;


/**
 * @ClassName:MyDBConstant
 * @Description:TODO<数据库常量工具类--方便管理、维护>
 * @author:zihao
 * @date:2015年12月22日 上午11:23:52
 * @version:v1.0
 */
public class MyDBConstant {

    /** 用户信息表 **/
    public static final String TB_NAME_USERINFO = "userInfo";

    /**
     * @ClassName:TB_USER
     * @Description:TODO<用户表>
     * @author:zihao
     * @date:2015年12月22日 上午11:26:56
     * @version:v1.0
     */
    public class TB_USER {
        /** 用户id **/
        public static final String USER_ID = "user_id";
        /** 用户名 **/
        public static final String USER_NAME = "user_name";
        /** 密码 **/
        public static final String PASS_WORD = "pass_word";
    }

    /** 新建会议主题信息表sql语句 **/
    public static final String CREATE_TB_USER_SQL = "CREATE TABLE IF NOT EXISTS "
            + TB_NAME_USERINFO
            + "("
            + TB_USER.USER_ID
            + " VARCHAR(100) PRIMARY KEY NOT NULL,"
            + TB_USER.USER_NAME
            + " VARCHAR(40)," + TB_USER.PASS_WORD + " VARCHAR(40))";

}

3.疑问

1.为什么要使用线程锁?

  限制并发线程访问同一个对象中的这个synchronized(this)同步代码块时,同一时间内只能有一个线程得到执行,另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。

2.是否有必要使用AtomicInteger计数?

  假设我们在ThreadA、ThreadB中对DBManager中的getWriteDatabase()方法进行操作,那么我们获取到的是相同的数据库连接对象,那么在不加AtomicInteger计数的情况下,我们调用closeDatabase()会直接执行数据库连接关闭功能,那么在ThreadB中再次使用该对象的时候就会抛出以下异常:
  java.lang.IllegalStateException: attempt to re-open an already-closed object: SQLiteDatabase(尝试重新打开一个已经关闭的对象).
  所以使用AtomicInteger来进行计数,在每获取一次数据库连接时自增统计,在调用数据库连接关闭方法时,通过自减判断是否还有引用,如果在别处已无引用,则关闭该连接。
   
    

建议阅读:
多线程下访问数据库
JAVA多线程与并发学习总结

声明:如本文有不正之处,还望各位不吝赐教,多谢^_^

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
SQLite中,数据库连接是串行的,这意味着同一时刻只能有一个线程访问数据库。然而,SQLite提供了一些机制来支持多线程的操作。 首先,可以在SQLite上设置多线程模式。SQLite提供了三种多线程模式:Serialized、Single-thread和Multi-thread。Serialized模式是默认模式,它将所有连接串行化,不支持多线程操作。Single-thread模式允许多个连接,但只能在同一个线程内共享。Multi-thread模式允许多个连接在多个线程内共享,但要确保每个连接在不同的线程内使用,否则会出现并发问题。 其次,SQLite提供了一个名为sqlite3_threadsafe()的函数,可以用来检查当前SQLite库的多线程支持情况。该函数返回0表示SQLite不支持多线程操作,返回1表示支持多线程操作。 对于多线程应用程序,我们需要确保每个线程都有自己的数据库连接,并且在使用连接前,要确保使用同步机制来避免并发访问数据库的问题。常用的同步机制有互斥锁(Mutex)和条件变量(Condition Variable)。互斥锁用于保护对数据库访问,只有获取到锁的线程才能执行数据库操作;条件变量用于线程之间的通信,可以通过等待条件变量触发来实现线程的同步。 在使用SQLite多线程的过程中,需要注意以下几点: 1. 每个线程都应该有自己的独立数据库连接。 2. 在访问数据库之前,需要获取互斥锁来保护对数据库访问。 3. 避免在多个线程之间共享数据库连接对象。 4. 在多线程操作中,要注意处理并发访问的问题,避免出现数据竞争和资源争用。 5. 当不再使用数据库连接时,要确保正确关闭连接,释放资源。 总的来说,虽然SQLite是一个轻量级的数据库,但是它也提供了一些机制来支持多线程操作。要正确使用SQLite多线程,需要确保每个线程都有自己的数据库连接,并且使用同步机制来避免并发访问的问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值