SSM

Spring的学习笔记(十七)——SpringDataJpa动态查询和复杂的多表操作

Nick · 5月6日 · 2020年 · 本文14387字 · 阅读36分钟827

Specifications动态查询

有时我们在查询某个实体的时候,给定的条件是不固定的,这时就需要动态构建相应的查询语句,在Spring Data JPA中可以通过JpaSpecificationExecutor接口查询。相比JPQL,其优势是类型安全,更加的面向对象。

JpaSpecificationExecutor 方法列表

T findOne(Specification<T> spec);  //查询单个对象

List<T> findAll(Specification<T> spec);  //查询列表

//查询全部,分页
//pageable:分页参数
//返回值:分页pageBean(page:是springdatajpa提供的)
Page<T> findAll(Specification<T> spec, Pageable pageable);

//查询列表
//Sort:排序参数
List<T> findAll(Specification<T> spec, Sort sort);

long count(Specification<T> spec);//统计查询
Specification :查询条件

自定义我们自己的Specification实现类

    实现
        //root:查询的根对象(查询的任何属性都可以从根对象中获取)
        //CriteriaQuery:顶层查询对象,自定义查询方式(了解:一般不用)
        //CriteriaBuilder:查询的构造器,封装了很多的查询条件
        Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb); //封装查询条件

分别用Specification实现:
1. 精确查询
2. 模糊查询
3. 多条件查询
4. 排序查询
5. 分页查询

代码如下

package cn.kt.test;/*
 *Created by tao on 2020-05-05.
 */

import cn.kt.dao.CustomerDao;
import cn.kt.domain.Customer;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import javax.persistence.criteria.*;
import java.util.List;

@RunWith(SpringJUnit4ClassRunner.class)  //声明单元测试
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class SpecTest {
    @Autowired
    private CustomerDao customerDao;

    /*
     * 根据条件查询单个对象
     * 自定义查询条件
        1.实现Specification接口(提供泛型:查询的对象类型)
        2.实现toPredicate方法(构造查询条件)
        3.需要借助方法参数中的两个参数(
            root:获取需要查询的对象属性
            CriteriaBuilder:构造查询条件的,内部封装了很多的查询条件(模糊匹配,精准匹配)

            查询条件:
            1. 查询方式:
                cb对象
            2. 比较属性的名称
                oot对象
        )
     * */
    @Test
    public void testSepc1() {
        Specification<Customer> sprec = new Specification<Customer>() {
            public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
                //1. 获取比较的属性
                Path<Object> custName = root.get("custName");
                //2. 构造查询的条件    第一个参数,path (属性),第二个参数,属性的取值
                Predicate equal = criteriaBuilder.equal(custName, "左眼会陪右眼哭");
                return equal;
            }
        };
        Customer customer = customerDao.findOne(sprec);
        System.out.println(customer);
    }

    /*
    * 多条件查询
    * root:获取属性
        客户名
        所属行业
      cb:构造查询
        1.构造客户名的精准匹配查询
        2.构造所属行业的精准匹配查询
        3.将以上两个查询联系起来
    * */
    @Test
    public void testSepc2() {
        Specification<Customer> sprec = new Specification<Customer>() {
            public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
                //1. 获取比较的属性
                Path<Object> custName = root.get("custName");//客户名
                Path<Object> custAddress = root.get("custAddress");//地址
                //2. 构造查询的条件  第一个参数,path (属性),第二个参数,属性的取值
                Predicate p1 = criteriaBuilder.equal(custName, "左眼会陪右眼哭");
                Predicate p2 = criteriaBuilder.equal(custAddress, "江西赣州");
                //3. 将多个条件组合到一起(满足条件一并且满足条件二,满足条件一或者满足条件二)
                Predicate and = criteriaBuilder.and(p1, p2);//以与的形式拼接多个条件
                //Predicate or = criteriaBuilder.or();//以或的形式拼接多个条件
                return and;
            }
        };
        Customer customer = customerDao.findOne(sprec);
        System.out.println(customer);
    }

    /*完成根据客户名称的模糊匹配,返回客户列表
        equal :直接的到path对象(属性),然后进行比较即可
        gt, lt,ge,le,like
        得到path对象, 根据path指定比较的参数类型,再去进行比较
        指定参数类型: path.as (类型的字节码对象)
     */
    @Test
    public void testSepc3() {
        Specification<Customer> sprec = new Specification<Customer>() {
            public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
                //1. 获取比较的属性
                Path<Object> custName = root.get("custName");//客户名
                //2. 构造查询的条件:模糊查询
                Predicate like = criteriaBuilder.like(custName.as(String.class), "%吉%");
                return like;
            }
        };
        List<Customer> all = customerDao.findAll(sprec);
        for (Customer customer : all) {
            System.out.println(customer);
        }

        //添加排序
        //创建排序对象,需要调用构造方法实例化sort对象
        //第一个参数:排序的顺序(倒序,正序)
        // Sort .Direction. DESC:倒序
        // Sort . Direction.ASC :升序
        //第二个参数:排序的属性名称
        System.out.println("****************排序实现的***************");
        Sort sort = new Sort(Sort.Direction.DESC,"custId");
        List<Customer> list = customerDao.findAll(sprec, sort);
        for (Customer customer : list) {
            System.out.println(customer);
        }
    }

    /*分页查询
    Specification:查询条件
    Pageable:分页参数
    分页参数:查询的页码,每页查询的条数
    findAll(Specification, Pageable):带有条件的分页
    findA1l(Pageable):没有条件的分页
    返回:Page (sptingDataJpa为我们封装好的pageBean对象,数据列表,共条数)
    */
    @Test
    public void testPage() {
        //构造查询条件
        Specification<Customer> spec = new Specification<Customer>() {
            public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
                return cb.like(root.get("custName").as(String.class), "%吉%");
            }
        };

        /**
         * 构造分页参数
         *      Pageable : 接口
         *          PageRequest实现了Pageable接口,调用构造方法的形式构造
         *              第一个参数:页码(从0开始)
         *              第二个参数:每页查询条数
         */
        Pageable pageable = new PageRequest(0, 5);

        /**
         * 分页查询,封装为Spring Data Jpa 内部的page bean
         *      此重载的findAll方法为分页方法需要两个参数
         *          第一个参数:查询条件Specification
         *          第二个参数:分页参数
         */
        Page<Customer> page = customerDao.findAll(spec,pageable);
        System.out.println(pageable);
        System.out.println(page.getContent());//得到数据集合列表
        System.out.println(page.getTotalElements());//得到总条数
        System.out.println(page.getTotalPages());//得到总页数
    }
}

