基本用法
LitePal
LitePal是一款开源的Android数据库框架,它采用了对象关系映射(ORM)的模式,LitePal很“轻”,jar包只有100k不到,使用起来也比较简单,源码地址为Github地址。
首先需要引入lib,可以通过gradle引入也可以将下载的litepal.jar包直接放入libs目录下。然后需要在assets目录下新建一个litepal.xml文件,文件名称不能随意更改,代码如下:
用于设定所有的映射模型,
然后还需要配置LitePalApplication,由于操作数据库时需要用到Context,而我们显然不希望在每个接口中都去传一遍这个参数,那样操作数据库就显得太繁琐了。因此,LitePal使用了一个方法来简化掉Context这个参数,只需要在AndroidManifest.xml中配置一下LitePalApplication,所有的数据库操作就都不用再传Context了。如果需要自定义application文件,只需继承LitePalApplication即可。
接下来需要创建Model类
public class News extends DataSupport {
private int id;
private String title;
private String content;
private Date publishDate;
private int commentCount;
// 自动生成get、set方法
...
}
继承了DataSupport类之后,这些实体类就拥有了进行CRUD操作的能力
News news = new News();
news.setTitle("这是一条新闻标题");
news.setContent("这是一条新闻内容");
news.setPublishDate(new Date());
news.save();
List newsList = DataSupport.select("title", "content")
.where("commentcount > ?", "0")
.order("publishdate desc").find(News.class);
LitePal还提供了直接用sql原句实现查询的api方法,扩展性也比较好。
AFinal
AFinal是一个Android的orm、ioc快速开发框架,里面包含了四大功能:控件的id绑定和事件绑定功能;网络图片的显示功能(里面包含了强大的缓存框架);数据库sqlite的操作功能;http数据的读取功能(支持ajax方式读取),可从Github AFinal获取源码。数据库FinalDb是其中的一个组件,使用比较简单。
首先需要创建model类,承担对象与数据库表的映射功能。
@Table(name="table_user")
public class User {
@id
private int id;
private String name;
private String email;
private Date birth;
// getter and setter不能省略
...
}
声明@Table注解,表示表名称为table_user;@id表示id作为该表自动增长的主键,接下来就可以直接使用了。
FinalDb db = FinalDb.create(this, "afinal_db");
User user = new User();
user.setEmail("[email protected]");
user.setName("探索者");
user.setBirth(new Date());
db.save(user);
String name = "探索者";
List userList = db.findAllByWhere(User.class, "name="' + name + "'" );//查询用户名为探索者的用户
if(userList.size() > 0){
User other = userList.get(0);
other.setEmail("[email protected]");
db.update(other);
}
以上就是AFinal中关于数据库的简单用法,不需要额外的配置,api接口也比较简单易用。
greenDAO
greenDAO与上述两种ORM框架不同,其原理不是根据反射进行数据库的各项操作,而是一开始就人工生成业务需要的Model和DAO文件,业务中可以直接调用相应的DAO文件进行数据库操作,从而避免了因反射带来的性能损耗和效率低下。但是由于需要人工生成model和DAO文件,所以greenDAO的配置就略显复杂。
首先需要新建一个java工程来生成DAO类文件,该工程需要导入greendao-generator.jar和freemarker.jar文件到项目中,github官方源码中已经提供了该工程。
public class ExampleDaoGenerator
{
public static void main(String[] args) throws Exception
{
Schema schema = new Schema(3, "de.greenrobot.daoexample");
addNote(schema);
addCustomerOrder(schema);
new DaoGenerator().generateAll(schema, "../DaoExample/src-gen");
}
private static void addNote(Schema schema)
{
Entity note = schema.addEntity("Note");
note.addIdProperty();
note.addStringProperty("text").notNull();
note.addStringProperty("comment");
note.addDateProperty("date");
}
private static void addCustomerOrder(Schema schema)
{
Entity customer = schema.addEntity("Customer");
customer.addIdProperty();
customer.addStringProperty("name").notNull();
Entity order = schema.addEntity("Order");
order.setTableName("ORDERS"); // "ORDER" is a reserved keyword
order.addIdProperty();
Property orderDate = order.addDateProperty("date").getProperty();
Property customerId = order.addLongProperty("customerId").notNull().getProperty();
order.addToOne(customer, customerId);
ToMany customerToOrders = customer.addToMany(order, customerId);
customerToOrders.setName("orders");
customerToOrders.orderAsc(orderDate);
}
}
在main方法中,
Schema schema = new Schema(3, "de.greenrobot.daoexample");
该方法第一个参数用来更新数据库版本号,第二个参数为要生成的DAO类所在包路径。然后进行建表和设置要生成DAO文件的目标工程的项目路径。
addNote(schema);
addCustomerOrder(schema);
new DaoGenerator().generateAll(schema, "../DaoExample/src-gen");
其中src-gen这个目录名需要在运行前手动创建,否则会报错。运行后出现以下的提示说明DAO文件自动生成成功了,刷新一下DaoExample项目即可看到。
Written /Users/duanyangyang/Documents/Android/greenDAO/DaoExample/src/main/java/de/greenrobot/daoexample/NoteDao.java
Written /Users/duanyangyang/Documents/Android/greenDAO/DaoExample/src/main/java/de/greenrobot/daoexample/Note.java
Written /Users/duanyangyang/Documents/Android/greenDAO/DaoExample/src/main/java/de/greenrobot/daoexample/CustomerDao.java
Written /Users/duanyangyang/Documents/Android/greenDAO/DaoExample/src/main/java/de/greenrobot/daoexample/Customer.java
Written /Users/duanyangyang/Documents/Android/greenDAO/DaoExample/src/main/java/de/greenrobot/daoexample/OrderDao.java
Written /Users/duanyangyang/Documents/Android/greenDAO/DaoExample/src/main/java/de/greenrobot/daoexample/Order.java
Written /Users/duanyangyang/Documents/Android/greenDAO/DaoExample/src/main/java/de/greenrobot/daoexample/DaoMaster.java
Written /Users/duanyangyang/Documents/Android/greenDAO/DaoExample/src/main/java/de/greenrobot/daoexample/DaoSession.java
Processed 3 entities in 268ms
然后具体的使用就比较简单了
DevOpenHelper helper = new DaoMaster.DevOpenHelper(this, "notes-db", null);
db = helper.getWritableDatabase();
daoMaster = new DaoMaster(db);
daoSession = daoMaster.newSession();
noteDao = daoSession.getNoteDao();
String textColumn = NoteDao.Properties.Text.columnName;
String orderBy = textColumn + " COLLATE LOCALIZED ASC";
cursor = db.query(noteDao.getTablename(), noteDao.getAllColumns(), null, null, null, null, orderBy);
String[] from = { textColumn, NoteDao.Properties.Comment.columnName };
int[] to = { android.R.id.text1, android.R.id.text2 };
SimpleCursorAdapter adapter = new SimpleCursorAdapter(this, android.R.layout.simple_list_item_2, cursor, from,
to);
setListAdapter(adapter);
三种数据库框架性能比较
为了比较三种数据库框架的性能,首先选择同一款手机华为Mate7,然后从以下几个方面分析比较:
循环插入10000次,每次插入一条数据,三种框架用时为:
LitePal 155313ms;
AFinal 110601ms;
greedDAO 98238ms;
批量插入10000条数据,三种框架用时为:
LitePal 12882ms;
AFinal 2783ms;
greedDAO 623ms;
循环更新10000次,每次更新一条数据,三种框架用时为:
LitePal 152395ms;
AFinal 4123ms;
greedDAO 97840ms;
批量更新10000条数据,三种框架用时为:
LitePal 12234ms;
AFinal 1535ms;
greedDAO 739ms;
循环查询10000条数据,每次查询一条数据,三种框架用时为:
LitePal 13806ms;
AFinal 4117ms;
greedDAO 31923ms;
批量查询10000条数据,三种框架用时为:
LitePal 6863ms;
AFinal 1585ms;
greedDAO 149ms;
根据以上数据可以得出以下几个结论:
- 不管是批量插入、更新、查询,greenDAO都是用时最短,执行速度最快的;
- 大部分数据库操作的情况下,LitePal都是用时最长的,只有在循环查询时表现比greenDAO好
- AFinal对于很多批量操作没有提供相应的api方法
- 不管对于哪个框架,批量操作比循环操作用时都要短很多
下面是以greenDAO为例的测试源码
private void testInsertQueryTime(final int count){
noteList1 = new ArrayList();
for (int i = 0; i < count; i++){
Note note = new Note(null, "title", "comment", new Date());
noteList1.add(note);
}
noteList2 = new ArrayList();
for (int i = 0; i < count; i++){
Note note = new Note(null, "title", "comment", new Date());
noteList2.add(note);
}
new Thread(){
@Override
public void run() {
super.run();
long time1 = System.currentTimeMillis();
for(Note note : noteList1){
noteDao.insert(note);
}
// 循环插入10000次,每次插入一条数据
long time = System.currentTimeMillis() - time1;
Log.d("NoteActivity", "greedDAO one by one insert " + noteList1.size() + " data, use time " + time + "ms");
// 批量插入10000条数据
long time2 = System.currentTimeMillis();
noteDao.insertInTx((Note[])noteList2.toArray(new Note[noteList2.size()]));
long time3 = System.currentTimeMillis() - time2;
Log.d("NoteActivity", "greedDAO batch insert " + noteList2.size() + " data, use time " + time3 + "ms");
// 循环查询10000条数据,每次查询一条数据
long time4 = System.currentTimeMillis();
for(int i = 0; i < count; i++){
noteDao.queryBuilder().limit(1).list();
}
long time5 = System.currentTimeMillis() - time4;
Log.d("NoteActivity", "greedDAO one by one query " + count + " data, use time " + time5 + "ms");
// 批量查询10000条数据
long time6 = System.currentTimeMillis();
List list2 = noteDao.queryBuilder().limit(10000).offset(-1).list();
long time7 = System.currentTimeMillis() - time6;
Log.d("NoteActivity", "greedDAO batch query " + list2.size() + " data, use time " + time7 + "ms");
// 循环更新10000次,每次更新一条数据
long time8 = System.currentTimeMillis();
for (Note note : noteList1){
note.setText("update_title");
note.setComment("update_comment");
noteDao.update(note);
}
// 批量更新10000条数据
long time9 = System.currentTimeMillis() - time8;
Log.d("NoteActivity", "greedDAO one by one update " + count + " data, use time " + time9 + "ms");
for (Note note : noteList2){
note.setText("update_title");
note.setComment("update_comment");
}
long time10 = System.currentTimeMillis();
noteDao.updateInTx(noteList2);
long time11 = System.currentTimeMillis() - time10;
Log.d("NoteActivity", "greedDAO batch update " + count + " data, use time " + time11 + "ms");
}
}.start();
}
三种数据库框架的原理和源码分析
LitePal
LitePal通过LitePal.xml文件获取数据库的名称、版本号以及表,然后创建数据库和表,要执行增删改查操作时,就会根据数据model进行参数拼装,最后调用系统原生的数据库操作。解析litepal.xml文件方法源码如下:
private InputStream getConfigInputStream() throws IOException {
AssetManager assetManager = LitePalApplication.getContext().getAssets();
String[] fileNames = assetManager.list("");
if (fileNames != null && fileNames.length > 0) {
for (String fileName : fileNames) {
if (Const.LitePal.CONFIGURATION_FILE_NAME.equalsIgnoreCase(fileName)) {
return assetManager.open(fileName, AssetManager.ACCESS_BUFFER);
}
}
}
throw new ParseConfigurationFileException(
ParseConfigurationFileException.CAN_NOT_FIND_LITEPAL_FILE);
}
void usePullParse() {
try {
LitePalAttr litePalAttr = LitePalAttr.getInstance();
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
XmlPullParser xmlPullParser = factory.newPullParser();
xmlPullParser.setInput(getConfigInputStream(), "UTF-8");
int eventType = xmlPullParser.getEventType();
while (eventType != XmlPullParser.END_DOCUMENT) {
String nodeName = xmlPullParser.getName();
switch (eventType) {
case XmlPullParser.START_TAG: {
if (NODE_DB_NAME.equals(nodeName)) {
String dbName = xmlPullParser.getAttributeValue("", ATTR_VALUE);
litePalAttr.setDbName(dbName);
} else if (NODE_VERSION.equals(nodeName)) {
String version = xmlPullParser.getAttributeValue("", ATTR_VALUE);
litePalAttr.setVersion(Integer.parseInt(version));
} else if (NODE_MAPPING.equals(nodeName)) {
String className = xmlPullParser.getAttributeValue("", ATTR_CLASS);
litePalAttr.addClassName(className);
} else if (NODE_CASES.equals(nodeName)) {
String cases = xmlPullParser.getAttributeValue("", ATTR_VALUE);
litePalAttr.setCases(cases);
}
break;
}
default:
break;
}
eventType = xmlPullParser.next();
}
} catch (XmlPullParserException e) {
throw new ParseConfigurationFileException(
ParseConfigurationFileException.FILE_FORMAT_IS_NOT_CORRECT);
} catch (IOException e) {
throw new ParseConfigurationFileException(ParseConfigurationFileException.IO_EXCEPTION);
}
}
LitePal框架会根据model自动创建好相应的数据表,以及表数据类型和非空约束等。
protected String generateCreateTableSQL(String tableName, List columnModels,
boolean autoIncrementId) {
StringBuilder createTableSQL = new StringBuilder("create table ");
createTableSQL.append(tableName).append(" (");
if (autoIncrementId) {
createTableSQL.append("id integer primary key autoincrement,");
}
if (columnModels.size() == 0) {
createTableSQL.deleteCharAt(createTableSQL.length() - 1);
}
boolean needSeparator = false;
for (ColumnModel columnModel : columnModels) {
if (columnModel.isIdColumn()) {
continue;
}
if (needSeparator) {
createTableSQL.append(", ");
}
needSeparator = true;
createTableSQL.append(columnModel.getColumnName()).append(" ").append(columnModel.getColumnType());
if (!columnModel.isNullable()) {
createTableSQL.append(" not null");
}
if (columnModel.isUnique()) {
createTableSQL.append(" unique");
}
String defaultValue = columnModel.getDefaultValue();
if (!TextUtils.isEmpty(defaultValue)) {
createTableSQL.append(" default ").append(defaultValue);
}
}
createTableSQL.append(")");
LogUtil.d(TAG, "create table sql is >> " + createTableSQL);
return createTableSQL.toString();
}
要执行增删改查操作的数据model都会继承DataSupport,以查询方法findAll()为例,DataSupport会先调用QueryHandler.onFindAll()方法,然后最后会执行到QueryHandler.query方法,该方法传入的参数有类名、列名称、查询条件、排序等等,经过处理后调用SDLiteDatabase.query方法,最后将查询得到的数据转换成List并返回,源码如下:
protected List query(Class modelClass, String[] columns, String selection,
String[] selectionArgs, String groupBy, String having, String orderBy, String limit,
List foreignKeyAssociations) {
List dataList = new ArrayList();
Cursor cursor = null;
try {
List supportedFields = getSupportedFields(modelClass.getName());
String tableName = getTableName(modelClass);
String[] customizedColumns = getCustomizedColumns(columns, foreignKeyAssociations);
cursor = mDatabase.query(tableName, customizedColumns, selection, selectionArgs,
groupBy, having, orderBy, limit);
if (cursor.moveToFirst()) {
SparseArray queryInfoCacheSparseArray = new SparseArray();
do {
T modelInstance = (T) createInstanceFromClass(modelClass);
giveBaseObjIdValue((DataSupport) modelInstance,
cursor.getLong(cursor.getColumnIndexOrThrow("id")));
setValueToModel(modelInstance, supportedFields, foreignKeyAssociations, cursor, queryInfoCacheSparseArray);
if (foreignKeyAssociations != null) {
setAssociatedModel((DataSupport) modelInstance);
}
dataList.add(modelInstance);
} while (cursor.moveToNext());
queryInfoCacheSparseArray.clear();
}
return dataList;
} catch (Exception e) {
e.printStackTrace();
throw new DataSupportException(e.getMessage());
} finally {
if (cursor != null) {
cursor.close();
}
}
}
可以看到LitePal不管是创建数据库、表还是执行增删改查都是根据Model的类名和属性名,每次都需要进行反射拼装然后调用Android原生的数据库操作或者直接执行sql语句。
AFinal
AFinal本质上也是利用java的反射原理实现对象与数据表的映射的,实现上和LitePal有很多不同。AFinal并未提前创建数据库和表,而是在第一次执行增删改查方法时,先判断数据库和表是否存在,如果不存在的话就根据model执行sql语句进行创建,创建完成后再拼装具体的操作sql语句,完成相应的增删改查操作。相关源码如下:
public void save(Object entity) {
checkTableExist(entity.getClass());
exeSqlInfo(SqlBuilder.buildInsertSql(entity));
}
private void checkTableExist(Class> clazz) {
if (!tableIsExist(TableInfo.get(clazz))) {
String sql = SqlBuilder.getCreatTableSQL(clazz);
debugSql(sql);
db.execSQL(sql);
}
}
public static String getCreatTableSQL(Class> clazz) {
TableInfo table = TableInfo.get(clazz);
Id id = table.getId();
StringBuffer strSQL = new StringBuffer();
strSQL.append("CREATE TABLE IF NOT EXISTS ");
strSQL.append(table.getTableName());
strSQL.append(" ( ");
Class> primaryClazz = id.getDataType();
if (primaryClazz == int.class || primaryClazz == Integer.class
|| primaryClazz == long.class || primaryClazz == Long.class) {
strSQL.append(id.getColumn()).append(" INTEGER PRIMARY KEY AUTOINCREMENT,");
} else {
strSQL.append(id.getColumn()).append(" TEXT PRIMARY KEY,");
}
Collection propertys = table.propertyMap.values();
for (Property property : propertys) {
strSQL.append(property.getColumn());
Class> dataType = property.getDataType();
if (dataType == int.class || dataType == Integer.class
|| dataType == long.class || dataType == Long.class) {
strSQL.append(" INTEGER");
} else if (dataType == float.class || dataType == Float.class
|| dataType == double.class || dataType == Double.class) {
strSQL.append(" REAL");
} else if (dataType == boolean.class || dataType == Boolean.class) {
strSQL.append(" NUMERIC");
}
strSQL.append(",");
}
Collection manyToOnes = table.manyToOneMap.values();
for (ManyToOne manyToOne : manyToOnes) {
strSQL.append(manyToOne.getColumn())
.append(" INTEGER")
.append(",");
}
strSQL.deleteCharAt(strSQL.length() - 1);
strSQL.append(" )");
return strSQL.toString();
}
greenDAO
greenDAO与上述两种ORM框架不同,其原理不是根据反射进行数据库的各项操作,而是一开始就人工生成业务需要的Model和DAO文件,业务中可以直接调用相应的DAO文件进行数据库的增删改查操作,从而避免了因反射带来的性能损耗和效率低下。以查询为例,其代码如下:
DevOpenHelper helper = new DaoMaster.DevOpenHelper(this, "notes-db", null);
db = helper.getWritableDatabase();
daoMaster = new DaoMaster(db);
daoSession = daoMaster.newSession();
noteDao = daoSession.getNoteDao();
cursor = db.query(noteDao.getTablename(), noteDao.getAllColumns(), null, null, null, null, orderBy);
首先先创建数据库,然后在SQLiteOpenHelper.onCreate方法中根据已生成的model创建所有的表,而db.query其实就是Android原生的查询操作,只不过参数是经过DAO文件处理过的,无需手动匹配。其他数据库操作与查询雷同。