android笔记之greendao3.0学习总结(代码片段)

mictoy_朱 mictoy_朱     2022-12-13     500

关键词:

前言

本博文旨在记录greenDao3.0的基本用法以及基于greenDao3.0的数据库的升级策略。本文基于Window10下Android Studio环境操作使用。废话不多说,直入正题。


关于greenDao3.0

greenDao是一个对象关系映射(ORM)的框架,能够提供一个接口通过操作对象的方式去操作关系型数据库,它能够让你操作数据库时更简单、更方便。如下图来之其官网的图片:


官网地址:http://greenrobot.org/greendao/

github地址:https://github.com/greenrobot/greenDAO

关于greenDao3.0之前的2.0、1.0版本,本人没有接触(现在似乎也没必要再去了解)过。据网上大神介绍,3.0之前需要通过新建GreenDaoGenerator工程生成Java数据对象(实体)和DAO对象,非常的繁琐而且也加大了使用成本。而GreenDao3.0最大的变化就是采用注解的方式通过编译方式生成Java数据对象和DAO对象。

greenDao3.0特点

greenDao3.0主要有以下特点:

·        1.性能高,号称Android最快的关系型数据库

          2.内存占用小

·        3.库文件比较小,小于100K,编译时间低,而且可以避免65K方法限制

·        4.支持数据库加密 greendao支持SQLCipher进行数据库加密

         5.简洁易用的API


greenDao3.0使用

greenDao3.0基本配置:

在项目的build.gradle添加如下配置:

dependencies 
        classpath 'org.greenrobot:greendao-gradle-plugin:3.0.0'
    

这里添加的是greenDao插件路径。

在主工程下的build.gradle下添加如下配置:

apply plugin: 'org.greenrobot.greendao'

dependencies 
    compile 'org.greenrobot:greendao:3.0.1'

如以上配置所示,greenDao3.0使用需要依赖greenDao插件配合使用。完成这些基本配置后我们同步编译(Sync Project),没毛病,接着往下。

greenDao基本使用:

现在我们就可以使用greenDao框架了,新建实体类:

@Entity
public class User 
    @Id(autoincrement = true)
    private Long id;
    private String name;
    private int age;
    ...
注意:因为我们要使用greenDao3.0框架,所以我们的实体类要添加greenDao3.0的注解@Entity,表示该实体类的相关数据要添加到数据库中。此外,我们还对实体类中id字段添加了注解@Id(autoincrement = true)  表示将该字段设为主键并自动增长,需要注意的是,主键是Long型,不是long。

此外,定义实体类时对于字段还可以添加以下注解:

  • @Id :主键 Long型,可以通过@Id(autoincrement = true)设置自增长
  • @Property:设置一个非默认关系映射所对应的列名,默认是的使用字段名 举例:@Property (nameInDb="name")
  • @NotNul:设置数据库表当前列不能为空
  • @Transient :添加次标记之后不会生成数据库表的列

再次同步编译,编译完成后,我们可以发现,User类中已经自动给我们添加了字段对应的setter、getter方法以及构造函数:

public int getAge() 
        return this.age;
    
    public void setAge(int age) 
        this.age = age;
    
    public String getName() 
        return this.name;
    
    public void setName(String name) 
        this.name = name;
    
    public Long getId() 
        return this.id;
    
    public void setId(Long id) 
        this.id = id;
    
    @Generated(hash = 1309193360)
    public User(Long id, String name, int age) 
        this.id = id;
        this.name = name;
        this.age = age;
    
    @Generated(hash = 586692638)
    public User() 
    
这些都是greenDao3.0插件为我们自动生成的代码。此外,我们还可以在app/build/generated/source目录下新增了一个greendao目录,里面有三个java文件分别是DaoMaster、DaoSession、UserDao。没错,这些文件也是greenDao3.0插件为我们自动生成的。这些类就是我们后面操作数据库主要依赖的类。


那么问题来了,如果我想让这些文件放到其它目录下该怎么办?good question!

这时我们就可以在主工程下的build.gradle添加相应的辅助配置(比如我想将这些文件放在src/mian/java目录下):

