我对Android数据库架构的理解,至少分为两层,有时分为三层甚至更多。
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是我做手机软件时接触的一个关系型数据库框架。适合快速开发。而我对于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差不多。
写在最后,应该是写什么转载注明出处之类的话了,但是自己也知道,经验不够,写的也不咋地。反正每隔一段时间就会意识到自己之前的代码写的像翔一样。到时候真的自己写的东西很好的时候,我再加上转载请注明出处喽。