多表之间的关系和操作

多表之间的关系和操作多表的操作步骤

  • 表关系
    一对一
    一对多:
    一的一方:主表
    多的一方:从表
    外键:需要再从表上新建一列作为外键,他的取值来源于主表的主键
    多对多:
    中间表:中间表中最少应该由两个字段组成,这两个字段做为外键指向两张表的主键,又组成了联合主键

  • 讲师对学员:一对多关系

  • 实体类中的关系
    包含关系:可以通过实体类中的包含关系描述表关系
    继承关系

  • 分析步骤
    1.明确表关系
    2.确定表关系(描述 外键|中间表)
    3.编写实体类,再实体类中描述表关系(包含关系)
    4.配置映射关系

完成多表操作

映射的注解说明

  • @OneToMany:
    作用:建立一对多的关系映射
    属性:
    targetEntityClass:指定多的多方的类的字节码
    mappedBy:指定从表实体类中引用主表对象的名称。
    cascade:指定要使用的级联操作
    fetch:指定是否采用延迟加载
    orphanRemoval:是否使用孤儿删除

  • @ManyToOne
    作用:建立多对一的关系
    属性:
    targetEntityClass:指定一的一方实体类字节码
    cascade:指定要使用的级联操作
    fetch:指定是否采用延迟加载
    optional:关联是否可选。如果设置为false,则必须始终存在非空关系。

  • @JoinColumn
    作用:用于定义主键字段和外键字段的对应关系。
    属性:
    name:指定外键字段的名称
    referencedColumnName:指定引用主表的主键字段名称
    unique:是否唯一。默认值不唯一
    nullable:是否允许为空。默认值允许。
    insertable:是否允许插入。默认值允许。
    updatable:是否允许更新。默认值允许。
    columnDefinition:列的定义信息。

i.一对多操作

    案例:客户和联系人的案例(一对多关系)
        客户:一家公司
        联系人:这家公司的员工

        一个客户可以具有多个联系人
        一个联系人从属于一家公司

    分析步骤
        1.明确表关系
            一对多关系
        2.确定表关系(描述 外键|中间表)
            主表:客户表
            从表:联系人表
                * 再从表上添加外键
        3.编写实体类,再实体类中描述表关系(包含关系)
            客户:再客户的实体类中包含一个联系人的集合
            联系人:在联系人的实体类中包含一个客户的对象
        4.配置映射关系
            * 使用jpa注解配置一对多映射关系

    级联:
        操作一个对象的同时操作他的关联对象

        级联操作:
            1.需要区分操作主体
            2.需要在操作主体的实体类上,添加级联属性(需要添加到多表映射关系的注解上)
            3.cascade(配置级联)
        级联添加,
            案例:当我保存一个客户的同时保存联系人
        级联删除
            案例:当我删除一个客户的同时删除此客户的所有联系人