greendao 
    targetGenDir 'src/main/java'
这时,我们重新编译,就会发现这些文件已经转移到这个目录下了。


这里的greendao,我们还可以添加下面一些数据库的基本属性配置

  • schemaVersion: 数据库schema版本,也可以理解为数据库版本号
  • daoPackage:设置DaoMaster 、DaoSession、Dao包名
  • targetGenDir:设置DaoMaster 、DaoSession、Dao目录
  • targetGenDirTest:设置生成单元测试目录
  • generateTests:设置自动生成单元测试用例
现在,我们就可以进行greendao数据库的实在操作了。

greenDao简单操作

借助上面生成的DaoMaster、DaoSession、UserDao三个类,我们可以创建一个数据库工具类DBManager

-------------------------------------------------------明天接着写

。。。一天后,继续……

-------------------------------------------------------

首先明确我们这个工具类的功能。很明显,对于数据库的操作,无非就是增、删、改、查,也就是说,我们要在DBManager这个类里实现实现数据库增删改查的功能。

1.声明一个数据库管理者DBManager单例

public class DBManager 
    private static final String dbName = "my_test_db";
    private static DBManager mInstance;
    private DaoMaster.DevOpenHelper mOpenHelper;
    private Context mContext;

    public DBManager(Context context)
        this.mContext = context;
        mOpenHelper = new DaoMaster.DevOpenHelper(mContext,dbName,null);
    

    /**
     * 获取单例引用
     *
     * @param context
     * @return
     */
    public static DBManager getInstance(Context context) 
        if (mInstance == null)
            synchronized (DBManager.class)
                if (mInstance == null)
                    mInstance = new DBManager(context);
                
            
        
        return mInstance;
    

2.获取可写、可读数据库

/**
     * 获取可读数据库
     * @return
     */
    private SQLiteDatabase getReadableDatabase()
        if (mOpenHelper == null)
            mOpenHelper = new DaoMaster.DevOpenHelper(mContext,dbName,null);
        
        SQLiteDatabase db = mOpenHelper.getReadableDatabase();
        return db;
    

    /**
     * 获取可写数据库
     * @return
     */
    private SQLiteDatabase getWritableDatabase()
        if (mOpenHelper == null)
            mOpenHelper = new DaoMaster.DevOpenHelper(mContext,dbName,null);
        
        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
        return db;
    
3.增删改查系列方法

通过greenDao插件给我们自动生成的这三个工具类,我们可以很轻易的实现增删改查系列方法

/**
     * 插入一条记录
     * @param user
     */
    public void insertUser(User user)
        DaoMaster daoMaster = new DaoMaster(getWritableDatabase());
        DaoSession daoSession = daoMaster.newSession();
        UserDao userDao = daoSession.getUserDao();
        userDao.insert(user);
    

    /**
     * 插入用户集合
     *
     * @param users
     */
    public void insertUserList(List<User> users) 
        if (users == null || users.isEmpty()) 
            return;
        
        DaoMaster daoMaster = new DaoMaster(getWritableDatabase());
        DaoSession daoSession = daoMaster.newSession();
        UserDao userDao = daoSession.getUserDao();
        userDao.insertInTx(users);
    

    /**
     * 删除一条记录
     *
     * @param user
     */
    public void deleteUser(User user) 
        DaoMaster daoMaster = new DaoMaster(getWritableDatabase());
        DaoSession daoSession = daoMaster.newSession();
        UserDao userDao = daoSession.getUserDao();
        userDao.delete(user);
    

    /**
     * 清空所有记录
     */
    public void clearUser()
        DaoMaster daoMaster = new DaoMaster(getWritableDatabase());
        DaoSession daoSession = daoMaster.newSession();
        UserDao userDao = daoSession.getUserDao();
        userDao.deleteAll();
    

    /**
     * 批量删除
     * @param list
     */
    public void deleteUsers(List<User> list)
        DaoMaster daoMaster = new DaoMaster(getWritableDatabase());
        DaoSession daoSession = daoMaster.newSession();
        UserDao userDao = daoSession.getUserDao();
        userDao.deleteInTx(list);
    


    /**
     * 更新一条记录
     *
     * @param user
     */
    public void updateUser(User user) 
        DaoMaster daoMaster = new DaoMaster(getWritableDatabase());
        DaoSession daoSession = daoMaster.newSession();
        UserDao userDao = daoSession.getUserDao();
        userDao.update(user);
    

    /**
     * 查询用户列表
     */
    public List<User> queryUserList() 
        DaoMaster daoMaster = new DaoMaster(getReadableDatabase());
        DaoSession daoSession = daoMaster.newSession();
        UserDao userDao = daoSession.getUserDao();
        QueryBuilder<User> qb = userDao.queryBuilder();
        List<User> list = qb.list();
        return list;
    

    /**
     * 根据条件查询用户列表
     */
    public List<User> queryUserList(int age) 
        DaoMaster daoMaster = new DaoMaster(getReadableDatabase());
        DaoSession daoSession = daoMaster.newSession();
        UserDao userDao = daoSession.getUserDao();
        QueryBuilder<User> qb = userDao.queryBuilder();
        qb.where(UserDao.Properties.Age.gt(age)).orderAsc(UserDao.Properties.Age);
        List<User> list = qb.list();
        return list;
    
