LitePal源码解析——数据库的创建

从上一篇文章中,我们了解了LitePal这个开源项目的基本结构,以及框架的特点,并对部分关键类有了一定的了解。这为我们接下来更好阅读LitePal源码做好准备,让我们可以更轻松的去理解各个功能的实现流程。如果没有看过这篇文章,可以去参考一下LitePal源码解析——框架结构及关键类说明

其实在今年7月之前的一年多时间里,我基本上是没怎么接触Android开发,大部分时间都用在小组工作协调、跟进产品需求文档的编写以及杂七杂八的撕扯。最近这段时间,新来了一批实习生,有几个想要学习Android,而我这段时间正好不忙,我就想自己来带一下他们,因此,就开始拾起放了很长一段时间的Android开发知识。花了差不多两周时间,快速看完了郭神的《Android第一行代码2》,这本书属于入门书籍,书中的内容都是Android很基础的知识,整个看下来并没费很大的劲,对于书中的demo,我也只是coding了其中一部分。在看SQLite数据库操作章节,作者讲解了其自己维护的开源框架LitePal。在使用LitePal时,其极度简洁的特性(对于CRUD操作几乎只需要一行代码就可以搞定)勾起了我对这个框架的兴趣,于是便决定深入到源码,看看郭神是如何实现的。

由于从Android6.0之后的版本,我已经没有再仔细去了解过,甚至都不知道谷歌都做了哪些更新了,因此,这次看源码主要有两个目的,一是了解LitePal的实现,二是借此找回阅读源码的感觉,为之后看Android系统源码做准备。由于脱离Android开发已有很长时间,所以在对一些知识点的理解上可能会出错,还望帮忙指正。好了废话不多说,开始进入正题,来看看LitePal框架是如何实现的。
在正式上源码之前,先贴出LitePal创建数据库及数据表的整体流程图。
LitePal源码解析——数据库的创建_第1张图片
我们知道在项目中,当要使用LitePal来操作管理数据库时,只需先调用LitePal的getDatabase()方法获得SQLiteDatabase实例,之后就可以进行数据库的CRUD操作了。

public static SQLiteDatabase getDatabase() {
    return Connector.getDatabase();
}

getDatabase()中只有一行代码,即调用了Connector.getDatabase()方法,直接返回一个SQLiteDatabase 实例。我们继续往下看,进入Connector类的getDatabase()内部。

public static SQLiteDatabase getDatabase() {
    return getWritableDatabase();
}

public synchronized static SQLiteDatabase getWritableDatabase() {
    LitePalOpenHelper litePalHelper = buildConnection();
    return litePalHelper.getWritableDatabase();
}

我们看到getDatabase()其实也只是一个包装方法,调用了getWritableDatabase()。这两个方法总共就三行代码,但是这里涉及到一个很关键的类,也就是LitePalOpenHelper类。

package org.litepal.tablemanager;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.database.sqlite.SQLiteOpenHelper;

import org.litepal.LitePalApplication;
import org.litepal.parser.LitePalAttr;
import org.litepal.util.SharedUtil;

class LitePalOpenHelper extends SQLiteOpenHelper {
    public static final String TAG = "LitePalHelper";

    LitePalOpenHelper(Context context, String name, CursorFactory factory, int version) {
        super(context, name, factory, version);
    }

    LitePalOpenHelper(String dbName, int version) {
        this(LitePalApplication.getContext(), dbName, null, version);
    }

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

从代码中,我们可以看到LitePalOpenHelper其实是继承于Android原生提供的SQLiteOpenHelper帮助类的,并实现了父类中的两个抽象方法。关于SQLiteOpenHelper的onCreate()和onUpgrade()方法的调用,我们就不深入去了解了,这里只是进行简单的说明。
在调用getWritableDatabase()或者getReadableDatabase()后,SQLiteOpenHelper会自动检测数据库文件是否存在。如果存在,会打开这个数据库,在这种情况下就不会调用onCreate()方法。如果数据库文件不存在,SQLiteOpenHelper首先会创建一个数据库文件,然后打开这个数据库,最后调用onCreate()方法。因此,onCreate()方法一般用来在新创建的数据库中建立表、视图等数据库组件。也就是说onCreate()方法在数据库文件第一次创建时调用。

对于onUpgrade()方法的调用,我们要结合SQLiteOpenHelper的构造方法来看。public SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version)构造方法有4个参数,其中name参数表示数据库文件名(不包括文件路径),SQLiteOpenHelper会根据这个文件名创建数据库文件。version表示数据库的版本号。如果当前传入的数据库版本号比上次创建或升级的版本号高,SQLiteOpenHelper就会调用onUpdate()方法。也就是说,当数据库第一次创建时会有一个初始的版本号。当需要对数据库中的表、视图等组建升级时可以增大版本号,再重新创建它们。

正如常见的自定义帮助类,LitePalOpenHelper也是在onCreate()方法中执行了数据表的创建工作,在onUpgrade()方法中进行升级操作。在探索数据表的创建之前,我们先回到Connector类中,看看buildConnection()是如何返回LitePalOpenHelper 对象的。

解析及保存配置信息

//org.litepal.tablemanager.Connector#buildConnection
private static LitePalOpenHelper buildConnection() {
    // 解析配置文件,并保存在LitePalAttr的单例对象
    LitePalAttr litePalAttr = LitePalAttr.getInstance();
    // 检查litePalAttr的有效性
    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("//", "/");
             &&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.getInstance()获取LitePalAttr的实例。LitePalAttr对象中保存了litepal.xml中配置的信息,因此,getInstance()主要就是一个解析并存储数据的过程。

//org.litepal.parser.LitePalAttr#getInstance
public static LitePalAttr getInstance() {
    if (litePalAttr == null) {
        synchronized (LitePalAttr.class) {
            if (litePalAttr == null) {
                litePalAttr = new LitePalAttr();
                loadLitePalXMLConfiguration();
            }
        }
    }
    return litePalAttr;
}

//org.litepal.parser.LitePalAttr#loadLitePalXMLConfiguration
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());
   }
}

