Android SQLite

在日常项目开发中,我们不可避免的会使用到数据库。Android中自带SQLite数据库是我们在开发中必须知道和了解的,那么如何更好的使用SQLite数据库就是我们需要学习的内容了。

首先,需要创建一个类继承SQLiteOpenHelper,并重写onCreate()与onUpdate()方法。

public class DBHelper extends SQLiteOpenHelper {
    private static final int DB_VERSION = 1;
    private static final String DB_NAME = "test.db";
    private static final String TABLE_NAME = "t_test";

    /**
     * 构造方法,创建数据库
     *
     * @param context
     */
    public DBHelper(Context context) {
        super(context, DB_NAME, null, DB_VERSION);
    }

    /**
     * 创建表
     *
     * @param db
     */
    @Override
    public void onCreate(SQLiteDatabase db) {
        String sql = "CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " (Id integer PRIMARY KEY,UserName text, Age integer, Country text)";
        db.execSQL(sql);
    }

    /**
     * 升级表
     *
     * @param db
     * @param oldVersion
     * @param newVersion
     */
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        String sql = "DROP TABLE IF EXISTS " + TABLE_NAME;
        db.execSQL(sql);
        onCreate(db);
    }
}

拥有这个类之后,就可以通过类实例的getWritableDatabase()和getReadableDatabase()方法获取到SQLiteDatabase对象,对数据库进行增删改查操作了。那么这两个方法获取到的SQLiteDatabase对象有什么区别呢?

  • 两个方法获取到的SQLiteDatabase对象都具有读与写的功能。
  • getWritableDatabase()方法以读写的方式打开数据库,若数据库已满,则此时调用getWritableDatabase()方法获取SQLiteDatabase实例将会发生错误(异常)。
  • getReadableDatabase()先以getWritableDatabase()方式打开数据库,若数据库已满,则以getReadableDatabase()获取SQLiteDatabase实例以只读的方式打开数据库。

拿到SQLiteDatabase对象后,我们就可以操作数据库了,但是在使用的时候要注意数据库的线程安全问题。在不同线程中使用DBHelper去操作数据库将会抛出如下异常:

android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5)

这是因为你每次去new一个SQLiteOpenHelper对象时都会去创建一个新的数据库连接,所以如果你试图同时从不同的连接去操作数据库时,就会抛出此异常。因此,在操作数据库时我们需要确保我们只用一个数据库连接。因此,我们可以创建一个DataBaseHelper单例类用于管理数据库连接。代码如下所示:

public class DataBaseHelper {
    private static DataBaseHelper instance;
    private static SQLiteOpenHelper mDatabaseHelper;

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

    public static synchronized DataBaseHelper getInstance() {
        if (instance == null) {
            throw new IllegalStateException(DataBaseHelper.class.getSimpleName() +
                    " is not initialized, call initialize(..) method first.");
        }
        return instance;
    }
  
    public synchronized SQLiteDatabase getDatabase() {
        return mDatabaseHelper.getWritableDatabase();
    }
}

这样子,我们在不同线程中操作数据库就没有问题了吗?然而事实并没有你想像的那么简单。我们都知道,在我们操作数据库的时候,每次操作完成之后就必须关闭数据库以节省资源,这样在不同线程中会出现这么一种情形,一个线程中的数据库操作结束后将数据库关闭,而另一个线程中的数据库操作却还没有完成,因此,系统会抛出如下异常:

java.lang.IllegalStateException: attempt to re-open an already-closed object: SQLiteDatabase

当然,我们可以在数据库操作结束后不去关闭连接,然而,这样会造成程序内存泄漏,系统提示如下:

Leak found
Caused by: java.lang.IllegalStateException: SQLiteDatabase created and never closed

所以,怎么样才能让数据库的操作在不同的线程中保持线程安全呢?我们可以使用一个AtomicInteger 变量去判断数据库使用的总数,从而进行相关的操作,完整代码如下:

public class DataBaseHelper {
    private AtomicInteger mOpenCounter = new AtomicInteger();
    private static DataBaseHelper instance;
    private static SQLiteOpenHelper mDatabaseHelper;
    private static SQLiteDatabase mDatabase;

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

    public static synchronized DataBaseHelper getInstance() {
        if (instance == null) {
            throw new IllegalStateException(DataBaseHelper.class.getSimpleName() +
                    " is not initialized, call initialize(..) 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();
        }
    }
}

有了上述两个类的准备,我们就可以对数据库进行增删改查的操作了,同时可以保证它是线程安全的。我们需要在自己的Application类中对DataBaseHelper进行初始化操作,代码如下:

