面向对象的Android数据库框架OOSqlite

喜欢看代码的,直接上代码
GitHub:https://github.com/kkmoving/OOSqliteApp

在Android应用中,对条目类数据的存储通常会用到数据库,Andriod提供sqlite来实现数据库存储。

应用对数据的使用通常是面向对象的,sqlite对数据的存储并不是面向对象的。这里提供一种面向对象的sqlite数据库框架设计OOSqlite(Object-Oriented Sqlite)。

OOSqlite借鉴J2EE中Hibernate的思想,将数据抽象为Entity,并映射为数据库的一张表,一个Entity的实例就是数据表的一条记录。

先来看看OOSqlite的使用
Define your entity

public class User extends OOSqliteEntity {   
  private static final int NAME_COL_ID = 1;
  private static final int AGE_COL_ID = 2; 

  @Indexing
  @ColumnTag(NAME_COL_ID)    
  String name;  

  @ColumnTag(AGE_COL_ID)
  int age;    

  @Transient    
  boolean choosen;        

  float score;        
}

Create

User.insert(user);

Retrieve

User.query(User.class, null);

Update

User.update(user);

Delete

User.delete(user);

OOSqlite设计思想

一个应用可以包含多个DB,一个DB可以包含多个Table,每个Table由若干字段组成。应用启动时创建DB及其所包含的Table;在应用运行过程中,对Table进行CRUD(增查改删)操作。

OOSqlite的核心是将数据抽象为Entity(实体),并将其注册到框架中,框架根据Entity的定义进行转换,并在DB中创建Table;进行CRUD操作时直接对Entity对象进行操作。

框架将DB抽象为OODatabase,它包含DB名称,版本及其Table列表。同时它是与底层数据库交互的媒介。

Table抽象为OOSqliteTable,包含Table名称,字段列表。它提供CRUD操作接口。

字段抽象为OOColumn,包含字段名称,字段类型,是否建索引等信息。同时OOColumn绑定到Entity的Field,用于Entity字段值的获取和设置,便于Entity和Table之间的转换。

数据抽象OOSqliteEntity,它包含数据库基础字段(如数据库id,last_access等)。业务层数据直接继承OOSqliteEntity,并定义业务字段。

OOSqlite实现

OOSqlite的实现包括:DB和Table的创建,CRUD操作。Table的创建涉及到Entity到Table的转换。

DB和Table的创建

OOSqlite框架通过OOSqliteManager来进行统一管理,提供注册DB和Table的方法,以及初始化数据库的方法。在应用启动时,进行DB和Table的注册,并调用初始化方法进行框架的初始化。

OOSqliteManager在初始化时,会对业务层注册的OODatabase进行初始化,通过实例化sqlite的SQLiteOpenHelper来完成物理层DB的创建。

OODatabase持有SQLiteOpenHelper实例引用,用于底层数据库交互;同时OODatabase持有业务层注册的OOSqliteTable列表,在SQLiteOpenHelper的onCreate回调中完成数据表的创建;在onUpgrade和onDowngrade中完成数据表的升级和降级。

Table创建的关键是获取字段列表,字段列表的获取是把Entity类的成员变量转换为数据库字段,然后组装成sql语句,使用SQLiteOpenHelper回传的SQLiteDatabase执行sql语句,创建物理数据表。

Table的升级和降级可能会进行表结构调整,如增加字段,同样通过获取Entity类的新增字段(新增字段使用注解标识,后面会讲到)。

Entity到Table的转换

Entity到Table有两个转换,一个是创建表时对表结构信息的转换,一个是CRUD操作时Entity对象实例和数据库数据的转换。CRUD操作的转换在CRUD操作中会具体讲到,这里先说创建表时的转换。

创建表的转换其实就是通过反射将Entity类的成员信息转换为数据表字段信息,这个转换会生成一个OOColumn列表,保存在OOSqliteTable中,OOSqliteTable使用OOColumn列表生成创建表的sql语句。

最简单的情况是,将Entity类的成员全部映射为数据表的字段,字段名为成员名(当然,可以做一些命名转换,如转为全小写),字段类型由Java基本数据类型转换为数据库字段类型。(暂时支持Java基本数据类型,也就是说该框架仅支持基本数据类型的存储,不涉及外键的关联存储。对于一般Android应用而言, 这其实是足够的。)