这里使用单例模式,所以意味着应用程序启动之后,只会去做一次文件解析。在解析之前,先检查配置文件是否存在,然后调用LitePalParser.parseLitePalConfiguration()正式解析配置文件,并在解析完成之后将数据保存到litePalAttr中。

//org.litepal.parser.LitePalParser#usePullParse
private LitePalConfig usePullParse() {
    try {
        LitePalConfig litePalConfig = new LitePalConfig();
        XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
        XmlPullParser xmlPullParser = factory.newPullParser();
        xmlPullParser.setInput(getConfigInputStream(), "UTF-8");
        int eventType = xmlPullParser.getEventType();
        while (eventType != XmlPullParser.END_DOCUMENT) {
            String nodeName = xmlPullParser.getName();
            switch (eventType) {
            case XmlPullParser.START_TAG: {
                if (NODE_DB_NAME.equals(nodeName)) {
                    String dbName = xmlPullParser.getAttributeValue("", ATTR_VALUE);
                    litePalConfig.setDbName(dbName);
                } else if (NODE_VERSION.equals(nodeName)) {
                    String version = xmlPullParser.getAttributeValue("", ATTR_VALUE);
                    litePalConfig.setVersion(Integer.parseInt(version));
                } else if (NODE_MAPPING.equals(nodeName)) {
                    String className = xmlPullParser.getAttributeValue("", ATTR_CLASS);
                    litePalConfig.addClassName(className);
                } else if (NODE_CASES.equals(nodeName)) {
                    String cases = xmlPullParser.getAttributeValue("", ATTR_VALUE);
                    litePalConfig.setCases(cases);
                } else if (NODE_STORAGE.equals(nodeName)) {
                    String storage = xmlPullParser.getAttributeValue("", ATTR_VALUE);
                    litePalConfig.setStorage(storage);
                }
                break;
            }
            default:
                break;
            }
            eventType = xmlPullParser.next();
        }
        return litePalConfig;
    } catch (XmlPullParserException e) {
        throw new ParseConfigurationFileException(
                ParseConfigurationFileException.FILE_FORMAT_IS_NOT_CORRECT);
    } catch (IOException e) {
        throw new ParseConfigurationFileException(ParseConfigurationFileException.IO_EXCEPTION);
    }
}

在parseLitePalConfiguration()方法中调用了usePullParse(),使用pull解析器进行文件的解析,将解析出的文件信息保存到LitePalConfig实例中,并最终返回。

创建并连接数据库

解析出数据库名和版本信息之后,接下来创建数据库文件,将LitePalOpenHelper实例进行实例化的, 并返回LitePalOpenHelper实例,这其实是一个与数据库文件进行连接的过程。拥有LitePalOpenHelper的实例化对象之后,直接调用getWritableDatabase()方法,之后就是系统原生创建数据库的流程了。

创建数据表

关于数据库创建,我们已经有所了解,接下来我们需要关心的是如何创建数据表。在前面我们已经有提到过,数据表的创建将会在SQLiteOpenHelper的onCreate()方法中进行,在此方法中调用了Generator.create(db)。

//org.litepal.tablemanager.Generator#create(android.database.sqlite.SQLiteDatabase)
static void create(SQLiteDatabase db) {
    // 创建基于在litepal.xml文件中定义的类模型表。在创建表之后,基于类模型之间的关联将这些关联添加到这些表中。
    create(db, true);
    //基于类模型之间的关联向所有表添加关联。
    addAssociation(db, true);
}

//org.litepal.tablemanager.Generator#create(android.database.sqlite.SQLiteDatabase, boolean)
private static void create(SQLiteDatabase db, boolean force) {
    Creator creator = new Creator();
    // 分析表模型,基于表模型的值在数据库中创建一个表。
    creator.createOrUpgradeTable(db, force);
}

//org.litepal.tablemanager.Generator#addAssociation
private static void addAssociation(SQLiteDatabase db, boolean force) {
    AssociationCreator associationsCreator = new Creator();
    associationsCreator.addOrUpdateAssociation(db, force);
}

在create方法中调用了Generator自身的两个方法,其主要做了两件事:
1. 解析模型类,并创建模型类对应的类模型表;
2. 分析模型类的关联关系,并将关联关系添加到类模型表中。

解析模型类

我们先来看看解析模型类的过程,进入到Creator的createOrUpgradeTable()看看都做了什么。

//org.litepal.tablemanager.Creator#createOrUpgradeTable(android.database.sqlite.SQLiteDatabase, boolean)
protected void createOrUpgradeTable(SQLiteDatabase db, boolean force) {
    for (TableModel tableModel : getAllTableModels()) {
        createOrUpgradeTable(tableModel, db, force);
    }
}

createOrUpgradeTable()方法的代码量也很少,就是一个for循环,通过遍历getAllTableModels()方法返回的数据集,获得每一个模型类对应的表模型,并调用createOrUpgradeTable()方法创建数据表。很显然,对于模型类的解析是在getAllTableModels()中进行的。

//这是获取映射表中定义的每个模型类的所有表模型的快捷方式。不需要迭代所有的模型类,并为每个模型获取表模型。
//org.litepal.tablemanager.Generator#getAllTableModels
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;
}

方法返回一个包装了TableModel对象的集合,这里之所以使用一个成员变量来维护,目的是为了避免重复解析。第二个if的判断条件中调用了canUseCache()方法,判断是否可以使用缓存,该方法判断了配置文件中配置的模型类是否有变化,如果不变且有缓存的解析结果,则直接使用缓存,否则重新解析。for循环遍历了LitePalAttr中保存的模型类类名,获取完整类名,并调用getTableModel(),传入类名,执行解析。

