# JPA学习
**Repository Path**: liao-zhenyuan/demo-study
## Basic Information
- **Project Name**: JPA学习
- **Description**: JPA的学习使用与熟悉
- **Primary Language**: Java
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 2
- **Forks**: 0
- **Created**: 2021-11-03
- **Last Updated**: 2024-01-03
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
**@Data**
> @Data注解是lombok.jar包下的注解,该注解通常用在实体bean上,不需要写出set和get方法,但是具备实体bean所具备的方法,简化编程提高变成速度。注意:项目中一定要引入lombok.jar
>
> @Data相当于@Getter @Setter @RequiredArgsConstructor @ToString @EqualsAndHashCode这5个注解的合集。
# JPA
## 1 简介
**JPA**全称Java Persistence API,通过JDK 5.0`注解`或`XML`描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中。
> 1. ORM映射元数据:JPA支持XML和注解两种元数据的形式,元数据描述对象和表之间的映射关系,框架据此将实体对象持久化到数据库表中;
>
> 如:@Entity、@Table、@Column、@Transient等注解。
>
> 2. JPA 的API:用来操作实体对象,执行CRUD操作,框架在后台替我们完成所有的事情,开发者从繁琐的JDBC和SQL代码中解脱出来。
>
> 如:entityManager.merge(T t);
>
> 3. JPQL查询语言:通过面向对象而非面向数据库的查询语言查询数据,避免程序的SQL语句紧密耦合。
>
> 如:from Student s where s.name = ?
>
> 但是,JPA仅仅是一种规范,也就是说JPA仅仅定义了一些接口,而接口是需要实现才能工作的。所以底层需要某种实现,而Hibernate就是实现了JPA接口的ORM框架。
**什么是spring data jpa?**
spirng data jpa是spring提供的一套简化JPA开发的框架,按照约定好的`【方法命名规则】`写dao层接口,就可以在不写接口实现的情况下,实现对数据库的访问和操作。同时提供了很多除了CRUD之外的功能,如分页、排序、复杂查询等等。
Spring Data JPA 可以理解为 JPA 规范的再次封装抽象,底层还是使用了 Hibernate 的 JPA 技术实现。

