简介
GreenDao是一个简化数据库开发的ORM(Object Ralativational Mapping)来(对象关系映射)源框架,就是通过操作对象间接操作数据库方式。
GreenDao的使用说明
-
gradle 配置中参数说明
- schemaVersion:指定数据库schema版本号,迁移等操作会用到。
- targetGenDir:生成数据库文件的目录。
- daoPackage:dao的包名,包名默认是entity所在的包。
-
实体类中注解说明
@Entity:表明这个实体类会在数据库中生成一个与之相对应的表,其中
nameInDb:可以自定义表名,表明该实体对应数据库中的那张表,默认为实体类名。
indexes:定义索引,这里可跨越多个列。
createInDb:如果是有多个实体都关联这个表,可以把多余的实体里面设置为false避免重复创建(默认是true)。
schema:一个项目中有多个schema时,表明要让这个dao属于哪个schema。
active:是否应该生成更新/删除/刷新方法。如果Entity定义了 @ToOne 或 @ToMany关系,那么独立于该值是有效的。意为是否支持实体类之间update,refresh,delete等操作。@Id:对应数据表中的主键,是一条数据的唯一标识。如果实体没有声明主键,默认创建Long类型主键"_id"自增。使用Long类型主键时可以通过@Id(autoincrement = true)设置为自增。
@Property(nameInDb = "USER_NAME" ):可以自定义字段名,注意外键不能使用该属性。表明这个属性对应数据表中的 USER_NAME 字段。
@NotNull:该属性值不能为空。
@Transient:该属性不会被存入数据库中。
@Unique:表明该属性在数据库中只能有唯一值。
@Index:创建一个索引。通过name设置索引别名,也可以通过unique给索引添加约束。
@Convert:指定一个PropertyConverter用于支持自定义类型(没用过)。
@ToOne:定义自己与一个实体对象的关系。
@ToMany:定义自己与多个实体对象的关系(可不与@ToOne联合使用)。@ToMany的属性referencedJoinProperty,类似于外键约束。
@JoinProperty:对于更复杂的关系,可以使用这个注解标明目标属性的源属性,起关联作用。
@JoinEntity:如果你在做多对多的关系,有其他的表或实体参与,可以给目标属性添加这个额外的注解。
@OrderBy:指定{@ToMany}关系的相关集合的排序,(propertyA, propertyB)默认为按主键ASC排序。
@Generated:这个是build后greendao自动生成的,这个注解理解为防止重复,每一块代码生成后会加个hash作为标记。
表关联(一对一,一对多)举例
现有两张表:作者表和文章表,文章表中有一列authorId对应作者表的id。希望可以通过文章的id查询作者信息。
一对一:
@Entity(nameInDb = "CsYdd",createInDb = true,indexes = {@Index(value = "id",unique = true)}) //value值必须是主键
public class Article {
@Id(autoincrement = true)
public long id; //id是主键
@SerializedName("is_FORCEEEE") //json解析换名
@Property(nameInDb = "is_FORCEEEE2") //db中换名
public boolean isFirst;
private long authId; //该authId就是Author的主键id,注意类型一定要相同,这里都为long类型
@ToOne(joinProperty = "authId")
private Author bean;
}
一对多:
@Entity(nameInDb = "CsYEE",createInDb = true,indexes = {@Index(value = "id",unique = true)})
public class Author {
@Id
public long id;
@ToMany(referencedJoinProperty = "authId") //authId是Article中与Author关联的参数名
private List<Article> list;
}
代码调用:
ArticleDao dao = daoSession.getArticleDao();
Article load = dao.load(id);
Author bean = load.getBean();
AuthorDao dao = daoSession.getArticleDao();
Author load = dao.load(id);
List<Article> list = load.getList();
多对多:(例如:老师与学生关系)
@Entity public class Teacher {
@Id(autoincrement = true) private Long id;
private String name;
// 对多,@JoinEntity注解:entity 中间表;sourceProperty 实体属性;targetProperty 外链实体属性
@ToMany
@JoinEntity(entity = JoinStudentToTeacher.class, sourceProperty = "tid", targetProperty = "sid")
private List<Student> students;
}
@Entity public class Student {
@Id private Long id;
private String name;
//对多,@JoinEntity注解:entity 中间表;sourceProperty 实体属性;targetProperty 外链实体属性
@ToMany
@JoinEntity(entity = JoinStudentToTeacher.class, sourceProperty = "sid", targetProperty = "tid")
private List<Teacher> teachers;
}
//借助的中间关系网
@Entity public class JoinStudentToTeacher {
@Id(autoincrement = true) private Long id;
//和teacher关联的id
private Long tid;
//和student关联的id
private Long sid;
}
greenDao方式测试
private void cs_toMany_toMany() {
TeacherDao TeacherDao = MyApplication.getDaoSession().getTeacherDao();
StudentDao studentDao = MyApplication.getDaoSession().getStudentDao();
JoinStudentToTeacherDao spDao = MyApplication.getDaoSession().getJoinStudentToTeacherDao();
Teacher p1 = new Teacher();
p1.setId(1L);
p1.setName("teacherOne");
Teacher p2 = new Teacher();
p2.setId(2L);
p2.setName("teacherTwo");
Teacher p3 = new Teacher();
p3.setId(3L);
p3.setName("teacherThree");
Student stu1 = new Student();
stu1.setId(1L);
stu1.setName("stu1");
Student stu2 = new Student();
stu2.setId(2L);
stu2.setName("stu2");
Student stu3 = new Student();
stu3.setId(3L);
stu3.setName("stu3");
// 模拟 多对多关系,建立关系网
//p1有stu1 stu2 stu3 ,那么反过来stu123都有p1
JoinStudentToTeacher sp1 = new JoinStudentToTeacher();
sp1.setTid(1L);
sp1.setSid(1l);
JoinStudentToTeacher sp2 = new JoinStudentToTeacher();
sp2.setTid(1L);
sp2.setSid(2L);
JoinStudentToTeacher sp3 = new JoinStudentToTeacher();
sp3.setTid(1L);
sp3.setSid(3L);
//p2有stu1 stu2 stu3 ,那么反过来stu123都有p2
JoinStudentToTeacher sp4 = new JoinStudentToTeacher();
sp4.setTid(2L);
sp4.setSid(1L);
JoinStudentToTeacher sp5 = new JoinStudentToTeacher();
sp5.setTid(2L);
sp5.setSid(2L);
JoinStudentToTeacher sp6 = new JoinStudentToTeacher();
sp6.setTid(2L);
sp6.setSid(3L);
TeacherDao.insertOrReplace(p1); //也可用insertOrReplaceInTx()方式直接插入List
TeacherDao.insertOrReplace(p2);
TeacherDao.insertOrReplace(p3);
studentDao.insertOrReplace(stu1);
studentDao.insertOrReplace(stu2);
studentDao.insertOrReplace(stu3);
spDao.insertOrReplace(sp1);
spDao.insertOrReplace(sp2);
spDao.insertOrReplace(sp3);
spDao.insertOrReplace(sp4);
spDao.insertOrReplace(sp5);
spDao.insertOrReplace(sp6);
List<Teacher> Teachers = TeacherDao.queryBuilder().build().list();
for (Teacher Teacher : Teachers) {
Log.d("GreenDao","Teacher:"+Teacher.toString());
}
}
容易忽略的:
1.如果是list或者数组类型的属性XX,只有getXX方法,没有setXX方法,但可用GreenDao的insertOrReplaceInTx()方法设置。
2.如果你要打印 @ToOne对应的bean类时,要先调用getXXX方法,而不是直接打印对象(因为没有赋值,而是在getXX的时候才赋值的)
3.以上三种方法,删除时候都不会关联删除。
GreenDao的其他方法
1 inser() 插入一条数据
2 insertInTx() 批量插入数据,比如List
3 insertOrReplace() 插入数据,传入的对象主键如果存在于数据库中,有则更新,否则插入
4 insertOrReplaceInTx() 批量插入数据,同上
5 save()比insertOrReplace()更有效率,但其会先判断对象是否有Key值,有则采用update方式更新(若在数据库中无任何数据,则此数据不会保存到数据库),无则采用insert方式插入。
6 insertWithoutSettingPk() 不需要设置主键属性,因为仅对主键为Long类型自增有效,String类型主键会Error。
7 deleteAll() 删除相应表的全部数据(清空相应表)。
8 deleteByKey() 删除给定PK的实体。
9 deleteByKeyInTx(K... keys) 使用事务删除数据库中给定键的所有实体。批量删除。
10 delete(T entity) entity的id属性必须有值,否则报错。就是删除与entity主键值相同的数据,其他数据值与表中数据值不一致时,会删除。
11 deleteInTx(T... entities) 使用事务删除数据库中给定的实体。批量删除。每个entity的id属性必须有值,否则报错。
12 update(T entity) entity的id属性必须有值,否则报错。若entity的id值表中不存在时,不会报错。
13 updateInTx(T... entities) 使用事务更新数据库中给定的实体。批量更新。
14 load(K key) 加载给定PK的实体。传入:key or null。返回:该实体 或 null。
15 loadByRowId(long rowId) 加载给定RowId的实体。传入:key or null。返回:该实体 或 null。
16 loadAll() 从数据库加载所有可用的实体。
17 unique() 返回非空的结果,否则抛出异常。
18 uniqueOrThrow() 返回非空的结果,否则抛出异常。
19 queryBuilder()用法示例如下:
mCustomerBeanDao.queryBuilder()
.limit(3) //取前3条数据
.offset(2) //偏移量(必须与limit()配合使用)
.distinct() //去重
.orderDesc(CustomerBeanDao.Properties.Adcode) //降序---descending
.orderAsc(CustomerBeanDao.Properties.District) //升序---ascending
.orderCustom(CustomerBeanDao.Properties.Adcode,"") //偏好排序---custom---定制
.orderRaw("") //使用原始sql排序
.preferLocalizedStringOrder() //使用本地化排序
.stringOrderCollation("COLLATE NOCASE") //orderAsc和orderAsc自定义排序
.where(CustomerBeanDao.Properties.CustName.like("dfsfs%")) //查询条件,参数间关系为 and
.whereOr(CustomerBeanDao.Properties.AreaName.like("dsfsf%") //查询条件,参数间关系为 or
,CustomerBeanDao.Properties.Address.eq("")) //n个查询条件
.build()
.list();
查询条件有:
between(Object value1, Object value2): BETWEEN … AND …
eq(Object value): equal (‘=’)
notEq(Object value): not equal (‘<>’)
gt(Object value): than (‘>’) ---- greater than
lt(Object value): less than (‘<’) ---less than
ge(Object value): greater or equal (‘>=’)
le(Object value): less or equal (‘<=’)
like(String value): LIKE
isNotNull(): IS NOT NULL
isNull(): IS NULL
in(Object… inValues): IN (…, …, …)
notIn(Object… notInValues): NOT IN (…, …, …)
in(Collection< ?> inValues): IN (…, …, …)
notIn(Collection< ?> notInValues): NOT IN (…, …, …)
queryBuilder.or()用法,借用一个网上的例子说明:
获取1970年10月或之后出生的名为“Tom”的用户信息(对应的sql:where name = "Tom" and ((year = 1970 and month >=10) or (year >1970)))
QueryBuilder<User> qb = userDao.queryBuilder();
//要特别注意qb.or(,qb.and())中嵌套的层级结构
qb.where(Properties.FirstName.eq("Tom"),qb.or(Properties.YearOfBirth.gt(1970),qb.and(Properties.YearOfBirth.eq(1970),Properties.MonthOfBirth.ge(10))));
List<User> youngJoes = qb.list();
20 or\and(WhereCondition... condMore) 在where或whereOr中使用,condMore参数间关系为or\and
21 list() 执行查询并将返回一个包含所有entity的list为结果,直接加载在内存中(存在缓存问题)。
22 listLazy() 当真正用到数据时(访问entity的属性时),才会查询数据库。
23 listLazyUncached() 也实现了懒加载技术,但是返回结果list没有保存在内存中,没法复用,每次都会向数据库查询真正的数据。
24 listIterator() 执行查询并将结果作为列表迭代器返回;确保关闭它以关闭底层游标。一旦迭代器完全迭代,游标就会关闭。
25 queryRawCreate() 基于给定的原始SQL创建一个可重复的{查询}对象,可以在这里传递任何WHERE子句和参数。
26 queryRaw() 一个原始样式查询,可以在这里传递任何WHERE子句和参数。
27 queryRawCreateListArgs() 基于给定的原始SQL创建一个可重复的{查询}对象,您可以在这里传递任何WHERE子句和参数。
28 多线程查询:
29 rx():返回在IO线程发射事件的 Observables
30 rxPlain():返回没有设置无线程调度的 Observables
31 detach() 将一个实体从会话中拆分。后续查询结果不会返回此对象。
32 detachAll() 所有的 同上
33 refresh() 通过从数据库重新加载重置该实体的所有本地更改属性。
34 daoSession.clear() 清除daoSession的缓存
35 dao.detachAll() 清除指定dao类的缓存
36 QueryBuilder.LOG_SQL = true; //打印SQL标志
37 QueryBuilder.LOG_VALUES = true; //打印参数标志
执行原始SQL查询举例:(目的:根据一个表的数据查另一个表中数据)
方法一,被拼接在where之后添加查询条件:
Query<User> query = userDao.queryBuilder().where(new StringCondition("_ID in " +"(SELECT USER_ID FROM USER_MESSAGE WHERE READ_FLAG = 0)")).build();
方法二,被拼接在查询语句的SELECT和实体之后:
Query<User> query = userDao.queryRawCreate(", Table2 G WHERE G.NAME=? AND T.GROUP_ID=G._ID", "admin"); //注意:实体User默认别名为 T,Table2前边的","是表间的分隔符
方法三,Database方式:
Cursor cursor = daoSession().getDatabase().rawQuery(sql,selectionArgs);
数据库升级
Android数据库升级涉及到的问题:
1. 若不升级,是不能增加新表,也不能修改或增加旧表字段的,除非卸载APP重装(即删除数据库并重建,但丢数据),否则会crash;
2. 升级版本号,触发onUpgrade()方法,可手动进行新旧表的增、删、改等操作;
3. 实现数据库升级方案:a.采用增加新表,同时修改或增减旧表字段;b.旧表改名为临时表,新建表,导入数据,如上方式;
public final class MigrationHelper {
public static void migrate(SQLiteDatabase sqliteDatabase, Class<? extends AbstractDao<?, ?>>... daoClasses) {
StandardDatabase db = new StandardDatabase(sqliteDatabase);
generateNewTablesIfNotExists(db, daoClasses);
generateTempTables(db, daoClasses);
dropAllTables(db, true, daoClasses);
createAllTables(db, false, daoClasses);
restoreData(db, daoClasses);
}
public static void migrate(StandardDatabase db, Class<? extends AbstractDao<?, ?>>... daoClasses) {
generateNewTablesIfNotExists(db, daoClasses);
generateTempTables(db, daoClasses);
dropAllTables(db, true, daoClasses);
createAllTables(db, false, daoClasses);
restoreData(db, daoClasses);
}
private static void generateNewTablesIfNotExists(StandardDatabase db, Class<? extends AbstractDao<?, ?>>... daoClasses) {
reflectMethod(db, "createTable", true, daoClasses);
}
private static void generateTempTables(StandardDatabase 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");
StringBuilder insertTableStringBuilder = new StringBuilder();
insertTableStringBuilder.append("CREATE TEMP TABLE ").append(tempTableName);
insertTableStringBuilder.append(" AS SELECT * FROM ").append(tableName).append(";");
db.execSQL(insertTableStringBuilder.toString());
}
}
private static void dropAllTables(StandardDatabase db, boolean ifExists, @NonNull Class<? extends AbstractDao<?, ?>>... daoClasses) {
reflectMethod(db, "dropTable", ifExists, daoClasses);
}
private static void createAllTables(StandardDatabase db, boolean ifNotExists, @NonNull Class<? extends AbstractDao<?, ?>>... daoClasses) {
reflectMethod(db, "createTable", ifNotExists, daoClasses);
}
/**
* dao class already define the sql exec method, so just invoke it
*/
private static void reflectMethod(StandardDatabase 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(StandardDatabase 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");
// get all columns from tempTable, take careful to use the columns list
List<String> columns = getColumns(db, tempTableName);
ArrayList<String> properties = new ArrayList<>(columns.size());
for (int j = 0; j < daoConfig.properties.length; j++) {
String columnName = daoConfig.properties[j].columnName;
if (columns.contains(columnName)) {
properties.add(columnName);
}
}
if (properties.size() > 0) {
final String columnSQL = TextUtils.join(",", properties);
StringBuilder insertTableStringBuilder = new StringBuilder();
insertTableStringBuilder.append("INSERT INTO ").append(tableName).append(" (");
insertTableStringBuilder.append(columnSQL);
insertTableStringBuilder.append(") SELECT ");
insertTableStringBuilder.append(columnSQL);
insertTableStringBuilder.append(" FROM ").append(tempTableName).append(";");
db.execSQL(insertTableStringBuilder.toString());
}
StringBuilder dropTableStringBuilder = new StringBuilder();
dropTableStringBuilder.append("DROP TABLE ").append(tempTableName);
db.execSQL(dropTableStringBuilder.toString());
}
}
private static List<String> getColumns(StandardDatabase 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;
}
}
然后在初始化时用到的 UpgradeHelper 类的onUpgrade方法中添加:
public class UpgradeHelper extends DaoMaster.OpenHelper {
public UpgradeHelper(Context context, String name, SQLiteDatabase.CursorFactory factory) {
super(context, name, factory);
}
@Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
MigrationHelper.migrate(db, PortraitPhotoDao.class, FeatureDao.class, PhotoJoinUserDao.class,
UserPhotoDao.class);
}
}
升级方案参考于:https://blog.csdn.net/zhanlv/article/details/82425709
SQL中常用命令
对表结构操作命令:
修改表名:
rename table 旧表名 to 新表名修改表选项:
alter table 表名 表选项 [=] 新值新增字段:
alter table 表名 add [column] 新字段名 列类型[列属性] [位置first/after 字段名](默认加到表的最后面)字段位置:字段想要存放的位置,first:在某某之间(最前面),第一个字段;after 字段名:放在某个具体的字段后(默认的)删除字段
alter table 表名 drop 字段名修改字段名
alter table 表名 change 旧字段名 新字段名 字段类型[列属性] [位置属性] (修改名字需要修改字段类型)修改字段类型(属性):
alter table 表名 modify 字段名 新类型 [新属性] [新位置]删除表结构
drop table 表名[,表名2....]; 可同时删除多个表小结:对表结构操作时,都需要用特定字段如alter,并且需要特指table及名称,但下边对数据操作,则不需要特指table。
对表中数据操作命令:
- 增:insert into 表名(字段列表,当插入所有字段时可省略) values(对应字段列表)
- 删:delete from 表名 [where条件] 如果没有where条件,意味着系统会自动删除该表所有数据(慎用)
- 改:update 表名 set 字段名 = 新值 [where条件];
- 查:select 字段列表 from 表名 from 表名 where 字段名=值 and 字段名=值 order by name DESC, age ASC //字段列表使用‘ , ’隔开
需要注意:
写的顺序:select ... from... where.... group by... having... order by..
执行顺序:from... where...group by... having.... select ... order by...
- group by数据分组举例:
方法一:
SELECT * FROM (select a.* from new_table a inner join new_table b on a.name = b.name group by name order by time desc) c GROUP BY c.kefuid
方法二:
SELECT * FROM (select * from new_table group by id order by time desc) c GROUP BY c.kefuid
- having条件语句:
having与where有类似作用,当WHERE 关键字无法与合计函数一起使用时使用:
例如:我们希望查找订单总金额少于 2000 的客户:
SELECT Customer,SUM(OrderPrice) FROM Orders GROUP BY Customer HAVING SUM(OrderPrice)<2000
然而,现在我们希望查找客户 "Bush" 或 "Adams" 拥有超过 1500 的订单总金额,则需要这样:
SELECT Customer,SUM(OrderPrice) FROM Orders WHERE Customer='Bush' OR Customer='Adams'
GROUP BY Customer HAVING SUM(OrderPrice)>1500
- SUM () 函数返回数值列的总数:
SELECT COUNT(column_name) FROM table_name //函数返回指定列的值的数目值(NULL 不计入)
SELECT COUNT(*) FROM table_name //函数返回表中的记录数目值
SELECT COUNT(DISTINCT column_name) FROM table_name //函数返回指定列的不同值的数目值
- AVG()取平局值:如我们希望找到 OrderPrice 值高于 OrderPrice 平均值的客户
SELECT Customer FROM Orders WHERE OrderPrice>(SELECT AVG(OrderPrice) FROM Orders)
- inner join
nner join(等值联接) 从两表中抽取出联结字段相等的行数据,整合到一起后返回。
left join(左联接) 将左表中的所有数据和右表中联结字段相等的数据,整合到一起后返回。
right join(右联接) 将右表中的所有记录和左表中联结字段相等的数据,整合到一起后返回。
inner join 连接两个数据表的用法:
SELECT * FROM 表1 INNER JOIN 表2 ON 表1.字段号=表2.字段号
inner join 连接三个数据表的用法:
SELECT * FROM (表1 INNER JOIN 表2 ON 表1.字段号=表2.字段号) INNER JOIN 表3 ON 表1.字段号=表3.字段号
总结:
只要两个表的公共字段有匹配值,就将这两个表中的记录组合起来,类似数学中的取并集,并集(合并相同部分)。
推荐几个网址:
查看 inner join on 用法示例
sql语法大全
sql举例大全
Mysql教程(Windows)
网上相关资源很多,在此记录只为方便查看。