Android框架-数据库GreenDao

mac2024-07-18  52

转载:https://www.jianshu.com/p/53083f782ea2 本文用作学习笔记,非商业用途,感谢原作者分享。

1、概念

GreenDAO是一个开源的Android ORM(“对象 / 关系映射”),通过ORM(称为“对象/关系映射”)

通过GreenDao,我们可以更快速的操作数据库,我们可以使用简单的面相对象的API来存储,更新,删除和查询Java对象。

2、GreenDao的优缺点
1)高性能2)易于使用的强大API,涵盖关系和连接;3)最小的内存消耗;4)小库大小(<100KB) 以保持较低的构建时间并避免65k方法限制;5)数据库加密:greenDAO支持SQLCipher,以确保用户的数据安全;
3、GreenDao的核心类

GreenDao 核心类有三个:分别是 DaoMaster, DaoSession, XXXDao 这三个类都会自动创建,无需自己编写创建!

1)DaoMaster DaoMaster 【保存数据库对象(SQLiteDatabase)】并管理特定模式的DAO类(而不是对象)

它有静态方法来 创建表或删除表 它的内部类 OpenHelper 、 DevOpenHelper 是 SQLiteOpenHelper 实现

2)DaoSession 管理特定模式的所有可用【DAO对象】,可以使用其中一个getter方法获取该对象。

DaoSession 提供了一些通用的方法,实体的插入,加载,更新,刷新,删除

3)XXXDao 数据访问对象(DAO)持久存在并查询实体。 对于每个实体,greenDAO生成DAO 比 DaoSession 更多的数据库操作方法,例如:count,loadAll和insertInTx。

4)Entities

可持久化对象。通常, 实体对象代表一个数据库行使用标准 Java 属性(如一个POJO 或 JavaBean )。

【DaoMaster】 --creates–> 【DaoSession】–creates & manages–>【XXXDao】–loads&saves–>【XXXEntity】

DaoMaster.OpenHelper appHelper = new DaoMaster.OpenHelper(MyApplication.getInstance(), "aserbao.db", null); db = appHelper.getWritableDatabase();

DaoMaster daoMaster = new DaoMaster(db); //Dao会话 DaoSession daoSession = daoMaster.newSession(); //获取各个表的xxxDao操作类 userDao = daoSession .getUserDao();

4、GreenDao使用配置

1)导入Gradle 插件 和 Dao 代码生成

//在 Project的 build.gradle 文件中添加: buildscript { repositories { jcenter() mavenCentral() // add repository } dependencies { classpath 'com.android.tools.build:gradle:3.1.2' classpath 'org.greenrobot:greendao-gradle-plugin:3.2.2' // add plugin } }

2)配置相关依赖

//在 Moudle:app的 build.gradle 文件中添加: apply plugin: 'com.android.application' apply plugin: 'org.greenrobot.greendao' // apply plugin dependencies { implementation 'org.greenrobot:greendao:3.2.2' // add library }

3)配置数据库相关信息

greendao { //数据库版本号 schemaVersion 1 //设置DaoMaster、DaoSession、Dao 包名 daoPackage 'com.aserbao.aserbaosandroid.functions.database.greenDao.db' //设置DaoMaster、DaoSession、Dao目录,请注意,这里路径用/不要用. targetGenDir 'src/main/java' //设置为true 自动生成单元测试。 generateTests false //应存储生成的单元测试的基本目录。默认为 src / androidTest / java。 targetGenDirTests 'src/main/java' }

4)配置完成,在Android Studio中使用 Build> Make Project,重写build项目,GreenDao集成完成!

5、创建存储对象实体类

只需要在存储对象 实体类,声明 @Entity 注解,就让GreenDao为其生成必要的代码

