1. 程式人生 > >Android ORM 框架之 Android中ORMLite應用基礎

Android ORM 框架之 Android中ORMLite應用基礎

ORMLite是常用的一個ORM框架,她不止可以用在Android的sqlite資料庫,也可以使用她操作其他常見的資料庫。這兒是根據官方文件抽取來的android用法。

一,新增依賴

匯入ormlite-core.jar和ormlite-android.jar:下載jar
或者build.gradle中新增:

    compile 'com.j256.ormlite:ormlite-android:5.0'
    compile 'com.j256.ormlite:ormlite-core:5.0'

二,建立Bean類

1>類使用@DatabaseTable註解,標識一個表。
2>成員變數使用 @DatabaseField 註解,表示表中的列。
3>相關屬性設定可以查詢其依賴包中的DatabaseTable和Column註解類。持久化的資料型別可以查閱ORMLite官方文件或庫原始碼。
4>新增一個無參的建構函式。當執行查詢返回結果時,ORMLite會使用Java反射建構函式建立物件。

@DatabaseTable(tableName = "accounts")
public class Account {
    //generatedId = true表示id主鍵自動生成
    @DatabaseField(generatedId = true)  
    private int id;
    //id = true表示這個列可以通過ID方法執行查詢,更新,刪除
    @DatabaseField(id = true)
    private String name;
    //canBeNull = false表示不可以為空
    @DatabaseField(canBeNull = false
) private String password; public Account() { // ORMLite 需要一個無參構造器 } public Account(String name, String password) { this.name = name; this.password = password; } public int getId() { return id; } public void setId(int id) { this
.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } }

三,建立資料庫

1>繼承OrmLiteSqliteOpenHelper 建立database helper;
2>在onCreate方法中呼叫TableUtils.createTable建立表,onUpgrade方法執行資料庫更新。TableUtils類提供了一些建立和刪除表的靜態方法,參考原始碼。

public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
    private static final String TABLE_NAME = "sqlite.db";

    public DatabaseHelper(Context context) {
        super(context, TABLE_NAME, null, 2);
    }

    @Override
    public void onCreate(SQLiteDatabase database, ConnectionSource connectionSource) {

        try {
            TableUtils.createTable(connectionSource, Bean.class);
        } catch (java.sql.SQLException e) {
            e.printStackTrace();
        }

    }
    @Override
    public void onUpgrade(SQLiteDatabase database, ConnectionSource connectionSource, int oldVersion, int newVersion) {

    }
}

四,建立DAO

在資料訪問物件(Data Access Objects (DAO))類中隔離資料庫操作,提供建立,刪除,更新,等等操作。每個表都對應一個DAO,每個DAO有兩個引數:使用DAO持久化資料的實體類和用來標示指定表中行的ID列,如果類中沒設定ID列,可以把物件或null作為第二個引數。例如,在上面Account類中,“name”成員是ID列(id = true),所以ID列是字串。
建立DAO的最簡單的方法是呼叫DaoManager.createDao(),這樣如果它們被內部ORMLite功能需要,可以重複使用,而不是重新建立(建立DAO很消耗資源,應該儘可能重複利用):


Dao<Account, String> accountDao =DaoManager.createDao(connectionSource, Account.class);
Dao<Order, Integer> orderDao =DaoManager.createDao(connectionSource, Order.class);

使用這種方式需要ConnectionSource引數,和實體類的Class物件,ConnectionSource引數可以通過OrmLiteSqliteOpenHelper的getConnectionSource()方法獲得:

ConnectionSource connectionSource = OrmLiteSqliteOpenHelper.getConnectionSource();

如果想要一個更好的類層次結構,或者在DAO中需要新增較多的方法,可以定義一個介面,繼承Dao介面,介面不是必須的,但這樣做可以減少耦合:


/** Account DAO ,具有字串id (Account.name) */
public interface AccountDao extends Dao<Account, String> {
    // 新增 DAO的方法
}

然後實現類繼承BaseDaoImpl並實現上面定義的介面:


/** JDBC implementation of the AccountDao interface. */
public class AccountDaoImpl extends BaseDaoImpl<Account, String>implements AccountDao {
    // 這個構造器必須有
    public AccountDaoImpl(ConnectionSource connectionSource)
      throws SQLException {
        super(connectionSource, Account.class);
    }
}

要使用自定義DAO類的,需要到相應的實體類@DatabaseTable註解中新增daoClass引數:


@DatabaseTable(daoClass = AccountDaoImpl.class)
public class Account {
   …
}

也可以在DatabaseHelper中呼叫OrmLiteSqliteOpenHelper.getDao建立:

    private Dao<Account , Integer> accountDao;  
    public Dao<Account , Integer> getAccountDao() throws SQLException  
    {  
        if (AccountDao == null)  
        {  
            AccountDao = getDao(Account.class);  
        }  
        return accountDao;  
    }  

五,執行時與SQL異常

