首页技术栈归档照片墙音乐日记随想收藏夹友链留言关于

Spring Data JPA-03

写作时间:2026-07-08

1. Specification动态条件查询

JpaSpecificationExecutor提供了一种以更加面向对象的动态构建查询条件的方式.

  • 动态条件查询
  • 面向对象的查询方式

1.1 步骤

1. Dao接口继承JpaSpecificationExecutor
2. 创建Specification对象封装查询条件
3. 调用JpaSpecificationExecutor接口方法,传递Specification对象进行条件查询

1.2 常见查询

1.2.1 创建Dao接口

/**
 * 客户Dao
 */
public interface CustomerDao extends JpaRepository<Customer,Long> ,JpaSpecificationExecutor<Customer>{

}

1.2.2 单条件

/**
 * 单条件查询:查询名称是'黑马程序员1'的数据
 */
@Test
public void testSpec() {
    /**
     * 创建Specification对象,封装查询条件
     *      root对象:获得条件查询的属性;
     *      cb对象:构建查询条件;
     */

  Specification<Customer> spec = new Specification<Customer>() {
     public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
     		//1.获得条件查询属性
     		Path<Object> custName = root.get("custName");
     		//2.构造查询条件;   
         	//同功能JPQL: from Customer where  custName = "黑马程序员1"
     		Predicate predicate = cb.equal(custName, "黑马程序员1");
            return predicate;
      }
  };
  Customer customer = customerDao.findOne(spec);
  System.out.println(customer);
}

1.2.3 多条件

/**
 * 多条件查询:根据客户名(传智播客)和客户所属行业查询(it教育)
 */
@Test
public void testSpec1() {

    /**
     * 创建Specification对象,封装查询条件
     *      root对象:获得条件查询的属性;
     *      cb对象:构建查询条件;
     */
  Specification<Customer> spec = new Specification<Customer>() {
     public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
        //属性1:客户名
        Path<Object> custName = root.get("custName");
        //属性2:所属行业
        Path<Object> custIndustry = root.get("custIndustry");
        //构造查询条件
        //1.构造客户名的精准匹配查询
        Predicate p1 = cb.equal(custName, "传智播客");
        //2.构造所属行业的精准匹配查询
        Predicate p2 = cb.equal(custIndustry, "it教育");
        //3.将多个查询条件进行组合
        //同功能JPQL语句:from Customer where custName = "传智播客" and custIndustry="it教育"
        Predicate and = cb.and(p1, p2);
        return and;
     }
  };
  Customer customer = customerDao.findOne(spec);
  System.out.println(customer);
}

1.2.4 带条件查询并排序

/**
 * 模糊查询:查询客户名称以 ’黑马程序员‘ 开头的客户列表,并按照ID排序
 */
@Test
public void testSpec3() {
  //构造查询条件
  Specification<Customer> spec = new Specification<Customer>() {
     public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
            //查询属性:客户名
            Path<Object> custName = root.get("custName");
            //查询方式:模糊匹配,除过equals的其他查询方式必须指定比较的属性类型
            Predicate like = cb.like(custName.as(String.class), "黑马程序员%");
            return like;
        }
    };

    //创建排序对象
    Sort sort = new Sort(Sort.Direction.DESC,"custId");
    //执行查询
    List<Customer> list = customerDao.findAll(spec, sort);
    for (Customer customer : list) {
        System.out.println(customer);
    }
}

1.2.5 带条件分页查询

/**
 * 带条件分页查询:查询客户名称以 ’黑马程序员‘ 开头的客户列表,显示0-5条数据
 */
@Test
public void testSpec4() {
    //条件条件对象
    Specification spec = new Specification() {
        public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) {
            //查询属性:客户名
            Path<Object> custName = root.get("custName");
            //查询方式:模糊匹配,除过equals的其他查询方式必须指定比较的属性类型
            Predicate like = cb.like(custName.as(String.class), "黑马程序员%");
            return like;
        }
    };
    //创建分页对象,参数1:从哪里开始查;参数2:查询多少条
    Pageable pageable = new PageRequest(0,5);
    //分页查询
    Page<Customer> page = customerDao.findAll(spec, pageable);
    System.out.println("数据集合:"+page.getContent());
    System.out.println("总条数:"+page.getTotalElements());
    System.out.println("总页数:"+page.getTotalPages());
}

1.2.6 动态查询

