美文网首页Android数据库
GreenDAO 3.2.2 简单入门(二)多表查询和多表关联

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

作者: 大荒里种菜 | 来源:发表于2019-05-20 12:44 被阅读0次

    前言

    在上一章我们已经学习了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')
    

    相关文章

      网友评论

        本文标题:GreenDAO 3.2.2 简单入门(二)多表查询和多表关联

        本文链接:https://www.haomeiwen.com/subject/sdvcaqtx.html