喜欢看代码的,直接上代码
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应用而言, 这其实是足够的。)
复杂的情况是:
- 为字段创建索引
- 排除非持久化字段。业务层是直接使用Entity对象实例的,可能会在其中定义一些不需要持久化的字段,在真正的数据表中应该排除这些字段。
- 扩展表字段。在数据库升级时,可能会增加表字段。
- 条件查询指定列 。业务层仅仅定义了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的使用,包括以下几个方面:
- 定义业务Entity
- 注册业务DB和Table,调用中间件初始化
- 调用业务Entity的CRUD操作
- 扩展表字段
下面举个例子,以应用需要批量保存用户信息为例。
定义业务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 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 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 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