Spring Data JPA
Spring Boot整合使用Spring Data JPA:
源代码仓库地址:
https://gitee.com/sc2glory/spring-data-jpa-knowledge.git
1. pom.xml配置:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.springdata</groupId>
<artifactId>springdata</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springData</name>
<description>Demo project for Spring Data</description>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
</properties>
<!--boot依赖-->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--将依赖项声明为可选-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!--spring data jpa 依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!--MySQL-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.23</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2. YAML文件配置:
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/springdata
username: root
password: root
# jpa数据库
jpa:
database: mysql
# sql
show-sql: true
hibernate:
ddl-auto: update
# tomcat
server:
port: 80
# 日志
logging:
level:
com.springboot: debug
org.springframework: info
3. 编写实体类:
package com.springdata.entity;
import javax.persistence.*;
/**
* 1. 实体类和表的映射关系
* @author GLoRy
* @Entity,@Table
* 2. 类中属性和表中字段的映射关系
* @Id @GeneratedValue @Column
*/
@Entity
@Table(name = "cst_customer")
public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "custom_industry")
private String customIndustry;
@Column(name = "custom_address")
private String customAddress;
@Column(name = "custom_level")
private String customLevel;
@Column(name = "custom_name")
private String customName;
@Column(name = "custom_phone")
private String customPhone;
@Column(name = "custom_source")
private String customSource;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getCustomIndustry() {
return customIndustry;
}
public void setCustomIndustry(String customIndustry) {
this.customIndustry = customIndustry;
}
public String getCustomAddress() {
return customAddress;
}
public void setCustomAddress(String customAddress) {
this.customAddress = customAddress;
}
public String getCustomLevel() {
return customLevel;
}
public void setCustomLevel(String customLevel) {
this.customLevel = customLevel;
}
public String getCustomName() {
return customName;
}
public void setCustomName(String customName) {
this.customName = customName;
}
public String getCustomPhone() {
return customPhone;
}
public void setCustomPhone(String customPhone) {
this.customPhone = customPhone;
}
public String getCustomSource() {
return customSource;
}
public void setCustomSource(String customSource) {
this.customSource = customSource;
}
@Override
public String toString() {
return "Customer{" +
"id=" + id +
", customIndustry='" + customIndustry + '\'' +
", customAddress='" + customAddress + '\'' +
", customLevel='" + customLevel + '\'' +
", customName='" + customName + '\'' +
", customPhone='" + customPhone + '\'' +
", customSource='" + customSource + '\'' +
'}';
}
}
4. 编写符合规范的DAO层接口:
package com.springdata.dao;
import com.springdata.entity.Customer;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.jpa.repository.support.JpaRepositoryImplementation;
import java.util.List;
/**
* 符合Spring Data Jpa 的 Dao层接口规范
* JpaRepository<操作的实体类型,实体类中主键属性的类型> 封装了基本CRUD操作
* JpaSpecificationExecutor<操作的实体类型> 封装了复杂查询(分页)
* JpaRepositoryImplementation<操作的实体类型,实体类中主键属性的类型> 继承了上面两个接口
*
* @author GLoRy
* @date 2021/5/11 22:43
*/
public interface CustomerRepository extends JpaRepositoryImplementation<Customer, Long> {
/**
* 根据客户名称查询客户
* JPQL:from Customer where customName = ?
* 注意:在注解中 ? 后面要加index,意思是代表方法中的第几个参数,否则报错,低版本spring可以不加
*
* @param customName 客户名称
* @return 客户信息
*/
@Query(value = "from Customer where customName = ?1")
Customer findByName(String customName);
/**
* 根据客户id和名称查询客户信息
* JPQL:from Customer where id = ?1 and customName = ?2
*
* @param id 客户id
* @param name 姓名
* @return 客户信息
*/
@Query(value = "from Customer where id = ?1 and customName = ?2")
Customer findByIdAndName(Long id, String name);
/**
* 根据id更新客户名称
* SQL:update t_customer set custom_name = ? where id = ?;
* JPQL:update Customer set customName = ?2 where id = ?1
*
* @param name 客户名称
* @param id 客户id
* @Query :用来进行查询操作,可以通过声明方法进行其他操作
* @Modifying :声明当前的方法是更新操作
*/
@Query(value = "update Customer set customName = ?2 where id = ?1")
@Modifying
void updateCustomerNameById(Long id, String name);
/**
* 使用SQL查询全部的客户信息
* value:SQL语句
* nativeQuery :true:使用本地查询SQL false:使用JPQL
*
* @return 全部客户信息
*/
@Query(value = "select * from cst_customer", nativeQuery = true)
List<Object[]> useSqlFindAll();
/**
* 使用SQL根据姓名模糊查询
*
* @param name 客户名称
* @return 客户信息
*/
@Query(value = "select * from cst_customer where custom_name like ?1", nativeQuery = true)
List<Object[]> useSqlFindLikeName(String name);
/**
* 根据客户的姓名查询
* findBy:查询
* 查询的条件:对象中的属性名(首字母大写)
* Spring Data JPA会解析:findBy(属性名称) from (实体类) where customName = ?
* 默认方式:使用 "=" 的方式进行查询 findBy + 属性名称(根据属性名称完成匹配的查询)
*
* @param customName 客户名称
* @return 客户信息
*/
Customer findByCustomName(String customName);
/**
* 特殊的查询方式:findBy + 属性名称 + 查询方式(like | isnull 等等)
* 模糊匹配查询
*
* @param customName 客户名称
* @return 客户信息
*/
List<Customer> findByCustomNameLike(String customName);
/**
* 多条件查询:findBy + 属性名称 + ”查询方式“ + 多条件的连接符(and | or)+ 属性名称 + ”查询方式“
* 使用客户名称模糊匹配和来源精确匹配的查询
*
* @param customName 客户名称
* @param customSource 客户来源
* @return 客户
*/
Customer findByCustomNameLikeAndCustomSource(String customName, String customSource);
}
5. 基础CRUD代码测试:
数据库表结构字段:
id | custom_industry | custom_level | custom_address | custom_name | custom_pone | custom_source |
---|---|---|---|---|---|---|
1 | IT行业 | good | 北京 | 顾客001 | 10001 | 北京 |
2 | IT行业 | good | 南京 | 顾客002 | 10002 | 南京 |
3 | IT行业 | good | 上海 | 顾客003 | 10003 | 上海 |
4 | IT行业 | common | 成都 | 顾客004 | 10004 | 成都 |
5 | IT行业 | good | 杭州 | 顾客005 | 10005 | 杭州 |
6 | 服务业 | good | 成都 | 顾客006 | 100006 | 成都 |
8 | IT行业 | good | 深圳 | 顾客008 | 10008 | 广州 |
package com.springdata;
import com.springdata.dao.CustomerRepository;
import com.springdata.entity.Customer;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Optional;
/**
* @author GLoRy
* @date 2021/5/11 22:52
*/
@SpringBootTest
public class CustomerDaoTest {
@Autowired
private CustomerRepository customerRepository;
/**
* 保存或者更新
* 根据是否存在主键id,如果没有id:保存
* 如果存在id,根据id查询数据,更新数据
*/
@Test
void testSave() {
Customer customer = new Customer();
customer.setCustomIndustry("IT行业");
customer.setCustomLevel("good");
customer.setCustomAddress("北京");
customer.setCustomName("顾客001");
customer.setCustomPhone("10001");
customer.setCustomSource("北京");
customerRepository.save(customer);
}
/**
* 更新
*/
@Test
void testUpdate() {
Customer customer = new Customer();
customer.setId(8L);
customer.setCustomIndustry("IT行业");
customer.setCustomLevel("good");
customer.setCustomAddress("深圳");
customer.setCustomName("顾客008");
customer.setCustomPhone("10008");
customer.setCustomSource("广州");
customerRepository.save(customer);
}
/**
* 删除
*/
@Test
void deleteTest() {
customerRepository.deleteById(7L);
}
/**
* 根据id查询
*/
@Test
void testFindById() {
Optional<Customer> byId = customerRepository.findById(2L);
System.out.println(byId);
}
/**
* 查询所有
*/
@Test
void findAllTest() {
List<Customer> customers = customerRepository.findAll();
for (Customer customer : customers) {
System.out.println(customer);
}
}
/**
* 统计查询总数量
*/
@Test
void testCount() {
long count = customerRepository.count();
System.out.println(count);
}
/**
* 判断id为2的客户是否存在
* 1. 查询用户id为2是否存在,如果为null就不存在,否则存在
* 2. 判断数据库里id为2的客户数量。数量为0不存在,大于0存在
* jpa使用的第二种方式
*/
@Test
void testExistById() {
boolean exist = customerRepository.existsById(2L);
System.out.println(exist);
}
/**
* 根据id从数据库查询
*
* @Transactional 保证getOne()正常运行
* findOne():调用的是em.find() 立即加载
* getOne():调用的是em.getReference() 延迟加载 返回的是一个动态代理对象,什么时候就用什么时候查询
*/
@Test
@Transactional
void testGetOne() {
Customer customer = customerRepository.getOne(2L);
System.out.println(customer);
}
}
6. 基本CRUD的执行过程原理:
UML图展示:
源码截图:
以findById()方法为例,执行过程如下:
- 在findById方法上打断点
- 进入JdkDynamicAopProxy方法,可以看到动态代理相关内容
- 通过JdkDynamicAopProxy创建动态代理对象
代理对象:target
- 进入到实现类方法SimpleJpaRepository,其中封装了JPA的操作,图中进入到findById()方法
- 执行EntityManager下的em方法完成方法功能
7. 复杂查询:
- 借助接口中定义好的方法进行查询
- JPQL的方式完成查询
- JPQL:JPA Query Language(JPA查询语言)
- 特点:语法或者关键字和SQL语句类似
- 查询的是类和类中的属性
- 需要将JPQL语句配置到接口方法上
- 特有的查询:需要在DAO层接口上配置查询方式
- 在新添加的方法上,使用注解的形式配置JPQL查询语句
- 注解:@Query
- SQL语句的查询
- 需要将SQL语句配置到接口方法上
- 特有的查询:需要在DAO层接口上配置查询方式
- 在新添加的方法上,使用注解的形式配置SQL查询语句
- 注解:@Query
- value:JPQL | SQL
- nativeQuery :false | true 代表是否是使用本地查询
- true:使用本地查询SQL
- false:使用JPQL
- 需要将SQL语句配置到接口方法上
- 方法名称规则查询
- 这种方法是对JPQL查询更深入的一层封装
- 只需按照Spring Data JPA提供的方法名称规则定义方法,不需要再去配置JPQL语句,就可以完成查询操作
- 命名约定:
- findBy开头:代表查询
- 查询的条件:对象中的属性名(首字母大写)
- 根据属性名称进行查询
- 例如findByCustomName – 根据客户的姓名查询
- 在Spring Data JPA的运行阶段,会根据方法名进行解析
- findBy(属性名称) from (实体类) where customName = ?
- 默认方式:使用 “=” 的方式进行查询 findBy + 属性名称(根据属性名称完成匹配的查询)
- 特殊的查询方式:findBy + 属性名称 + 查询方式(like | isnull 等等)
- 多条件查询:findBy + 属性名称 + ”查询方式“ + 多条件的连接符(and | or)+ 属性名称 + ”查询方式“
- findBy开头:代表查询
JPQL,SQL和方法名称规则代码测试:
JPQL和SQL查询代码:
package com.springdata;
import com.springdata.dao.CustomerRepository;
import com.springdata.entity.Customer;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.Rollback;
import org.springframework.transaction.annotation.Transactional;
import java.util.Arrays;
import java.util.List;
/**
* @author GLoRy
* @date 2021/5/12 14:35
*/
@SpringBootTest
public class JpqlTest {
@Autowired
private CustomerRepository customerRepository;
/**
* JPQL 通过名字查询
*/
@Test
void testFindByName() {
Customer customer = customerRepository.findByName("顾客002");
System.out.println(customer);
}
/**
* JPQL 通过id和名字查询
*/
@Test
void testFindByIdAndName() {
Customer customer = customerRepository.findByIdAndName(2L, "顾客002");
System.out.println(customer);
}
/**
* JPQl 通过id更新名称
*
* @Transactional 进行更新或者删除操作需要加上事务注解,默认执行之后回滚事务
* @Rollback :设置是否自动回滚
*/
@Test
@Transactional
@Rollback(value = false)
void updateCustomerNameById() {
customerRepository.updateCustomerNameById(6L, "顾客0006");
}
/**
* 使用SQL查询全部
*/
@Test
void useSqlFindAll() {
List<Object[]> lists = customerRepository.useSqlFindAll();
for (Object[] list : lists) {
System.out.println(Arrays.toString(list));
}
}
/**
* 使用SQL根据姓名模糊查询
*/
@Test
void useSqlFindLikeName() {
List<Object[]> lists = customerRepository.useSqlFindLikeName("%000%");
for (Object[] list : lists) {
System.out.println(Arrays.toString(list));
}
}
}
方法名称规则查询代码:
package com.springdata;
import com.springdata.dao.CustomerRepository;
import com.springdata.entity.Customer;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
/**
* 测试方法命名规则查询
*/
@SpringBootTest
class NamingRulesTest {
@Autowired
private CustomerRepository customerRepository;
/**
* 通过姓名查询
*/
@Test
void findByCustomName() {
Customer customer = customerRepository.findByCustomName("顾客002");
System.out.println(customer);
}
/**
* 模糊匹配查询
*/
@Test
void testFindByCustomNameLike() {
List<Customer> customers = customerRepository.findByCustomNameLike("顾客%");
for (Customer customer : customers) {
System.out.println(customer);
}
}
/**
* 使用客户名称模糊匹配和来源精确匹配的查询
*/
@Test
void testFindByCustomNameLikeAndCustomSource() {
Customer customer = customerRepository.findByCustomNameLikeAndCustomSource("顾客%", "南京");
System.out.println(customer);
}
}
Specifications动态查询
方法:
//查询单个对象
Optional<T> findOne(@Nullable Specification<T> var1);
//查询全部
List<T> findAll(@Nullable Specification<T> var1);
/*
* 查询全部并分页
* Pageable:分页参数
* 返回值:分页PageBean(spring data jpa提供)
*/
Page<T> findAll(@Nullable Specification<T> var1, Pageable var2);
/*
* 查询全部并排序
* Sort:分页参数
*/
List<T> findAll(@Nullable Specification<T> var1, Sort var2);
//统计查询
long count(@Nullable Specification<T> var1);
详细代码测试以及注释:
package com.springdata;
import com.springdata.dao.CustomerRepository;
import com.springdata.entity.Customer;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import javax.persistence.criteria.*;
import java.util.List;
import java.util.Optional;
/**
* Specification测试
*
* @author GLoRy
* @date 2021/5/13 13:48
*/
@SpringBootTest
public class SpecificationTest {
@Autowired
private CustomerRepository customerRepository;
/**
* 根据条件,查询单个对象
*/
@Test
void testSpecification1() {
/*
* 匿名内部类
* 自定义查询条件
* 1. 实现Specification<T>接口
* 2. 实现toPredicate()方法(构造查询条件)
* 3. 需要借助方法中的两个参数(
* Root:获取需要查询的属性
* CriteriaBuilder:构造查询条件,内部封装了如(模糊匹配,精准匹配等)
* )
* 例如:根据客户名称查询
* 查询条件
* 1. 查询方式:
* criteriaBuilder对象
* 2. 比较的属性名称
* root对象中
*/
Specification<Customer> specification = (root, criteriaQuery, criteriaBuilder) -> {
//1.获取比较的属性
Path<Object> customName = root.get("customName");
/*
* 2.构造查询条件
* 第一个参数:需要比较的属性(path对象)
* 第二个参数:当前需要比较的取值
*/
return criteriaBuilder.equal(customName, "顾客002");
};
Optional<Customer> customer = customerRepository.findOne(specification);
System.out.println(customer);
}
/**
* 多条件查询
* 根据客户名和客户所属行业查询
*/
@Test
void testSpecification2() {
/*
* root:获取属性(客户名称,客户行业)
* criteriaBuilder:构造查询
* 1.构造客户名的精准匹配查询
* 2.构造所属行业的精准匹配查询
* 3.将上面两个查询联系起来
* */
Specification<Customer> specification = (root, criteriaQuery, criteriaBuilder) -> {
Path<Object> customName = root.get("customName");//客户名称
Path<Object> customIndustry = root.get("customIndustry");//所属行业
Predicate predicate1 = criteriaBuilder.equal(customName, "顾客003");
Predicate predicate2 = criteriaBuilder.equal(customIndustry, "IT行业");
/*
* 将多个查询条件组合到一起
* 满足条件一且满足条件二 “与”关系 and
* 满足条件一或满足条件二 “或”关系 or
*/
return criteriaBuilder.and(predicate1, predicate2);
};
Optional<Customer> customer = customerRepository.findOne(specification);
System.out.println(customer);
}
/**
* 根据客户名称模糊查询,返回客户集合
* 查询属性:客户名称
* 查询方式:模糊查询
* equal:直接得到path对象,然后进行比较即可
* like,gt,ge,le,lt等等:得到path对象,根据path指定比较的参数类型,再进行比较 path.as(类型的字节码对象)
*/
@Test
void testSpecification3() {
Specification<Customer> specification = (root, criteriaQuery, criteriaBuilder) -> {
Path<Object> customName = root.get("customName");
return criteriaBuilder.like(customName.as(String.class), "顾客%");
};
List<Customer> customerList = customerRepository.findAll(specification);
for (Customer customer : customerList) {
System.out.println(customer);
}
}
/**
* 查询并排序,需要调用构造方法实例化对象
* (ASC升序,DESC倒序)
*/
@Test
void testSpecificationSort() {
Specification<Customer> specification = (root, criteriaQuery, criteriaBuilder) -> {
Path<Object> customName = root.get("customName");
Predicate predicate = criteriaBuilder.like(customName.as(String.class), "顾客%");
return criteriaQuery.where(predicate).orderBy(criteriaBuilder.desc
(root.get("id"))).getRestriction();
};
List<Customer> customerList = customerRepository.findAll(specification);
for (Customer customer : customerList) {
System.out.println(customer);
}
}
/**
* 分页查询
* Specification;查询条件
* Pageable:分页参数(当前页码,每页查询条数)
* findAll(Specification,Pageable) 条件分页
* findAll(Specification) 无条件分页
* 返回:Page对象(spring data jpa封装好的PageBean,可以获取数据列表,总条数等等)
*/
@Test
void testPageSpecification() {
/*
* PageRequest对象是Pageable的实现类
* 创建PageRequest,需要调用它的构造方法传入参数
* 第一个参数:当前查询的页数(从0开始)
* 第二个参数:每页查询的数量
*/
//两种方式构建排序对象
Sort sort = Sort.by(Sort.Direction.DESC, "id");
Sort.Order order = new Sort.Order(Sort.Direction.DESC, "id");
//PageRequest传入参数,不带排序
PageRequest pageRequest = PageRequest.of(0, 2);
//PageRequest传入参数,排序
PageRequest pageRequest1 = PageRequest.of(0, 2, sort);
PageRequest pageRequest2 = PageRequest.of(0, 2, Sort.by(order));
//创建page对象
Page<Customer> page = customerRepository.findAll((Specification<Customer>) null, pageRequest);
Page<Customer> page1 = customerRepository.findAll((Specification<Customer>) null, pageRequest1);
Page<Customer> page2 = customerRepository.findAll((Specification<Customer>) null, pageRequest2);
System.out.println("不排序的总页数" + page.getTotalPages());
System.out.println("不排序的总条数" + page.getTotalElements());
System.out.println("第一种排序方式的总页数" + page1.getTotalPages());
System.out.println("第一种排序方式的总条数" + page1.getTotalElements());
System.out.println("第二种排序方式的总页数" + page2.getTotalPages());
System.out.println("第二种排序方式的总条数" + page2.getTotalElements());
//打印没有排序的客户信息列表
List<Customer> pageContent = page.getContent();
for (Customer customer : pageContent) {
System.out.println("非排序的客户信息:" + customer);
}
//打印降序排序的客户信息列表
List<Customer> pageContent1 = page1.getContent();
for (Customer customer1 : pageContent1) {
System.out.println("降序的客户信息:" + customer1);
}
}
/**
* 使用criteriaQuery方法
* 模糊查询,分页,排序
*/
@Test
void testPageOrder() {0
Page<Customer> page = customerRepository.findAll((Specification<Customer>) (root, criteriaQuery, criteriaBuilder) -> {
Path<Object> customIndustry = root.get("customIndustry");
//模糊匹配条件
Predicate predicate1 = criteriaBuilder.like(customIndustry.as(String.class), "IT%");
return criteriaQuery.where(predicate1).orderBy(criteriaBuilder.desc(root.get("id")))
.getRestriction();
}, PageRequest.of(0, 2));
System.out.println("总条数" + page.getTotalElements());
System.out.println("总页数" + page.getTotalPages());
//获取查询内容
List<Customer> pageContent = page.getContent();
for (Customer customer : pageContent) {
System.out.println(customer);
}
}
}
8. 多表之间的关系和操作步骤
多表之间的关系
- 一对一:在实际的开发中应用不多,因为一对一可以创建成一张表。
- 一对多:
- “一”的一方:主表
- “多”的一方:从表
- 外键:需要从“多”的一方建立外键,取值于主表的主键
- 例如:老师和学生
- 多对多:
- 中间表:最少由两个字段组成,两个字段作为外键指向两张表的主键,又组成了联合主键
- 例如:学生和课程
实体类中的关系
- 包含关系:可以通过实体类中的包含关系描述表关系
- 继承关系
分析步骤
- 明确了解表关系
- 确定表关系(描述 外键|中间表)
- 编写实体类,在实体类中描述表关系(包含关系)
- 配置映射关系
一对多
- 案例:客户和联系人
- 客户:代表一家公司 一个客户可以有多个联系人
- 联系人:公司的员工 一个联系人从属于一家公司
- 分析:
- 表关系:一对多
2. 表关系:- 主表:客户表
- 从表:联系人表,在从表添加外键
- 编写实体类,描述包含关系
- 客户:在客户的实体类中包含一个联系人的集合
- 联系人:在联系人的实体类中包含一个客户的对象
- 配置映射关系
- 使用JPA注解配置一对多映射关系
- 表关系:一对多
- 级联:
- 级联操作
- 需要区分操作主体
- 需要在操作主体的实体类上添加级联属性(需要添加到多表映射关系的注解上)
- cascade(配置级联)
- 操作一个对象的同时操作它的关联对象,如级联添加,删除,更新等
- 级联添加:当保存一个主表数据的同时保存从表数据
- 级联删除:当删除一个主表数据的同时删除从表的所有关联数据
- 级联操作
详细代码以及注释:
实体类代码:
package com.springdata.multitablejpa.entity;
import javax.persistence.*;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import static sun.management.snmp.jvminstr.JvmThreadInstanceEntryImpl.ThreadStateMap.Byte1.other;
/**
* 客户(主表)
*
* @author GLoRy
* @date 2021/5/13 21:39
*/
@Entity
@Table(name = "t_customer")
public class MultiCustomer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "customer_id")
private Long customerId;
@Column(name = "customer_name")
private String customerName;
@Column(name = "customer_source")
private String customerSource;
@Column(name = "customer_industry")
private String customerIndustry;
@Column(name = "customer_level")
private String customerLevel;
@Column(name = "customer_address")
private String customerAddress;
@Column(name = "customer_phone")
private String customerPhone;
/**
* 配置客户和联系人之间的关系(一对多)
* 在客户实体类上(”主表“)添加外键配置,所以具备了维护外键的作用
* 使用注解形式配置
* 1.声明关系
* 2.配置(外键|中间表)
*
* @OneToMany(targetEntity = MultiContact.class)
* @JoinColumn(name = "contact_customer_id", referencedColumnName = "customer_id")
* @OneToMany :配置一对多关系
* targetEntity :对方对象的字节码对象
* @JoinColumn :配置外键
* name:外键字段名称
* referencedColumnName:参照的主表主键字段名称
* ---------------------------------------------------------
* @OneToMany(mappedBy = "customer", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
* 放弃外键维护权,mapperBy的取值为对方配置关系的属性名称
* cascade = CascadeType.ALL 设置级联类型,例如All(所有),MERGE(更新),PERSIST(保存),REMOVE(删除)
* fetch:配置关联对象的加载方式,EAGER(立即加载),LAZY(延迟加载)
*/
@OneToMany(mappedBy = "customer", cascade = CascadeType.ALL)
private Set<MultiContact> multiContacts = new HashSet<>();
public Set<MultiContact> getMultiContacts() {
return multiContacts;
}
public void setMultiContacts(Set<MultiContact> multiContacts) {
this.multiContacts = multiContacts;
}
public Long getCustomerId() {
return customerId;
}
public void setCustomerId(Long customerId) {
this.customerId = customerId;
}
public String getCustomerName() {
return customerName;
}
public void setCustomerName(String customerName) {
this.customerName = customerName;
}
public String getCustomerSource() {
return customerSource;
}
public void setCustomerSource(String customerSource) {
this.customerSource = customerSource;
}
public String getCustomerIndustry() {
return customerIndustry;
}
public void setCustomerIndustry(String customerIndustry) {
this.customerIndustry = customerIndustry;
}
public String getCustomerLevel() {
return customerLevel;
}
public void setCustomerLevel(String customerLevel) {
this.customerLevel = customerLevel;
}
public String getCustomerAddress() {
return customerAddress;
}
public void setCustomerAddress(String customerAddress) {
this.customerAddress = customerAddress;
}
public String getCustomerPhone() {
return customerPhone;
}
public void setCustomerPhone(String customerPhone) {
this.customerPhone = customerPhone;
}
@Override
public String toString() {
return "MultiCustomer{" +
"customerId=" + customerId +
", customerName='" + customerName + '\'' +
", customerSource='" + customerSource + '\'' +
", customerIndustry='" + customerIndustry + '\'' +
", customerLevel='" + customerLevel + '\'' +
", customerAddress='" + customerAddress + '\'' +
", customerPhone='" + customerPhone + '\'' +
'}';
}
}
package com.springdata.multitablejpa.entity;
import javax.persistence.*;
import java.util.Objects;
/**
* 联系人(从表)
*
* @author GLoRy
* @date 2021/5/13 21:43
*/
@Entity
@Table(name = "t_contact")
public class MultiContact {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "contact_id")
private Long contactId;
@Column(name = "contact_name")
private String contactName;
@Column(name = "contact_gender")
private String contactGender;
@Column(name = "contact_phone")
private String contactPhone;
@Column(name = "contact_mobile")
private String contactMobile;
@Column(name = "contact_email")
private String contactEmail;
@Column(name = "contact_position")
private String contactPosition;
@Column(name = "contact_remark")
private String contactRemark;
/**
* 配置联系人到客户的多对一关系
* 配置外键的过程,配置到了多的一方,就会在多的一方维护外键
* 注解形式配置多对一
* 1.配置表关系
* 2.配置"外键|中间表"
*
* @ManyToOne :配置多对一关系
* targetEntity : 配置对方对象的实体类字节码
*/
@ManyToOne(targetEntity = MultiCustomer.class,fetch = FetchType.LAZY)
@JoinColumn(name = "contact_customer_id", referencedColumnName = "customer_id")
private MultiCustomer customer;
public MultiCustomer getCustomer() {
return customer;
}
public void setCustomer(MultiCustomer customer) {
this.customer = customer;
}
public Long getContactId() {
return contactId;
}
public void setContactId(Long contactId) {
this.contactId = contactId;
}
public String getContactName() {
return contactName;
}
public void setContactName(String contactName) {
this.contactName = contactName;
}
public String getContactGender() {
return contactGender;
}
public void setContactGender(String contactGender) {
this.contactGender = contactGender;
}
public String getContactPhone() {
return contactPhone;
}
public void setContactPhone(String contactPhone) {
this.contactPhone = contactPhone;
}
public String getContactMobile() {
return contactMobile;
}
public void setContactMobile(String contactMobile) {
this.contactMobile = contactMobile;
}
public String getContactEmail() {
return contactEmail;
}
public void setContactEmail(String contactEmail) {
this.contactEmail = contactEmail;
}
public String getContactPosition() {
return contactPosition;
}
public void setContactPosition(String contactPosition) {
this.contactPosition = contactPosition;
}
public String getContactRemark() {
return contactRemark;
}
public void setContactRemark(String contactRemark) {
this.contactRemark = contactRemark;
}
@Override
public String toString() {
return "MultiContact{" +
"contactId=" + contactId +
", contactName='" + contactName + '\'' +
", contactGender='" + contactGender + '\'' +
", contactPhone='" + contactPhone + '\'' +
", contactMobile='" + contactMobile + '\'' +
", contactEmail='" + contactEmail + '\'' +
", contactPosition='" + contactPosition + '\'' +
", contactRemark='" + contactRemark + '\'' +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
MultiContact that = (MultiContact) o;
return customer.equals(that.customer);
}
@Override
public int hashCode() {
return Objects.hash(customer);
}
}
DAO接口代码:
package com.springdata.multitablejpa.dao;
import com.springdata.multitablejpa.entity.MultiCustomer;
import org.springframework.data.jpa.repository.support.JpaRepositoryImplementation;
/**
* @author GLoRy
* @date 2021/5/13 21:59
*/
public interface MultiCustomerRepository extends JpaRepositoryImplementation<MultiCustomer, Long> {
}
package com.springdata.multitablejpa.dao;
import com.springdata.multitablejpa.entity.MultiContact;
import org.springframework.data.jpa.repository.support.JpaRepositoryImplementation;
/**
* @author GLoRy
* @date 2021/5/13 21:59
*/
public interface MultiContactRepository extends JpaRepositoryImplementation<MultiContact, Long> {
}
测试代码:
package com.springdata.multitablejpa.onetomany;
import com.springdata.multitablejpa.dao.MultiContactRepository;
import com.springdata.multitablejpa.dao.MultiCustomerRepository;
import com.springdata.multitablejpa.entity.MultiContact;
import com.springdata.multitablejpa.entity.MultiCustomer;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.Rollback;
import org.springframework.transaction.annotation.Transactional;
/**
* 一对多测试
*
* @author GLoRy
* @date 2021/5/14 9:48
*/
@SpringBootTest
public class OneToManyTest {
@Autowired
private MultiCustomerRepository multiCustomerRepository;
@Autowired
private MultiContactRepository multiContactRepository;
/**
* 保存一个客户和一个联系人
*/
@Test
@Transactional
@Rollback(value = false)
void testSave1() {
MultiCustomer multiCustomer = new MultiCustomer();
multiCustomer.setCustomerName("公司1");
MultiContact multiContact = new MultiContact();
multiContact.setContactName("张三");
/*
* 配置客户到联系人的关系(一对多)
* 客户:执行了两条insert语句和一条更新语句(更新外键)
* 由于配置了对象的主从关系,主表对象可以对外键进行维护
*/
multiCustomer.getMultiContacts().add(multiContact);
multiCustomerRepository.save(multiCustomer);
multiContactRepository.save(multiContact);
}
/**
* 保存一个客户和一个联系人
*/
@Test
@Transactional
@Rollback(value = false)
void testSave2() {
MultiCustomer multiCustomer = new MultiCustomer();
multiCustomer.setCustomerName("公司1");
MultiContact multiContact = new MultiContact();
multiContact.setContactName("张三");
/*
* 配置联客人到客户的关系(多对一)
* 只执行了两条insert语句
* 由于配置了联系人到客户的映射关系,所以可以直接维护外键
*/
multiContact.setCustomer(multiCustomer);
multiCustomerRepository.save(multiCustomer);
multiContactRepository.save(multiContact);
}
/**
* 保存一个客户和一个联系人
*/
@Test
@Transactional
@Rollback(value = false)
void testSave3() {
MultiCustomer multiCustomer = new MultiCustomer();
multiCustomer.setCustomerName("公司1");
MultiContact multiContact = new MultiContact();
multiContact.setContactName("张三");
/*
* 两种关系都配置
* 由于配置了多对一的关联关系,当保存的时候就已经对外键赋值
* 由于配置了一对多的关联关系,会发送update语句
* 会有一条多余的update语句,因为“一”的一方可以维护外键
* 解决方案:只需在“一”的一方放弃维护权即可
*/
multiContact.setCustomer(multiCustomer);
multiCustomer.getMultiContacts().add(multiContact);
multiCustomerRepository.save(multiCustomer);
multiContactRepository.save(multiContact);
}
/**
* 级联添加
* 需要在操作主体的实体类上,配置cascade属性
*/
@Test
@Transactional
@Rollback(value = false)
void testCascadeAdd() {
MultiCustomer multiCustomer = new MultiCustomer();
multiCustomer.setCustomerName("公司2");
MultiContact multiContact = new MultiContact();
multiContact.setContactName("李四");
multiContact.setCustomer(multiCustomer);
multiCustomer.getMultiContacts().add(multiContact);
multiCustomerRepository.save(multiCustomer);
}
/**
* 级联删除
* 案例:删除id为1客户的同时删除所有对应联系人
*/
@Test
@Transactional
@Rollback(value = false)
void testCascadeRemove() {
//1. 查询客户
MultiCustomer customer = multiCustomerRepository.getOne(1L);
//2. 删除客户
multiCustomerRepository.delete(customer);
}
}
多对多
-
用户和角色(多对多)
-
分析:
-
表关系:多对多
-
确定表关系:中间表
-
编写实体类:
- 用户:包含角色的集合
- 角色:包含用户的集合
-
配置映射关系:
- 使用JPA注解配置多对多映射关系
-
详细代码以及注释:
实体类代码:
package com.springdata.multitablejpa.entity;
import javax.persistence.*;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
/**
* 多对多 角色实体
*
* @author GLoRy
* @date 2021/5/14 13:03
*/
@Entity
@Table(name = "t_role")
public class RoleMany {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "role_id")
private Long roleId;
@Column(name = "role_name")
private String roleName;
/**
* 配置角色到用户的多对多关系
* 被动的一方,比如角色放弃维护权
*
* @ManyToMany(targetEntity = UserMany.class)
* @JoinTable(name = "t_user_role",
* joinColumns = {@JoinColumn(name = "t_role_id", referencedColumnName = "role_id")},
* inverseJoinColumns = {@JoinColumn(name = "t_user_id", referencedColumnName = "user_id")}
* )
*/
@ManyToMany(mappedBy = "roles")
private Set<UserMany> users = new HashSet<>();
public Set<UserMany> getUsers() {
return users;
}
public void setUsers(Set<UserMany> users) {
this.users = users;
}
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;
}
@Override
public String toString() {
return "RoleMany{" +
"roleId=" + roleId +
", roleName='" + roleName + '\'' +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
RoleMany roleMany = (RoleMany) o;
return users.equals(roleMany.users);
}
@Override
public int hashCode() {
return Objects.hash(users);
}
}
package com.springdata.multitablejpa.entity;
import javax.persistence.*;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
/**
* 多对多 用户实体
*
* @author GLoRy
* @date 2021/5/14 13:02
*/
@Entity
@Table(name = "t_user")
public class UserMany {
@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. 配置中间表(包含2个外键)
*
* @JoinTable name:中间表的名称
* joinColumns;当前对象在中间表中的外键
* inverseJoinColumns;对方对象在中间表的外键
*/
@ManyToMany(targetEntity = RoleMany.class, cascade = CascadeType.ALL)
@JoinTable(name = "t_user_role",
joinColumns = {@JoinColumn(name = "t_user_id", referencedColumnName = "user_id")},
inverseJoinColumns = {@JoinColumn(name = "t_role_id", referencedColumnName = "role_id")}
)
private Set<RoleMany> roles = new HashSet<>();
public Set<RoleMany> getRoles() {
return roles;
}
public void setRoles(Set<RoleMany> roles) {
this.roles = roles;
}
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "UserMany{" +
"userId=" + userId +
", userName='" + userName + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
UserMany userMany = (UserMany) o;
return roles.equals(userMany.roles);
}
@Override
public int hashCode() {
return Objects.hash(roles);
}
}
DAO接口代码:
package com.springdata.multitablejpa.dao;
import com.springdata.multitablejpa.entity.UserMany;
import org.springframework.data.jpa.repository.support.JpaRepositoryImplementation;
/**
* 用户数据库接口
*
* @author GLoRy
* @date 2021/5/14 13:09
*/
public interface UserRepository extends JpaRepositoryImplementation<UserMany, Long> {
}
package com.springdata.multitablejpa.dao;
import com.springdata.multitablejpa.entity.RoleMany;
import org.springframework.data.jpa.repository.support.JpaRepositoryImplementation;
/**
* 角色数据库接口
*
* @author GLoRy
* @date 2021/5/14 13:09
*/
public interface RoleRepository extends JpaRepositoryImplementation<RoleMany, Long> {
}
测试代码:
package com.springdata.multitablejpa.manytomany;
import com.springdata.multitablejpa.dao.RoleRepository;
import com.springdata.multitablejpa.dao.UserRepository;
import com.springdata.multitablejpa.entity.RoleMany;
import com.springdata.multitablejpa.entity.UserMany;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.Rollback;
import org.springframework.transaction.annotation.Transactional;
/**
* 多对多测试
*/
@SpringBootTest
class ManyToManyTest {
@Autowired
private UserRepository userRepository;
@Autowired
private RoleRepository roleRepository;
/**
* 保存一个用户和角色
* 多对多放弃维护权:被动的一放弃
*/
@Test
@Transactional
@Rollback(value = false)
void testSave() {
UserMany user = new UserMany();
user.setUserName("张三");
user.setAge(18);
RoleMany role = new RoleMany();
role.setRoleName("Java程序员");
//配置用户到角色关系,可以对中间表中的关系进行维护
user.getRoles().add(role);
//配置角色到用户关系
role.getUsers().add(user);
userRepository.save(user);
roleRepository.save(role);
}
/**
* 测试级联添加
* 测试:保存一个用户的同时保存用户的关联角色
*/
@Test
@Transactional
@Rollback(value = false)
void testCascadeSave() {
UserMany user = new UserMany();
user.setUserName("张三");
user.setAge(18);
RoleMany role = new RoleMany();
role.setRoleName("爪哇程序员");
//配置用户到角色关系,可以对中间表中的关系进行维护
user.getRoles().add(role);
//配置角色到用户关系
role.getUsers().add(user);
userRepository.save(user);
}
/**
* 测试级联删除
* 测试:删除id为1的用户的同时删除用户的关联角色
*/
@Test
@Transactional
@Rollback(value = false)
void testCascadeDelete() {
//查询id为1的用户
UserMany user = userRepository.getOne(1L);
//删除用户
userRepository.delete(user);
}
}
多表查询
- 对象导航查询
- 查询一个对象的同时,通过此对象查询它的关联对象
- 案例:一对多形式,比较有代表性(使用上面一对多的例子,客户和联系人)
- “一”方查询“多”方:默认使用延迟加载
- “多”方查询“一”方:默认使用立即加载
测试代码及注释示例:
package com.springdata.multitablejpa.query;
import com.springdata.multitablejpa.dao.MultiContactRepository;
import com.springdata.multitablejpa.dao.MultiCustomerRepository;
import com.springdata.multitablejpa.entity.MultiContact;
import com.springdata.multitablejpa.entity.MultiCustomer;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;
import java.util.Set;
/**
* 对象导航查询测试
* 查询一个对象的同时,通过此对象查询它的关联对象
*
* @author GLoRy
* @date 2021/5/14 14:04
*/
@SpringBootTest
public class ObjectQueryTest {
/*
* 对象导航查询:
* 一对多默认使用的延迟加载形式查询,调用get方法不会立即加载,而是在使用关联对象的时候才会查询
* 多对一默认使用的立即加载形式查询
* 解决方案:
* 修改配置,将延迟加载改为(立即|延迟)加载
* fetch 需要配置到多表映射关系的注解上
*/
@Autowired
private MultiCustomerRepository multiCustomerRepository;
@Autowired
private MultiContactRepository multiContactRepository;
/**
* 查询id为1的客户,并导航查询所有的联系人
*
* @Transactional :解决no session的问题
*/
@Test
@Transactional
void testQuery1() {
//查询id为1的客户
MultiCustomer customer = multiCustomerRepository.getOne(1L);
//对象导航查询
Set<MultiContact> contacts = customer.getMultiContacts();
for (MultiContact contact : contacts) {
System.out.println(contact);
}
}
/**
* 从联系人导航查询所属客户
*/
@Test
@Transactional
void testQuery2() {
MultiContact contact = multiContactRepository.getOne(2L);
//对象导航查询
MultiCustomer customer = contact.getCustomer();
System.out.println(customer);
}
}