Android数据库工具ORM-greenDAO学习

1. 项目地址

2. 项目文档

3. 工程例子下载

4. 相关参考

GreenDao官方文档翻译

greenDaoMaster的学习研究

SQLite数据库框架ORMLite与GreenDao的简单比较

greenDAO讲义(一):使用篇

Android开源:数据库ORM框架GreenDao学习心得及使用总结

Android 数据库升级完整解决方案

greenDao Schema Upgrade

Newest greendao questions - Stack Overflow


之前没有接触过ORM概念,现在有理解了。即是一个中间层,把面向对象的操作方法转换为关系型数据库的操作。这部分不用我们去做。这样,我们可以用熟悉的、方便的面向对象的做法去操作数据库(如增删改查),避免了写不熟悉的SQL语句,避免了写重复的SQL语句。

Android上有几种ORM工具,从大牛那里推荐的是使用greenDAO,那就开始学习使用greenDAO吧。学习完成后,感觉真心不错,这种感觉更多是从编程的角度而来的,建库建表极其方便,对数据的增删改查非常容易理解和方便操作。而性能方面,网上的介绍对比greenDAO也是很出色的。

从项目的托管地址中可以下载到整个项目,整个压缩包包含了DaoCore,使用greenDAO进行数据库操作的源文件;DaoGenerator,生成中间层dao的工具源文件;以及DaoExample和DaoExampleGenerator两个例子文件夹。结合官网的文档说明、网上的介绍资料、以及两个例子文件工程,可以弄懂它的使用操作。于是,自己操作了一遍,并对此操作和遇到的问题进行下记录。

有这么一个记事本的需求,一共要建立七张表,其中表之间有一对一和一对多的关系。那么,该如何来建立呢?这个数据库建立的并不是很好,所以写生成代码的时候也适当的进行了一些小的修改,但对怎样进行建库建表还是有非常好的说明。

Android数据库工具ORM-greenDAO学习_第1张图片


首先新建一个普通的Java工程,引入 freemarker.jar 和 greendao-generator.jar 两个JAR包。先获取一个Schema实例,

public Schema(int version, String defaultJavaPackage)
构造方法的第一个参数是版本号,和用原生的数据库操作类的版本号是对应的,第二个是生成的包名。

	public static void main(String[] args) {
		Schema schema = new Schema(VERSION, "com.example.notepad.databasedao");
		createTable(schema);
	}

有了Schema,就可以获得一个Entity实体,这个实体相对于一张数据表。它的解释很明了:

/*
Model class for an entity: a Java data object mapped to a data base table. A new entity is added to a Schema by the method Schema.addEntity(String) (there is no public constructor for Entity itself). 

*/

有了Entity实例,就可以开始生成字段属性了。生成的方法很简单,就是进行一系列的 add***Property 方法的调用。生成的名字的规则是全部使用大写,在添加的字段名中用下划线来替代骆驼命名。比如,下面的代码就生成了用户表:

	//建立用户表
		Entity user = schema.addEntity("User");
		Property user_username = user.addStringProperty("username").notNull().primaryKey().getProperty();
		user.addStringProperty("password").notNull();
		user.addStringProperty("email");

对于生成的建表代码如下:

    public static void createTable(SQLiteDatabase db, boolean ifNotExists) {
        String constraint = ifNotExists? "IF NOT EXISTS ": "";
        db.execSQL("CREATE TABLE " + constraint + "'USER' (" + //
                "'USERNAME' TEXT PRIMARY KEY NOT NULL ," + // 0: username
                "'PASSWORD' TEXT NOT NULL ," + // 1: password
                "'EMAIL' TEXT);"); // 2: email
    }

大多数情况下,我们需要自己指定字段属性的类型,那我们可以这样做,以建立用户笔记表为例:

		//建立用户笔记表
		Entity userNote = schema.addEntity("UserNote");
		Property note_noteId = userNote.addLongProperty("noteId").primaryKey().autoincrement().getProperty();
		userNote.addIntProperty("noteType").notNull().columnType("tinyint");
		Property note_categoryId = userNote.addLongProperty("categoryId").notNull().getProperty();
		userNote.addIntProperty("boolPrivate").notNull().columnType("tinyint");
		Property note_username = userNote.addStringProperty("username").columnType("varchar(20)").getProperty();