@Entity public class Student { @Id(autoincrement = true) Long id; @Unique int studentNo;//学号 int age; //年龄 String telPhone;//手机号 String sex; //性别 String name;//姓名 String address;//家庭住址 String schoolName;//学校名字 String grade;//几年级 ……getter and setter and constructor method…… }
6、GreenDao初始化

可以创建一个单例管理数据库操作。

public class DBHelper { private volatile static DBHelper mInstance = null; private UserDao userDao; private ContactDao contactDao; private DBHelper() { initDBHelper(); } public static DBHelper getInstance() { if (mInstance == null) { synchronized (DBHelper.class) { if (mInstance == null) { mInstance = new DBHelper(null); } } } return mInstance; } private void initDBHelper() { DaoMaster.OpenHelper appHelper = new DaoMaster.OpenHelper(MyApplication.getInstance(), "aserbao.db", null); db = appHelper.getWritableDatabase(); DaoMaster daoMaster = new DaoMaster(db); //Dao会话 DaoSession daoSession = daoMaster.newSession(); //获取各个表的xxxDao操作类 userDao = daoSession .getUserDao(); contactDao = daoSession .getContactDao(); } }
6、使用GreenDao实现数据的增删改查

1)增 通过 DaoSession 获取到 表的 xxxDao 对象后,调用相应api即可

userDao.insert(UserBean) 插入数据 /* 可以避免插入 UNIQUE 字段重复的数据时报错 * 但是重复的这条数据的id会被修改! * 若项目中有用到id字段进行排序的话,这一点需要特别注意 */ userDao.insertOrReplace(UserBean) 数据存在则替换,数据不存在则插入

2)删 userDao.delete(UserBean)和deleteAll();分别表示删除单个和删除所有

3)改 userDao.update(UserBean)

4)查 i.查询所有数据 userDao.loadAll()

ii.根据条件查询 daoSession.queryRaw(UserBean.class, " where id = ?", id);

iii.方便查询的创建,后面详细讲解 daoSession.queryBuilder() :

5) QueryBuilder的使用 用于辅助生成SQL语句。

编写SQL可能很困难并且容易出现错误,这些错误仅在运行时才会被注意到。 该QueryBuilder的类可以让你建立你的实体,而不SQL自定义查询,并有助于在编译时已检测错误。

i.常见方法: where、or、and、join、list

//查询条件,参数为查询的条件! where(WhereCondition cond, WhereCondition... condMore) //嵌套条件或者,用法同or。 or(WhereCondition cond1, WhereCondition cond2, WhereCondition... condMore) //嵌套条件且,用法同and and(WhereCondition cond1, WhereCondition cond2, WhereCondition... condMore) //多表查询,后面会讲 join(Property sourceProperty, Class<J> destinationEntityClass) /* * 输出结果有四种方式 * list()返回值是List, * 而其他三种返回值均实现Closeable, 需要注意的不使用数据时游标的关闭操作 */ //所有实体都加载到内存中。结果通常是一个没有魔法的 ArrayList list() //实体按需加载到内存中。首次访问列表中的元素后,将加载并缓存该元素以供将来使用。必须关闭。 listLazy() //实体的“虚拟”列表:对列表元素的任何访问都会导致从数据库加载其数据。必须关闭。 listLazyUncached () //让我们通过按需加载数据(懒惰)来迭代结果。数据未缓存。必须关闭。 listIterator() //按某个属性升序排; orderAsc() //按某个属性降序排; orderDesc()

ii. GreenDao中SQL语句的缩写,我们也了解下,源码在Property中

eq():"equal ('=?')" 等于; notEq():"not equal ('<>?')" 不等于; like():" LIKE ?" 值等于; between():" BETWEEN ? AND ?" 取中间范围; in():" IN (" in命令; notIn():" NOT IN (" not in 命令; gt():">?" 大于; lt():"<? " 小于; ge():">=?" 大于等于; le():"<=? " 小于等于; isNull():" IS NULL" 为空; isNotNull():" IS NOT NULL" 不为空;

iii.使用 QueryBuilder 进行查询操作 daoSession.queryBuilder(Student.class)

