源码探索系列4---数据库ORM框架之Ormlite解析

在做项目时候,有使用Ormlite来做数据库部分,以提高开发速度。

OrmLite - Lightweight Object Relational Mapping (ORM) Java Package

这个ORM框架在一般数据量不大的情况下,是一个很好的工具,他封装得很好,使用起来也很方便!
相比于原始的Sqlite来说,使用后,很容易回不去了,
因为你不再需要敲一堆的sql语句,担心那个字段写错,处理cursor,写各种查询等等!
重要的是在增加一些字段属性时候,不再需要改到哭了!

他提供了很多Builder模式设计的接口,方便我们进行CRUD操作,这真的用起来很友好啊。
当然这写ORM都是这样的友好

如果你用的还是原生的Sqlite,最数据的速度没有那么高的要求(虽然我们实际也经常是开一个线程去加载的),没听说过ORM的,建议你采用下Ormlite
它类似于Hibernate和Mybatis等等,在提高开发效率的同时,也可以有效避免数据库操作对应用带来的潜在影响。不要再去写那些 Magic Number和Xxx了 ,我对他的广告就到这里吧,对于他的使用,可以查看这篇文章

现在刚好有点时间,就开始解析整个项目的代码,看下他背后是怎么做到这些功能的!

起航 -- 创建表

Ormlite版本:V4.48

万事开头难,这个库已经挺成熟也庞大的了。就让我们从创建表格开始说起吧
如果你用过,对这句应该很熟悉