复杂的情况是:

  1. 为字段创建索引
  2. 排除非持久化字段。业务层是直接使用Entity对象实例的,可能会在其中定义一些不需要持久化的字段,在真正的数据表中应该排除这些字段。
  3. 扩展表字段。在数据库升级时,可能会增加表字段。
  4. 条件查询指定列 。业务层仅仅定义了Entity的,在条件查询时需要指定列进行条件查询。

这些都通过注解来实现。定义4种注解:
@ Indexing:标识字段需要创建索引。
@Transient:标识字段为非持久化字段。
@TargetVersion(version):为字段绑定一个版本,在数据库升级到相应版本或之上时,增加该字段。
@ColumnTag(col_id):为字段绑定一个col_id,在条件查询时通过一个col_id对应到指定列。同一个Entity中,不同字段指定不用col_id。

创建表转换时,对于标识@Indexing的字段,生成创建索引的sql语句,创建表时同时创建索引;对于标识@Transient的字段,排除在OOColumn列表之外,也就不创建在表中;对于标识@TargetVersion的字段,在Table升级到对应版本及之后时,增加到表中。

对于标识@ColumnTag的字段,OOSqliteTable会保存col_id到OOColumn的映射,这样就可以通过指定的col_id来指定列,实现条件查询。

CRUD

CRUD操作过程中,OODatabase主要负责向下执行,它持有SQLiteOpenHelper实例用于底层sqlite交互。OOSqliteTable主要负责向上提供面向Entity对象的CRUD操作接口,向下通过OODatabase间接与底层数据库交互,并在数据库数据和Entity对象实例之间进行转换。

OOSqliteTable类似于Hibernate中EntityManager的角色,提供面向对象的CRUD操作接口。但是对于业务层来讲,这个角色是不必要的,业务层只定义了业务Entity,并不关心其他的。因此,这里的实现使用一种更简洁方便的方式,CRUD操作直接通过Entity提供静态方法来实现

为了能够提供静态CRUD接口,框架保存了一个Entity的Class到OOSqliteTable的映射,CRUD操作传入相应的Class即可映射到OOSqliteTable,进行真正的CRUD操作。

插入(Create)

public static void insert(LeSqliteEntity entity)

插入操作只需要传入一个Entity对象。

通过对象的Class找到OOSqliteTable,在其中完成插入操作。OOSqliteTable将Entity对象转换为ContentValues,并调用SQLiteDatabase的insert接口,插入到数据库中。Entity对象转换到ContentValues本质上是将Entity对应的OOColumn列表转换到ContentValues中。

查询(Retrieve)

public static List query(Class cls, String selection) 

查询操作传入Entity的Class和selection,指定要查询的表和条件。
通过Class对应的OOSqliteTable调用SQLiteDatabase
的query接口进行查询,并将查询结果Cursor转换化为OOColumn,创建Entity实例并设置Field值。
另外,Entity基类中封装了常用的条件语句接口,可直接调用作为selection,如equalSelection,likeSelection等。

修改(Update)

 public static int update(LeSqliteEntity entity)

修改操作传入Entity对象。
与插入操作类似,将Entity对象转换成ContentValues后,调用SQLiteDatabase的update接口进行更新操作。不同的是修改操作需要事先判断Entity是否为游离实体(不是从数据库取出的实体为游离实体),如果不是游离实体才能更新到数据库。(很好理解,如果是游离实体,应该是插入操作而不是更新操作)。

删除(Delete)

public static int update(LeSqliteEntity entity)

删除操作传入Entity对象。
OOSqliteTable根据其数据库ID,调用SQLiteDatabase的delete接口,实现删除操作。与更新操作类似,删除操作事先也会判断Entity是否为游离实体。

以上就是面向对象的Android数据库框架的设计与实现。下面讲讲基于这个框架,应用层如何使用数据库。

使用

对于OOSqlite的使用,包括以下几个方面:

  1. 定义业务Entity
  2. 注册业务DB和Table,调用中间件初始化
  3. 调用业务Entity的CRUD操作
  4. 扩展表字段

下面举个例子,以应用需要批量保存用户信息为例。

定义业务Entity

首先我们需要一张表来存储用户信息,先定义实体User继承自OOSqliteEntity。User字段包括:
姓名:name。通常会指定姓名进行查询,因此name是需要创建索引的,另外需要指定name进行查询。
年龄:age
是否选中:choosen。这是业务层需要的临时字段,不需要持久化。
得分:score

 public class User extends OOSqliteEntity {   
      private static final int NAME_COL_ID = 1;
      private static final int AGE_COL_ID = 2; 

      @Indexing
      @ColumnTag(NAME_COL_ID)    
      String name;  

      @ColumnTag(AGE_COL_ID)
      int age;    

      @Transient    
      boolean choosen;        

      float score;        
 }