DaoSession daoSession = ((AserbaoApplication) getApplication()).getDaoSession(); //简单条件查询 QueryBuilder<Student> qb = daoSession.queryBuilder(Student.class); List<Student> list = qb.list(); //查询Name为“一”的所有Student,并按名字升序排列 QueryBuilder<Student> qb = daoSession.queryBuilder(Student.class); QueryBuilder<Student> studentQueryBuilder = qb.where(StudentDao.Properties.Name.eq("一")).orderAsc(StudentDao.Properties.Name); List<Student> studentList = studentQueryBuilder.list(); //查出当前对应的数据 //原始查询 Query<Student> query = daoSession.queryBuilder(Student.class) .where( new WhereCondition.StringCondition("_ID IN (SELECT _ID FROM STUDENT WHERE _ID > 5)")) .build(); List<Student> list = query.list(); //嵌套条件查询 //查询Id大于5小于10,且Name值为"一"的数据 QueryBuilder<Student> qb = daoSession.queryBuilder(Student.class); List<Student> list = qb.where( StudentDao.Properties.Name.eq("一") , qb.and(StudentDao.Properties.Id.gt(5), StudentDao.Properties.Id.le(50)) ).list(); //取10条Id大于1的数据,且偏移2条 QueryBuilder<Student> qb = daoSession.queryBuilder(Student.class); List<Student> list = qb.where(StudentDao.Properties.Id.gt(1))//搜索条件为Id值大于1,即结果为[2,3,4,5,6,7,8,9,10,11]; .limit(10) .offset(2)// offset(2)表示往后偏移2个,结果为[4,5,6,7,8,9,10,11,12,13]; .list(); //多次执行查找 QueryBuilder<Student> qb = daoSession.queryBuilder(Student.class); Query<Student> query = qb.where(StudentDao.Properties.Id.gt(1))//搜索条件为Id值大于1,即结果为[2,3,4,5,6,7,8,9,10,11]; .limit(10) .offset(2)// offset(2)表示往后偏移2个,结果为[4,5,6,7,8,9,10,11,12,13]; .build(); List<Student> list = query.list(); /* 通过 setParameter 来修改上面的查询条件, * 比如我们将上面条件修改取10条Id值大于5,往后偏移两位的数据,方法如下! */ query.setParameter(0,5);//第一个(position为0)参数值,改为5 List<Student> list1 = query.list();

//在多个线程中使用QueryBuilder 多个线程中使用查询,必须调用 【forCurrentThread()】以获取当前线程的Query实例。 Query的对象实例绑定到构建查询的拥有线程 可以安全地在Query对象上设置参数,而其他线程不会干扰

如果其他线程尝试在查询上设置参数、执行绑定到另一个线程的查询,则会抛出异常。

像这样,您不需要同步语句。 实际上,您应该避免锁定,因为如果并发事务使用相同的Query对象,这可能会导致死锁。

每次调用 forCurrentThread()时,参数都会在使用其构建器构建查询时设置为初始参数。

//使用QueryBuilder进行批量删除操作:删除符合某些条件的所有实体 QueryBuilder<Student> qb = daoSession.queryBuilder(Student.class) .where(StudentDao.Properties.Id.gt(5)); DeleteQuery<Student> deleteQuery = qb.buildDelete(); deleteQuery.executeDeleteWithoutDetachingEntities();
7、GreenDao的注解使用

1)@Entity注解

在【实体类】中使用了@Entity注解GreenDao才会创建对应的表。

@Entity( schema = "myschema", //如果你有多个架构,你可以告诉GreenDao当前属于哪个架构 active = true, //标记一个实体处于活跃状态,活动实体有更新、删除和刷新方法 nameInDb = "AWESOME_USERS",//数据中使用的别名,默认使用的是实体的类名 /* 标记如果DAO应该创建数据库表(默认为true), * 如果您有多个实体映射到一个表,或者表的创建是在greenDAO之外进行的,那么将其设置为false */ indexes = { @Index(value = "message DESC", unique = true) }, createInDb = false, //标记创建数据库表 generateConstructors = true, //如果缺少,是否应生成构造方法 generateGettersSetters = true //如果缺少,是否应生成属性的getter和setter方法 ) public class Student{ ... }

2)基础属性注解(@Id,@Property,@NotNull,@Transient) i. @Id

