JPA(java 持久层 API)
2.1 JPA的简单使用
2.1.1 简介
在过去,有很多针对Java的ORM框架,但是每一套框架都有自己的一套操作方法和规范,这就使得Java程序操作不同数据库时显得杂乱无章。于是乎,Sun公司推出了一套操作持久层(数据库)的规范(API)用于结束这种乱象,这套规范也就是JPA。
JPA(Java Persistence API,Java持久层API) 是Sun公司定义的一套基于ORM的接口规范,用于给Java程序操作数据库。JPA 通过 JDK 5.0 注解描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中 。在这之后很多ORM框架实现了JPA规范,其中最有名的有Hibernate、TopLink、JDO等。JPA 和 Hibernate 的关系就像 JDBC 和 JDBC 驱动的关系,JPA 是规范,Hibernate 除了作为 ORM 框架之外,它也是一种 JPA 实现。
2.1.2 优势
标准化
JPA 是 JCP 组织发布的 Java EE 标准之一,因此任何声称符合 JPA 标准的框架都遵循同 样的架构,提供相同的访问API,这保证了基于JPA开发的企业应用能够经过少量的修改就能够在 不同的 JPA 框架下运行。
容器级特性的支持
JPA 框架中支持大数据集、事务、并发等容器级事务,这使得 JPA 超越了简单持久化框架的 局限,在企业应用发挥更大的作用。
简单方便 JPA 的主要目标之一就是提供更加简单的编程模型:在 JPA 框架下创建实体和创建 Java 类一样简单,没有任何的约束和限制,只需要使用 javax.persistence.Entity 进行注释,JPA的 框架和接口也都非常简单,没有太多特别的规则和设计模式的要求,开发者可以很容易的掌握。 JPA 基于非侵入式原则设计,因此可以很容易的和其它框架或者容器集成
查询能力 JPA 的查询语言是面向对象而非面向数据库的,它以面向对象的自然语法构造查询语句,可以看成是 Hibernate HQL 的等价物。JPA 定义了独特的 JPQL(Java Persistence Query Language),JPQL 是 EJB QL 的一种扩展,它是针对实体的一种查询语言,操作对象是实体,而 不是关系数据库的表,而且能够支持批量更新和修改、JOIN、GROUP BY、HAVING 等通常只有 SQL 才能够提供的高级查询特性,甚至还能够支持子查询
高级特性 JPA 中能够支持面向对象的高级特性,如类之间的继承、多态和类之间的复杂关系。这样的支持能够让开发者最大限度的使用面向对象的模型设计企业应用,而不需要自行处理这些特性。 Spring Data JPA 是 Spring 基于 ORM 框架、JPA 规范的基础上封装的一套 JPA 应用框架,可使开发者用极简的代码即可实现对数据库的访问和操作。它提供了包括增删改查等在内的常用功能,且易于扩展!学习并使用 Spring Data JPA 可以极大提高开发效率! Spring Data JPA 让我们解脱了 DAO 层的操作,基本上所有 CRUD 都可以依赖于它来实现,在实际的工作工程中,推荐使用 Spring Data JPA + ORM(如:hibernate)完成操作,这样在切换不同的 ORM 框架时提供了极大的方便,同时也使数据库层操作更加简单,方便。
Spring Data JPA
Spring Data JPA 是 Spring 基于 ORM 框架、JPA 规范的基础上封装的一套 JPA 应用框架,可使开发者用极简的代码即可实现对数据库的访问和操作。它提供了包括增删改查等在内的常用功能,且易于扩展!学习并使用 Spring Data JPA 可以极大提高开发效率!
Spring Data JPA 让我们解脱了 DAO 层的操作,基本上所有 CRUD 都可以依赖于它来实现,在实际的 工作工程中,推荐使用 Spring Data JPA + ORM(如:hibernate)完成操作,这样在切换不同的 ORM 框架时提供了极大的方便,同时也使数据库层操作更加简单,方便解耦。
JPA、ORM(Hibernate /EclipseTop/OpenJPA /)、Spring Data JPA之间的区别
区别一:JPA是一个规范,提出了很多接口(就像jdbc一样),Hibernate这些实现了这些接口;
区别二:Spring Data JPA是在JPA的上面又做了一次封装和抽象,提出了更多的可用接口,但最后实现还是使用了Hibernate的JPA技术实现。可以在不写接口实现的情况下,完成对数据库的访问和操作,减少了大量的数据库操作方面的代码。
2.1.3 使用实列
依赖导入
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
配置文件
application.yml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
url: jdbc:mysql://localhost:3306/jpa_db?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=false
jpa:
properties:
hibernate:
dialect: org.hibernate.dialect.MySQL5InnoDBDialect
format_sql: true
show-sql: true
hibernate:
ddl-auto: update
编写对象类
@Entity
@Table(name = "customer")
@Data
public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;
@Column(name = "name")
private String name;
}
@Entity
@Table(name = "customer_address")
@Data
public class CustomerAddress {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;
@Column(name = "address")
private String address;
}
测试
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WSgamX5E-1666313786078)(C:\Users\MrLiu\Desktop\培训课程学习\笔记\SpringBoot笔记\img\a6.png)]
去数据库里查看字段desc customer
在一对多,多对多等关系中,都会有维护关系的一方和被维护的的一方,
2.1.4 新增实列
添加两个对象关联关系 ,分析可知道,一个用户可能对应多个地址,所以需要去建立一对多的关系
在Customer和AddressCustomer两个类中分别加入List 和 Customer两个属性
@Entity
@Table(name = "customer")
@Data
public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;
@Column(name = "name")
private String name;
//使用@OneToMany注释的mappedBy="customer"属性表明Customer是关系被维护端
@OneToMany(mappedBy = "customer")
private List<CustomerAddress> addresses;
}
@Entity
@Table(name = "customer_address")
@Data
public class CustomerAddress {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;
@Column(name = "address")
private String address;
//@ManyToOne和@JoinColumn来注释我要维护谁.
//name:从表的外键名,referencedColumnName:对应主表的主键字段。
@ManyToOne(targetEntity = Customer.class)
@JoinColumn(name = "c_id",referencedColumnName = "id")
private Customer customer;
}
编写新建接口CustomerRespository去继承JpaRepository<类,ID类型>
@Repository
public interface CustomerRepository extends JpaRepository<Customer,Long> {
}
编写测试
这是新增顾客
@SpringBootTest
@Slf4j
public class CustomerRepositoryTest {
@Autowired
private CustomerRepository customerRepository;
@Test
public void addTest(){
Customer customer=new Customer();
customer.setName("张三");
customerRepository.save(customer);
log.info("顾客{}",customer);
}
}
这是给顾客新增地址,
注意
新增地址的顾客必须是持久态的
@SpringBootTest
@Slf4j
public class AddressRepository {
@Autowired
private CustomerRepository customerRepository;
@Autowired
private CustomerAddressRepository customerAddressRepository;
@Test
public void addTest(){
//新增方法一
Customer customer=new Customer();
customer.setName("李四");
CustomerAddress customerAddress=new CustomerAddress();
customerAddress.setAddress("成都");
customerAddress.setCustomer(customer);
CustomerAddress customerAddress2=new CustomerAddress();
customerAddress2.setAddress("北京");
customerAddress2.setCustomer(customer);
//保存对象,使得对象由瞬时态转变为持久态
log.info("顾客{}",customer);
customerRepository.save(customer);
log.info("顾客{}",customer);
log.info("顾客地址1{}",customerAddress);
log.info("顾客地址2{}",customerAddress2);
customerAddressRepository.save(customerAddress);
customerAddressRepository.save(customerAddress2);
//新增方法二
Customer customer1=customerRepository.findById(3L).orElse(null);
CustomerAddress customerAddress3=new CustomerAddress();
customerAddress3.setCustomer(customer1);
customerAddress3.setAddress("上海");
customerAddressRepository.save(customerAddress3);
}
}
2.1.5 对象的三种状态
对象存在的三大状态有:瞬时态、持久态、游离态(托管态)
2.1.6 多表查询实列
##Customer类
@OneToMany(mappedBy = "customer",fetch = FetchType.EAGER)
private List<CustomerAddress> addresses;
##Test类
@Transactional
@Test
public void findByIdTest(){
Customer customer=customerRepository.findById(2L).orElse(null);
assert customer != null;
List<CustomerAddress> list=customer.getAddresses();
log.info("地址{}",list);
System.out.println(list);
log.info("顾客{}",customer);
}
在Customer表里加上fetch = FetchType.EAGER 或者 在测试表里加入测试类中的测试方法上加上@Transactional注解
原因:在联合查询时,他会默认不开启联合查询,也就是会优先单表查询,而且查询完后一个事务结束就会关闭session,这时查询customer的会话已经关闭了。所以就没有可用的session来查address。他们是两个事务,按照这个思路,只需要把两个事务加上即可,如果你有需要就需要开起联合事务,然后就可以联合去查询
测试
运行后会去联合查查询两张表
2.1.7 一对一删除实列
我们在删除前先需要知道主从表的关系,如果存在外键约束,我们要么先关闭外键再去删除表数据,要么就的先删从表数据在删主表数据;
如果直接删主表数据的话就会出现下面的错误
@Test
public void deleteTest(){
Customer customer=customerRepository.findById(3L).orElse(null);
log.info("顾客{}",customer);
customerRepository.delete(customer);
}
从上图错误信息可以看出,报错信息告诉我们,这个表有一个外键约束。
方法一
在jpa中并不支持关闭外键,所以修改删除代码,先从 从表 删除
@Test
public void deleteTest(){
Customer customer=customerRepository.findById(3L).orElse(null);
List<CustomerAddress> list=customerAddressRepository.findByCustomer(customer);
//根据查询到的从表中对应主表中所需删除的所有数据,在从表中去删除这些数据
log.info("地址列表:{}",list);
customerAddressRepository.deleteAll(list.stream().collect(Collectors.toList()));
//删除主表中所需删除数据
log.info("顾客{}",customer);
customerRepository.delete(customer);
}
注意
我们还需要去检查是否将维护关系关闭,不然主表会去查从表,而我们已经将从表中数据删除,所以会报错
@OneToMany(mappedBy = "customer",fetch = FetchType.EAGER)
private List<CustomerAddress> addresses;
这里将fetch = FetchType.EAGER删掉就可以了
方法二
通过开启级联操作,可以保持数据完整性,从而达到删除用户会自动去删除该用户残留的垃圾数据
//cascade = {CascadeType.ALL}级联操作属于数据库层面的支持,是为了保证数据的完整性。ALL是不合理的,我这里更好的用法是用REMOVE.
@OneToMany(mappedBy = "customer",cascade = {CascadeType.REMOVE})
private List<CustomerAddress> address
测试
//方法二,前提开启所有级联操作
Customer customer1=customerRepository.findById(7L).orElse(null);
customerRepository.delete(customer1);