[关闭]
@ruicbAndroid 2017-02-27T12:34:03.000000Z 字数 9993 阅读 1456

GreenDao 3.2.0 教程

我的公众号&博客


本教程涉及GreenDao 3.2.0的接入、使用以及更新。

1 接入

1.1 添加依赖

1.在Project(build.gradle)中:

  1. buildscript {
  2. repositories {
  3. jcenter()
  4. mavenCentral()
  5. }
  6. dependencies {
  7. //对gradle_plugin要求2.2.0+ https://github.com/greenrobot/greenDAO/issues/565
  8. classpath 'com.android.tools.build:gradle:2.2.3'
  9. classpath 'org.greenrobot:greendao-gradle-plugin:3.2.1'
  10. }
  11. }

2.在App(build.gradle)中:

  1. apply plugin: 'com.android.application'
  2. apply plugin: 'org.greenrobot.greendao'
  3. android {
  4. compileSdkVersion 25
  5. buildToolsVersion "25.0.2"
  6. defaultConfig {
  7. //...
  8. }
  9. buildTypes {
  10. //...
  11. }
  12. greendao{
  13. schemaVersion 1//数据库版本号,从1开始
  14. daoPackage 'com.ruicb.greendaotest.database'//自动生成DaoMaster、DaoSession的目录
  15. targetGenDir 'src/main/java'//目标目录
  16. }
  17. }
  18. dependencies {
  19. //添加对greenDao的依赖
  20. compile 'org.greenrobot:greendao:3.2.0'
  21. }

3.同步一下项目,将依赖拉取到本地。

1.2 激活GreenDao自动生成管理类和会话类。

新增一个bean对象,只写属性即可,如下:

  1. @Entity
  2. public class Face {
  3. @Id
  4. Long id;
  5. @NotNull
  6. String localPath;
  7. }

build一下项目,greenDao会在之前指定的目录下,自动生成管理类DaoMaster和会话类DaoSession,同时会自动为Face类完善构造方法和getter、setter方法,以及生成一个对应的FaceDao类。

1.3 初始化GreenDao

在自定义Application中初始化GreenDao,并对外暴露DaoSession,作为后期对数据库进行操作的入口:

  1. public class App extends Application {
  2. private static DaoSession daoSession;
  3. @Override
  4. public void onCreate() {
  5. super.onCreate();
  6. /**
  7. * context 上下文
  8. * name 要创建的数据库名称
  9. */
  10. DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(this, "magic-sticker-db");
  11. Database db = helper.getWritableDb();
  12. daoSession = new DaoMaster(db).newSession();
  13. }
  14. //对外暴露DaoSession
  15. public static DaoSession getDaoSession() {
  16. return daoSession;
  17. }
  18. }

2 使用

依赖并初始化完毕,下面就是Api的使用,不再赘述。比如向数据库中插入Face对象:

  1. //得到会话
  2. DaoSession session = App.getDaoSession();
  3. Face face = new Face();
  4. session.getFaceDao().insert(face);

3 更新

3.1 GreenDao自身更新的问题

GreenDao自带的更新类DevOpenHelper,是不可用的,如下:

  1. /** WARNING: Drops all table on Upgrade! Use only during development. */
  2. public static class DevOpenHelper extends OpenHelper {
  3. public DevOpenHelper(Context context, String name) {
  4. super(context, name);
  5. }
  6. public DevOpenHelper(Context context, String name, CursorFactory factory) {
  7. super(context, name, factory);
  8. }
  9. @Override
  10. public void onUpgrade(Database db, int oldVersion, int newVersion) {
  11. dropAllTables(db, true);
  12. onCreate(db);
  13. }
  14. }

注释明确说明了仅限于开发阶段,并不适合上线后再更新数据库。在11、12行,GreenDao在数据库版本更新时,默认删除所有表再新建,这对于线上app来说是致命的。

可能我们会想,那我们改掉它不就行了吗?改是不行的,因为这个类是GreenDao自己生成的,即使改了也会在下次build时被覆盖,没用的。

3.2 自定义更新