//org.litepal.LitePalBase#getTableModel
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;
}

该方法的第一行代码就得到了表名,DBUtility.getTableNameByClassName()方法的内部其实就是对字符串的操作,传入完整类名,调用字符串的substring()方法,截取简单类名。在往下看会涉及到两个很关键的实体类,分别是TableModel和ColumnModel实体,我们来看看都有些什么属性。

//org.litepal.tablemanager.model.TableModel实体: 
public class TableModel {

    /**
     * Table name.
     */
    private String tableName;

    /**
     * A list contains all column models with column name, type and constraints.
     */
    private List columnModels = new ArrayList();

    /**
     * Class name for the table name. This value might be null. Don't rely on it.
     */
    private String className;

    /**
     * Get table name.
     * 
     * @return Name of table.
     */
    public String getTableName() {
        return tableName;
    }

    /**
     * Set table name.
     * 
     * @param tableName
     *            Name of table.
     */
    public void setTableName(String tableName) {
        this.tableName = tableName;
    }

    /**
     * Get class name.
     * 
     * @return Return the class name or null.
     */
    public String getClassName() {
        return className;
    }

    /**
     * Set class name.
     * 
     * @param className
     *            The class name.
     */
    public void setClassName(String className) {
        this.className = className;
    }

    /**
     * Add a column model into the table model.
     *
     * @param columnModel
     *            A column model contains name, type and constraints.
     */
    public void addColumnModel(ColumnModel columnModel) {
        columnModels.add(columnModel);
    }

    /**
     * Find all the column models of the current table model.
     * @return A list contains all column models.
     */
    public List getColumnModels() {
        return columnModels;
    }

    /**
     * Find the ColumnModel which can map the column name passed in.
     * @param columnName
     *          Name of column.
     * @return A ColumnModel which can map the column name passed in. Or null.
     */
    public ColumnModel getColumnModelByName(String columnName) {
        for (ColumnModel columnModel : columnModels) {
            if (columnModel.getColumnName().equalsIgnoreCase(columnName)) {
                return columnModel;
            }
        }
        return null;
    }

    /**
     * Remove a column model by the specified column name.
     * @param columnName
     *          Name of the column to remove.
     */
    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);
        }
    }

    /**
     * Judge the table model has such a column or not.
     * @param columnName
     *          The name of column to check.
     * @return True if matches a column in the table model. False otherwise.
     */
    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;
    }
}

类中有三个属性,分别为表名、列模型集和模型类的完整名称。

//org.litepal.tablemanager.model.ColumnModel实体
public class ColumnModel {

    /**
     * Name of column.
     */
    private String columnName;

    /**
     * Type for column.
     */
    private String columnType;

    /**
     * Nullable constraint.
     */
    private boolean isNullable = true;

    /**
     * Unique constraint.
     */
    private boolean isUnique = false;

    /**
     * Default constraint.
     */
    private String defaultValue = "";
    /** 省略get、set */
    /**
     * Judge current ColumnModel is id column or not.
     * @return True if it's id column. False otherwise.
     */
    public boolean isIdColumn() {
        return "_id".equalsIgnoreCase(columnName) || "id".equalsIgnoreCase(columnName);
    }
}

类中三个属性分别为:列名、列类型、是否可空、唯一约束和默认值。

这两个类作为数据表和列属性保存的载体,对象中保存的数据将会直接被转换成sql语句。在getTableModel()方法的第二行代码,创建了一个TableModel实例,并进行了实例化,这个实例对象用来保存建表时的必要属性信息。第五行调用了getSupportedFields()方法,此方法通过传入的类名,获取类中可用于建表的属性。

//org.litepal.LitePalBase#getSupportedFields
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;
   }
   eturn fieldList;
}

// 递归获取符合条件的属性(只有基本数据类型的属性才支持)
//org.litepal.LitePalBase#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;
            }
           //获取属性修饰符(1-PUBLIC;2-PRIVATE;4-PROTECTED;8-STATIC;16-FINAL;32-SYNCHRONIZED;64-VOLATILE;128-TRANSIENT;256-NATIVE;512-INTERFACE;1024-ABSTRACT;2048-STRICT)
           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);
}

查找类中所有可支持建表的类型的属性。只支持基本数据类型和String类型。recursiveSupportedFields()方法将拦截不支持的所有类型,并返回一个新的支持字段列表。另外,其维护了一个以完整类名为作为键值的成员变量classFieldsMap,用于避免重复遍历。

在org.litepal.LitePalBase#getTableModel方法的for循环中遍历所支持的属性列表,并封装到表模型实体对象中。至此,通过解析模型类,保存分析结果,完成所有表模型对象的创建及赋值。接下来将进行数据表的创建。

组装sql语句

通过对上述解析得到的数据表模型映射类进行分析,组装成建表所需的sql语句,添加到建表语句列表中;然后调用系统执行sql语句的方法,即,android.database.sqlite.SQLiteDatabase#execSQL(java.lang.String)方法,执行建表语句列表中的所有sql语句。一旦创建了新表,表名将被保存到table_schema作为副本,且每个表名只保存一次。

调用org.litepal.tablemanager.Creator#createOrUpgradeTable()方法,传入三个参数,分别为表模型类对象、SQLiteDatabase对象和boolean值,布尔类型参数是用于标识当目标表已经存在时是否进行删除,传入true,则删除已存在的表,否则保留。