@Id 注解选择 long / Long属性作为实体ID。在数据库方面,它是主键。 参数autoincrement = true 表示自增, id不给赋值或者为赋值为null即可

这里需要注意,如果要实现自增,id必须是Long, 为long不行!

@Entity public class Student { @Id(autoincrement = true) Long id; …… }

ii. @Property 允许您定义属性映射到的非默认列名,自定义与类变量名不同的数据库存储字段名。 如果不存在,GreenDAO将以SQL-ish方式使用字段名称(默认变量名大写,作为数据库字段名, 下划线而不是camel情况,例如 name将成为 NAME) 注意:您当前只能使用内联常量来指定列名。

@Entity public class Student { @Id(autoincrement = true) Long id; //设置了,数据库中的表格属性名为"name",如果不设置,数据库中表格属性名为"NAME" @Property (nameInDb="name") String name; …… }

iii. @NotNull 设置数据库表 当前列不能为空

iv.@Transient

添加此标记之后,不会生成数据库表的列。 标记要从持久性中排除的属性 将它们用于临时状态等。 或者,您也可以使用Java中的transient关键字。

3)索引注解

i.@Index 使用 @Index 作为一个属性来创建一个索引,通过 name 设置索引别名,也可以通过 unique给索引添加约束。

ii.@Unique 向索引添加 UNIQUE 约束,强制所有值都是唯一的

@Entity public class Student { @Id(autoincrement = true) Long id; @Property(nameInDb="name") @Index(unique = true) String name; …… }

4)关系注解 关系型注解GreenDao中主要就两个

@ToOne: 定义与另一个实体(一个实体对象)的关系

@ToMany: 定义与多个实体对象的关系

8、GreenDao的表关系处理

1)一对一 一个学生对应一个身份证号:

做法: i.我们在Student中设置一个注解 @ToOne( joinProperty = “name”) ii.在创建Student的时候,将对应的数据传递给 IdCard;

学生Student代码:

