数据库ORM之LitePal

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对象,然后返回一个数据库对象。

数据库ORM之LitePal_第1张图片

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分析

数据库ORM之LitePal_第2张图片

所有的操作的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框架。支持外部数据库,支持表的关联,支持多数据库,支持自动管理表等强大的功能。

你可能感兴趣的:(数据库ORM之LitePal)