Sugar ORM框架源码分析
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这个数据库表来判断是否存在,如果不存在,创建这个表,如果存在会比较出来数据库中表中不存在的字段,然后添加指定的字段。
- 判断表是否存在的方法
select count(*) from sqlite_master where type='table' and name='%s';
如果这个查询结果为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框架源码分析相关推荐
- Spark RPC框架源码分析(二)RPC运行时序
前情提要: Spark RPC框架源码分析(一)简述 一. Spark RPC概述 上一篇我们已经说明了Spark RPC框架的一个简单例子,Spark RPC相关的两个编程模型,Actor模型和Re ...
- Linux驱动修炼之道-SPI驱动框架源码分析(上)
Linux驱动修炼之道-SPI驱动框架源码分析(上) SPI协议是一种同步的串行数据连接标准,由摩托罗拉公司命名,可工作于全双工模式.相关通讯设备可工作于m/s模式.主设备发起数据帧,允许多个从设 ...
- Android框架源码分析——从设计模式角度看 Retrofit 核心源码
Android框架源码分析--从设计模式角度看 Retrofit 核心源码 Retrofit中用到了许多常见的设计模式:代理模式.外观模式.构建者模式等.我们将从这三种设计模式入手,分析 Retrof ...
- SPI驱动框架源码分析
SPI驱动框架源码分析 2013-04-12 16:13:08 分类: LINUX SPI驱动框架源码分析 SPI协议是一种同步的串行数据连接标准,由摩托罗拉公司命名,可工作于全双工模式.相关通讯设 ...
- php+yii框架,yii框架源码分析(一)
yii框架源码分析(一) 本文将对yii中的mvc,路由器,filter,组件机制等最主要的部分进行自己的一点浅析,力求说明自己做一个php mvc不是那么的遥不可及,其实是很简单的. 源码基于yii ...
- S3C24XX DMA框架源码分析
基于S3C2440 的DMA 框架源码分析 基于S3C2440 的DMA 框架源码分析 二寻根溯源 1 设备类的注册 2 s3c2410_dma_init 3 s3c24xx_dma_order_se ...
- golang gin框架源码分析(二)---- 渐入佳境 摸索Engine ServeHTTP访问前缀树真正原理
文章目录 全系列总结博客链接 前引 golang gin框架源码分析(二)---- 渐入佳境 摸索Engine ServeHTTP访问前缀树真正远原理 1.再列示例代码 从示例代码入手 2.r.Run ...
- Java集合类框架源码分析 之 LinkedList源码解析 【4】
上一篇介绍了ArrayList的源码分析[点击看文章],既然ArrayList都已经做了介绍,那么作为他同胞兄弟的LinkedList,当然必须也配拥有姓名! Talk is cheap,show m ...
- leaf框架源码分析
leaf框架源码分析 近来阅读leaf框架的代码,有些感悟,特来记录一番.既是一个总结,又是对后来阅读者的一个启发. 个人看代码的一个习惯,喜欢有上下文,因此会各个代码文件之间相互乱窜.但多看几次也就 ...
最新文章
- 5. Binary Tree Postorder Traversal
- SAP Spartacus Popover Directive 构造函数的用途分析
- c语言递归函数变量作用域,C语言课程变量的作用域和生存周期、递归.ppt
- Python+OpenCV:图像金字塔
- 高级政工师具备的能力_广东省固体废弃物利用处置能力评价资质申报指南
- java 密码加密_Java如何实现密码加密
- Android中字体文件位置
- 取暖器外贸出口到欧洲申请欧盟CE认证指令标准解析
- 苹果系统版本依次顺序_苹果手机排列顺序
- linux 运行.deb,Ubuntu下如何用命令行运行deb安装包
- 题目:输入一个字符串,输出该字符串中字符的所有组合。举个例子,如果输入abc,它的组合有a、b、c、ab、ac、bc、abc。
- Android Studio连接手机调试出现Session 'app': Error Installing APK
- BP神经网络推导(两个隐藏层)
- python canvas画弧度_编程作战丨如何利用python绘制可爱皮卡丘?
- mc通用计算机,《我的世界》里打造一台计算机有多难?复旦大神花一年终于实现!...
- 目前免费用可用的天气api接口及失效接口
- python调用IE浏览器进行数据批量下载小技巧
- java中string类型的哈希值是怎么计算的
- Docker练习安装
- 大学的多级放大电路,你交给老师了吗?
热门文章
- 织梦CMS安装的时候出现安全警告:Safe Alert: Request Error step 2 解决办法。
- Launcher开发——入门篇
- 防止被甩锅的几个建议
- 惠普暗夜精灵2怎么打开后盖_#原创新人# 拆机加内存 — HP 惠普 暗影精灵Ⅱ代 开箱...
- es6的杂乱的知识总结
- 华为手表watch3 eSIM一号双终端怎么开通,办理流程是什么?
- Oracle ceil函数
- tplogin服务器未响应,tplogin.cn登录地址无法打开显示解决办法
- 新浪微博android版,新浪微博Android版 登录之Logo欢迎页
- iOS 16怎么升级?iPhone升级iOS 16测试版图文教程