-----
【个人理解】使用**JPA**,可以做到开发人员不用关注于数据库的实现,只需要关注于Java实体类的编写,就可以在程序运行时,自动生成对应实体的数据库表
> 相对于传统不使用JPA,优点如下:
>
> - **大大减少对数据库的操作**
>
> - 优点:
> 1. 自动创建新表
> 2. 自动创建新字段
> 3. 自动修改字段类型
> - 缺点:
> 1. 不会自动删除表
> 2. 不会自动删除字段
> 3. 自动创建的新字段只能是在最后
>
> 【针对缺点的建议】:定期把数据库清空(删除所有表),然后启动项目,让hibernate自动创建表结构和索引,当然一些初始化数据需要手工导入
>
> - ..........
>
>
## 2 JPA的简单使用
### 2.1 实操练习
> 只介绍注解的使用,另一种基于xml方式的使用,自行了解
**准备:**创建一个空数据库`mytest`
1.添加maven依赖
```xml
org.springframework.boot
spring-boot-starter-data-jpa
```
2.配置全局配置文件(application.yml)
```yaml
spring:
# 1.配置数据源
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/mytest?useUnicode=true&characterEncoding=utf-8
username: root
password: root
# 2.配置jpa
jpa:
hibernate:
ddl-auto: update # 自动更新数据库
show-sql: true # 控制台日志中显示sql语句
```
> `jpa.hibernate.ddl-auto`:是hibernate的配置属性,其主要作用是:自动创建、更新、验证数据库表结构。该参数的几种配置如下:
>
> - `create`:每次加载hibernate时都会删除上一次的生成的表,然后根据你的model类再重新来生成新表,哪怕两次没有任何改变也要这样执行,这就是导致数据库表数据丢失的一个重要原因。
> - `create-drop`:每次加载hibernate时根据model类生成表,但是sessionFactory一关闭,表就自动删除。
> - `update`:最常用的属性,第一次加载hibernate时根据model类会自动建立起表的结构(前提是先建立好数据库),以后加载hibernate时根据model类自动更新表结构,即使表结构改变了但表中的行仍然存在不会删除以前的行。要注意的是当部署到服务器后,表结构是不会被马上建立起来的,是要等应用第一次运行起来后才会。
> - `validate`:每次加载hibernate时,验证创建数据库表结构,只会和数据库中的表进行比较,不会创建新表,但是会插入新值。
3.配置实体类
```java
//使用JPA注解配置映射关系
@Entity
@Data
public class User {
@Id
// @GeneratedValue(strategy = GenerationType.IDENTITY) //自增主键
private Integer id;
@Column
private String username;
private String password;
private Integer age;
private String phone;
}
```
> 【注意】
>
> 1. `@Id`:指定的类的属性,用于识别。==其作用相当于规定一个表中的主键==。
>
> 2. `@Data`:注解是lombok.jar包下的注解,该注解通常用在实体bean上,不需要写出set和get方法,但是具备实体bean所具备的方法,简化编程提高变成速度。注意:项目中一定要引入lombok.jar
>
> @Data相当于@Getter、 @Setter、 @RequiredArgsConstructor、 @ToString、 @EqualsAndHashCode这5个注解的合集。
4.编写Dao接口来操作实体类对应的数据表,该接口需要继承`JpaRepository`接口即可
```java
/**
* User的Dao层
* JpaRepository
* 参数一 T :当前需要映射的实体
* 参数二 ID :当前映射的实体中的ID的类型
*
*/
public interface UserRepository extends JpaRepository {
}
```
> 【讲解】
>
> `JpaRepository`:
>
> - 参数一 T:代表当前需要映射的实体
> - 参数二 ID:代表当前映射的实体类中的ID(主键)的类型
5.编写控制器
```java
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserRepository userRepository;
@PostMapping("/addUser")
public void addUser(User user) {
userRepository.save(user);
}
@GetMapping("/findById")
public ResultBean findById(Integer id) {
Optional o = userRepository.findById(id);
if (o.isPresent()) {
User user = o.get();
return ResultBean.success(user);
} else {
return ResultBean.successCode(205, "查询记录不存在");
}
}
}
```
> 从上面的`UserRepository`接口代码我们可以看到,接口中并没有定义任何的方法,这是因为`JpaRepository`中帮我们定义了基础的增删改查方法,可以很方便的直接使用。
**系统目录结构**
**运行程序,查询数据库我们就可以看到,JPA以及自动帮我们创建了表**
| 1. 运行前 |
| ------------------------------------------------------------ |
|  |
| **2. 运行后** |
|  |
| **3. 生成的数据库表结构** |
|  |
(1)调用程序接口`/user/addUser`
apiPost请求:
数据库user表:

---
(2)调用程序接口`/user/findById`

