Android中数据库操作框架(ActiveAndroid、ormlite、greenDAO、DBFlow、Realm)介绍及使用详情
介绍
无论是ios平台还是Android平台,底层数据库都是基于开源的SQLite实现,然后在系统层封装成用于应用层的API。虽然直接使用系统的数据库API性能很高,但是这些API接口并不是很方便开发者使用,一不小心就会引入Bugs,而且代码的视觉效果也不好,为了解决这个问题,一系列的对象关系映射(ORM)框架出现,这其中比较出名的且广泛使用的有:ActiveAndroid、ormlite、DBFlow和greenDAO。
ActiveAndroid
ActiveAndroid是一种Active Record 风格的ORM框架,它可以极大的简化数据库的使用,使用面向对象的方式管理数据库,告别手写SQL历史。每一个数据库表都可以映射为一个类,开发者只需要使用类似save()或者delete()这样的函数即可。
一、添加依赖,把下面的代码添加到工程的build.gradle中:
repositories { mavenCentral() maven { url "https://oss.sonatype.org/content/repositories/snapshots/" } } compile 'com.michaelpardo:activeandroid:3.1.0-SNAPSHOT'
二,配置与初始化:
1,在清单文件中配置数据库名称和数据库版本号:
通过mete-data来配置数据库名称和数据库版本号,其中AA_DB_NAME为数据库名称,AA_DB_VERSION为数据库版本号,默认为1,AA_MODELS为对应的模型。
自定义Application,需要让你的Application对象继承自com.activeandroid.app.Application而不android.app.Application。然后在清单文件的applicatin下通过name来引用,这样就完成了ActiveAndroid的初始化工作。
public class MyApp extends Application { //为了兼容5.0以上的手机,需添加下面的代码 @Override public void onCreate() { super.onCreate(); Configuration.Builder builder = new Configuration.Builder(this); //手动的添加模型类 builder.addModelClasses(User.class); ActiveAndroid.initialize(builder.create()); } }
然后在清单文件的applicatin下通过name来引用:
android:name=".MyApp"
创建Model类 (必须要加空构造函数且super()不然会有时报错)
/** * Created by MG_ZXC on 2018/2/4. * 1,必须继承Model,继承后就不用声明主键了 * 2,按照表名添加字段 * 3,添加注释 */ @Table(name = "user") public class User extends Model { @Column private String userName; @Column private int userId; @Column private int age; @Column private String addr; public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public User() { super(); } public int getUserId() { return userId; } public void setUserId(int userId) { this.userId = userId; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getAddr() { return addr; } public void setAddr(String addr) { this.addr = addr; } @Override public String toString() { return "User{" + "userName='" + userName + '\'' + ", userId=" + userId + ", age=" + age + ", addr='" + addr + '\'' + '}'; } }
操作数据库的工具类
/** * Created by MG_ZXC on 2018/2/4. * 操作数据库的工具类 */ public class DbManager { //添加数据 public void insertUser(User user){ //操作数据库的对象就是实体类本身 user.save(); } //删除数据 public void deleteUser(User user){ user.delete(); } //更新数据 public void updateUser(User user){ user.setUserName("关羽"); user.setAddr("北京"); //在activeandroid中save既可以创建,也可以修改 user.save(); } //查询所有的数据 public List queryUser(){ List execute = new Select() .from(User.class) //model类 .execute(); return execute; } //条件查询 public List query(String userName){ List queryName = new Select() .from(User.class) .where("userName = ?", userName) //查询条件 .execute(); return queryName; } //条件删除 public void delete(String userName){ new Delete() .from(User.class) .where("userName = ?", userName) .execute(); } //条件更新 public void update(String userName){ Update update = new Update(User.class); update.set("addr = ?","上海") .where("userName=?",userName) .execute(); } }
使用:
增加:
User user = new User(); user.setAddr("信阳"); user.setAge(28); user.setUserId(1); user.setUserName("张飞"); dbManager.insertUser(user);
查询:
users = dbManager.query("张飞");
删除:
dbManager.delete("张飞");
更新:
dbManager.update("张飞");
数据库升级
上面的代码完全能够实现ActiveAndroid的功能了,但ActiveAndroid的数据库升级不像其他数据库升级那么简单,只需改变版本号就行了,下面介绍一下ActiveAndroid的版本升级的步骤:
1,先把数据库版本改为要升级到的版本,如改为2
2,在工程下新建assets/migrations,在该目录下增加一个修改过得版本号,比如我现在的版本号升级为2,则文件夹名为2.sql。
3,在2.sql文件夹下使用sql语句来编写版本升级新增的功能,如新增了一个字段:
ALTER TABLE user ADD COLUMN Count INTEGER;
OK,到这里ActiveAndroid的使用已经完成了。
通过GitHub上的ActiveAndroid在14年已经停止更新,而且本人在使用ActiveAndroid过程中发现,在高版本Android 中会报一些”莫名其妙”的Bug.
ormlite
ormlite是Java平台的一个ORM框架,支持JDBC链接,Spring和Android平台,在Android中使用包含两部分。简述: 优点: 1.轻量级;2.使用简单,易上手;3.封装完善;4.文档全面。缺点:1.基于反射,效率较低(本人还没有觉得效率低);2.缺少中文翻译文档
ormlite-core:核心模块,无论在哪个平台使用,都必须基于这个核心库,是实现ORM映射的关键模块。 ormlite-android:基于ormlite-core封装的针对Android平台的适配模块,Android开发中主要跟这个模块打交道。定义Bean类
@DatabaseTable(tableName = "user") public class User { @DatabaseField(useGetSet=true, columnName = "userName") private String userName; //generatedId定义主键自增长,columnName定义该字段在数据库中的列名 @DatabaseField(useGetSet=true,generatedId=true,columnName="id") private int userId; @DatabaseField(useGetSet=true, columnName = "age") private int age; @DatabaseField(useGetSet=true, columnName = "addr") private String addr; @Override public String toString() { return "User{" + "userName='" + userName + '\'' + ", userId=" + userId + ", age=" + age + ", addr='" + addr + '\'' + '}'; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public int getUserId() { return userId; } public void setUserId(int userId) { this.userId = userId; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getAddr() { return addr; } public void setAddr(String addr) { this.addr = addr; } }
SQLiteOpenHelper类
原生的数据库操作,都需要继承SQLiteOpenHelper,而ormlite我们则需要继承OrmLiteSqliteOpenHelper
public class DatabaseHelper extends OrmLiteSqliteOpenHelper { private static final String TABLE_NAME = "ormlite.db";//默认是在data/data/包名/databases/路径下 private static final int DB_VERSION = 1; private Map daos = new HashMap(); private DatabaseHelper(Context context) { super(context, context.getFilesDir().getAbsolutePath()+TABLE_NAME, null, DB_VERSION); } @Override public void onCreate(SQLiteDatabase database, ConnectionSource connectionSource) { try { TableUtils.createTableIfNotExists(connectionSource, User.class); } catch (SQLException e) { e.printStackTrace(); } } @Override public void onUpgrade(SQLiteDatabase database, ConnectionSource connectionSource, int oldVersion, int newVersion) { //整表删除创建 // TableUtils.dropTable(); onCreate(database, connectionSource); /* //更新数据库时只需添加新增字段 if (newVersion == 3) { //数据库、表名、列名、类型 DatabaseUtil.updateColumn(database, "", "", "VARCHAR", null); }*/ } private static DatabaseHelper instance; /** * 单例获取该Helper * * @param context * @return */ public static synchronized DatabaseHelper getHelper(Context context) { context = context.getApplicationContext(); if (instance == null) { synchronized (DatabaseHelper.class) { if (instance == null) instance = new DatabaseHelper(context); } } return instance; } 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; } /** * 释放资源 */ @Override public void close() { super.close(); for (String key : daos.keySet()) { Dao dao = daos.get(key); dao = null; } } }
DatabaseUtil
public class DatabaseUtil { public static void updateColumn(SQLiteDatabase db, String tableName, String columnName, String columnType, Object defaultField) { try { if (db != null) { Cursor c = db.rawQuery("SELECT * from " + tableName + " limit 1 ", null); boolean flag = false; if (c != null) { for (int i = 0; i < c.getColumnCount(); i++) { if (columnName.equalsIgnoreCase(c.getColumnName(i))) { flag = true; break; } } if (flag == false) { String sql = "alter table " + tableName + " add " + columnName + " " + columnType + " default " + defaultField; db.execSQL(sql); } c.close(); } } } catch (Exception e) { e.printStackTrace(); } } public static void deleteRecordsWithoutForeignKey(SQLiteDatabase db, String tableName){ if (db != null) { String sql = "DELETE from " + tableName; db.execSQL(sql); } } }
编写Dao类
我们可以将需要的增、删、改、查等方法都放在Dao里,便于操作使用
public class DBDao { private Dao userDao; private DatabaseHelper helper; public DBDao(Context contex) { try { helper = DatabaseHelper.getHelper(contex); userDao = helper.getDao(User.class); } catch (Exception e) { e.printStackTrace(); } } /** * 增 * @param */ public void addUser(User user) { try { userDao.create(user); } catch (SQLException e) { e.printStackTrace(); } } /** * 删(通过实体) * @param */ public void delUser(User user) { try { userDao.delete(user); } catch (SQLException e) { e.printStackTrace(); } } /** * 删(通过id) * @param id */ public void delUserById(Integer id) { try { userDao.deleteById(id); } catch (SQLException e) { e.printStackTrace(); } } /** * 改 * @param user */ public void updateUser(User user) { try { userDao.update(user); } catch (SQLException e) { e.printStackTrace(); } } /** * 查 * @return */ public List queryAllUser() { ArrayList users = null; try { users = (ArrayList) userDao.queryForAll(); } catch (SQLException e) { e.printStackTrace(); } return users; } /** * 获取user * @param id user编号 * @return */ public User getUser(Integer id) { try { //父母信息为空 return userDao.queryForId(id); } catch (SQLException e) { e.printStackTrace(); return null; } } }
greenDAO
greenDAO是一个轻量化且快速的ORM框架,专门为Android高度优化和定制的,它能够支持每秒数千记录的CRUD操作。我们从官网上面一张Benchmark图可以看出它与ormlite和ActiveAndroid的性能对比,
添加依赖:
// In your root build.gradle file: buildscript { repositories { jcenter() mavenCentral() // add repository } dependencies { classpath 'com.android.tools.build:gradle:2.3.3' classpath 'org.greenrobot:greendao-gradle-plugin:3.2.2' // add plugin } } // In your app projects build.gradle file: apply plugin: 'com.android.application' apply plugin: 'org.greenrobot.greendao' // apply plugin dependencies { compile 'org.greenrobot:greendao:3.2.2' // add library }
实体类生成
@Entity public class User { @Id(autoincrement = true) private Long userId; @Property(nameInDb = "userName") private String userName; @Property(nameInDb = "age") private int age; @Property(nameInDb = "addr") private String addr; @Generated(hash = 2044495610) public User(Long userId, String userName, int age, String addr) { this.userId = userId; this.userName = userName; this.age = age; this.addr = addr; } @Generated(hash = 586692638) public User() { } public Long getUserId() { return this.userId; } public void setUserId(Long userId) { this.userId = userId; } public String getUserName() { return this.userName; } public void setUserName(String userName) { this.userName = userName; } public int getAge() { return this.age; } public void setAge(int age) { this.age = age; } public String getAddr() { return this.addr; } public void setAddr(String addr) { this.addr = addr; } @Override public String toString() { return "User{" + "userId=" + userId + ", userName='" + userName + '\'' + ", age=" + age + ", addr='" + addr + '\'' + '}'; } }
@Entity 表明这个实体类会在数据库中生成一个与之相对应的表。
@Id 对应数据表中的 Id 字段,有了解数据库的话,是一条数据的唯一标识。且数据类型必须为Long类型
@Property(nameInDb = “STUDENTNUM”) 表名这个属性对应数据表中的 STUDENTNUM 字段。
@Property 可以自定义字段名,注意外键不能使用该属性
@NotNull 该属性值不能为空
@Transient 该属性不会被存入数据库中
@Unique 表名该属性在数据库中只能有唯一值
当你写完实体类中的属性之后,点击编译,就会自动生成相应的 setter 和 getter 方法,至于那些 hash 值是自动赋值上去的。并且在该目录下生成 DaoMaster 和 DaoSession 这两个类用于初始化数据库。
增删改查
一般数据库的操作都离不开增删改查,那么我们就从这开始。
初始化
DaoMaster.DevOpenHelper devOpenHelper = new DaoMaster.DevOpenHelper(context, "user", null); DaoMaster daoMaster = new DaoMaster(devOpenHelper.getWritableDb()); daoSession = daoMaster.newSession();
如果实体类有更新,那么要调用 daoSession.clear() 清除缓存,才能得到更新。
增
public void add(User user){ UserDao userDao = daoSession.getUserDao(); userDao.insert(user); }
删
public void delete(User user){ UserDao userDao = daoSession.getUserDao(); userDao.delete(user);//通过实体删除 //userDao.deleteByKey(user.getUserId());//通过主键删除 }
改:
public void update(User user){ UserDao userDao = daoSession.getUserDao(); userDao.update(user); }
查:
public List query(User user){ UserDao userDao = daoSession.getUserDao(); List users=userDao.queryBuilder() .offset(1) .limit(3) .orderAsc(UserDao.Properties.UserName) .where(UserDao.Properties.UserName.eq(user.getUserName())) .build() .list(); return users; } public List queryAll(){ UserDao userDao = daoSession.getUserDao(); List users = userDao.queryBuilder().list(); return users; }
DBFlow
添加依赖:
annotationProcessor 'com.github.Raizlabs.DBFlow:dbflow-processor:4.2.4' compile 'com.github.Raizlabs.DBFlow:dbflow-core:4.2.4' compile 'com.github.Raizlabs.DBFlow:dbflow:4.2.4'
需要创建一个application文件,在onCrete()方法中初始化
//DBFlow初始化配置 FlowManager.init(getApplicationContext());
这样整个配置就完成了,下面我们来看看如何使用
第一,创建数据库
我们需要自己创建一个数据库,定义数据库名称,版本号
@Database(name = DBFlowDatabase.NAME, version = DBFlowDatabase.VERSION) public class DBFlowDatabase { //数据库名称 public static final String NAME = "DBFlowDatabase"; //数据库版本号 public static final int VERSION = 1; }
其次创建model文件
@Table(database = DBFlowDatabase.class) public class User extends BaseModel { @Column public String userName; @Column public String addr; @Column public int age; @PrimaryKey(autoincrement = true)//ID自增 public long userId; public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getAddr() { return addr; } public void setAddr(String addr) { this.addr = addr; } @Override public String toString() { return "User{" + "userName='" + userName + '\'' + ", addr='" + addr + '\'' + ", age=" + age + ", userId=" + userId + '}'; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public long getUserId() { return userId; } public void setUserId(long userId) { this.userId = userId; } }
这里简单介绍下,必须继承BaseModel,BaseModel包含了基本的数据库操作(save、delete、update、insert、exists),可以发现这个表是关联上面定义的数据库,UserModel 的id是自增的id(autoincrement )。
一个正确的数据表类需要以下几项: 对类添加@Table注解 声明所连接的数据库类,这里是DBFlowDatabase。 定义至少一个主键。
这个类和这个类中数据库相关列的修饰符必须是包内私有或者public。 这样生成的_Adapter类能够访问到它。 NOTE:
列(Column)属性可以是private,但这样就必须指定公有public的getter和setter方法。
创建完成后,需要编译一下,点击编译按钮,或者Build->Make Project即可,它会自动生成一些数据库文件,也会提示你创建是否有误!
然后,就是重头戏,怎么使用增删改查
User user = new User(); user.setAddr("信阳"); user.setAge(28); user.setUserName("张飞"); user.save(); //people.update();//更新对象 //people.delete();//删除对象 //people.insert();//插入对象
查询:
List users = SQLite.select() .from(User.class) .where(User_Table.age.greaterThan(18)) .queryList();
查询全部:
List users=new Select().from(User.class).queryList()
Realm
Realm 是一个全新的移动数据库引擎,它既不是基于ios平台的Core Data,也不是基于SQLite,它拥有自己的数据库存储引擎,并实现了高效快速的数据库构建操作,相比Core Data和SQLite,Realm操作要快很多,跟ORM框架相比就更不用说了。
数据库Realm,是用来替代sqlite的一种解决方案,它有一套自己的数据库存储引擎,比sqlite更轻量级,拥有更快的速度,并且具有很多现代数据库的特性,比如支持JSON,流式api,数据变更通知,自动数据同步,简单身份验证,访问控制,事件处理,最重要的是跨平台,目前已有Java,Objective C,Swift,React-Native,Xamarin这五种实现。
开发者应该尝试使用Realm的理由如下:
跨平台:Android和ios已经是事实上的两大移动互联网操作系统,绝大部分应用都会支持这两个平台。使用Realm,Android和ios无需考虑内部数据的架构,调用Realm的Api即可轻松完成数据的交互,实现“一个数据库,两个平台之间的无缝衔接” 用法简单:相比Core Data和SQLite所需要的入门知识,Relam可以极大降低开发者的学习成本,快速实现数据库存贮功能。 可视化操作:Relam为开发者提供了一个轻量级的数据库可视化操作工具,开发者可以轻松查看数据库里的内容,并实现简单的插入和删除操作。环境搭建:
参考 GITHUB
buildscript { repositories { jcenter() google() maven { url 'https://oss.jfrog.org/artifactory/oss-snapshot-local' } } dependencies { classpath "io.realm:realm-gradle-plugin:-SNAPSHOT" } } allprojects { repositories { jcenter() google() maven { url 'https://oss.jfrog.org/artifactory/oss-snapshot-local' } } }
使用默认配置
public class MyApplication extends Application { @Override public void onCreate() { super.onCreate(); // The Realm file will be located in Context.getFilesDir() with name "default.realm" Realm.init(this); RealmConfiguration config = new RealmConfiguration.Builder().build(); Realm.setDefaultConfiguration(config); } }
使用自定义配置
public class MyApplication extends Application { @Override public void onCreate() { super.onCreate(); Realm.init(this); RealmConfiguration config = new RealmConfiguration.Builder() .name("myRealm.realm") .deleteRealmIfMigrationNeeded() .build(); Realm.setDefaultConfiguration(config); } }
在AndroidManifest.xml配置自定义的Application
创建实体
新建一个类继承RealmObject
public class User extends RealmObject { public String userName; public String addr; public int age; @PrimaryKey public long userId; public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getAddr() { return addr; } public void setAddr(String addr) { this.addr = addr; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public long getUserId() { return userId; } public void setUserId(long userId) { this.userId = userId; } @Override public String toString() { return "User{" + "userName='" + userName + '\'' + ", addr='" + addr + '\'' + ", age=" + age + ", userId=" + userId + '}'; } }
(2)其他相关说明
1、支持的数据类型:
boolean, byte, short, int, long, float, double, String, Date and byte[]
在Realm中byte, short, int, long最终都被映射成long类型
2、注解说明
@PrimaryKey
①字段必须是String、 integer、byte、short、 int、long 以及它们的封装类Byte, Short, Integer, and Long
②使用了该注解之后可以使用copyToRealmOrUpdate()方法,通过主键查询它的对象,如果查询到了,则更新它,否则新建一个对象来代替。
③使用了该注解将默认设置@index注解
④使用了该注解之后,创建和更新数据将会慢一点,查询数据会快一点。
@Required
数据不能为null
@Ignore
忽略,即该字段不被存储到本地
@Index
为这个字段添加一个搜索引擎,这将使插入数据变慢、数据增大,但是查询会变快。建议在需要优化读取性能的情况下使用。
增
(1)实现方法一:事务操作
类型一 :新建一个对象,并进行存储
Realm realm=Realm.getDefaultInstance(); realm.beginTransaction(); User user = realm.createObject(User.class); // Create a new object user.setUserName("John"); user.setAge(18); user.setAddr("asd"); user.setuserId(1); realm.commitTransaction();
类型二:复制一个对象到Realm数据库
Realm realm=Realm.getDefaultInstance(); User user = new User(); user.setUserName("john"); user.setAge(18); user.setAddr("asd"); user.setuserId(1); // Copy the object to Realm. Any further changes must happen on realmUser realm.beginTransaction(); realm.copyToRealm(user); realm.commitTransaction();
(2)实现方法二:使用事务块
Realm mRealm=Realm.getDefaultInstance(); final User user = new User(); user.setUserName("john"); user.setAge(18); user.setAddr("asd"); user.setuserId(1); mRealm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { realm.copyToRealm(user); } });
删
Realm mRealm=Realm.getDefaultInstance(); final RealmResults users= mRealm.where(User.class).findAll(); mRealm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { User user=users.get(5); user.deleteFromRealm(); //删除第一个数据 users.deleteFirstFromRealm(); //删除最后一个数据 users.deleteLastFromRealm(); //删除位置为1的数据 users.deleteFromRealm(1); //删除所有数据 users.deleteAllFromRealm(); } });
同样也可以使用同上的beginTransaction和commitTransaction方法进行删除
改
Realm mRealm=Realm.getDefaultInstance(); Dog dog = mRealm.where(User.class).equalTo("userId", 2).findFirst(); mRealm.beginTransaction(); dog.setName("asd"); mRealm.commitTransaction();
同样也可以用事物块来更新数据
查
(1)查询全部
查询结果为RealmResults,可以使用mRealm.copyFromRealm(users)方法将它转为List
public List queryAllUser() { Realm mRealm=Realm.getDefaultInstance(); RealmResults usres = mRealm.where(User.class).findAll(); return mRealm.copyFromRealm(usres); }
(2)条件查询
public User queryUserById(int id) { Realm mRealm=Realm.getDefaultInstance(); User user = mRealm.where(User.class).equalTo("userId", id).findFirst(); return user; }
常见的条件如下(详细资料请查官方文档):
between(), greaterThan(), lessThan(), greaterThanOrEqualTo() & lessThanOrEqualTo() equalTo() & notEqualTo() contains(), beginsWith() & endsWith() isNull() & isNotNull() isEmpty() & isNotEmpty()
(3)对查询结果进行排序
/** * query (查询所有) */ public List queryAllUser() { RealmResults users = mRealm.where(User.class).findAll(); /** * 对查询结果,按Id进行排序,只能对查询结果进行排序 */ //增序排列 users=users.sort("userId"); //降序排列 users=users.sort("userId", Sort.DESCENDING); return mRealm.copyFromRealm(users); }
(4)其他查询
sum,min,max,average只支持整型数据字段
/** * 查询平均年龄 */ private void getAverageAge() { double avgAge= mRealm.where(User.class).findAll().average("age"); } /** * 查询总年龄 */ private void getSumAge() { Number sum= mRealm.where(User.class).findAll().sum("age"); int sumAge=sum.intValue(); } /** * 查询最大年龄 */ private void getMaxId(){ Number max= mRealm.where(User.class).findAll().max("age"); int maxAge=max.intValue(); }
这里就介绍到这里,对于Realm的异步操作,以及数据库的数据迁移(版本升级
前四个ORM框架的大小是在正常范围之内的,但是Realm的大小一般项目可能无法接受,我们将这个JAR包解压,如下图所示
原来不同的CPU架构平台的.so文件增加了整个包的大小,由于arm平台的so在其他平台上面使能够以兼容模式运行的,虽然会损失性能,但是这样可以极大的减少函数库占用的空间。因此,可以选择只保留armeabi-v7a和x86两个平台的.so文件,直接删除无用的.so文件,或者通过在工程的build.gradle文件增加ndk abi过滤 语法如下:
android{ defaultConfig{ ndk{ abiFilters "armeabi-v7a","x86" } } }
总结
因此,综合考虑性能,包的大小及开源的可持续发展等因素,建议使用greenDAO。