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的持久层框架,用起来还是蛮舒服的。日后的工程估计也会尝试使用吧。

原文地址:https://www.cnblogs.com/wushenjiang/p/13196954.html