这样我们就自己指定了字段的类型为 tinyint 、varchar(20)、或者指定其它的类型。那么如何添加外键约束呢,表示一对一、一对多的关系呢?这里要说明的是,它并不生成实际的sql外键约束语句,即不是物理的外键约束,只是概念上的外键约束。实现这种关系,对于获取数据是非常方便的。比如,一条笔记对应于一条笔记详细项,

		//一条笔记对应一条笔记详细项
//		userNote.addToOne(userNoteItem, item_noteId);	//错误,会生成错误的代码,要重读文档是如何写的
		userNote.addToOne(userNoteItem, note_noteId);

那我们获得一条笔记时,可以方便的获得笔记的详细项:

	/**
	 * 返回笔记的详细项
	 * @param userNote
	 * @return
	 */
	public UserNoteItem queryUserNoteItems(UserNote userNote) {
		UserNoteItem item = userNote.getUserNoteItem();
		
		log("笔记详细项:" + item.toString());
		
		return item;
	}

再比如,一条笔记对应于一个类别,

		//一条笔记对应一个类别,一对一的关系
		userNote.addToOne(userCategory, note_categoryId, "category");

当我们想知道该类别的信息时,可以这样做:

		UserNote userNote = ...
		UserCategory category = userNote.getCategory();

实现一对多的关系,同样是很方便的和值得做的。比如,一个用户对应于多个类别,

		//一个用户可以有多个类别,一对多关系		
		ToMany userToUserCategory = user.addToMany(userCategory, categoroy_username);
//		userToUserCategory.setName("categorys");
		userToUserCategory.orderAsc(categoryId);

其中,addToMany()的重载方法中,有个name的参数,这是设置get方法的Name,因为对于关系中可能会发生重复;或者单独使用 setName 方法来进行设置。同样,我们可以进行排序处理。

获取到一个用户的实例后,想查看该用户建立的所有类别,可以这样做:

	
	/**
	 * 查询一个用户的全部类别
	 * @param user
	 * @return
	 */
	public List queryUserCategorys(User user ) {
		return user.getUserCategoryList();
	}

真是太方便了。

其它的数据表同样的操作,最后,我们要触发生成这些文件:

			DaoGenerator daoGenerator = new DaoGenerator();
			daoGenerator.generateAll(schema, "../GreenDAOLearn/src-gen");

我们先要在工程目录下新建工程文件GreenDAOLearn,并新建source folder文件夹src-gen即可。

这样,我们就完成了建表的任务。接下来,看看如何进行增删改查。

我们先看初始化的工作:获得一个DevOpenHelper对象,该对象继承抽象类OpenHelper,OpenHlper继承我们熟悉的SQLiteOpenHelper,然后获得一个SQLiteDatabase对象,接着得到DaoMaster和DaoSession对象,得到DaoSession就可以方便的获得各个实体类的DAO方法类。其中上述对象的关系可以参考这里。当然,文档上推荐的是使用单例模式,把DaoSession保存到 ApplicationContext中。

	private DBManager(Context context) {
		if (mDaoSession == null ) {
			DevOpenHelper devOpenHelper = new DaoMaster.DevOpenHelper(context.getApplicationContext(), "notes-db", null);
			mDb = devOpenHelper.getWritableDatabase();
			mDaoSession = new DaoMaster(mDb).newSession();
		}
		
	}
	
	public static synchronized DBManager getInstance(Context context ) {
		if (mInstance == null ) {
			mInstance = new DBManager(context);
			mContext = context;
		}
		
		QueryBuilder.LOG_SQL = true;
		QueryBuilder.LOG_VALUES = true;
		
		return mInstance;
	}

①增加

我们看如何添加一个用户:

	public void addUser(String username, String password, String email ) {
		User user = new User(username, password, email);
		UserDao userDao = mDaoSession.getUserDao();
		userDao.insert(user);
		log("Inserted new user: username:"+user.getUsername()+", password:"+user.getPassword()+", email:"+user.getEmail());
	}