保存操作
 @Test
    @Transactional  //开启事务
    @Rollback(false)//设置为不回滚
    public void testAdd() {
        Customer c = new Customer();
        c.setCustName("TBD云集中心");
        c.setCustLevel("VIP客户");
        c.setCustSource("网络");
        c.setCustIndustry("商业办公");
        c.setCustAddress("昌平区北七家镇");
        c.setCustPhone("010-84389340");

        LinkMan l = new LinkMan();
        l.setLkmName("TBD联系人");
        l.setLkmGender("male");
        l.setLkmMobile("13811111111");
        l.setLkmPhone("010-34785348");
        l.setLkmEmail("98354834@qq.com");
        l.setLkmPosition("老师");
        l.setLkmMemo("还行吧");
        c.getLinkmans().add(l);
        l.setCustomer(c);
        customerDao.save(c);
        linkManDao.save(l);
    }

通过保存的案例,我们可以发现在设置了双向关系之后,会发送两条insert语句,一条多余的update语句,那我们的解决是思路很简单,就是一的一方放弃维护权

/**
     *放弃外键维护权的配置将如下配置改为
     */
    //@OneToMany(targetEntity=LinkMan.class)
//@JoinColumn(name="lkm_cust_id",referencedColumnName="cust_id")    
//设置为
    @OneToMany(mappedBy="customer")
级联

首先要配置级联属性
在配置一对多关系是添加一下注解

@OneToMany(mappedBy = "customer",cascade = CascadeType.ALL)
    private Set<LinkMan> linkmans = new HashSet<LinkMan>(0);
级联添加,
案例:当我保存一个客户的同时保存联系人
/*
    * 级联添加:保存一个客户的同时,保存客户的所有联系人
        需要在操作主体的实体类上, 配置casacde属性
    */
    @Test
    @Transactional  //开启事务
    @Rollback(false)//设置为不回滚
    public void testCascadeAdd() {
        Customer c = new Customer();
        c.setCustName("TBD云集中心");
        c.setCustLevel("VIP客户");
        c.setCustSource("网络");
        c.setCustIndustry("商业办公");
        c.setCustAddress("昌平区北七家镇");
        c.setCustPhone("010-84389340");

        LinkMan l = new LinkMan();
        l.setLkmName("TBD联系人");
        l.setLkmGender("male");
        l.setLkmMobile("13811111111");
        l.setLkmPhone("010-34785348");
        l.setLkmEmail("98354834@qq.com");
        l.setLkmPosition("老师");
        l.setLkmMemo("还行吧");
        c.getLinkmans().add(l);
        l.setCustomer(c);
        customerDao.save(c);
    }
级联删除
案例:当我删除一个客户的同时删除此客户的所有联系人
/*
     * 级联删除:
     * 删除一号客户的同时,删除一号客户的所有联系人
     */
    @Test
    @Transactional  //开启事务
    @Rollback(false)//设置为不回滚
    public void testCascadeRemove() {
        //1. 查询1号客户
        Customer one = customerDao.findOne(2l);
        //2. 删除1号客户
        customerDao.delete(one);
    }

ii.多对多操作

    案例:用户和角色(多对多关系)
        用户:
        角色:

    分析步骤
        1.明确表关系
            多对多关系
        2.确定表关系(描述 外键|中间表)
            中间间表
        3.编写实体类,再实体类中描述表关系(包含关系)
            用户:包含角色的集合
            角色:包含用户的集合
        4.配置映射关系
多对多操作案例
  1. 多对多保存操作(放弃维护权)
  2. 级联添加操作
  3. 级联删除操作

映射的注解说明
* @ManyToMany
作用:用于映射多对多关系
属性:
cascade:配置级联操作。
fetch:配置是否采用延迟加载。
targetEntity:配置目标的实体类。映射多对多的时候不用写。

  • @JoinTable
    作用:针对中间表的配置
    属性:
    nam:配置中间表的名称
    joinColumns:中间表的外键字段关联当前实体类所对应表的主键字段
    inverseJoinColumn:中间表的外键字段关联对方表的主键字段

  • @JoinColumn
    作用:用于定义主键字段和外键字段的对应关系。
    属性:
    name:指定外键字段的名称
    referencedColumnName:指定引用主表的主键字段名称
    unique:是否唯一。默认值不唯一