public void test(){
        Customer customer = new Customer();
        customer.setCustIndustry("it教育");
        customer.setCustName("传智播客");

        Specification<Customer> spec = new Specification<Customer>() {
            @Override
            public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> query, CriteriaBuilder cb) {

                List<Predicate> list = new ArrayList<Predicate>();

                //判断cusName是否有值
                if(customer.getCustName()!=null && !"".equals(customer.getCustName())){
                    Path<Object> cusName = root.get("custName");
                    Predicate p1 = cb.like(cusName.as(String.class), customer.getCustName());
                    list.add(p1);
                }

                //判断custIndustry是否有值
                if(customer.getCustIndustry()!=null && !"".equals(customer.getCustIndustry())){
                    Path<Object> custIndustry = root.get("custIndustry");
                    Predicate p2 = cb.equal(custIndustry.as(String.class), customer.getCustIndustry());
                    list.add(p2);
                }

                Predicate[] predicates = list.toArray(new Predicate[0]);

                return cb.and(predicates);
            }
        };
        List<Customer> list = customerDao.findAll(spec);
        System.out.println(list);
}

1.3 cb查询方式总结

方法名称 Sql对应关系
equle filed = value
gt(greaterThan ) filed > value
lt(lessThan ) filed < value
ge(greaterThanOrEqualTo ) filed >= value
le( lessThanOrEqualTo) filed <= value
notEqule filed != value
like filed like value
notLike filed not like value

2. JPA的多表操作

2.1 多表之间的关系

举例:

一对一:居民和身份证
一对多:公司和员工
多对多:用户和角色

数据库实现:

一对一:在任意一张表中添加外键(唯一的),关联对方主键
一对多:在多方表中添加外键,关联一方的主键
多对多:创建中间表,分别关联两张表的主键

JPA实体的实现:

一对一:在任意一方包含对方的对象属性
一对多:在一方包含多方的集合属性,在多方中包含一方的对象属性
多对多:在任意一方包含对方的集合属性

ORM配置多表关系步骤:

1. 添加关系注解:@OneToMany @ManyToOne @ManyToMany
2. 确定由哪方维护外键关系(另外一方放弃关系维护)
3. 在维护关系一方配置外键信息

2.2 一对多配置

一方

/**
 * 客户实体类
 */
@Entity
@Table(name="cst_customer")
public class Customer {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long custId;
    private String custAddress;
    private String custIndustry;
    private String custLevel;
    private String custName;
    private String custPhone;
    private String custSource;

   /**
    * 一对多注解
    * 一方放弃维护外键关系
    */
    @OneToMany(mappedBy = "customer")
    private Set<LinkMan> linkMans = new HashSet<LinkMan>();
}

多方

@Entity
@Table(name = "cst_linkman")
public class LinkMan {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long lkmId; //联系人编号(主键)
    private String lkmName;//联系人姓名
    private String lkmGender;//联系人性别
    private String lkmPhone;//联系人办公电话
    private String lkmMobile;//联系人手机
    private String lkmEmail;//联系人邮箱
    private String lkmPosition;//联系人职位
    private String lkmMemo;//联系人备注

   /**
    * 多对一注解
    * 多方维护外键关系,配置外键名称
    */
    @ManyToOne
    @JoinColumn(name = "lkm_cust_id")
    private Customer customer;    
}

2.2.1 一般保存

@Test
@Transactional //配置事务
@Rollback(false) //不自动回滚
public void testAdd() {
    //创建一个客户
    Customer customer = new Customer();
    customer.setCustName("百度");
	//创建一个联系人
    LinkMan linkMan = new LinkMan();
    linkMan.setLkmName("小李");

	//维护外键关系
    linkMan.setCustomer(customer);
    //保存客户
    customerDao.save(customer);
    //保存联系人
    linkManDao.save(linkMan);
}

2.2.2 级联保存

/**
 * 级联添加:保存一个客户的同时,保存客户的所有联系人
 *      需要在操作主体的实体类上,配置cascade属性
 *		    @OneToMany(mappedBy = "customer",cascade =CascadeType.PERSIST)
 *  		private Set<LinkMan> linkMans = new HashSet<LinkMan>();
 */
@Test
@Transactional //配置事务
@Rollback(false) //不自动回滚
public void testCascadeAdd() {
    Customer customer = new Customer();
    customer.setCustName("百度1");

    LinkMan linkMan = new LinkMan();
    linkMan.setLkmName("小李1");

    //维护外键
    linkMan.setCustomer(customer);
    //级联保存
    customer.getLinkMans().add(linkMan);

    customerDao.save(customer);
}

2.2.3 级联删除

/**
 * 级联删除:删除客户的同时,删除该客户的所有联系人
 *      需要在操作主体的实体类上,配置cascade属性
 *		    @OneToMany(mappedBy = "customer",cascade =CascadeType.REMOVE)
 *  		private Set<LinkMan> linkMans = new HashSet<LinkMan>();
 */
@Test
@Transactional //配置事务
@Rollback(false) //不自动回滚
public void testCascadeRemove() {
    //1.查询1号客户
    Customer customer = customerDao.findOne(1L);
    //2.删除1号客户
    customerDao.delete(customer);
}