或者如何添加一条笔记:

	public void addUserNote(int noteType, long categoryId, int boolPrivate, String title, String content, String linkUri, 
			Date mdate, byte[] mdata, int commentsCount, String username ) {
		
		UserNoteDao userNoteDao = mDaoSession.getUserNoteDao();
		UserNote userNote = new UserNote();
		userNote.setCategoryId(categoryId);
		userNote.setBoolPrivate(boolPrivate);
		userNote.setUsername(username);
		userNoteDao.insert(userNote);
		
		long noteId = userNote.getNoteId();
		
		log("Add a UserNote");
		log("noteId:"+noteId);
		
		UserNoteItemDao userNoteItemDao = mDaoSession.getUserNoteItemDao();
		UserNoteItem userNoteItem = new UserNoteItem(noteId, title, content, linkUri, mdate, mdata, commentsCount, username);
		userNoteItemDao.insert(userNoteItem);
		
		log("Add a UserNoteItem");
		
		
	}

②删除

删除一个类别,由主键指定:

	public void deleteCategory(Long categoryId ) {
		UserCategoryDao userCategoryDao = mDaoSession.getUserCategoryDao();
		userCategoryDao.deleteByKey(categoryId);
		
		log("Delete a category");
	} 

删除一个类别,由其它字段指定:

	public void deleteCategory(String categoryName ) {
		UserCategoryDao userCategoryDao = mDaoSession.getUserCategoryDao();
		QueryBuilder qb =  userCategoryDao.queryBuilder();
		DeleteQuery bd = qb.where(Properties.CategoryName.eq(categoryName)).buildDelete();
		bd.executeDeleteWithoutDetachingEntities();
		
		log("Delete a category.");
	}

删除一个用户:

	public void deleteUser(String username ) {
		UserDao userDao = mDaoSession.getUserDao();
		userDao.deleteByKey(username);
		log("Delete a user: username:"+username);
	}

③更新

更新的方法有两种,一种是用update语句,如果没有存在相应的数据将会出错;另一种是之前没有接触过的,好像是sqlite的特点之一,即是 insert or replace into 语句,如果不存在则插入,如果存在就更新。

更新用户的信息:

	public void updateUser(User user ) {
		UserDao userDao = mDaoSession.getUserDao();
		userDao.update(user);
	}

插入或更新用户的信息:

	public void updateUser(String username, String password, String email ) {
		UserDao userDao = mDaoSession.getUserDao();
		userDao.insertOrReplace(new User(username, password, email));
		
	}

④查询

我们可以方便的用主键来加载一个实体信息(一条信息),我们看加载一个用户:

	public User loadUser(String username ) {
		UserDao userDao = mDaoSession.getUserDao();
		return userDao.load(username);
	}

文档推荐使用QueryBuilder来构建条件语句,比如查询笔记中类别id为1的所有笔记,where的参数可以使用类似如下的方式构建,

List list2 = userNoteDao.queryBuilder().where(UserNoteDao.Properties.CategoryId.eq(1)).list();

文档的例子如下:

QueryBuilder qb = userDao.queryBuilder();
qb.where(Properties.FirstName.eq("Joe"),
qb.or(Properties.YearOfBirth.gt(1970),
qb.and(Properties.YearOfBirth.eq(1970), Properties.MonthOfBirth.ge(10))));
List youngJoes = qb.list();
如果返回的结果是唯一的,可以如下使用,当不存在时返回为空:

User user = userDao.queryBuilder().where(UserDao.Properties.Username.eq(username)).unique();

我们可以自己写条件语句:

Query query = userDao.queryBuilder().where(new WhereCondition.StringCondition("sql condition")).build();

文档上的例子如下:

