Sugar目前来看是一个不错的ORM框架,我们分析下它的框架设计吧,通过分析框架一定能加深我们对代码的理解。

Sugar的使用

http://satyan.github.io/sugar/getting-started.html

整体来看Sugar的好处就是简单,毕竟它是基于反射实现的,但是可能会造成一定的性能问题。

下面边根据使用来分析下实现。

Mainfest文件中配置数据库名称和版本

<application android:label="@string/app_name" android:icon="@drawable/icon"
android:name="com.orm.SugarApp">
.
.
<meta-data android:name="DATABASE" android:value="sugar_example.db" />
<meta-data android:name="VERSION" android:value="2" />
<meta-data android:name="QUERY_LOG" android:value="true" />
<meta-data android:name="DOMAIN_PACKAGE_NAME" android:value="com.example" />
.
.
</application>

这个地方主要用途就是处理数据库创建和数据库升级

在Mainfest文件中的VERSION表示当前的数据库版本,同时将通过Asset文件中的sugar_upgrades目录下的.sql进行升级,这个是对数据库升级的设计。

这个图片表示当前的数据库版本是3,对小于3版本的数据库将执行文件中的sql进行升级。

Meta标签的解析

在AndroidMainfest.xml文件中配置的DB信息会通过这个ManifestHelper类进行解析

比如在获取当前数据库版本时,就是使用这个类从Meta标签中获取的。

  //Prevent instantiationprivate SugarDb() {super(getContext(), getDbName(), new SugarCursorFactory(ManifestHelper.isDebugEnabled()), getDatabaseVersion());schemaGenerator = SchemaGenerator.getInstance();}// getDatabaseVersion()中的获取meta标签/*** Grabs the database version from the manifest.** @return the database version as specified by the {@link #METADATA_VERSION} version or 1 of*         not present*/public static int getDatabaseVersion() {Integer databaseVersion = getMetaDataInteger(METADATA_VERSION);if ((databaseVersion == null) || (databaseVersion == 0)) {databaseVersion = 1;}return databaseVersion;}private static Integer getMetaDataInteger(String name) {PackageManager pm = getPackageManager();Integer value = null;try {ApplicationInfo ai = pm.getApplicationInfo(getPackageName(), PackageManager.GET_META_DATA);value = ai.metaData.getInt(name);} catch (Exception e) {if (ManifestHelper.isDebugEnabled()) {Log.d(LOG_TAG, "Couldn't find config value: " + name);}}return value;}

其他Meta标签都是这么解析的,都是使用PackageManager获取到ApplicationInfo然后获取到meta对象,然后使用对应的getXXX()方法获取相应的类型。

Sugar的初始化过程

Sugar应该在Appication创建的时候进行初始化(找不到合适的时候了)

// Suagr的初始化代码
public class SugarApp extends Application {@Overridepublic void onCreate() {super.onCreate();SugarContext.init(this);}@Overridepublic void onTerminate() {super.onTerminate();SugarContext.terminate();}}