至此,我们可以真正测试操作数据库了

public class MainActivity extends AppCompatActivity 

    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initData();
    

    private void initData()
        DBManager dbManager = DBManager.getInstance(this);
        for (int i = 0;i<6;i++)
            User user = new User("测试数据"+i,10+i);
            dbManager.insertUser(user);
        

        List<User> users = dbManager.queryUserList();
        for (User user : users)
            Log.i(TAG,user.toString()+"\\n");
        

    
运行结果:



初步测试,没毛病。其它增删改查功能,大家自己可以测试下,这里就不做示例了。接下来,我们讨论下一个问题:数据库升级。

数据库升级

应用上线之后,app的更新或多或少涉及到数据库的版本升级。可能要给某张表添加一个字段,也可能要新增一张表等等此类情况。那么,这里数据库更新可以有两种方案:

方案一:删除旧的数据库,创建新的数据库和数据库表

这种方案,简单粗暴,一般也不会出什么Bug,但也出在一个很明显的问题,就是在已经上线的应用,更新app升级数据库时删除了用户原有的数据,对于用户体验来说是很不友好的。

方案二:保留原有数据库数据,并在此基础上添加新的字段或新表。这种方案也就是为了解决方案一存在的问题。

那么,greenDao3.0在升级数据库上是怎么做的呢?

通过调查源码发现,我们操作的对象mOpenHelper对应的类DevOpenHelper重写了一个方法onUpgrade(),没错,这就是数据库版本升级时要调用的方法,看内容:

@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);
        
通过源码我们发现:

dropAllTables(db,true);//删除当前数据库所有表单;

onCreate(db);//新建当前所有表

即greenDao默认的升级方法就是我们的方案一,简单粗暴,问题也突出。

然而,我们还是先来调查一下它什么时候会调用onUpgrade()方法。

查看源码,发现在执行mOpenHelper.getWritableDatabase()和mOpenHelper.gerReadableDatabase()的时候会比较数据库的版本号和app设置的数据库版本号(即所谓的数据库的旧版本号和新版本号),如果不同者会执行升级(onUpgrade())或降级(onDowngrde())数据库操作,看源码:

final int version = db.getVersion();
            if (version != mNewVersion) 
                if (db.isReadOnly()) 
                    throw new SQLiteException("Can't upgrade read-only database from version " +
                            db.getVersion() + " to " + mNewVersion + ": " + mName);
                

                db.beginTransaction();
                try 
                    if (version == 0) 
                        onCreate(db);
                     else 
                        if (version > mNewVersion) 
                            onDowngrade(db, version, mNewVersion);
                         else 
                            onUpgrade(db, version, mNewVersion);
                        
                    
                    db.setVersion(mNewVersion);
                    db.setTransactionSuccessful();
                 finally 
                    db.endTransaction();
                
            
