GreenDAO 3.2.2 简单入门(二)多表查询和多表关联

前言

在上一章我们已经学习了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 projectUser类保持不变:

@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类,daoUserDao的对象,翻译过来就是使用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')
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,524评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,869评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,813评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,210评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,085评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,117评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,533评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,219评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,487评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,582评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,362评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,218评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,589评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,899评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,176评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,503评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,707评论 2 335

推荐阅读更多精彩内容