 @Override
    public void onCreate() {
        super.onCreate();
        DataBaseHelper.initializeInstance(new DBHelper(this));
    }

别忘了在清单文件中配置自定义的Application。对于数据库的增、删、改,我们可以通过调用execSQL(String sql)方法或对应的操作API:insert()、delete()、update()。但是对于查的操作,我们就需要调用query()或rawQuery()方法了。

增加数据

我们可以使用execSQL(String sql)或者insert(String table,String nullColumnHack,ContentValues values)方法来插入,示例代码如下:

        //通过execSQL()方法插入数据
        db.beginTransaction();
        db.execSQL("insert into " + DBHelper.TABLE_NAME + " (Id, UserName, Age, Country) values (1, 'Arc', 30, 'China')");
        db.setTransactionSuccessful();
        DataBaseHelper.getInstance().closeDatabase();
        //通过insert()方法插入数据
        db = DataBaseHelper.getInstance().openDatabase();
        db.beginTransaction();
        ContentValues values = new ContentValues();
        values.put("Id", 2);
        values.put("UserName", "Alice");
        values.put("Age", 25);
        values.put("Country", "America");
        db.insert(DBHelper.TABLE_NAME, null, values);
        db.setTransactionSuccessful();
        DataBaseHelper.getInstance().closeDatabase();
删除数据

删除数据的方法除了execSQL还有delete(String table,String whereClause,String[] whereArgs),whereClause是删除条件,whereArgs是删除条件值数组,代码如下:

        //通过delete()方法删除Id=2的数据
        db = DataBaseHelper.getInstance().openDatabase();
        db.beginTransaction();
        db.delete(DBHelper.TABLE_NAME, "Id = ?", new String[]{String.valueOf(1)});
        db.setTransactionSuccessful();
        DataBaseHelper.getInstance().closeDatabase();

点击delete方法进入源码可以发现,源码内拼装了删除条件和删除条件值得数组,源码如下:

    public int delete(String table, String whereClause, String[] whereArgs) {
        acquireReference();
        try {
            SQLiteStatement statement =  new SQLiteStatement(this, "DELETE FROM " + table +
                    (!TextUtils.isEmpty(whereClause) ? " WHERE " + whereClause : ""), whereArgs);
            try {
                return statement.executeUpdateDelete();
            } finally {
                statement.close();
            }
        } finally {
            releaseReference();
        }
    }
修改数据

修改数据可以调用execSQL()和update(String table,ContentValues values,String whereClause, String[] whereArgs)方法,代码如下:

        //通过update()方法修改Id=1的数据Age=22
        db = DataBaseHelper.getInstance().openDatabase();
        db.beginTransaction();
        ContentValues cv = new ContentValues();
        cv.put("Age", 22);
        db.update(DBHelper.TABLE_NAME,
                cv,
                "Id = ?",
                new String[]{String.valueOf(1)});
        db.setTransactionSuccessful();
        DataBaseHelper.getInstance().closeDatabase();
查找数据

查找数据有两个方法,一是public Cursor query(String table,String[] columns,String selection,String[] selectionArgs,String groupBy,String having,String orderBy,String limit);,另外一个是public Cursor rawQuery(String sql, String[] selectionArgs)。rawQuery的写法类似上面的execSQL,在此不做介绍,query方法中的参数如下:

  • table:表名称
  • columns:列名称数组
  • selection:条件字句,相当于where
  • selectionArgs:条件字句,参数数组
  • groupBy:分组列
  • having:分组条件
  • orderBy:排序列
  • limit:分页查询限制
  • Cursor:返回值,相当于结果集ResultSet
    返回值Cursor是一个游标对象,其常用的方法如下表所示:
方法名方法描述
getCount()获得总数据条数
isFirst()判断是否为第一条记录
isLast()判断是否为最后一条记录
moveToFirst()移动到第一条记录
moveToLast()移动到最后一条记录
move(int offset)移动到指定记录
moveToPrevious()移动到上一条记录
moveToNext()移动到下一条记录
getColumnIndexOrThrow()根据列名获取索引
getInt(int columnIndex)获取指定索引的int类型数据
getString(int columnIndex)获取指定索引的String类型数据

查询数据的代码如下所示:

        //查询表中所有数据
        db = DataBaseHelper.getInstance().openDatabase();
        Cursor cursor = db.query(DBHelper.TABLE_NAME, null, null,
                null, null, null, null);
        if (cursor.getCount() > 0) {
            while (cursor.moveToNext()) {
                int id = cursor.getInt(cursor.getColumnIndex("Id"));
                String userName = cursor.getString(cursor.getColumnIndex("UserName"));
                int age = cursor.getInt(cursor.getColumnIndex("Age"));
                String country = cursor.getString(cursor.getColumnIndex("Country"));
            }
        }
        cursor.close();
        DataBaseHelper.getInstance().closeDatabase();

参考资料:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值