zoukankan      html  css  js  c++  java
  • SpringDataJpa学习(3)——SpringDataJpa的多表映射和动态查询

    写在前面

    本文接SpringDataJpa学习(2)——SpringDataJpa的单表使用,上次我们学习了单表的使用,这次我们来学习下多表的配置和使用

    一对多的配置

    这里我们先定义一个新的实体类:

    @Entity
    @Table(name = "cst_linkman")
    public class LinkMan {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        @Column(name = "lkm_id")
        private Long lkmId;
        @Column(name = "lkm_name")
        private String lkmName;
        @Column(name = "lkm_gender")
        private String lkmGender;
        @Column(name = "lkm_phone")
        private String lkmPhone;
        @Column(name = "lkm_mobile")
        private String lkmMobile;
        @Column(name = "lkm_email")
        private String lkmEmail;
        @Column(name = "lkm_position")
        private String lkmPosition;
        @Column(name = "lkm_memo")
        private String lkmMemo;
    }
    

    之后我们在一的那方配置一下:

        /**
         *  1.声明关系 @OneToMany 配置一对多关系
         *  2.配置外键 @JoinColumn 配置外键 name:外键字段名称 referencedColumnName:参照的主表的主键字段名称
         *  在客户实体类上(一的那方)添加了外键配置,所以对于客户而言,也具备了维护外键的作用
         *
         * mappedBy:对方配置关系的属性名称,放弃外键维护权
         * cascade:配置级联(可以配置到设置多表的映射关系的注解上)
         *  CascadeType.ALL:所有 CascadeType.MERGE:更新 CascadeType.PERSIST 保存,CascadeType.REMOVE 删除
         */
    //    @OneToMany(targetEntity = LinkMan.class)
    //    @JoinColumn(name = "lkm_cust_id",referencedColumnName = "cust_id")
        /**
         * fetch:配置关联对象的加载方式
         *      EAGER: 立即加载
         *      LAZY:  延迟加载
         */
        @OneToMany(mappedBy = "customer",cascade = CascadeType.ALL,fetch = FetchType.EAGER)
        private Set<LinkMan> linkMen = new HashSet<>();
    

    在多的那方配置一下:

        /**
         * 配置联系人到客户的多对一关系
         *  使用注解的形式配置多对一关系
         *      1.配置表关系  @ManyToOne 配置多对一关系
         *      2.配置外键(中间表)
         *          @JoinColumn(外键名称)
         */
        @ManyToOne(targetEntity = Customer.class,fetch = FetchType.LAZY)
        @JoinColumn(name = "lkm_cust_id",referencedColumnName = "cust_id")
        private Customer customer;
    

    到此配置即告一段落。
    下面写一个测试类来测试一下:

        @Test
        @Transactional
        @Rollback(false)
        public void testAdd1() {
            // 创建一个客户
            Customer customer = new Customer();
            customer.setCustName("淫荡");
            // 创建一个联系人
            LinkMan linkMan = new LinkMan();
            linkMan.setLkmName("小白");
            /**
             * 配置联系人到客户的关系(多对一)
             *      只发送了两条insert语句
             * 由于配置了联系人到客户的映射关系(多对一)
             */
            linkMan.setCustomer(customer);
            customerDao.save(customer);
            linkManDao.save(linkMan);
        }
    

    可以看到数据库保存成功了。这样就说明我们的配置没有问题了。

    Specifications动态查询

    有的时候我们的条件是不固定的,这时候我们就需要动态的构建对应的sql语句。SpringDataJpa是提供了该功能的,我们来试一试:

       /**
         * 根据条件查询单个对象
         */
        @Test
        public void testSpec() {
            // 匿名内部类
            /**
             * 自定义查询条件
             *      1.实现Specification接口(提供泛型:查询的对象类型)
             *      2.实现toPredicate方法(构造查询条件)
             *      3.需要借助方法参数中的两个参数 (root:获取需要查询的对象属性,CriteriaBuilder:构造查询条件)
             *  根据客户名称查询
             *          查询条件:1.查询方式 :cb对象
             *                  2.比较的属性名称:root对象
             */
            Specification<Customer> spec = new Specification<Customer>() {
                @Override
                public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder cb) {
                    // 1.获取比较的属性
                    Path<Object> custName = root.get("custName");
                    // 2.构造查询条件 equal:进行精准匹配 (比较的属性,比较的属性的取值)  select * from cst_customer where cust_name = ?
                    // 第一个参数:需要比较的属性 (path对象) 第二个参数:当前需要比较的取值
                    Predicate pre = cb.equal(custName, "我去");
                    return pre;
                }
            };
            Optional<Customer> customerOptional = customerDao.findOne(spec);
            Customer customer = customerOptional.get();
            System.out.println(customer);
        }
    

    可以看到使用的方法也是比较简单的,直接定义一个内部类实现里面的方法即可。
    关于两个条件的And或者or,如下:

     /**
         * 多条件查询
         *      案例:根据客户名和客户所属行业查询
         */
        @Test
        public void testSpec1(){
            /**
             * root:获取属性 :客户名称/所属行业
             * cb:构造查询
             *      1. 构造客户名的精准匹配查询
             *      2. 构造所属行业的精准匹配查询
             *      3. 将以上两个查询联系起来
             */
            Specification<Customer> specification = new Specification<Customer>() {
                @Override
                public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder cb) {
                    // 客户名称
                    Path<Object> custName = root.get("custName");
                    // 所属行业
                    Path<Object> custIndustry = root.get("custIndustry");
                    // 构造客户名的精准匹配
                    Predicate predicate1 = cb.equal(custName, "我去");
                    // 构造所属行业的精准匹配
                    Predicate predicate2 = cb.equal(custIndustry, "IT黑狗");
                    // 多个条件组合(与(and) 或(or))
                    Predicate predicate = cb.and(predicate1, predicate2);
                    return predicate;
                }
            };
            Optional<Customer> customerOptional = customerDao.findOne(specification);
            Customer customer = customerOptional.get();
            System.out.println(customer);
        }
    

    而模糊查询的like略有不同:

        /**
         * 根据客户名称的模糊匹配
         *
         * equal:直接使用path对象即可
         * gt,lt,ge,le,like 根据path对象,指定比较的参数类型,再去进行比较
         *      指定参数类型,path.as(类型的字节码对象)
         */
        @Test
        public void testSpec2(){
            // 构造查询条件
            Specification<Customer> spec = new Specification<Customer>() {
                @Override
                public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder cb) {
                    // 查询的属性
                    Path<Object> custName = root.get("custName");
                    // 查询方式:模糊匹配
                    Predicate like = cb.like(custName.as(String.class), "我%");
                    return like;
                }
            };
            List<Customer> all = customerDao.findAll(spec);
            for (Customer customer : all) {
                System.out.println(customer);
            }
        }
    

    需要提前指定类型。
    同样的,我们也可以实现分页查询:

    
        /**
         * 分页查询
         *      findAll() (Specification,Pageable)
         *              分页参数:查询的页码,每页查询的条数
         *      返回:Page(springDataJpa为我们封装好的PageBean对象)
          */
        @Test
        public void testSpec4(){
            Specification specification = null;
            // 第一个参数:当前查询的页数
            // 第二个参数:每页查询的数量
            Pageable pageable = PageRequest.of(0,2);
            // 分页查询
            Page<Customer> page = customerDao.findAll(specification, pageable);
            // 得到数据列表
            List<Customer> content = page.getContent();
            for (Customer o : content) {
                System.out.println(o);
            }
            // 得到总条数
            System.out.println(page.getTotalElements());
            // 得到总页数
            System.out.println(page.getTotalPages());
    
        }
    

    多对多的配置

    与一对多的配置类似,我们先新建两个实体类:

    @Entity
    @Table(name = "sys_role")
    public class Role {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        @Column(name = "role_id")
        private Long roleId;
        @Column(name = "role_name")
        private String roleName;
        @ManyToMany(mappedBy = "roles")
          private Set<User> users = new HashSet<>();
    }
    
    @Entity
    @Table(name = "sys_user")
    public class User {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        @Column(name = "user_id")
        private Long userId;
        @Column(name = "user_name")
        private String username;
        @Column(name = "age")
        private Integer age;
    
        /**
         * 配置用户到角色的多对多关系
         * 配置多对多的映射关系
         * 1.声明表关系的配置
         * 2.配置中间表(包含两个外键)
         *
         * @ManyToMany 多对多
         *  JoinTable name:中间表的名称
         */
        @ManyToMany(targetEntity = Role.class,cascade = CascadeType.ALL)
        @JoinTable(name = "sys_user_role",
                // joinColumns当前表在中间表的外键
                joinColumns = @JoinColumn(name = "sys_user_id", referencedColumnName = "user_id"),
                //inverseJoinColumns对方对象在中间表的外键
                inverseJoinColumns = @JoinColumn(name = "sys_role_id", referencedColumnName = "role_id")
        )
        private Set<Role> roles = new HashSet<>();
    }
    

    并且生成对应的get和set方法
    之后编写一个测试类测试一下:

      /**
         * 保存一个用户,保存一个角色
         */
        @Test
        @Transactional
        @Rollback(false)
        public void testAdd(){
            User user = new User();
            user.setUsername("小丽");
            Role role = new Role();
            role.setRoleName("爱吃饭");
            // 配置用户到角色的关系
            user.getRoles().add(role);
            role.getUsers().add(user);
            userDao.save(user);
        }
    

    对象导航功能

    在多对多种,SpringDataJpa本身还具有对象导航的特性,即可以直接查询到对应的对象,如我们使用之前使用的一对多里的实体类编写测试类:

        /**
         * 测试对象导航查询(查询一个对象的时候,通过此对象查询所有的关联对象)
         */
        @Test
        @Transactional
        public void testQuery1() {
            // 查询id为1的客户
            Customer customer = customerDao.getOne(1L);
            // 对象导航查询此客户下的所有联系人
            Set<LinkMan> linkMen = customer.getLinkMen();
            for (LinkMan linkMAN : linkMen) {
                System.out.println(linkMAN);
            }
        }
    

    这里也有延迟加载和立即加载的区别:

        /**
         * 对象导航查询;默认使用的是延迟加载的形式查询的
         * 调用get方法并不会立即发送查询,而是在使用关联对象时才会查询
         * 不想用:修改配置,将延迟加载改为立即加载 fetch:配置到多表映射关系的注解上
         */
        @Test
        @Transactional
        public void testQuery2() {
            // 查询id为1的客户
            Optional<Customer> customer = customerDao.findById(1L);
            // 对象导航查询此客户下的所有联系人
            Set<LinkMan> linkMen = customer.get().getLinkMen();
    
        }
    
        /**
         * 从联系人查询他的所属客户
         *      * 默认使用立即加载
         *      
         */
        @Test
        @Transactional
        public void testQuery3() {
            Optional<LinkMan> linkManOptional = linkManDao.findById(2L);
            LinkMan linkMan = linkManOptional.get();
            // 对象导航查询
            Customer customer = linkMan.getCustomer();
            System.out.println(customer);
        }
    

    这里我们可以看到,由于linkMan是多的那一方,查询到的一般就一个,速度不会有太大影响,所以默认是立即加载。而customer是一的那一方,查询的结果有可能会很多,这时默认使用延迟加载就会更加利于性能。

    总结

    到这里SpringDataJpa的学习就差不多结束了。体会到了一种不写sql的持久层框架,用起来还是蛮舒服的。日后的工程估计也会尝试使用吧。

  • 相关阅读:
    Cookies 和 Session的区别
    List接口、Set接口和Map接口
    Java NIO:IO与NIO的区别
    NIO与传统IO的区别
    Java中堆内存和栈内存详解
    Java序列化与反序列化
    maven搭建
    深入研究java.lang.ThreadLocal类
    SQL 优化经验总结34条
    数据库事务的四大特性以及事务的隔离级别
  • 原文地址:https://www.cnblogs.com/wushenjiang/p/13196954.html
Copyright © 2011-2022 走看看