SpringDataJPA第三天讲义
Posted 清晨的第一抹阳光
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringDataJPA第三天讲义相关的知识,希望对你有一定的参考价值。
第1章 Specifications动态查询
有时我们在查询某个实体的时候,给定的条件是不固定的,这时就需要动态构建相应的查询语句,在Spring Data JPA中可以通过JpaSpecificationExecutor接口查询。相比JPQL,其优势是类型安全,更加的面向对象。
1 package org.springframework.data.jpa.repository; 2 3 import java.util.List; 4 import org.springframework.data.domain.Page; 5 import org.springframework.data.domain.Pageable; 6 import org.springframework.data.domain.Sort; 7 import org.springframework.data.jpa.domain.Specification; 8 9 /** 10 * JpaSpecificationExecutor中定义的方法 11 **/ 12 public interface JpaSpecificationExecutor<T> { 13 T findOne(Specification<T> var1); 14 15 List<T> findAll(Specification<T> var1); 16 17 Page<T> findAll(Specification<T> var1, Pageable var2); 18 19 List<T> findAll(Specification<T> var1, Sort var2); 20 21 long count(Specification<T> var1); 22 }
对于JpaSpecificationExecutor,这个接口基本是围绕着Specification接口来定义的。我们可以简单的理解为,Specification构造的就是查询条件。
Specification接口中只定义了如下一个方法:
1 //构造查询条件 2 /** 3 * root :Root接口,代表查询的根对象,可以通过root获取实体中的属性 4 * query :代表一个顶层查询对象,用来自定义查询 5 * cb :用来构建查询,此对象里有很多条件方法 6 **/ 7 public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb);
1.1 使用Specifications查询实例
Customer.java
1 package cn.itcast.domain; 2 3 import lombok.Getter; 4 import lombok.Setter; 5 import lombok.ToString; 6 7 import javax.persistence.*; 8 9 /** 10 * 1.实体类和表的映射关系 11 * @Eitity 12 * @Table 13 * 2.类中属性和表中字段的映射关系 14 * @Id 15 * @GeneratedValue 16 * @Column 17 */ 18 @Entity 19 @Table(name="cst_customer") 20 @Getter 21 @Setter 22 @ToString 23 public class Customer { 24 25 @Id 26 @GeneratedValue(strategy = GenerationType.IDENTITY) 27 @Column(name = "cust_id") 28 private Long custId; 29 @Column(name = "cust_address") 30 private String custAddress; 31 @Column(name = "cust_industry") 32 private String custIndustry; 33 @Column(name = "cust_level") 34 private String custLevel; 35 @Column(name = "cust_name") 36 private String custName; 37 @Column(name = "cust_phone") 38 private String custPhone; 39 @Column(name = "cust_source") 40 private String custSource; 41 42 }
CustomerDao.java
1 package cn.itcast.dao; 2 3 4 import cn.itcast.domain.Customer; 5 import org.springframework.data.jpa.repository.JpaRepository; 6 import org.springframework.data.jpa.repository.JpaSpecificationExecutor; 7 8 public interface CustomerDao extends JpaRepository<Customer,Long>, JpaSpecificationExecutor<Customer> { 9 }
SpecTest.java
1 package cn.itcast.test; 2 3 import cn.itcast.dao.CustomerDao; 4 import cn.itcast.domain.Customer; 5 import org.junit.Test; 6 import org.junit.runner.RunWith; 7 import org.springframework.beans.factory.annotation.Autowired; 8 import org.springframework.data.domain.Page; 9 import org.springframework.data.domain.PageRequest; 10 import org.springframework.data.domain.Pageable; 11 import org.springframework.data.domain.Sort; 12 import org.springframework.data.jpa.domain.Specification; 13 import org.springframework.test.context.ContextConfiguration; 14 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 15 16 import javax.persistence.criteria.*; 17 import java.util.List; 18 19 @RunWith(SpringJUnit4ClassRunner.class) 20 @ContextConfiguration(locations = "classpath:applicationContext.xml") 21 public class SpecTest { 22 23 24 @Autowired 25 private CustomerDao customerDao; 26 27 /** 28 * 根据条件,查询单个对象 29 * 30 */ 31 @Test 32 public void testSpec() { 33 //匿名内部类 34 /** 35 * 自定义查询条件 36 * 1.实现Specification接口(提供泛型:查询的对象类型) 37 * 2.实现toPredicate方法(构造查询条件) 38 * 3.需要借助方法参数中的两个参数( 39 * root:获取需要查询的对象属性 40 * CriteriaBuilder:构造查询条件的,内部封装了很多的查询条件(模糊匹配,精准匹配) 41 * ) 42 * 案例:根据客户名称查询,查询客户名为传智播客的客户 43 * 查询条件 44 * 1.查询方式 45 * cb对象 46 * 2.比较的属性名称 47 * root对象 48 * 49 */ 50 Specification<Customer> spec = new Specification<Customer>() { 51 @Override 52 public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> query, CriteriaBuilder cb) { 53 //1.获取比较的属性 54 Path<Object> custName = root.get("custName"); 55 //2.构造查询条件 : select * from cst_customer where cust_name = \'传智播客\' 56 /** 57 * 第一个参数:需要比较的属性(path对象) 58 * 第二个参数:当前需要比较的取值 59 */ 60 Predicate predicate = cb.equal(custName, "传智播客");//进行精准的匹配 (比较的属性,比较的属性的取值) 61 return predicate; 62 } 63 }; 64 Customer customer = customerDao.findOne(spec); 65 System.out.println(customer); 66 } 67 68 /** 69 * 多条件查询 70 * 案例:根据客户名(传智播客)和客户所属行业查询(it教育) 71 * 72 */ 73 @Test 74 public void testSpec1() { 75 /** 76 * root:获取属性 77 * 客户名 78 * 所属行业 79 * cb:构造查询 80 * 1.构造客户名的精准匹配查询 81 * 2.构造所属行业的精准匹配查询 82 * 3.将以上两个查询联系起来 83 */ 84 Specification<Customer> spec = new Specification<Customer>() { 85 @Override 86 public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> query, CriteriaBuilder cb) { 87 Path<Object> custName = root.get("custName");//客户名 88 Path<Object> custIndustry = root.get("custIndustry");//所属行业 89 90 //构造查询 91 //1.构造客户名的精准匹配查询 92 Predicate p1 = cb.equal(custName, "传智播客");//第一个参数,path(属性),第二个参数,属性的取值 93 //2..构造所属行业的精准匹配查询 94 Predicate p2 = cb.equal(custIndustry, "it教育"); 95 //3.将多个查询条件组合到一起:组合(满足条件一并且满足条件二:与关系,满足条件一或满足条件二即可:或关系) 96 Predicate and = cb.and(p1, p2);//以与的形式拼接多个查询条件 97 // cb.or();//以或的形式拼接多个查询条件 98 return and; 99 } 100 }; 101 Customer customer = customerDao.findOne(spec); 102 System.out.println(customer); 103 } 104 105 /** 106 * 案例:完成根据客户名称的模糊匹配,返回客户列表 107 * 客户名称以 ’传智播客‘ 开头 108 * 109 * equal :直接的到path对象(属性),然后进行比较即可 110 * gt,lt,ge,le,like : 得到path对象,根据path指定比较的参数类型,再去进行比较 111 * 指定参数类型:path.as(类型的字节码对象) 112 */ 113 @Test 114 public void testSpec3() { 115 //构造查询条件 116 Specification<Customer> spec = new Specification<Customer>() { 117 @Override 118 public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> query, CriteriaBuilder cb) { 119 //查询属性:客户名 120 Path<Object> custName = root.get("custName"); 121 //查询方式:模糊匹配 122 Predicate like = cb.like(custName.as(String.class), "传智播客%"); 123 return like; 124 } 125 }; 126 // List<Customer> list = customerDao.findAll(spec); 127 // for (Customer customer : list) { 128 // System.out.println(customer); 129 // } 130 //添加排序 131 //创建排序对象,需要调用构造方法实例化sort对象 132 //第一个参数:排序的顺序(倒序,正序) 133 // Sort.Direction.DESC:倒序 134 // Sort.Direction.ASC : 升序 135 //第二个参数:排序的属性名称 136 Sort sort = new Sort(Sort.Direction.DESC,"custId"); 137 List<Customer> list = customerDao.findAll(spec, sort); 138 for (Customer customer : list) { 139 System.out.println(customer); 140 } 141 } 142 143 144 /** 145 * 分页查询 146 * Specification: 查询条件 147 * Pageable:分页参数 148 * 分页参数:查询的页码,每页查询的条数 149 * findAll(Specification,Pageable):带有条件的分页 150 * findAll(Pageable):没有条件的分页 151 * 返回:Page(springDataJpa为我们封装好的pageBean对象,数据列表,共条数) 152 */ 153 @Test 154 public void testSpec4() { 155 156 Specification spec = null; 157 //PageRequest对象是Pageable接口的实现类 158 /** 159 * 创建PageRequest的过程中,需要调用他的构造方法传入两个参数 160 * 第一个参数:当前查询的页数(从0开始) 161 * 第二个参数:每页查询的数量 162 */ 163 Pageable pageable = new PageRequest(0,2); 164 //分页查询 165 Page<Customer> page = customerDao.findAll(null, pageable); 166 System.out.println(page.getContent()); //得到数据集合列表 167 System.out.println(page.getTotalElements());//得到总条数 168 System.out.println(page.getTotalPages());//得到总页数 169 } 170 171 172 }
对于Spring Data JPA中的分页查询,是其内部自动实现的封装过程,返回的是一个Spring Data JPA提供的pageBean对象。其中的方法说明如下:
1 //获取总页数 2 int getTotalPages(); 3 //获取总记录数 4 long getTotalElements(); 5 //获取列表数据 6 List<T> getContent();
1.2 方法对应关系
第2章 多表设计
2.1 表之间关系的划分
数据库中多表之间存在着三种关系,如图所示。
从图可以看出,系统设计的三种实体关系分别为:多对多、一对多和一对一关系。注意:一对多关系可以看为两种: 即一对多,多对一。所以说四种更精确。
明确: 我们只涉及实际开发中常用的关联关系,一对多和多对多。而一对一的情况,在实际开发中几乎不用。
2.2 在JPA框架中表关系的分析步骤
在实际开发中,我们数据库的表难免会有相互的关联关系,在操作表的时候就有可能会涉及到多张表的操作。而在这种实现了ORM思想的框架中(如JPA),可以让我们通过操作实体类就实现对数据库表的操作。所以今天我们的学习重点是:掌握配置实体之间的关联关系。
第一步:首先确定两张表之间的关系。
如果关系确定错了,后面做的所有操作就都不可能正确。
第二步:在数据库中实现两张表的关系
第三步:在实体类中描述出两个实体的关系
第四步:配置出实体类和数据库表的关系映射(重点)
第3章 JPA中的一对多
3.1 示例分析
我们采用的示例为客户和联系人。
客户:指的是一家公司,我们记为A。
联系人:指的是A公司中的员工。
在不考虑兼职的情况下,公司和员工的关系即为一对多。
3.2 表关系建立
在一对多关系中,我们习惯把一的一方称之为主表,把多的一方称之为从表。在数据库中建立一对多的关系,需要使用数据库的外键约束。
什么是外键?
指的是从表中有一列,取值参照主表的主键,这一列就是外键。
一对多数据库关系的建立,如下图所示
3.3 实体类关系建立以及映射配置
在实体类中,由于客户是少的一方,它应该包含多个联系人,所以实体类要体现出客户中有多个联系人的信息,代码如下:
1 package cn.itcast.domain; 2 3 import lombok.Getter; 4 import lombok.Setter; 5 import lombok.ToString; 6 7 import javax.persistence.*; 8 import java.util.HashSet; 9 import java.util.Set; 10 11 /** 12 * 1.实体类和表的映射关系 13 * @Eitity 14 * @Table 15 * 2.类中属性和表中字段的映射关系 16 * @Id 17 * @GeneratedValue 18 * @Column 19 */ 20 @Entity 21 @Table(name="cst_customer") 22 @Getter 23 @Setter 24 @ToString 25 public class Customer { 26 27 @Id 28 @GeneratedValue(strategy = GenerationType.IDENTITY) 29 @Column(name = "cust_id") 30 private Long custId; 31 @Column(name = "cust_address") 32 private String custAddress; 33 @Column(name = "cust_industry") 34 private String custIndustry; 35 @Column(name = "cust_level") 36 private String custLevel; 37 @Column(name = "cust_name") 38 private String custName; 39 @Column(name = "cust_phone") 40 private String custPhone; 41 @Column(name = "cust_source") 42 private String custSource; 43 44 //配置客户和联系人之间的关系(一对多关系) 45 /** 46 * 使用注解的形式配置多表关系 47 * 1.声明关系 48 * @OneToMany : 配置一对多关系 49 * targetEntity :对方对象的字节码对象 50 * 2.配置外键(中间表) 51 * @JoinColumn : 配置外键 52 * name:外键字段名称 53 * referencedColumnName:参照的主表的主键字段名称 54 * 55 * * 在客户实体类上(一的一方)添加了外键了配置,所以对于客户而言,也具备了维护外键的作用 56 * 57 */ 58 59 //@OneToMany(targetEntity = LinkMan.class) 60 //@JoinColumn(name = "lkm_cust_id",referencedColumnName = "cust_id") 61 /** 62 * 放弃外键维护权 63 * mappedBy:对方配置关系的属性名称\\ 64 * cascade : 配置级联(可以配置到设置多表的映射关系的注解上) 65 * CascadeType.all : 所有 66 * MERGE :更新 67 * PERSIST :保存 68 * REMOVE :删除 69 * 70 * fetch : 配置关联对象的加载方式 71 * EAGER :立即加载(有左外连接 left out join) 72 * LAZY :延迟加载 73 74 */ 75 //@OneToMany(mappedBy = "customer",cascade = CascadeType.ALL,fetch = FetchType.EAGER) 76 @OneToMany(mappedBy = "customer",cascade = CascadeType.ALL) 77 private Set<LinkMan> linkMans = new HashSet<>(); 78 79 80 }
由于联系人是多的一方,在实体类中要体现出,每个联系人只能对应一个客户,代码如下:
1 package cn.itcast.domain; 2 3 4 import lombok.Getter; 5 import lombok.Setter; 6 7 import javax.persistence.Entity; 8 import javax.persistence.*; 9 10 @Entity 11 @Table(name = "cst_linkman") 12 @Setter 13 @Getter 14 public class LinkMan { 15 16 @Id 17 @GeneratedValue(strategy = GenerationType.IDENTITY) 18 @Column(name = "lkm_id") 19 private Long lkmId; //联系人编号(主键) 20 @Column(name = "lkm_name") 21 private String lkmName;//联系人姓名 22 @Column(name = "lkm_gender") 23 private String lkmGender;//联系人性别 24 @Column(name = "lkm_phone") 25 private String lkmPhone;//联系人办公电话 26 @Column(name = "lkm_mobile") 27 private String lkmMobile;//联系人手机 28 @Column(name = "lkm_email") 29 private String lkmEmail;//联系人邮箱 30 @Column(name = "lkm_position") 31 private String lkmPosition;//联系人职位 32 @Column(name = "lkm_memo") 33 private String lkmMemo;//联系人备注 34 35 /** 36 * 配置联系人到客户的多对一关系 37 * 使用注解的形式配置多对一关系 38 * 1.配置表关系 39 * @ManyToOne : 配置多对一关系 40 * targetEntity:对方的实体类字节码 41 * 2.配置外键(中间表) 42 * 43 * * 配置外键的过程,配置到了多的一方,就会在多的一方维护外键 44 * 45 */ 46 @ManyToOne(targetEntity = Customer.class,fetch = FetchType.LAZY) 47 @JoinColumn(name = "lkm_cust_id",referencedColumnName = "cust_id") 48 private Customer customer; 49 }
3.4 映射的注解说明
@OneToMany:
作用:建立一对多的关系映射
属性:
targetEntityClass:指定多的多方的类的字节码
mappedBy:指定从表实体类中引用主表对象的名称。
cascade:指定要使用的级联操作
fetch:指定是否采用延迟加载
orphanRemoval:是否使用孤儿删除
@ManyToOne
作用:建立多对一的关系
属性:
targetEntityClass:指定一的一方实体类字节码
cascade:指定要使用的级联操作
fetch:指定是否采用延迟加载
optional:关联是否可选。如果设置为false,则必须始终存在非空关系。
@JoinColumn
作用:用于定义主键字段和外键字段的对应关系。
属性:
name:指定外键字段的名称
referencedColumnName:指定引用主表的主键字段名称
unique:是否唯一。默认值不唯一
nullable:是否允许为空。默认值允许。
insertable:是否允许插入。默认值允许。
updatable:是否允许更新。默认值允许。
columnDefinition:列的定义信息。
3.5 一对多的操作
3.5.1 添加
1 @RunWith(SpringJUnit4ClassRunner.class) 2 @ContextConfiguration(locations="classpath:applicationContext.xml") 3 public class OneToManyTest { 4 5 @Autowired 6 private CustomerDao customerDao; 7 8 @Autowired 9 private LinkManDao linkManDao; 10 11 12 /** 以上是关于SpringDataJPA第三天讲义的主要内容,如果未能解决你的问题,请参考以下文章