在做项目时候,有使用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;
}
注意 注意 注意
说到这里,需要重点强调下!
有些人为了省去在自己的ormliteHelper
里面写各种的getXXxDao,用下面的方法:
例如那个鸿大神的Android 快速开发系列 ORMLite 框架最佳实践
public synchronized Dao getDao(Class clazz) throws SQLException
{
Dao dao = null;
String className = clazz.getSimpleName();
if (daos.containsKey(className))
{
dao = daos.get(className);
}
if (dao == null)
{
dao = super.getDao(clazz);
daos.put(className, dao);
}
return dao;
}
显然是没必要的,因为我们看到内部实现已经写了,用一个HashMap
去保存了,但不得不说一般人不看源代码,哪里知道这事!他的最佳实践写得还有很多可以优化的地方,下次有空再把自己写的贴上来!最少要把各种操作给封装以下,免得下次要换掉ORMLite
怎办?
创建表:
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;
}
这里有需要注意的
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); }
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() 里面返回了什么,因为这是把
Ormlite
和Sqlite
连接上的地方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");
}
后记
时间限制,今天就先写到这类把。。。
更多内容再做补充
- 关于CRUD操作的Buildr模式
- 直接的查询工作。
- 还有解析注解部分,生成表的工作
- 各个类之间调用的类关系图等
参考资料: