该篇博客会对ormlite的性能优化做一些总结,包括使用ormlite的config文件、WAL、以及一些性能优化总结点。

一、使用ormlite的config文件

使用ormlite_config文件来创建数据库和dao,可以减少初始化数据库的开销。
1.原理
ormlite的数据库创建会调用TableUtils.createTableIfNotExists(connectionSource, LaunchAdEO.class);方法,其中又会调用到

1
2
3
4
5
6
7
8
9
10
private static <T, ID> int createTable(ConnectionSource connectionSource, Class<T> dataClass, boolean ifNotExists)
throws SQLException {

Dao<T, ID> dao = DaoManager.createDao(connectionSource, dataClass);
if (dao instanceof BaseDaoImpl<?, ?>) {
return doCreateTable(connectionSource, ((BaseDaoImpl<?, ?>) dao).getTableInfo(), ifNotExists);
} else {
TableInfo<T, ID> tableInfo = new TableInfo<T, ID>(connectionSource, null, dataClass);
return doCreateTable(connectionSource, tableInfo, ifNotExists);
}
}

在其中的DaoManager.createDao(connectionSource, dataClass)方法中会进行dao创建,其中的逻辑是先会调用到DaoManager的createDaoFromConfig方法,如果返回值不为null,那么就会直接返回,否则会通过耗时的反射来创建dao。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* Creates the DAO if we have config information cached and caches the DAO.
*/

private static <D, T> D createDaoFromConfig(ConnectionSource connectionSource, Class<T> clazz) throws SQLException {
// no loaded configs
if (configMap == null) {
return null;
}

@SuppressWarnings("unchecked")
DatabaseTableConfig<T> config = (DatabaseTableConfig<T>) configMap.get(clazz);
// if we don't config information cached return null
if (config == null) {
return null;
}

// else create a DAO using configuration
Dao<T, ?> configedDao = doCreateDao(connectionSource, config);
@SuppressWarnings("unchecked")
D castDao = (D) configedDao;
return castDao;
}

那么这里的configMap又是在哪里被填充进去的呢?
搜索代码发现是在DaoManager的addCachedDatabaseConfig中完成的,而该方法是在OrmLiteSqliteOpenHelper的构造函数。

1
2
3
4
public OrmLiteSqliteOpenHelper(Context context, String databaseName, SQLiteDatabase.CursorFactory factory, int databaseVersion,
File configFile) {
this(context, databaseName, factory, databaseVersion, openFile(configFile));
}

或者

1
2
3
4
public OrmLiteSqliteOpenHelper(Context context, String databaseName, SQLiteDatabase.CursorFactory factory, int databaseVersion,
int configFileId) {
this(context, databaseName, factory, databaseVersion, openFileId(context, configFileId));
}

中完成的调用,该构造函数会调用到下面的代码,也就是在构造函数中完成配置文件的读取,并最终生成DatabaseTableConfig的对象

1
2
BufferedReader reader = new BufferedReader(new InputStreamReader(stream), 4096);
DaoManager.addCachedDatabaseConfigs(DatabaseTableConfigLoader.loadDatabaseConfigFromReader(reader));

总结:ormlite,在调用openHelper时,如果不传入配置文件,会默认使用耗时的反射来定义数据库的名称和字段

2.如何生成配置文件
在官方文档[1]的提示下,在类OrmliteConfigUtil的方法writeConfigForFile

1
2
3
4
5
public class DatabaseConfigUtil extends OrmLiteConfigUtil {
public static void main(String[] args) throws Exception {
writeConfigFile("ormlite_config.txt");
}
}

中会调用到OrmliteConfigUtil的writeConfigForTable方法。
该方法会扫描所有的.java文件,效率比较差,我们可以给出需要写入config文件中的java类名。

1
2
3
4
5
6
7
8
public class DatabaseConfigUtil extends OrmLiteConfigUtil {
private static final Class<?>[] classes = new Class[] {
SimpleData.class,
};
public static void main(String[] args) throws Exception {
writeConfigFile("ormlite_config.txt", classes);
}
}

当我们运行完上述工具类,我们就会在raw resource folder下面得到config文件,同时,也会生成R.java文件,这样我们就可以在用

1
2
3
4
public OrmLiteSqliteOpenHelper(Context context, String databaseName, SQLiteDatabase.CursorFactory factory, int databaseVersion,
int configFileId) {
this(context, databaseName, factory, databaseVersion, openFileId(context, configFileId));
}

构造函数中传入R.raw.ormlite_config这个configFileld参数。

按照[2]进行操作,
但注意出现了类似错误Exception in thread “main” java.lang.ClassNotFoundException: my.package.DatabaseConfigUtil

如何解决?
首先我们得在app/src/main/res/raw/ 创建文件,因为我们在DBHHelper的构造函数中已经引入了R.raw.ormlite_config了
然后Edit Configuration中的before launch中去掉make
在Working directory中进行$MODULE_DIR$/src/main的配置
同时我们需要compile project(‘:ormlite’) 也就是编译包含DatabaseConfigUtil的项目,这点非常重要
在writeConfigFile(“ormlite_config.txt”, classes);时直接写出ormlite文件即可。
如果还是出现Exception in thread “main” java.lang.ClassNotFoundException错误,可以考虑将配置删除以后,再运行并更改配置。

二、一些tips
1.将数据库的任何操作放在启动页之后来进行,防止在启动页中由于调用到了getWritableDatabase()方法来进行耗时的数据库初始化以及更新操作。
2.ormlite使用批处理,包括dao.callbatchtasks,或者直接使用TransactionManager.callInTransaction。其中dao.callbatchtasks在内部也是调用callInTransaction的。
3.
SQLite日志[6][8]
开启

1
2
3
4
5
adb shell setprop log.tag.SQLiteLog V
adb shell setprop log.tag.SQLiteStatements V
adb shell setprop log.tag.SQLiteTime V
adb shell stop
adb shell start

上述语句也可单独使用 其中第三条更有用一些 代表打印时间
并且需要杀掉进程重启

关闭 注意这里为中文的双引号

1
2
3
4
5
adb shell setprop log.tag.SQLiteLog “”
adb shell setprop log.tag.SQLiteStatements “”
adb shell setprop log.tag.SQLiteTime “”
adb shell stop
adb shell start

4.复杂的实体数据,可以考虑使用executeRaw来执行原生sql
getDao().executeRaw()
5.增加数据库cache,在插入或者更新数据后,将数据保存在内存中,下次只要从内存中取即可。
6.直接使用cursor减少不必要的映射。

1
2
3
4
5
6
7
8
9
10
11
12
public Cursor getCursorOfAll() {
QueryBuilder<EntityType, Long> qb = getDao().queryBuilder();
Where<EntityType, Long> where = qb.where();
try {
where.eq("deleteTime", 0);
CloseableIterator<EntityType> iterator = getDao().iterator(where.prepare());
AndroidDatabaseResults results = (AndroidDatabaseResults) iterator.getRawResults();
return results.getRawCursor();
} catch (Exception e) {
return null;
}
}

参考文献
[1]http://ormlite.com/javadoc/ormlite-core/doc-files/ormlite_4.html#Config-Optimization
[2]http://stackoverflow.com/questions/17298773/android-studio-run-configuration-for-ormlite-config-generation
[3]https://www.sqlite.org/wal.html
[4]http://stackoverflow.com/questions/34059173/exception-write-ahead-logging-wal-mode-cannot-be-enabled-or-disabled-while-th
[5]http://stackoverflow.com/questions/12909489/ormlite-performance-on-4-0-3-ics
[6]http://blog.chengyunfeng.com/?p=583
[7]https://www.sqlite.org/wal.html
[8]http://stackoverflow.com/questions/29022059/log-query-time-in-sqlite-on-android