很明显的,这里的version就是从当前数据库中获取的版本号(即旧版本号),而mNewVersion,我们通过追踪,发现它是从DaoMaster类里传过来的,也就是SCHEMA_VERSION

public class DaoMaster extends AbstractDaoMaster 
    public static final int SCHEMA_VERSION = 1;
    ...
换言之,我们要升级数据库,就要改变这个值,但是我们都知道这是greenDao框架自动生成的,不能手动修改:

// THIS CODE IS GENERATED BY greenDAO, DO NOT EDIT.

是的,即使我们非要手动修改,你会发现编译之后它又还原了。这是,我们就要用到上面提到的greendao辅助配置

我们添加配置schemaVersion,这就是设置新数据库的版本号:

greendao 
    schemaVersion 2
    ...
编译,我们发现,DaoMaster的变成了SCHEMA_VERSION我们设置的值了

/**
 * Master of DAO (schema version 2): knows all DAOs.
 */
public class DaoMaster extends AbstractDaoMaster 
    public static final int SCHEMA_VERSION = 2;
    。。。
Ok,现在就可以升级数据库了,运行:

查看打印信息,我们知道,它确实执行了onUpgrade()方法,也就是完成了数据库的升级

知道了如何执行数据库升级,现在就还剩下最后一个问题,如何实现方案二?

这里我通过网上查阅资料,看到了某大神提供的方法,合并(MigrationHelper),其核心思路如下:

1 把旧表改为临时表

2 建立新表

3 临时表数据写入新表,删除临时表

看MigrationHelper.java核心方法:

public class MigrationHelper 
    /**
     * 调用升级方法
     * @param db
     * @param daoClasses 一系列dao.class
     */
    public static void migrate(Database db, Class<? extends AbstractDao<?, ?>>... daoClasses) 
        //1 新建临时表
        generateTempTables(db, daoClasses);
        //2 创建新表
        createAllTables(db, false, daoClasses);
        //3 临时表数据写入新表,删除临时表
        restoreData(db, daoClasses);
    
    ...
所以我们需要新建一个类MyDBOpenHelper继承DaoMaster.DevOpenHelper,重写onUpgrade()方法,里面调用MigrationHelper.migrate()方法就行了。

public class MyDbOpenHelper extends DaoMaster.DevOpenHelper 

    public MyDbOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory) 
        super(context, name, factory);
    

    @Override
    public void onUpgrade(Database db, int oldVersion, int newVersion) 
        MigrationHelper.migrate(db, UserDao.class);
    

现在,我们修改SCHEMA_VERSION为3,给User添加新字段number,将DBManager中的DaoMaster.DevOpenHelper替换成MyDBOpenHelper,以及修改其他对应代码,编译运行:


我们发现数据库确实保存了原来的数据,并添加了新的字段number。至此,我们终于实现了方案二,完美

BUT,我们新增表能行吗?这里,我们测试下添加一个实体类News

@Entity
public class News 
    private int id;
    private String newsInfo;
    ...
同步编译,同样在DBManager.java里添加增删改查方法,并在onUpgrade()方法里添加NewsDao.class

@Override
    public void onUpgrade(Database db, int oldVersion, int newVersion) 
        MigrationHelper.migrate(db, UserDao.class, NewsDao.class);
    
在MainActivity里添加代码

private void initData()
        ...
        for (int i = 0;i<6;i++)
            News user = new News(i,"测试数据"+i);
            dbManager.insertNews(user);
        

        List<News> newsList = dbManager.queryNewsList();
        for (News user : newsList)
            Log.i(TAG,user.toString()+"\\n");
        
    
修改SCHEMA_VERSION为4,运行,发现提示错误 E/SQLiteLog: (1) no such table: NEWS即没有创建NEWS表

所以大神的MigrationHelper这个类的功能似乎也稍有欠缺,呐,现在我们又有了新的目标,当旧数据库里不存在某表时,我们创建该表。

怎么做呢?是的,greenDao框架怎么做,我们就怎么做。

greenDao是怎么做的呢,通过研究代码发现,greenDao是通过DaoMaster调用createAllTables(Database db, boolean ifNotExists)方法创建表的