Query query = userDao.queryBuilder().where(
new StringCondition("_ID IN " +
"(SELECT USER_ID FROM USER_MESSAGE WHERE READ_FLAG = 0)").build();

或者,

Query query = userDao.queryRawCreate(
  ", GROUP G WHERE G.NAME=? AND T.GROUP_ID=G._ID", "admin");

这个用法是自己写接在select 和 实体(table)后面的语句,具体怎样没有去试验,文档的解释也很清楚:

In case QueryBuilder does not provide the feature you need (e.g. joins), you can fall back to one of the queryRaw or queryRawCreate methods. They allow you to pass a raw SQL string, which is appended after the SELECT and the entities columns. This way, you can have any WHERE and ORDER BY clause you want to select entities. The entity table can be referred using the alias “T”

我们在查询中,通常是取出一定量的数据,而不是一下子全部取出来,那该如何实现呢,看个例子,取出5条类别id为1的的笔记,偏移量为5,这个我们很熟悉:

List list = userNoteDao.queryBuilder().where(UserNoteDao.Properties.CategoryId.eq(1)).offset(5).limit(5).list();

查询中,我们可能要使用到递增还是递减,我们看文档中的例子:

List joes = userDao.queryBuilder()
.where(Properties.FirstName.eq("Joe"))
.orderAsc(Properties.LastName)
.list();

到这里,我们可以使用greenDAO来方便我们的开发了。但还有问题没有解决。使用起来不放心。一个是数据库版本更新的问题,一个是事务操作的问题。

数据库版本更新的问题需要我们在生成的文件中自己手动进行修改。greenDAO的维护团队greenrobot也在stackoverflow中回答说目前并没有支持自动生成。我们看生成的文件DaoMaster.java 中的这段代码:

    /** WARNING: Drops all table on Upgrade! Use only during development. */
    public static class DevOpenHelper extends OpenHelper {
        public DevOpenHelper(Context context, String name, CursorFactory factory) {
            super(context, name, factory);
        }

        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
            Log.i("greenDAO", "Upgrading schema from version " + oldVersion + " to " + newVersion + " by dropping all tables");
            dropAllTables(db, true);
            onCreate(db);
        }
    }

我们可以在回调函数onUpgrade中进行我们自己的升级策略。具体的维护策略和如何操作可以参考 这里,和 这里。

事务操作可以对大量的数据操作进行优化,以及在另一方面保持数据的同步性。greenDAO提供了几种事务操作的方法,比如对同一类型的操作进行大量的处理,它提供了类似如下的方法: 

		try {
			userDao.insertInTx(new User("abc13", "123456", "[email protected]"), new User("abc8", "123456", "[email protected]"),new User("abc14", "123456", "[email protected]"));
		} catch (Exception e) {
			log("执行失败-insertInTx");
			e.printStackTrace();
		}

同样的,我们可以这样进行事务处理:

		//事务
		try {
			mDaoSession.runInTx(new Runnable(){

				@Override
				public void run() {
						int size = userDao.loadAll().size();
						log("user size is " + size);
						userDao.insert(new User("abc20", "123456", "[email protected]"));
						userDao.insert(new User("abc21", "123456", "[email protected]"));
						userDao.insert(new User("abc4", "123456", "[email protected]"));
						userDao.insert(new User("abc22", "123456", "[email protected]"));
						size = userDao.loadAll().size();
						log("user size is " + size);				
				}});
		} catch (Exception e1) {
			log("执行失败-runInTx");
			e1.printStackTrace();
		}

run()方法里面跑的就是一系列的sql操作语句,我们可以查看其中的源码,更方便理解:

    /**
     * Run the given Runnable inside a database transaction. If you except a result, consider callInTx.
     */
    public void runInTx(Runnable runnable) {
        db.beginTransaction();
        try {
            runnable.run();
            db.setTransactionSuccessful();
        } finally {
            db.endTransaction();
        }
    }

一开始,我写出如下的方式,结果事务不能回滚,这当然是非常大的错误,如果结合源码分析,则可以很快的发现我的错误,我把Exception在里边捕获了,db.setTransactionSuccessful()总是会执行,当然不会回滚。

			//事务,错误的写法
			mDaoSession.runInTx(new Runnable(){

				@Override
				public void run() {
						try {
							int size = userDao.loadAll().size();
							log("user size is " + size);
							userDao.insert(new User("abc20", "123456", "[email protected]"));
							userDao.insert(new User("abc21", "123456", "[email protected]"));
							userDao.insert(new User("abc4", "123456", "[email protected]"));
							userDao.insert(new User("abc22", "123456", "[email protected]"));
							size = userDao.loadAll().size();
							log("user size is " + size);
						} catch (Exception e) {
							e.printStackTrace();
						}				
				}});

这样,我们就可以较放心的使用这个工具。我们可以获取SQLiteDatabase对象来进行一些greenDAO没有提供的操作,这要写原始的SQL语句。我们在调试的时候,要打开两个选项,这样就可以观察到构建的SQL语句:

	public static synchronized DBManager getInstance(Context context ) {
		if (mInstance == null ) {
			mInstance = new DBManager(context);
			mContext = context;
		}
		
		QueryBuilder.LOG_SQL = true;
		QueryBuilder.LOG_VALUES = true;
		
		return mInstance;
	}



你可能感兴趣的:(Android)