LitePal首页 LitePalFramework/LitePal
使用方法请参阅官方说明。这里主要通过分析源码来介绍其原理。
初始化
通过查看使用说明,我们发现首先要对LitePal进行初始化。调用的是
/**
* Initialize to make LitePal ready to work. If you didn't configure LitePalApplication
* in the AndroidManifest.xml, make sure you call this method as soon as possible. In
* Application's onCreate() method will be fine.
*
* @param context
* Application context.
*/
public static void initialize(Context context) {
LitePalApplication.sContext = context;
}
* Global application context.
*/
static Context sContext;
/**
* Construct of LitePalApplication. Initialize application context.
*/
public LitePalApplication() {
sContext = this;
}
初始化主要是为了得到sContext对象。
数据库的创建和使用
查看说明,我们发现table的创建和修改都是自动实现的。对外公开的主要是对数据的操作,所以我们就顺着对数据的操作来分析。
/**
* Saves the model.
*
*
* Person person = new Person();
* person.setName("Tom");
* person.setAge(22);
* person.saveThrows();
*
*
* If the model is a new record gets created in the database, otherwise the
* existing record gets updated.
* If saving process failed by any accident, the whole action will be
* cancelled and your database will be rolled back and throws
* {@link DataSupportException}
* If the model has a field named id or _id and field type is int or long,
* the id value generated by database will assign to it after the model is
* saved.
* Note that if the associated models of this model is already saved. The
* associations between them will be built automatically in database after
* it saved.
*
* @throws DataSupportException
*/
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();
}
}
查看源码是我们最好先宏观上理清逻辑和思路,再细化研究每个模块的细节。
总结一下:
1,操作是一个线程安全的方法,使用了synchronized关键字。
2,操作使用了事务。
3,最重要的是Connector.getDatabase()方法,后续分析。
4,真正的操作需要使用对应的Handler方法。
Connector分析
/**
* Get a writable SQLiteDatabase.
*
* There're a lot of ways to operate database in android. But LitePal
* doesn't support using ContentProvider currently. The best way to use
* LitePal well is get the SQLiteDatabase instance and use the methods like
* SQLiteDatabase#save, SQLiteDatabase#update, SQLiteDatabase#delete,
* SQLiteDatabase#query in the SQLiteDatabase class to do the database
* operation. It will be improved in the future.
*
* @return A writable SQLiteDatabase instance
*/
public synchronized static SQLiteDatabase getWritableDatabase() {
LitePalOpenHelper litePalHelper = buildConnection();
return litePalHelper.getWritableDatabase();
}
/**
* Deprecated. Using {@link LitePal#getDatabase()} instead.
*
* @return A readable SQLiteDatabase instance.
*/
@Deprecated
public synchronized static SQLiteDatabase getReadableDatabase() {
LitePalOpenHelper litePalHelper = buildConnection();
return litePalHelper.getReadableDatabase();
}
是通过buildConnection来得到一个LitePalOpenHelper对象,然后返回一个数据库对象。
LitePalOpenHelper本身就是SQLiteOpenHelper。
/**
* Build a connection to the database. This progress will analysis the
* litepal.xml file, and will check if the fields in LitePalAttr are valid,
* and it will open a SQLiteOpenHelper to decide to create tables or update
* tables or doing nothing depends on the version attributes.
*
* After all the stuffs above are finished. This method will return a
* LitePalHelper object.Notes this method could throw a lot of exceptions.
*
* @return LitePalHelper object.
*
* @throws org.litepal.exceptions.InvalidAttributesException
*/
private static LitePalOpenHelper buildConnection() {
LitePalAttr litePalAttr = LitePalAttr.getInstance();
litePalAttr.checkSelfValid();
if (mLitePalHelper == null) {
String dbName = litePalAttr.getDbName();
if ("external".equalsIgnoreCase(litePalAttr.getStorage())) {
dbName = LitePalApplication.getContext().getExternalFilesDir("") + "/databases/" + dbName;
} else if (!"internal".equalsIgnoreCase(litePalAttr.getStorage()) && !TextUtils.isEmpty(litePalAttr.getStorage())) {
// internal or empty means internal storage, neither or them means sdcard storage
String dbPath = Environment.getExternalStorageDirectory().getPath() + "/" + litePalAttr.getStorage();
dbPath = dbPath.replace("//", "/");
if (BaseUtility.isClassAndMethodExist("android.support.v4.content.ContextCompat", "checkSelfPermission") &&
ContextCompat.checkSelfPermission(LitePalApplication.getContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
throw new DatabaseGenerateException(String.format(DatabaseGenerateException.EXTERNAL_STORAGE_PERMISSION_DENIED, dbPath));
}
File path = new File(dbPath);
if (!path.exists()) {
path.mkdirs();
}
dbName = dbPath + "/" + dbName;
}
mLitePalHelper = new LitePalOpenHelper(dbName, litePalAttr.getVersion());
}
return mLitePalHelper;
}
首先需要一个LitePalAttr对象。我们查看源码
/**
* Provide a way to get the instance of LitePalAttr.
* @return the singleton instance of LitePalAttr
*/
public static LitePalAttr getInstance() {
if (litePalAttr == null) {
synchronized (LitePalAttr.class) {
if (litePalAttr == null) {
litePalAttr = new LitePalAttr();
loadLitePalXMLConfiguration();
}
}
}
return litePalAttr;
}
private static void loadLitePalXMLConfiguration() {
if (BaseUtility.isLitePalXMLExists()) {
LitePalConfig config = LitePalParser.parseLitePalConfiguration();
litePalAttr.setDbName(config.getDbName());
litePalAttr.setVersion(config.getVersion());
litePalAttr.setClassNames(config.getClassNames());
litePalAttr.setCases(config.getCases());
litePalAttr.setStorage(config.getStorage());
}
}
LitePalAttr对象其实是单例,是配置文件LitePal.xml的解析结果。有兴趣的同学可以再深入研究。
得到这个litePalAttr后,首先要检验,调用checkSelfValid。有兴趣的同学可以再深入研究。
然后根据litePalAttr身上的属性来生成对应的数据库文件。支持SD卡外置数据库。
@Override
public void onCreate(SQLiteDatabase db) {
Generator.create(db);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Generator.upgrade(db);
SharedUtil.updateVersion(LitePalAttr.getInstance().getExtraKeyName(), newVersion);
}
查看源码我们看到数据库的创建和更新都是通过Generator来操作的。
/**
* Create tables based on the class models defined in the litepal.xml file.
* After the tables are created, add association to these tables based on
* the associations between class models.
*
* @param db
* Instance of SQLiteDatabase.
*/
static void create(SQLiteDatabase db) {
create(db, true);
addAssociation(db, true);
}
/**
* Create tables based on the class models defined in the litepal.xml file.
* After the tables are created, add association to these tables based on
* the associations between class models.
*
* @param db
* Instance of SQLiteDatabase.
* @param force
* Drop the table first if it already exists.
*/
private static void create(SQLiteDatabase db, boolean force) {
Creator creator = new Creator();
creator.createOrUpgradeTable(db, force);
}
/**
* Analyzing the table model, create a table in the database based on the
* table model's value.
*/
@Override
protected void createOrUpgradeTable(SQLiteDatabase db, boolean force) {
for (TableModel tableModel : getAllTableModels()) {
createOrUpgradeTable(tableModel, db, force);
}
}
/**
* This is a shortcut way to get all the table models for each model class
* defined in the mapping list. No need to iterate all the model classes and
* get table model for each one.
*
* @return A collection contains all table models.
*/
protected Collection getAllTableModels() {
if (mTableModels == null) {
mTableModels = new ArrayList();
}
if (!canUseCache()) {
mTableModels.clear();
for (String className : LitePalAttr.getInstance().getClassNames()) {
mTableModels.add(getTableModel(className));
}
}
return mTableModels;
}
/**
* This method is used to get the table model by the class name passed
* in. The principle to generate table model is that each field in the class
* with non-static modifier and has a type among int/Integer, long/Long,
* short/Short, float/Float, double/Double, char/Character, boolean/Boolean
* or String, would generate a column with same name as corresponding field.
* If users don't want some of the fields map a column, declare an ignore
* annotation with {@link Column#ignore()}.
*
* @param className
* The full name of the class to map in database.
* @return A table model with table name, class name and the map of column
* name and column type.
*/
protected TableModel getTableModel(String className) {
String tableName = DBUtility.getTableNameByClassName(className);
TableModel tableModel = new TableModel();
tableModel.setTableName(tableName);
tableModel.setClassName(className);
List supportedFields = getSupportedFields(className);
for (Field field : supportedFields) {
ColumnModel columnModel = convertFieldToColumnModel(field);
tableModel.addColumnModel(columnModel);
}
return tableModel;
}
getTableModels其实是通过读取配置文件里边的配置信息来生成对应的tableModel。
protected void createOrUpgradeTable(TableModel tableModel, SQLiteDatabase db, boolean force) {
execute(getCreateTableSQLs(tableModel, db, force), db);
giveTableSchemaACopy(tableModel.getTableName(), Const.TableSchema.NORMAL_TABLE, db);
}
/**
* When creating a new table, it should always try to drop the same name
* table if exists. This method create a SQL array for the whole create
* table job.
*
* @param tableModel
* The table model.
* @param db
* Instance of SQLiteDatabase.
* @param force
* Drop the table first if it already exists.
* @return A SQL array contains drop table if it exists and create new
* table.
*/
protected List getCreateTableSQLs(TableModel tableModel, SQLiteDatabase db, boolean force) {
List sqls = new ArrayList();
if (force) {
sqls.add(generateDropTableSQL(tableModel));
sqls.add(generateCreateTableSQL(tableModel));
} else {
if (DBUtility.isTableExists(tableModel.getTableName(), db)) {
return null;
} else {
sqls.add(generateCreateTableSQL(tableModel));
}
}
return sqls;
}
先根据规则,用tableModel生成sql。
/**
* Use the parameter SQLiteDatabase to execute the passing SQLs. Subclasses
* can add their own logic when do the executing job by overriding this
* method.
*
* @param sqls
* SQLs that want to execute.
* @param db
* instance of SQLiteDatabase
*
* @throws org.litepal.exceptions.DatabaseGenerateException
*/
protected void execute(List 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);
}
}
通过执行sql来生成数据表。
生成表完成之后,需要加入表的关联,后续分析。
upgrade数据库的暂时不做分析。
SaveHandler分析
所有的操作的Handler都是继承自DataHandler的。我们首先来分析SaveHandler。
真正的保存操作是调用的SaveHandler的onSave方法。
/**
* The open interface for other classes in CRUD package to save a model. It
* is called when a model class calls the save method. First of all, the
* passed in baseObj will be saved into database. Then LitePal will analyze
* the associations. If there're associated models detected, each associated
* model which is persisted will build association with current model in
* database.
*
* @param baseObj
* Current model to persist.
* @throws java.lang.reflect.InvocationTargetException
* @throws IllegalAccessException
* @throws NoSuchMethodException
* @throws IllegalArgumentException
* @throws SecurityException
*/
void onSave(DataSupport baseObj) throws SecurityException, IllegalArgumentException,
NoSuchMethodException, IllegalAccessException, InvocationTargetException {
String className = baseObj.getClassName();
List supportedFields = getSupportedFields(className);
List supportedGenericFields = getSupportedGenericFields(className);
Collection 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);
}
}
调用了getSupportedFields,getSupportedGenericFields,getAssociationInfo。这三个方法全部在LitpalBase里边。
/**
* Find all the fields in the class. But not each field is supported to add
* a column to the table. Only the basic data types and String are
* supported. This method will intercept all the types which are not
* supported and return a new list of supported fields.
*
* @param className
* The full name of the class.
* @return A list of supported fields.
*/
protected List getSupportedFields(String className) {
List fieldList = classFieldsMap.get(className);
if (fieldList == null) {
List supportedFields = new ArrayList();
Class> clazz;
try {
clazz = Class.forName(className);
} catch (ClassNotFoundException e) {
throw new DatabaseGenerateException(DatabaseGenerateException.CLASS_NOT_FOUND + className);
}
recursiveSupportedFields(clazz, supportedFields);
classFieldsMap.put(className, supportedFields);
return supportedFields;
}
return fieldList;
}
通过一个map进行了一次缓存。真正的操作是recursiveSupportedFields。
private void recursiveSupportedFields(Class> clazz, List supportedFields) {
if (clazz == DataSupport.class || clazz == Object.class) {
return;
}
Field[] fields = clazz.getDeclaredFields();
if (fields != null && fields.length > 0) {
for (Field field : fields) {
Column annotation = field.getAnnotation(Column.class);
if (annotation != null && annotation.ignore()) {
continue;
}
int modifiers = field.getModifiers();
if (!Modifier.isStatic(modifiers)) {
Class> fieldTypeClass = field.getType();
String fieldType = fieldTypeClass.getName();
if (BaseUtility.isFieldTypeSupported(fieldType)) {
supportedFields.add(field);
}
}
}
}
recursiveSupportedFields(clazz.getSuperclass(), supportedFields);
}
递归调用,直到不是DataSupport为止,通过反射取得所有的fields,当不需要忽略时,不是静态,一些基本支持的数据类型时会直接加入到这个fieldList里边。
/**
* Find all supported generic fields in the class. Supporting rule is in {@link BaseUtility#isGenericTypeSupported(String)}.
* @param className
* The full name of the class.
* @return A list of supported generic fields.
*/
protected List getSupportedGenericFields(String className) {
List genericFieldList = classGenericFieldsMap.get(className);
if (genericFieldList == null) {
List supportedGenericFields = new ArrayList();
Class> clazz;
try {
clazz = Class.forName(className);
} catch (ClassNotFoundException e) {
throw new DatabaseGenerateException(DatabaseGenerateException.CLASS_NOT_FOUND + className);
}
recursiveSupportedGenericFields(clazz, supportedGenericFields);
classGenericFieldsMap.put(className, supportedGenericFields);
return supportedGenericFields;
}
return genericFieldList;
}
private void recursiveSupportedGenericFields(Class> clazz, List supportedGenericFields) {
if (clazz == DataSupport.class || clazz == Object.class) {
return;
}
Field[] fields = clazz.getDeclaredFields();
if (fields != null && fields.length > 0) {
for (Field field : fields) {
Column annotation = field.getAnnotation(Column.class);
if (annotation != null && annotation.ignore()) {
continue;
}
int modifiers = field.getModifiers();
if (!Modifier.isStatic(modifiers) && isCollection(field.getType())) {
String genericTypeName = getGenericTypeName(field);
if (BaseUtility.isGenericTypeSupported(genericTypeName)) {
supportedGenericFields.add(field);
}
}
}
}
recursiveSupportedGenericFields(clazz.getSuperclass(), supportedGenericFields);
}
跟上一个类似,唯一的差别就是这个是处理泛型是基本数据的集合类型的field。
/**
* Get the association info model by the class name.
*
* @param className
* The class name to introspection.
* @return Collection of association info.
*/
protected Collection getAssociationInfo(String className) {
if (mAssociationInfos == null) {
mAssociationInfos = new HashSet();
}
mAssociationInfos.clear();
analyzeClassFields(className, GET_ASSOCIATION_INFO_ACTION);
return mAssociationInfos;
}
/**
* Introspection of the passed in class. Analyze the fields of current class
* and find out the associations of it.
*
* @param className
* The class name to introspection.
* @param action
* Between {@link org.litepal.LitePalBase#GET_ASSOCIATIONS_ACTION} and
* {@link org.litepal.LitePalBase#GET_ASSOCIATION_INFO_ACTION}
*/
private void analyzeClassFields(String className, int action) {
try {
Class> dynamicClass = Class.forName(className);
Field[] fields = dynamicClass.getDeclaredFields();
for (Field field : fields) {
if (isNonPrimitive(field)) {
Column annotation = field.getAnnotation(Column.class);
if (annotation != null && annotation.ignore()) {
continue;
}
oneToAnyConditions(className, field, action);
manyToAnyConditions(className, field, action);
}
}
} catch (ClassNotFoundException ex) {
ex.printStackTrace();
throw new DatabaseGenerateException(DatabaseGenerateException.CLASS_NOT_FOUND + className);
}
}
这个是用来处理引用类型的field。其实就是表之间的引用。又分为一对多和多对多。
/**
* Deals with one to any association conditions. e.g. Song and Album. An
* album have many songs, and a song belongs to one album. So if there's an
* Album model defined in Song with private modifier, and in Album there's a
* List or Set with generic type of Song and declared as private modifier,
* they are one2many association. If there's no List or Set defined in
* Album, they will become one2one associations. If there's also a Song
* model defined in Album with private modifier, maybe the album just have
* one song, they are one2one association too.
*
* When it's many2one association, it's easy to just simply add a foreign id
* column to the many side model's table. But when it comes to many2many
* association, it can not be done without intermediate join table in
* database. LitePal assumes that this join table's name is the
* concatenation of the two target table names in alphabetical order.
*
* @param className
* Source class name.
* @param field
* A field of source class.
* @param action
* Between {@link org.litepal.LitePalBase#GET_ASSOCIATIONS_ACTION} and
* {@link org.litepal.LitePalBase#GET_ASSOCIATION_INFO_ACTION}
*
* @throws ClassNotFoundException
*/
private void oneToAnyConditions(String className, Field field, int action) throws ClassNotFoundException {
Class> fieldTypeClass = field.getType();
// If the mapping list contains the class name
// defined in one class.
if (LitePalAttr.getInstance().getClassNames().contains(fieldTypeClass.getName())) {
Class> reverseDynamicClass = Class.forName(fieldTypeClass.getName());
Field[] reverseFields = reverseDynamicClass.getDeclaredFields();
// Look up if there's a reverse association
// definition in the reverse class.
boolean reverseAssociations = false;
// Begin to check the fields of the defined
// class.
for (int i = 0; i < reverseFields.length; i++) {
Field reverseField = reverseFields[i];
if (!Modifier.isStatic(reverseField.getModifiers())) {
Class> reverseFieldTypeClass = reverseField.getType();
// If there's the from class name in the
// defined class, they are one2one bidirectional
// associations.
if (className.equals(reverseFieldTypeClass.getName())) {
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, reverseField, Const.Model.ONE_TO_ONE);
}
reverseAssociations = true;
}
// If there's the from class Set or List in
// the defined class, they are many2one bidirectional
// associations.
else if (isCollection(reverseFieldTypeClass)) {
String genericTypeName = getGenericTypeName(reverseField);
if (className.equals(genericTypeName)) {
if (action == GET_ASSOCIATIONS_ACTION) {
addIntoAssociationModelCollection(className, fieldTypeClass.getName(),
className, Const.Model.MANY_TO_ONE);
} else if (action == GET_ASSOCIATION_INFO_ACTION) {
addIntoAssociationInfoCollection(className, fieldTypeClass.getName(),
className, field, reverseField, Const.Model.MANY_TO_ONE);
}
reverseAssociations = true;
}
}
}
}
// If there's no from class in the defined class, they are
// one2one unidirectional associations.
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);
}
}
}
}
这里我们定义原来的类型为A,A里边有一个引用类型为B的字段。
首先判断B是否在配置文件里边配置的。如果是,就找到这个类的所有字段。找到一个和A相同的字段,这代表A里边有一个B,B里边有一个A,是一对一关系。如果B找到的和A相同的字段是一个集合,代表A里边有一个B,B里边有多个A,是多对一关系。
最后统一把关系加入到一个集合里边来管理。
/**
* Package a {@link org.litepal.tablemanager.model.AssociationsModel}, and add it into
* {@link #mAssociationModels} Collection.
*
* @param className
* The class name for {@link org.litepal.tablemanager.model.AssociationsModel}.
* @param associatedClassName
* The associated class name for {@link org.litepal.tablemanager.model.AssociationsModel}.
* @param classHoldsForeignKey
* The class which holds foreign key.
* @param associationType
* The association type for {@link org.litepal.tablemanager.model.AssociationsModel}.
*/
private void addIntoAssociationModelCollection(String className, String associatedClassName,
String classHoldsForeignKey, int associationType) {
AssociationsModel associationModel = new AssociationsModel();
associationModel.setTableName(DBUtility.getTableNameByClassName(className));
associationModel.setAssociatedTableName(DBUtility.getTableNameByClassName(associatedClassName));
associationModel.setTableHoldsForeignKey(DBUtility.getTableNameByClassName(classHoldsForeignKey));
associationModel.setAssociationType(associationType);
mAssociationModels.add(associationModel);
}
/**
* Deals with one to any association conditions. e.g. Song and Album. An
* album have many songs, and a song belongs to one album. So if there's an
* Album model defined in Song with private modifier, and in Album there's a
* List or Set with generic type of Song and declared as private modifier,
* they are one2many association. If there's no List or Set defined in
* Album, they will become one2one associations. If there's also a Song
* model defined in Album with private modifier, maybe the album just have
* one song, they are one2one association too.
*
* When it's many2one association, it's easy to just simply add a foreign id
* column to the many side model's table. But when it comes to many2many
* association, it can not be done without intermediate join table in
* database. LitePal assumes that this join table's name is the
* concatenation of the two target table names in alphabetical order.
*
* @param className
* Source class name.
* @param field
* A field of source class.
* @param action
* Between {@link org.litepal.LitePalBase#GET_ASSOCIATIONS_ACTION} and
* {@link org.litepal.LitePalBase#GET_ASSOCIATION_INFO_ACTION}
*
* @throws ClassNotFoundException
*/
private void manyToAnyConditions(String className, Field field, int action) throws ClassNotFoundException {
if (isCollection(field.getType())) {
String genericTypeName = getGenericTypeName(field);
// If the mapping list contains the genericTypeName, begin to check
// this genericTypeName class.
if (LitePalAttr.getInstance().getClassNames().contains(genericTypeName)) {
Class> reverseDynamicClass = Class.forName(genericTypeName);
Field[] reverseFields = reverseDynamicClass.getDeclaredFields();
// Look up if there's a reverse association
// definition in the reverse class.
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();
// If there's a from class name defined in the reverse
// class, they are many2one bidirectional
// associations.
if (className.equals(reverseFieldTypeClass.getName())) {
if (action == GET_ASSOCIATIONS_ACTION) {
addIntoAssociationModelCollection(className, genericTypeName,
genericTypeName, Const.Model.MANY_TO_ONE);
} else if (action == GET_ASSOCIATION_INFO_ACTION) {
addIntoAssociationInfoCollection(className, genericTypeName, genericTypeName,
field, reverseField, Const.Model.MANY_TO_ONE);
}
reverseAssociations = true;
}
// If there's a List or Set contains from class name
// defined in the reverse class, they are many2many
// association.
else if (isCollection(reverseFieldTypeClass)) {
String reverseGenericTypeName = getGenericTypeName(reverseField);
if (className.equals(reverseGenericTypeName)) {
if (action == GET_ASSOCIATIONS_ACTION) {
addIntoAssociationModelCollection(className, genericTypeName, null,
Const.Model.MANY_TO_MANY);
} else if (action == GET_ASSOCIATION_INFO_ACTION) {
addIntoAssociationInfoCollection(className, genericTypeName, null, field,
reverseField, Const.Model.MANY_TO_MANY);
}
reverseAssociations = true;
}
}
}
}
// If there's no from class in the defined class, they
// are many2one unidirectional associations.
if (!reverseAssociations) {
if (action == GET_ASSOCIATIONS_ACTION) {
addIntoAssociationModelCollection(className, genericTypeName,
genericTypeName, Const.Model.MANY_TO_ONE);
} else if (action == GET_ASSOCIATION_INFO_ACTION) {
addIntoAssociationInfoCollection(className, genericTypeName, genericTypeName,
field, null, Const.Model.MANY_TO_ONE);
}
}
} else if(BaseUtility.isGenericTypeSupported(genericTypeName) && action == GET_ASSOCIATIONS_ACTION) {
Column annotation = field.getAnnotation(Column.class);
if (annotation != null && annotation.ignore()) {
return;
}
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);
}
}
}
和前边的类似,用来处理多对多和多对一的关系。
这里可能会有人问,这两个方法都调用了,会不会重复。结论是不会的。如果是A里边的B不是集合类型,第二个不走,肯定没有问题。如果B是一个集合类型,走了第一个方法,但是不会有任何的成功匹配。
我们回到onSave方法。
对所有的字段进行分析后,首先判断这个对象是否已经存在,如果存在进行更新,如果不存在进行保存。
操作的时候要同时修改关联表的数据。有关修改关联数据表的部分暂时不再分析。最重要的操作就是doSaveAction。
/**
* Persisting model class into database happens here. But first
* {@link #beforeSave(org.litepal.crud.DataSupport, java.util.List, android.content.ContentValues)} will be called to
* put the values for ContentValues. When the model is saved,
* {@link #afterSave(org.litepal.crud.DataSupport, java.util.List, java.util.List, long)} will be called to do stuffs
* after model is saved. Note that SaveSupport won't help with id. Any
* developer who wants to set value to id will be ignored here. The value of
* id will be generated by SQLite automatically.
*
* @param baseObj
* Current model to persist.
* @param supportedFields
* List of all supported fields.
* @param supportedGenericFields
* List of all supported generic fields.
* @throws java.lang.reflect.InvocationTargetException
* @throws IllegalAccessException
* @throws NoSuchMethodException
* @throws IllegalArgumentException
* @throws SecurityException
*/
private void doSaveAction(DataSupport baseObj, List supportedFields, List supportedGenericFields)
throws SecurityException, IllegalArgumentException, NoSuchMethodException,
IllegalAccessException, InvocationTargetException {
values.clear();
beforeSave(baseObj, supportedFields, values);
long id = saving(baseObj, values);
afterSave(baseObj, supportedFields, supportedGenericFields, id);
}
/**
* Before the self model is saved, it will be analyzed first. Put all the
* data contained by the model into ContentValues, including the fields
* value and foreign key value.
*
* @param baseObj
* Current model to persist.
* @param supportedFields
* List of all supported fields.
* @param values
* To store data of current model for persisting.
* @throws java.lang.reflect.InvocationTargetException
* @throws IllegalAccessException
* @throws NoSuchMethodException
* @throws IllegalArgumentException
* @throws SecurityException
*/
private void beforeSave(DataSupport baseObj, List supportedFields, ContentValues values)
throws SecurityException, IllegalArgumentException, NoSuchMethodException,
IllegalAccessException, InvocationTargetException {
putFieldsValue(baseObj, supportedFields, values);
if (!ignoreAssociations) {
putForeignKeyValue(values, baseObj);
}
}
/**
* Iterate all the fields passed in. Each field calls
* {@link #putFieldsValueDependsOnSaveOrUpdate(DataSupport, java.lang.reflect.Field, android.content.ContentValues)}
* if it's not id field.
*
* @param baseObj
* Current model to persist or update.
* @param supportedFields
* List of all supported fields.
* @param values
* To store data of current model for persisting or updating.
* @throws java.lang.reflect.InvocationTargetException
* @throws IllegalAccessException
* @throws NoSuchMethodException
* @throws IllegalArgumentException
* @throws SecurityException
*/
protected void putFieldsValue(DataSupport baseObj, List supportedFields,
ContentValues values) throws SecurityException, IllegalArgumentException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {
for (Field field : supportedFields) {
if (!isIdColumn(field.getName())) {
putFieldsValueDependsOnSaveOrUpdate(baseObj, field, values);
}
}
}
在saving之前,先把所有的属性值复制到values里边。注意最后一个方法,isIdColumn方法,查看源码:
/**
* Judge the passed in column is an id column or not. The column named id or
* _id will be considered as id column.
*
* @param columnName
* The name of column.
* @return Return true if it's id column, otherwise return false.
*/
protected boolean isIdColumn(String columnName) {
return "_id".equalsIgnoreCase(columnName) || "id".equalsIgnoreCase(columnName);
}
这里表明id相当于litepal的保留关键字,我们只可以使用,不可以赋值。
/**
* This method deals with the putting values job into ContentValues. The
* ContentValues has put method to set data. But we do not know we
* should use which put method cause the field type isn't clear. So
* the reflection API is necessary here to put values into ContentValues
* with dynamically getting field type to put value.
*
* @param baseObj
* The class of base object.
* @param field
* Field to put into ContentValues.
* @param values
* To store data of current model for persisting or updating.
* @throws SecurityException
* @throws NoSuchMethodException
* @throws IllegalArgumentException
* @throws IllegalAccessException
* @throws java.lang.reflect.InvocationTargetException
*/
protected void putContentValuesForSave(DataSupport baseObj, Field field, ContentValues values)
throws SecurityException, IllegalArgumentException, NoSuchMethodException,
IllegalAccessException, InvocationTargetException {
Object fieldValue = DynamicExecutor.getField(baseObj, field.getName(), baseObj.getClass());
if (fieldValue != null) {
// put content value only when value is not null. this allows to use defaultValue declared in annotation.
if ("java.util.Date".equals(field.getType().getName())) {
Date date = (Date) fieldValue;
fieldValue = date.getTime();
}
Encrypt annotation = field.getAnnotation(Encrypt.class);
if (annotation != null && "java.lang.String".equals(field.getType().getName())) {
fieldValue = encryptValue(annotation.algorithm(), fieldValue);
}
Object[] parameters = new Object[] { changeCase(DBUtility.convertToValidColumnName(field.getName())), fieldValue };
Class>[] parameterTypes = getParameterTypes(field, fieldValue, parameters);
DynamicExecutor.send(values, "put", parameters, values.getClass(), parameterTypes);
}
}
/**
* This method use java reflect API to get field value dynamically. Most
* importantly, it could access fields with private modifier to break
* encapsulation.
*
* @param object
* The object to access.
* @param fieldName
* The field name to access.
* @param objectClass
* The class of object.
* @throws SecurityException
* @throws IllegalArgumentException
* @throws IllegalAccessException
*/
static Object getField(Object object, String fieldName, Class> objectClass)
throws IllegalArgumentException, IllegalAccessException {
if (objectClass == DataSupport.class || objectClass == Object.class) {
throw new DataSupportException(DataSupportException.noSuchFieldExceptioin(
objectClass.getSimpleName(), fieldName));
}
try {
Field objectField = objectClass.getDeclaredField(fieldName);
objectField.setAccessible(true);
return objectField.get(object);
} catch (NoSuchFieldException e) {
return getField(object, fieldName, objectClass.getSuperclass());
}
}
/**
* This method use java reflect API to execute method dynamically. Most
* importantly, it could access the methods with private modifier to break
* encapsulation.
*
* @param object
* The object to invoke method.
* @param methodName
* The method name to invoke.
* @param parameters
* The parameters.
* @param objectClass
* Use objectClass to find method to invoke.
* @param parameterTypes
* The parameter types.
* @return Returns the result of dynamically invoking method.
* @throws SecurityException
* @throws IllegalArgumentException
* @throws IllegalAccessException
* @throws java.lang.reflect.InvocationTargetException
*/
static Object send(Object object, String methodName, Object[] parameters, Class> objectClass,
Class>[] parameterTypes) throws SecurityException, IllegalArgumentException,
IllegalAccessException, InvocationTargetException {
try {
if (parameters == null) {
parameters = new Object[] {};
}
if (parameterTypes == null) {
parameterTypes = new Class[] {};
}
Method method = objectClass.getDeclaredMethod(methodName, parameterTypes);
method.setAccessible(true);
return method.invoke(object, parameters);
} catch (NoSuchMethodException e) {
throw new DataSupportException(DataSupportException.noSuchMethodException(
objectClass.getSimpleName(), methodName), e);
}
}
通过反射取得field的值,然后再通过反射放到values里边。
最后saving方法
/**
* Calling {@link android.database.sqlite.SQLiteDatabase#insert(String, String, android.content.ContentValues)} to
* persist the current model.
*
* @param baseObj
* Current model to persist.
* @param values
* To store data of current model for persisting.
* @return The row ID of the newly inserted row, or -1 if an error occurred.
*/
private long saving(DataSupport baseObj, ContentValues values) {
if (values.size() == 0) {
values.putNull("id");
}
return mDatabase.insert(baseObj.getTableName(), null, values);
}
调用的SQLiteDatabase 的insert方法。
/**
* After the model is saved, do the extra work that need to do.
*
* @param baseObj
* Current model that is persisted.
* @param supportedFields
* List of all supported fields.
* @param supportedGenericFields
* List of all supported generic fields.
* @param id
* The current model's id.
*/
private void afterSave(DataSupport baseObj, List supportedFields,
List supportedGenericFields, long id) throws IllegalAccessException, InvocationTargetException {
throwIfSaveFailed(id);
assignIdValue(baseObj, getIdField(supportedFields), id);
updateGenericTables(baseObj, supportedGenericFields, id);
if (!ignoreAssociations) {
updateAssociatedTableWithFK(baseObj);
insertIntermediateJoinTableValue(baseObj, false);
}
}
AfterSave首先检查是否插入成功。然后刚才我们说过,id属性是litepal的保留字,所以对其进行赋值。
然后对关联的表进行处理。
总结
因为各种原因,后边的分析可能比较简单,并且只分析了save方法部分。但我们不难发现litepal主要是一个通过反射机制来实现的ORM框架。支持外部数据库,支持表的关联,支持多数据库,支持自动管理表等强大的功能。