代码如下
1.SysUser.java(用户的数据模型)

package cn.kt.domain;/*
 *Created by tao on 2020-05-06.
 */

import javax.persistence.*;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;

/**
 * 用户的数据模型
 */
@Entity
@Table(name = "sys_user")
public class SysUser implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "user_id")
    private Long userId;
    @Column(name = "user_code")
    private String userCode;
    @Column(name = "user_name")
    private String userName;
    @Column(name = "user_password")
    private String userPassword;
    @Column(name = "user_state")
    private String userState;

    /*
    * 配置用户到角色的多对多关系
        配置多对多的映射关系
        1.声明表关系的配置
            @ManyToMany(mappedBy = SysRole.class) //多对多
            targetEntity 对方实体类字节码

        2.配置中间表(包含两个外键)
            JoinTable
                name:中间表的名称
                joinColumns配置当前对象在中间表中的外键
                inverseJoinColumns配置对方对象在中间表中的外键
    * */
    @ManyToMany(targetEntity = SysRole.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<SysRole> roles = new HashSet<SysRole>(0);

    public Long getUserId() {
        return userId;
    }

    public void setUserId(Long userId) {
        this.userId = userId;
    }

    public String getUserCode() {
        return userCode;
    }

    public void setUserCode(String userCode) {
        this.userCode = userCode;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getUserPassword() {
        return userPassword;
    }

    public void setUserPassword(String userPassword) {
        this.userPassword = userPassword;
    }

    public String getUserState() {
        return userState;
    }

    public void setUserState(String userState) {
        this.userState = userState;
    }

    public Set<SysRole> getRoles() {
        return roles;
    }

    public void setRoles(Set<SysRole> roles) {
        this.roles = roles;
    }

    @Override
    public String toString() {
        return "SysUser [userId=" + userId + ", userCode=" + userCode + ", userName=" + userName + ", userPassword="
                + userPassword + ", userState=" + userState + "]";
    }
}
  1. SysRole.java(角色的数据模型)
package cn.kt.domain;/*
 *Created by tao on 2020-05-06.
 */

import javax.persistence.*;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;

/**
 * 角色的数据模型
 */
@Entity
@Table(name="sys_role")
public class SysRole implements Serializable {

    @Id
    @GeneratedValue(strategy= GenerationType.IDENTITY)
    @Column(name="role_id")
    private Long roleId;
    @Column(name="role_name")
    private String roleName;
    @Column(name="role_memo")
    private String roleMemo;

    //多对多关系映射
    @ManyToMany(mappedBy = "roles")
    private Set<SysUser> users = new HashSet<SysUser>(0);


    public Long getRoleId() {
        return roleId;
    }
    public void setRoleId(Long roleId) {
        this.roleId = roleId;
    }
    public String getRoleName() {
        return roleName;
    }
    public void setRoleName(String roleName) {
        this.roleName = roleName;
    }
    public String getRoleMemo() {
        return roleMemo;
    }
    public void setRoleMemo(String roleMemo) {
        this.roleMemo = roleMemo;
    }
    public Set<SysUser> getUsers() {
        return users;
    }
    public void setUsers(Set<SysUser> users) {
        this.users = users;
    }
    @Override
    public String toString() {
        return "SysRoleDao [roleId=" + roleId + ", roleName=" + roleName + ", roleMemo=" + roleMemo + "]";
    }
}
  1. ManyToManyTest.java(测试)
package cn.kt.test;/*
 *Created by tao on 2020-05-06.
 */

import cn.kt.dao.SysRoleDao;
import cn.kt.dao.SysUserDao;
import cn.kt.domain.SysRole;
import cn.kt.domain.SysUser;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;

@RunWith(SpringJUnit4ClassRunner.class)  //声明单元测试
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class ManyToManyTest {
    @Autowired
    private SysUserDao userDao;
    @Autowired
    private SysRoleDao roleDao;

    /**
     * 需求:
     * 保存用户和角色
     * 要求:
     * 创建2个用户和3个角色
     * 让1号用户具有1号和2号角色(双向的)
     * 让2号用户具有2号和3号角色(双向的)
     * 保存用户和角色
     * 问题:
     * 在保存时,会出现主键重复的错误,因为都是要往中间表中保存数据造成的。
     * 解决办法:
     * 让任意一方放弃维护关联关系的权利
     * 一般让被动的一方放弃维护权
     */
    @Test
    @Transactional  //开启事务
    @Rollback(false)//设置为不回滚
    public void testAdd() {
        //创建对象
        SysUser u1 = new SysUser();
        u1.setUserName("用户1");
        SysRole r1 = new SysRole();
        r1.setRoleName("角色1");
        //建立关联关系
        //配置用户到角色关系,可以对中间表中的数据进行维护 1- 1
        u1.getRoles().add(r1);
        //配置角色到用户关系,可以对中间表中的数据进行维护 1- 1
        r1.getUsers().add(u1);
        //保存
        roleDao.save(r1);
        userDao.save(u1);
    }

    /*测试级联添加:保存一个用户的同时,保存用户的关联角色
    *
    * */
    @Test
    @Transactional  //开启事务
    @Rollback(false)//设置为不回滚
    public void testCasCadeAdd() {
        //创建对象
        SysUser u1 = new SysUser();
        u1.setUserName("用户2");
        SysRole r1 = new SysRole();
        r1.setRoleName("角色3");
        //建立关联关系
        //配置用户到角色关系,可以对中间表中的数据进行维护 1- 1
        u1.getRoles().add(r1);
        //配置角色到用户关系,可以对中间表中的数据进行维护 1- 1
        r1.getUsers().add(u1);
        //保存
        userDao.save(u1);
    }

    /*级联删除:删除id为2的用户同时删除他的关联对象*/
    @Test
    @Transactional  //开启事务
    @Rollback(false)//设置为不回滚
    public void testCasCadeRemove() {
        //1. 查询2号用户
        SysUser user = userDao.findOne(2l);
        //2. 删除用户
        userDao.delete(user);
    }

}

iii.多表的查询

    1.对象导航查询
        查询一个对象的同时,通过此对象查询他的关联对象

        案例:客户和联系人

        从一方查询多方
            * 默认:使用延迟加载(****)

        从多方查询一方
            * 默认:使用立即加载
对象导航查询测试

ObjectQueryTest.java

package cn.kt.test;/*
 *Created by tao on 2020-05-06.
 */

import cn.kt.dao.CustomerDao;
import cn.kt.dao.LinkManDao;
import cn.kt.domain.Customer;
import cn.kt.domain.LinkMan;
import org.hibernate.event.spi.SaveOrUpdateEvent;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;

import java.util.Set;

@RunWith(SpringJUnit4ClassRunner.class)  //声明单元测试
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class ObjectQueryTest {
    @Autowired
    private CustomerDao customerDao;

    @Autowired
    private LinkManDao linkManDao;

    /*测试对象导航查询:查询一个对象的时候,通过此对象查询所有的关联对象
    * 默认使用的是延迟加载的形式查询的
        调用get方法并不会立即发送查询,而是在使用关联对象的时候才会差和讯
        延迟加载!
      修改配置,将延迟加载改为立即加载
        fetch,需要配置到多表映射关系的注解上
    * */
    @Test
    @Transactional  //解决java代码当中的 np session的问题
    public void testQuery1() {
        //查询id为1的对象
        Customer customer = customerDao.findOne(1l);
        //对象导航查询,此客户下的所有联系人
        Set<LinkMan> linkmans = customer.getLinkmans();
        for (LinkMan linkman : linkmans) {
            System.out.println(linkman);
        }
    }

    /*
     * 从联系人导航查询所属客户
     *  默认:立即加载
     * */
    @Test
    @Transactional  //解决java代码当中的 np session的问题
    public void testQuery2() {
        //查询id为1的对象
        LinkMan linkMan = linkManDao.findOne((long) 1);
        //对象导航查询所属客户
        Customer customer = linkMan.getCustomer();
        System.out.println(customer);
    }
}

总结:SpringDataJpa的使用方法

  1. 根据主键单表的CRUD
  2. 在接口使用@Query注解配置Jpql的灵活CRUD
  3. 在接口使用@Query注解配置Sql,nativeQuery = true的灵活CRUD
  4. 使用方法名的约定的方法进行查询
    findBy +属性名+ "查询方式"+ "多条件的连接符(and|or)" +属性名+"查询方式"
  5. 给定条件不固定的时候,使用Specifications动态查询
  6. 一对多操作,在实体类里面配置一对多的关系映射
  7. 多对多操作,在实体类里面配置多对多的关系映射
  8. 对象导航查询测试
    主体对象查询所有的关联对象
    关联对象查询所属的主体对象
  9. 多表查询的级联操作
0 条回应
在线人数:1人 来访统计
隐藏