Suagr#init()

  public static void init(Context context) {ContextUtil.init(context);// 初始化单例对象instance = new SugarContext();dbConfiguration = null;}private SugarContext() {// 这个是SQLiteOpenHelper对象// 用来获取SQLiteDatabase实例this.sugarDb = SugarDb.getInstance();// 对Entity进行缓存this.entitiesMap = Collections.synchronizedMap(new WeakHashMap<Object, Long>());}

SugarDb初始化过程

private SugarDb() {super(getContext(), getDbName(), new SugarCursorFactory(ManifestHelper.isDebugEnabled()), getDatabaseVersion());schemaGenerator = SchemaGenerator.getInstance();}

这里创建了一个CursorFactory,通过这个抽象工厂来创建自定义的Cursor,这里我们只是进行打LOG

 /*** Used to allow returning sub-classes of {@link Cursor} when calling query.*/public interface CursorFactory {/*** See {@link SQLiteCursor#SQLiteCursor(SQLiteCursorDriver, String, SQLiteQuery)}.*/public Cursor newCursor(SQLiteDatabase db,SQLiteCursorDriver masterQuery, String editTable,SQLiteQuery query);}
public class SugarCursorFactory implements SQLiteDatabase.CursorFactory {private boolean debugEnabled;public SugarCursorFactory() {this.debugEnabled = false;}public SugarCursorFactory(boolean debugEnabled) {this.debugEnabled = debugEnabled;}// 创建自定义的Cursor,添加LOG功能public Cursor newCursor(SQLiteDatabase sqLiteDatabase,SQLiteCursorDriver sqLiteCursorDriver,String editTable,SQLiteQuery sqLiteQuery) {if (debugEnabled) {Log.d("SQL Log", sqLiteQuery.toString());}return new SQLiteCursor(sqLiteDatabase, sqLiteCursorDriver, editTable, sqLiteQuery);}}

同时初始化了schemaGenerator,将通过这个类实现数据库创建和升级。

SchemaGenerator处理数据库升级的过程

在SQLiteOpenHelper这个onCreate()回调中处理新用户的数据库安装过程

 // 创建数据库中的所有表public void createDatabase(SQLiteDatabase sqLiteDatabase) {List<Class> domainClasses = getDomainClasses();for (Class domain : domainClasses) {// 创建数据库各表createTable(domain, sqLiteDatabase);// 执行在表创建后的SQL语句afterTableCreated(domain,sqLiteDatabase);}}

getDomainClasses()是一个工具方法,作用是遍历Dex文件,找到里面所有的类,并且找到所有SugarRecord或者使用@Table注解表示的类,然后使用反射去获取它们的字段,创建到一个表中。

    //  根据获取到的Class对象,然后根据对应的字段创建出建表的SQL语句protected void createTable(Class<?> table, SQLiteDatabase sqLiteDatabase) {// 生成创建表的SQLString createSQL = createTableSQL(table);if (!createSQL.isEmpty()) {try {sqLiteDatabase.execSQL(createSQL);} catch (SQLException e) {e.printStackTrace();}}}
    // 根据Class生成SQL语句private String createTableSQL(Class<?> table) {if(ManifestHelper.isDebugEnabled()) {Log.i(SUGAR, "Create table if not exists");}List<Field> fields = ReflectionUtil.getTableFields(table);String tableName = NamingHelper.toTableName(table);if(KeyWordUtil.isKeyword(tableName)) {if(ManifestHelper.isDebugEnabled()) {Log.i(SUGAR, "ERROR, SQLITE RESERVED WORD USED IN " + tableName);}}StringBuilder sb = new StringBuilder("CREATE TABLE IF NOT EXISTS ");sb.append(tableName).append(" ( ID INTEGER PRIMARY KEY AUTOINCREMENT ");// 遍历每一个Field,根据类型生成语句for (Field column : fields) {String columnName = NamingHelper.toColumnName(column);String columnType = QueryBuilder.getColumnType(column.getType());if (columnType != null) {// 会忽略id字段,这个应该是一个不好的设计,会破坏用户的数据类型if (columnName.equalsIgnoreCase("Id")) {continue;}// 对Field判断是否存在某个注解的方法if (column.isAnnotationPresent(Column.class)) {Column columnAnnotation = column.getAnnotation(Column.class);columnName = columnAnnotation.name();sb.append(", ").append(columnName).append(" ").append(columnType);if (columnAnnotation.notNull()) {if (columnType.endsWith(NULL)) {sb.delete(sb.length() - 5, sb.length());}sb.append(NOT_NULL);}if (columnAnnotation.unique()) {sb.append(UNIQUE);}} else {sb.append(", ").append(columnName).append(" ").append(columnType);if (column.isAnnotationPresent(NotNull.class)) {if (columnType.endsWith(NULL)) {sb.delete(sb.length() - 5, sb.length());}sb.append(NOT_NULL);}if (column.isAnnotationPresent(Unique.class)) {sb.append(UNIQUE);}}}}if (table.isAnnotationPresent(MultiUnique.class)) {String constraint = table.getAnnotation(MultiUnique.class).value();sb.append(", UNIQUE(");String[] constraintFields = constraint.split(",");for(int i = 0; i < constraintFields.length; i++) {String columnName = NamingHelper.toSQLNameDefault(constraintFields[i]);sb.append(columnName);if(i < (constraintFields.length -1)) {sb.append(",");}}sb.append(") ON CONFLICT REPLACE");}sb.append(" ) ");if(ManifestHelper.isDebugEnabled()) {Log.i(SUGAR, "Creating table " + tableName);}return sb.toString();}

SchemaGenerator处理数据库升级

    public void doUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion) {List<Class> domainClasses = getDomainClasses();// 判断表是否存在的SQL语句String sql = "select count(*) from sqlite_master where type='table' and name='%s';";// Sugar 会自动比较当前数据库版本和新需要的字段// 然后决定是创建表还是添加字段for (Class domain : domainClasses) {// 遍历每一个Schema类String tableName = NamingHelper.toTableName(domain);Cursor c = sqLiteDatabase.rawQuery(String.format(sql, tableName), null);if (c.moveToFirst() && c.getInt(0) == 0) {createTable(domain, sqLiteDatabase);} else {addColumns(domain, sqLiteDatabase);}c.close();}executeSugarUpgrade(sqLiteDatabase, oldVersion, newVersion);}

数据库升级也比较复杂,首先先查询sqlite_master这个数据库表来判断是否存在,如果不存在,创建这个表,如果存在会比较出来数据库中表中不存在的字段,然后添加指定的字段。

  1. 判断表是否存在的方法
select count(*) from sqlite_master where type='table' and name='%s';

如果这个查询结果为1,那么表就是存在的。

  1. 判断字段是否存在
protected ArrayList<String> getColumnNames(SQLiteDatabase sqLiteDatabase, String tableName) {// 首先进行一次查询final Cursor resultsQuery = sqLiteDatabase.query(tableName, null, null, null, null, null, null);//Check if columns match vs the one on the domain classArrayList<String> columnNames = new ArrayList<>();// 找出所有的字段名for (int i = 0; i < resultsQuery.getColumnCount(); i++) {String columnName = resultsQuery.getColumnName(i);columnNames.add(columnName);}resultsQuery.close();return columnNames;}

使用一次查询获取所有的字段名,然后与当前的所有字段名做差集就是已经存在的字段。

数据库升级和创建均可以执行自定义的SQL语句

上面两个文件为创建表和升级后执行的SQL语句。

Sugar插入数据

Sugar插入数据非常简单,调用SugarRecord的save()方法即可。

 public long save() {return save(getSugarDataBase(), this);}

save()的工作原理是使用反射获取这个对象的所有字段,然后将它们映射成ContentValues,存入数据库。
每个Pojo对象Sugar都会生成一个id,这个ID在表中是默认的自增主键(Sugar已指定),如果存在相同的情况,会进行覆盖。

static long save(SQLiteDatabase db, Object object) {//  获取缓存的字段?Map<Object, Long> entitiesMap = getSugarContext().getEntitiesMap();// 根据这个对象List<Field> columns = ReflectionUtil.getTableFields(object.getClass());ContentValues values = new ContentValues(columns.size());Field idField = null;for (Field column : columns) {ReflectionUtil.addFieldValueToColumn(values, column, object, entitiesMap);if (column.getName().equals("id")) {idField = column;}}boolean isSugarEntity = isSugarEntity(object.getClass());if (isSugarEntity && entitiesMap.containsKey(object)) {values.put("id", entitiesMap.get(object));}long id = db.insertWithOnConflict(NamingHelper.toTableName(object.getClass()), null, values,SQLiteDatabase.CONFLICT_REPLACE);if (object.getClass().isAnnotationPresent(Table.class)) {// 使用反射更新id字段if (idField != null) {idField.setAccessible(true);try {idField.set(object, id);} catch (IllegalAccessException e) {e.printStackTrace();}} else {// 将id字段存储在表中entitiesMap.put(object, id);}} else if (SugarRecord.class.isAssignableFrom(object.getClass())) {// 将Id更新到实体类中((SugarRecord) object).setId(id);}if (ManifestHelper.isDebugEnabled()) {Log.i(SUGAR, object.getClass().getSimpleName() + " saved : " + id);}return id;}

Sugar查询数据

Sugar的查询流程是这样的,首先拼接出SQL语句,然后使用SQLiteDatabase进行查询,最后根据Cursor使用反射生成这个Entity对象,核心代码如下:

public static <T> List<T> getEntitiesFromCursor(Cursor cursor, Class<T> type, String relationFieldName, Object relationObject){T entity;List<T> result = new ArrayList<>();try {while (cursor.moveToNext()) {// 使用反射创建一个对象entity = type.getDeclaredConstructor().newInstance();new EntityInflater().withCursor(cursor) //  获取Cursor对象.withObject(entity).withEntitiesMap(getSugarContext().getEntitiesMap()).withRelationFieldName(relationFieldName).withRelationObject(relationObject).inflate();result.add(entity);}} catch (Exception e) {e.printStackTrace();} finally {cursor.close();}return result;}// EntityInflater中的inflate()public void inflate() {List<Field> columns = ReflectionUtil.getTableFields(object.getClass());Long objectId = cursor.getLong(cursor.getColumnIndex(("ID")));if (!entitiesMap.containsKey(object)) {entitiesMap.put(object, objectId);}FieldInflater fieldInflater;for (Field field : columns) {field.setAccessible(true);Class<?> fieldType = field.getType();if (SugarRecord.isSugarEntity(fieldType)) {if (field.getName().equals(relationFieldName)) {fieldInflater = new RelationEntityFieldInflater(field, cursor, object, fieldType, relationObject);} else {fieldInflater = new EntityFieldInflater(field, cursor, object, fieldType);}} else if (fieldType.equals(List.class)) {fieldInflater = new ListFieldInflater(field, cursor, object, fieldType);} else {fieldInflater = new DefaultFieldInflater(field, cursor, object, fieldType);}fieldInflater.inflate();}}

总结

Sugar的源码还是很简单的,类结构也很清晰,使用也是相当的方便。这次分析我们主要分析了Sugar在数据库升级上的工作,在CRUD上没有过多的分析细节。Sugar也是有一些可以改进之处,比如说Pojo对象id字段无法自定义,因为Sugar将这个作为自增的主键。

Sugar ORM框架源码分析相关推荐

  1. Spark RPC框架源码分析(二)RPC运行时序

    前情提要: Spark RPC框架源码分析(一)简述 一. Spark RPC概述 上一篇我们已经说明了Spark RPC框架的一个简单例子,Spark RPC相关的两个编程模型,Actor模型和Re ...

  2. Linux驱动修炼之道-SPI驱动框架源码分析(上)

    Linux驱动修炼之道-SPI驱动框架源码分析(上)   SPI协议是一种同步的串行数据连接标准,由摩托罗拉公司命名,可工作于全双工模式.相关通讯设备可工作于m/s模式.主设备发起数据帧,允许多个从设 ...

  3. Android框架源码分析——从设计模式角度看 Retrofit 核心源码

    Android框架源码分析--从设计模式角度看 Retrofit 核心源码 Retrofit中用到了许多常见的设计模式:代理模式.外观模式.构建者模式等.我们将从这三种设计模式入手,分析 Retrof ...

  4. SPI驱动框架源码分析

     SPI驱动框架源码分析 2013-04-12 16:13:08 分类: LINUX SPI驱动框架源码分析 SPI协议是一种同步的串行数据连接标准,由摩托罗拉公司命名,可工作于全双工模式.相关通讯设 ...

  5. php+yii框架,yii框架源码分析(一)

    yii框架源码分析(一) 本文将对yii中的mvc,路由器,filter,组件机制等最主要的部分进行自己的一点浅析,力求说明自己做一个php mvc不是那么的遥不可及,其实是很简单的. 源码基于yii ...

  6. S3C24XX DMA框架源码分析

    基于S3C2440 的DMA 框架源码分析 基于S3C2440 的DMA 框架源码分析 二寻根溯源 1 设备类的注册 2 s3c2410_dma_init 3 s3c24xx_dma_order_se ...

  7. golang gin框架源码分析(二)---- 渐入佳境 摸索Engine ServeHTTP访问前缀树真正原理

    文章目录 全系列总结博客链接 前引 golang gin框架源码分析(二)---- 渐入佳境 摸索Engine ServeHTTP访问前缀树真正远原理 1.再列示例代码 从示例代码入手 2.r.Run ...

  8. Java集合类框架源码分析 之 LinkedList源码解析 【4】

    上一篇介绍了ArrayList的源码分析[点击看文章],既然ArrayList都已经做了介绍,那么作为他同胞兄弟的LinkedList,当然必须也配拥有姓名! Talk is cheap,show m ...

  9. leaf框架源码分析

    leaf框架源码分析 近来阅读leaf框架的代码,有些感悟,特来记录一番.既是一个总结,又是对后来阅读者的一个启发. 个人看代码的一个习惯,喜欢有上下文,因此会各个代码文件之间相互乱窜.但多看几次也就 ...

最新文章

  1. 5. Binary Tree Postorder Traversal
  2. SAP Spartacus Popover Directive 构造函数的用途分析
  3. c语言递归函数变量作用域,C语言课程变量的作用域和生存周期、递归.ppt
  4. Python+OpenCV:图像金字塔
  5. 高级政工师具备的能力_广东省固体废弃物利用处置能力评价资质申报指南
  6. java 密码加密_Java如何实现密码加密
  7. Android中字体文件位置
  8. 取暖器外贸出口到欧洲申请欧盟CE认证指令标准解析
  9. 苹果系统版本依次顺序_苹果手机排列顺序
  10. linux 运行.deb,Ubuntu下如何用命令行运行deb安装包
  11. 题目:输入一个字符串,输出该字符串中字符的所有组合。举个例子,如果输入abc,它的组合有a、b、c、ab、ac、bc、abc。
  12. Android Studio连接手机调试出现Session 'app': Error Installing APK
  13. BP神经网络推导(两个隐藏层)
  14. python canvas画弧度_编程作战丨如何利用python绘制可爱皮卡丘?
  15. mc通用计算机,《我的世界》里打造一台计算机有多难?复旦大神花一年终于实现!...
  16. 目前免费用可用的天气api接口及失效接口
  17. python调用IE浏览器进行数据批量下载小技巧
  18. java中string类型的哈希值是怎么计算的
  19. Docker练习安装
  20. 大学的多级放大电路,你交给老师了吗?

热门文章

  1. 织梦CMS安装的时候出现安全警告:Safe Alert: Request Error step 2 解决办法。
  2. Launcher开发——入门篇
  3. 防止被甩锅的几个建议
  4. 惠普暗夜精灵2怎么打开后盖_#原创新人# 拆机加内存 — HP 惠普 暗影精灵Ⅱ代 开箱...
  5. es6的杂乱的知识总结
  6. 华为手表watch3 eSIM一号双终端怎么开通,办理流程是什么?
  7. Oracle ceil函数
  8. tplogin服务器未响应,tplogin.cn登录地址无法打开显示解决办法
  9. 新浪微博android版,新浪微博Android版 登录之Logo欢迎页
  10. iOS 16怎么升级?iPhone升级iOS 16测试版图文教程