现在市面上主流的框架有 OrmLite、SugarORM、Active Android、Realm 与 GreenDAO。
官网上的介绍,greenDAO 是一个将对象映射到 SQLite 数据库中的轻量且快速的 ORM 解决方案。
GreenDao特点
性能最大化,可能是Android平台上最快的ORM框架
易于使用的API
最小的内存开销
依赖体积小
支持数据库加密
强大的社区支持
GreenDao配置
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.0.0'
classpath 'org.greenrobot:greendao-gradle-plugin:3.2.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
apply plugin: 'com.android.application'
apply plugin: 'org.greenrobot.greendao'
greendao {
schemaVersion 1//数据库版本号
daoPackage 'com.xiaoyehai.landsurvey.greendao'//设置DaoMaster、DaoSession、Dao包名
targetGenDir 'src/main/java'//设置DaoMaster、DaoSession、Dao目录
//targetGenDirTest:设置生成单元测试目录
//generateTests:设置自动生成单元测试用例
}
implementation 'org.greenrobot:greendao:3.2.2'
实体类:
实体类,不需要自己写get和set方法,在生成表的时候会自动生成。不要忘了在类名上标记@Entity注解
/**
* Created by xiaoyehai on 2018/8/9 0009.
*/
@Entity //@Entity 将我们的java普通类变为一个能够被greenDAO识别的数据库类型的实体类
public class User {
//@Id:主键,通过这个注解标记的字段必须是Long类型的,这个字段在数据库中表示它就是主键,并且它默认就是自增的
@Id(autoincrement = true)
private Long id;
@NotNull // @NotNull 设置数据库表当前列不能为空
@Unique //唯一
private String name;
//@Property:设置一个非默认关系映射所对应的列名,默认是使用字段名,例如:@Property(nameInDb = "name")
@Property(nameInDb = "userage")
private int age;
//@Transient:表明这个字段不会被写入数据库,只是作为一个普通的java类字段,用来临时存储数据的,不会被持久化
@Transient
private String like;
@Entity:告诉GreenDao该对象为实体,只有被@Entity注释的Bean类才能被dao类操作
@Id:对象的Id,使用Long类型作为EntityId,否则会报错。(autoincrement = true)表示主键会自增,如果false就会使用旧值 。
@Property:可以自定义字段名,注意外键不能使用该属性
@NotNull:属性不能为空 @Transient:使用该注释的属性不会被存入数据库的字段中
@Unique:该属性值必须在数据库中是唯一值
@Generated:编译后自动生成的构造函数、方法等的注释,提示构造函数、方法等不能被修改
写好实体类之后重新编译
初始化GreenDao
public class CustomApplication extends Application {
public static final String DB_NAME = "app.db";
private static DaoSession mDaoSession;
@Override
public void onCreate() {
super.onCreate();
initGreenDao();
}
private void initGreenDao() {
DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(this, DB_NAME);
SQLiteDatabase db = helper.getWritableDatabase();
DaoMaster daoMaster = new DaoMaster(db);
mDaoSession = daoMaster.newSession();
}
public static DaoSession getmDaoSession() {
return mDaoSession;
}
}
添加数据
注意:Long型id,如果传入null,则GreenDao会默认设置自增长的值。
insert(User entity):插入一条记录, 当指定主键在表中存在时会发生异常
添加一条数据
CustomApplication.getmDaoSession().getUserDao().insert(user);
insertOrReplace(User entity) :当指定主键在表中存在时会覆盖数据,有该数据时则更新
save(User entity):
使用数据库插入数据时,使用insert会因为key值(通常是id)重复的异常。我们当然想通过一个简单的方法:有该数据时则更新,没有该数据时则插入的“有更无插”的方法,在GreenDao有两个方insertOrReplace和save,可两者时有区别的:
结论
insertOrReplace : 传入的对象在数据库中,有则更新无则插入。推荐同步数据库时使用该方法。
save 类似于insertOrReplace,区别在于save会判断传入对象的key,有key的对象执行更新,无key的执行插入。当对象有key但并不在数据库时会执行失败.适用于保存本地列表。
//key为null,插入
User user = new User(null, "xyh" , 120, "篮球");
//key不为null,在数据库中有该key执行更新,在数据无无该key,不插入不更新
User user = new User(21l, "xyh333" , 120, "篮球");
save源码
public void save(T entity) {
if (hasKey(entity)) {
update(entity);
} else {
insert(entity);
}
}
在确保插入数据有key时必须存在于数据库的情况下,适用save更高效。其他情况一律适用insertOrReplace
其他一些插入方法
insertInTx(T... entities):使用事务在数据库中插入给定的实体。
insertInTx(Iterable entities) :使用事务操作,将给定的实体集合插入数据库。
insertInTx(Iterable entities, boolean setPrimaryKey) :使用事务操作,将给定的实体集合插入数据库,并设置是否设定主键 。
insertOrReplaceInTx(T... entities):使用事务操作,将给定的实体插入数据库,若此实体类存在,则覆盖
insertOrReplaceInTx(Iterable entities) :使用事务操作,将给定的实体插入数据库,若此实体类存在,则覆盖 。
insertOrReplaceInTx(Iterable entities, boolean setPrimaryKey) :使用事务操作,将给定的实体插入数据库,若此实体类存在,则覆盖,并设置是否设定主键 。
insertWithoutSettingPk(T entity):将给定的实体插入数据库,但不设定主键。
// 新增数据插入相关API
save(T entity):将给定的实体插入数据库
saveInTx(Iterable entities) :将给定的实体集合插入数据库
saveInTx(T... entities):使用事务操作,将给定的实体插入数据库
查询
//查询全部
mUserDao.loadAll();
//根据主键获取对象,也就是通过id获取
mUserDao.load(Long key)
//根据行号查找数据
loadByRowId(long rowId)
条件查询
//查询全部
List list = mUserDao.queryBuilder().list();
//查询 name等于xyh8的数据
List list= mUserDao.queryBuilder().where(UserDao.Properties.Name.eq("xyh8")).list();
//查询 name不等于xyh8的数据
List list= mUserDao.queryBuilder().where(UserDao.Properties.Name.notEq("xyh8")).list();
//like 模糊查询
//查询 name以xyh3开头的数据
List list = mUserDao.queryBuilder().where(UserDao.Properties.Name.like("xyh3%")).list();
//between 区间查询 年龄在20到30之间
List list = mUserDao.queryBuilder().where(UserDao.Properties.Age.between(20,30)).list();
//gt: greater than 半开区间查询,年龄大于18
List list = mUserDao.queryBuilder().where(UserDao.Properties.Age.gt(18)).list();
//ge: greater equal 半封闭区间查询,年龄大于或者等于18
List list = mUserDao.queryBuilder().where(UserDao.Properties.Age.ge(18)).list();
//lt: less than 半开区间查询,年龄小于18
List list = mUserDao.queryBuilder().where(UserDao.Properties.Age.lt(18)).list();
//le: less equal 半封闭区间查询,年龄小于或者等于18
List list = mUserDao.queryBuilder().where(UserDao.Properties.Age.le(18)).list();
//排序
//名字以xyh8开头,年龄升序排序
List list = mUserDao.queryBuilder()
.where(UserDao.Properties.Name.like("xyh8%"))
.orderAsc(UserDao.Properties.Age)
.list();
//名字以xyh8开头,年龄降序排序
List list = mUserDao.queryBuilder()
.where(UserDao.Properties.Name.like("xyh8%"))
.orderDesc(UserDao.Properties.Age)
.list();
三.更新
update(T entity) :更新给定的实体
updateInTx(Iterable<T> entities) :使用事务操作,更新给定的实体
updateInTx(T... entities):使用事务操作,更新给定的实体
案例:
User user = mUserDao.load(2L);
user.setAge(2000);
mUserDao.update(user);
也可以用insertOrReplace()更新数据,但key必须在数据可存在,否则就是插入新数据。
User user = new User(5L, "ty", 250, "qq");
mUserDao.insertOrReplace(user);
四.删除
//删除全部
mUserDao.deleteAll();
delete(T entity):从数据库中删除给定的实体
deleteByKey(K key):从数据库中删除给定Key所对应的实体
deleteInTx(T... entities):使用事务操作删除数据库中给定的实体
deleteInTx( entities) :使用事务操作删除数据库中给定实体集合中的实体
deleteByKeyInTx(K... keys):使用事务操作删除数据库中给定的所有key所对应的实体
deleteByKeyInTx(Iterable keys) :使用事务操作删除数据库中给定的所有key所对应的实体
封装
DaoManager
package com.xiaoyehai;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import com.xiaoyehai.landsurvey.greendao.DaoMaster;
import com.xiaoyehai.landsurvey.greendao.DaoSession;
/**
* Created by xiaoyehai on 2018/8/10 0010.
*/
public class DaoManager {
private Context mContext;
//创建数据库的名字
private static final String DB_NAME = "MyGreenDb.db";
//多线程中要被共享的使用volatile关键字修饰 GreenDao管理类
private volatile static DaoManager mInstance;
//它里边实际上是保存数据库的对象
private static DaoMaster mDaoMaster;
//创建数据库的工具
private static DaoMaster.DevOpenHelper mHelper;
//管理gen里生成的所有的Dao对象里边带有基本的增删改查的方法
private static DaoSession mDaoSession;
private DaoManager() {
}
/**
* 单例模式获得操作数据库对象
*
* @return
*/
public static DaoManager getInstance() {
if (mInstance == null) {
synchronized (DaoManager.class) {
if (mInstance == null) {
mInstance = new DaoManager();
}
}
}
return mInstance;
}
/**
* 初始化上下文创建数据库的时候使用
*/
public void init(Context context) {
this.mContext = context;
}
/**
* 判断是否有存在数据库,如果没有则创建
*
* @return
*/
public DaoMaster getDaoMaster() {
if (mDaoMaster == null) {
mHelper = new DaoMaster.DevOpenHelper(mContext, DB_NAME, null);
mDaoMaster = new DaoMaster(mHelper.getWritableDatabase());
}
return mDaoMaster;
}
/**
* 完成对数据库的添加、删除、修改、查询操作,
*
* @return
*/
public DaoSession getDaoSession() {
if (mDaoSession == null) {
if (mDaoMaster == null) {
mDaoMaster = getDaoMaster();
}
mDaoSession = mDaoMaster.newSession();
}
return mDaoSession;
}
/**
* 关闭所有的操作,数据库开启后,使用完毕要关闭
*/
public void closeConnection() {
closeHelper();
closeDaoSession();
}
public void closeHelper() {
if (mHelper != null) {
mHelper.close();
mHelper = null;
}
}
public void closeDaoSession() {
if (mDaoSession != null) {
mDaoSession.clear();
mDaoSession = null;
}
}
}
AbstractDao
所有的自动生成的XXDao都是继承于AbstractDao,此类中基本上封装了所有的增删改操作,包括数据库的事务操作。
QueryBuilder
GreenDao中,使用QueryBuilder自定义查询实体,而不是再写繁琐的SQL语句,避免了SQL语句的出错率。大家都知道写SQL语句时,非常容易出错,出错后又十分的难查。QueryBuilder真是帮忙解决了一个大麻烦。具体该如何使用呢?
数据库升级
比如需要在实体类加一个字段 或者 改变字段属性等 就需要版本更新来保存以前的数据了;
思路
创建临时表–>删除原表–>创建新表–>复制临时表数据到新表并删除临时表;这样数据库表的更新就完成了
MigrationHelper:国外大神写的数据库升级帮助类
package com.xiaoyehai;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.text.TextUtils;
import android.util.Log;
import com.xiaoyehai.landsurvey.greendao.DaoMaster;
import org.greenrobot.greendao.AbstractDao;
import org.greenrobot.greendao.database.Database;
import org.greenrobot.greendao.internal.DaoConfig;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* 数据库升级帮助类
* Created by xiaoyehai on 2018/8/10 0010.
*/
public class MigrationHelper {
private static final String CONVERSION_CLASS_NOT_FOUND_EXCEPTION = "MIGRATION HELPER - CLASS DOESN'T MATCH WITH THE CURRENT PARAMETERS";
private static MigrationHelper instance;
public static MigrationHelper getInstance() {
if (instance == null) {
instance = new MigrationHelper();
}
return instance;
}
public void migrate(Database db, Class extends AbstractDao, ?>>... daoClasses) {
generateTempTables(db, daoClasses);
DaoMaster.dropAllTables(db, true);
DaoMaster.createAllTables(db, false);
restoreData(db, daoClasses);
}
/**
* 生成临时列表
*
* @param db
* @param daoClasses
*/
private void generateTempTables(Database db, Class extends AbstractDao, ?>>... daoClasses) {
for (int i = 0; i < daoClasses.length; i++) {
DaoConfig daoConfig = new DaoConfig(db, daoClasses[i]);
String divider = "";
String tableName = daoConfig.tablename;
String tempTableName = daoConfig.tablename.concat("_TEMP");
ArrayList properties = new ArrayList<>();
StringBuilder createTableStringBuilder = new StringBuilder();
createTableStringBuilder.append("CREATE TABLE ").append(tempTableName).append(" (");
for (int j = 0; j < daoConfig.properties.length; j++) {
String columnName = daoConfig.properties[j].columnName;
if (getColumns(db, tableName).contains(columnName)) {
properties.add(columnName);
String type = null;
try {
type = getTypeByClass(daoConfig.properties[j].type);
} catch (Exception exception) {
exception.printStackTrace();
}
createTableStringBuilder.append(divider).append(columnName).append(" ").append(type);
if (daoConfig.properties[j].primaryKey) {
createTableStringBuilder.append(" PRIMARY KEY");
}
divider = ",";
}
}
createTableStringBuilder.append(");");
db.execSQL(createTableStringBuilder.toString());
StringBuilder insertTableStringBuilder = new StringBuilder();
insertTableStringBuilder.append("INSERT INTO ").append(tempTableName).append(" (");
insertTableStringBuilder.append(TextUtils.join(",", properties));
insertTableStringBuilder.append(") SELECT ");
insertTableStringBuilder.append(TextUtils.join(",", properties));
insertTableStringBuilder.append(" FROM ").append(tableName).append(";");
db.execSQL(insertTableStringBuilder.toString());
}
}
/**
* 存储新的数据库表 以及数据
*
* @param db
* @param daoClasses
*/
private void restoreData(Database db, Class extends AbstractDao, ?>>... daoClasses) {
for (int i = 0; i < daoClasses.length; i++) {
DaoConfig daoConfig = new DaoConfig(db, daoClasses[i]);
String tableName = daoConfig.tablename;
String tempTableName = daoConfig.tablename.concat("_TEMP");
ArrayList properties = new ArrayList();
for (int j = 0; j < daoConfig.properties.length; j++) {
String columnName = daoConfig.properties[j].columnName;
if (getColumns(db, tempTableName).contains(columnName)) {
properties.add(columnName);
}
}
StringBuilder insertTableStringBuilder = new StringBuilder();
insertTableStringBuilder.append("INSERT INTO ").append(tableName).append(" (");
insertTableStringBuilder.append(TextUtils.join(",", properties));
insertTableStringBuilder.append(") SELECT ");
insertTableStringBuilder.append(TextUtils.join(",", properties));
insertTableStringBuilder.append(" FROM ").append(tempTableName).append(";");
StringBuilder dropTableStringBuilder = new StringBuilder();
dropTableStringBuilder.append("DROP TABLE ").append(tempTableName);
db.execSQL(insertTableStringBuilder.toString());
db.execSQL(dropTableStringBuilder.toString());
}
}
private String getTypeByClass(Class> type) throws Exception {
if (type.equals(String.class)) {
return "TEXT";
}
if (type.equals(Long.class) || type.equals(Integer.class) || type.equals(long.class)) {
return "INTEGER";
}
if (type.equals(Boolean.class)) {
return "BOOLEAN";
}
Exception exception = new Exception(CONVERSION_CLASS_NOT_FOUND_EXCEPTION.concat(" - Class: ").concat(type.toString()));
exception.printStackTrace();
throw exception;
}
private List getColumns(Database db, String tableName) {
List columns = new ArrayList<>();
Cursor cursor = null;
try {
cursor = db.rawQuery("SELECT * FROM " + tableName + " limit 1", null);
if (cursor != null) {
columns = new ArrayList<>(Arrays.asList(cursor.getColumnNames()));
}
} catch (Exception e) {
Log.v(tableName, e.getMessage(), e);
e.printStackTrace();
} finally {
if (cursor != null)
cursor.close();
}
return columns;
}
}
1.由于升级数据库需要在DevOpenHelper类的onUpgrade()方法里面继续,因此我们需要自定义一个类继承DevOpenHelper重写onUpgrade()方法
/**
* 自定义 MySQLiteOpenHelper继承DaoMaster.OpenHelper 重写更新数据库的方法
*
* 当app下的build.gradle 的schemaVersion数据库的版本号改变时,创建数据库会调用onUpgrade更细数据库的方法
*
* Created by xiaoyehai on 2018/8/10 0010.
*/
public class MyDevOpenHelper extends DaoMaster.DevOpenHelper {
public MyDevOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory) {
super(context, name, factory);
}
/**
* 数据库升级
*
* @param db
* @param oldVersion
* @param newVersion
*/
@Override
public void onUpgrade(Database db, int oldVersion, int newVersion) {
//super.onUpgrade(db, oldVersion, newVersion);
//操作数据库的更新 有几个表升级都可以传入到下面
MigrationHelper.getInstance().migrate(db, UserDao.class);
}
}
2.修改在项目根目录build.gradle文件中配置的数据库版本号(新版本号一定要比老版本大)
greendao {
schemaVersion 2//数据库版本号
daoPackage 'com.xiaoyehai.landsurvey.greendao'//设置DaoMaster、DaoSession、Dao包名
targetGenDir 'src/main/java'//设置DaoMaster、DaoSession、Dao目录
//targetGenDirTest:设置生成单元测试目录
//generateTests:设置自动生成单元测试用例
}
3.初始化GreenDao
private void initGreenDao() {
MyDevOpenHelper helper = new MyDevOpenHelper(this, DB_NAME, null);
SQLiteDatabase db = helper.getWritableDatabase();
DaoMaster daoMaster = new DaoMaster(db);
mDaoSession = daoMaster.newSession();
}
4.bean类新增加一个address字段
package com.xiaoyehai.greendao.bean;
import org.greenrobot.greendao.annotation.Entity;
import org.greenrobot.greendao.annotation.Id;
import org.greenrobot.greendao.annotation.Keep;
import org.greenrobot.greendao.annotation.NotNull;
import org.greenrobot.greendao.annotation.Property;
import org.greenrobot.greendao.annotation.Transient;
import org.greenrobot.greendao.annotation.Generated;
import org.greenrobot.greendao.annotation.Unique;
/**
* 实体类,不需要自己写get和set方法,在生成表的时候会自动生成。不要忘了在类名上标记@Entity注解
* Created by xiaoyehai on 2018/8/9 0009.
*/
@Entity //@Entity 将我们的java普通类变为一个能够被greenDAO识别的数据库类型的实体类
public class User {
//@Id:通过这个注解标记的字段必须是Long类型的,这个字段在数据库中表示它就是主键,并且它默认就是自增的
@Id(autoincrement = true)
private Long id;
@NotNull // @NotNull 设置数据库表当前列不能为空
private String name;
//@Property:设置一个非默认关系映射所对应的列名,默认是使用字段名,例如:@Property(nameInDb = "name")
@Property(nameInDb = "userage")
private int age;
//@Transient:表明这个字段不会被写入数据库,只是作为一个普通的java类字段,用来临时存储数据的,不会被持久化
@Transient
private String like;
private String address; //新增字段
@Generated(hash = 586692638)
public User() {
}
@Keep
public User(Long id, String name, int age, String like) {
this.id = id;
this.name = name;
this.age = age;
this.like = like;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Keep
public Long getId() {
return this.id;
}
@Keep
public void setId(Long id) {
this.id = id;
}
@Keep
public String getName() {
return this.name;
}
@Keep
public void setName(String name) {
this.name = name;
}
@Keep
public int getAge() {
return this.age;
}
@Keep
public void setAge(int age) {
this.age = age;
}
@Keep
public String getLike() {
return like;
}
@Keep
public void setLike(String like) {
this.like = like;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
", like='" + like + '\'' +
'}';
}
}
新增的字段不能为空
我们新增加的和修改的字段做好为String类型,避免字段不能为null的情况发生
对数据库进行加密
添加依赖
implementation "net.zetetic:android-database-sqlcipher:3.5.2"
DaoMaster.DevOpenHelper a = new DaoMaster.DevOpenHelper(this,"database_name",null);
try {
daoSession = new DaoMaster(a.getEncryptedWritableDb(MY_PWD)).newSession();
daoSession.getUserDao().insert(man1);
}catch (Exception e){
Log.d("e", String.valueOf(e));
}