既然如此,我们就需要有自己的更新类继承自OpenHelper,重写onUpgrade()方法以自己维护更新:

  1. public class MyOpenHelper extends DaoMaster.OpenHelper {
  2. public MyOpenHelper(Context context, String name) {
  3. super(context, name);
  4. }
  5. public MyOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory) {
  6. super(context, name, factory);
  7. }
  8. @Override
  9. public void onUpgrade(Database db, int oldVersion, int newVersion) {
  10. super.onUpgrade(db, oldVersion, newVersion);
  11. //写自己的更新逻辑
  12. }

此时,就可以在14行的位置写自己的逻辑了。

同时,要让GreenDao知道我们使用自定义的更新类,所以修改Application中初始化得操作:

  1. public class App extends Application {
  2. private static DaoSession daoSession;
  3. @Override
  4. public void onCreate() {
  5. super.onCreate();
  6. //使用自定义类
  7. MyOpenHelper helper = new MyOpenHelper(this, "magic-sticker-db");
  8. //...
  9. }
  10. //...
  11. }

下面开始真正的更新操作,分别以新建表更新已有表的字段为例。

3.3 新建表

新建表是比较简单的,大致步骤如下:
1.新建bean对象,build一下项目,会生成对应的XxxDao;
2.修改app:build.gradle中的版本号,加1;
3.在MyOpenHelperonUpgrade()方法中创建新表:

  1. XxxDao.createTable(db, false);

4.运行即可;

3.4 更新已有表的字段

相比于新建,新增字段比较复杂,以在Face为例,分为三步:
1. 备份Face表到Face_Temp;
2. 删除Face表;
3. 新建Fce表;
4. 迁移Face_Temp的数据至Face;