public class DaoMaster extends AbstractDaoMaster 
    public static final int SCHEMA_VERSION = 4;

    /** Creates underlying database table using DAOs. */
    public static void createAllTables(Database db, boolean ifNotExists) 
        UserDao.createTable(db, ifNotExists);
        NewsDao.createTable(db, ifNotExists);
    
    ...

/** Creates the underlying database table. */
    public static void createTable(Database db, boolean ifNotExists) 
        String constraint = ifNotExists? "IF NOT EXISTS ": "";
        db.execSQL("CREATE TABLE " + constraint + "\\"NEWS\\" (" + //
                "\\"ID\\" INTEGER NOT NULL ," + // 0: id
                "\\"NEWS_INFO\\" TEXT);"); // 1: newsInfo
    

这就好办了,我们直接使用该方法,研究里面方法里面的sql语句发现,我们要将ifNotExists设为true,即表不存在才创建。

@Override
    public void onUpgrade(Database db, int oldVersion, int newVersion) 
        DaoMaster.createAllTables(db,true);//如果表不存在,则创建
        MigrationHelper.migrate(db, UserDao.class,NewsDao.class);
    

重新编译运行


是的,成功,没毛病

至此,我们基于greenDao的数据库升级就基本搞定啦。

示例源码链接










springcloud学习笔记总结(代码片段)

文章目录1.SpringCloudBus之消息总线介绍2.SpringCloudBus之搭建RabbitMQ3.SpringCloudBus之动态刷新全局广播的设计思想和选型4.SpringCloudBus之动态刷新全局广播配置实现5.SpringCloudStream之消息驱动介绍6.SpringCloudStream之设计思想7.SpringCloudStream之... 查看详情

springcloud学习笔记总结(代码片段)

文章目录1.zuul与Gateway2.Gateway之简介3.Gateway之核心概念+架构流程4.Gateway之搭建Gateway服务器+测试5.Gateway之路由的两种配置方式6.Gateway之配置动态路由7.Gateway之Predicate分类8.Gateway之Filter(GatewayFilter)9.SpringCloudConfig之分布式配置... 查看详情

springcloud学习笔记总结(代码片段)

文章目录1.OpenFeign之介绍2.OpenFeign之消费端的服务调用3.OpenFeign之超时控制4.OpenFeign之日志增强5.Hystrix之重要概念6.jmeter普通服务测试7.Hystrix之服务提供和服务消费环境搭建8.Hystrix之服务降级消费端和服务端9.Hystrix之全局服务降级&#... 查看详情

springcloud学习笔记总结(代码片段)

文章目录1.OpenFeign之介绍2.OpenFeign之消费端的服务调用3.OpenFeign之超时控制4.OpenFeign之日志增强5.Hystrix之重要概念6.jmeter普通服务测试7.Hystrix之服务提供和服务消费环境搭建8.Hystrix之服务降级消费端和服务端9.Hystrix之全局服务降级&#... 查看详情

springcloud学习笔记总结(代码片段)

文章目录1.Eureka之Eureka基础知识2.Eureka之EurekaServer服务端安装3.Eureka之将别的项目入驻进EurekaServer4.Eureka之搭建Eureka集群5.Eureka之搭建业务逻辑集群6.Eureka之actuator微服务信息完善7.Eureka之服务发现Discovery(重点)8.Eureka之自我保护9.Eurek... 查看详情

greendao3.x之基本使用

 在 GreenDao3.X之注解已经了解到GreenDao3.0的改动及注解。对于数据库的操作,无异于增删改查等四个操作。下面我们将了解GreenDao3.X如何使用?AbstractDao   所有的自动生成的XXDao都是继承于AbstractDao,此类中基本上... 查看详情

springcloud学习笔记总结(代码片段)

文章目录1.zookeeper之支付服务注册(服务提供者注册)2.zookeeper之临时节点和持久节点3.zookeeper之服务消费者注册4.Consul之简介5.Consul之安装运行Consul6.Consul之服务提供者注册进consul7.Consul之服务消费者注册进Consul8.三个注册中心的异同... 查看详情