//使用SQLite数据库执行语句的参数传递。子类可以在执行任务时通过重写此方法来添加自己的逻辑。
//org.litepal.tablemanager.Creator#createOrUpgradeTable(android.database.sqlite.SQLiteDatabase, boolean)
protected void createOrUpgradeTable(TableModel tableModel, SQLiteDatabase db, boolean force) {
    // 通过getCreateTableSQLs()方法,得到建表所需的sql语句列表,传入execute()方法中进行执行
    execute(getCreateTableSQLs(tableModel, db, force), db);
    //一旦创建了新表。表的名字将被保存到table_schema作为副本。每个表名只保存一次。
    giveTableSchemaACopy(tableModel.getTableName(), Const.TableSchema.NORMAL_TABLE, db);
}

上述方法中调用org.litepal.tablemanager.Creator#getCreateTableSQLs()方法进行sql语句的组装装。

//org.litepal.tablemanager.Creator#getCreateTableSQLs
protected List<String> getCreateTableSQLs(TableModel tableModel, SQLiteDatabase db, boolean force) {
    List<String> sqls = new ArrayList<String>();
    if (force) {
        // 组装表删除语句
        sqls.add(generateDropTableSQL(tableModel));
        // 组装建表语句(通过传入表模型类对象,根据对数据表列模型类集的分析,组装出表创建的sql语句)
        sqls.add(generateCreateTableSQL(tableModel));
    } else {
        // 从SQLite内置表sqlite_master中查询出当前数据中所有表的索引,并通过org.litepal.util.BaseUtility.containsIgnoreCases()方法检查是否已经存在指定的表。
        if (DBUtility.isTableExists(tableModel.getTableName(), db)) {
            return null;
        } else {
            // 不存在则组装建表语句
            sqls.add(generateCreateTableSQL(tableModel));
        }
    }
    return sqls;
}

根据第三个参数,判断是否需要删除已存在的目标数据表,但凡不需要删除目标表,那么如果已存在目标数据表,那么将不会在继续拼装建表语句。关于检查目标数据表是否已经存在,是通过调用org.litepal.util.DBUtility#isTableExists()来判断的。从SQLite内置表sqlite_master中查询出当前数据中所有表的索引,并通过org.litepal.util.BaseUtility.containsIgnoreCases()方法检查是否已经存在指定的表。相关方法如下。

//封装检查方法
//org.litepal.util.DBUtility#isTableExists
public static boolean isTableExists(String tableName, SQLiteDatabase db) {
    boolean exist;
    try {
        exist = BaseUtility.containsIgnoreCases(findAllTableNames(db), tableName);
    } catch (Exception e) {
        e.printStackTrace();
        exist = false;
    }
    return exist;
}

// 判断集合中是否已存在目标表名
//org.litepal.util.BaseUtility#containsIgnoreCases
public static boolean containsIgnoreCases(Collection collection, String string) {
    if (collection == null) {
        return false;
    }
    if (string == null) {
        return collection.contains(null);
    }
    boolean contains = false;
    for (String element : collection) {
        if (string.equalsIgnoreCase(element)) {
            contains = true;
            break;
        }
    }
    return contains;
}

//查找数据库中的所有表名,即查询SQLite内置表sqlite_master中保存的表索引信息
//org.litepal.util.DBUtility#findAllTableNames
lic static List findAllTableNames(SQLiteDatabase db) {
    List tableNames = new ArrayList();
    Cursor cursor = null;
    try {
        cursor = db.rawQuery("select * from sqlite_master where type = ?", new String[] { "table" });
        if (cursor.moveToFirst()) {
            do {
                String tableName = cursor.getString(cursor.getColumnIndexOrThrow("tbl_name"));
                if (!tableNames.contains(tableName)) {
                    tableNames.add(tableName);
                }
            } while (cursor.moveToNext());
        }
    } catch (Exception e) {
        e.printStackTrace();
        throw new DatabaseGenerateException(e.getMessage());
        } finally {
        if (cursor != null) {
            cursor.close();
        }
    }
    return tableNames;
}

组装sql语句,无论是组装表删除语句亦或是创建语句,Creator类中提供的只是封装方法,真正的组装都放到子类AssociationCreator中进行。

// 组装表删除语句
//org.litepal.tablemanager.AssociationCreator#generateDropTableSQL()
protected String generateDropTableSQL(String tableName) {
    //组装表删除语句非常简单,仅仅之后一行代码
    return "drop table if exists " + tableName;
}

//组装建表语句
//org.litepal.tablemanager.AssociationCreator#generateCreateTableSQL
protected String generateCreateTableSQL(String tableName, List columnModels, boolean autoIncrementId) {
    StringBuilder createTableSQL = new StringBuilder("create table ");
    createTableSQL.append(tableName).append(" (");
    if (autoIncrementId) {
        createTableSQL.append("id integer primary key autoincrement,");
    }
    if (isContainsOnlyIdField(columnModels)) {
        // Remove the last comma when only have id field in model.
        createTableSQL.deleteCharAt(createTableSQL.length() - 1);
    }
    boolean needSeparator = false;
    for (ColumnModel columnModel : columnModels) {
        if (columnModel.isIdColumn()) {
            continue;
        }
        if (needSeparator) {
            createTableSQL.append(", ");
        }
        needSeparator = true;
        createTableSQL.append(columnModel.getColumnName()).append(" ").append(columnModel.getColumnType());
        if (!columnModel.isNullable()) {
            createTableSQL.append(" not null");
        }
        if (columnModel.isUnique()) {
            createTableSQL.append(" unique");
        }
        String defaultValue = columnModel.getDefaultValue();
        if (!TextUtils.isEmpty(defaultValue)) {
            createTableSQL.append(" default ").append(defaultValue);
        }
    }
    createTableSQL.append(")");
    LogUtil.d(TAG, "create table sql is >> " + createTableSQL);
    return createTableSQL.toString();
}

