Android數據庫-從SQLite到ORMLite封裝

JoManess 8年前發布 | 14K 次閱讀 SQLite Android開發 移動開發

前言

幾乎每一個android項目中,都必不可少的會使用數據庫的操作。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使用中的重點的基礎部分進行提及并對官網的例子做出改動。

首先看看封裝之前的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 方法 }</code></pre>

除了通過 @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<String> 序列化對象的的支持,需要使用 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; }

}</code></pre>

可以看到的是 NewsItem 就是我們的實體類,通過 TableUtils.createTable(connectionSource, NewsItem.class); 在 onCreate 方法中完成了對象表的創建。

繼承OrmLiteSqliteOpenHelper類,其實是間接繼承了SQLiteOpenHelper

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;
    }
}

}</code></pre>

為了區分后面封裝后的 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 進行操作。

ORMLite提供的基類

當然也可以在自己的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; }</code></pre>

然后這里由于我引入了Dagger2提供的全局單例對象我也就采取了自己的做法。我們全局使用的是同一個DatabaseHelper對象,也就避免了上方的操作。

解決這四個問題,那么一起來看看我的思路吧:

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>

針對問題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;
    }
}

}</code></pre>

這里NewsItem的Dao類NewsItemDao繼承了BaseDao,并且添加兩個查詢方法對數據庫對象進行操作。

針對問題2

這里為了統一管理,我將所有的表的類名寫到array.xml文件中,從而可以在DatabaseHelper中進行獲取和統一創建。

針對上面的問題3 和4 結合在一起進行講解

將DatabaseHelper封裝到DataManager中,讓DataManager作為數據的入口。

在ApplicationModule中提供幾個DAO類的實例。也就是在全局中都是用的是這些Dao類的實例。也就解決了問題4中提及的數據庫操作的問題。

同時在ApplicationComponent中進行實例的暴露。這樣我們可以在任何進行了注入的類中使用這三個實例了 。這三個實例的初始化已經在上面這張圖中進行了說明。

最后在Application的實現類中進行DatabaseHelper的實例對象的獲取,大功告成。

項目效果和源碼

這里結合新聞列表,提供了一個導出數據庫的操作,數據庫可以在

Android - data - com.anthony.app - cache 中找到對應的數據庫表。利用SQLiteExpert進行數據庫的查看。

這里可以看到我這里有三個表,但是目前并沒有對channel表和offline_res表進行添加操作。tb_news_item是我們之前在實體類中定義的表名。里面有對應的數據。

參考資料

 

來自:http://www.jianshu.com/p/776a01485d91

 

 本文由用戶 JoManess 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!