一聚教程网:一个值得你收藏的教程网站

最新下载

热门教程

Android xUtils3源码解析之数据库模块

时间:2017-04-01 编辑:简简单单 来源:一聚教程网

配置数据库

DbManager.DaoConfig  daoConfig =newDbManager.DaoConfig() .setDbName("test.db")  .setDbVersion(1) .setDbOpenListener(newDbManager.DbOpenListener()  {@OverridepublicvoidonDbOpened(DbManager db) {// 开启WAL,  对写入加速提升巨大db.getDatabase().enableWriteAheadLogging(); } })  .setDbUpgradeListener(newDbManager.DbUpgradeListener()  {@OverridepublicvoidonUpgrade(DbManager db,intoldVersion,intnewVersion) {  ... } });

xUtil3支持数据库多库的配置,使用不同的DaoConfig,可以创建多个.db文件,每个.db文件彼此独立。

数据库操作

初始化

由于xUtils3设计的是在需要使用数据库的时候,才创建数据表。所以下文以save操作为例,跟进初始化数据表的过程。示例代码:

DbManager db = x.getDb(daoConfig);Parentparent=newParent();parent.setName("CSDN 一口仨馍"); db.save(parent);

数据库的操作比较耗时,真实应该异步执行。可以看到,xUtils3提供的数据库操作是非常简单的,首先getDb,之后调用save()方法即可。其中save方法接受List

创建数据库文件

x.getDb(daoConfig)

publicfinalclassx {publicstaticDbManagergetDb(DbManager.DaoConfig daoConfig) {returnDbManagerImpl.getInstance(daoConfig); } }

这里只是简单的返回了一个DbManagerImpl实例,看样子真正的初始化操作都在DbManagerImpl里。跟进。

publicfinalclassDbManagerImplextendsDbBase{privateDbManagerImpl(DaoConfig  config) {if(config ==null) {thrownewIllegalArgumentException("daoConfig  may not be null"); }this.daoConfig = config;this.allowTransaction =  config.isAllowTransaction();this.database =  openOrCreateDatabase(config); DbOpenListener dbOpenListener =  config.getDbOpenListener();if(dbOpenListener !=null) {  dbOpenListener.onDbOpened(this); }  }publicsynchronizedstaticDbManagergetInstance(DaoConfig daoConfig)  {if(daoConfig ==null) {//使用默认配置daoConfig =newDaoConfig(); }  DbManagerImpl dao = DAO_MAP.get(daoConfig);if(dao ==null) { dao  =newDbManagerImpl(daoConfig); DAO_MAP.put(daoConfig, dao); }else{  dao.daoConfig = daoConfig; }// update the database if  neededSQLiteDatabase database = dao.database;intoldVersion =  database.getVersion();intnewVersion =  daoConfig.getDbVersion();if(oldVersion != newVersion) {if(oldVersion  !=0) { DbUpgradeListener upgradeListener =  daoConfig.getDbUpgradeListener();if(upgradeListener !=null) {  upgradeListener.onUpgrade(dao, oldVersion, newVersion); }else{try{  dao.dropDb(); }catch(DbException e) { LogUtil.e(e.getMessage(), e); } } } database.setVersion(newVersion); }returndao; } }

乍一看代码有些长,其实也没做太多操作,绝大部分是些缓存赋值相关的操作。这里注意两个地方

  1. 在数据库版本更新时,如果没有设置DbUpgradeListener,那么在更新的时候会直接删除旧表。

  2. 在获取DbManagerImpl实例的时候,创建了数据库,例如:“test.db”。如果指定了数据库的位置(通过DaoConfig#setDbDir()),则在指定位置创建,默认在data/data/package name/database/下创建。

由于返回的是DbManagerImpl实例,所以实际调用的是DbManagerImpl.save()。

DbManagerImpl.save()

publicfinalclassDbManagerImplextendsDbBase{publicvoidsave(Object  entity)throwsDbException {try{// 开启事务beginTransaction();//  判断将要保存的是对象还是对象的集合if(entityinstanceofList) {// 向上转型为ListList entities = (List) entity;if(entities.isEmpty())return;// 依据被注解的类获取数据表对应的包装类TableEntity  table =this.getTable(entities.get(0).getClass());//  如果没有表则创建createTableIfNotExist(table);// 遍历插入数据库for(Object item :  entities) {//  拼接sql语句,执行数据库插入操作execNonQuery(SqlInfoBuilder.buildInsertSqlInfo(table,  item)); } }else{ TableEntity table =this.getTable(entity.getClass());  createTableIfNotExist(table);  execNonQuery(SqlInfoBuilder.buildInsertSqlInfo(table, entity)); }//  设置事务成功setTransactionSuccessful(); }finally{// 结束事务endTransaction(); } } }

接下来每行都有注释,这些是我在看的过程中写下的。我只说贴代码的逻辑吧。先看下创建TableEntity

JavaBean到TableEntity的转化

创建表的包装类

publicfinalclassTableEntity{/*package*/TableEntity(DbManager db, ClassentityType)  throws Throwable {this.db = db;this.entityType =  entityType;this.constructor =  entityType.getConstructor();this.constructor.setAccessible(true);//  被保存的类没有没Table注解,这里会抛出NullPointerException。// ps:作者这里应该验证下为null的问题Table  table = entityType.getAnnotation(Table.class);// 获取表名this.name =  table.name();// 获取创建表之后执行的SQL语句this.onCreated = table.onCreated();//  获取列Map,Map<列的类型,列的包装类>this.columnMap =  TableUtils.findColumnMap(entityType);//  遍历查找列的包装类,直到找到id列for(ColumnEntity column : columnMap.values())  {if(column.isId()) {this.id = column;break; } } } }

这里涉及到Table注解,从Table注解中获取表名。之后封装了一个Map,key为列名,value为列的包装类,例如:Map

/* package */finalclassTableUtils{staticsynchronized LinkedHashMapfindColumnMap(ClassentityType) {LinkedHashMapcolumnMap =newLinkedHashMap(); addColumns2Map(entityType, columnMap);returncolumnMap; }privatestaticvoid addColumns2Map(ClassentityType,HashMapcolumnMap)  {// 递归出口if(Object.class.equals(entityType))return;try{//  获取表实体类的所有属性Field[] fields = entityType.getDeclaredFields();for(Field  field : fields) {// 获取属性的修饰符int modify = field.getModifiers();//  修饰符不能是static或者transientif(Modifier.isStatic(modify) ||  Modifier.isTransient(modify)) {continue; }//  为下面判断属性有没有被Column注解修饰做准备Column columnAnn =  field.getAnnotation(Column.class);if(columnAnn !=null) {//  判断属性是否支持转换if(ColumnConverterFactory.isSupportColumnConverter(field.getType()))  {// 新建列(属性)的包装类ColumnEntity column =newColumnEntity(entityType, field,  columnAnn);if(!columnMap.containsKey(column.getName())) {  columnMap.put(column.getName(), column); } } } }//  递归解析属性addColumns2Map(entityType.getSuperclass(), columnMap);  }catch(Throwable e) { LogUtil.e(e.getMessage(), e); } } }

创建列的包装类

/** *@paramentityType 实体类 *@paramfield 属性 *@paramcolumn 注解 *//* package */ColumnEntity(Class  entityType, Field field, Column column) {//  设置属性可访问field.setAccessible(true);this.columnField = field;//  获取数据库中列的名称,一般和属性值保持一致this.name = column.name();// 获取属性的值this.property =  column.property();// 是否是主键this.isId = column.isId();// 获取属性的类型Class  fieldType = field.getType();//  是否自增,int、Integer、long、Long类型的主键,默认自增this.isAutoId =this.isId &&  column.autoGen() && ColumnUtils.isAutoIdType(fieldType);//  String为例,返回的是StringColumnConverterthis.columnConverter =  ColumnConverterFactory.getColumnConverter(fieldType);//  查找get方法。例如:对于age属性,查找getAge()方法this.getMethod =  ColumnUtils.findGetMethod(entityType, field);if(this.getMethod  !=null&& !this.getMethod.isAccessible()) {//  设置可反射访问this.getMethod.setAccessible(true); }// 查找set方法this.setMethod =  ColumnUtils.findSetMethod(entityType, field);if(this.setMethod  !=null&& !this.setMethod.isAccessible())  {this.setMethod.setAccessible(true); } }

数据操作的时候不用每次都这么繁琐,因为表格有tableMap缓存,下次直接就能取出相应的表包装类TableEntity。下面跟进下创建表的过程。

创建数据表

createTableIfNotExist()

// 创建数据表protectedvoidcreateTableIfNotExist(TableEntity  table)throwsDbException {//  根据系统表SQLITE_MASTER判断指定表格是否存在if(!table.tableIsExist())  {synchronized(table.getClass()) {// 表不存在if(!table.tableIsExist()) {//  获取创建表格语句SqlInfo sqlInfo =  SqlInfoBuilder.buildCreateTableSqlInfo(table);//  执行创建表格语句execNonQuery(sqlInfo);//  获取创建表格之后的语句,例如:可用于创建索引。PS:Table注解中的属性String execAfterTableCreated =  table.getOnCreated();if(!TextUtils.isEmpty(execAfterTableCreated)) {//  执行创建表之后的语句execNonQuery(execAfterTableCreated); }//  再次设置"表已创建"标志位table.setCheckedDatabase(true);// 获取监听TableCreateListener  listener =this.getDaoConfig().getTableCreateListener();if(listener  !=null) {// 调用创建表之后的监听listener.onTableCreated(this, table); } } } } }

创建表的语句如下

public static SqlInfo buildCreateTableSqlInfo(TableEntity  table) throws DbException { ColumnEntity id =  table.getId();StringBuilder builder = new  StringBuilder();builder.append("CREATE TABLE IF NOT EXISTS  ");builder.append("\"").append(table.getName()).append("\"");builder.append("  ( ");if (id.isAutoId()) {  builder.append("\"").append(id.getName()).append("\"").append(" INTEGER  PRIMARY KEY AUTOINCREMENT, ");} else {  builder.append("\"").append(id.getName()).append("\"").append(id.getColumnDbType()).append("  PRIMARY KEY, ");} Collectioncolumns =  table.getColumnMap().values();for (ColumnEntity column : columns) { if  (column.isId())  continue;builder.append("\"").append(column.getName()).append("\"");builder.append('  ').append(column.getColumnDbType());builder.append('  ').append(column.getProperty());builder.append(',');}  builder.deleteCharAt(builder.length() -1);builder.append(" )");return  new SqlInfo(builder.toString());}

就是拼接了一条创建数据表的语句,而且使用的是CREATE TABLE IF NOT EXISTS。最后执行下创建表的语句。

publicvoidexecNonQuery(SqlInfo  sqlInfo) throws DbException { SQLiteStatement statement =null;try{  statement = sqlInfo.buildStatement(database); statement.execute();  }catch(Throwable e) {thrownewDbException(e); }finally{if(statement  !=null) {try{ statement.releaseReference(); }catch(Throwable ex) {  LogUtil.e(ex.getMessage(), ex); } } } }//  绑定SQL语句中"?"对应的值publicSQLiteStatementbuildStatement(SQLiteDatabase  database) { SQLiteStatement result =  database.compileStatement(sql);if(bindArgs !=null) {for(inti =1; i <  bindArgs.size() +1; i++) { KeyValue kv = bindArgs.get(i -1);//  将属性的类型转换为数据库类型,例如String 转换成 TEXTObjectvalue=  ColumnUtils.convert2DbValueIfNeeded(kv.value);if(value==null) {  result.bindNull(i); }else{ ColumnConverter converter =  ColumnConverterFactory.getColumnConverter(value.getClass());  ColumnDbType type = converter.getColumnDbType();switch(type)  {caseINTEGER: result.bindLong(i,  ((Number)value).longValue());break;caseREAL: result.bindDouble(i,  ((Number)value).doubleValue());break;caseTEXT:  result.bindString(i,value.toString());break;caseBLOB: result.bindBlob(i,  (byte[])value);break;default: result.bindNull(i);break; }// end switch}  } }returnresult; }

??

save在上述初始化的基础上操作,真正执行save操作的地方在于execNonQuery(SqlInfoBuilder.buildInsertSqlInfo(table,   item))。和创建表的过程类似,使用SqlInfoBuilder.buildInsertSqlInfo()构建一条SQL插入语句,之后执行。跟进看下。

public static SqlInfo buildInsertSqlInfo(TableEntity table, Object entity) throws DbException { ListkeyValueList  = entity2KeyValueList(table, entity);if (keyValueList.size() ==0)  return null;SqlInfo result = new SqlInfo();String sql =  INSERT_SQL_CACHE.get(table);if (sql == null) { StringBuilder builder =  new StringBuilder();builder.append("INSERT INTO  ");builder.append("\"").append(table.getName()).append("\"");builder.append("  (");for (KeyValue kv : keyValueList) {  builder.append("\"").append(kv.key).append("\"").append(',');}  builder.deleteCharAt(builder.length() -1);builder.append(") VALUES  (");int length = keyValueList.size();for (int i =0; i < length; i++)  {builder.append("?,");} builder.deleteCharAt(builder.length()  -1);builder.append(")");sql =  builder.toString();result.setSql(sql);result.addBindArgs(keyValueList);INSERT_SQL_CACHE.put(table,  sql);} else { result.setSql(sql);result.addBindArgs(keyValueList);}  return result;}

这个方法的作用就是拼接SQL语句:INSERT INTO “tableName”( “key1”,”key2”) VALUES (?,?),之后存入缓存,下次直接从缓存中取出上面拼接的SQL语句。执行的过程和创建表是同一个方法,不再赘述。

示例代码:

DbManager db =x.getDb(daoConfig);db.delete(Parent.class); @Overridepublicvoiddelete(Class entityType)throwsDbException { delete(entityType,null); }@Overridepublicintdelete(Class entityType, WhereBuilder whereBuilder)throwsDbException { TableEntity  table  =this.getTable(entityType);if(!table.tableIsExist())return0;intresult  =0;try{ beginTransaction(); result =  executeUpdateDelete(SqlInfoBuilder.buildDeleteSqlInfo(table,  whereBuilder)); setTransactionSuccessful(); }finally{ endTransaction();  }returnresult; }

因为使用WhereBuilder涉及到查找,而查找的源码还没看,所以这里以删除表中所有数据为例。

创建删除语句

publicstaticSqlInfobuildDeleteSqlInfo(TableEntity  table, WhereBuilder whereBuilder)throwsDbException { StringBuilder  builder =newStringBuilder("DELETE FROM ");  builder.append("\"").append(table.getName()).append("\"");if(whereBuilder  !=null&& whereBuilder.getWhereItemSize() >0) {  builder.append(" WHERE ").append(whereBuilder.toString());  }returnnewSqlInfo(builder.toString()); }

因为这里的WhereBuilder为null,所以返回的是DELETE FROM "tableName",即删除表中所有数据。

示例代码:

DbManager db = x.getDb(daoConfig);Parentparent=newParent();parent.setName("CSDN 一口仨馍"); db.update(parent,"name");

update后面照样支持WhereBuilder甚至指定列名,为了方便分析主要流程,这里就简单点来。update方法就不贴了,和前面save过程几乎一样,区别主要在执行的SQL语句不同,下面主要看下更新语句的构建。

public static SqlInfo buildUpdateSqlInfo(TableEntity table, Object entity, String... updateColumnNames) throws DbException { ListkeyValueList = entity2KeyValueList(table, entity);if (keyValueList.size() ==0) return null;HashSetupdateColumnNameSet  = null;if (updateColumnNames != null &&  updateColumnNames.length>0) { updateColumnNameSet = new HashSet(updateColumnNames.length);Collections.addAll(updateColumnNameSet,  updateColumnNames);} ColumnEntity id = table.getId();Object idValue =  id.getColumnValue(entity);if (idValue == null) { throw new  DbException("this entity["+ table.getEntityType() +"]'s id value is  null");} SqlInfo result = new SqlInfo();StringBuilder builder = new  StringBuilder("UPDATE  ");builder.append("\"").append(table.getName()).append("\"");builder.append("  SET ");for (KeyValue kv : keyValueList) { if (updateColumnNameSet ==  null || updateColumnNameSet.contains(kv.key)) {  builder.append("\"").append(kv.key).append("\"").append("=?,");result.addBindArg(kv);}  } builder.deleteCharAt(builder.length() -1);builder.append(" WHERE  ").append(WhereBuilder.b(id.getName(),"=",  idValue));result.setSql(builder.toString());return result;}

倒数第五行表明是依据对象主键的值来查找数据表中对应的行,使用更新语句,数据库实体类(JavaBean)被Column修饰的属性中必须要有isId修饰,而且还必须有值,否则会抛出DbException。拼接出的SQL语句类似于UPDATE  "tableName" SET "name"=?,"age"=? WHERE "ID" =  Ƈ'。其中的?表示占位符,在执行前被替换成具体的值。

示例代码:

DbManager db =x.getDb(daoConfig);WhereBuilder whereBuilder = WhereBuilder.b("name","=","一口仨馍").and("age","=","18");db.selector(Parent.class).where(whereBuilder).findAll();

WhereBuilder的作用是构建查找的SQL语句后半段。例如在select  * from parent where "name" = '一口仨馍' and "age" =  ཎ'中,WhereBuilder返回的字符串是”name” = ‘一口仨馍’ and “age” = ‘18’。

db.selector()

@OverridepublicSelectorselector(ClassentityType) throws DbException {returnSelector.from(this.getTable(entityType)); }staticSelectorfrom(TableEntitytable) {returnnewSelector(table); }privateSelector(TableEntitytable) {this.table = table; }

new了个Selector对象,除了赋值,啥也木干。

Selector.findAll()

publicListfindAll()throwsDbException {if(!table.tableIsExist())returnnull; Listresult =null; Cursor cursor = table.getDb().execQuery(this.toString());if(cursor !=null) {try{ result =newArrayList();while(cursor.moveToNext())  { T entity = CursorUtils.getEntity(table, cursor); result.add(entity); }  }catch(Throwable e) {thrownewDbException(e); }finally{  IOUtil.closeQuietly(cursor); } }returnresult; }

乍一看execQuery里的参数吓我一跳,传个this.toString()是什么鬼啊!!

Selector.findAll()

public  String toString() { StringBuilder result = new  StringBuilder();result.append("SELECT  ");result.append("*");result.append(" FROM  ").append("\"").append(table.getName()).append("\"");if (whereBuilder !=  null && whereBuilder.getWhereItemSize() >0) {  result.append(" WHERE ").append(whereBuilder.toString());} if  (orderByList != null && orderByList.size() >0) {  result.append(" ORDER BY ");for (OrderBy orderBy : orderByList) {  result.append(orderBy.toString()).append(',');}  result.deleteCharAt(result.length() -1);} if (limit >0) {  result.append(" LIMIT ").append(limit);result.append(" OFFSET  ").append(offset);} return result.toString();}

在这里拼接的SQL语句(手动冷漠脸)。可以看到查找也支持ORDER BY、LIMIT和OFFSET关键字。

总结

xUtils3的数据库模块,采用Table和Column注解修饰JavaBean,初始化的时候(实际是调用具体操作才会检查是否已经初始化,没有初始化才会执行初始化操作)会依据注解实例化相应的TableEntity和ColumnEntity并添加进缓存,执行增删改查时依据TableEntity和ColumnEntity拼接相应的SQL语句并执行。

原来没有看过ORM框架的源码,外加上自己数据库也渣的一匹,以为ORM框架多难了,以至于最后才分析xUtils3中的数据库模块。愿意看源码,实际稍微花点时间也能看出个大概。没经历会觉得似乎难以逾越,实际上也没有想象的那么难~

xUtils3四大模块到此就全部解析结束了。加上写作,前后大概花了一周工作时间,基本上把类翻了几遍,得益于框架功能比较全面,所以收获还是蛮多的。不敢说自己完全掌握了xUtils3的精髓,至少弄清了xUtils3的许多设计思想,而且从具体的编码中get到不少小技能。总体来说还是比较满意的。如果您看完四篇博客之后,仍有很多疑惑,建议对着博文思路同步阅读源码,实在有不好解决的问题,可以在下面留言,我尽量解答。感谢悉心阅读到最后~

 代码如下复制代码

热门栏目