通过对表名和传入的表列模型对象集的分析,遍历列模型对象集,得到列表和相关标识信息,拼装建表语句,并返回字符串类型的建表语句。generateCreateTableSQL()方法中的第一if语句,判断是否需要自动生成主键,在创建业务表时,传入的都true,创建关联中间表时则传入false。接下来就是获取表模型对象中存储的值,拼接sql语句。至此,创建表相关的sql语句拼接完毕。

执行sql语句

通过上述的拼装过程,得到了一个sql语句列表,org.litepal.tablemanager.Generator#execute()方法中,遍历列表,并做简单的处理,然后调用系统SQLiteDatabase的execSQL()方法,真正执行sql语句。

//org.litepal.tablemanager.Generator#execute()
protected void execute(List sqls, SQLiteDatabase db) {
    String throwSQL = "";
    try {
        if (sqls != null && !sqls.isEmpty()) {
            for (String sql : sqls) {//遍历sql语句集合
                if (!TextUtils.isEmpty(sql)) {
                    // 根据litepal.xml文件cases标签所定义的案例,规范表名和列名(即,保数据表模型映射的模型类命名、或者用大小写命名)
                    throwSQL = BaseUtility.changeCase(sql);
                    db.execSQL(throwSQL);// 真正执行sql语句
                }
            }
        }
    } catch (SQLException e) {
        throw new DatabaseGenerateException(DatabaseGenerateException.SQL_ERROR + throwSQL);
    }
}

备份新建表的信息

一旦创建了新表,表的名字将被保存到table_schema作为副本,每个表名只保存一次。