我们有一个开源的辅助类,如下:

  1. public class MigrationHelper {
  2. private static final String CONVERSION_CLASS_NOT_FOUND_EXCEPTION =
  3. "MIGRATION HELPER - CLASS DOESN'T MATCH WITH THE CURRENT PARAMETERS";
  4. private static MigrationHelper instance;
  5. public static MigrationHelper getInstance() {
  6. if (instance == null) {
  7. instance = new MigrationHelper();
  8. }
  9. return instance;
  10. }
  11. public void migrate(Database db, Class<? extends AbstractDao<?, ?>>... daoClasses) {
  12. //备份
  13. generateTempTables(db, daoClasses);
  14. //删除
  15. DaoMaster.dropAllTables(db, true);
  16. //重新创建
  17. DaoMaster.createAllTables(db, false);
  18. //恢复数据
  19. restoreData(db, daoClasses);
  20. }
  21. /**
  22. * 备份要更新的表
  23. */
  24. private void generateTempTables(Database db, Class<? extends AbstractDao<?, ?>>... daoClasses) {
  25. for (int i = 0; i < daoClasses.length; i++) {
  26. DaoConfig daoConfig = new DaoConfig(db, daoClasses[i]);
  27. String divider = "";
  28. String tableName = daoConfig.tablename;
  29. String tempTableName = daoConfig.tablename.concat("_TEMP");
  30. ArrayList<String> properties = new ArrayList<>();
  31. StringBuilder createTableStringBuilder = new StringBuilder();
  32. createTableStringBuilder.append("CREATE TABLE ").append(tempTableName).append(" (");
  33. for (int j = 0; j < daoConfig.properties.length; j++) {
  34. String columnName = daoConfig.properties[j].columnName;
  35. if (getColumns(db, tableName).contains(columnName)) {
  36. properties.add(columnName);
  37. String type = null;
  38. try {
  39. type = getTypeByClass(daoConfig.properties[j].type);
  40. } catch (Exception exception) {
  41. }
  42. createTableStringBuilder.append(divider).append(columnName).append(" ").append(type);
  43. if (daoConfig.properties[j].primaryKey) {
  44. createTableStringBuilder.append(" PRIMARY KEY");
  45. }
  46. divider = ",";
  47. }
  48. }
  49. createTableStringBuilder.append(");");
  50. db.execSQL(createTableStringBuilder.toString());
  51. StringBuilder insertTableStringBuilder = new StringBuilder();
  52. insertTableStringBuilder.append("INSERT INTO ").append(tempTableName).append(" (");
  53. insertTableStringBuilder.append(TextUtils.join(",", properties));
  54. insertTableStringBuilder.append(") SELECT ");
  55. insertTableStringBuilder.append(TextUtils.join(",", properties));
  56. insertTableStringBuilder.append(" FROM ").append(tableName).append(";");
  57. db.execSQL(insertTableStringBuilder.toString());
  58. }
  59. }
  60. /**
  61. * 恢复数据
  62. */
  63. private void restoreData(Database db, Class<? extends AbstractDao<?, ?>>... daoClasses) {
  64. for (int i = 0; i < daoClasses.length; i++) {
  65. DaoConfig daoConfig = new DaoConfig(db, daoClasses[i]);
  66. String tableName = daoConfig.tablename;
  67. String tempTableName = daoConfig.tablename.concat("_TEMP");
  68. ArrayList<String> properties = new ArrayList();
  69. for (int j = 0; j < daoConfig.properties.length; j++) {
  70. String columnName = daoConfig.properties[j].columnName;
  71. if (getColumns(db, tempTableName).contains(columnName)) {
  72. properties.add(columnName);
  73. }
  74. }
  75. StringBuilder insertTableStringBuilder = new StringBuilder();
  76. insertTableStringBuilder.append("INSERT INTO ").append(tableName).append(" (");
  77. insertTableStringBuilder.append(TextUtils.join(",", properties));
  78. insertTableStringBuilder.append(") SELECT ");
  79. insertTableStringBuilder.append(TextUtils.join(",", properties));
  80. insertTableStringBuilder.append(" FROM ").append(tempTableName).append(";");
  81. StringBuilder dropTableStringBuilder = new StringBuilder();
  82. dropTableStringBuilder.append("DROP TABLE ").append(tempTableName);
  83. db.execSQL(insertTableStringBuilder.toString());
  84. db.execSQL(dropTableStringBuilder.toString());
  85. }
  86. }
  87. private String getTypeByClass(Class<?> type) throws Exception {
  88. if (type.equals(String.class)) {
  89. return "TEXT";
  90. }
  91. if (type.equals(Long.class) || type.equals(Integer.class) || type.equals(long.class)) {
  92. return "INTEGER";
  93. }
  94. if (type.equals(Boolean.class)) {
  95. return "BOOLEAN";
  96. }
  97. Exception exception =
  98. new Exception(CONVERSION_CLASS_NOT_FOUND_EXCEPTION.concat(" - Class: ").concat(type.toString()));
  99. throw exception;
  100. }
  101. private static List<String> getColumns(Database db, String tableName) {
  102. List<String> columns = new ArrayList<>();
  103. Cursor cursor = null;
  104. try {
  105. cursor = db.rawQuery("SELECT * FROM " + tableName + " limit 1", null);
  106. if (cursor != null) {
  107. columns = new ArrayList<>(Arrays.asList(cursor.getColumnNames()));
  108. }
  109. } catch (Exception e) {
  110. Log.v(tableName, e.getMessage(), e);
  111. e.printStackTrace();
  112. } finally {
  113. if (cursor != null) cursor.close();
  114. }
  115. return columns;
  116. }
  117. }

这样,只需要在更新字段时,在MyOpenHelper类的onUpgrade()方法中:

  1. //更新字段
  2. MigrationHelper.getInstance().migrate(db, FaceDao.class);

但是这样有问题,发现其每次都是删除所有表、再新建所有表,意思就是:每次我要更新一张表中的某个字段,我都需要传入所有的表对应的XxxDao.class对象,即使其他的不需要更新,也会经历备份删除新建恢复的过程,效率极其低下。如果你不传所有,只传当前需要更新的表,则其他表的数据都会丢失。