預設情況下,大多數的DAO方法都會丟擲SQLException,大部分的異常繼承RuntimeException,所以會需要大量的try … catch語句,出於這個原因,RuntimeExceptionDao包裝了呼叫DAO方法會出現的執行時異常SQL exceptions:

Dao<Account, String> dao = DaoManager.createDao(connectionSource, Account.class);
RuntimeExceptionDao<Account, String> accountDao = new RuntimeExceptionDao<Account, String>(dao);

或者直接使用其createDao方法:


RuntimeExceptionDao<Account, String> accountDao =RuntimeExceptionDao.createDao(connectionSource, Account.class);

在DatabaseHelper中可以呼叫OrmLiteSqliteOpenHelper.getRuntimeExceptionDao。

六,使用DAO

插入新的行到資料庫:

Account account = new Account();
account.name = "Jim Coakley";
accountDao.create(account);

如果物件具有註解宣告的id引數,那麼我們就可以使用其ID在資料庫中查詢此物件:


Account account = accountDao.queryForId(name);
if (account == null) {
  account not found handling … 
}

如果改變物件的成員屬性,必須呼叫update()將改變更新到資料庫中:

account.password = "_secret";
accountDao.update(account);

重新整理物件

如果資料空正使用的某個物件被其他實體改變,需要重新整理這個物件,保證這個物件是實時的:

accountDao.refresh(account);

刪除物件對應的資料庫中的行,一旦物件已經從資料庫中刪除,您可以繼續使用在記憶體中的該物件,但任何更新或重新整理會失效:

accountDao.delete(account);

迭代

該DAO本身也是一個迭代器,因此可以直接遍歷資料庫資料中表的行:

// page through all of the accounts in the database
for (Account account : accountDao) {
    System.out.println(account.getName());
}

這種方式必須遍歷資料庫中表的所有行,然後才會關閉潛在的sql物件。 如果沒有通過迴圈遍歷所有的行,
ORMLite並不會關閉sql物件,資料庫的連線會被弱化並且只有GC回收才會被關閉,這樣會導致程式Bug,所以最好使用
try…finally迭代器。以下for迴圈,是一個非常糟糕的程式碼:

for (Account account : accountDao) {
    if (account.getName().equals("Bob Smith")) {
        // you can't return, break, or throw from here
        return account;
    }
}

也可以選擇直接使用迭代器,因為for迴圈式並不總是最佳的。這樣允許使用try…finally,達到一種更優化的程式碼:

 CloseableIterator<Account> iterator = accountDao.closeableIterator();
      try {
         while (iterator.hasNext()) {
            Account account = iterator.next();
            System.out.println(account.getName());
         }
      } finally {
         // close it at the end to close underlying SQL statement
         iterator.close();    
      }

或者使用迭代包裝器,for迴圈之後仍然可以關閉:

 CloseableWrappedIterable<Account> wrappedIterable =
       accountDao.getWrappedIterable();
       try {
          for (Account account : wrappedIterable) {
              …
          }
       } finally {
          wrappedIterable.close();
   }

七,使用OpenHelperManager

DatabaseHelper可能在App或多個Activity的執行緒中被多次例項化使用,如果同時有多個物件,可能會出現過時的資料和意外的結果。所以建議使用OpenHelperManager管理DatabaseHelper的使用:

private DatabaseHelper databaseHelper = null;

private void releaseHelper() {
    if (databaseHelper != null) {
        OpenHelperManager.releaseHelper();
        databaseHelper = null;
    }
}
private DatabaseHelper getHelper() {
    if (databaseHelper == null) {
        databaseHelper =
            OpenHelperManager.getHelper(this, DatabaseHelper.class);
    }
    return databaseHelper;
}

還可以讓Activity繼承OrmLiteBaseActivity,OrmLiteBaseActivity對OpenHelperManager的使用做了進一步封裝,還有OrmLiteBaseListActivity, OrmLiteBaseService, OrmLiteBaseTabActivity等,不過一般不這麼做。但是可以在使用OpenHelperManager的時候拿來參考。
然後通過databaseHelper操作資料庫:

    Dao<Account, String> accountDao = getHelper().getAccountDao();
    Account account = new Account ();
    account.setName("one");
    account.setPassword("123");
    accountDao.create(account);

八,ORMLite外來鍵

ORMLite支援外來鍵的概念,既一個物件的一個或者多個屬性儲存在相同資料庫中其他表中。比如資料庫中有一個Order物件,每個Order物件都對應一個Account物件,然後就說Order(是一個表)物件的Account(也是一個表)成員有外來鍵屬性。id成員在表建立的時候會生成”account_id”列:

@DatabaseTable(tableName = "orders")
public class Order {

    @DatabaseField(generatedId = true)
    private int id;

    @DatabaseField(canBeNull = false, foreign = true)
    private Account account;
    …
}

當建立有外來鍵屬性成員的物件(Order)時,外來鍵物件(Account)不會被自動建立(Account表不會自動建立)。如果外來鍵物件(Account物件)在資料庫中有generated id(Order物件有 id成員),應該在建立引用這個物件(Order物件有Account成員)的物件(Order物件)之前建立外來鍵物件(Account表):

