Android数据库-从SQLite到ORMLite封装
<h2><strong>前言</strong></h2> <p>几乎每一个android项目中,都必不可少的会使用数据库的操作。SqlBrite是对 Android 系统的 SQLiteOpenHelper 的封装,对SQL操作引入了响应式语义 (Rx)(用来在 RxJava 中使用)。在那之后确实也使用过一段时间的SqlBrite,不过可能是本人能力原因 ,在我的业务开发中,SqlBrite使用起来也并没有多么的方便 ,反而对整体的封装起到了一定的阻碍。所以后来也就继续回归使用 ORMLite 做数据库操作。下面文章还是从基础到封装再到实例讲讲我的项目中的 ORMLite 是怎么使用的吧。</p> <h2><strong>ORMLite的引入</strong></h2> <p><strong>1 从SQLite到ORM</strong></p> <p>SQLite是在世界上使用的最多的数据库引擎,并且还是开源的。它实现了无配置,无服务要求的事务数据库引擎。SQLite可以在Mac OS-X, iOS, Android, Linux, 和 Windows上使用。android中使用的正是SQLite。在Android开发中,使用SQLite作为基础部分,想必大家对继承 SQLiteOpenHelper 创建数据库,调用 SQLiteDatabase 的 execSQL() 方法执行 INSERT, UPDATE, DELETE 等语句来更新表的数据,不管你如何执行查询都会返回一个Android 的 SQLite 数据库游标......这一系列概念并不陌生啊 。想必很多人都和我一样,并不想写任何SQL语句,因为一不小心就写错了,而且各种重复的SQL语句写着真的心烦,大大的影响了开发的效率。</p> <p>我们当然希望不需要再去和复杂的SQL语句打交道,在面向对象的编程中只要像平时操作对象一样操作它就可以了。这就引入了ORM。</p> <p>ORM是对象关系映射(Object Relational Mapping)的缩写,对象和关系数据是业务实体的两种表现形式,业务实体在内存中表现为对象,在数据库中表现为关系数据,ORM实现了对象和关系数据库之间的 <strong>转换</strong> 。</p> <p><strong>2 java中ORM的原理</strong></p> <p>要实现JavaBean的属性到数据库表的字段的映射,任何ORM框架不外乎是读某个配置文件把JavaBean的属性和数据库表的字段自动关联起来,当从数据库Query时,自动把字段的值塞进JavaBean的对应属性里,当做INSERT或UPDATE时,自动把 JavaBean的属性值绑定到SQL语句中。</p> <p><strong>3 从ORM到ORMLite</strong></p> <p>ORM框架广泛引用于各种语言中,对于java开发者比较熟悉的有 Hibernate , Ormlite 等, Ormlite 作为一个Java ORM。支持JDBC连接,Spring以及Android平台。除此之外Android中使用的ORM框架还有 Greendao , ActiveAndroid , SugarORM , Realm 等。后续项目会考虑使用 Realm ,到时候再进行讲解。</p> <h2><strong>ORMLite基础</strong></h2> <p>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.</p> <p>ORMLite 提供了一个轻量级的java对象和数据库的对象关系操作,相比于Hibernate 和iBatis 等成熟的ORM框架的繁重,ORMLite旨在提供一个简单而有效的解决方案。</p> <p>当然和之前的所有文章一样, 基础部分都回归 官方文档 。这里会对android使用中的重点的基础部分进行提及并对官网的例子做出改动。</p> <h3><strong>首先看看封装之前的ORMLite在我项目中的使用步骤:</strong></h3> <p><strong>1 下载ORMLite的jar包</strong></p> <p>首先去 http://ormlite.com/releases/ 下载jar包,对于Android目前版本为:ormlite-android-5.0.jar 和 ormlite-core-5.0.jar ;在我项目中添加的是之前的4.49的jar包。</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/efaa0cc6c327600fb4c35cd1cb43be71.png"></p> <p><strong>2 创建实体类,这里利用新闻信息实体NewsItem类</strong></p> <pre> <code class="language-java">@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 方法 }</code></pre> <p>除了通过 @SerializedName(value = "xxx") 支持Gson序列化。</p> <p>这里有几个需要注意的地方:</p> <ul> <li>1 通过 @DatabaseTable(tableName = "tb_news_item") 指定了表名为 tb_news_item .</li> <li>2 通过 @DatabaseField(generatedId = true, columnName = "i_id") 指定id字段为自动生成,并且名为i_id。</li> <li>3 对于ArrayList<String> 序列化对象的的支持,需要使用 dataType = DataType.SERIALIZABLE .可以通过DataType控制数据库表中字段类型。</li> </ul> <p style="text-align:center"><img src="https://simg.open-open.com/show/4523120ad142e0ff89687829b69eccb4.png"></p> <p><strong>3 继承OrmLiteSqliteOpenHelper类</strong></p> <p>我们需要通过继承OrmLiteSqliteOpenHelper类来编写自己的数据库帮助类,</p> <p>需要实现的方法为 onCreate(SQLiteDatabase sqliteDatabase, ConnectionSource connectionSource) 以及 onUpgrade(SQLiteDatabase database, ConnectionSource connectionSource, int oldVersion, int newVersion) 。分别对应着数据库第一次创建 以及版本更新的时候调用的方法。</p> <p>比如针对上面的 NewsItem 类,</p> <pre> <code class="language-java">/** * 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; } }</code></pre> <p>可以看到的是 NewsItem 就是我们的实体类,通过 TableUtils.createTable(connectionSource, NewsItem.class); 在 onCreate 方法中完成了对象表的创建。</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/e8bfaa91bcbc3d7fa37e0c0003b45441.png"></p> <p>继承OrmLiteSqliteOpenHelper类,其实是间接继承了SQLiteOpenHelper</p> <p><strong>4 提取DAO类并封装其中的方法</strong></p> <p style="text-align:center"><img src="https://simg.open-open.com/show/581e7d586c0e232a0de39dc483a4cbee.png"></p> <p>可以通过上面的这张图看到的是 当前 DatabaseHelper 类除了完成 onCreate 中创建以及 onUpgrade 中更新以外,也提供了 DAO 类 。</p> <p>java的设计模式中这会经常遇到,我们需要将数据库的操作独立到数据库连接类(也就是Data Access Objects 简称DAO )。也就是说每个DAO类提供增删查改等操作。每个实体对象比如说上面的NewsItem.class 都对应着一个DAO类。OrmLiteSqliteOpenHelper 类正为我们提供了getDao方法,也就有了上面图片中的操作。</p> <pre> <code class="language-java">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; } } }</code></pre> <p>为了区分后面封装后的 NewsItemDao.class ,我这里命名为 NewsItemDaoOld .class 。</p> <p>这里可以看到在构造方法中得到了通过Application的继承类 MyApplication 。然后获得 DatabaseHelper 的实例,也就能够通过 DatabaseHelper 中的 getNewsItemDao() 方法得到其中的NewsItemDaoOld()的实例。这里也可以发现ORMLite中各种方法的便利,增删改查都在 NewsItemDaoOld .class 中完成。</p> <p><strong>5 利用DAO类完成增删改查</strong></p> <p style="text-align:center"><img src="https://simg.open-open.com/show/9ed1c042220df04d34ad4293a4b8bb13.png"></p> <p>到这里就可以在任何类中使用 NewsItemDaoOld .class 进行数据库的操作了。你同时也会发现操作数据库的代码变得异常简洁。导出数据库数据,通过SQLiteExpert查看数据,可以看到,新闻数据成功添加。</p> <p style="text-align: center;"><img src="https://simg.open-open.com/show/4d13ea4a05d06b3d1489d8ecb6689f07.png"></p> <p>ORMLite还提供了一些基类ORMLiteBaseActivity,ORMLiteBaseService之类的,便于数据库操作的,这里不做考虑,毕竟项目中很大可能自己也需要继承自己的BaseActvity之类的。</p> <h2><strong>ORMLite封装</strong></h2> <p>封装之前我们先来总结需求和问题</p> <p>上面的代码总的来说,封装到我的代码和业务逻辑中,有几个需要改进的地方。</p> <p>1 对于NewsItemDao我们需要在DatabaseHelper中获取,在自己封装的NewsItemDaoOld 中进行数据操作。那么当一个app的表多了之后,我希望提供统一的增删改查操作,也就需要一个BaseDao完成一些基本的操作。以后的类统一命名为xxDao,并且继承自BaseDao。并且将DatabaseHelper中的getDAO获取各种数据的DAO的操作移到BaseDao中。</p> <p>2 对表进行统一的管理。</p> <p>3 将DataBaseHelper添加到DataManager中,按照以前的思路,将DataManager作为唯一的数据入口。</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/1942541dd6ffbf5c5fc4b1398fed190f.png"></p> <p>4 <strong>结合Dagger2进行全局的DataBaseHelper对象的管理</strong></p> <p>对于数据库操作需要有一个关注点,就是我们需要确保整个app中不同页面的数据库链接和操作应当都是一个 ,也就是说,不能让不同的线程同时操作数据库,这样肯定会存在数据库的紊乱和异常。对于官网提供的例子,建议通过继承 OrmLiteBaseActivity 作为Activity的基类来使用 OpenHelperManager (将会在第一次链接数据库的时候创建,每次操作数据库的时候使用,在释放的时候进行关闭)。然后通过 getHelper() 类来获取 OpenHelperManager 进行操作。</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/145f966b3ae03d4729861239e4a926f1.png"></p> <p style="text-align:center">ORMLite提供的基类</p> <p>当然也可以在自己的BaseActivity中封装下面的操作。</p> <pre> <code class="language-java">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; }</code></pre> <p>然后这里由于我引入了Dagger2提供的全局单例对象我也就采取了自己的做法。我们全局使用的是同一个DatabaseHelper对象,也就避免了上方的操作。</p> <p><strong>解决这四个问题,那么一起来看看我的思路吧:</strong></p> <pre> <code class="language-java">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; } } }</code></pre> <p><strong>针对问题1</strong></p> <p>BaseDao 的构造方法中得到了MyApplication 实例,也就能够得到DatabaseHelper对象。我们也能通过反射获取DAO子类的泛型,从而能在当前的BaseDao中通过 daoOpe = mApplication.dbHelper.getDao(this.clazz); 获取到ORMLite中的Dao对象。从而可以进行增删改查。</p> <pre> <code class="language-java">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; } } }</code></pre> <p>这里NewsItem的Dao类NewsItemDao继承了BaseDao,并且添加两个查询方法对数据库对象进行操作。</p> <p><strong>针对问题2</strong></p> <p>这里为了统一管理,我将所有的表的类名写到array.xml文件中,从而可以在DatabaseHelper中进行获取和统一创建。</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/15e5ffd93d3fc0115cbaa3766efe3126.png"></p> <p style="text-align:center"><img src="https://simg.open-open.com/show/99cbb5eec89440457a73ebf3b872a77f.png"></p> <p><strong>针对上面的问题3 和4 结合在一起进行讲解</strong></p> <p>将DatabaseHelper封装到DataManager中,让DataManager作为数据的入口。</p> <p style="text-align: center;"><img src="https://simg.open-open.com/show/74657eba40166363355bd7413ff52a57.png"></p> <p>在ApplicationModule中提供几个DAO类的实例。也就是在全局中都是用的是这些Dao类的实例。也就解决了问题4中提及的数据库操作的问题。</p> <p style="text-align: center;"><img src="https://simg.open-open.com/show/a37cd35a0881f2ac93ad9aca521a35a1.png"></p> <p>同时在ApplicationComponent中进行实例的暴露。这样我们可以在任何进行了注入的类中使用这三个实例了 。这三个实例的初始化已经在上面这张图中进行了说明。</p> <p style="text-align: center;"><img src="https://simg.open-open.com/show/ce96c69564064ab9999c269664c7818c.png"></p> <p>最后在Application的实现类中进行DatabaseHelper的实例对象的获取,大功告成。</p> <p style="text-align: center;"><img src="https://simg.open-open.com/show/46a45281c7defa8777fed4c22c3be13b.png"></p> <h2><strong>项目效果和源码</strong></h2> <p>这里结合新闻列表,提供了一个导出数据库的操作,数据库可以在</p> <p>Android - data - com.anthony.app - cache 中找到对应的数据库表。利用SQLiteExpert进行数据库的查看。</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/0b9043c3db128d266a35a7c60c161b4c.png"></p> <p>这里可以看到我这里有三个表,但是目前并没有对channel表和offline_res表进行添加操作。tb_news_item是我们之前在实体类中定义的表名。里面有对应的数据。</p> <p style="text-align: center;"><img src="https://simg.open-open.com/show/789ccbb4772ef3757da0948ad55b7e68.png"></p> <h2><strong>参考资料</strong></h2> <ul> <li>1 <a href="/misc/goto?guid=4959656096438177758" rel="nofollow,noindex">Android ORMLite 框架的入门用法</a></li> <li>2 <a href="/misc/goto?guid=4959643713540909304" rel="nofollow,noindex">Android 快速开发系列 ORMLite 框架最佳实践</a></li> </ul> <p> </p> <p>来自:http://www.jianshu.com/p/776a01485d91</p> <p> </p>
本文由用户 JoManess 自行上传分享,仅供网友学习交流。所有权归原作者,若您的权利被侵害,请联系管理员。
转载本站原创文章,请注明出处,并保留原始链接、图片水印。
本站是一个以用户分享为主的开源技术平台,欢迎各类分享!