概述
SQLite
在介紹LitePal之前還是要先介紹一下SQLite,也就是我們通常所說的資料庫,開發中多多少少會用到,不過原生的SQLiteDatabase,只要寫過你就知道,寫Demo還是可以的,但是在實際專案中就不夠靈活了,因為Java作為物件導向的語言,我們在實際開發的過程中操作的大部分都是物件,如果使用SQLiteDatabase,我們進行CRUD操作的時候需要寫SQL語句,查詢的也是一個Cursor。所以需要根據就跟網路請求一樣,如果是簡單的網路請求,你可以用HttpURLConnection或者OKHttpURLConnection,但是真正的專案開發的時候,也是各種框架用地飛起,所以就有人對DB的CRUD操作進行了封裝,於是就產生了很多的ORM框架,LitePal便是其中的一種。
LitePal
GitHub上面有很多的ORM(Object Relational Mapping),也就是通常所說的關係型資料庫框架。常見有greenDAO,LitePal,OrmLite-Android,等等,下面看一下這三個框架的GitHub的使用情況
ORM | Initial Commit | Star | Fork | Contributors |
---|---|---|---|---|
greenDAO | 2011-07 | 9366 | 2537 | 6 |
Realm | 2012-04 | 9018 | 1427 | 76 |
LitePal | 2013-03 | 4361 | 1088 | 1 |
Sugar | 2011-08 | 2484 | 596 | 57 |
Ormlite | 2010-09 | 1296 | 343 | 6 |
上面的表格是按照GitHub上的Star數量來排序的,可以看到LIitePal是排第三,Fork數跟Star數量基本上是正相關的,但是由於LitePal推出的比較晚,所以感覺還是挺優秀的,也是我第一個使用的ORM資料庫框架。
LitePal的作者是郭霖,不過通過看原始碼,使用方式比較簡潔,從名字可以看出來LitePal比較輕,翻譯過來是,'輕的朋友',可以在asset下的literpal.xml中配置DB中DB的版本以及儲存的資料物件,儲存路徑,拿官方的Sample舉例:
<?xml version="1.0" encoding="utf-8"?>
<litepal>
<dbname value="sample" />
<version value="1" />
<list>
<mapping class="org.litepal.litepalsample.model.Album" />
<mapping class="org.litepal.litepalsample.model.Song" />
<mapping class="org.litepal.litepalsample.model.Singer" />
</list>
<storage value="external" />
</litepal>
複製程式碼
本文分析的是GitHub上最新的版本是1.6.1,使用方式比較簡單,並且作者專門開了一個專欄:Android資料庫高手祕籍,關於LItePal的使用可以看一下作者的專欄,下面主要從原始碼的角度來解讀一下Litepal的實現原理,其實分析起來還是很簡單的,就是註釋有些翻譯起來很痛苦,強烈建議來一箇中文版的註釋。
正文
工作流程
Litepal是個資料庫框架,所以不像AsncTask,Volley,Picasso那樣關心多執行緒併發,以及執行緒切換等,它的中心在於讓DB操作更加簡單跟高效,基本上跟資料庫打過交道都知道,資料庫的主要操作就是CRUD,然後稍微麻煩點的就是DB的建立,升級等,說白了就是編寫SQL語句比較麻煩,畢竟做Android客戶端開發不像後臺天天跟資料庫打交道,隨手一個SQL語句信手拈來,不過某些特殊的應用除外,Litepal將DB的操作封裝了物件的操作,也就是我們通常所說的ORM操作,這樣操作起來就會比較方便了,我們不需要擔心SQL語句編寫錯誤,平時怎麼操作物件,現在早怎麼運算元據庫,同時Litepal也保留了原始的SQLite語句查詢方式。
下面從跟隨Sample中的示例程式碼,LitePal的Save操作,跟著原始碼來追一下Litepal的工作流程
Singer是一個繼承自DataSupport的類,呼叫一下save方法,即可觸發DB的儲存造作,跟一下原始碼
Singer singer = new Singer();
singer.setName(mSingerNameEdit.getText().toString());
singer.setAge(Integer.parseInt(mSingerAgeEdit.getText().toString()));
singer.setMale(Boolean.parseBoolean(mSingerGenderEdit.getText().toString()));
singer.save();
複製程式碼
依然是方法呼叫,呼叫了Singer的saveThrows方法,繼續跟
public synchronized boolean save() {
try {
saveThrows();
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
複製程式碼
saveThrows是一個同步方法,在這裡通過Connector獲取到SQLiteDatabase的例項db,然後開啟事務,建立了一個SaveHandler的例項,並且在構造方法中傳入了SQLiteDatabase,呼叫了Singer的onSave方法,傳入了當前物件的例項,如果執行此方法沒有異常,那麼就說明儲存成功,關閉事務,否則會丟擲異常,事務失敗。那麼繼續看SaveHandler中的onSave方法,並且傳入了自身物件,所以繼續跟onSave方法
public synchronized void saveThrows() {
SQLiteDatabase db = Connector.getDatabase();
db.beginTransaction();
try {
SaveHandler saveHandler = new SaveHandler(db);
saveHandler.onSave(this);
clearAssociatedData();
db.setTransactionSuccessful();
} catch (Exception e) {
throw new DataSupportException(e.getMessage(), e);
} finally {
db.endTransaction();
}
}
複製程式碼
onSave方法,邏輯稍微複雜一下,下面通過一行一行的程式碼來分析一下
void onSave(DataSupport baseObj) throws SecurityException, IllegalArgumentException,
NoSuchMethodException, IllegalAccessException, InvocationTargetException {
//拿到物件的類名
String className = baseObj.getClassName();
//根據className,通過反射獲取到LitePal支援的普通成員變數
List<Field> supportedFields = getSupportedFields(className);
//根據className,通過反射獲取到LitePal支援的泛型變數
List<Field> supportedGenericFields = getSupportedGenericFields(className);
//根據className,通過反射獲得到資料庫的對映關係
Collection<AssociationsInfo> associationInfos = getAssociationInfo(className);
if (!baseObj.isSaved()) {
//通過Id判斷是否是首次儲存
if (!ignoreAssociations) {
//看錶的對映關係是否需要處理
analyzeAssociatedModels(baseObj, associationInfos);
}
//儲存該列資料
doSaveAction(baseObj, supportedFields, supportedGenericFields);
if (!ignoreAssociations) {
analyzeAssociatedModels(baseObj, associationInfos);
}
} else {
//更新操作
if (!ignoreAssociations) {
analyzeAssociatedModels(baseObj, associationInfos);
}
//更新表中的欄位
doUpdateAction(baseObj, supportedFields, supportedGenericFields);
}
}
複製程式碼
跟一下doSaveAction
private void doSaveAction(DataSupport baseObj, List<Field> supportedFields, List<Field> supportedGenericFields)
throws SecurityException, IllegalArgumentException, NoSuchMethodException,
IllegalAccessException, InvocationTargetException {
values.clear();//清空上一次CRUD操作的ContentValue
//將Singer中的資料轉換成ContentValues
beforeSave(baseObj, supportedFields, values);
//儲存資料,並且獲取Id
long id = saving(baseObj, values);
//對儲存的module賦予Id,進行加密等操作
afterSave(baseObj, supportedFields, supportedGenericFields, id);
}
複製程式碼
跟一下saving這個方法,發現已經到頭了,直接呼叫了SQLiteDataBase的insert方法
private long saving(DataSupport baseObj, ContentValues values) {
if (values.size() == 0) {
values.putNull("id");
}
return mDatabase.insert(baseObj.getTableName(), null, values);
}
複製程式碼
好像整個流程到這裡基本上完成了一次儲存或者更新的操作,還是比較簡單的。不過上面主要是在分析流程,有很多細節沒有深入,涉及到的幾個類有DataSupport,SaveHandler,Connector,由於只是進行了儲存操作,所以還有很多類沒有涉及到,類似typechange,LitePalBase,AsyncExecutor,DataHandler等,這些會接下來的LitePal架構中進行具體的分析。
LitePal架構
由於LitePal分了很多包,而且是通過功能進行劃分的,為了便於直觀的展示,我將包轉化成了思維導圖,這樣可以更加直觀地瞭解整個LitePal的架構。
其實觀察一下可以發現,crud包跟tablemanager包是整個框架的核心,因為其實這兩個包有些東西是有關聯的,所以沒法具體的進行劃分,所以現在選取了三個抽象類LitePalBase,AsyncExecutor,OrmChange因為大部分核心類都是繼承自這三個抽象類的。
LitePal
註釋
LitePal is an Android library that allows developers to use SQLite database extremely easy.You can initialized it by calling {@link #initialize(Context)} method to make LitePal ready to work. Also you can switch the using database by calling {@link #use(LitePalDB)} and {@link #useDefault()} methods.
複製程式碼
LitePal是一個Android庫,開發者可以用這個庫很容易地運算元據庫。你可以通過呼叫initialize(Contetext)來初始化LitePal,當然你也可以通過呼叫use(LitePalDB)來使用指定的資料庫或者呼叫useDefault來使用litepal.xml中預設的資料庫。
aesKey
設定AES加密的key
public static void aesKey(String key) {
CipherUtil.aesKey = key;
}
複製程式碼
isDefaultDatabase
//判斷資料庫是否是預設的資料庫
private static boolean isDefaultDatabase(String dbName) {
if (BaseUtility.isLitePalXMLExists()) {
if (!dbName.endsWith(Const.Config.DB_NAME_SUFFIX)) {
dbName = dbName + Const.Config.DB_NAME_SUFFIX;
}
LitePalConfig config = LitePalParser.parseLitePalConfiguration();
String defaultDbName = config.getDbName();
if (!defaultDbName.endsWith(Const.Config.DB_NAME_SUFFIX)) {
defaultDbName = defaultDbName + Const.Config.DB_NAME_SUFFIX;
}
return dbName.equalsIgnoreCase(defaultDbName);
}
return false;
}
複製程式碼
removeVersionInSharedPreferences
//移除SP中指定的資料庫版本
private static void removeVersionInSharedPreferences(String dbName) {
if (isDefaultDatabase(dbName)) {
SharedUtil.removeVersion(null);
} else {
SharedUtil.removeVersion(dbName);
}
}
複製程式碼
LitePalApplication
主要用來給LitePal運算元據庫新增Context
public LitePalApplication() {
sContext = this;
}
//已經被遺棄
@Deprecated
public static void initialize(Context context) {
sContext = context;
}
//獲取Context
public static Context getContext() {
if (sContext == null) {
throw new GlobalException(GlobalException.APPLICATION_CONTEXT_IS_NULL);
}
return sContext;
}
複製程式碼
LitePalDB
註釋
Configuration of LitePal database. It's similar to litepal.xml configuration, but allows to configure database details at runtime. This is very important when comes to support multiple databases functionality.
複製程式碼
LitePal DB的配置。類似於litepal.xml,不過可以動態地配置DB,在需要新增多個資料庫的時候這個功能非常重要
通過litepal.xml配置的資料庫的時候只能新增一個,通過LitePalDB可以支援多個資料庫。
成員變數
private int version;//版本號
private String dbName;//DB名稱
private String storage;//儲存路徑
private boolean isExternalStorage = false;//外部儲存是否又讀取許可權
private List<String> classNames;//對映的類名集合
複製程式碼
fromDefault
//獲取預設的DB,也就是litepal.xml中定義的DB
public static LitePalDB fromDefault(String dbName) {
LitePalConfig config = LitePalParser.parseLitePalConfiguration();
LitePalDB litePalDB = new LitePalDB(dbName, config.getVersion());
litePalDB.setStorage(config.getStorage());
litePalDB.setClassNames(config.getClassNames());
return litePalDB;
}
複製程式碼
getClassNames
//獲取對映集合
public List<String> getClassNames() {
if (classNames == null) {
classNames = new ArrayList<String>();
//新增系統自動生成的資料庫對映類
classNames.add("org.litepal.model.Table_Schema");
} else if (classNames.isEmpty()) {
classNames.add("org.litepal.model.Table_Schema");
}
return classNames;
}
複製程式碼
OrmChange
註釋
This is abstract super class to map the object field types to database column
types. The purpose of this class is to define a abstract method, and let all
subclasses implement it. Each subclass deals with a kind of changing and each
subclass will do their own logic to finish the changing job.
複製程式碼
一個用於將物件的成員變數屬性對映成資料庫中表的列的屬性的抽象父類。這個類的目的在於定義一個抽象方法,並且讓子類去實現他。每一個子類處理一種變換並且每個子類各司其職。
繼承關係
通過註釋,我們知道OrmChange是型別轉換的父類,然後有六個子類,也就是資料庫支援的儲存型別,BlobOrm是二進位制,DecimalOrm是浮點數型別,BooleanOrm是布林型別,TextOrm是文字型別,DateOrm是日期型別。
成員變數
由於OrmChange的只有一個抽象方法,所以我選擇了其中的一個子類DecimalOrm來分析,比較簡單。
public class DecimalOrm extends OrmChange {
/**
* If the field type passed in is float or double, it will change it into
* real as column type.
*/
@Override
public String object2Relation(String fieldType) {
if (fieldType != null) {
if (fieldType.equals("float") || fieldType.equals("java.lang.Float")) {
return "real";
}
if (fieldType.equals("double") || fieldType.equals("java.lang.Double")) {
return "real";
}
}
return null;
}
}
複製程式碼
Annotion
LitePal在annotion中有兩個自定義註解,一個是Encrypt,一個是Column。
Encrypt
執行時註解,提供加密演算法
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Encrypt {
/**
* Set the algorithm for encryption.
*/
String algorithm();
}
複製程式碼
Cloumn
列註解,主要是用來修飾表中的欄位
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Column {
//變數的值屬性是否能為空,預設值為true
boolean nullable() default true;
//欄位是否唯一,預設值為false
boolean unique() default false;
//欄位的預設值,預設值為“”
String defaultValue() default "";
//對映到資料庫時,是否忽略此欄位,預設值是false
boolean ignore() default false;
}
複製程式碼
Model
實際上沒有這個類,我為了分析方便,虛擬了這個Model,因為這幾個Module都是在建立表的時候會用到的
繼承關係
下面一一對這些Module進行說明,
ColumnModel
註釋
This is a model class for columns. It stores column name, column type, and column constraints information.
複製程式碼
這個類主要用於資料庫中的列,儲存了列的名稱,型別以及關於列的一下限制資訊
成員變數
private String columnName;//列名
private String columnType;//列的型別
private boolean isNullable = true;//是否可以為null
private boolean isUnique = false;//是否具有唯一性
private String defaultValue = "";//預設值
複製程式碼
主要方法
//設定預設值
public void setDefaultValue(String defaultValue) {
if ("text".equalsIgnoreCase(columnType)) {
if (!TextUtils.isEmpty(defaultValue)) {
this.defaultValue = "'" + defaultValue + "'";
}
} else {
this.defaultValue = defaultValue;
}
}
//是否是ID列
public boolean isIdColumn() {
return "_id".equalsIgnoreCase(columnName) || "id".equalsIgnoreCase(columnName);
}
複製程式碼
GenericModel
註釋
This is a model class for generic table. It stores table name, value column name, value column type and value id column name. This class is used to create generic tables when generic collection fields are declared in the model class.
複製程式碼
用於存放泛型的Model,存放了表名,列名,列的型別以及id的列名。當有泛型的集合在model中宣告的時候,這個類用來建立泛型的表。
成員變數
private String tableName;//表名
private String valueColumnName;//列表
private String valueColumnType;//列的型別
private String valueIdColumnName;//在主表中的引用
private String getMethodName;//查詢的關聯的表名
複製程式碼
所有的方法都是Get,Set方法,不再贅述。
AssociationsModel
註釋
This is a model class for table associations. It stores table name,
associated table name, table name which holds foreign key, and association
type. Relations have three types. One2One, Many2One and Many2Many. If the
association type is One2One or Many2One, the foreign key will be on the side
of tableHoldsForeignKey. If the association is Many2Many, a intermediate join
table will be built and named by the concatenation of the two target table
names in alphabetical order with underline in the middle.
複製程式碼
表關係的model,它主要用來儲存表名,關聯的表名,持有外來鍵的表名以及關聯的型別。表有三種關聯關係:一對一、多對一和多對多。如果關聯的型別是一對一或者多對一,外來鍵就在在關聯表中。如果關聯關係是多對多,一箇中間表將會被建立,中間表的名稱是相互關聯的兩個表的名稱中間加上下劃線,關聯表的排列順序是按照首字母的的順序進行排列。
成員變數
private String tableName;//表名
private String associatedTableName;//關聯的表名
private String tableHoldsForeignKey;//持有外來鍵的表
private int associationType;//關聯型別
複製程式碼
所有的方法都是Get,Set方法,不再贅述。
TableModel
註釋
This is a model class for tables. It stores a table name and a HashMap for
columns in the table.
複製程式碼
表相關的model,儲存了表名以及存放了所有列的HashMap
這個之前可能用的是HashMap來儲存的,不過新版中是採用List來進行 儲存的。
成員變數
private String tableName;//表名
//存放列的Module的集合
private List<ColumnModel> columnModels = new ArrayList<ColumnModel>();
//對映的物件名稱
private String className;
複製程式碼
getColumnModelByName
根據名稱返回指定的列
public ColumnModel getColumnModelByName(String columnName) {
for (ColumnModel columnModel : columnModels) {
if (columnModel.getColumnName().equalsIgnoreCase(columnName)) {
return columnModel;
}
}
return null;
}
複製程式碼
containsColumn
是否包含某一列
public boolean containsColumn(String columnName) {
for (int i = 0; i < columnModels.size(); i++) {
ColumnModel columnModel = columnModels.get(i);
if (columnName.equalsIgnoreCase(columnModel.getColumnName())) {
return true;
}
}
return false;
}
複製程式碼
removeColumnModelByName
根據列名刪除某一列
public void removeColumnModelByName(String columnName) {
if (TextUtils.isEmpty(columnName)) {
return;
}
int indexToRemove = -1;
for (int i = 0; i < columnModels.size(); i++) {
ColumnModel columnModel = columnModels.get(i);
if (columnName.equalsIgnoreCase(columnModel.getColumnName())) {
indexToRemove = i;
break;
}
}
if (indexToRemove != -1) {
columnModels.remove(indexToRemove);
}
}
複製程式碼
LitePalBase
註釋
Base class of all the LitePal components. If each component need to
interactive with other components or they have some same logic with duplicate
codes, LitePalBase may be the solution.
複製程式碼
LitePal元件的基類。如果一個元件需要跟其他的元件進行互動,或者由於他們有相同的邏輯以至於導致了重複的程式碼,LitePalBase可能是一個很好的解決方案。
繼承關係
通過類圖可以看到,LitePal的直接子類有兩個:
- Generator:製造者,那麼就是操作表,最終的子類一個是Dropper:刪除表,一個是Upgrader:升級表。
- DataHandler:資料處理者,表中的資料處理最常見的就是CRUD操作,確實他有五個子類,UpdateHandler用來更新表中的資料,SaveHandler用來儲存表中的資料,QueryHandler用來查詢表中的資料,DeleteHandler則用來刪除表中資料,還有一個是AssociationsAnalyzer,關係分析者,用來處理表之間的對映關係的,他的子類的有三個,One2OneAnalyzer,Many2OneAnalyzer,Many2ManyAnalyzer,郭神就差用中文命名了,很好懂了,不再多說。
LitePalBase
成員變數
public static final String TAG = "LitePalBase";
//Action標誌,獲取表的對映關係種類
private static final int GET_ASSOCIATIONS_ACTION = 1;
//Action標誌,獲取表的具體對映關係
private static final int GET_ASSOCIATION_INFO_ACTION = 2;
//物件型別對映的型別陣列
private OrmChange[] typeChangeRules = { new NumericOrm(), new TextOrm(), new BooleanOrm(),
new DecimalOrm(), new DateOrm(), new BlobOrm()};
//key為物件的類名,value是DB支援的普通成員變數集合
private Map<String, List<Field>> classFieldsMap = new HashMap<>();
//key為物件的類名,value是DB支援泛型成員變數集合
private Map<String, List<Field>> classGenericFieldsMap = new HashMap<>();
// AssociationsModel的集合
private Collection<AssociationsModel> mAssociationModels;
//association info的Collection
private Collection<AssociationsInfo> mAssociationInfos;
//GenericModel models的Collection
private Collection<GenericModel> mGenericModels;
複製程式碼
通過成員變數,能夠看出LitePalBase作為父類主要是通過反射把Singer中的成員變數轉換成對應的AssociationsModel,GenericModel,以及ColumnModule的集合或者Map,封裝了一些獲取這些值的方法,供他的子類在表結構更改,單個表的增刪改查中進行使用。
convertFieldToColumnModel
將物件中DB支援的欄位轉化成對應的Model
private ColumnModel convertFieldToColumnModel(Field field) {
//獲取變數型別
String fieldType = field.getType().getName();
//根據型別匹配在表中對應的型別
String columnType = getColumnType(fieldType);
boolean nullable = true;
boolean unique = false;
String defaultValue = "";
Column annotation = field.getAnnotation(Column.class);
//通過執行時註解給ColumnModel設值
if (annotation != null) {
nullable = annotation.nullable();
unique = annotation.unique();
defaultValue = annotation.defaultValue();
}
//初始化ColumnModel
ColumnModel columnModel = new ColumnModel();
columnModel.setColumnName(DBUtility.convertToValidColumnName(field.getName()));
columnModel.setColumnType(columnType);
columnModel.setNullable(nullable);
columnModel.setUnique(unique);
columnModel.setDefaultValue(defaultValue);
return columnModel;
}
複製程式碼
關聯關係
看原始碼的時候感覺這個地方是最難理解的。簡單來說關聯關係其實有三種,一對一,多對一,多對多。還有就是單向關聯跟雙向關聯,所以實際上一共有6種關聯關係。在LitePal中,是通過物件跟集合來區分的。下面舉例說明
假設現在有兩個類,A跟B,都繼承自DataSupport
- 一對一單向關聯:A中包含B,但是B中不包含A
public class A extends DataSupport {
private B b;
}
public class B extends DataSupport{
}
複製程式碼
- 一對一雙向關聯:A中包含B,同時B中也包含A
public class A extends DataSupport {
private B b;
}
public class B extends DataSupport{
private A a;
}
複製程式碼
public class A extends DataSupport {
private List<B> list;
}
public class B extends DataSupport {
}
複製程式碼
public class A extends DataSupport {
private List<B> list;
}
public class B extends DataSupport {
private A a;
}
複製程式碼
-
多對多單向關聯
public class A extends DataSupport { private List<B> list; } public class B extends DataSupport { private List<A> list; } 複製程式碼
-
多對多單向關聯
public class A extends DataSupport { private List<B> list; } public class B extends DataSupport { private List<A> list; } 複製程式碼
接下來的兩個方法,在理解了上面的概念之後才能更好地理解,不然很難理解
oneToAnyConditions
private void oneToAnyConditions(String className, Field field, int action) throws ClassNotFoundException {
//獲取成員變數的型別
Class<?> fieldTypeClass = field.getType();
// 判斷litepal.xml對映的類中是否包含fieldTypeClass
if (LitePalAttr.getInstance().getClassNames().contains(fieldTypeClass.getName())) {
//通過反射建立對映的關係表的類
Class<?> reverseDynamicClass = Class.forName(fieldTypeClass.getName());
//獲取所有的成員變數
Field[] reverseFields = reverseDynamicClass.getDeclaredFields();
//是否是雙向關聯的標誌,預設為false
boolean reverseAssociations = false;
for (int i = 0; i < reverseFields.length; i++) {
Field reverseField = reverseFields[i];
if (!Modifier.isStatic(reverseField.getModifiers())) {
Class<?> reverseFieldTypeClass = reverseField.getType();
//B中含有A,關聯關係是一對一
if (className.equals(reverseFieldTypeClass.getName())) {
if (action == GET_ASSOCIATIONS_ACTION) {
//將AssociationModel新增進Collection
addIntoAssociationModelCollection(className, fieldTypeClass.getName(),
fieldTypeClass.getName(), Const.Model.ONE_TO_ONE);
} else if (action == GET_ASSOCIATION_INFO_ACTION) {
//將AssociationInfo新增進Collection
addIntoAssociationInfoCollection(className, fieldTypeClass.getName(),
fieldTypeClass.getName(), field, reverseField, Const.Model.ONE_TO_ONE);
}
//雙向關聯的標誌置為true
reverseAssociations = true;
}
//如果在B類中含有泛型集合,說明關聯關係是多對一
else if (isCollection(reverseFieldTypeClass)) {
String genericTypeName = getGenericTypeName(reverseField);
if (className.equals(genericTypeName)) {
if (action == GET_ASSOCIATIONS_ACTION) {
//將AssociationModel新增進Collection
addIntoAssociationModelCollection(className, fieldTypeClass.getName(), className, Const.Model.MANY_TO_ONE);
} else if (action == GET_ASSOCIATION_INFO_ACTION) {
//將AssociationInfo新增進Collection
addIntoAssociationInfoCollection(className, fieldTypeClass.getName(),className, field, reverseField, Const.Model.MANY_TO_ONE);
}
//雙向關聯的標誌置為true
reverseAssociations = true;
}
}
}
}
//reverseAssociations為false,說明是單向關聯
//單向關聯
if (!reverseAssociations) {
if (action == GET_ASSOCIATIONS_ACTION) {
addIntoAssociationModelCollection(className, fieldTypeClass.getName(),
fieldTypeClass.getName(), Const.Model.ONE_TO_ONE);
} else if (action == GET_ASSOCIATION_INFO_ACTION) {
addIntoAssociationInfoCollection(className, fieldTypeClass.getName(),
fieldTypeClass.getName(), field, null, Const.Model.ONE_TO_ONE);
}
}
}
}
複製程式碼
manyToAnyConditions
private void manyToAnyConditions(String className, Field field, int action) throws ClassNotFoundException {
//如果A中沒有集合類的成員變數,那麼肯定不是多對多
if (isCollection(field.getType())) {
String genericTypeName = getGenericTypeName(field);
//A類中包含泛型集合類
if (LitePalAttr.getInstance().getClassNames().contains(genericTypeName)) {
//通過反射建立一個B類
Class<?> reverseDynamicClass = Class.forName(genericTypeName);
//拿到B類的所有成員變數
Field[] reverseFields = reverseDynamicClass.getDeclaredFields();
//雙向關聯的標誌
boolean reverseAssociations = false;
for (int i = 0; i < reverseFields.length; i++) {
Field reverseField = reverseFields[i];
// Only map private fields
if (!Modifier.isStatic(reverseField.getModifiers())) {
Class<?> reverseFieldTypeClass = reverseField.getType();
//如果B中含有A,說明是多對一
if (className.equals(reverseFieldTypeClass.getName())) {
if (action == GET_ASSOCIATIONS_ACTION) {
//將AssociationModel新增進Collection
addIntoAssociationModelCollection(className, genericTypeName,
genericTypeName, Const.Model.MANY_TO_ONE);
} else if (action == GET_ASSOCIATION_INFO_ACTION) {
//將AssociationInfo新增進Collection
addIntoAssociationInfoCollection(className, genericTypeName, genericTypeName,field, reverseField, Const.Model.MANY_TO_ONE);
}
//雙向關聯的標誌置為true
reverseAssociations = true;
}
//如果B的成員變數有集合類
else if (isCollection(reverseFieldTypeClass)) {
String reverseGenericTypeName = getGenericTypeName(reverseField);
//如果B中的泛型跟A相同,那麼就是多對多
if (className.equals(reverseGenericTypeName)) {
if (action == GET_ASSOCIATIONS_ACTION) {
//將AssociationModel新增進Collection
addIntoAssociationModelCollection(className, genericTypeName, null,Const.Model.MANY_TO_MANY);
} else if (action == GET_ASSOCIATION_INFO_ACTION) {
//將AssociationInfo新增進Collection
addIntoAssociationInfoCollection(className, genericTypeName, null, field,reverseField, Const.Model.MANY_TO_MANY);
}
//雙向關聯的標誌置為true
reverseAssociations = true;
}
}
}
}
//非雙向關聯,作為多對一處理
if (!reverseAssociations) {
if (action == GET_ASSOCIATIONS_ACTION) {
//將AssociationModel新增進Collection
addIntoAssociationModelCollection(className, genericTypeName,
genericTypeName, Const.Model.MANY_TO_ONE);
} else if (action == GET_ASSOCIATION_INFO_ACTION) {
//將AssociationInfo新增進Collection
addIntoAssociationInfoCollection(className, genericTypeName, genericTypeName, field, null, Const.Model.MANY_TO_ONE);
}
}
} else if (BaseUtility.isGenericTypeSupported(genericTypeName) && action == GET_ASSOCIATIONS_ACTION) {
//如果是雙向關聯,並且是獲取關聯關係型別的Action下
Column annotation = field.getAnnotation(Column.class);
//變數是有效的
if (annotation != null && annotation.ignore()) {
return;
}
//建立GenericModel
GenericModel genericModel = new GenericModel();
genericModel.setTableName(DBUtility.getGenericTableName(className, field.getName()));
genericModel.setValueColumnName(DBUtility.convertToValidColumnName(field.getName()));
genericModel.setValueColumnType(getColumnType(genericTypeName));
genericModel.setValueIdColumnName(DBUtility.getGenericValueIdColumnName(className));
mGenericModels.add(genericModel);
}
}
}
複製程式碼
Generator
繼承關係
成員變數
private Collection<TableModel> mTableModels;//表的Module集合
private Collection<AssociationsModel> mAllRelationModels;//關係Module集合
複製程式碼
execute
通過SQL語句建表
protected void execute(List<String> sqls, SQLiteDatabase db) {
String throwSQL = "";
try {
if (sqls != null && !sqls.isEmpty()) {
for (String sql : sqls) {
if (!TextUtils.isEmpty(sql)) {
//轉換成小寫
throwSQL = BaseUtility.changeCase(sql);
db.execSQL(throwSQL);
}
}
}
} catch (SQLException e) {
throw new DatabaseGenerateException(DatabaseGenerateException.SQL_ERROR + throwSQL);
}
}
複製程式碼
create
呼叫Creatord的方法建立或更新表
private static void create(SQLiteDatabase db, boolean force) {
Creator creator = new Creator();
creator.createOrUpgradeTable(db, force);
}
複製程式碼
drop
呼叫Dropper的方法刪除表
private static void drop(SQLiteDatabase db) {
Dropper dropper = new Dropper();
dropper.createOrUpgradeTable(db, false);
}
複製程式碼
upgrade
呼叫Upgrader的方法
private static void upgradeTables(SQLiteDatabase db) {
Upgrader upgrader = new Upgrader();
upgrader.createOrUpgradeTable(db, false);
}
複製程式碼
DataHandler
繼承關係
成員變數
//DB的例項用來進行CRUD操作
SQLiteDatabase mDatabase;
//空的DataSupport例項
private DataSupport tempEmptyModel;
//ModuleA中的AssociationsInfo集合
private List<AssociationsInfo> fkInCurrentModel;
//ModuleB中的AssociationsInfo集合
private List<AssociationsInfo> fkInOtherModel;
複製程式碼
query
protected <T> List<T> query(Class<T> modelClass, String[] columns, String selection,
String[] selectionArgs, String groupBy, String having, String orderBy, String limit,
List<AssociationsInfo> foreignKeyAssociations) {
List<T> dataList = new ArrayList<T>();
Cursor cursor = null;
try {
List<Field> supportedFields = getSupportedFields(modelClass.getName());
List<Field> supportedGenericFields =getSupportedGenericFields(modelClass.getName());
String[] customizedColumns = DBUtility.convertSelectClauseToValidNames(getCustomizedColumns(columns, supportedGenericFields, foreignKeyAssociations));
//拿到table的名字
String tableName = getTableName(modelClass);
//通過引數進行查詢
cursor = mDatabase.query(tableName, customizedColumns, selection, selectionArgs,
groupBy, having, orderBy, limit);
if (cursor.moveToFirst()) {
SparseArray<QueryInfoCache> queryInfoCacheSparseArray = new SparseArray<QueryInfoCache>();
Map<Field, GenericModel> genericModelMap = new HashMap<Field, GenericModel>();
do {
//建立泛型例項
T modelInstance = (T) createInstanceFromClass(modelClass);
//進行Id賦值
giveBaseObjIdValue((DataSupport) modelInstance,
cursor.getLong(cursor.getColumnIndexOrThrow("id")));
//將查詢出來的Value給Module賦值
setValueToModel(modelInstance, supportedFields, foreignKeyAssociations, cursor, queryInfoCacheSparseArray);
//將泛型的Value賦值給Module
setGenericValueToModel((DataSupport) modelInstance, supportedGenericFields, genericModelMap);
//設定關聯物件的值
if (foreignKeyAssociations != null) {
setAssociatedModel((DataSupport) modelInstance);
}
//新增元素進集合
dataList.add(modelInstance);
} while (cursor.moveToNext());
queryInfoCacheSparseArray.clear();
genericModelMap.clear();
}
return dataList;
} catch (Exception e) {
throw new DataSupportException(e.getMessage(), e);
} finally {
if (cursor != null) {
cursor.close();
}
}
}
複製程式碼
analyzeAssociations
對關聯關係進行分析
private void analyzeAssociations(String className) {
Collection<AssociationsInfo> associationInfos = getAssociationInfo(className);
if (fkInCurrentModel == null) {
fkInCurrentModel = new ArrayList<AssociationsInfo>();
} else {
fkInCurrentModel.clear();
}
if (fkInOtherModel == null) {
fkInOtherModel = new ArrayList<AssociationsInfo>();
} else {
fkInOtherModel.clear();
}
for (AssociationsInfo associationInfo : associationInfos) {
//關聯關係為多對一或者一對一
if (associationInfo.getAssociationType() == Const.Model.MANY_TO_ONE
|| associationInfo.getAssociationType() == Const.Model.ONE_TO_ONE) {
if (associationInfo.getClassHoldsForeignKey().equals(className)) {
//當前module
fkInCurrentModel.add(associationInfo);
} else {
//關聯Module
fkInOtherModel.add(associationInfo);
}
} else if (associationInfo.getAssociationType() == Const.Model.MANY_TO_MANY) {
//多對多
fkInOtherModel.add(associationInfo);
}
}
}
複製程式碼
analyzeAssociatedModels
protected void analyzeAssociatedModels(DataSupport baseObj, Collection<AssociationsInfo> associationInfos) {
try {
for (AssociationsInfo associationInfo : associationInfos) {
//遍歷associationInfos集合
if (associationInfo.getAssociationType() == Const.Model.MANY_TO_ONE) {
//Many2OneAnalyzer處理
new Many2OneAnalyzer().analyze(baseObj, associationInfo);
} else if (associationInfo.getAssociationType() == Const.Model.ONE_TO_ONE) {
//One2OneAnalyzer處理
new One2OneAnalyzer().analyze(baseObj, associationInfo);
} else if (associationInfo.getAssociationType() == Const.Model.MANY_TO_MANY) {
//Many2ManyAnalyzer處理
new Many2ManyAnalyzer().analyze(baseObj, associationInfo);
}
}
} catch (Exception e) {
throw new DataSupportException(e.getMessage(), e);
}
}
複製程式碼
AssociationsAnalyzer的子類實際上是在處理AssociationsInfo,就是將AssociationsInfo分配給當前的Module或者是關聯的Module
encryptValue
LitePal支援MD5加密跟AES加密,但是MD5加密之後是不能解密的,那麼有什麼用,沒明白
protected Object encryptValue(String algorithm, Object fieldValue) {
if (algorithm != null && fieldValue != null) {
if (DataSupport.AES.equalsIgnoreCase(algorithm)) {
fieldValue = CipherUtil.aesEncrypt((String) fieldValue);
} else if (DataSupport.MD5.equalsIgnoreCase(algorithm)) {
fieldValue = CipherUtil.md5Encrypt((String) fieldValue);
}
}
return fieldValue;
}
複製程式碼
decryptValue
*/
protected Object decryptValue(String algorithm, Object fieldValue) {
if (algorithm != null && fieldValue != null) {
if (DataSupport.AES.equalsIgnoreCase(algorithm)) {
fieldValue = CipherUtil.aesDecrypt((String) fieldValue);
}
}
return fieldValue;
}
複製程式碼
SaveHandler
方法概覽
onSave
void onSave(DataSupport baseObj) throws SecurityException, IllegalArgumentException,
NoSuchMethodException, IllegalAccessException, InvocationTargetException {
//獲取類名
String className = baseObj.getClassName();
//獲取DB支援的成員變數
List<Field> supportedFields = getSupportedFields(className);
//獲取DB支援的泛型變數
List<Field> supportedGenericFields = getSupportedGenericFields(className);
//獲取所有的關聯關係
Collection<AssociationsInfo> associationInfos = getAssociationInfo(className);
//是否儲存過
if (!baseObj.isSaved()) {
if (!ignoreAssociations) {
analyzeAssociatedModels(baseObj, associationInfos);
}
//進行資料儲存
doSaveAction(baseObj, supportedFields, supportedGenericFields);
if (!ignoreAssociations) {
analyzeAssociatedModels(baseObj, associationInfos);
}
} else {
if (!ignoreAssociations) {
analyzeAssociatedModels(baseObj, associationInfos);
}
//更新操作
doUpdateAction(baseObj, supportedFields, supportedGenericFields);
}
}
複製程式碼
doSaveAction
private void doSaveAction(DataSupport baseObj, List<Field> supportedFields, List<Field> supportedGenericFields)
throws SecurityException, IllegalArgumentException, NoSuchMethodException,
IllegalAccessException, InvocationTargetException {
//清空ContentValues
values.clear();
//將成員變數轉化成Values
beforeSave(baseObj, supportedFields, values);
//進行插入操作,拿到返回的ID
long id = saving(baseObj, values);
//如果有關聯表,則進行關聯表的插入
afterSave(baseObj, supportedFields, supportedGenericFields, id);
}
複製程式碼
doUpdateAction
private void doUpdateAction(DataSupport baseObj, List<Field> supportedFields, List<Field> supportedGenericFields)
throws SecurityException, IllegalArgumentException, NoSuchMethodException,
IllegalAccessException, InvocationTargetException {
//清空ContentValues
values.clear();
//將成員變數轉化成Values
beforeUpdate(baseObj, supportedFields, values);
//進行更新操作
updating(baseObj, values);
//進行關聯表操作
afterUpdate(baseObj, supportedGenericFields);
}
複製程式碼
QueryHandler
方法概覽
onFindFirst
找到第一個
<T> T onFindFirst(Class<T> modelClass, boolean isEager) {
List<T> dataList = query(modelClass, null, null, null, null, null, "id", "1",
getForeignKeyAssociations(modelClass.getName(), isEager));
if (dataList.size() > 0) {
return dataList.get(0);
}
return null;
}
複製程式碼
onFindAll
<T> List<T> onFindAll(Class<T> modelClass, boolean isEager, long... ids) {
List<T> dataList;
if (isAffectAllLines(ids)) {
dataList = query(modelClass, null, null, null, null, null, "id", null,
getForeignKeyAssociations(modelClass.getName(), isEager));
} else {
dataList = query(modelClass, null, getWhereOfIdsWithOr(ids), null, null, null, "id",
null, getForeignKeyAssociations(modelClass.getName(), isEager));
}
return dataList;
}
複製程式碼
Query
方法概覽
update
int onUpdate(DataSupport baseObj, long id) throws SecurityException, IllegalArgumentException,
NoSuchMethodException, IllegalAccessException, InvocationTargetException {
List<Field> supportedFields = getSupportedFields(baseObj.getClassName());
List<Field> supportedGenericFields = getSupportedGenericFields(baseObj.getClassName());
updateGenericTables(baseObj, supportedGenericFields, id);
ContentValues values = new ContentValues();
//轉換成員賓亮
putFieldsValue(baseObj, supportedFields, values);
//設定預設值
putFieldsToDefaultValue(baseObj, values, id);
if (values.size() > 0) {
return mDatabase.update(baseObj.getTableName(), values, "id = " + id, null);
}
return 0;
}
複製程式碼
qanalyzeAssociations
private void analyzeAssociations(DataSupport baseObj) {
try {
Collection<AssociationsInfo> associationInfos = getAssociationInfo(baseObj
.getClassName());
//關聯表查詢
analyzeAssociatedModels(baseObj, associationInfos);
} catch (Exception e) {
throw new DataSupportException(e.getMessage(), e);
}
}
複製程式碼
DeleteHandler
方法概覽
onDelete
int onDelete(Class<?> modelClass, long id) {
//獲取泛型變數集合
List<Field> supportedGenericFields = getSupportedGenericFields(modelClass.getName());
//刪除關聯表
deleteGenericData(modelClass, supportedGenericFields, id);
//分析關聯表
analyzeAssociations(modelClass);
int rowsAffected = deleteCascade(modelClass, id);
rowsAffected += mDatabase.delete(getTableName(modelClass),"id = " + id, null);
//刪除關聯表中的資料
getForeignKeyTableToDelete().clear();
return rowsAffected;
}
複製程式碼
AsyncExecutor
如果只是對少量的資料進行操作,實際上在主執行緒中操作是沒有問題的,但是如果是大量的資料,那麼就會比較耗時,所以就必須在子執行緒中進行,所以LitePal支援在子執行緒中進行DB操作。
繼承關係
AsyncExecutor
public abstract class AsyncExecutor {
//後臺Runnable
private Runnable pendingTask;
//提交一個任務
public void submit(Runnable task) {
pendingTask = task;
}
//開啟執行緒
void execute() {
if (pendingTask != null) {
new Thread(pendingTask).start();
}
}
}
複製程式碼
AverageExecutor
//回撥的介面
public interface AverageCallback {
void onFinish(double average);
}
public class AverageExecutor extends AsyncExecutor {
private AverageCallback cb;
//開啟執行緒
public void listen(AverageCallback callback) {
cb = callback;
execute();
}
public AverageCallback getListener() {
return cb;
}
}
複製程式碼
使用方式
public static AverageExecutor averageAsync(final String tableName, final String column) {
final AverageExecutor executor = new AverageExecutor();
Runnable runnable = new Runnable() {
@Override
public void run() {
synchronized (DataSupport.class) {
final double average = average(tableName, column);
if (executor.getListener() != null) {
LitePal.getHandler().post(new Runnable() {
@Override
public void run() {
//主執行緒中回撥
executor.getListener().onFinish(average);
}
});
}
}
}
};
executor.submit(runnable);
return executor;
}
複製程式碼
另外幾個Executor實際上也是這麼使用的,也就是在子執行緒中進行查詢完成,然後通過Handler將結果以post Runnable的形式傳遞到主執行緒,通過介面回撥傳遞操作的結果。
DataSupport
註釋
DataSupport connects classes to SQLite database tables to establish an almost zero-configuration persistence layer for applications. In the context of an application, these classes are commonly referred to as models. Models can also be connected to other models.
DataSupport relies heavily on naming in that it uses class and association names to establish mappings between respective database tables and foreign key columns.
Automated mapping between classes and tables, attributes and columns.
複製程式碼
DataSupport將類連線到SQLite資料庫的表以建立一個應用程式幾乎零配置的持久層。在應用的Context中,這些類被定義為Module,Module也可以跟別的Modules產生聯絡。
DataSupport嚴重依賴於它使用類和關聯的命名。在資料庫表和外部表之間建立對映的名稱,外來鍵。
在類和表、屬性和列之間的自動對映。
delete&deleteAsync
public static synchronized int delete(Class<?> modelClass, long id) {
int rowsAffected = 0;
SQLiteDatabase db = Connector.getDatabase();
db.beginTransaction();
try {
//呼叫了DeleteHandler
DeleteHandler deleteHandler = new DeleteHandler(db);
rowsAffected = deleteHandler.onDelete(modelClass, id);
db.setTransactionSuccessful();
return rowsAffected;
} finally {
db.endTransaction();
}
}
複製程式碼
saveAll&saveAllAsync
public static synchronized <T extends DataSupport> void saveAll(Collection<T> collection) {
SQLiteDatabase db = Connector.getDatabase();
db.beginTransaction();
try {
//呼叫了SaveHandler
SaveHandler saveHandler = new SaveHandler(db);
saveHandler.onSaveAll(collection);
db.setTransactionSuccessful();
} catch (Exception e) {
throw new DataSupportException(e.getMessage(), e);
} finally {
db.endTransaction();
}
}
複製程式碼
update&updateAsync
*/
public synchronized int update(long id) {
try {
//呼叫了UpdateHandler
UpdateHandler updateHandler = new UpdateHandler(Connector.getDatabase());
int rowsAffected = updateHandler.onUpdate(this, id);
getFieldsToSetToDefault().clear();
return rowsAffected;
} catch (Exception e) {
throw new DataSupportException(e.getMessage(), e);
}
}
複製程式碼
average&averageAsync
public static synchronized double average(String tableName, String column) {
ClusterQuery cQuery = new ClusterQuery();
return cQuery.average(tableName, column);
}
複製程式碼
接著呼叫了ClusterQuery的average方法,ClusterQuery然後呼叫了QueryHandler的方法
public synchronized double average(String tableName, String column) {
QueryHandler queryHandler = new QueryHandler(Connector.getDatabase());
return queryHandler.onAverage(tableName, column, mConditions);
}
複製程式碼
max&maxAsync
public static synchronized <T> T max(String tableName, String columnName, Class<T> columnType) {
ClusterQuery cQuery = new ClusterQuery();
return cQuery.max(tableName, columnName, columnType);
}
複製程式碼
接著呼叫了ClusterQuery的max方法,ClusterQuery然後呼叫了max的方法
public synchronized <T> T max(String tableName, String columnName, Class<T> columnType) {
QueryHandler queryHandler = new QueryHandler(Connector.getDatabase());
return queryHandler.onMax(tableName, columnName, mConditions, columnType);
}
複製程式碼
CRUD操作
DataSupport是所有對映的物件的父類,定義了所有的關於DB的操作方法,並且每個操作都有同步跟非同步方法,非同步方法最終還是會呼叫同步方法,只是在呼叫的時候加了一個同步鎖,有點類似Picasso的單例,可以是單個物件也就是某一列的操作,也可以是對整個DB的操作,如果是基本的CRUD操作,那麼DataSupport會直接呼叫DataHandler的相應的子類的相應的方法去執行。
聚合查詢
如果是聚合查詢,也就是說需要多個條件的查詢,LitePal提供了一個類ClusterQuery,可以設定多個查詢條件,並且採用了Builder設計模式可以動態的設定查詢條件,類似average,count,limit等操作,下面會重點分析一下這個類。
ClusterQuery
註釋
Allows developers to query tables with cluster style.
複製程式碼
讓開發者能夠以聚集的風格進行查詢。
這段確實不知道怎麼翻譯,不過意思就是能夠進行聚合查詢。
成員變數
//查詢的列
String[] mColumns;
//查詢條件
String[] mConditions;
//排序條件
String mOrderBy;
//返回資料的數量
String mLimit;
//偏移量,也就是第幾列開始查詢
String mOffset;
複製程式碼
構造方法
ClusterQuery() {
}
複製程式碼
空方法,可以讓外部例項化
Builder
//設定要查詢的列
public ClusterQuery select(String... columns) {
mColumns = columns;
return this;
}
//設定查詢條件
public ClusterQuery where(String... conditions) {
mConditions = conditions;
return this;
}
//設定排序準則
public ClusterQuery order(String column) {
mOrderBy = column;
return this;
}
//設定返回資料的數量
public ClusterQuery limit(int value) {
mLimit = String.valueOf(value);
return this;
}
//設定偏移量
public ClusterQuery offset(int value) {
mOffset = String.valueOf(value);
return this;
}
複製程式碼
ClusterQuery採用了Builder模式,可以動態地進行新增引數,也就是實現連綴查詢,查詢起來比較方便。
count&countAsync
public CountExecutor countAsync(final String tableName) {
final CountExecutor executor = new CountExecutor();
Runnable runnable = new Runnable() {
@Override
public void run() {
synchronized (DataSupport.class) {
//同步獲取查詢結果
final int count = count(tableName);
if (executor.getListener() != null) {
LitePal.getHandler().post(new Runnable() {
@Override
public void run() {
executor.getListener().onFinish(count);
}
});
}
}
}
};
executor.submit(runnable);
return executor;
}
複製程式碼
追一下count方法,呼叫了QueryHandler方法
public synchronized int count(String tableName) {
QueryHandler queryHandler = new QueryHandler(Connector.getDatabase());
//然後新增了構造時候的引數
return queryHandler.onCount(tableName, mConditions);
}
複製程式碼
min&minAsync
public <T> FindExecutor minAsync(final String tableName, final String columnName, final Class<T> columnType) {
final FindExecutor executor = new FindExecutor();
Runnable runnable = new Runnable() {
@Override
public void run() {
synchronized (DataSupport.class) {
//同步獲取最小值的查詢結果
final T t = min(tableName, columnName, columnType);
if (executor.getListener() != null) {
LitePal.getHandler().post(new Runnable() {
@Override
public void run() {
//回撥通知主執行緒
executor.getListener().onFinish(t);
}
});
}
}
}
};
executor.submit(runnable);
return executor;
}
複製程式碼
追一下min方法
public synchronized <T> T min(String tableName, String columnName, Class<T> columnType) {
QueryHandler queryHandler = new QueryHandler(Connector.getDatabase());
//然後新增了構造時候的查詢引數
return queryHandler.onMin(tableName, columnName, mConditions, columnType);
}
複製程式碼
Utils
CipherUtil
這個主要用來進行加解密的,LitePal採用的是AES加密演算法,而且加密的key可以在LitePal中進行動態設定,不過也支援MD5加密,不過MD5無法解密,不過好像可以用來對DB的名稱進行加密。
//AES加密
public static String aesEncrypt(String plainText) {
if (TextUtils.isEmpty(plainText)) {
return plainText;
}
try {
return AESCrypt.encrypt(aesKey, plainText);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
//AES解密
public static String aesDecrypt(String encryptedText) {
if (TextUtils.isEmpty(encryptedText)) {
return encryptedText;
}
try {
return AESCrypt.decrypt(aesKey, encryptedText);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
//MD5加密
public static String md5Encrypt(String plainText) {
try {
MessageDigest digest = MessageDigest.getInstance("MD5");
digest.update(plainText.getBytes(Charset.defaultCharset()));
return new String(toHex(digest.digest()));
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return "";
}
複製程式碼
BaseUtility
基本的工具類,封裝了一些最基本的方法
- changeCase:對傳入的String進行大小寫轉換,或者保持不變
- checkConditionsCorrect:檢查查詢條件是否合理
- isFieldTypeSupported:檢查成員變數是否合理
DBUtility
- getTableNameByClassName:通過類名獲取表名
- isTableExists:查詢表是否存在
SharedUtil
- updateVersion:根據Key升級當前版本號
- getLastVersion:根據Key獲取上一個版本
- removeVersion:根據Key移除特定的版本
總結
LitePal利用反射對獲取Model的屬性,然後拼接成SQL語句,對於呼叫者來說減少了很多重複性操作,升級也比較方便,符合Java的物件導向,使用者不用再關心底層的SQL語句,當然LitePal同時也提供了使用SQL語句查詢的功能,使用者可以憑喜好使用,看了SQL的原始碼實現之後,個人覺得有些地方可以改進。
反射優化
LitePal的CRUD操作每次都需要反射,這些在資料量少的時候影響還好,在資料量較大的時候會比較影響效率,可以對已經反射過的Model屬性快取起來,那麼下一次CRUD操作的時候如果是同一個Model,那麼直接從快取中取,那麼效率就會快一些。
同步優化
LitePal的CRUD操作全部都使用了synchronized關鍵字,其實如果我們的操作是單執行緒中進行,沒必要上鎖,可以再提供一個過載的非同步方法,因為很多時候CRUD操作都是在單執行緒中的。
單例優化
LitePal的每一次CRUD操作,都需要對DataHandler的子類SaveHandler、QueryHandler進行一次例項化,如果採用單例結合Builder模式的話可以減少一部分開銷,還有ClusterQuery也是如此。
執行緒池優化
LitePal開啟多執行緒的時候是採用的new Thread方式,如果有多個非同步的DB操作的時候,會建立很多個執行緒,如果使用執行緒池可以減少一部分開銷。
其實上面的一些建議只是我個人的理解,LitePal已經是一款很優秀的框架,希望LitePal越來越好。