//org.litepal.tablemanager.AssociationCreator#giveTableSchemaACopy
protected void giveTableSchemaACopy(String tableName, int tableType, SQLiteDatabase db) {
    StringBuilder sql = new StringBuilder("select * from ");
    sql.append(Const.TableSchema.TABLE_NAME);
    LogUtil.d(TAG, "giveTableSchemaACopy SQL is >> " + sql);
    Cursor cursor = null;
    try {
        cursor = db.rawQuery(sql.toString(), null);
        if (isNeedtoGiveACopy(cursor, tableName)) {// 一旦已经存在,将不会再进行保存操作
            ContentValues values = new ContentValues();
            values.put(Const.TableSchema.COLUMN_NAME, BaseUtility.changeCase(tableName));
            values.put(Const.TableSchema.COLUMN_TYPE, tableType);
            db.insert(Const.TableSchema.TABLE_NAME, null, values);
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        if (cursor != null) {
        cursor.close();
        }
    }
}

分析模型关联关系

在开发中,应用所要展示的数据从来都不是来源于单一数据表的,这些数据表之间是存在一定得关联关系的,那么LitePal又是如何处理这种关联的呢?这是通过对模型类的分析,并创建相应关联模型对象,将所有的关联模型对象维护在HashSet的成员变量中。根据关联模型中的关联方式,判断是在已有业务表中添加外键还是创建中间关联表。创建中间关联表的过程同上面所提到的业务表的创建,完成中间关联表的创建之后,同样对表名进行了备份。

相关模型实体

在开始分析其关联关系之前,我们先来看看几个相关的Model类,我们对于模型关联关系的分析结构都将存放在这些模型中,然后基于这些模型中存储的信息来建立表的关联。

//关联模型实体类
public class AssociationsModel {
    /**
     * Table name.
     */
    private String tableName;
    /**
     * Associated table name.
     */
    private String associatedTableName;
    /**
     * The table which holds foreign key.
     */
    private String tableHoldsForeignKey;
    /**
     * The association type, including {@link Const.Model#MANY_TO_ONE},
     * {@link Const.Model#MANY_TO_MANY}, {@link Const.Model#ONE_TO_ONE}.
     */
    private int associationType;
    /**省略get、set*/
    /**
     * Override equals method to make sure that if two associated tables in the
     * association model are same ignoring sides, they are same association
     * model.
     */
    @Override
    public boolean equals(Object o) {
        if (o instanceof AssociationsModel) {
            AssociationsModel association = (AssociationsModel) o;
            if (association.getTableName() != null && association.getAssociatedTableName() != null) {
                if (association.getAssociationType() == associationType
                        && association.getTableHoldsForeignKey().equals(tableHoldsForeignKey)) {
                    if (association.getTableName().equals(tableName)
                            && association.getAssociatedTableName().equals(associatedTableName)
                            && association.getTableHoldsForeignKey().equals(tableHoldsForeignKey)) {
                        return true;
                    } else if (association.getTableName().equals(associatedTableName)
                            && association.getAssociatedTableName().equals(tableName)
                            && association.getTableHoldsForeignKey().equals(tableHoldsForeignKey)) {
                        return true;
                    }
                }
            }
        }
        return false;
    }
}

这是表关联的模型类。它存储表名、关联表名、保存外键的表名和关联类型。关系有三种类型。one2one,many2one和many2many。如果关联类型是one2one或many2one,将会在关联表中创建一个外键列,外键列存储的数据为被关联表的主键(即id)。如果关联关系是many2many,则会创建一个中间关联表,用于维系关联关系,中间关联表的表名以两个目标表的表名按字母顺序,并以“_”连接的命名。中间表只有两列,列名分别以两个目标表名拼接“_id”,分别存放两个目标表的主键值。

//关联信息实体
public class AssociationsInfo {
    /**
     * The class name of self class.
     */
    private String selfClassName;
    /**
     * The class name of the class which associated with self class.
     */
    private String associatedClassName;
    /**
     * The class which holds foreign key.
     */
    private String classHoldsForeignKey;
    /**
     * The field of self class to declare has association with other class.
     */
    private Field associateOtherModelFromSelf;
    /**
     * The field of the associated class to declare has association with self
     * class.
     */
    private Field associateSelfFromOtherModel;
    /** 省略get、set */
    /**
     * Override equals method to make sure that if two associated classes in the
     * association info model are same ignoring sides, they are same association
     * info model.
     */
    @Override
    public boolean equals(Object o) {
        if (o instanceof AssociationsInfo) {
            AssociationsInfo other = (AssociationsInfo) o;
            if (o != null && other != null) {
                if (other.getAssociationType() == associationType
                        && other.getClassHoldsForeignKey().equals(classHoldsForeignKey)) {
                    if (other.getSelfClassName().equals(selfClassName)
                            && other.getAssociatedClassName().equals(associatedClassName)) {
                        return true;
                    }
                    if (other.getSelfClassName().equals(associatedClassName)
                            && other.getAssociatedClassName().equals(selfClassName)) {
                        return true;
                    }
                }
            }
        }
        return false;
    }
}

该模型用于分析和处理自相关模型的相关信息,主要在查询业务中使用。

public class GenericModel {
    /**
     * Table name.
     */
    private String tableName;

    /**
     * Column name for storing value.
     */
    private String valueColumnName;

    /**
     * Column type for storing value.
     */
    private String valueColumnType;

    /**
     * Column name for reference with main table.
     */
    private String valueIdColumnName;

    /**
     * Only used when query generic data. This is cache fields for improving performance.
     */
     //仅在查询泛型数据时使用。这是用于提高性能的缓存字段。
    private String getMethodName;
}

这是泛型表的模型类。它存储表名、值列名称、值列类型和值ID列名。当泛型集合字段在模型类中声明时,这个类用于创建泛型表。需要使用此类创建对象的条件是泛型对应的类型必须为基本数据类型或java.lang.String类型。检查方法如下:

//org.litepal.util.BaseUtility#isGenericTypeSupported
public static boolean isGenericTypeSupported(String genericType) {
   if ("java.lang.String".equals(genericType)) {
       return true;
   } else if ("java.lang.Integer".equals(genericType)) {
       return true;
   } else if ("java.lang.Float".equals(genericType)) {
       return true;
   } else if ("java.lang.Double".equals(genericType)) {
       return true;
   } else if ("java.lang.Long".equals(genericType)) {
       return true;
   } else if ("java.lang.Short".equals(genericType)) {
       return true;
   } else if ("java.lang.Boolean".equals(genericType)) {
       return true;
   } else if ("java.lang.Character".equals(genericType)) {
       return true;
   }
   return false;
}

使用多个if语句,符合条件的,将返回true,否则返回false。

//关联方式
//org.litepal.util.Const.Model
public interface Model {
    /**
     * One2One constant value.
     */
    public static final int ONE_TO_ONE = 1;
    /**
     * Many2One constant value.
     */
    public static final int MANY_TO_ONE = 2;
    /**
     * Many2Many constant value.
     */
    public static final int MANY_TO_MANY = 3;
}

这里维护了三个常量,用以标识关联方式,有一对一、多对一和多对多三种关联。在分析模型关联关系时,关联方式将被存储在AssociationsModel的associationType字段中。

了解数据表关联关系

如何理解一对一、一对多(多对一)、多对多关联关系呢?下面我们通过学生、学生档案、学院和课程来举例。原文链接

  • 一对一:一个学生对应一个学生档案,或者是每个人都有唯一的身份证编号。
  • 一对多:一个学生只属于一个班,但是一个学院有多名学生。
  • 多对多:一个学生可以选择多门课,一门课也有多名学生。
    一对一的处理:
    通常直接在被关联表中来增加一个外键,来放置关联表的主键。如学生表和学生档案表,我们可以在学生表中添加一个外键,存储学生档案记录id。当然也可以反过来操作,这个视具体情况而定。
    一对多(多对一)的处理:
    假设现有基本表学生表(学号,姓名,……),班级表(班级号,备注信息,……)。
    在学生表中新增字段(班级号),该字段作为学生表的外键与班级表中的班级号关联。每一个学生实体在班级号字段的值,表示该学生属于的班级。
    小结:一般情况下,一对多关系采用方法二来处理。一对多的两个实体间,在“多”的实体表中新增一个字段,该字段是“一”实体表的主键。
    多对多的处理:
    在多对多关系中,我们要新增加一个关系表。如在上面示例中,在学生表和课程表的基础上增加选课表(中间表)来表示学生与课程之间的多对多关系。在选课表中,
    必须含有的属性有学生号和课程号。(学生号,课程号)这个属性集刚好也就是选课表的关键字(主键)。

开始分析

了解上面几个Model类,接下来我们来看看其如何分析关联关系。

// 创建基于在litepal.xml文件中定义的类模型表。在创建表之后,基于类模型之间的关联将这些关联添加到这些表中。
static void create(SQLiteDatabase db) {
    create(db, true);
    addAssociation(db, true);
}

从上述create()方法中,我们看到,在创建完业务表之后,调用了addAssociation()方法,这个方法正是基于类模型之间的关联向所有表添加关联。下面我们来看看addAssociation()中到底是怎么实现的。

//org.litepal.tablemanager.Generator#addAssociation
private static void addAssociation(SQLiteDatabase db, boolean force) {
    AssociationCreator associationsCreator = new Creator();//多态的体现
    associationsCreator.addOrUpdateAssociation(db, force);
}

addAssociation()中只有两行代码,创建了一个Creator对象,并赋值给AssociationCreator类的变量。 并调用了addOrUpdateAssociation()方法,该方法是一个包装方法,其内部调用了getAllAssociations()和addAssociations()方法,将getAllAssociations()的返回值作为参数传入到addAssociations()中,我们先来看看getAllAssociations()方法。

//org.litepal.tablemanager.Generator#getAllAssociations
protected Collection getAllAssociations() {
    if (mAllRelationModels == null || mAllRelationModels.isEmpty()) {
        mAllRelationModels = getAssociations(LitePalAttr.getInstance().getClassNames());
    }
    return mAllRelationModels;
}

此方法其实也是一个包装方法,方法返回了一个存放AssociationsModel类型对象的集合。方法中的if判断主要是避免重复分析的工作。如果成员变量mAllRelationModels为空,那么将会调用org.litepal.LitePalBase#getAssociations()方法,通过传入类名列表集合获取关联模型。

//org.litepal.LitePalBase#getAssociations
protected Collection getAssociations(List classNames) {
    if (mAssociationModels == null) {
        mAssociationModels = new HashSet();
    }
    if (mGenericModels == null) {
        mGenericModels = new HashSet();
    }
    mAssociationModels.clear();
    mGenericModels.clear();
    for (String className : classNames) {
       //真正开启分析之路
        analyzeClassFields(className, GET_ASSOCIATIONS_ACTION);
    }
    return mAssociationModels;
}

方法中有对两个成员变量进行初始化,这两个变量正是用于存放分析结果信息的。for循环对存放映射模型类的集合进行了遍历,并调用分析方法org.litepal.LitePalBase#analyzeClassFields进行正式分析。

// 对符合条件的类进行反射。分析当前类的字段并找出其关联
//org.litepal.LitePalBase#analyzeClassFields
private void analyzeClassFields(String className, int action) {
    try {
        Class dynamicClass = Class.forName(className);//动态加载模型类
        Field[] fields = dynamicClass.getDeclaredFields();//获得类的所有声明的字段,即包括public、private和proteced
        for (Field field : fields) {
            if (isNonPrimitive(field)) {// 检查字段是否为原始字段(只支持使用原始类型字段进行表的创建)
                // Column注解类,用于向列添加约束。注意,此注释不会影响id列。
                Column annotation = field.getAnnotation(Column.class);//获取元素注解类型
                // 若ignore的值为true,则说明被标识为忽略字段,在创建表时,将不会生成其对应的列。
                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);
    }
}

先通过反射获取类的所有声明的字段,过了掉非原始类型的字段和被标识为忽略的字段。然后检查其关联关系,处理并存储到相应的集合中。

//org.litepal.LitePalBase#oneToAnyConditions
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();//获得某个类的所有声明的字段,即包括public、private和proteced
        // 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())) {//过滤掉static修饰的属性字段
                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)) {// 属性类型是否实现了set或list接口
                    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);
            }
        }
    }
}

