关于Android数据库的小结

一.SQLite

我对Android数据库架构的理解,至少分为两层,有时分为三层甚至更多。

  1. Helper层,继承SQLiteOpenHelper,负责完成数据库的创建和更新操作
  2. DAO层,(Database Access Object)数据库访问层,负责封装增删改查等具体操作
  3. BIZ层,它在DAO层之上,封装具体业务操作。比如解析xml或json文件后把数据直接插入数据库等。

1)关于Helper层

我从Helper层开始讲起,这是Android代码的基础。这里涉及到onCreate方法和onUpgrade方法何时会被调用的问题,不过那都是小问题。首先我想谈的是跨版本升级数据库时,如何转移数据。

我采取的做法是,在onUpgrade方法中做一个for循环,循环的值是每一次的版本,用switch语句来执行从低版本向紧邻的高版本过度时,数据库表结构改变的代码。

for(int i=oldVersion;i<newVersion;i++){
    switch(i){
        case 1:
            upgradeFromVersion1ToVersion2(db);//这是从版本一升级到版本二时调用的方法,参数是数据库对象
            break;
        case 2:
            upgradeFromVersion2ToVersion3(db);
            break;
        ...
}

这样一旦跨版本要升级数据库时,间隔的升级方法都会走一次。保证跨版本升级数据库不会有问题。

另外,更改表结构时,Android中SQLite并不支持删除表中的字段。所以当需要删除表中字段时就比较蛋疼,需要先创建临时表(临时表中已经把该删的字段删掉了),然后把原表数据都复制到新表中,再删除旧表,把临时表改名为旧表的名称,来完成删除表中字段的目的。同时,给某个字段改名也要用这种方式。

给某个表添加一个新的字段(测试成功)

//给mytable表添加一个新的字段为remark
String sql="alter table mytable add column 'remark' varchar;";
db.execSQL(sql);

去掉某个表的某个字段(测试成功)

//创建临时表temp,这个表仿照旧表,去掉要去掉的字段
String sql1="create table temp( id integer primary key autoincrement , img varchar, url varchar);";
//从mytable中把数据复制给temp
String sql2="insert into temp select id ,img , url from mytable;";
//删除mytable表
String sql3="drop table mytable;";
//把temp改名为mytable,取代了之前的mytable的表结构
String sql4="alter table temp rename to mytable;";
db.execSQL(sql1);
db.execSQL(sql2);
db.execSQL(sql3);
db.execSQL(sql4);

新建表(测试成功)

String sql5="create table table_name( column_name1 varchar,column_name2 varchar);";
db.execSQL(sql5);

修改列名(测试失败)

String sql6="alter table table_name rename column_name1 to column_name3;";
db.execSQL(sql6);

删除表(测试成功)

String sql7="drop table table_name;";
db.execSQL(sql7);

一个值得深究的问题是,多个Helper类来管理数据库好,还是单个Helper来管理数据库好呢?

每一个Helper类,都是对应了一个数据库。把数据分成多个数据库的好处就是,庞大的数据库代码很容易分成很多不同的小类。这是最简单的拆类的方法,对新手来说又清晰又容易。但是导致的问题就是,多个数据库的数据涉及到并发处理的时候会很麻烦,比如,因为异常退出而要回滚两个数据库的事务。而单独一个Helper一个数据库的话,这个问题就好解决多了。所以,如果头脑清晰,最好还是用一个Helper类,一个数据库来管理数据。

2)关于DAO层
接下来讲DAO层。不同数据表的增删改查,有相同也有不同。

a)通过引用计数法来开关数据库提高效率。

什么是引用计数法,为啥用引用计数法呢?

多个Helper对象的时候,引用计数法会导致一个问题,就是每个Helper类都应该对应一个引用计数器,如果在BaseDao即Dao层父类中使用一个引用计数器,那么多个Helper对象开或关则会导致计数器错误。这个时候有两种解决方案,一是给Helper类加一个父类,在这个BaseHelper中添加引用计数器。二是加一个DBManager来管理每个Helper和计数器的对应关系。

我当时用的是第二种解决方案。

public class DBManager {

    /** * key是DBHelper的类名 */
    private static Map<String, HelperObject> map = new HashMap<String, HelperObject>();

    /** * 在计数器中注册DBHelper * @param helper * @return 成功表示注册成功,失败表示已存在 */
    public static boolean registerDBHelper(SQLiteOpenHelper helper) {
        String key = helper.getClass().getSimpleName();
        if (map.containsKey(key)) {
            return false;
        } else {
            map.put(helper.getClass().getSimpleName(), new HelperObject(helper));
            return true;
        }
    }

    /** * 打开数据库 * @param helper * @return * @throws Exception */
    public static SQLiteDatabase openDB(SQLiteOpenHelper helper) throws Exception {
        String key = helper.getClass().getSimpleName();
        if (!map.containsKey(key)) {
            return null;
        } else {
            HelperObject o = map.get(key);
            return o.open();
        }
    }

