前言
Android开发中,经常用到的三种本地数据持久化的方式为:
- SharedPreference
- 文件存储的方式
- 数据库(SQLite)
我们在开发过程中,经常会需要存储相对大量的数据,因此数据库常常会用到。Android使用数据库一般有两种方案:
- 使用原生数据库SQLite直接操作;
- 使用第三方数据库,如GreenDao。
GreenDao使用简单,加上其本身存取快、体积小、支持缓存、支持加密等优点,使得它成为了一个受欢迎的ORM解决方案,今天我们就简要介绍一下GreenDao的用法和使用过程中一些坑。
基本使用
github地址
项目中引入
- 项目的build.gradle中引入
buildscript {
repositories {
jcenter()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.1.1'
classpath 'org.greenrobot:greendao-gradle-plugin:3.2.2' // 添加GreenDao依赖
}
}
- module的build.gradle中引入
apply plugin: 'com.android.application'
apply plugin: 'org.greenrobot.greendao' // 添加GreenDao Plugin
dependencies {
implementation 'org.greenrobot:greendao:3.2.2' // 添加GreenDao依赖
}
- 数据库版本号与路径设置
GreenDao核心类共有三个分别为DaoMaster.java、DaoSession.java以及根据你创建的持久化数据结构而生成的XXDao.java。这三个类都可以自动生成,无需手动编写代码。
生成以上三个类需要在module的build.gradle文件中进行如下配置:
greendao{
schemaVersion 1 //数据库版本号
daoPackage 'com.example.greendaotest.dao'//DaoMaster、DaoSession、XXDao的包名
targetGenDir 'src/main/java' //DaoMaster、DaoSession、XXDao的路径
generateTests false //设置为true以自动生成单元测试。
targetGenDirTests 'src/androidTest/java' //应存储生成的单元测试的基本目录。
}
其中,schemaVersion为数据库版本号必填;若不需要单元测试,generateTests和targetGenDirTests可以删除;daoPackage和targetGenDir选填,如果填写会根据填写的路径生成相关的代码,否则会在build/generated/source/greendao中自动生成相关的类。
到此,GreenDao的引入工作已经完成,可以再代码中使用GreenDao进行数据库相关开发了。
构建实体类
- 注意:实体类中的属性即为数据库中对应的字段
@Entity
public class UserBean {
@Id(autoincrement = true)
private Long id;
@NotNull
private String name;
private int age;
}
GreenDao注解
- @Entity:告诉GreenDao该对象为实体,只有被@Entity注释的Bean类才能被dao类操作
- @Id:对象的Id,使用Long类型作为EntityId,否则会报错。(autoincrement = true)表示主键会自增,如果false就会使用旧值
- @Property:可以自定义字段名,注意外键不能使用该属性
- @NotNull:属性不能为空
- @Transient:使用该注释的属性不会被存入数据库的字段中
- @Unique:该属性值必须在数据库中是唯一值
- @Generated:编译后自动生成的构造函数、方法等的注释,提示构造函数、方法等不能被修改
实体类创建后,Build --> Make Module 'app', 会自动生成相应的类,build后刚刚创建的实体类如下:
@Entity
public class UserBean {
@Id(autoincrement = true)
private Long id;
@NotNull
private String name;
private int age;
@Generated(hash = 1420883130)
public UserBean(Long id, @NotNull String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
@Generated(hash = 1203313951)
public UserBean() {
}
public Long getId() {
return this.id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return this.age;
}
public void setAge(int age) {
this.age = age;
}
}
并且,在刚刚build.gradle 中配置的daoPackage目录下生成了三个java文件,分别为:
- DaoMaster.java
- DaoSession.java
- UserBeanDao.java
这三个类是自动生成的,不需要手动做修改。自此,实体类构建完成。
初始化数据库
- 创建数据库管理类DbManager,也可以不创建,直接在Application中初始化:
public class DbManager {
private static final String DB_NAME = "search";
private static DbManager mDbManager;
private static DaoMaster.DevOpenHelper mDevOpenHelper;
private static DaoMaster mDaoMaster;
private static DaoSession mDaoSession;
private Context mContext;
private DbManager(Context context) {
this.mContext = context;
// 初始化数据库信息
mDevOpenHelper = new DaoMaster.DevOpenHelper(context, DB_NAME);
getDaoMaster(context);
getDaoSession(context);
}
public static DbManager getInstance(Context context) {
if (null == mDbManager) {
synchronized (DbManager.class) {
if (null == mDbManager) {
mDbManager = new DbManager(context);
}
}
}
return mDbManager;
}
/**
* 获取可读数据库
*
* @param context
* @return
*/
public static SQLiteDatabase getReadableDatabase(Context context) {
if (null == mDevOpenHelper) {
getInstance(context);
}
return mDevOpenHelper.getReadableDatabase();
}
/**
* 获取可写数据库
*
* @param context
* @return
*/
public static SQLiteDatabase getWritableDatabase(Context context) {
if (null == mDevOpenHelper) {
getInstance(context);
}
return mDevOpenHelper.getWritableDatabase();
}
/**
* 获取DaoMaster
*
* @param context
* @return
*/
public static DaoMaster getDaoMaster(Context context) {
if (null == mDaoMaster) {
synchronized (DbManager.class) {
if (null == mDaoMaster) {
mDaoMaster = new DaoMaster(getWritableDatabase(context));
}
}
}
return mDaoMaster;
}
/**
* 获取DaoSession
*
* @param context
* @return
*/
public static DaoSession getDaoSession(Context context) {
if (null == mDaoSession) {
synchronized (DbManager.class) {
mDaoSession = getDaoMaster(context).newSession();
}
}
return mDaoSession;
}
}
2.初始化GreenDao:
public class GreenDaoApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
//初始化GreenDao
DbManager.getInstance(this);
}
}
增删改查
为了方便介绍,单独建立一个单例类,创建UserBeanDaoManager,如下:
package com.example.greendaotest;
import android.content.Context;
import com.example.greendaotest.bean.UserBean;
import com.example.greendaotest.dao.UserBeanDao;
import java.util.List;
/**
* 1 * FileName: UserBeanDaoManager
* 2 * Author: WangDongDong
* 3 * Date: 2019/1/3 4:34 PM
* 4 * Description:
* 5 * History:
* 6 * <author> <time> <version> <desc>
* 7 * 作者姓名 修改时间 版本号 描述
*/
public class UserBeanDaoManager {
private static UserBeanDaoManager mManger;
private static UserBeanDao mUserBeanDao;
public static UserBeanDaoManager getInstance(Context context) {
synchronized (UserBeanDaoManager.class) {
if (mManger == null) {
mManger = new UserBeanDaoManager(context);
}
return mManger;
}
}
public UserBeanDaoManager(Context context) {
mUserBeanDao = DbManager.getDaoSession(context).getUserBeanDao();
}
/**
* 新增数据
* @param bean
*/
public void insertData(UserBean bean) {
mUserBeanDao.insert(bean);
}
/**
* 增加一组数据
* @param list
*/
public void insertData(List<UserBean> list) {
mUserBeanDao.insertInTx(list);
}
/**
* 修改数据
* @param bean
*/
public void updateData(UserBean bean) {
mUserBeanDao.update(bean);
}
/**
* 查询数量
* @return
*/
public long getUserCount() {
return mUserBeanDao.count();
}
/**
* 按年龄降序查询所有数据
* @return
*/
public List<UserBean> queryAll() {
return mUserBeanDao
.queryBuilder()
.orderDesc(UserBeanDao.Properties.Age)
.list();
}
/**
* 按条件查询
* @param name
* @return
*/
public List<UserBean> queryByName(String name) {
List<UserBean> list = mUserBeanDao
.queryBuilder()
.where(UserBeanDao.Properties.Name.eq(name))
.list();
return list;
}
/**
* 删除数据
* @param bean
*/
public void deleteData(UserBean bean) {
mUserBeanDao.delete(bean);
}
/**
* 删除全部
*/
public void deleteAll() {
mUserBeanDao.deleteAll();
}
}
以上是GreenDao基本的增删改查操作,GreenDao提供了更加丰富的API,这些API可以通过java doc直接看到,再此不做更多介绍。
联合查询
创建班级类,ClassBean.java:
@Entity
public class ClassBean {
@Id(autoincrement = true)
private Long id;
private String className;
@Generated(hash = 2143102101)
public ClassBean(Long id, String className) {
this.id = id;
this.className = className;
}
@Generated(hash = 1395092832)
public ClassBean() {
}
public Long getId() {
return this.id;
}
public void setId(Long id) {
this.id = id;
}
public String getClassName() {
return this.className;
}
public void setClassName(String className) {
this.className = className;
}
}
在UserBeanDaoManager中新增方法:
/**
* 联合查询,查询班级为一班的学生
* @param name
* @param age
* @return
*/
public List<UserBean> queryByNameAndAge(String name, int age) {
QueryBuilder builder = mUserBeanDao.queryBuilder();
builder.join(ClassBean.class, ClassBeanDao.Properties.Id)
.where(ClassBeanDao.Properties.ClassName.eq("一班"));
List<UserBean> list = builder.list();
return list;
}
查询条件
- distinct() 过滤字段
- limit() 限制数量
- offset() 忽略查询出的前n条结果
- orderAsc() 升序排列
- orderDesc() 降序排列
- eq() 等于
- notEq() 不等于
- in() 在范围之内
- notIn() 不再范围之内
- or() 或者
- like() 就是sql语句的 LIKE "%" +string+ "%"
- between() 也就是BETWEEN ? AND ? 可以取两个值的区间
- gt() 相当于 >
- ge() 相当于 >=
- lt() 相当于 <
- le() 相当于 <=
- isNull 为空
- notIsNull 不为空
数据库升级
build.gradle 的greendao配置中,有schemaVersion字段:
greendao{
schemaVersion 1 //数据库版本号
daoPackage 'com.example.greendaotest.dao'//DaoMaster、DaoSession、XXDao的包名
targetGenDir 'src/main/java' //DaoMaster、DaoSession、XXDao的路径
generateTests false //设置为true以自动生成单元测试。
// targetGenDirTests 'src/androidTest/java' //应存储生成的单元测试的基本目录。
}
升级数据库需要将该字段值加1。
但是!但是!!但是!!!如果只是版本+1 ,数据库字段确实添加成功了,但所有的数据都没了,这在实际应用中,是一个很严重的问题。
原因可见自动生成的DaoMaster类中,onUpgrade处的注释:
/** WARNING: Drops all table on Upgrade! Use only during development. */
public static class DevOpenHelper extends OpenHelper {
public DevOpenHelper(Context context, String name) {
super(context, name);
}
public DevOpenHelper(Context context, String name, CursorFactory factory) {
super(context, name, factory);
}
@Override
public void onUpgrade(Database db, int oldVersion, int newVersion) {
Log.i("greenDAO", "Upgrading schema from version " + oldVersion + " to " + newVersion + " by dropping all tables");
dropAllTables(db, true);
onCreate(db);
}
}
原来,GreenDao在升级时,首先会删除所有table,然后重新创建,因此数据在升级是会丢。
因此,我们可以通过以下的方式进行升级:
- 方案一:每次升级时,在onUpgrade中,执行升级数据库的sql:
@Override
public void onUpgrade(Database db, int oldVersion, int newVersion) {
if (oldVersion < 2) {
//添加性别字段
db.execSQL(String.format("ALTER TABLE %s ADD %s varchar", DbManager.DB_NAME, "gender"));
}
if (oldVersion < 3) {
//添加成绩字段
db.execSQL(String.format("ALTER TABLE %s ADD %s integer default 0", DbManager.DB_NAME, "score"));
}
}
- 方案二:
1.首先创建临时表。
2.把当前表的数据插入到临时表中去。
3.删除掉原表,创建新表。
4.把临时表数据插入到新表中去,然后删除临时表。
数据库升级方案可以参考GreenDaoUpgradeHelper,这里提供了完整的数据库升级解决方案,在此处就不搬运了。
混淆
#greendao3.2.0,此是针对3.2.0,如果是之前的,可能需要更换下包名
-keep class org.greenrobot.greendao.**{*;}
-keepclassmembers class * extends org.greenrobot.greendao.AbstractDao {
public static java.lang.String TABLENAME;
}
-keep class **$Properties
一些坑
- 自动生成的类(DaoMaster.java、DaoSession.java,XXDao.java)是动态编译的,因此在多人协作开发时,会出现在Git更改记录中,总是需要提交。因此,该部分应放在gitignore中。
- JavaBean在编译之后,会生成@Generated(hash = xxxxxxxxx)样式的注解,如:
@Generated(hash = 1420883130)
public UserBean(Long id, @NotNull String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
该hash值也是动态生成的,因此如果动态编译之后,hash值有可能会改变,会发生编译错误
java.lang.RuntimeException: Constructor (see UserBean:24) has been changed after generation.
Please either mark it with @Keep annotation instead of @Generated to keep it untouched,
or use @Generated (without hash) to allow to replace it.
因此,此部分需要在生成之后,使用@Keep注解替换:
@Keep
public UserBean(Long id, @NotNull String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
- 数据库升级问题,如果需要升级数据库,一定记得重写DaoMaster中onUpgrade的方法,升级数据库的过程中,数据会直接丢失,造成严重后果。
网友评论