于是我改造了一下,只需要传入你想更新的表对应的类的class对象即可:

  1. public class MigrationHelper {
  2. private static final String CONVERSION_CLASS_NOT_FOUND_EXCEPTION =
  3. "MIGRATION HELPER - CLASS DOESN'T MATCH WITH THE CURRENT PARAMETERS";
  4. private static MigrationHelper instance;
  5. public static MigrationHelper getInstance() {
  6. if (instance == null) {
  7. instance = new MigrationHelper();
  8. }
  9. return instance;
  10. }
  11. public void migrate(Database db, Class<? extends AbstractDao<?, ?>>... daoClasses) {
  12. //备份
  13. generateTempTables(db, daoClasses);
  14. //只删除需要更新的表
  15. deleteOriginalTables(db, daoClasses);
  16. //只创建需要更新的表
  17. //DaoMaster.createAllTables(db, false);
  18. createOrignalTables(db, daoClasses);
  19. //恢复数据
  20. restoreData(db, daoClasses);
  21. }
  22. /**
  23. * 备份要更新的表
  24. */
  25. private void generateTempTables(Database db, Class<? extends AbstractDao<?, ?>>... daoClasses) {
  26. //...
  27. }
  28. /**
  29. * 通过反射,删除要更新的表
  30. */
  31. private void deleteOriginalTables(Database db, Class<? extends AbstractDao<?, ?>>... daoClasses){
  32. for (Class<? extends AbstractDao<?, ?>> daoClass : daoClasses) {
  33. try {
  34. Method method = daoClass.getMethod("dropTable", Database.class, boolean.class);
  35. method.invoke(null, db, true);
  36. } catch (IllegalAccessException e) {
  37. e.printStackTrace();
  38. } catch (InvocationTargetException e) {
  39. e.printStackTrace();
  40. } catch (NoSuchMethodException e) {
  41. e.printStackTrace();
  42. }
  43. }
  44. }
  45. /**
  46. * 通过反射,重新创建要更新的表
  47. */
  48. private void createOrignalTables(Database db, Class<? extends AbstractDao<?, ?>>... daoClasses){
  49. for (Class<? extends AbstractDao<?, ?>> daoClass : daoClasses) {
  50. try {
  51. Method method = daoClass.getMethod("createTable", Database.class, boolean.class);
  52. method.invoke(null, db, false);
  53. } catch (IllegalAccessException e) {
  54. e.printStackTrace();
  55. } catch (InvocationTargetException e) {
  56. e.printStackTrace();
  57. } catch (NoSuchMethodException e) {
  58. e.printStackTrace();
  59. }
  60. }
  61. }
  62. /**
  63. * 恢复数据
  64. */
  65. private void restoreData(Database db, Class<? extends AbstractDao<?, ?>>... daoClasses) {
  66. //...
  67. }
  68. private String getTypeByClass(Class<?> type) throws Exception {
  69. //...
  70. }
  71. private static List<String> getColumns(Database db, String tableName) {
  72. //...
  73. }
  74. }

至此,有关GreenDao依赖、初始化、使用、更新全部介绍完了。

4 跨版本升级

升级数据库时,我们要考虑跨版本更新的用户,所以一般在MyOpenHelperonUpgrade()的方法中,会这么写:

  1. @Override
  2. public void onUpgrade(Database db, int oldVersion, int newVersion) {
  3. super.onUpgrade(db, oldVersion, newVersion);
  4. switch (oldVersion){
  5. case 1:
  6. //更新字段
  7. MigrationHelper.getInstance().migrate(db, FaceDao.class);
  8. case 2:
  9. case 3:
  10. case 4:
  11. //新增表
  12. StickerDao.createTable(db, false);
  13. case 5:
  14. //更新字段
  15. MigrationHelper.getInstance().migrate(db, StickerDao.class);
  16. case 6:
  17. //更新字段
  18. MigrationHelper.getInstance().migrate(db, StickerDao.class);
  19. case 7:
  20. //新增表
  21. StickerBackgroundDao.createTable(db, false);
  22. StickerCategoryDao.createTable(db, false);
  23. //更新字段
  24. MigrationHelper.getInstance().migrate(db, StickerDao.class, FaceDao.class);
  25. case 8:
  26. //更新字段
  27. MigrationHelper.getInstance().migrate(db, StickerBackgroundDao.class);
  28. case 9:
  29. //更新字段
  30. MigrationHelper.getInstance().migrate(db, StickerBackgroundDao.class);
  31. //更新字段
  32. MigrationHelper.getInstance().migrate(db, StickerCategoryDao.class);
  33. //新增表
  34. StickerTextDao.createTable(db, false);
  35. case 10:
  36. case 11:
  37. //新增表
  38. StickerFontDao.createTable(db, false);
  39. }
  40. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注