    /** * 关闭数据库 * @param helper * @return * @throws Exception */
    public static boolean closeDB(SQLiteOpenHelper helper) throws Exception {
        String key = helper.getClass().getSimpleName();
        if (!map.containsKey(key)) {
            return false;
        } else {
            HelperObject o = map.get(key);
            o.close();
            return true;
        }
    }
}

/** * 数据库对象和计数器的组合类 */
class HelperObject {
    SQLiteOpenHelper helper;
    SQLiteDatabase database;
    AtomicInteger counter;

    public SQLiteDatabase open() throws Exception {
        if (counter.incrementAndGet() == 1) {
            return database = helper.getWritableDatabase();
        }
        if(counter.get()<1)
        {
            throw new Exception("数据库计数器异常");
        }
        return database;
    }

    public void close() throws Exception {
        if (counter.decrementAndGet() == 0) {
            database.close();
        }
        if(counter.get()<0)
        {
            throw new Exception("数据库计数器异常");
        }
    }

    public HelperObject(SQLiteOpenHelper helper) {
        // TODO Auto-generated constructor stub
        this.helper = helper;
        counter = new AtomicInteger();
    }
}

b)通过抽象父类来写公用的方法,再通过实现接口的方式来规范参数或返回值不同的方法。
我这里的例子,因为把数据从实体类中放入ContentValues的代码都放在了Biz中,所以BaseDao比较清晰。同时,我用了上面写的DBManger类来管理引用计数。

abstract class BaseDao
{
    protected SQLiteDatabase mDatabase;// 数据库对象
    protected SQLiteOpenHelper mhelper;// SQLiteOpenHelper对象

    /** * @description:构造方法 子类继承时,需要传入SQLiteOpenHelper对象 * @param helper SQLiteOpenHelper对象 */
    protected BaseDao(SQLiteOpenHelper helper)
    {
        mhelper = helper;
        DBManager.registerDBHelper(helper);
    }

