JPA的使用
1. ORM概述
ORM(Object-Relational Mapping) 表示对象关系映射。在面向对象的软件开发中,通过ORM,就可以把对象映射到关系型数据库中。只要有一套程序能够做到建立对象与数据库的关联,操作对象就可以直接操作数据库数据,就可以说这套程序实现了ORM对象关系映射。
简单的说: ORM就是建立实体类和数据库表之间的关系,从而达到操作实体类就相当于操作数据库表的目的。
原因: 当实现一个应用程序时(不使用O/R Mapping),我们可能会写特别多数据访问层的代码,从数据库保存数据、修改数据、删除数据,而这些代码都是重复的。而使用ORM则会大大减少重复性代码。对象关系映射(Object Relational Mapping,简称ORM),主要实现程序对象到关系数据库数据的映射。
2. hibernate与JPA的概述
Hibernate是一个开放源代码的对象关系映射框架,它对JDBC进行了非常轻量级的对象封装,它将POJO与数据库表建立映射关系,是一个全自动的orm框架,hibernate可以自动生成SQL语句,自动执行,使得Java程序员可以随心所欲的使用对象编程思维来操纵数据库。
JPA的全称是Java Persistence API, 即Java 持久化API,是SUN公司推出的一套基于ORM的规范,内部是由一系列的接口和抽象类构成。
JPA通过JDK 5.0注解描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中。
2.1 Jpa的优势
-
标准化
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 中能够支持面向对象的高级特性,如类之间的继承、多态和类之间的复杂关系,这样的支持能够让开发者最大限度的使用面向对象的模型设计企业应用,而不需要自行处理这些特性在关系数据库的持久化。
2.2 JPA与hibernate的关系
一图胜千言
2.3 Spring Data JPA 与 JPA和hibernate之间的关系
- JPA是一套规范,内部是有接口和抽象类组成的。hibernate是一套成熟的ORM框架,而且Hibernate实现了JPA规范,所以也可以称hibernate为JPA的一种实现方式,我们使用JPA的API编程,意味着站在更高的角度上看待问题(面向接口编程)。
- Spring Data JPA是Spring提供的一套对JPA操作更加高级的封装,是在JPA规范下的专门用来进行数据持久化的解决方案。
3. Spring Boot整合JPA的入门案例
搭建环境的过程
1.创建maven工程导入坐标
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>maven_test</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.3.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<version>2.3.10.RELEASE</version>
</dependency>
<!--jpa依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<version>2.3.10.RELEASE</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>2.3.10.RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.18</version>
</dependency>
</dependencies>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
</project>
2.需要配置jpa的核心配置文件
- 在 application.xml 中配置:数据库连接信息、⾃动创建表结构的设置,当使⽤mysql时:
spring.datasource.url=jdbc:mysql://localhost:3306/jpa?useUnicode=true&characterEncoding=utf-8&useSSL=false
spring.datasource.username=root
spring.datasource.password=110120
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.jpa.properties.hibernate.hbm2ddl.auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.hbm2ddl.auto
是hibernate的配置属性,其主要作⽤是:⾃动创
建、更新、验证数据库表结构。该参数的⼏种配置如下:
- create :每次加载hibernate时都会删除上⼀次的⽣成的表,然后根据你的model类再重新来⽣成
新表,哪怕两次没有任何改变也要这样执⾏,这就是导致数据库表数据丢失的⼀个重要原因。 - create-drop :每次加载hibernate时根据model类⽣成表,但是sessionFactory⼀关闭,表就⾃动
删除。 - update :最常⽤的属性,第⼀次加载hibernate时根据model类会⾃动建⽴起表的结构(前提是先
建⽴好数据库),以后加载hibernate时根据model类⾃动更新表结构,即使表结构改变了但表中
的⾏仍然存在不会删除以前的⾏。要注意的是当部署到服务器后,表结构是不会被⻢上建⽴起来
的,是要等应⽤第⼀次运⾏起来后才会。 - validate :每次加载hibernate时,验证创建数据库表结构,只会和数据库中的表进⾏⽐较,不
会创建新表,但是会插⼊新值。
3.编写实体类
创建⼀个User实体,包含id(主键)、name(姓名)、age(年龄)属性,通过ORM框架其会被映射
到数据库表中,由于配置了 hibernate.hbm2ddl.auto ,在应⽤启动的时候框架会⾃动去数据库中创建
对应的表。
@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
@Id
@GeneratedValue
private Integer id;
@Column(nullable = false)
private String name;
@Column(nullable = false)
private Integer age;
public User(String name,Integer age){
this.name = name;
this.age = age;
}
}
@Entity
作用:指定当前类是实体类。
@Table
作用:指定实体类和表之间的对应关系。
属性:
name:指定数据库表的名称
@Id
作用:指定当前字段是主键。
@GeneratedValue
作用:指定主键的生成方式。
属性:
strategy :指定主键生成策略。
参数:
GenerationType.IDENTITY(主要使用) :自增,mysql,底层数据库必须支持自动增长(底层数据库支持的自动增长方式,对id自增)
GenerationType.SEQUENCE : 序列,oracle,底层数据库必须支持序列
GenerationType.TABLE : jpa提供的一种机制,通过一张数据库表的形式帮助我们完成主键自增
GenerationType.AUTO : 由程序自动的帮助我们选择主键生成策略
@Column
作用:指定实体类属性和数据库表之间的对应关系
属性:
name:指定数据库表的列名称。
unique:是否唯一
nullable:是否可以为空
inserttable:是否可以插入
updateable:是否可以更新
columnDefinition: 定义建表时创建此列的DDL
secondaryTable: 从表名。如果此列不建在主表上(默认建在主表),该属性定义该列所在从表的名字搭建开发环境
4.创建数据访问接口
在Spring Data JPA中,对于定义符合规范的Dao层接口,我们只需要遵循以下几点就可以了:
- 1.创建一个Dao层接口,并实现JpaRepository和JpaSpecificationExecutor
- 2.提供相应的泛型
-
a. JpaRepository<操作的实体类类型,实体类中主键属性的类型> :封装了基本CRUD操作
-
b. JpaSpecificationExecutor<操作的实体类类型> : 封装了复杂查询(分页)
@Component
public interface UserRepository extends JpaRepository<User,Integer>, JpaSpecificationExecutor<User> {
User findByName(String name);
User findByNameAndAge(String name,Integer age);
@Query("from User u where u.name=:name")
User findUser(@Param("name") String name);
}
在Spring-data-jpa中,只需要编写类似上⾯这样的接⼝就可实现数据访问,不需要编写接⼝实现类。UserRepository继承⾃ JpaRepository ,通过查看 JpaRepository 接⼝的API⽂档,可以看到该接⼝本身已经实现了创建(save)、更新(save)、删除(delete)、查询(findAll、findOne)等基本操作的函数,因此对于这些基础操作的数据访问就不需要开发者再⾃⼰定义。
在上例中:
- User findByName(String name)
- User findByNameAndAge(String name, Integer age)
它们分别实现了按name查询User实体和按name和age查询User实体,可以看到我们这⾥没有任何类,SQL语句就完成了两个条件查询⽅法。这就是Spring-data-jpa的⼀⼤特性:通过解析⽅法名创建查
询。
注意: 除了通过解析⽅法名来创建查询外,它也提供通过使⽤@Query
注解来创建查询,您只需要编写JPQL语句,并通过类似:name
来映射@Param
指定的参数,就像UserRepositor接口中的第三个findUser函数⼀样。
JPQL全称Java Persistence Query Language
基于首次在EJB2.0中引入的EJB查询语言(EJB QL),Java持久化查询语言(JPQL)是一种可移植的查询语言,旨在以面向对象表达式语言的表达式,将SQL语法和简单查询语义绑定在一起·使用这种语言编写的查询是可移植的,可以被编译成所有主流数据库服务器上的SQL。
其特征与原生SQL语句类似,并且完全面向对象,通过类名和属性访问,而不是表名和表的属性。
5.编写Controller
@RestController
public class HelloWorldController {
@Autowired
private UserRepository userRepository;
@GetMapping("/")
public String sayHello(){
userRepository.save(new User("xiaomi",10)); //存数据
int x = userRepository.findByName("aa").getAge(); //查找名字为aa数据的年龄
return "hello world" + x;
}
}
6.启动并验证结果
- web端
- 数据库
4. Spring Data JPA相关API
在Spring Data JPA中,对于定义符合规范的Dao层接口,我们只需要遵循以下几点就可以了:
- 1.创建一个Dao层接口,并实现JpaRepository和JpaSpecificationExecutor
- 2.提供相应的泛型
-
a. JpaRepository<操作的实体类类型,实体类中主键属性的类型> :封装了基本CRUD操作
-
b. JpaSpecificationExecutor<操作的实体类类型> : 封装了复杂查询(分页)
@Component
public interface UserRepository extends JpaRepository<User,Integer>, JpaSpecificationExecutor<User> {
User findByName(String name);
User findByNameAndAge(String name,Integer age);
@Query("from User u where u.name=:name")
User findUser(@Param("name") String name);
}
4.1 基本CRUD操作
接着上述入门案例的操作
增
/**
- save :保存或者更新
- 根据传递的对象是否存在主键id,如果没有id主键属性----保存
- 存在id属性,根据id查询属性,查询到了才去更新数据
*/
@Test
public void testSave(){
User xiaomi = new User("xiaomi",10)
customerRepository.save(xiaomi);
}
删
@Test
public void testDelete(){
userRepository.delete(one);
userRepository.deleteById(2);
}
删除之前,也是先查询一下:
Hibernate: select user0_.id as id1_0_0_, user0_.age as age2_0_0_, user0_.name as name3_0_0_ from user user0_ where user0_.id=?
Hibernate: delete from user where id=?
- 为什么?
JPA的缓存机制,更新删除的时候JPA都需要先维护缓存才可以删除。如果你要直接删除,必须自己写EQL语句,和SQL差不多,不过那样有可能造成整个对象所有缓存失效。
改
@Test
public void testUpdate(){
User user = new User();
user.setId(2);
user.setName("Tom");
userRepository.save(user);
}
查
- 根据id从数据库查询
@Transactional : 保证getOne正常运行
findOne:它的底层 em.find() 表示立即加载
getOne: 它的底层 em.getReference 表示延迟加载 - 返回的是一个客户的动态代理对象
- 什么时候用,什么时候查询
@Test
public void testQuery(){
User one = userRepository.getOne(1);
Optional<User> byId = userRepository.findById(1);
List<User> all = userRepository.findAll();
}
4.2 为什么只写接口也能执行查询
- 通过JDKDynamicAopProxy创建动态代理对象
- 动态代理对象:SimpleJpaRepository,实现了JpaRepository接口
- 如findById():通过entityManager完成查询操作
- Jpa规范
- hibernate->数据库
4.3 count,exists
count:查询数据库中的数量
@Test
public void testQuery(){
long count = userRepository.count();
System.out.println(count);
}
exists:测试数据是否存在
测试:判断id为4的User是否存在
1. 可以查询以下id为4的User
如果值为空,代表不存在,如果不为空,代表存在
2. 判断数据库中id为4的User的数量
如果数量为0,代表不存在,如果大于0,代表存在
@Test
public void testExists(){
boolean exists = userRepository.existsById(1);
System.out.println(exists);
}
4.4 JPQL查询
jpql : jpa query language (jpq查询语言) 特点:语法或关键字和sql语句类似,查询的是类和类中的属性
-
需要将JPQL语句配置到接口方法上
-
特有的查询:需要在dao接口上配置方法
-
在新添加的方法上,使用注解的形式配置jpql查询语句
-
注解:@Query
jpql完成基本查询
- 指定参数
@Component
public interface UserRepository extends JpaRepository<User,Integer>, JpaSpecificationExecutor<User> {
@Query("from User u where u.name=:name")
User findUser(@Param("name") String name);
}
- 使用占位符
注意: 下面的操作中即便是一个?占位符,后面也要加索引数字(从1开始),不然会报错
@Query(value="from User where name = ?1")
public User findJpql(String name);
-
多占位符的赋值
制定索引位置
@Query(value = "from User where name = ?2 and id = ?1")
public User findCustNameAndId(Long id,String name);
jpql完成更新操作
注意:@Transactional ,添加事务的支持,默认执行结束后,回滚事务,下面的更新才会生效。
@Query(value = " update User set name = ?2 where id = ?1 ")
@Modifying
public void updateCustomer(long custId,String custName);
事务的位置
4.5 SQL语句的查询
特有的查询:需要在dao接口上配置方法,在新添加的方法上,使用注解的形式配置sql查询语句。
- 注解 : @Query
- value :jpql语句 | sql语句
- nativeQuery :false(使用jpql查询) | true(使用本地查询:sql查询),是否使用本地查询
//条件查询,因为是sql语句查询,没有映射实体类,所以返回类型写Object就行
@Query(value="select * from User where name like ?1",nativeQuery = true)
public List<Object [] > findSql(String name);
4.6 方法命名规则查询
-
顾名思义,方法命名规则查询就是根据方法的名字,就能创建查询。只需要按照Spring Data JPA提供的方法命名规则定义方法的名称,就可以完成查询工作。Spring Data JPA在程序执行的时候会根据方法名称进行解析,并自动生成查询语句进行查询。
-
按照Spring Data JPA 定义的规则,查询方法以findBy开头,涉及条件查询时,条件的属性用条件关键字连接,要注意的是:条件属性首字母需大写。框架在进行方法名解析时,会先把方法名多余的前缀截取掉,然后对剩下部分进行解析。
方法名的约定: 1.findBy + 属性名称:根据属性名称进行完成匹配的查询 findByName:条件查询(对象中的属性名(首字母大写) : 查询的条件) 2.findByNameLike : 模糊查询 3.多条件查询 findBy + 属性名 + “查询方式” + “多条件的连接符(and|or)” + 属性名 + “查询方式”
@Component
public interface UserRepository extends JpaRepository<User,Integer>, JpaSpecificationExecutor<User> {
User findByName(String name);
User findByNameAndAge(String name,Integer age);
List<User> findByNameLike(String name);
User findByNameLikeAndAge(String name,Integer age);//顺序要对应
}
5. JPA高级操作
5.1 Specifications动态查询
- 我们在查询某个实体的时候,给定的条件是不固定的,这时就需要动态构建相应的查询语句,在Spring Data JPA中可以通过
JpaSpecificationExecutor
接口查询。相比JPQL,其优势是类型安全,更加的面向对象。
public interface JpaSpecificationExecutor<T> {
//查询单个对象,spec:条件
Optional<T> findOne(@Nullable Specification<T> spec);
//查询全部,spec:条件
List<T> findAll(@Nullable Specification<T> spec);
//分页查询,pageable分页参数,Page:Sprringdata jpa提供的
Page<T> findAll(@Nullable Specification<T> spec, Pageable pageable);
//sort: 排序参数
List<T> findAll(@Nullable Specification<T> spec, Sort sort);
//按照条件的统计查询
long count(@Nullable Specification<T> spec);
}