前言
在上一章我們已經(jīng)學(xué)習(xí)了GreenDAO的配置以及簡單的增刪查改( GreenDAO 3.2.2 簡單入門 (一)),本章的內(nèi)容是有關(guān)表之間的進階操作八匠,是對上一章內(nèi)容的擴展。
準(zhǔn)備
因為涉及到數(shù)據(jù)庫結(jié)構(gòu)發(fā)生改變魁兼,需要對數(shù)據(jù)庫版本進行升級:
// 在HMROpenHelper類中重寫onUpgrade()方法
@Override
public void onUpgrade(Database db, int oldVersion, int newVersion) {
DaoMaster.dropAllTables(db,true);
onCreate(db);
}
記得每次發(fā)生上述變化時监署,要在模塊下的build.gradle
文件中對數(shù)據(jù)庫版本號進行加一:
greendao{
schemaVersion 5 // 數(shù)據(jù)庫版本號
daoPackage 'com.jk.greendao.gen'
targetGenDir 'src/main/java'
}
一對一關(guān)聯(lián)
假如有兩個表杭煎,A表和B表,A表的一條記錄只對應(yīng)于B表的一條記錄背率,反之亦然话瞧,就是一對一關(guān)聯(lián)。比如學(xué)生表和身份證表寝姿,一條學(xué)生記錄只對應(yīng)于一條身份證記錄交排,一條身份證記錄只對應(yīng)一條學(xué)生記錄,學(xué)生表和身份證表就是一對一關(guān)聯(lián)饵筑。
-
@ToOne
:表示實體類A和實體類B是一對一關(guān)聯(lián)-
joinProperty
:該參數(shù)表示兩個實體類的連接屬性
-
單向關(guān)聯(lián)
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; // 新增的根资,外鍵
//設(shè)置一對一關(guān)聯(lián)都许,連接屬性是cardId
@ToOne(joinProperty ="cardId") // 注意該參數(shù)的值
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;
}
更改數(shù)據(jù)庫版本號,sync now
后嫂冻,make project
胶征。在MainActivity中添加以下方法,然后調(diào)用即可:
// 單向關(guān)聯(lián)插入
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);
}
// 單向關(guān)聯(lián)查詢
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", "一對一添加記錄桨仿,查詢后的結(jié)果是:\n" + "姓名:" + user.getName()
+ "\n身份證號" + card.getCardCode() + "\n"
+ "Card表的id主鍵值為:" + card.getId()+ "\n"
+ "User表的外鍵cardId的值為:" + user.getCardId());
}
}
雙向關(guān)聯(lián)
Card
類更改如下睛低,然后更改數(shù)據(jù)庫版本號和make project
,User
類保持不變:
@Entity
public class Card {
@Id(autoincrement = true)
private Long id;
@Unique
@NotNull
private String cardCode;
private Long userId; // 新增的服傍,外鍵
//設(shè)置一對一關(guān)聯(lián)
@ToOne(joinProperty = "userId") // 注意該參數(shù)的值
private User user; // 新增的
}
雙向關(guān)聯(lián)的插入稍微不同钱雷,但查詢的思想時一樣的。在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);
//補上之前沒有設(shè)置的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());
}
}
來個小結(jié)吹零,先來看單向關(guān)聯(lián)罩抗,可以通過一個User
對象得到一個Card
對象與之對應(yīng),反之不能灿椅。但是套蒂,雙向關(guān)聯(lián)可以做到,這就是它們之間最主要的區(qū)別茫蛹。
一對多關(guān)聯(lián)
兩個表操刀,A表和B表,A表的一條記錄可對應(yīng)B表的多條記錄婴洼,而B表的一條記錄只能對應(yīng)A表的一條記錄骨坑,則B表對A表就是多對一關(guān)聯(lián),而A表對B表就是一對多關(guān)聯(lián)柬采。舉個例子欢唾,班級表的一個班級可對應(yīng)于學(xué)生表的多個學(xué)生且警,而一個學(xué)生只能屬于一個班級,學(xué)生表對班級表就是多對一關(guān)聯(lián)礁遣,班級表對學(xué)生表就是一對多關(guān)聯(lián)振湾。因此,一對多和多對一是相互關(guān)系亡脸。
-
@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") // 注意該參數(shù)的值
private User user;
}
User
類的更改如下:
@Entity(
nameInDb = "USERS",
indexes = {
@Index(value = "name DESC")
}
)
public class User {
@Id(autoincrement = true)
private Long id;
private Long cardId;
//設(shè)置一對一關(guān)聯(lián),連接屬性是cardId
@ToOne(joinProperty ="cardId") //注意參數(shù)的值
private Card card;
//設(shè)置一對多關(guān)聯(lián)垂谢,連接屬性是Orders類的外鍵userId
@ToMany(referencedJoinProperty = "userId") // 注意參數(shù)的值
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;
}
更改數(shù)據(jù)庫版本號厦画,然后make project
,接著在MainActivity
中添加如下代碼滥朱,然后直接調(diào)用即可:
private void insertOneToMany(){
List<Orders> orderList=new ArrayList<Orders>();
// 這些數(shù)據(jù)的來源請參考上一章所講的內(nèi)容根暑,因為在上一章中有方法為測試提供數(shù)據(jù)源
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); //設(shè)置外鍵值時,要用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() + "的訂單內(nèi)容為:");
int i = 0;
if (ordersList != null) {
for (Orders order : ordersList) {
i = i + 1;
Log.d("TAG", "第" + i + "條訂單的結(jié)果:" + ",id:" + order.getId()
+ ",商品名:" + order.getGoodsName()
+ ",用戶名:" + user1.getName());
}
}
}
多對多關(guān)聯(lián)
兩個表排嫌,A表和B表,A表的一條記錄可對應(yīng)于B表的多條記錄缰犁,而B表的一條記錄也對應(yīng)A表的多條記錄淳地,則A表和B表之間就是多對多關(guān)聯(lián)。比如作者表和書表帅容,作者表的一個作者可以寫多本書颇象,而一本書也可以是多個作者,作者表和書表之間就是多對多關(guān)聯(lián)并徘。
-
@ToMany
:設(shè)置多對多 -
@JoinEntity
:設(shè)置連接中間類-
entity
:中間類遣钳,需要創(chuàng)建 -
sourceProperty
:源屬性,就是本類麦乞,而參數(shù)的值就是中間類中代表該類的屬性 -
targetProperty
:目標(biāo)屬性蕴茴,就是要關(guān)聯(lián)的類,而參數(shù)的值就是中間類中代表該類的屬性
-
示例的場景是路幸,一個老師可以教多門課程荐开,一個課程可以也可以由多個老師任教付翁。
新建中間類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,
// 源屬性百侧,中間類的外鍵砰识,對應(yīng)Teacher類的主鍵
sourceProperty = "tId",
// 目標(biāo)屬性能扒,中間類的外鍵,對應(yīng)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,
// 源屬性辫狼,中間類的外鍵初斑,對應(yīng)Course類的主鍵
sourceProperty = "cId",
// 目標(biāo)屬性,中間類的外鍵膨处,對應(yīng)Teacher類的主鍵
targetProperty = "tId"
)
private List<Teacher> teachers;
}
更改數(shù)據(jù)庫版本號见秤,然后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("數(shù)學(xué)");
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());
// 悟空叫數(shù)學(xué)
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的記錄鹃答。
多表查詢
根據(jù)需求,查詢兩個或兩個以上的表的記錄突硝,要求這些表有關(guān)聯(lián)测摔,也就是本章節(jié)前面所講的內(nèi)容。它的思想和MySQL里的內(nèi)連接解恰,左/右連接一樣锋八。
示例代碼需要用到User、Card和Orders三個類护盈,在前面已經(jīng)創(chuàng)建好了挟纱。測試所用到的數(shù)據(jù)可以直接調(diào)用前面定義好的方法。
先看兩表查詢腐宋,代碼如下:
// 兩表查詢樊销,購買紫金冠的用戶有
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
表的痒谴,傳遞的值應(yīng)該是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
表連接起來呢?那是因為它們之間并沒有關(guān)聯(lián)槐雾,或者說能使它們連接起來的屬性夭委。
join()
方法的重載方式很多,在這就不一一列出來了募强,讀者可以直接查看源碼株灸,需要根據(jù)場景選擇。對于更多的表之間的連接擎值,以此類推蚂且,多寫幾個join
就可以了。
總結(jié)
至此幅恋,多表關(guān)聯(lián)和多表查詢就講完了杏死。
最終源碼
GreenDAO 3.2.2 簡單入門(一)增刪改查
GreenDAO 3.2.2 簡單入門(三)數(shù)據(jù)庫升級
內(nèi)容補充
補充的內(nèi)容是關(guān)于MySQL的多表連接的,對于GreenDAO的多表關(guān)聯(lián)的理解有一定的幫助。下圖是所用到三張表的結(jié)構(gòu):
其場景是記錄用戶訪問了哪些路徑下的資源捆交,類似打卡淑翼,而
res_user
表是中間表。其語法如下所示:
select column(需指明那個表的列) from table1(使用左連接還是右連接是相對這個表而言的) left/right outer join table2(需連接的表) on condition [可以繼續(xù)接where品追、group by等語句對table1而不是table2表進一步約束玄括,是查詢結(jié)果更準(zhǔn)確]
現(xiàn)在有一個需求:查詢用戶是13077581222
和日期是2019-06-16
的資源路徑,也就是res_path
表的path
肉瓦。
// 左連接遭京,改成右連接,內(nèi)連接也可以查詢成功
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')