2.3 多对多配置

多方

@Entity
@Table(name = "sys_user")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long userId;
    private String userName;
    private Integer age;
    //配置多对多关系,User方维护外键关系
  	@ManyToMany
    @JoinTable(name="user_role",joinColumns = {@JoinColumn(name ="user_id" )},
               inverseJoinColumns = {@JoinColumn(name="role_id")})
    private Set<Role> roles = new HashSet<Role>();

}

另一多方

@Entity
@Table(name = "sys_role")
public class Role {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long roleId;
    private String roleName;

    //配置多对多关系,Role方放弃维护外键关系
    @ManyToMany(mappedBy = "roles")  //配置多表关系
    private Set<User> users = new HashSet<User>();
}

2.3.1 一般保存

/**
 * 保存一个用户,保存一个角色
 */
@Test
@Transactional
@Rollback(false)
public void  testAdd() {
    User user = new User();
    user.setUserName("小李");

    Role role = new Role();
    role.setRoleName("java程序员");

    //用户维护外键关系
    user.getRoles().add(role);

    userDao.save(user);
    roleDao.save(role);
}

2.3.2 级联保存

/**
 * 测试级联添加(保存一个用户的同时保存用户的关联角色)
 *      @ManyToMany(cascade = {CascadeType.PERSIST})
 *      @JoinTable(joinColumns = {@JoinColumn(name = "user_id")}, 
 *				   inverseJoinColumns ={@JoinColumn(name = "role_id")})
 *      private Set<Role> roles = new HashSet<Role>();
 */
@Test
@Transactional
@Rollback(false)
public void  testCasCadeAdd() {
    User user = new User();
    user.setUserName("小李");

    Role role = new Role();
    role.setRoleName("java程序员");

    //配置用户到角色关系,可以对中间表中的数据进行维护     1-1
    user.getRoles().add(role);

    //保存客户,级联保存角色
    userDao.save(user);
}

2.3.3 级联删除

/**
 * 案例:删除id为5的用户,同时删除他的关联角色
 *	    @ManyToMany(cascade = {CascadeType.REMOVE})
 *      @JoinTable(joinColumns = {@JoinColumn(name = "user_id")}, 
 *				   inverseJoinColumns ={@JoinColumn(name = "role_id")})
 *      private Set<Role> roles = new HashSet<Role>();
 */
@Test
@Transactional
@Rollback(false)
public void  testCasCadeRemove() {
    //查询5号用户
    User user = userDao.findOne(5L);
    //删除5号用户
    userDao.delete(user);
}

2.4 级联配置总结

//在关系注解上添加如下属性
增:CascadeType.PERSIST
删:CascadeType.REMOVE
改:CascadeType.MERGE
所有:CascadeType.ALL

3. 多表之间的查询

3.1 对象导航图查询

通过实体类中的关联属性查询关联数据

一方查询多方

/**
 * 先查询客户
 * 再通过对象导航图方式查询对应的联系人
 */
@Test
@Transactional // 解决在java代码中的no session问题
public void  testQuery1() {
    //查询id为1的客户
    Customer customer = customerDao.getOne(6L);
    //对象导航查询,此客户下的所有联系人
    Set<LinkMan> linkMans = customer.getLinkMans();

    for (LinkMan linkMan : linkMans) {
        System.out.println(linkMan);
    }
}

多方查询一方

@Test
public void  testQuery3() {
    LinkMan linkMan = linkManDao.findOne(6L);
    //对象导航查询所属的客户
    Customer customer = linkMan.getCustomer();
    System.out.println(customer);
}

一方查询多方,默认是懒加载

多方查询一方,默认是立即加载

配置对象导航图加载方式

//在关系注解上添加如下属性
立即加载:fetch=FetchType.EAGER
懒加载:fetch=FetchType.LAZY

3.2 Specification查询

/**
 * Specification的多表查询
 */
@Test
public void testFind() {
 Specification<LinkMan> spec = new Specification<LinkMan>() {
  public Predicate toPredicate(Root<LinkMan> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
        /**
         * Join代表连接查询,通过root对象获取
         * JoinType.INNER
         * JoinType.LEFT
         * JoinType.RIGHT
         */
        Join<LinkMan, Customer> join = root.join("customer", JoinType.INNER);
        return cb.like(join.get("custName").as(String.class),"百度");
   }
 };
 List<LinkMan> list = linkManDao.findAll(spec);
 for (LinkMan linkMan : list) {
     System.out.println(linkMan);
 }
}
avatar

yuanyourdomain

写代码,做研究,记录生活。

RECOMMENDED

MyBatis 动态 SQL

2026-07-08

Nginx 基础入门

2026-07-08

Maven 多模块与私服

2026-07-08