Account account = new Account("Jim Coakley");
accountDao.create(account);
// 這時會建立一個account物件,並且設定generated ids

// 現在用order設定account,建立並插入account表
Order order = new Order("Jim Sanders", 12.34);
order.setAccount(account);
…
orderDao.create(order);

某些情況下如果希望外來鍵自建,可以在外來鍵(account)的註解中設定foreignAutoCreate引數。
當你查詢一個Order時,會獲得一個account物件,這個物件只有id屬性被設定了值,其他屬性都是預設值(null, 0, false, etc.)。如果需要使用這個account物件的其他值,則必須呼叫accountDao類的refresh方法獲得這個Account物件的屬性:

Order order = orderDao.queryForId(orderId);
System.out.println("Account-id on the order should be set: " +
   order.account.id);
// 此時 order.account.name為null
System.out.println("But other fields on the account should not be set: "
   + order.account.name);

// 用AccountDao重新整理一下account
accountDao.refresh(order.getAccount());
System.out.println("Now the account fields will be set: " +
   order.account.name);

也可以在註解中新增foreignAutoRefresh引數值設定自動重新整理外來鍵物件的資料。
有不同的方式查詢外來鍵的引數值,比如查詢匹配某個account的所有Order,通過ID引數的成員和account的name查詢:

// query for all orders that match a certain account
List<Order> results =
  orderDao.queryBuilder().where().
    eq("account_id", account.getName()).query();

也可以讓ORMLite從該account中自動提取id獲得:

// ORMLite will extract and use the id field internally
List<Order> results =
  orderDao.queryBuilder().where().
    eq("account_id", account).query();

九,外來鍵集合

上面說了Account是Order的外來鍵,如果一個Account物件對應多個Order物件,則可以將其以成員的形式通過 @ForeignCollectionField註解以以下形式新增到Account中去:

public class Account {
    …
    @ForeignCollectionField(eager = false)
    ForeignCollection<Order> orders;
    …
}

成員型別只可以使用ForeignCollection< T> 或 Collection< T>。註解引數可以查閱庫中的ForeignCollectionField類。
當改變了Collection中的order時,需要呼叫update在資料庫中更新它:

for (Order order : account.orders()) {
   // 如果改變了order的某些成員
   order.setAmount(123);
   // 必須在資料庫中更新account
   account.orders.update(order);
}

十,事務

當對資料庫有較多的執行動作時,用事務處理比較好,圖個方便,下段程式碼摘自ormlite的事務使用

private boolean doBatchInTransaction(final List<T> list, final int batchType) {
        boolean doBatch = false;
        ConnectionSource connectionSource = ormLiteDao.getConnectionSource();
        TransactionManager transactionManager = new TransactionManager(connectionSource);
        Callable<Boolean> callable = new Callable<Boolean>() {
            @Override
            public Boolean call() throws Exception {
                return doBatch(list, batchType);
            }
        };
        try {
            doBatch = transactionManager.callInTransaction(callable);
        } catch (SQLException e) {
            LogUtil.e(e);
        }
        return doBatch;
    }

    private boolean doBatch(List<T> list, int batchType) {
        int result = 0;
        try {
            for (T t : list) {
                switch (batchType) {
                    case DaoOperation.INSERT:
                        result += ormLiteDao.create(t);
                        break;
                    case DaoOperation.DELETE:
                        result += ormLiteDao.delete(t);
                        break;
                    case DaoOperation.UPDATE:
                        result += updateIfValueNotNull(t);
                        break;
                    default:
                        LogUtil.w("no this type.");
                        break;
                }
            }
        } catch (SQLException e) {
            LogUtil.e(e);
        }
        return result == list.size();
    }

十一,更新資料庫

更新app時,可能會修改現有的資料庫,這時就需要在 建立的DatabaseHelper的onUpgrade方法中進行一些工作了。比如在表中增加一列,可以在onUpgrade方法中這麼做:

Dao<Account, Integer> dao = getHelper().getAccountDao();
// 在表account中增加 "age" 列
dao.executeRaw("ALTER TABLE `account` ADD COLUMN age INTEGER;");

所有能做的就是重新命名錶名和新增新列,不要重新命名或刪除列或更改列的屬性。
最常用的,是根據版本條件更改資料庫:

if (oldVersion < 2) {
  // we added the age column in version 2
  dao.executeRaw("ALTER TABLE `account` ADD COLUMN age INTEGER;");
}
if (oldVersion < 3) {
  // we added the weight column in version 3
  dao.executeRaw("ALTER TABLE `account` ADD COLUMN weight INTEGER;");
}

或者:

dao.executeRaw(
    "ALTER TABLE `account` ADD COLUMN hasDog BOOLEAN DEFAULT 0;");
dao.updateRaw("UPDATE `account` SET hasDog = 1 WHERE dogCount > 0;");

這兒用到了資料庫語句,可以查閱sqlite官方文件。