# 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.assets/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2NteDEwNjAyMjAyMTk=,size_16,color_FFFFFF,t_70#pic_center.png) ----- 【个人理解】使用**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`中帮我们定义了基础的增删改查方法,可以很方便的直接使用。 **系统目录结构** image-20211103154354495 **运行程序,查询数据库我们就可以看到,JPA以及自动帮我们创建了表** | 1. 运行前 | | ------------------------------------------------------------ | | ![image-20211103153020207](JPA.assets/image-20211103153020207.png) | | **2. 运行后** | | ![image-20211103152640704](JPA.assets/image-20211103152640704.png) | | **3. 生成的数据库表结构** | | ![image-20211103155645721](JPA.assets/image-20211103155645721.png) | (1)调用程序接口`/user/addUser` apiPost请求: image-20211103155813671 数据库user表: ![image-20211103160300266](JPA.assets/image-20211103160300266.png) --- (2)调用程序接口`/user/findById` ![image-20211103161115575](JPA.assets/image-20211103161115575.png) ### 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 多对多的关联关系