    /** * 打开数据库 */
    protected synchronized void openDatabase()
    {
        try
        {
            mDatabase = DBManager.openDB(mhelper);
        }
        catch (Exception e)
        {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    /** * 关闭数据库 */
    protected synchronized void closeDatabase()
    {
        try
        {
            DBManager.closeDB(mhelper);
        }
        catch (Exception e)
        {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    /** * @description:删除数据 */
    public void deleteAll()
    {
        openDatabase();
        try
        {
            mDatabase.beginTransaction();
            mDatabase.delete(getTableName(), null, null);
            mDatabase.setTransactionSuccessful();
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
        finally
        {
            mDatabase.endTransaction();
        }
        closeDatabase();
    }

    /** * @description:通过id删除 * @param id */
    public void deleteById(String id)
    {
        openDatabase();
        mDatabase.beginTransaction();
        try
        {
            mDatabase.delete(getTableName(), getIdName()
                    + "=?", new String[] { id });
            mDatabase.setTransactionSuccessful();
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
        finally
        {
            mDatabase.endTransaction();
        }
        closeDatabase();
    }

    /** * @description:通过条件删除 * @param whereClause * @param whereArgs */
    public void deleteByWhere(String whereClause, String[] whereArgs)
    {
        openDatabase();
        mDatabase.delete(getTableName(), whereClause, whereArgs);
        closeDatabase();
    }

    /** * @description:执行sql语句 * @param sql */
    protected void execSQL(String sql)
    {
        openDatabase();
        mDatabase.beginTransaction();
        try
        {
            mDatabase.execSQL(sql);
            mDatabase.setTransactionSuccessful();
        }
        catch (SQLException e)
        {
            e.printStackTrace();
        }
        finally
        {
            mDatabase.endTransaction();
        }
        closeDatabase();
    }

    /** * @description:通过条件获取Cursor对象 * @param selection * @param selectionArgs * @param orderBy * @return */
    protected Cursor getCursor(String selection, String[] selectionArgs, String orderBy)
    {
        if (mDatabase.isOpen())
            return mDatabase.query(getTableName(), null, selection, selectionArgs, null, null,
                    orderBy);
        return null;
    }

    /** * @description:获取主键Id的名称 * @return */
    public abstract String getIdName();

    /** * 获取数据条数 * * @return 数据条数 */
    public int getCount()
    {
        openDatabase();
        int count = 0;
        Cursor c = mDatabase.rawQuery("select count(*) from "+ getTableName(), null);
        if (c != null)
        {
            while (c.moveToNext())
            {
                count = c.getInt(0);
            }
            c.close();
        }
        closeDatabase();
        return count;
    }

    /** * @description:通过条件查找数据,判断数量 * @param selection * @param selectionArgs * @return */
    protected int getNumOfDataByClause(String selection, String[] selectionArgs)
    {
        openDatabase();
        int num = 0;
        Cursor c = getCursor(selection, selectionArgs, null);
        if (null != c)
        {
            while (c.moveToNext())
            {
                num = c.getCount();
            }
            c.close();
        }
        closeDatabase();
        return num;
    };

    /** * @description:获取数据库名称 * @return 数据库名 */
    public abstract String getTableName();

    /** * @description:插入数据 * @param value ContentValues对象 */
    public void insert(ContentValues value)
    {
        openDatabase();
        mDatabase.beginTransaction();
        try
        {
            mDatabase.insert(getTableName(), null, value);
            mDatabase.setTransactionSuccessful();
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
        finally
        {
            mDatabase.endTransaction();
            closeDatabase();
        }
    }

    /** * @description:插入数据表 * @param values */
    public void insertList(List<ContentValues> values)
    {
        openDatabase();
        mDatabase.beginTransaction();
        try
        {
            for (int i = 0; i < values.size(); i++)
            {
                ContentValues value = values.get(i);
                mDatabase.insert(getTableName(), null, value);
            }
            mDatabase.setTransactionSuccessful();
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
        finally
        {
            mDatabase.endTransaction();
            closeDatabase();
        }
    }

    /** * @description:更新数据 * @param values * @param whereClause * @param whereArgs */
    public void update(ContentValues values, String whereClause, String[] whereArgs)
    {
        openDatabase();
        mDatabase.beginTransaction();
        try
        {
            mDatabase.update(getTableName(), values, whereClause, whereArgs);
            mDatabase.setTransactionSuccessful();
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
        finally
        {
            mDatabase.endTransaction();
            closeDatabase();
        }
    }

    /** * @description: 实现查方法的接口 * @author: Jax * @date: 2015年7月2日 下午12:50:25 */
    interface IDao<T>
    {
        /** * @description:查询全部 * @return */
        public List<T> queryAll();

        /** * @description:通过Id查询 * @param id * @return */
        public T queryById(String id);

        /** * @description:通过sql查询 * @param sql * @return */
        public List<T> queryBySql(String sql, String[] selectionArgs);

        /** * @description:通过条件查询 * @param whereClause * @param whereArgs * @return */
        public List<T> queryByWhere(String whereClause, String[] whereArgs);
    }
}

这里解释几个设计思路。

首先,为啥要用抽象父类而不是普通父类呢?而且为啥没有加public修饰呢?因为,继承它要实现两个抽象方法,getTableName()和getIdName(),分别要在里面实现获取表名和获取主键名称的代码,里面很多公用方法都需要这两个参数。然后,考虑到这个类不应该让包外的类访问到,所以没有加public。

另外,为啥要用接口呢?用接口主要是考虑到DAO层和实体类的映射关系。很多表在查找数据时,都可以直接封装成实体类或实体类的List来返回出去。继承该接口,则可以很方便的实现这些方法就可以方便地完成映射关系了。同时,可以给DAO层的编写提供一个规范,减少冗余。

当然,因为自己是第一次参与架构。很多东西并没有经验,后来也并没有精益求精地改善。比如接口里面,queryAll,还有queryById方法实现就用queryByWhere方法就能完成等。

这里要注意的问题是,数据嵌套查询。如果你在一个DAO类中引用到了另一个DAO类,就意味着你出现了数据嵌套查询。虽然能实现功能,但是会十分影响效率。有一些方法是可以避免这种情况出现的,比如用sql语句中的in,来一次性查询数据。

二.OrmLite

OrmLite是我做手机软件时接触的一个关系型数据库框架。适合快速开发。而我对于DAO层尝试实现的种种框架,都是想自己实现其中的效果。

所谓关系型数据库,把实体类和数据库表做了个映射。

简而言之,你只要有实体类,OrmLite能自动帮你建表和生成DAO对象。它通过给实体类加标注的方式来实现映射关系。然后在Helper中通过TableUtil来建表。需要数据的时候,通过helper对象获取DAO对象,然后DAO对象中自动封装好了增删改查的方法。

看过一些别人的例子后,对于OrmLite大概有一些直观的印像。但是方便起见,我把它生成的DAO对象封装在了BIZ层中,并没有直接通过Activity来调用。我在BIZ层中加入了如下代码,来获取我们的Helper对象。

private MyOrmliteOpenHelper myHelper = null;
private MyOrmliteOpenHelper getHelper() {
    if (myHelper == null) {
        myHelper = 
            OpenHelperManager.getHelper(context,
                MyOrmliteOpenHelper .class);
    }
    return myHelper;
}

在别人的例子中我们可以看到,DAO对象都是通过Helper来获取的。在BIZ层中,我们就通过getHelper方法获取Helper对象,来获取DAO对象,然后包装业务。

通过Where类的说明文档中我们可以看到,OrmLite帮我们封装好了很多查询语句,包括and,between,大于小于等。

OrmLite,连续单条数据操作时,和SQLite相比比较缓慢。所以尽量用多条数据一起操作。其他方面和SQLite差不多。

In the end.

写在最后,应该是写什么转载注明出处之类的话了,但是自己也知道,经验不够,写的也不咋地。反正每隔一段时间就会意识到自己之前的代码写的像翔一样。到时候真的自己写的东西很好的时候,我再加上转载请注明出处喽。

你可能感兴趣的:(android,数据库)