Android数据库-从SQLite到ORMLite封装

JoManess 7年前
   <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>