@Override
    public void onCreate(SQLiteDatabase db, ConnectionSource connectionSource) {
        try {
            TableUtils.createTable(connectionSource, User.class);
}

那他背后到底怎么做到创建表的呢?
我猜可能就是根据获取类的注解,来获取各个字段,最后拼凑成sql语句的,让我看下源码吧

public static <T> int createTable(ConnectionSource connectionSource, Class<T> dataClass) throws SQLException {
    return createTable(connectionSource, dataClass, false);
}

private static <T, ID> int createTable(ConnectionSource connectionSource, Class<T> dataClass, boolean ifNotExists) throws SQLException {
    Dao dao = DaoManager.createDao(connectionSource, dataClass);
    if(dao instanceof BaseDaoImpl) {
        return doCreateTable(connectionSource, ((BaseDaoImpl)dao).getTableInfo(), ifNotExists);
    } else {
        TableInfo tableInfo = new TableInfo(connectionSource, (BaseDaoImpl)null, dataClass);
        return doCreateTable(connectionSource, tableInfo, ifNotExists);
    }
}

他会先去创建dao,同时这个dao是用容器单例的模式来做的,所以后面我们继续调用这个Dao的时候,可以快速的获得,得到Dao后,他去创建Table,相关代码如下

 public static synchronized <D extends Dao<T, ?>, T> D createDao(
                 ConnectionSource connectionSource, Class<T> clazz) throws SQLException {
 
  
        DaoManager.ClassConnectionSource key = new DaoManager.ClassConnectionSource(connectionSource, clazz);
        
        Dao dao = lookupDao(key);
        if(dao != null) {
            return dao;
        }  
        ...
          
}              

private static <T> Dao<?, ?> lookupDao(DaoManager.ClassConnectionSource key) {
    if(classMap == null) {
        classMap = new HashMap();
    }

    Dao dao = (Dao)classMap.get(key);
    return dao == null?null:dao;
}

创建表:

private static <T, ID> int doCreateTable(ConnectionSource connectionSource, TableInfo<T, ID> tableInfo, boolean ifNotExists) throws SQLException {
    DatabaseType databaseType = connectionSource.getDatabaseType();
    logger.info("creating table \'{}\'", tableInfo.getTableName());
    ArrayList statements = new ArrayList();
    ArrayList queriesAfter = new ArrayList();
    addCreateTableStatements(databaseType, tableInfo, statements, queriesAfter, ifNotExists);
    DatabaseConnection connection = connectionSource.getReadWriteConnection();

    int var8;
    try {
        int stmtC = doStatements(connection, "create", statements, false, databaseType.isCreateTableReturnsNegative(), databaseType.isCreateTableReturnsZero());
        stmtC += doCreateTestQueries(connection, databaseType, queriesAfter);
        var8 = stmtC;
    } finally {
        connectionSource.releaseConnection(connection);
    }

    return var8;
}

这里有需要注意的

  1. addCreateTableStatements(databaseType, tableInfo, statements, queriesAfter, ifNotExists);
    这句负责去生成我们要执行的sql语句。

     private static <T, ID> void addCreateTableStatements(DatabaseType databaseType, TableInfo<T, ID> tableInfo, List<String> statements, List<String> queriesAfter, boolean ifNotExists) throws SQLException {
        StringBuilder sb = new StringBuilder(256);
        sb.append("CREATE TABLE ");
        if(ifNotExists && databaseType.isCreateIfNotExistsSupported()) {
            sb.append("IF NOT EXISTS ");
        }
    
        databaseType.appendEscapedEntityName(sb, tableInfo.getTableName());
        sb.append(" (");
        ArrayList additionalArgs = new ArrayList();
        ArrayList statementsBefore = new ArrayList();
        ArrayList statementsAfter = new ArrayList();
        boolean first = true;
        FieldType[] i$ = tableInfo.getFieldTypes();
        int arg = i$.length;
    
        for(int i$1 = 0; i$1 < arg; ++i$1) {
            FieldType fieldType = i$[i$1];
            if(!fieldType.isForeignCollection()) {
                if(first) {
                    first = false;
                } else {
                    sb.append(", ");
                }
    
                String columnDefinition = fieldType.getColumnDefinition();
                if(columnDefinition == null) {
                    databaseType.appendColumnArg(tableInfo.getTableName(), sb, fieldType, additionalArgs, statementsBefore, statementsAfter, queriesAfter);
                } else {
                    databaseType.appendEscapedEntityName(sb, fieldType.getColumnName());
                    sb.append(' ').append(columnDefinition).append(' ');
                }
            }
        }
    
        databaseType.addPrimaryKeySql(tableInfo.getFieldTypes(), additionalArgs, statementsBefore, statementsAfter, queriesAfter);
        databaseType.addUniqueComboSql(tableInfo.getFieldTypes(), additionalArgs, statementsBefore, statementsAfter, queriesAfter);
        Iterator var16 = additionalArgs.iterator();
    
        while(var16.hasNext()) {
            String var15 = (String)var16.next();
            sb.append(", ").append(var15);
        }
    
        sb.append(") ");
        databaseType.appendCreateTableSuffix(sb);
        statements.addAll(statementsBefore);
        statements.add(sb.toString());
        statements.addAll(statementsAfter);
        addCreateIndexStatements(databaseType, tableInfo, statements, ifNotExists, false);
        addCreateIndexStatements(databaseType, tableInfo, statements, ifNotExists, true);
    }
    
  2. **int stmtC = doStatements(connection, "create", statements, false, **
    databaseType.isCreateTableReturnsNegative(), databaseType.isCreateTableReturnsZero());
    这句负责去执行我们的sql语句

     private static int doStatements(DatabaseConnection connection, String label, Collection<String> statements, boolean ignoreErrors, boolean returnsNegative, boolean expectingZero) throws SQLException {
     
        int stmtC = 0;
    
        for(Iterator i$ = statements.iterator(); i$.hasNext(); ++stmtC) {
            String statement = (String)i$.next();
            int rowC = 0;
            CompiledStatement compiledStmt = null;
    
                compiledStmt = connection.compileStatement(statement, StatementType.EXECUTE, noFieldTypes, -1);
                rowC = compiledStmt.runExecute();
                logger.info("executed {} table statement changed {} rows: {}", label, Integer.valueOf(rowC), statement); 
            }
    
           ...
    
        return stmtC;
    }
    

    其中 connection.compileStatement(),这个connection从前面可以看到在
    DatabaseConnection connection =connectionSource.getReadWriteConnection();
    ConnectionSource给他的。而 DatabaseConnection是一个接口,他的具体实现是AndroidConnectionSource,出现在OrmLiteSqliteOpenHelper 的构造函数里面。

    我们继续看下这个getReadWriteConnection() 里面返回了什么,因为这是把OrmliteSqlite连接上的地方

    public DatabaseConnection getReadWriteConnection() throws SQLException {
    
                ...
                
          SQLiteDatabase db;
          if(this.sqliteDatabase == null) { 
                   ....
              db = this.helper.getWritableDatabase();
          }else{                         
              db = this.sqliteDatabase;
          }
    
          this.connection = new AndroidDatabaseConnection(db, true, this.cancelQueriesEnabled);
          if(connectionProxyFactory != null) {
              this.connection = connectionProxyFactory.createProxy(this.connection);
          } 
          
          return this.connection;
       
    }
    

我们清楚的看到,他把我们的SQLiteDatabase给抓了,重新包装成了AndroidDatabaseConnection类,然后返回,而这个是实现了DatabaseConnection接口的,感觉像一个代理模型啊。
既然找到别后实际干活的人,那我们继续看下我们原来的那个
compiledStmt = connection.compileStatement(statement, StatementType.EXECUTE, noFieldTypes, -1);
里面到底做了什么。

    public CompiledStatement compileStatement(String statement, StatementType type, FieldType[] argFieldTypes, int resultFlags) {
    
            AndroidCompiledStatement stmt = new AndroidCompiledStatement(statement, this.db, type, this.cancelQueriesEnabled);
     
            return stmt;
        }

我们看到的是他只是简单的把他封装成了一个AndroidCompiledStatement类,同时把我们的SQLiteDatabase DB也送给他,交给他去干活 。另外他的命名也很有规则啊,涉及到具体于安卓相关的干活的人,都加个AndroidXXX的格式,这个则具体实现了CompiledStatement接口。

      public class AndroidCompiledStatement implements CompiledStatement 

我们继续看那个runExecute()方法的内容

    public int runExecute() throws SQLException {
       ...
       return execSql(this.db, "runExecute", this.sql, this.getArgArray()); 
    }

好啦,看了这么久,终于看到一个背后具体干活去执行语句,创建我们的表的地方啦

    static int execSql(SQLiteDatabase db, String label, String finalSql, Object[] argArray) {         
      
        db.execSQL(finalSql, argArray);
       ...
    
        SQLiteStatement stmt = null;        
        int result;
     
        stmt = db.compileStatement("SELECT CHANGES()");
        result = (int)stmt.simpleQueryForLong();          
        if(stmt != null) {
            stmt.close();
        }        
    
        return result;
    }

到这里,我们的第一步,创建表的过程就了解。
他把所有的关于操作数据库的部分用接口写处理啊,然后加一个AndroidXXX,来表示具体实现,由它负责去干活,从而解耦,很好的面向接口编程的方式。

四大神兽---CRUD

看完了创建表的功能,接下来我们了解下基本的四大神兽增删查改---CRUD

增加(Create)

先从我们的看到的第一句开始做入口

DataHelper helper = new DataHelper(this);        
helper.getUserDao().create(mUser);

这里,我们的Dao类也是一个借口,我们重新找真正干活的,不知道你在上面的流程有没注意到他的实现类是出现过的,就在我们创建表的时候,看到了一个BaseDaoImpl,居然不是AndroidDao的样子,不科学.

private static <T, ID> int createTable(ConnectionSource connectionSource, Class<T> dataClass, boolean ifNotExists) throws SQLException {
    Dao dao = DaoManager.createDao(connectionSource, dataClass);
    if(dao instanceof BaseDaoImpl) {
        return doCreateTable(connectionSource, ((BaseDaoImpl)dao).getTableInfo(), ifNotExists);
    } else {
        TableInfo tableInfo = new TableInfo(connectionSource, (BaseDaoImpl)null, dataClass);
        return doCreateTable(connectionSource, tableInfo, ifNotExists);
    }
}

就是这里提示了我们是BaseDaoImpl,我们就去这里,看下背后干了什么

public int create(T data) throws SQLException {

    ...
     
    DatabaseConnection connection1 = this.connectionSource.getReadWriteConnection();

    int var3;
    try {
        var3 = this.statementExecutor.create(connection1, data, this.objectCache);
    } finally {
        this.connectionSource.releaseConnection(connection1);
    }

    return var3;  
}

我们看到了些熟悉的东西,DatabaseConnection connection1 = this.connectionSource.getReadWriteConnection();
获得与数据库的连接,然后this.statementExecutor.create(connection1, data, this.objectCache);这个执行语句。

public int create(DatabaseConnection databaseConnection, T data, ObjectCache objectCache) throws SQLException {
    if(this.mappedInsert == null) {
        this.mappedInsert = MappedCreate.build(this.databaseType, this.tableInfo);
    }

    return this.mappedInsert.insert(this.databaseType, databaseConnection, data, objectCache);
}

这里有一点需要注意的,就是这个MappedCreate.build(),他负责的工作和他名字一样,是做Map工作的,build()函数他根据传给他的表信息tableInfo,生成了相关的插入Sql语句。交给后面去执行
经过处理后,我们继续看插入操作

public int insert(DatabaseType databaseType, DatabaseConnection databaseConnection, T data, ObjectCache objectCache) throws SQLException {
     
    ...
        
   rowC = databaseConnection.insert(this.statement, var14, this.argFieldTypes, keyHolder);
    ...             

}

很好,这个databaseConnection我们知道,前面遇到过他了,AndroidDatabaseConnection是他的具体实现,我们去他的insert看看,很可能就是实际插入的地方

public int insert(String statement, Object[] args, FieldType[] argFieldTypes, GeneratedKeyHolder keyHolder) throws SQLException {

    SQLiteStatement stmt = null;
    byte var9; 
    
    stmt = this.db.compileStatement(statement);
    this.bindArgs(stmt, args, argFieldTypes);
    long e = stmt.executeInsert();

    ...
    byte result = 1;      
    var9 = result;     
     ...
    return var9;
}

看到这里,整个语句也就结束了, 他这里使用的方式是
stmt = this.db.compileStatement(statement);然后加参数bindArgs()的形式去执的。

读取(Retrieve)

下次补充。。。
这个和前面不一样

更新(Update)

前面的步骤和Create的差不多,就不多余,在我们的最后的AndroidDatabaseConnection里面有一点不一样,

public int update(String statement, Object[] args, FieldType[] argFieldTypes) throws SQLException {
    return this.update(statement, args, argFieldTypes, "updated");
} 

 private int update(String statement, Object[] args, FieldType[] argFieldTypes, String label) throws SQLException {
    SQLiteStatement stmt = null;
    ...
   
    stmt = this.db.compileStatement(statement);
    this.bindArgs(stmt, args, argFieldTypes);
    stmt.execute();
    
    int result;
     
    stmt = this.db.compileStatement("SELECT CHANGES()");
    result = (int)stmt.simpleQueryForLong();
     ...
     
    return result;
}

删除(Delete)

前面的步骤和Create的差不多,就不重复,不一样的地方在于他是重用了update函数的

public int delete(String statement, Object[] args, FieldType[] argFieldTypes) throws SQLException {
    return this.update(statement, args, argFieldTypes, "deleted");
}

后记

时间限制,今天就先写到这类把。。。
更多内容再做补充

  1. 关于CRUD操作的Buildr模式
  2. 直接的查询工作。
  3. 还有解析注解部分,生成表的工作
  4. 各个类之间调用的类关系图等

参考资料:

Ormlite官网

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,186评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,858评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,620评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,888评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,009评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,149评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,204评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,956评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,385评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,698评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,863评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,544评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,185评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,899评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,141评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,684评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,750评论 2 351

推荐阅读更多精彩内容