代码中已经加入很清晰的注释说明,这里就不再赘述。在上述方法中,已经对一对一和一对多进行处理,两处关联方式的处理方式是类似的。

//org.litepal.LitePalBase#manyToAnyConditions
private void manyToAnyConditions(String className, Field field, int action) throws ClassNotFoundException {
    if (isCollection(field.getType())) {//属性类型为实现了Set和List接口的类型
        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) {
            /*如果泛型对应的是基本数据类型或java.lang.String类型,并且是获取关联关系的操作*/
            /*通用基础数据类型*/
            Column annotation = field.getAnnotation(Column.class);
            if (annotation != null && annotation.ignore()) {
                return;
            }
            GenericModel genericModel = new GenericModel();
            //使用传入的检查类类名拼接属性名,并根据配置的cases,进行转换得到的结果作为表名
            genericModel.setTableName(DBUtility.getGenericTableName(className, field.getName()));
            //属性名拼接后缀“_lpcolumn”作为列名
            genericModel.setValueColumnName(DBUtility.convertToValidColumnName(field.getName()));
            //将属性的java类型转换为数据库支持的类型来作为列类型(存储值的类型)
            genericModel.setValueColumnType(getColumnType(genericTypeName));
            //主表引用的列名(通过主表表名拼接“_id”得到)
            genericModel.setValueIdColumnName(DBUtility.getGenericValueIdColumnName(className));
            mGenericModels.add(genericModel);
        }
    }
}

方法中的第一个if过滤掉非集合类型的字段。第二个if用于判断属性类型集合中的泛型对应的类是映射表中配置的模型类还是原始数据类型,如果是原始数据类型,则创建泛型模型对象,并加入到泛型模型对象集合中;如果是配置的模型类,那么将进行多对一或多对多关联关系的处理。上述方法同样是围绕反射来实现的。

//org.litepal.LitePalBase#addIntoAssociationModelCollection
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);
}

三个入参为:关联模型映射类类名、关联类类名、外键和关联类型。方法中通过调用org.litepal.util.DBUtility#getTableNameByClassName方法,传入完整类名,得到表名(去掉包名之后的类名)。把封装好的关联模型对象维护到成员变量mAssociationModels中。

建立数据表的关联

private void addAssociations(Collection associatedModels, SQLiteDatabase db, boolean force) {
    for (AssociationsModel associationModel : associatedModels) {
        if (Const.Model.MANY_TO_ONE == associationModel.getAssociationType()|| Const.Model.ONE_TO_ONE == associationModel.getAssociationType()) {
        // 向关联添加外键列,列名为“被关联的表名_id”
            addForeignKeyColumn(associationModel.getTableName(),
                    associationModel.getAssociatedTableName(),
                    associationModel.getTableHoldsForeignKey(), db);
        } else if (Const.Model.MANY_TO_MANY == associationModel.getAssociationType()) {
            //当为many2many关联时。数据库需要创建映射这个关联的中间表。此方法有助于创建这样的表,表名按照两个目标表名称按字母顺序连接,中间有下划线。
            createIntermediateTable(associationModel.getTableName(), associationModel.getAssociatedTableName(), db, force);
        }
    }
    for (GenericModel genericModel : getGenericModels()) {
        //在模型类中声明泛型集合字段时。数据库需要创建用于映射这些字段的泛型表。此方法有助于创建这样的表。
        //此表之后两列,一列是以主表中属性名作为列名的,一列是“主表名_id”作为列名)<存放被关联的主表id>
        createGenericTable(genericModel, db, force);
    }
}