### 2.2 JPA的使用(不需要实现sql语句编写)
**`JPA注解`**
| 注解 | 解释 |
| ------------------ | ------------------------------------------------------------ |
| @Entity | 声明类为实体或表 |
| @Table | 声明表名 |
| @Basic | 表示一个简单的属性到数据库表的字段的映射(【属性】`fetch`:表示该属性的读取策略,有 EAGER 和 LAZY 两种,分别表示主支抓取和延迟加载,默认为 EAGER;`optional`:表示该属性是否允许为null, 默认为true) |
| @Embedded | |
| @Id | 指定的类的属性,用于识别`一个表中的主键` |
| @GeneratedValue | 标注主键的生成策略,例如自动、手动或从序列表中获得的值 |
| @Transient | 指定的属性,它是不持久的,即:该值永远不会存储在数据库中 |
| @Column | 指定持久属性栏属性,可用于指定数据库表的列名 |
| @SequenceGenerator | 指定在@GeneratedValue注解中指定的属性的值。它创建了一个序列 |
| @TableGenerator | |
| @AccessType | |
| @JoinColumn | 指定一个实体组织或实体的集合。`这是用在多对一和一对多关联` |
| @UniqueConstraint | |
| @ColumnResult | |
| @NamedQuery | 指定使用静态名称的查询 |
| | |
| @OneToOne | 定义连接表之间的`一对一`的关系 |
| @OneToMany | 定义连接表之间的`一对多`的关系 |
| @ManyToOne | 定义连接表之间的`多对一`的关系 |
| @ManyToMany | 定义连接表之间的`多对多`的关系 |
> 常用注解属性解释:
>
> `@GeneratedValue`:
>
> - `strategy`:
> - GenerationType.IDENTITY:采用数据库 ID自增长的方式来自增主键字段,Oracle 不支持这种方式
> - GenerationType.TABLE:通过表产生主键,框架借由表模拟序列产生主键,使用该策略可以使应用更易于数据库移植
> - GenerationType.AUTO:JPA自动选择合适的策略,是默认选项
> - GenerationType.SEQUENCE:通过序列产生主键,通过 @SequenceGenerator 注解指定序列名,MySql 不支持这种方式
>
> `@Column`:
>
> - name:定义了被标注字段在数据库表中所对应字段的名称;
> - unique:表示该字段是否为唯一标识,默认为false。如果表中有一个字段需要唯一标识,则既可以使用该标记,也可以使用@Table标记中的@UniqueConstraint。
> - nullable:表示该字段是否可以为null值,默认为true。
> - insertable:表示在使用“INSERT”脚本插入数据时,是否需要插入该字段的值。
> - updatable:表示在使用“UPDATE”脚本插入数据时,是否需要更新该字段的值。insertable和updatable属性一般多用于只读的属性,例如主键和外键等。这些字段的值通常是自动生成的。
> - columnDefinition:表示该字段在数据库中的实际类型。
> - table:表示当映射多个表时,指定表的表中的字段。默认值为主表的表名。
> - length:表示字段的长度,当字段的类型为varchar时,该属性才有效,默认为255个字符。
> - precision和scale:precision属性和scale属性表示精度,当字段类型为double时,precision表示数值的总长度,scale表示小数点所占的位数。
**`JPA关键字`**
> **那么JPA是通过什么规则来根据方法名生成sql语句查询的呢?**
> 其实JPA在这里遵循Convention over configuration(约定大约配置)的原则,遵循spring 以及JPQL定义的方法命名。Spring提供了一套可以通过命名规则进行查询构建的机制。这套机制会把方法名首先过滤一些关键字,比如 find…By, read…By, query…By, count…By 和 get…By 。系统会根据关键字将命名解析成2个子语句,第一个 By 是区分这两个子语句的关键词。这个 By 之前的子语句是查询子语句(指明返回要查询的对象),后面的部分是条件子语句。如果直接就是 findBy… 返回的就是定义Respository时指定的领域对象集合,同时JPQL中也定义了丰富的关键字:and、or、Between等等。
>
> 关键字如下表:
| 关键字 | 例子 | 作用解释 |
| ----------------- | ------------------------------ | --------------------------------------------- |
| And | | |
| Or | | |
| Is.Equals | | |
| Between | | |
| LessThan | | |
| LessThanEqual | | |
| GreaterThan | | |
| GreaterThanEqual | | |
| After | | |
| Before | | |
| IsNull | | |
| isNotNull.NotNull | | |
| Like | | |
| NotLike | | |
| StartingWith | findByFirstnameStartingWith | |
| EndingWith | findByFirstnameEndingWith | |
| Containing | findByFirstnameContaining | |
| OrderBy | findByAgeOrderByLastnameDesc | ... where x.age = ?1 order by x.lastname desc |
| Not | findByLastnameNot | ... where x.lastname <> ?1 |
| In | findByAgeIn(Collection age) | ... where x.age in ?1 |
| NotIn | findByAgeNotIn(Collection age) | ... where x.age not in ?1 |
| TRUE | findByActiveTrue() | ... where x.active = true |
| FALSE | findByActiveFalse() | ... where x.active = false |
| IgnoreCase | findByFirstnameIgnoreCase | |
### 2.3 自定义sql
> @Query(value=" 这里就是查询语句")
>
> @Query支持hql和原生sql两种方式,默认是hql ,hql就是语句中用的是实体名字和实体属性,原生sql用的是表名字和表字段,
#### 2.3.1 Hql
要想查询全部字段可以用 sellect 实体名 这里省略了value ,参数使用了占位置符 `?1` 代表第一个参数 `?2`代表第二个
```java
public interface UserRepository extends JpaRepository {
@Query("select u from User u where u.emailAddress = ?1")
User findByEmailAddress(String emailAddress);
}
//如果是更新或者删除操作,方法上面要加@Modifying 默认开启的事务只是可读的,更新操作加入@Modifying 就会关闭可读
@Modifying
@Transactional
@Query("update CardConfig set cardStatus=?1 where id in ?2")
void updateCardStatus( Integer status,List listIds);
// @Param 代替参数占位符, hql或者sql里就用 :firstname替换 方法里的参数顺序可以打乱
@Query("select u from User u where u.firstname = :firstname or u.lastname = :lastname")
User findByLastnameOrFirstname(@Param("lastname") String lastname,
@Param("firstname") String firstname);
//返回字段 组成新的entity返回 类名必须是全写的
@Query(value="select new com.hikvision.metro.modules.repository.entity.CameraIndexs(c.preOneCameraIndexcode, c.preTwoCameraIndexcode, c.backOneCameraIndexcode) from StationDeviceConfig c")
List getAllCameraIndexs();
```
#### 2.3.2 原生Sql
语句可以直接放到数据库中执行 `nativeQuery=true`
```java
@Modifying
@Query(value="select status from t_station_device_config where pre_one_camera_indexcode=?1",nativeQuery = true)
List findStatusByPreOneCameraIndexcode(String index);
@Query(value="select radar_indexcode from t_station_device_config",nativeQuery = true)
List getAllRadarIndex();
```
### 2.4 Spring Boot JPA提供的核心接口使用
> (1)Repository接口
> (2)CrudRepository接口
> (3)PagingAndSortingRepository接口(`该接口提供了分页与排序的操作,注意:该接口继承了CrudRepository接口`)
> (4)JpaRepository接口(`该接口继承了PagingAndSortingRepository。对继承的父接口中方法的返回值进行适配。`)
> (5)JPASpecificationExecutor接口(`该接口主要是提供了多条件查询的支持,并且可以在查询中添加排序与分页。注意JPASpecificationExecutor是单独存在的。完全独立`)
#### 2.4.1 Repository接口的使用
> - 提供了方法名称命名查询方式
> - 提供了基于@Query注解查询与更新
#### 2.4.2 CrudRepository接口的使用
#### 2.4.3 PagingAndSortingRepository接口的使用
#### 2.4.4 JpaRepository接口的使用
#### 2.4.5 JPASpecificationExecutor接口的使用
## 3 JPA的深入使用
### 3.1 实体映射关系
**基本映射**
| 对象端 | 数据库端 | 注解(annotion) | 可选注解 |
| -------- | ----------- | ---------------- | ----------------------------- |
| Class | Table | @Entity | @Table(name="tableName") |
| Property | Column | — | @Column(name="columnName") |
| Property | Primary key | @Id | @GeneratedValue(ID生成策略) |
| Property | None | @Transient | |
**ID生成策略**
ID对应数据库表的主键,是保证唯一性的重要属性。JPA提供了以下几种ID生成策略
> - `GeneratorType.AUTO` :由JPA自动生成
> - `GenerationType.IDENTITY`:使用数据库的自增长字段,需要数据库的支持(如SQL Server、MySQL、DB2、Derby等)
> - `GenerationType.SEQUENCE`:使用数据库的序列号,需要数据库的支持(如Oracle)
> - `GenerationType.TABLE`:使用指定的数据库表记录ID的增长 需要定义一个TableGenerator,在@GeneratedValue中引用。例如:
> @TableGenerator( name=“myGenerator”, table=“GENERATORTABLE”, pkColumnName = “ENTITYNAME”, pkColumnValue=“MyEntity”, valueColumnName = “PKVALUE”, allocationSize=1 )
> @GeneratedValue(strategy = GenerationType.TABLE,generator=“myGenerator”)
**映射关系**
JPA定义了`one-to-one`、`one-to-many`、`many-to-one`、`many-to-many` 4种关系。可使用`joinColumns`来标注外键、使用 @Version来实现乐观锁
> 关联关系可以定制延迟加载和级联操作的行为。
>
> **定制关联对象加载方式:**
>
> - 延迟加载:`fetch=FetchType.LAZY`
> - 立即加载:`fetch=FetchType.EAGER`
>
>
>
> **通过设置cascade={options}设置级联操作行为**
>
> 其中options可以是以下组合:
>
> - 级联更新:`CascadeType.MERGE`
> - 级联保存:`CascadeType.PERSIST`
> - 级联刷新:`CascadeType.REFRESH`
> - 级联删除:`CascadeType.REMOVE`
> - 上述4种操作:`CascadeType.ALL`
### 3.2 关联映射操作
#### 3.1.1 一对一的关联关系
#### 3.1.2 一对多的关联关系
#### 3.1.3 多对多的关联关系