springcloud学习笔记总结(代码片段)

文章目录1.SpringCloudAlibaba简介2.SpringCloudAlibaba之Nacos介绍和安装3.Nacos服务注册与发现之服务提供者注册4.Nacos服务注册与发现之服务消费者注册和负载5.Nacos服务注册与发现之服务注册中心对比提升6.Nacos作为服务配置中心7.Nacos服务... 查看详情

springcloud学习笔记总结(代码片段)

文章目录1.SpringCloudAlibaba之Sentinel(哨兵)概述2.Sentinel之下载安装3.Sentinel之初始化监控4.Sentinel之流控限流4.1流控规则名词解释4.2流控QPS直接失败4.3流控线程数直接失败4.4流控关联4.5流控预热4.6流控排队等待5.Sentinel之熔断降级5.1降... 查看详情

android性能优化之谈谈java内存区域(代码片段)

Android性能优化之谈谈Java内存区域>基础的扎实程度直接决定高度。最近一年副业主要在学习投资和技能学习,把以前学习内存分析的一些笔记总结发出来,写了很多笔记总结都没有写完就又忙着了,最近再次总结复... 查看详情

android性能优化之谈谈java内存区域(代码片段)

Android性能优化之谈谈Java内存区域>基础的扎实程度直接决定高度。最近一年副业主要在学习投资和技能学习,把以前学习内存分析的一些笔记总结发出来,写了很多笔记总结都没有写完就又忙着了,最近再次总结复... 查看详情

vue3学习总结笔记(十四)(代码片段)

文章目录1.Vue3之生命周期2.Vue3之自定义hook函数3.Vue3之toRef和toRefs的使用4.Vue3之shallowReactive和shallowRef函数API5.Vue3之readonly函数和shallowReadonly函数6.Vue3之toRaw和markRaw函数使用7.Vue3之customRef函数使用8.Vue3之provide和inject函数使用8.Vue 查看详情

react学习笔记总结(代码片段)

文章目录1.React之props的使用2.React之对props进行限制3.React之props只读(知道修改在这是什么效果!)4.React之props简写方式5.类式组件中的构造器与props的注意事项6.函数式组件使用props7.类式组件字符串形式的refs属性8.React之回调形... 查看详情

react学习笔记总结(代码片段)

文章目录1.React之props的使用2.React之对props进行限制3.React之props只读(知道修改在这是什么效果!)4.React之props简写方式5.类式组件中的构造器与props的注意事项6.函数式组件使用props7.类式组件字符串形式的refs属性8.React之回调形... 查看详情

cv开山之作:《alexnet》深度学习图像分类经典论文总结学习笔记(原文+总结)

...#xff0c;本文为在学习论文期间的论文总结,也算是学习笔记,希望能帮助到大家,有不足地方还希望大家谅解,以后会给大家带来更高质量的学习论文笔记总结。一.Abstract-摘要摘要中主要提到这三件事: ①AlexNet的... 查看详情

cv开山之作:《alexnet》深度学习图像分类经典论文总结学习笔记(原文+总结)

...#xff0c;本文为在学习论文期间的论文总结,也算是学习笔记,希望能帮助到大家,有不足地方还希望大家谅解,以后会给大家带来更高质量的学习论文笔记总结。一.Abstract-摘要摘要中主要提到这三件事: ①AlexNet的... 查看详情

android:rxjava学习笔记之singlecompletable以及maybe(代码片段)

Single、Completable以及MaybeSingle、Completable以及Maybe概述1、Single2、Completable3、Maybe4、总结参考Single、Completable以及Maybe概述类型描述Observable<T>能够发射0或n个数据,并以成功或错误事件终止。Flowable<T>能够发射0或n个数据&... 查看详情

docker学习笔记总结(代码片段)

文章目录1.docker搭建三主三从的redis集群2.redis集群之主从容错切换迁移(基于redis集群)3.redis集群之主从扩容(基于redis集群)4.redis集群之主从缩容(基于redis集群)5.DockerFile介绍6.DockerFile构建过程解析7.DockerFile保留字简介8.DockerFile保留... 查看详情