分析集合中的所有关联模型。判断它们的关联类型。如果是one2one或many2one关联,到相关表添加外键列。如果是many2many协会,创建一个中间连接表。由于之前我们已经接触过建表的过程,所以这里就只看看在建表或者添加外键的前期处理,至于sql语句的拼装和执行,就不再赘述了。

添加外键

//org.litepal.tablemanager.AssociationCreator#addForeignKeyColumn
protected void addForeignKeyColumn(String tableName, String associatedTableName, String tableHoldsForeignKey, SQLiteDatabase db) {
    if (DBUtility.isTableExists(tableName, db)) {
        if (DBUtility.isTableExists(associatedTableName, db)) {
            String foreignKeyColumn = null;
            //如果两个表相互关联,两个表都会有一个外键列对应着与之关联的表的主键。外键列的名字将是“被关联的表名_id”
            if (tableName.equals(tableHoldsForeignKey)) {
                //保存外键的表为tableName时,那么将使用associatedTableName_id作为外键列的列名。
                foreignKeyColumn = getForeignKeyColumnName(associatedTableName);
            } else if (associatedTableName.equals(tableHoldsForeignKey)) {
                foreignKeyColumn = getForeignKeyColumnName(tableName);
            }
            if (!DBUtility.isColumnExists(foreignKeyColumn, tableHoldsForeignKey, db)) {
                ColumnModel columnModel = new ColumnModel();
                columnModel.setColumnName(foreignKeyColumn);
                columnModel.setColumnType("integer");
                List<String> sqls = new ArrayList<String>();
                sqls.add(generateAddColumnSQL(tableHoldsForeignKey, columnModel));
                execute(sqls, db);
            } else {
                LogUtil.d(TAG, "column " + foreignKeyColumn + " is already exist, no need to add one");
            }
        } else {
            throw new DatabaseGenerateException(DatabaseGenerateException.TABLE_DOES_NOT_EXIST + associatedTableName);
            }
    } else {
        throw new DatabaseGenerateException(DatabaseGenerateException.TABLE_DOES_NOT_EXIST + tableName);
    }
}

当需要添加外键的表存在时,获取需要添加的外键列的列名,封装列模型,组装并执行sql语句。

添加中间关联表

//org.litepal.tablemanager.AssociationCreator#createIntermediateTable
private void createIntermediateTable(String tableName, String associatedTableName, SQLiteDatabase db, boolean force) {
    //中间连接表只有两列,分别对应两个关联表的主键,且中间表不自动生成主键。
    List<ColumnModel> columnModelList = new ArrayList<ColumnModel>();
    ColumnModel column1 = new ColumnModel();
    column1.setColumnName(tableName + "_id");
    column1.setColumnType("integer");
    ColumnModel column2 = new ColumnModel();
    column2.setColumnName(associatedTableName + "_id");
    column2.setColumnType("integer");
    columnModelList.add(column1);
    columnModelList.add(column2);
    // 比较之后,以较小的值开头,两个表名之间以“_”连接作为中间连接表的表名。
    String intermediateTableName = DBUtility.getIntermediateTableName(tableName, associatedTableName);
    List<String> sqls = new ArrayList<String>();
    if (DBUtility.isTableExists(intermediateTableName, db)) {
        if (force) {// 如果已存在并且可删除,则先删除表,在创建
            sqls.add(generateDropTableSQL(intermediateTableName));
            sqls.add(generateCreateTableSQL(intermediateTableName, columnModelList, false));
        }
    } else {
        sqls.add(generateCreateTableSQL(intermediateTableName, columnModelList, false));
    }
    execute(sqls, db);//执行sql
    // 备份表信息
    giveTableSchemaACopy(intermediateTableName, Const.TableSchema.INTERMEDIATE_JOIN_TABLE, db);
}

由于我们在处理多对多关联关系时,是将其转化为连个多对一的关联关系。换而言之,就是中间将与两个目标关联表形成一对多的关联关系。所以,在中间关联表中,我们只需要有两列即可,分别对应两个关联表的主键,且中间表不自动生成主键。在完成了列实体对象的组装之后,调用执行sql语句的方法进行建表。最后,备份表信息。

创建泛型表

//org.litepal.tablemanager.AssociationCreator#createGenericTable
private void createGenericTable(GenericModel genericModel, SQLiteDatabase db, boolean force) {
        String tableName = genericModel.getTableName();
        String valueColumnName = genericModel.getValueColumnName();
        String valueColumnType = genericModel.getValueColumnType();
        String valueIdColumnName = genericModel.getValueIdColumnName();
        List columnModelList = new ArrayList();
        ColumnModel column1 = new ColumnModel();
        column1.setColumnName(valueColumnName);
        column1.setColumnType(valueColumnType);
        ColumnModel column2 = new ColumnModel();
        column2.setColumnName(valueIdColumnName);
        column2.setColumnType("integer");
        columnModelList.add(column1);
        columnModelList.add(column2);
        List sqls = new ArrayList();
        if (DBUtility.isTableExists(tableName, db)) {
            if (force) {
                sqls.add(generateDropTableSQL(tableName));
                sqls.add(generateCreateTableSQL(tableName, columnModelList, false));
            }
        } else {
            sqls.add(generateCreateTableSQL(tableName, columnModelList, false));
        }
        execute(sqls, db);
        giveTableSchemaACopy(tableName, Const.TableSchema.GENERIC_TABLE, db);
    }

根据泛型模型,组装列模型,并通过得到的列模型集,转换成sql语句,执行建表语句,备份表信息。

结语

至此,完成了整个建表的过程。由于表升级过程和建表过程很相似,所以,就不对升级过程进行讲解了。

LitePal项目链接

你可能感兴趣的:(android数据库)