注册业务DB和Table,中间件初始化

注册DB,DB名称可以随便取,这里叫app.db,版本号定义1。中间件初始化调用LeSqliteManager的初始化接口完成,调用的时机可以在Application的onCreate。

public class DemoApplication extends Application {
    @Override    
    public void onCreate() {
        super.onCreate();        
        DatabaseManager.init(this);    
    }
}

public class DatabaseManager {    

    public static void init(Context context) {        
        OODatabase database = new OODatabase("app.db", 1); 
        database.registerEntity(User.class);

        OOSqliteManager.getInstance().register(database);
        OOSqliteManager.getInstance().initAllDatabase(context);    
    }   
}

Table的注册直接注册Entity的Class即可。如果需要在数据表创建、升级和降级回调中执行业务逻辑,可以实例化OOSqliteTable注册到OODatabase 中, 在OOTableListener中完成实现业务逻辑。
比如,想要在数据表创建后即添加一个初始的用户。

调用OODatabase的registerTable接口:

database.registerEntityWithListener(User.class, User.createListener());

实现OOTableListener,并实例化OOSqliteTable:

public static OOTableListener createListener() {
    return new OOTableListener() {

        @Override
        public void onCreate() {
            User user = new User();
            user.name = "admin";

            User.insert(user);
        }

        @Override
        public void onUpgrade(int oldVersion, int newVersion) {
            if (oldVersion < 2 && newVersion >= 2) {
                User user = new User();
                user.name = "upgrade";

                User.insert(user);
            }
        }

        @Override
        public void onDowngrade(int oldVersion, int newVersion) {

        }

        @Override
        public void onReady() {

        }
    };
}

CRUD操作

添加用户

public static void add(String name, int age) {
    User user = new User();
    user.name = name;
    user.age = age;

    User.insert(user);
}

查询所有用户

同步查询

public static List<User> queryAll() {
    return User.query(User.class, null);
}

异步查询

public static void asyncQueryAll() {
    User.queryAsync(User.class, null, new LeQueryCallback() {
        @Override
        public void onQuerySuccess(List list) {
            
        }
    });
}

按名字查询,并按年年龄升序排序

public static List<User> queryByNameOrderByAge(String name) {
    String selection = User.equalSelection(getNameColumn(), name);
    return User.query(User.class, selection, getAgeColumn(), true);
}

private static OOColumn getNameColumn() {
    return getColumn(User.class, NAME_COL_ID);
}

private static OOColumn getAgeColumn() {
    return getColumn(User.class, AGE_COL_ID);
}

通过@ColumnTag绑定的字段ID找到对应的OOColumn,现使用equalSelection接口生成selection,并指定年龄需要升序排序的列。

自定义查询
查询年龄大于30的用户

public static List<User> queryAgeOlderThan30() {
    OOColumn ageCol = getAgeColumn();
    String selection = ageCol.mName + ">30";

    return User.query(User.class, selection);
}

更新分数

mUser.score += 1;
User.update(mUser);

删除用户

User.delete(mUser);

扩展表字段
前面我们已经生成的数据库,如果需要扩展数据表的字段,比如User需要增加一个是否激活字段,那么只需要在User中再定义@TargetVersion标识的字段,TargetVersion的值定为2,表示数据库升级版本2及之后时,增加该字段。然后,在注册DB时,指定DB的版本为2。
定义active字段

@TargetVersion(2)
boolean active;

初始化时指定DB版本为2

OODatabase database = new OODatabase("app.db", 2);

总结

面向对象的Android数据库中间件的设计和使用就介绍完了。它实现了简单的面向对象数据库操作,对于业务层数据库存储实现来讲非常方便。这里说简单,是因此它仅支持基础数据类型(文本,整型,浮点数据,布尔值,二进制数据)的存储,没有实现外键的关联存储,也不支持Lazy加载。尽管简单,对于大部分的Andriod应用数据库存储需求,已经足够了_

说了这么多,show me the code!!!

GitHub: https://github.com/kkmoving/OOSqliteApp

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,835评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,598评论 1 295
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,569评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,159评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,533评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,710评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,923评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,674评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,421评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,622评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,115评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,428评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,114评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,097评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,875评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,753评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,649评论 2 271

推荐阅读更多精彩内容