简要说明
YoDao是一个简单又高效的Android ORM开源框架。
项目主页:https://github.com/sandin/YoDao
项目作者:sandin
1.实现目标
项目受Hibernate等ORM框架启发,并实现了Java Persistence API标准,在大部分系统设计上学习了Spring Data JPA框架,所以在使用和其API上十分相似。
本项目和OrmLite等传统ORM框架不同的是,为了提高性能所以没有使用运行时的反射来解析注解,而使用dagger和butterknife一样的技术在编译时解析注解并自动生成代码,因此在使用ORM来提高开发效率的同时也没有牺牲运行时的性能和速度。
因此框架的目标是实现一个快速而轻量的ORM框架,即可以提高开发效率,但又不牺牲运行时的性能。
2.框架结构
整个框架和butterknife一样,分为三个子项目:
- 主项目,这是一个Android library项目,在使用的时候引用即可。
- 编译项目,这是一个编译工具,在IDE或gradle里配置即可,无需打包到APK里。
- 实例项目,DEMO演示。
3.加入项目
此项目为开源项目,因此欢迎任何有着开源精神的开发者参与进来,有意者可以联系作者,或直接在github上fork该项目。
使用说明
基本使用是和dagger一样,这里仅描述android studio的使用方式:
1. 修改gradle配置
在 build.gradle
文件加入一下配置:
apply plugin: 'com.neenbedankt.android-apt'
// ...
buildscript {
dependencies {
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
}
repositories {
jcenter()
}
dependencies {
compile 'com.lds:yodao:0.2.1'
apt 'com.lds:yodao-compiler:0.2.1'
}
注意:目前仅上传到jcenter,还没有同步到Maven Center。
2. JavaBean增加注解
import com.yoda.yodao.annotation.Column;
import com.yoda.yodao.annotation.GeneratedValue;
import com.yoda.yodao.annotation.GenerationType;
import com.yoda.yodao.annotation.Id;
@Entity
@Table(name="user")
public class User implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
@Column
private String name;
@Column(name = "age")
private int age;
public User() {
}
// Getter & Setter
// ...
}
这里使用的注解和JPA一模一样,只是因为android里面没有 javax.persistence
包,所以所有的注解都放置在 com.yoda.yodao.annotation
包下,但注解类和使用方法是一样的,可以说完全兼容JPA标准的实体类。
这里注意Getter&Setter方法的命令必须完全遵循POJO的命名规范,否则会编译不过。并建议使用驼峰命名。
3. DAO的定义
这里设计和Spring Data JPA框架一样,只要定义个 interface
,并继承 YoDao
即完成了Dao的编码工作,框架会根据这个接口的定义自动生成其实现类的代码。
例如:
public interface UserDao extends YoDao {
User findOneByName(String name);
List findListByNameAndAge(String name, int age);
List findListOrderByAge();
List findListOrderByAgeDesc();
long countByAge(int age);
}
YoDao
的两个泛型:
- T,映射的实体类
- ID,主键PK的类
4. 在数据库里建表
框架已经将所有实体对应的table的建表SQL都生成了,只需要在建表的时候调用:
public class MySQLiteOpenHelper
extends android.database.sqlite.SQLiteOpenHelper {
@Override
public void onCreate(SQLiteDatabase db) {
DaoFactory.create(HairDao.class, null).onCreateTable(db);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
DaoFactory.create(HairDao.class, null).onUpgradeTable(db, oldVersion, newVersion);
onCreate(db);
}
}
在修改实体类以后(如增加字段),只需要重新build一下,重建表的时候数据结构就会变化。
5. 使用你的Dao
UserDao userDao = DaoFactory.create(UserDao.class, mSQLiteOpenHelper);
// insert
User user = new User();
user.setName("Jack");
user.setAge(23);
userDao.save(user);
// update
user.setName("New Name");
userDao.update(user);
// query
List users = userDao.findAll();
user = userDao.findOne(user.getId());
// delete
userDao.delete(user);
注解说明
@Entity
使用在需要映射的实体类上,框架只会处理有此注解的实体类。
@Table(name='table_ame')
使用在需要映射的实体类上,属性name为表名,不提供name的时候将自动使用类名作为表名。(表名的命名规范是全部小写,下划线分割:如class名为 UserInfo
表名自动是 user_info
)
@Id
使用在需要映射成表主键的成员变量上,主键支持 int
long
String
类型。
@GeneratedValue
使用在需要映射成主键的成员变量上,
属性strategy
为主键的生成策略:
-
AUTO
主键自增 -
UUID
主键使用UUID随机生成
@Column(name="column_ame")
使用在需要映射的成员变量上
属性name
为表列名,不提供name的时候将默认使用变量名作为列名(一样会转成全部小写,下划线分割的命名规范)
属性unique
对于SQL里面的UNIQUE
属性nullable
对应SQL里面的NOT NULL
属性length
对于SQL里面的长度限制
其他属性请参考JPA。
@NotColumn
使用在不需要映射的成员变量上,因为框架会将需要实体类的所有成员变量都进行映射,使用了该注解告诉框架这个字段不需要在表中持久化,则在建表时忽略该字段。
@Repository
使用在自己编写的DAO上面,表示该DAO是需要框架来生成实现类的代码。
@Query
使用在DAO的方法上面,表示这个方法不需要自动生成,自定义SQL查询。
属性value
为自定义查询的SQL语句。
目前仅支持原生SQL,今后将支持在HSQL语法。
其他注解
今后将陆续开始支持 OneToOne
OneToMany
ManyToOne
ManyToMany
等JPA的注解。
YoDao增删改查接口
save()
/**
* Saves a given entity. Use the returned instance for further operations as
* the save operation might have changed the entity instance completely.
*
* @param entity
* @return the saved entity
*/
boolean save(T entity);
/**
* Saves all given entities.
*
* @param entities
* @return
*/
boolean save(List entities);
update()
/**
* update a row by primary key
*
* @param entity
* @return
*/
int update(T entity);
/**
* update by fields
*
* @param entity
* @param whereClause
* @param whereArgs
* @return
*/
int updateByFields(T entity, String whereClause,
String[] whereArgs);
/**
* update some fields
*
* @param values
* @param whereClause
* @param whereArgs
* @return
*/
int updateByFields(ContentValues values, String whereClause,
String[] whereArgs);
find()
/**
* Retrives an entity by its primary key.
*
* @param id
* @return the entity with the given primary key or {@code null} if none
* found
* @throws IllegalArgumentException
* if primaryKey is {@code null}
*/
T findOne(ID id);
/**
* Retrives an entity by its fields
*
* @param selection
* @param selectionArgs
* @param groupBy
* @param having
* @param orderBy
* @return
*/
T findOneByFields(String selection, String[] selectionArgs, String groupBy,
String having, String orderBy);
/**
* Retrives an entity by its fields
*
* @param selection
* @param selectionArgs
* @param orderBy
* @return
*/
T findOneByFields(String selection, String[] selectionArgs, String orderBy);
/**
* Retrives an entity by sql
*
* @param sql
* @return
*/
T findOneBySql(String sql, String[] selectionArgs);
/**
* Returns all instances of the type.
*
* @return all entities
*/
List findAll();
/**
* Find List By Fields
*
* @param selection
* @param selectionArgs
* @param groupBy
* @param having
* @param orderBy
* @return
*/
List findListByFields(String selection, String[] selectionArgs,
String groupBy, String having, String orderBy);
/**
* Find List By Fields
*
* @param selection
* @param selectionArgs
* @param orderBy
* @return
*/
List findListByFields(String selection, String[] selectionArgs,
String orderBy);
/**
* Find List By SQL
*
* @param sql
* @return
*/
List findListBySql(String sql, String[] selectionArgs);
delete()
/**
* Deletes the entity with the given id.
*
* @param id
*/
int delete(ID id);
/**
* Deletes a given entity.
*
* @param entity
*/
int delete(T entity);
/**
* Deletes the given entities.
*
* @param entities
*/
int delete(List entities);
/**
* Deletes all entities managed by the repository.
*/
int deleteAll();
/**
* Deletes entities by fields
*
* @param selection
* @param selectionArgs
* @return
*/
int deleteByFields(String selection, String[] selectionArgs);
exists()
/**
* Returns whether an entity with the given id exists.
*
* @param id
* @return true if an entity with the given id exists, alse otherwise
* @throws IllegalArgumentException
* if primaryKey is {@code null}
*/
boolean exists(ID id);
count()
/**
* Returns the number of entities available.
*
* @return the number of entities
*/
long count();
/**
* Returns the number of entities with selections available.
*
* @param selections
* @param selectionArgs
* @return
*/
long countByFields(String selections, String[] selectionArgs);
onCreateTable()
这个方法在SQLOpenHelper中的onCreate()中调用,调用后才会执行建表语句。
onUpgradeTable()
这个方法在SQLOpenHelper中的onUpgrade()中调用,调用后才会执行删除表语句。
DaoFactory工厂类
自动生成的DAO
实现类
默认情况下,框架会根据你写的dao的包名和类名来生成实现类的包名和类名。
例如,你的Dao为:
com.your.test.dao.UserDao
那么生成的实现类名为:
com.your.test.dao.impl.UserDaoImpl
在android studio里,自动的生成的代码在项目根目录下的build/source/apt/debug
目录里 ,这个目录由插件决定,eclipse上可以自行配置。
实例化DAO
框架推荐不直接调用和实例化自动生成的Impl类,而应该使用 DaoFactory
来实例化Dao,例如:
UserDao userDao = DaoFactory.create(UserDao.class, mSQLiteOpenHelper);
今后将支持通过注解来定义生命周期以支持对于指定Dao可选性的单例模式。目前所有Dao都默认非单例。
DAO的写法
自定义Dao
一般情况下,自定义的 dao
只需要写一个空的 interface
然后继承 YoDao
接口即够用了,因为接口里实现了大量增删改查的API。
例如:
public interface UserDao extends YoDao {
}
注意:两个泛型不要搞反了,第一个是实体类,第二个是主键。今后框架将会校验,目前还没有。
方法的定义
对于接口无法满足的时候,则可以通过定义接口的方法来实现,这里必须按照指定的约定来写方法名,框架则会按照方法的定义来帮你实现。
如果你写的方法名不被支持,则框架在编译时会提示哪一个方法定义出现了问题,请查询相关文档来修改方法的定义。
以下列出所有支持的方法:
WHERE
User findOneByName(String name);
// SQL: select * from user where name = ? limit 1
// AND
List findListByNameAndAge(String name, int age);
// SQL: select * from user where name = ? and age = ?
// OR
List findListByNameOrAge(String name, int age);
// SQL: select * from user where name ? or age = ?
GROUP BY
List findListByNameGroupByAge(String name);
// SQL: select * from user where name = ? group by age;
// HAVINGS
// 暂不支持
ORDER BY
List findListOrderByAge();
// SQL: select * from user order by age
List findListOrderByAgeDesc();
// SQL: select * from user order by age DESC
UPDATE
int updateByName(User user, String name);
// SQL: update user ... where name = ?
DELETE
int deleteByName(String name);
// SQL: delete from user where name = ?
COUNT
long countByAge(int age);
原生SQL
@Query(value= "select * from user where name = ?")
User findByUsername(String username);
关键要点:
- 所有SQL的关键字都有意义,不能随便将其放置在方法名中,如
BY
Order
Where
等。 - 注意字段名的命名规范,这里会将字段名转成和字段名一样的命名规范,即全部小写,下划线分词,例如
findByUserName
那么则直接使用where user_name = ?
的SQL来查找,并且会使用setUserName()
和getUserName()
方法来设值和取值。Java的字段名和方法名都保持驼峰命名法,数据库的表名和列名都按全部小写,下划线分割的命名法。
方法的参数
方法的参数的个数和顺序必须和方法定义的查询保持一致。
方法的返回值
方法的返回值类型只能是:
-
T
find语句 -
List
find语句 -
boolean
exists语句 -
long
count语句 -
int
delete语句