Android数据库进阶-从SQLite到ORMLite封装
Posted CameloeAnthony
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android数据库进阶-从SQLite到ORMLite封装相关的知识,希望对你有一定的参考价值。
项目代码:
CameloeAnthony/Ant
博客原地址:Android数据库进阶-从SQLite到ORMLite封装
前言
几乎每一个android项目中,都必不可少的会使用数据库的操作。在此之前我曾写过一篇文章Rxjava+数据库?来用用SqlBrite和SqlDelight吧! ,SqlBrite是对 Android 系统的 SQLiteOpenHelper 的封装,对SQL操作引入了响应式语义 (Rx)(用来在 RxJava 中使用)。在那之后确实也使用过一段时间的SqlBrite,不过可能是本人能力原因 ,在我的业务开发中,SqlBrite使用起来也并没有多么的方便 ,反而对整体的封装起到了一定的阻碍。所以后来也就继续回归使用ORMLite做数据库操作。下面文章还是从基础到封装再到实例讲讲我的项目中的ORMLite是怎么使用的吧。
ORMLite的引入
1 从SQLite到ORM
SQLite是在世界上使用的最多的数据库引擎,并且还是开源的。它实现了无配置,无服务要求的事务数据库引擎。SQLite可以在Mac OS-X, ios, Android, Linux, 和 Windows上使用。android中使用的正是SQLite。在Android开发中,使用SQLite作为基础部分,想必大家对继承 SQLiteOpenHelper 创建数据库,调用 SQLiteDatabase 的 execSQL() 方法执行 INSERT, UPDATE, DELETE 等语句来更新表的数据,不管你如何执行查询都会返回一个Android 的 SQLite 数据库游标……这一系列概念并不陌生啊 。想必很多人都和我一样,并不想写任何SQL语句,因为一不小心就写错了,而且各种重复的SQL语句写着真的心烦,大大的影响了开发的效率。
我们当然希望不需要再去和复杂的SQL语句打交道,在面向对象的编程中只要像平时操作对象一样操作它就可以了。这就引入了ORM。
ORM是对象关系映射(Object Relational Mapping)的缩写,对象和关系数据是业务实体的两种表现形式,业务实体在内存中表现为对象,在数据库中表现为关系数据,ORM实现了对象和关系数据库之间的转换。
2 java中ORM的原理
要实现JavaBean的属性到数据库表的字段的映射,任何ORM框架不外乎是读某个配置文件把JavaBean的属性和数据库表的字段自动关联起来,当从数据库Query时,自动把字段的值塞进JavaBean的对应属性里,当做INSERT或UPDATE时,自动把 JavaBean的属性值绑定到SQL语句中。
3 从ORM到ORMLite
ORM框架广泛引用于各种语言中,对于java开发者比较熟悉的有Hibernate,Ormlite等,Ormlite作为一个Java ORM。支持JDBC连接,Spring以及Android平台。除此之外Android中使用的ORM框架还有Greendao,ActiveAndroid,SugarORM, Realm等。后续项目会考虑使用 Realm,到时候再进行讲解。
ORMLite基础
ORMLite provides a lightweight Object Relational Mapping between Java classes and SQL databases. There are certainly more mature ORMs which provide this functionality including Hibernate and iBatis. However, the author wanted a simple yet powerful wrapper around the JDBC functions, and Hibernate and iBatis are significantly more complicated with many dependencies.
ORMLite 提供了一个轻量级的java对象和数据库的对象关系操作,相比于Hibernate 和iBatis 等成熟的ORM框架的繁重,ORMLite旨在提供一个简单而有效的解决方案。
当然和之前的所有文章一样, 基础部分都回归官方文档。这里会对android使用中的重点的基础部分进行提及并对官网的例子做出改动。官方文档中关于android的使用部分,请点击这里
首先看看封装之前的ORMLite在我项目中的使用步骤:
1 下载ORMLite的jar包
首先去http://ormlite.com/releases/ 下载jar包,对于Android目前版本为:ormlite-android-5.0.jar 和 ormlite-core-5.0.jar ;在我项目中添加的是之前的4.49的jar包。
2 创建实体类,这里利用新闻信息实体NewsItem类
@DatabaseTable(tableName = "tb_news_item")
public class NewsItem implements Serializable
@DatabaseField(generatedId = true, columnName = "i_id")
private int i_id;
@DatabaseField(columnName = "channelId")
@SerializedName(value = "channelId")
private int channelId;
@DatabaseField(columnName = "id")
@SerializedName(value = "id", alternate = "docid", "docId")
private int id;
@DatabaseField(columnName = "title")
@SerializedName(value = "MetaDataTitle", alternate = "title", "name")
private String title;
@DatabaseField(columnName = "content")
@SerializedName(value = "content")
private String content;
@DatabaseField(columnName = "type")
@SerializedName(value = "type", alternate = "t", "docType")
private int type;
@DatabaseField(columnName = "img", dataType = DataType.SERIALIZABLE)
@SerializedName(value = "image", alternate = "ic", "images", "picture", "pic", "img")
private ArrayList<String> images;
private String icon;
@DatabaseField(columnName = "url")
@SerializedName(value = "url", alternate = "link", "docURL","channelUrl")
private String url;
@DatabaseField(columnName = "date")
@SerializedName(value = "date", alternate = "PubDate", "time")
private String date;
@DatabaseField(columnName = "source")
@SerializedName(value = "source")
private String source;
@DatabaseField(columnName = "media")
@SerializedName(value = "media")
private String media;
@DatabaseField(columnName = "relPhotos")
@SerializedName(value = "RelPhotos")
private String relPhotos;
@DatabaseField(columnName = "isTopic")
private boolean isTopic = false;
@DatabaseField(columnName = "isStar")
private boolean isStar = false;
@DatabaseField(columnName = "channelItems", dataType = DataType.SERIALIZABLE)
@SerializedName(value = "channelItems")
private ArrayList<NewsItem> newsItems;
@DatabaseField(columnName = "parentChannelType")
private int parentChannelType;
//...... get set 方法
除了通过@SerializedName(value = "xxx")
支持Gson序列化。
这里有几个需要注意的地方:
- 1 通过
@DatabaseTable(tableName = "tb_news_item")
指定了表名为tb_news_item
.- 2 通过
@DatabaseField(generatedId = true, columnName = "i_id")
指定id字段为自动生成,并且名为i_id。- 3 对于ArrayList 序列化对象的的支持,需要使用
dataType = DataType.SERIALIZABLE
.可以通过DataType控制数据库表中字段类型。
3 继承OrmLiteSqliteOpenHelper类
我们需要通过继承OrmLiteSqliteOpenHelper类来编写自己的数据库帮助类,
需要实现的方法为onCreate(SQLiteDatabase sqliteDatabase, ConnectionSource connectionSource)
以及onUpgrade(SQLiteDatabase database, ConnectionSource connectionSource, int oldVersion, int newVersion)
。分别对应着数据库第一次创建 以及版本更新的时候调用的方法。
比如针对上面的NewsItem
类,
/**
* Database helper class used to manage the creation and upgrading of your database. This class also usually provides
* the DAOs used by the other classes.
*/
public class DatabaseHelper extends OrmLiteSqliteOpenHelper
private Context mContext;
// the DAO object we use to access the NewsItem table
private Dao<NewsItem, Integer> simpleDao = null;
// name of the database file for your application -- change to something appropriate for your app
private static final String DATABASE_NAME = "dbtest.db";
// any time you make changes to your database objects, you may have to increase the database version
private static final int DATABASE_VERSION = 1;
/**
*
* Returns the Database Access Object (DAO) for our NewsItem class. It will create it or just give the cached
* value.
*/
public Dao<NewsItem, Integer> getNewsItemDao() throws SQLException
if (simpleDao == null)
simpleDao = getDao(NewsItem.class);
return simpleDao;
public DatabaseHelper(Context context)
super(context, DATABASE_NAME, null, DATABASE_VERSION);
mContext = context;
@Override
public void onCreate(SQLiteDatabase database, ConnectionSource connectionSource)
try
TableUtils.createTable(connectionSource, NewsItem.class);
catch (SQLException e)
e.printStackTrace();
@Override
public void onUpgrade(SQLiteDatabase database, ConnectionSource connectionSource,
int oldVersion, int newVersion)
try
TableUtils.dropTable(connectionSource, NewsItem.class, true);
catch (SQLException e)
e.printStackTrace();
/**
* 释放资源
*/
@Override
public void close()
super.close();
// instance = null;
mContext = null;
可以看到的是NewsItem
就是我们的实体类,通过TableUtils.createTable(connectionSource, NewsItem.class);
在onCreate
方法中完成了对象表的创建。
4 提取DAO类并封装其中的方法
可以通过上面的这张图看到的是当前DatabaseHelper
类除了完成onCreate
中创建以及onUpgrade
中更新以外,也提供了DAO
类。
java的设计模式中这会经常遇到,我们需要将数据库的操作独立到数据库连接类(也就是Data Access Objects 简称DAO )。也就是说每个DAO类提供增删查改等操作。每个实体对象比如说上面的NewsItem.class 都对应着一个DAO类。OrmLiteSqliteOpenHelper 类正为我们提供了getDao方法,也就有了上面图片中的操作。
public class NewsItemDaoOld
private MyApplication myApplication;
public NewsItemDaoOld(MyApplication myApplication)
this.myApplication = myApplication;
public void add(NewsItem t)
try
myApplication.dbHelper.getNewsItemDao().create(t);
catch (SQLException e)
e.printStackTrace();
public void delete(NewsItem t)
try
myApplication.dbHelper.getNewsItemDao().delete(t);
catch (SQLException e)
e.printStackTrace();
public void update(NewsItem t)
try
myApplication.dbHelper.getNewsItemDao().update(t);
catch (SQLException e)
e.printStackTrace();
public List<NewsItem> all()
try
return myApplication.dbHelper.getNewsItemDao().queryForAll();
catch (SQLException e)
e.printStackTrace();
return null;
public List<NewsItem> queryByColumn(String columnName, Object columnValue)
try
QueryBuilder builder = myApplication.dbHelper.getNewsItemDao().queryBuilder();
builder.where().eq(columnName, columnValue);
return builder.query();
catch (SQLException e)
e.printStackTrace();
return null;
为了区分后面封装后的NewsItemDao.class
,我这里命名为NewsItemDaoOld .class
。
这里可以看到在构造方法中得到了通过Application的继承类MyApplication
。然后获得DatabaseHelper
的实例,也就能够通过DatabaseHelper
中的getNewsItemDao()
方法得到其中的NewsItemDaoOld()的实例。这里也可以发现ORMLite中各种方法的便利,增删改查都在NewsItemDaoOld .class
中完成。
5 利用DAO类完成增删改查
到这里就可以在任何类中使用NewsItemDaoOld .class
进行数据库的操作了。你同时也会发现操作数据库的代码变得异常简洁。导出数据库数据,通过SQLiteExpert查看数据,可以看到,新闻数据成功添加。
ORMLite还提供了一些基类ORMLiteBaseActivity,ORMLiteBaseService之类的,便于数据库操作的,这里不做考虑,毕竟项目中很大可能自己也需要继承自己的BaseActvity之类的。
ORMLite封装
封装之前我们先来总结需求和问题
上面的代码总的来说,封装到我的代码和业务逻辑中,有几个需要改进的地方。
1 对于NewsItemDao我们需要在DatabaseHelper中获取,在自己封装的NewsItemDaoOld 中进行数据操作。那么当一个app的表多了之后,我希望提供统一的增删改查操作,也就需要一个BaseDao完成一些基本的操作。以后的类统一命名为xxDao,并且继承自BaseDao。并且将DatabaseHelper中的getDAO获取各种数据的DAO的操作移到BaseDao中。
2 对表进行统一的管理。
3 将DataBaseHelper添加到DataManager中,按照以前的思路,将DataManager作为唯一的数据入口。
4 结合Dagger2进行全局的DataBaseHelper对象的管理
对于数据库操作需要有一个关注点,就是我们需要确保整个app中不同页面的数据库链接和操作应当都是一个 ,也就是说,不能让不同的线程同时操作数据库,这样肯定会存在数据库的紊乱和异常。对于官网提供的例子,建议通过继承OrmLiteBaseActivity
作为Activity的基类来使用OpenHelperManager
(将会在第一次链接数据库的时候创建,每次操作数据库的时候使用,在释放的时候进行关闭)。然后通过 getHelper()
类来获取OpenHelperManager
进行操作。
当然也可以在自己的BaseActivity中封装下面的操作。
private DatabaseHelper databaseHelper = null;
@Override
protected void onDestroy()
super.onDestroy();
if (databaseHelper != null)
OpenHelperManager.releaseHelper();
databaseHelper = null;
private DBHelper getHelper()
if (databaseHelper == null)
databaseHelper =
OpenHelperManager.getHelper(this, DatabaseHelper.class);
return databaseHelper;
然后这里由于我引入了Dagger2提供的全局单例对象我也就采取了自己的做法。我们全局使用的是同一个DatabaseHelper对象,也就避免了上方的操作。对Dagger2不理解的童鞋,请查看我之前的文章Google官方MVP+Dagger2架构详解。
解决这四个问题,那么一起来看看我的思路吧:
public class BaseDao<T>
protected Class<T> clazz;
protected Dao<T, Integer> daoOpe;
/**
* get dao class through @link com.anthony.app.common.data.database.DatabaseHelper
*
* @param mApplication using this to get instance of DatabaseHelper
*/
public BaseDao(MyApplication mApplication)
Class clazz = getClass();
while (clazz != Object.class)
Type t = clazz.getGenericSuperclass();
if (t instanceof ParameterizedType)
Type[] args = ((ParameterizedType) t).getActualTypeArguments();
if (args[0] instanceof Class)
this.clazz = (Class<T>) args[0];
break;
clazz = clazz.getSuperclass();
try
if (mApplication.dbHelper == null)
throw new RuntimeException("No DbHelper Found!");
daoOpe = mApplication.dbHelper.getDao(this.clazz);
catch (SQLException e)
e.printStackTrace();
public void add(T t)
try
daoOpe.create(t);
catch (SQLException e)
e.printStackTrace();
public void delete(T t)
try
daoOpe.delete(t);
catch (SQLException e)
e.printStackTrace();
public void update(T t)
try
daoOpe.update(t);
catch (SQLException e)
e.printStackTrace();
public List<T> all()
try
return daoOpe.queryForAll();
catch (SQLException e)
e.printStackTrace();
return null;
public List<T> queryByColumn(String columnName, Object columnValue)
try
QueryBuilder builder = daoOpe.queryBuilder();
builder.where().eq(columnName, columnValue);
return builder.query();
catch (SQLException e)
e.printStackTrace();
return null;
针对问题1
BaseDao 的构造方法中得到了MyApplication 实例,也就能够得到DatabaseHelper对象。我们也能通过反射获取DAO子类的泛型,从而能在当前的BaseDao中通过daoOpe = mApplication.dbHelper.getDao(this.clazz);
获取到ORMLite中的Dao对象。从而可以进行增删改查。
public class NewsItemDao extends BaseDao<NewsItem>
public NewsItemDao(MyApplication mApplication)
super(mApplication);
public List<NewsItem> queryLatest(int channelId, long limit)
try
QueryBuilder builder = daoOpe.queryBuilder();
builder.limit(limit)
.where()
.eq("channel_id", channelId)
.and()
.eq("isTopic", false);
return builder.query();
catch (SQLException e)
e.printStackTrace();
return null;
public List<NewsItem> queryTopic(int channelId)
try
QueryBuilder builder = daoOpe.queryBuilder();
builder.where()
.eq("channel_id", channelId)
.and()
.eq("isTopic", true);
return builder.query();
catch (SQLException e)
e.printStackTrace();
return null;
这里NewsItem的Dao类NewsItemDao继承了BaseDao,并且添加两个查询方法对数据库对象进行操作。
针对问题2
这里为了统一管理,我将所有的表的类名写到array.xml文件中,从而可以在DatabaseHelper中进行获取和统一创建。
针对上面的问题3 和4 结合在一起进行讲解
将DatabaseHelper封装到DataManager中,让DataManager作为数据的入口。
在ApplicationModule中提供几个DAO类的实例。也就是在全局中都是用的是这些Dao类的实例。也就解决了问题4中提及的数据库操作的问题。
同时在ApplicationComponent中进行实例的暴露。这样我们可以在任何进行了注入的类中使用这三个实例了 。这三个实例的初始化已经在上面这张图中进行了说明。
最后在Application的实现类中进行DatabaseHelper的实例对象的获取,大功告成。
项目效果和源码
项目代码:
CameloeAnthony/Ant
这里结合新闻列表,提供了一个导出数据库的操作,数据库可以在
Android - data - com.anthony.app - cache
中找到对应的数据库表。利用SQLiteExpert进行数据库的查看。
这里可以看到我这里有三个表,但是目前并没有对channel表和offline_res表进行添加操作。tb_news_item是我们之前在实体类中定义的表名。里面有对应的数据。
参考资料
以上是关于Android数据库进阶-从SQLite到ORMLite封装的主要内容,如果未能解决你的问题,请参考以下文章
Kotlin基础从入门到进阶系列讲解(入门篇)SQLite数据库存储
Kotlin基础从入门到进阶系列讲解(入门篇)SQLite数据库存储
Kotlin基础从入门到进阶系列讲解(入门篇)下载SQLite软件,如何创建/添加/更新/查询/删除数据