前言
在上一章我们已经学习了GreenDAO的配置以及简单的增删查改( GreenDAO 3.2.2 简单入门 (一)),本章的内容是有关表之间的进阶操作,是对上一章内容的扩展。
准备
因为涉及到数据库结构发生改变,需要对数据库版本进行升级:
// 在HMROpenHelper类中重写onUpgrade()方法
@Override
public void onUpgrade(Database db, int oldVersion, int newVersion) {
DaoMaster.dropAllTables(db,true);
onCreate(db);
}
记得每次发生上述变化时,要在模块下的build.gradle
文件中对数据库版本号进行加一:
greendao{
schemaVersion 5 // 数据库版本号
daoPackage 'com.jk.greendao.gen'
targetGenDir 'src/main/java'
}
一对一关联
假如有两个表,A表和B表,A表的一条记录只对应于B表的一条记录,反之亦然,就是一对一关联。比如学生表和身份证表,一条学生记录只对应于一条身份证记录,一条身份证记录只对应一条学生记录,学生表和身份证表就是一对一关联。
-
@ToOne
:表示实体类A和实体类B是一对一关联-
joinProperty
:该参数表示两个实体类的连接属性
-
单向关联
A表中持有B表的引用,而B表没有持有A表的引用。新建Card
实体类:
@Entity
public class Card {
@Id(autoincrement = true)
private Long id;
@Unique
@NotNull
private String cardCode;
}
User
类更改如下:
@Entity(
nameInDb = "USERS",
indexes = {
@Index(value = "name DESC")
}
)
public class User {
@Id(autoincrement = true)
private Long id;
private Long cardId; // 新增的,外键
//设置一对一关联,连接属性是cardId
@ToOne(joinProperty ="cardId") // 注意该参数的值
private Card card; // 新增的
@Index(name="usercode_index",unique = true)
private String usercode;
@Property(nameInDb = "userName")
@NotNull
private String name;
private String userAddress;
@Transient
private int tempUserSign;
}
更改数据库版本号,sync now
后,make project
。在MainActivity中添加以下方法,然后调用即可:
// 单向关联插入
private void insertOneToOne(){
// 先生成一条Card记录
Card card1=new Card();
card1.setCardCode("434377777");
cardDao.insert(card1);
User user1=new User();
user1.setName("孙悟空");
user1.setUserAddress("花果山水帘洞");
user1.setUsercode("001");
user1.setCard(card1);
userDao.insert(user1);
}
// 单向关联查询
private void queryOneToOne(){
User user=userDao.queryBuilder().where(UserDao.Properties.Name.eq("孙悟空")).build().unique();
Card card=user.getCard();
if(user!=null && card!=null){
Log.d("TAG", "一对一添加记录,查询后的结果是:\n" + "姓名:" + user.getName()
+ "\n身份证号" + card.getCardCode() + "\n"
+ "Card表的id主键值为:" + card.getId()+ "\n"
+ "User表的外键cardId的值为:" + user.getCardId());
}
}
双向关联
Card
类更改如下,然后更改数据库版本号和make project
,User
类保持不变:
@Entity
public class Card {
@Id(autoincrement = true)
private Long id;
@Unique
@NotNull
private String cardCode;
private Long userId; // 新增的,外键
//设置一对一关联
@ToOne(joinProperty = "userId") // 注意该参数的值
private User user; // 新增的
}
双向关联的插入稍微不同,但查询的思想时一样的。在MainActivity
中添加如下方法:
private void insertCardOneTOne(){
User user1=new User();
user1.setName("孙悟空");
user1.setUserAddress("花果山水帘洞");
user1.setUsercode("001");
Card card1=new Card();
card1.setCardCode("434377777");
/* 注意以下代码的顺序 */
userDao.insert(user1);
card1.setUser(user1);
cardDao.insert(card1);
//补上之前没有设置的user1的外键值
user1.setCard(card1);
//更新user1对象
userDao.update(user1);
}
private void queryCardOneToOne(){
Card card1=cardDao.queryBuilder().where(CardDao.Properties.CardCode.eq("434377777")).build().unique();
User user1=card1.getUser();
if(user1!=null && card1!=null){
L.d("TAG", "姓名:"+user1.getName()+"\n"
+"Card表的id主键值:"+card1.getId()+"\n"
+"User表的外键cardId的值为:"+user1.getCardId());
}
}
来个小结,先来看单向关联,可以通过一个User
对象得到一个Card
对象与之对应,反之不能。但是,双向关联可以做到,这就是它们之间最主要的区别。
一对多关联
两个表,A表和B表,A表的一条记录可对应B表的多条记录,而B表的一条记录只能对应A表的一条记录,则B表对A表就是多对一关联,而A表对B表就是一对多关联。举个例子,班级表的一个班级可对应于学生表的多个学生,而一个学生只能属于一个班级,学生表对班级表就是多对一关联,班级表对学生表就是一对多关联。因此,一对多和多对一是相互关系。
-
@ToOne
:代表“一”joinProperty
-
@ToMany
:代表“多”referencedJoinProperty
示例的场景是,一个用户可以购买多个商品。新建Orders
类,代码如下:
@Entity
public class Orders {
@Id(autoincrement = true)
private Long id;
private String goodsName;
private Long userId;//外键,对于User类的主键
@ToOne(joinProperty = "userId") // 注意该参数的值
private User user;
}
User
类的更改如下:
@Entity(
nameInDb = "USERS",
indexes = {
@Index(value = "name DESC")
}
)
public class User {
@Id(autoincrement = true)
private Long id;
private Long cardId;
//设置一对一关联,连接属性是cardId
@ToOne(joinProperty ="cardId") //注意参数的值
private Card card;
//设置一对多关联,连接属性是Orders类的外键userId
@ToMany(referencedJoinProperty = "userId") // 注意参数的值
private List<Orders> orders;
@Index(name="usercode_index",unique = true)
private String usercode;
@Property(nameInDb = "userName")
@NotNull
private String name;
private String userAddress;
@Transient
private int tempUserSign;
}
更改数据库版本号,然后make project
,接着在MainActivity
中添加如下代码,然后直接调用即可:
private void insertOneToMany(){
List<Orders> orderList=new ArrayList<Orders>();
// 这些数据的来源请参考上一章所讲的内容,因为在上一章中有方法为测试提供数据源
User user1=userDao.queryBuilder().where(UserDao.Properties.Name.eq("孙悟空")).build().unique();
User user2=userDao.queryBuilder().where(UserDao.Properties.Name.eq("猪八戒")).build().unique();
Orders order1=new Orders();
order1.setGoodsName("金箍棒");
order1.setUser(user1); //设置外键值时,要用setUser()方法,以确保外键值不会出错
Orders order2=new Orders();
order2.setGoodsName("黄金甲");
order2.setUser(user1);
Orders order3=new Orders();
order3.setGoodsName("紫金冠");
order3.setUser(user1);
Orders order4=new Orders();
order4.setGoodsName("紫金冠");
order4.setUser(user2);
orderList.add(order1);
orderList.add(order2);
orderList.add(order3);
orderList.add(order4);
ordersDao.insertInTx(orderList);
}
private void queryToManyUserToOrder() {
List<Orders> ordersList;
User user1 = userDao.queryBuilder().where(UserDao.Properties.Name.eq("猪八戒")).build().unique();
//直接通过User对象的getOrders()方法获得此用户的所有订单
ordersList = user1.getOrders();
Log.d("TAG", user1.getName() + "的订单内容为:");
int i = 0;
if (ordersList != null) {
for (Orders order : ordersList) {
i = i + 1;
Log.d("TAG", "第" + i + "条订单的结果:" + ",id:" + order.getId()
+ ",商品名:" + order.getGoodsName()
+ ",用户名:" + user1.getName());
}
}
}
多对多关联
两个表,A表和B表,A表的一条记录可对应于B表的多条记录,而B表的一条记录也对应A表的多条记录,则A表和B表之间就是多对多关联。比如作者表和书表,作者表的一个作者可以写多本书,而一本书也可以是多个作者,作者表和书表之间就是多对多关联。
-
@ToMany
:设置多对多 -
@JoinEntity
:设置连接中间类-
entity
:中间类,需要创建 -
sourceProperty
:源属性,就是本类,而参数的值就是中间类中代表该类的属性 -
targetProperty
:目标属性,就是要关联的类,而参数的值就是中间类中代表该类的属性
-
示例的场景是,一个老师可以教多门课程,一个课程可以也可以由多个老师任教。
新建中间类JoinTeacherWithCourse
,代码如下:
@Entity
public class JoinTeacherWithCourse {
@Id(autoincrement = true)
private Long id;
// 代表老师的id
private Long tId;
// 代表课程的id
private Long cId;
}
新建Teacher
类和Course
类,代码如下:
@Entity
public class Teacher {
@Id(autoincrement = true)
private Long id;
private String name;
// 多对多
@ToMany
// 连接两个多对多实体类的中间类
@JoinEntity(
// 中间类类名
entity = JoinTeacherWithCourse.class,
// 源属性,中间类的外键,对应Teacher类的主键
sourceProperty = "tId",
// 目标属性,中间类的外键,对应Course类的主键
targetProperty = "cId"
)
private List<Course> courses;
}
@Entity
public class Course {
@Id(autoincrement = true)
private Long id;
private String name;
//多对多
@ToMany
// 连接两个多对多实体类的中间类
@JoinEntity(
// 中间类类名
entity = JoinTeacherWithCourse.class,
// 源属性,中间类的外键,对应Course类的主键
sourceProperty = "cId",
// 目标属性,中间类的外键,对应Teacher类的主键
targetProperty = "tId"
)
private List<Teacher> teachers;
}
更改数据库版本号,然后make project
,接着在MainActivity
中添加如下代码:
// 多对多插入
private void insertManyToMany() {
List<Course> courses = new ArrayList<>();
Course course1 = new Course();
course1.setName("英语");
Course course2 = new Course();
course2.setName("语文");
Course course3 = new Course();
course3.setName("数学");
courses.add(course1);
courses.add(course2);
courses.add(course3);
courseDao.insertInTx(courses);
List<Teacher> teacherList = new ArrayList<>();
Teacher teacher1 = new Teacher();
teacher1.setName("孙悟空");
Teacher teacher2 = new Teacher();
teacher2.setName("猪八戒");
Teacher teacher3 = new Teacher();
teacher3.setName("沙和尚");
teacherList.add(teacher1);
teacherList.add(teacher2);
teacherList.add(teacher3);
teacherDao.insertInTx(teacherList);
List<JoinTeacherWithCourse> teacherWithCourses = new ArrayList<>();
// 悟空教英语
JoinTeacherWithCourse teacherWithCourse1 = new JoinTeacherWithCourse();
teacherWithCourse1.setTId(teacher1.getId());
teacherWithCourse1.setCId(course1.getId());
// 悟空叫语文
JoinTeacherWithCourse teacherWithCourse2 = new JoinTeacherWithCourse();
teacherWithCourse2.setTId(teacher1.getId());
teacherWithCourse2.setCId(course2.getId());
// 悟空叫数学
JoinTeacherWithCourse teacherWithCourse3 = new JoinTeacherWithCourse();
teacherWithCourse3.setTId(teacher1.getId());
teacherWithCourse3.setCId(course3.getId());
// 沙和尚教语文
JoinTeacherWithCourse teacherWithCourse4 = new JoinTeacherWithCourse();
teacherWithCourse4.setTId(teacher2.getId());
teacherWithCourse4.setCId(course2.getId());
teacherWithCourses.add(teacherWithCourse1);
teacherWithCourses.add(teacherWithCourse2);
teacherWithCourses.add(teacherWithCourse3);
teacherWithCourses.add(teacherWithCourse4);
teacherWithCourseDao.insertInTx(teacherWithCourses);
}
// 多对多查询,通过”教师“找到课程
private void queryManyToManyT() {
Teacher teacher = teacherDao.queryBuilder().where(TeacherDao.Properties.Name.eq("孙悟空"))
.build().unique();
List<Course> courses = teacher.getCourses();
if (courses != null) {
Log.d("TAG", "孙悟空所教的课程:");
for (Course course : courses) {
Log.d("TAG", "课程名:" + course.getName());
}
}
}
// 多对多查询,通过”课程“找到课程
private void queryManyToManyC() {
Course course = courseDao.queryBuilder().where(CourseDao.Properties.Name.eq("语文"))
.build().unique();
List<Teacher> teachers = course.getTeachers();
if (teachers != null) {
Log.d("TAG", "教语文的老师有:");
for (Teacher teacher : teachers) {
Log.d("TAG", "教师名:" + teacher.getName());
}
}
}
注意:要先插入Teacher和Course的记录,然后才能插入JoinTeacherWithCourse的记录。
多表查询
根据需求,查询两个或两个以上的表的记录,要求这些表有关联,也就是本章节前面所讲的内容。它的思想和MySQL里的内连接,左/右连接一样。
示例代码需要用到User、Card和Orders三个类,在前面已经创建好了。测试所用到的数据可以直接调用前面定义好的方法。
先看两表查询,代码如下:
// 两表查询,购买紫金冠的用户有
private void multiQueryTwoTb() {
QueryBuilder<User> qb = userDao.queryBuilder();
qb.join(Orders.class, OrdersDao.Properties.UserId)
.where(OrdersDao.Properties.GoodsName.eq("紫金冠"));
List<User> users = qb.list();
if (users != null) {
for (User u : users) {
Log.d("TAG", "购买紫金冠的用户有:" + u.getName());
}
}
}
// 两表查询,用户是孙悟空所购买的商品有
private void multiQueryTwoOrders() {
QueryBuilder<Orders> qb = ordersDao.queryBuilder();
qb.join(OrdersDao.Properties.UserId, User.class)
.where(UserDao.Properties.Name.eq("孙悟空"));
List<Orders> ordersList = qb.list();
if (ordersList != null) {
for (Orders o : ordersList) {
Log.d("TAG", o.getUser().getName() + "购买的商品有:" + o.getGoodsName());
}
}
}
multiQueryTwoTb()方法中join()的源码:
/**
* Expands the query to another entity type by using a JOIN. The primary key property of the primary entity for
* this QueryBuilder is used to match the given destinationProperty.
*/
public <J> Join<T, J> join(Class<J> destinationEntityClass, Property destinationProperty) {
return join(dao.getPkProperty(), destinationEntityClass, destinationProperty);
}
在这里T
表示User
类,J
表示Orders
类,dao
是UserDao
的对象,翻译过来就是使用USER
表(User
类)的主键属性去匹配Orders
类(destinationEntityClass
)的属性destinationProperty
,那为什么不能像multiQueryTwoOrders()
方法中join()
那样写呢?第一,USER表中没有外键,ORDERS
表有外键;第二,是USER表去连接ORDERS
表的,传递的值应该是Orders
类中的属性。
multiQueryTwoOrders()
方法中join()
的源码:
/**
* Expands the query to another entity type by using a JOIN. The given sourceProperty is used to match the primary
* key property of the given destinationEntity.
*/
public <J> Join<T, J> join(Property sourceProperty, Class<J> destinationEntityClass) {
AbstractDao<J, ?> destinationDao = (AbstractDao<J, ?>) dao.getSession().getDao(destinationEntityClass);
Property destinationProperty = destinationDao.getPkProperty();
return addJoin(tablePrefix, sourceProperty, destinationDao, destinationProperty);
}
在这里T
表示Orders
类,J
表示User
类,翻译过来就是使用ORDERS
表的外键属性sourceProperty
去匹配USER
表的主键,也就是destinationEntityClass
。
三表查询的代码如下:
// 查询买了紫金冠的客户的身份证号
private void queryThreeTb() {
QueryBuilder<Card> qb = cardDao.queryBuilder()
.where(CardDao.Properties.CardCode.like("1987%"));
Join user = qb.join(CardDao.Properties.UserId, User.class);
Join order = qb.join(user, UserDao.Properties.Id, Orders.class, OrdersDao.Properties.UserId);
order.where(OrdersDao.Properties.GoodsName.eq("紫金冠"));
List<Card> cardList = qb.list();
if (cardList != null) {
Log.d("TAG", "买了紫金冠的身份证前四位是1987的用户:");
for (Card card : cardList) {
Log.d("TAG", "身份证:" + card.getCardCode() + "名字:" + card.getUser().getName());
}
}
}
先通过CARD
表连接USER
表,再通过USER
表连接ORDERS
表,这样三表就连接起来了。那为什么CARD
表不能直接和ORDERS
表连接起来呢?那是因为它们之间并没有关联,或者说能使它们连接起来的属性。
join()
方法的重载方式很多,在这就不一一列出来了,读者可以直接查看源码,需要根据场景选择。对于更多的表之间的连接,以此类推,多写几个join
就可以了。
总结
至此,多表关联和多表查询就讲完了。
最终源码
GreenDAO 3.2.2 简单入门(一)增删改查
GreenDAO 3.2.2 简单入门(三)数据库升级
内容补充
补充的内容是关于MySQL的多表连接的,对于GreenDAO的多表关联的理解有一定的帮助。下图是所用到三张表的结构:
user表.png res_path表.png res_user表.png
其场景是记录用户访问了哪些路径下的资源,类似打卡,而
res_user
表是中间表。其语法如下所示:
select column(需指明那个表的列) from table1(使用左连接还是右连接是相对这个表而言的) left/right outer join table2(需连接的表) on condition [可以继续接where、group by等语句对table1而不是table2表进一步约束,是查询结果更准确]
现在有一个需求:查询用户是13077581222
和日期是2019-06-16
的资源路径,也就是res_path
表的path
。
// 左连接,改成右连接,内连接也可以查询成功
select `enword`.`res_path`.path from `enword`.`res_user` left outer join `enword`.`res_path` on `enword`.`res_user`.resId = `enword`.`res_path`.id where `enword`.`res_user`.date = '2019-06-16'and `enword`.`res_user`.userId = (select id from `enword`.`user` where name = '13077581227')
网友评论