数据库创建完,该创建表了。
在SQLiteOpenHelper类里getWritableDatabase()和getReadableDatabase()方法的内部实现都是调用了getDatabaseLocked()方法,该方法是处理数据库创建,表创建的核心方法。他定义了一系列逻辑骨架,但并不是具体的实现者。下面来看看该方法在创建完数据库后是怎样处理表的创建:
private SQLiteDatabase getDatabaseLocked(boolean writable) { //...... db.beginTransaction(); try { if (version == 0) { onCreate(db); } else { if (version > mNewVersion) { onDowngrade(db, version, mNewVersion); } else { onUpgrade(db, version, mNewVersion); } } db.setVersion(mNewVersion); db.setTransactionSuccessful(); } finally { db.endTransaction(); } } //...... }当版本为0时即还未创建过表时调用onCreate()方法, 如果当前数据库版本小于新设置的版本即升高版本号则调用onUpgrade()方法,在该方法里可以删除表,添加新表或进行其他升级操作。 这两个方法都是抽象方法,具体的实现延迟到子类去实现。如果新设置的版本号小于当前数据库版本,即降低了版本号,会抛出异常。数据库的版本只能升不能降。
在LitePal中,LitePalOpenHelper继承了SQLiteOpenHelper去实现onCreate()和onUpgrade():
package com.aliao.litepal.tablemanager; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import com.aliao.litepal.LitePalApplication; /** * Created by 丽双 on 2015/6/9. */ public class LitePalOpenHelper extends SQLiteOpenHelper{ public LitePalOpenHelper(String name,int version) { //将数据库名及版本号传给父类SQLiteOpenHelper super(LitePalApplication.getContext(), name, null, version); } @Override public void onCreate(SQLiteDatabase db) { Generator.create(db); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { } }在onCreate()里调用Genetator.create(db);
package com.aliao.litepal.tablemanager; import android.database.sqlite.SQLiteDatabase; /** * Created by 丽双 on 2015/6/10. */ public class Generator { static void create(SQLiteDatabase db){ create(db, true); } private static void create(SQLiteDatabase db, boolean force){ Creator creator = new Creator(); creator.createOrUpgradeTable(db, force); } }Genetator类中的create()方法调用Creator类里的createOrUpgradeTable()方法,谁能告诉我这个设计思路是什么,有点晕。往下走来到终极实现者Creator类,终于看到excute的字样,感觉挖到了洞底。
package com.aliao.litepal.tablemanager; import android.database.sqlite.SQLiteDatabase; import com.aliao.litepal.tablemanager.model.TableModel; import com.aliao.litepal.util.Const; import java.util.Collection; /** * Created by 丽双 on 2015/6/10. */ public class Creator { private Collection<TableModel> mTableModels; /** * 根据表的映射对象来创建表-批量创建表 * @param db * @param force */ protected void createOrUpgradeTable(SQLiteDatabase db, boolean force){ for (TableModel tableModel:getAllTableModels()){ execute(getCreateTableSQLs(tableModel, db, force), db); giveTableSchemaACopy(tableModel.getTableName(), Const.TableSchema.NORMAL_TABLE, db); } } /** * 批量执行sql语句来创建表 * @param sqls * @param db */ protected void execute(String[] sqls, SQLiteDatabase db) { } /** * 获取创建表的sql语句 * @param tableModel * @param db * @param force * @return */ protected String[] getCreateTableSQLs(TableModel tableModel, SQLiteDatabase db, boolean force) { return new String[0]; } /** * 通过所有关系映射的对象类名集合来组装表模型集合 * @return */ private Collection<TableModel> getAllTableModels() { return mTableModels; } /** * 将新创建的表的表名备份到table_schema中 * @param tableName * @param tableType * @param db */ protected void giveTableSchemaACopy(String tableName, int tableType, SQLiteDatabase db) { } }使用createOrUpgradeTable()方法来创建表,一个一个for循环去批量执行sql语句。接下来从这个方法展开,挨个看这里面调用到的方法的具体实现。
先来看下execute()方法:
/** * 批量执行sql语句来创建表 * @param sqls * @param db */ protected void execute(String[] sqls, SQLiteDatabase db) { String throwSQL = ""; try{ if (sqls != null){ for (String sql:sqls){ db.execSQL(BaseUtility.changeCase(sql)); } } }catch (SQLException e){ throw new DatabaseGenerateException(DatabaseGenerateException.SQL_ERROR + throwSQL); } }一个for循环去批量执行sql语句,这个sql数组由getCreateTableSQLs()方法提供,他是调用generateCreateTableSQL()来最终生成sql语句,有几个注意的是,一是如果要创建的表已经存在了,可以选择是否要删除已存在的表再重建,二是列名id是被自动构建到sql语句的,如果模型类里设置名字为id或_id的字段,会把他删除防止表里生成两个id列名。下面是生成sql语句的完整部分:
package com.aliao.litepal.tablemanager; import android.database.SQLException; import android.database.sqlite.SQLiteDatabase; import android.text.TextUtils; import com.aliao.litepal.exceptions.DatabaseGenerateException; import com.aliao.litepal.parser.LitePalAttr; import com.aliao.litepal.tablemanager.model.TableModel; import com.aliao.litepal.util.BaseUtility; import com.aliao.litepal.util.Const; import com.aliao.litepal.util.DBUtility; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; /** * Created by 丽双 on 2015/6/10. */ public class Creator { private Collection<TableModel> mTableModels; /** * 根据表的映射对象来创建表-批量创建表 * @param db * @param force */ protected void createOrUpgradeTable(SQLiteDatabase db, boolean force){ for (TableModel tableModel:getAllTableModels()){ execute(getCreateTableSQLs(tableModel, db, force), db); giveTableSchemaACopy(tableModel.getTableName(), Const.TableSchema.NORMAL_TABLE, db); } } /** * 批量执行sql语句来创建表 * @param sqls * @param db */ protected void execute(String[] sqls, SQLiteDatabase db) { String throwSQL = ""; try{ if (sqls != null){ for (String sql:sqls){ db.execSQL(BaseUtility.changeCase(sql)); } } }catch (SQLException e){ throw new DatabaseGenerateException(DatabaseGenerateException.SQL_ERROR + throwSQL); } } /** * 获取创建表的sql语句 * @param tableModel * @param db * @param force * @return */ private String[] getCreateTableSQLs(TableModel tableModel, SQLiteDatabase db, boolean force) { //force用来判断,是否要先删除已存在的表再新建 if (force){ return new String[]{ generateDropTableSQL(tableModel), generateCreateTableSQL(tableModel) }; }else { if (DBUtility.isTableExists(tableModel.getTableName(), db)) { return null; } else { return new String[] { generateCreateTableSQL(tableModel) }; } } } /** * 生成一个删除表的sql语句 * @param tableModel * @return */ private String generateDropTableSQL(TableModel tableModel) { return generateDropTableSQL(tableModel.getTableName()); } protected String generateDropTableSQL(String tableName) { return "drop table if exists " + tableName; } /** * 生成一个创建表的sql语句 * @param tableModel * @return */ private String generateCreateTableSQL(TableModel tableModel) { return generateCreateTableSQL(tableModel.getTableName(), tableModel.getColumns(), true); } protected String generateCreateTableSQL(String tableName, Map<String, String> columnsMap,boolean autoIncrementId) { Set<String> columnNames = columnsMap.keySet(); //判断集合里是有含有id或_id的列名,如果有则删除。防止生成两个id列,因为每个sql语句会自己创建id列作为主键 removeId(columnNames); StringBuilder createTableSQL = new StringBuilder("create table "); createTableSQL.append(tableName).append(" ("); //创建id作为主键,autoIncrementId总是true if (autoIncrementId){ createTableSQL.append("id integer primary key autoincrement,"); } //判断有没有列名,没有列名就把刚拼接的sql语句最后那个逗号给去了 Iterator<String> i = columnNames.iterator(); if (!i.hasNext()){ createTableSQL.deleteCharAt(createTableSQL.length() - 1); } boolean needSeparator = false; while (i.hasNext()){ if (needSeparator){ createTableSQL.append(", "); } needSeparator = true; String columnName = i.next(); createTableSQL.append(columnName).append(" ").append(columnsMap.get(columnName)); } createTableSQL.append(")"); return createTableSQL.toString(); } private void removeId(Set<String> columnNames) { String idName = ""; for (String columnName : columnNames) { if (isIdColumn(columnName)) { idName = columnName; break; } } if (!TextUtils.isEmpty(idName)) { columnNames.remove(idName); } } protected boolean isIdColumn(String columnName) { return "_id".equalsIgnoreCase(columnName) || "id".equalsIgnoreCase(columnName); } //........ }要拼接创建表的sql语句,就要知道表名,列名,列名的类型。这些信息都是保存在一个叫TableModel的类里。那这些信息如何保存到TabelModel里嘞,又是一堆的代码,揉揉眼睛直直腰,gogo,TableModel类里维护了表名,类名和一个columnsMap,这个columnsMap的key存储的是字段名,value存储该字段的数据类型:
package com.aliao.litepal.tablemanager.model; import java.util.HashMap; import java.util.Map; import java.util.Set; /** * Created by 丽双 on 2015/6/10. */ public class TableModel { //表名 private String tableName; //列名是key,列的类型是value private Map<String, String> columnsMap = new HashMap<>(); //类名 private String className; public String getTableName() { return tableName; } public void setTableName(String tableName) { this.tableName = tableName; } public Map<String, String> getColumns() { return columnsMap; } public void addColumns(String columnName, String columnType) { columnsMap.put(columnName, columnType); } public String getClassName() { return className; } public void setClassName(String className) { this.className = className; } public Set<String> getColumnNames(){ return columnsMap.keySet(); } }在createOrUpgradeTable()方法去构建sql语句之前先要获得所有tablemodel:
/** * 根据表的映射对象来创建表-批量创建表 * @param db * @param force */ protected void createOrUpgradeTable(SQLiteDatabase db, boolean force){ for (TableModel tableModel:getAllTableModels()){ execute(getCreateTableSQLs(tableModel, db, force), db); giveTableSchemaACopy(tableModel.getTableName(), Const.TableSchema.NORMAL_TABLE, db); } }
要构建tablemodel的信息,要拿到表名,也就是对应的映射关系的类名(没有包名前缀);类名即解析litepal.xml获取到的带有完整包名的类名;字段名即列名,字段的数据类型即列的类型。通过LitePalAttr类可以获取到在litepal.xml里配置的所有要映射到数据库的类名。基于这些类名再利用反射机制来获取该类的字段名及字段的数据类型。然而获取到的字段的数据类型不能直接用来构建sql语句,我们知道SQLite支持的数据类型有五种:NULL,INTEGER,REAL,TEXT, BLOB。所以要把字段的数据类型转换为这五种类型之一。
package com.aliao.litepal.tablemanager; import android.database.SQLException; import android.database.sqlite.SQLiteDatabase; import android.text.TextUtils; import com.aliao.litepal.exceptions.DatabaseGenerateException; import com.aliao.litepal.parser.LitePalAttr; import com.aliao.litepal.tablemanager.model.TableModel; import com.aliao.litepal.tablemanager.typechange.BooleanOrm; import com.aliao.litepal.tablemanager.typechange.DateOrm; import com.aliao.litepal.tablemanager.typechange.DecimalOrm; import com.aliao.litepal.tablemanager.typechange.NumericOrm; import com.aliao.litepal.tablemanager.typechange.OrmChange; import com.aliao.litepal.tablemanager.typechange.TextOrm; import com.aliao.litepal.util.BaseUtility; import com.aliao.litepal.util.Const; import com.aliao.litepal.util.DBUtility; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; /** * Created by 丽双 on 2015/6/10. */ public class Creator { private Collection<TableModel> mTableModels; /** * 根据表的映射对象来创建表-批量创建表 * @param db * @param force */ protected void createOrUpgradeTable(SQLiteDatabase db, boolean force){ for (TableModel tableModel:getAllTableModels()){ execute(getCreateTableSQLs(tableModel, db, force), db); giveTableSchemaACopy(tableModel.getTableName(), Const.TableSchema.NORMAL_TABLE, db); } } /** * 通过所有关系映射的对象类名集合来组装表模型集合 * @return */ private Collection<TableModel> getAllTableModels() { if (mTableModels == null){ mTableModels = new ArrayList<>(); } if(!canUseCache()){ mTableModels.clear(); for (String className : LitePalAttr.getIntance().getClassNames()){ mTableModels.add(getTableModel(className)); } } return mTableModels; } private OrmChange[] typeChangeRules = { new NumericOrm(), new TextOrm(), new BooleanOrm(), new DecimalOrm(), new DateOrm() }; /** * 根据类名来创建表模型 * @param className * @return */ private TableModel getTableModel(String className) { String tableName = DBUtility.getTableNameByClassName(className); TableModel tableModel = new TableModel(); tableModel.setTableName(tableName);//设置表名 tableModel.setClassName(className);//设置类名 //获取类的所有字段 List<Field> supportedFields = getSupportedFields(className); //根据字段获取该字段的名字及数据类型,添加到‘字段名-数据类型’的哈希表columnMap里 for (Field field : supportedFields){ String fieldName = field.getName(); Class<?> fieldTypeClass = field.getType(); String fieldType = fieldTypeClass.getName(); String columnName = null; String columnType = null; //将字段类型转换为SQLite数据库支持的数据类型 for (OrmChange ormChange:typeChangeRules){ String[] relations = ormChange.object2Relation(className, fieldName, fieldType); if (relations != null){ columnName = relations[0]; columnType = relations[1]; tableModel.addColumns(columnName, columnType); break; } } } return tableModel; } /** * 利用反射机制获取该类所有的字段 * 1.要求添加的字段的修饰符是私有的且被static的, * 2.字段的类型要符合基本数据类型 * @param className * @return */ private List<Field> getSupportedFields(String className) { List<Field> supportedFields = new ArrayList<>(); Class<?> dynamicClass = null; try { dynamicClass = Class.forName(className); } catch (ClassNotFoundException e) { e.printStackTrace(); } Field[] fields = dynamicClass.getDeclaredFields(); for (Field field:fields){ int modifiers = field.getModifiers(); if (Modifier.isPrivate(modifiers) && !Modifier.isStatic(modifiers)){ Class<?> fieldTypeClass = field.getType(); String fieldType = fieldTypeClass.getName(); if (BaseUtility.isFieldTypeSupported(fieldType)){ supportedFields.add(field); } } } return supportedFields; } private boolean canUseCache() { if (mTableModels == null) { return false; } return mTableModels.size() == LitePalAttr.getIntance().getClassNames().size(); } //........... }
在getSupportedFields()里,只有private修饰符及非static修饰的字段才能保留下来,作为支持对象关系映射的字段。那么也就是说如果有的字段不想映射到数据库表中,可以使用public,protected或者default修饰符。
再看getTableModel()中java基本类型向SQLite支持的数据类型转换的实现:
for (OrmChange ormChange : typeChangeRules) { String[] relations = ormChange.object2Relation(className, fieldName, fieldType); if (relations != null) { columnName = relations[0]; columnType = relations[1]; tableModel.addColumn(columnName, columnType); break; } }OrmChange是一个抽象父类,他有五个子类:NumericOrm,TextOrm,BooleanOrm,DecimalOrm,DateOrm都分别实现了各自的object2Relation()方法:
NumericOrm类,将基本类型int,long,short或者其包装类转换为INTEGER类型
TextOrm类,将基本类型char或者其包装类Character、String转换为TEXT类型
BooleanOrm类,将boolean或者其包装类Boolean转换为INTEGER类型。Note:SQLite 并没有单独的布尔存储类型,而是将布尔值存储为整数 0 (false) 和 1 (true)。
DecimalOrm类,将float,double或者其包装类转换为REAL类型
DateOrm类,将Date类型转换为INTEGER类型
(正如郭神blog所说LitePal支持对象关系映射的数据类型共8种,int、short、long、float、double、boolean、String和Date)
看一个NumericOrm类的具体实现:
package com.aliao.litepal.tablemanager.typechange; /** * Created by 丽双 on 2015/6/11. */ public class NumericOrm extends OrmChange { /** * 如果传入的字段类型是int,long或者short,则把他转换为integer类型作为列的数据类型 * @param className * @param fieldName * @param fieldType * @return */ @Override public String[] object2Relation(String className, String fieldName, String fieldType) { if (fieldName != null && fieldType != null) { String[] relations = new String[2]; relations[0] = fieldName; if (fieldType.equals("int") || fieldType.equals("java.lang.Integer")) { relations[1] = "INTEGER"; return relations; } if (fieldType.equals("long") || fieldType.equals("java.lang.Long")) { relations[1] = "INTEGER"; return relations; } if (fieldType.equals("short") || fieldType.equals("java.lang.Short")) { relations[1] = "INTEGER"; return relations; } } return null; } }上面的代码都是根据源码拿来测试敲的,郭神的方法很多都是封装到不同的类里,这里为方便放在同一个类里。
创建表的涉及的几乎所有代码就这些,运行项目创建表后,用sqlite命令行查询UserInfo表的数据结构和建表语句:
对应的UserInfo类:
package com.aliao.learninglitepal.entity; import java.io.Serializable; import java.util.ArrayList; import java.util.List; /** * Created by 丽双 on 2015/6/5. * 用户表 */ public class UserInfo implements Serializable { private long id; private String userId; private String userName; private String permissionEndTime; private String realName; private String email; private List<SurveyInfo> surveyInfos = new ArrayList<>();//与模板问卷一对多 public long getId() { return id; } public void setId(long id) { this.id = id; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getUserId() { return userId; } public void setUserId(String userId) { this.userId = userId; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getPermissionEndTime() { return permissionEndTime; } public void setPermissionEndTime(String permissionEndTime) { this.permissionEndTime = permissionEndTime; } public String getRealName() { return realName; } public void setRealName(String realName) { this.realName = realName; } public List<SurveyInfo> getSurveyInfos() { return surveyInfos; } public void setSurveyInfos(List<SurveyInfo> surveyInfos) { this.surveyInfos = surveyInfos; } }