@Entity public class Student { @Id(autoincrement = true) Long id; @Unique int studentNo;//学号 int age; //年龄 String telPhone;//手机号 String sex; //性别 String name;//姓名 String address;//家庭住址 String schoolName;//学校名字 String grade;//几年级 @ToOne(joinProperty = "name") IdCard student; }

身份证IdCard代码:

@Entity public class IdCard { @Id String userName;//用户名 @Unique String idNo;//身份证号 }

insert一组数据:

public void addStudent(){ DaoSession daoSession = ((AserbaoApplication) getApplication()).getDaoSession(); Student student = new Student(); student.setStudentNo(i); int age = mRandom.nextInt(10) + 10; student.setAge(age); student.setTelPhone(RandomValue.getTel()); String chineseName = RandomValue.getChineseName(); student.setName(chineseName); if (i % 2 == 0) { student.setSex("男"); } else { student.setSex("女"); } student.setAddress(RandomValue.getRoad()); student.setGrade(String.valueOf(age % 10) + "年纪"); student.setSchoolName(RandomValue.getSchoolName()); daoSession.insert(student); //插入对应的IdCard数据 IdCard idCard = new IdCard(); idCard.setUserName(userName); idCard.setIdNo(RandomValue.getRandomID()); daoSession.insert(idCard); }

2)一对多

一个人拥有多个信用卡

做法: i.在我们在Student中设置 @ToMany(referencedJoinProperty = “studentId”);

ii.我们在CreditCard中设置编写对应的id主键

Student的代码:

@Entity public class Student { @Id(autoincrement = true) Long id; @Unique int studentNo;//学号 int age; //年龄 String telPhone;//手机号 String sex; //性别 String name;//姓名 String address;//家庭住址 String schoolName;//学校名字 String grade;//几年级 //这个 studentId 是对应在CreditCard中的 studentId @ToMany(referencedJoinProperty = "studentId") List<CreditCard> creditCardsList; }

CreditCard的代码:

@Entity public class CreditCard { @Id Long id; Long studentId; Long teacherId; String userName;//持有者名字 String cardNum;//卡号 String whichBank;//哪个银行的 int cardType;//卡等级,分类 0 ~ 5 }

添加数据代码:

public void addStudent(){ DaoSession daoSession = ((AserbaoApplication) getApplication()).getDaoSession(); Student student = new Student(); student.setStudentNo(i); int age = mRandom.nextInt(10) + 10; student.setAge(age); student.setTelPhone(RandomValue.getTel()); String chineseName = RandomValue.getChineseName(); student.setName(chineseName); if (i % 2 == 0) { student.setSex("男"); } else { student.setSex("女"); } student.setAddress(RandomValue.getRoad()); student.setGrade(String.valueOf(age % 10) + "年纪"); student.setSchoolName(RandomValue.getSchoolName()); daoSession.insert(student); //插入对应的CreditCard数据 for (int j = 0; j < random.nextInt(5) + 1 ; j++) { CreditCard creditCard = new CreditCard(); creditCard.setUserId(id); creditCard.setUserName(userName); creditCard.setCardNum(String.valueOf(random.nextInt(899999999) + 100000000) + String.valueOf(random.nextInt(899999999) + 100000000)); creditCard.setWhichBank(RandomValue.getBankName()); creditCard.setCardType(random.nextInt(10)); daoSession.insert(creditCard); } }

3)多对多 需要新增一张关系表,用来维护多对多的表关系

一个学生有多个老师,老师有多个学生。

做法: i.我们需要创建一个学生老师管理器(StudentAndTeacherBean),用来对应学生和老师的ID;

ii.我们需要在学生对象中,添加注解:

@ToMany @JoinEntity(entity = StudentAndTeacherBean.class, //增加关系表 StudentAndTeacherBean sourceProperty = "studentId", targetProperty = "teacherId") List<Teacher> teacherList;

iii.我们需要在老师对象中,添加注解:

@ToMany @JoinEntity(entity = StudentAndTeacherBean.class, //增加关系表 StudentAndTeacherBean sourceProperty = "teacherId", targetProperty = "studentId") List<Student> studentList;

StudentAndTeacherBean代码:

@Entity public class StudentAndTeacherBean { @Id(autoincrement = true) Long id; Long studentId;//学生ID Long teacherId;//老师ID }

Student 代码:

@Entity public class Student { @Id(autoincrement = true) Long id; @Unique int studentNo;//学号 int age; //年龄 String telPhone;//手机号 String sex; //性别 String name;//姓名 String address;//家庭住址 String schoolName;//学校名字 String grade;//几年级 @ToMany @JoinEntity(entity = StudentAndTeacherBean.class, sourceProperty = "studentId", targetProperty = "teacherId") List<Teacher> teacherList; }

Teacher代码:

@Entity public class Teacher { @Id(autoincrement = true) Long id; @Unique int teacherNo;//职工号 int age; //年龄 String sex; //性别 String telPhone; String name;//姓名 String schoolName;//学校名字 String subject;//科目 @ToMany @JoinEntity(entity = StudentAndTeacherBean.class, sourceProperty = "teacherId", targetProperty = "studentId") List<Student> studentList; }

数据添加:

public void addData(){ Student student = new Student(); student.setStudentNo(i); int age = mRandom.nextInt(10) + 10; student.setAge(age); student.setTelPhone(RandomValue.getTel()); String chineseName = RandomValue.getChineseName(); student.setName(chineseName); if (i % 2 == 0) { student.setSex("男"); } else { student.setSex("女"); } student.setAddress(RandomValue.getRoad()); student.setGrade(String.valueOf(age % 10) + "年纪"); student.setSchoolName(RandomValue.getSchoolName()); daoSession.insert(student); Collections.shuffle(teacherList); for (int j = 0; j < mRandom.nextInt(8) + 1; j++) { if(j < teacherList.size()){ Teacher teacher = teacherList.get(j); StudentAndTeacherBean teacherBean = new StudentAndTeacherBean(student.getId(), teacher.getId()); daoSession.insert(teacherBean); } } }
9、GreenDao数据库加密

对于存储于数据库中的敏感数据,我们可以通过对数据库加密来进行保护 GreenDao 可通过 SQLCipher 来进行加密

1)导入加密库文件

implementation 'net.zetetic:android-database-sqlcipher:3.5.6'

2)修改DaoSession的生成方式

//MyDaoMaster helper =new MyDaoMaster(this, "aserbaos.db"); //数据库升级写法 DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(this, "aserbao.db"); //SQLiteDatabase db = helper.getWritableDatabase(); //不加密的写法 Database db = helper.getEncryptedWritableDb("aserbao"); //数据库加密密码为“aserbao"的写法 DaoMaster daoMaster = new DaoMaster(db); daoSession = daoMaster.newSession();
10、GreenDao的升级

GreenDao的 OpenHelper 下有个 onUpgrade(Database db, int oldVersion, int newVersion)方法, 当设置的数据库版本改变时,在数据库初始化的时候就会回调到这个方法, 我们可以通过继承 OpenHelper 重写 onUpgrade方法来实现数据库更新操作:

GreenDao的升级思路: 创建临时表; 把原表数据拷贝到临时表; 删除之前的原表; 创建新表; 将临时表中的数据复制到新表中,最后将TMP_表删除掉;

总共两个类: 一个 MyOpenHelper(OpenHelper继承类),一个 MigrationHelper(数据库操作类) 下面是代码编写。

1)修改Application中的DaoMaster的创建:

MyOpenHelper helper = new MyOpenHelper(this, "aserbaos.db"); //DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(this, "aserbao.db"); SQLiteDatabase db = helper.getWritableDatabase(); DaoMaster daoMaster = new DaoMaster(db); daoSession = daoMaster.newSession();

2)MyOpenHelper代码

public class MyOpenHelper extends OpenHelper { private static final String TAG = "MyOpenHelper"; public MyDaoMaster(Context context, String name) { super(context, name); } public MyOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory) { super(context, name, factory); } @Override public void onUpgrade(Database db, int oldVersion, int newVersion) { super.onUpgrade(db, oldVersion, newVersion); MigrationHelper.migrate(db, new MigrationHelper.ReCreateAllTableListener() { @Override public void onCreateAllTables(Database db, boolean ifNotExists) { DaoMaster.createAllTables(db, ifNotExists); } @Override public void onDropAllTables(Database db, boolean ifExists) { DaoMaster.dropAllTables(db, ifExists); } },ThingDao.class); Log.e(TAG, "onUpgrade: " + oldVersion + " newVersion = " + newVersion); } }

3)MigrationHelper 代码

public final class MigrationHelper { public static boolean DEBUG = false; private static String TAG = "MigrationHelper"; private static final String SQLITE_MASTER = "sqlite_master"; private static final String SQLITE_TEMP_MASTER = "sqlite_temp_master"; private static WeakReference<ReCreateAllTableListener> weakListener; public interface ReCreateAllTableListener{ void onCreateAllTables(Database db, boolean ifNotExists); void onDropAllTables(Database db, boolean ifExists); } public static void migrate(SQLiteDatabase db, Class<? extends AbstractDao<?, ?>>... daoClasses) { printLog("【The Old Database Version】" + db.getVersion()); Database database = new StandardDatabase(db); migrate(database, daoClasses); } public static void migrate(SQLiteDatabase db, ReCreateAllTableListener listener, Class<? extends AbstractDao<?, ?>>... daoClasses) { weakListener = new WeakReference<>(listener); migrate(db, daoClasses); } public static void migrate(Database database, ReCreateAllTableListener listener, Class<? extends AbstractDao<?, ?>>... daoClasses) { weakListener = new WeakReference<>(listener); migrate(database, daoClasses); } public static void migrate(Database database, Class<? extends AbstractDao<?, ?>>... daoClasses) { printLog("【Generate temp table】start"); generateTempTables(database, daoClasses); printLog("【Generate temp table】complete"); ReCreateAllTableListener listener = null; if (weakListener != null) { listener = weakListener.get(); } if (listener != null) { listener.onDropAllTables(database, true); printLog("【Drop all table by listener】"); listener.onCreateAllTables(database, false); printLog("【Create all table by listener】"); } else { dropAllTables(database, true, daoClasses); createAllTables(database, false, daoClasses); } printLog("【Restore data】start"); restoreData(database, daoClasses); printLog("【Restore data】complete"); } private static void generateTempTables(Database db, Class<? extends AbstractDao<?, ?>>... daoClasses) { for (int i = 0; i < daoClasses.length; i++) { String tempTableName = null; DaoConfig daoConfig = new DaoConfig(db, daoClasses[i]); String tableName = daoConfig.tablename; if (!isTableExists(db, false, tableName)) { printLog("【New Table】" + tableName); continue; } try { tempTableName = daoConfig.tablename.concat("_TEMP"); StringBuilder dropTableStringBuilder = new StringBuilder(); dropTableStringBuilder.append("DROP TABLE IF EXISTS ").append(tempTableName).append(";"); db.execSQL(dropTableStringBuilder.toString()); StringBuilder insertTableStringBuilder = new StringBuilder(); insertTableStringBuilder.append("CREATE TEMPORARY TABLE ").append(tempTableName); insertTableStringBuilder.append(" AS SELECT * FROM ").append(tableName).append(";"); db.execSQL(insertTableStringBuilder.toString()); printLog("【Table】" + tableName +"\n ---Columns-->"+getColumnsStr(daoConfig)); printLog("【Generate temp table】" + tempTableName); } catch (SQLException e) { Log.e(TAG, "【Failed to generate temp table】" + tempTableName, e); } } } private static boolean isTableExists(Database db, boolean isTemp, String tableName) { if (db == null || TextUtils.isEmpty(tableName)) { return false; } String dbName = isTemp ? SQLITE_TEMP_MASTER : SQLITE_MASTER; String sql = "SELECT COUNT(*) FROM " + dbName + " WHERE type = ? AND name = ?"; Cursor cursor=null; int count = 0; try { cursor = db.rawQuery(sql, new String[]{"table", tableName}); if (cursor == null || !cursor.moveToFirst()) { return false; } count = cursor.getInt(0); } catch (Exception e) { e.printStackTrace(); } finally { if (cursor != null) cursor.close(); } return count > 0; } private static String getColumnsStr(DaoConfig daoConfig) { if (daoConfig == null) { return "no columns"; } StringBuilder builder = new StringBuilder(); for (int i = 0; i < daoConfig.allColumns.length; i++) { builder.append(daoConfig.allColumns[i]); builder.append(","); } if (builder.length() > 0) { builder.deleteCharAt(builder.length() - 1); } return builder.toString(); } private static void dropAllTables(Database db, boolean ifExists, @NonNull Class<? extends AbstractDao<?, ?>>... daoClasses) { reflectMethod(db, "dropTable", ifExists, daoClasses); printLog("【Drop all table by reflect】"); } private static void createAllTables(Database db, boolean ifNotExists, @NonNull Class<? extends AbstractDao<?, ?>>... daoClasses) { reflectMethod(db, "createTable", ifNotExists, daoClasses); printLog("【Create all table by reflect】"); } /** * dao class already define the sql exec method, so just invoke it */ private static void reflectMethod(Database db, String methodName, boolean isExists, @NonNull Class<? extends AbstractDao<?, ?>>... daoClasses) { if (daoClasses.length < 1) { return; } try { for (Class cls : daoClasses) { Method method = cls.getDeclaredMethod(methodName, Database.class, boolean.class); method.invoke(null, db, isExists); } } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } private static 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"); if (!isTableExists(db, true, tempTableName)) { continue; } try { // get all columns from tempTable, take careful to use the columns list List<TableInfo> newTableInfos = TableInfo.getTableInfo(db, tableName); List<TableInfo> tempTableInfos = TableInfo.getTableInfo(db, tempTableName); ArrayList<String> selectColumns = new ArrayList<>(newTableInfos.size()); ArrayList<String> intoColumns = new ArrayList<>(newTableInfos.size()); for (TableInfo tableInfo : tempTableInfos) { if (newTableInfos.contains(tableInfo)) { String column = '`' + tableInfo.name + '`'; intoColumns.add(column); selectColumns.add(column); } } // NOT NULL columns list for (TableInfo tableInfo : newTableInfos) { if (tableInfo.notnull && !tempTableInfos.contains(tableInfo)) { String column = '`' + tableInfo.name + '`'; intoColumns.add(column); String value; if (tableInfo.dfltValue != null) { value = "'" + tableInfo.dfltValue + "' AS "; } else { value = "'' AS "; } selectColumns.add(value + column); } } if (intoColumns.size() != 0) { StringBuilder insertTableStringBuilder = new StringBuilder(); insertTableStringBuilder.append("REPLACE INTO ").append(tableName).append(" ("); insertTableStringBuilder.append(TextUtils.join(",", intoColumns)); insertTableStringBuilder.append(") SELECT "); insertTableStringBuilder.append(TextUtils.join(",", selectColumns)); insertTableStringBuilder.append(" FROM ").append(tempTableName).append(";"); db.execSQL(insertTableStringBuilder.toString()); printLog("【Restore data】 to " + tableName); } StringBuilder dropTableStringBuilder = new StringBuilder(); dropTableStringBuilder.append("DROP TABLE ").append(tempTableName); db.execSQL(dropTableStringBuilder.toString()); printLog("【Drop temp table】" + tempTableName); } catch (SQLException e) { Log.e(TAG, "【Failed to restore data from temp table 】" + tempTableName, e); } } } private static List<String> getColumns(Database db, String tableName) { List<String> columns = null; Cursor cursor = null; try { cursor = db.rawQuery("SELECT * FROM " + tableName + " limit 0", null); if (null != cursor && cursor.getColumnCount() > 0) { columns = Arrays.asList(cursor.getColumnNames()); } } catch (Exception e) { e.printStackTrace(); } finally { if (cursor != null) cursor.close(); if (null == columns) columns = new ArrayList<>(); } return columns; } private static void printLog(String info){ if(DEBUG){ Log.d(TAG, info); } } private static class TableInfo { int cid; String name; String type; boolean notnull; String dfltValue; boolean pk; @Override public boolean equals(Object o) { return this == o || o != null && getClass() == o.getClass() && name.equals(((TableInfo) o).name); } @Override public String toString() { return "TableInfo{" + "cid=" + cid + ", name='" + name + '\'' + ", type='" + type + '\'' + ", notnull=" + notnull + ", dfltValue='" + dfltValue + '\'' + ", pk=" + pk + '}'; } private static List<TableInfo> getTableInfo(Database db, String tableName) { String sql = "PRAGMA table_info(" + tableName + ")"; printLog(sql); Cursor cursor = db.rawQuery(sql, null); if (cursor == null) return new ArrayList<>(); TableInfo tableInfo; List<TableInfo> tableInfos = new ArrayList<>(); while (cursor.moveToNext()) { tableInfo = new TableInfo(); tableInfo.cid = cursor.getInt(0); tableInfo.name = cursor.getString(1); tableInfo.type = cursor.getString(2); tableInfo.notnull = cursor.getInt(3) == 1; tableInfo.dfltValue = cursor.getString(4); tableInfo.pk = cursor.getInt(5) == 1; tableInfos.add(tableInfo); // printLog(tableName + ":" + tableInfo); } cursor.close(); return tableInfos; } } }

推荐阅读: 一篇技术好文之Android数据库 GreenDao的使用完全解析 https://www.jianshu.com/p/53083f782ea2 GreenDao gitHub 地址 https://github.com/greenrobot/greenDAO GreenDao官方帮助文档 http://greenrobot.org/greendao/documentation/how-to-get-started/ GreenDao数据库迁移帮助类 MigrationHelper https://stackoverflow.com/questions/13373170/greendao-schema-update-and-data-migration/30334668#30334668

最新回复(0)