Java 持久层 API:JPA
- 1.Spring Data
- 1.1 主要模块
- 1.2 社区模块
- 2.JPA
- 3.使用 JPA
- 3.1 添加 JPA 和 MySQL 数据库的依赖
- 3.2 配置数据库连接信息
- 4.了解 JPA 注解和属性
- 4.1 常用注解
- 4.2 映射关系的注解
- 4.3 映射关系的属性
- 5.用 JPA 构建实体数据表
1.Spring Data
Spring Data 是 Spring 的一个子项目,旨在统一和简化各类型数据的持久化存储方式,而不拘泥于是关系型数据库还是 NoSQL 数据库。
无论是哪种持久化存储方式,数据访问对象(Data Access Objects
,DAO
)都会提供对对象的增加、删除、修改和查询的方法,以及排序和分页方法等。
Spring Data 提供了基于这些层面的统一接口(如:CrudRepository、PagingAndSortingRepository),以实现持久化的存储。
Spring Data 包含多个子模块,主要分为主模块和社区模块。
1.1 主要模块
| |
---|---|
Spring Data Commons | 提供共享的基础框架,适合各个子项目使用,支持跨数据库持久化。 |
Spring Data JDBC | 提供了对 JDBC 的支持,其中封装了 JDBCTemplate。 |
Spring Data JDBC Ext | 提供了对 JDBC 的支持,并扩展了标准的 JDBC,支持 Oracle RAD、高级队列和高级数据类型。 |
Spring Data JPA | 简化创建 JPA 数据访问层和跨存储的持久层功能。 |
Spring Data KeyValue | 集成了 Redis 和 Riak,提供多个常用场景下的简单封装,便于构建 key-value 模块。 |
Spring Data LDAP | 集成了 Spring Data repository 对 Spring LDAP 的支持。 |
Spring Data MongoDB | 集成了对数据库 MongoDB 支持。 |
Spring Data Redis | 集成了对 Redis 的支持。 |
Spring Data REST | 集成了对 RESTful 资源的支持。 |
Spring Data for Apache Cassandra | 集成了对大规模、高可用数据源 Apache Cassandra 的支持。 |
Spring Data for Apace Geode | 集成了对 Apache Geode 的支持。 |
Spring Data for Apache Solr | 集成了对 Apache Solr 的支持。 |
Spring Data for Pivotal GemFire | 集成了对 Pivotal GemFire 的支持。 |
1.2 社区模块
| |
---|---|
Spring Data Aerospike | 集成了对 Aerospike 的支持 |
Spring Data ArangoDB | 集成了对 ArangoDB 的支持。 |
Spring Data Couchbase | 集成了对 Couchbase 的支持。 |
Spring Data Azure Cosmos DB | 集成了对 Azure Cosmos 的支持。 |
Spring Data Cloud Datastore | 集成了对 Google Datastore 的支持 |
Spring Data Cloud Spanner | 集成了对 Google Spanner 的支持。 |
Spring Data DynamoDB | 集成了对 DynamoDB 的支持。 |
Spring Data Elasticsearch | 集成了对搜索引擎框架 Elasticsearch 的支持。 |
Spring Data Hazelcast | 集成了对 Hazelcast 的支持。 |
Spring Data Jest | 集成了对基于 Jest REST client 的 Elasticsearch 的支持。 |
Spring Data Neo4j | 集成了对 Neo4j 数据库的支持。 |
Spring Data Vault | 集成了对 Vault 的支持。 |
2.JPA
JPA(Java Persistence APl
)是 Java 的持久化 API,用于对象的持久化。它是一个非常强大的 ORM 持久化的解决方案,免去了使用 JDBCTemplate 开发的编写脚本工作。JPA 通过简单约定好接口方法的规则自动生成相应的 JPQL 语句,然后映射成 POJO 对象。
JPA 是一个规范化接口,封装了 Hibernate 的操作作为默认实现,让用户不通过任何配置即可完成数据库的操作。JPA、Spring Data 和 Hibernate 的关系如下图所示。
Hibernate 主要通过 hibernate-annotation
、hibernate-entitymanager
和 hibernate-core
三个组件来操作数据。
hibernate-annotation
:是 Hibernate 支持 annotation 方式配置的基础,它包括标准的 JPAannotation、Hibernate 自身特殊功能的 annotation。hibernate-core
:是 Hibernate 的核心实现,提供了 Hibernate 所有的核心功能。hibernate-entitymanager
:实现了标准的 JPA,它是 hibernate-core 和 JPA 之间的适配器,它不直接提供 ORM 的功能,而是对 hibernate-core 进行封装,使得 Hibernate 符合 JPA 的规范。
如果要 JPA 创建《Java 的数据库连接模板:JDBCTemplate》中 “2.2 新建实体类” 里的实体,可使用以下代码来实现。
@Data
@Entity
public class User {private int id;@Id// id 的自增由数据库自动管理@GeneratedValue(strategy = GenerationType.IDENTITY)private String username;private String password;
}
对比 JPA 与 JDBCTemplate 创建实体的方式可以看出:JPA 的实现方式简单明了,不需要写映射(支持自定义映射),只需要设置好属性即可。id
的自增由数据库自动管理,也可以由程序管理,其他的工作 JPA 自动处理好了。
3.使用 JPA
要使用 JPA,只要加入它的 Starter 依赖,然后配置数据库连接信息。
3.1 添加 JPA 和 MySQL 数据库的依赖
下面以配置 JPA 和 MySQL 数据库的依赖为例,具体配置见以下代码:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope>
</dependency>
3.2 配置数据库连接信息
Spring Boot 项目使用 MySQL 等关系型数据库,需要配置连接信息,可以在 application.properties
文件中进行配置。以下代码配置了与 MySQL 数据库的连接信息:
spring.datasource.url=jdbc:mysql://127.0.0.1/book?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC&useSSL=true
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.jpa.properties.hibernate.hbm2ddl.auto=update
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.show-sql= true
spring.datasource.username
:要填写的数据库用户名。spring.datasource.password
:要填写的数据库密码。spring.jpa.show-sql
:开发工具的控制台是否显示 SQL语句,建议打开。spring.jpa.properties.hibernate.hbm2ddl.auto
:hibernate 的配置属性,其主要作用是自动创建、更新、验证数据库表结构。该参数的几种配置如下表所示。
| |
---|---|
create | 每次加载 Hibernate 时都会删除上一次生成的表,然后根据 Model 类再重新生成新表,哪怕没有任何改变也会这样执行,这会导致数据库数据的丢失。 |
create-drop | 每次加载 Hibernate 时会根据 Model 类生成表,但是 sessionFactory 一旦关闭,表就会自动被删除。 |
update | 最常用的属性。第一次加载 Hibernate 时会根据 Model 类自动建立表的结构(前提是先建立好数据库)。以后加载 Hibernate 时,会根据 Model 类自动更新表结构,即使表结构改变了,但表中的数据仍然存在,不会被删除。要注意的是,当部署到服务器后,表结构是不会被马上建立起来的,要等应用程序第一次运行起来后才会建立。Update 表示如果 Entity 实体的字段发生了变化,那么直接在数据库中进行更新。 |
validate | 每次加载 Hibernate 时,会验证数据库的表结构,只会和数据库中的表进行比较,不会创建新表,但是会插入新值。 |
4.了解 JPA 注解和属性
4.1 常用注解
| |
---|---|
@Entity | 声明类为实体 |
@Table | 声明表名,@Entity 和 @Table 注解一般一块使用,如果表名和实体类名相同,那么 @Table 可以省略 |
@Basic | 指定非约束明确的各个字段 |
@Embedded | 用于注释属性,表示该属性的类是嵌入类(@embeddable 用于注释 Java 类的,表示类是嵌入类) |
@Id | 指定的类的属性,一个表中的主键 |
@GeneratedValue | 指定如何标识属性可以被初始化,如 @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="repair_seq") ,表示主键生成策略是 sequence ,还有 Auto、Identity、Native 等 |
@Transient | 表示该属性并非一个数据库表的字段的映射,ORM 框架将忽略该属性。如果一个属性并非数据库表的字段映射,就务必将其标示为 @Transient,即它是不持久的,为虚拟字段 |
@Column | 指定持久属性,即字段名。如果字段名与列名相同,则可以省略。使用方法,如 @Column(length=11, name="phone", nullable=false, columnDefinition="varchar(11) unique comment '电话号码'") |
@SequenceGenerator | 指定在 @GeneratedValue 注解中指定的属性的值。它创建一个序列 |
@TableGenerator | 在数据库生成一张表来管理主键生成策略 |
@AccessType | 这种类型的注释用于设置访问类型。如果设置 @AccessType(FIELD) ,则可以直接访问变量,并且不需要使用 Getter 和 Setter 方法,但必须为 public 属性。如果设置 @AccessType(PROPERTY) ,则通过 Getter 和 Setter 方法访问 Entity 的变量 |
@UniqueConstraint | 指定的字段和用于主要或辅助表的唯一约束 |
@ColumnResult | 可以参考使用 select 子句的 SQL 查询中的列名 |
@NamedQueries | 指定命名查询的列表 |
@NamedQuery | 指定使用静态名称的查询 |
@Basic | 指定实体属性的加载方式,如 @Basic(fetch=FetchType.LAZY) |
@Jsonignore | 作用是 JSON 序列化时将 Java Bean 中的一些属性忽略掉,序列化和反序列化都受影响 |
4.2 映射关系的注解
| |
---|---|
@JoinColumn | 指定一个实体组织或实体集合。用在 多对一 和 一对多 的关联中 |
@OneToOne | 定义表之间 一对一 的关系 |
@OneToMany | 定义表之间 一对多 的关系 |
@ManyToOne | 定义表之间 多对一 的关系 |
@ManyToMany | 定义表之间 多对多 的关系 |
4.3 映射关系的属性
| |
---|---|
targetEntity | 表示默认关联的实体类型,默认为当前标注的实体类。 |
cascade | 表示与此实体一对一关联的实体的级联样式类型,以及当对实体进行操作时的策略。在定义关系时经常会涉及是否定义 Cascade(级联处理)属性,如果担心级联处理容易造成负面影响,则可以不定义。它的类型包括 CascadeType.PERSIST (级联新建)、CascadeType.REMOVE (级联删除)、CascadeType.REFRESH (级联刷新)、CascadeType.MERGE (级联更新)、CascadeType.ALL (级联新建、更新、删除、刷新)。 |
fetch | 该实体的加载方式,包含 LAZY 和 EAGER。 |
optional | 表示关联的实体是否能够存在 null 值。默认为 true ,表示可以存在 null 值。如果为 false ,则要同时配合使用 @JoinColumn 标记。 |
mappedBy | 双向关联实体时使用,标注在不保存关系的实体中。 |
JoinColumn | 关联指定列。该属性值可接收多个 @JoinColumn。用于配置连接表中外键列的信息。@JoinColumn 配置的外键列参照当前实体对应表的主键列。 |
JoinTable | 两张表通过中间的关联表建立联系时使用,即多对多关系。 |
PrimaryKeyJoinColumn | 主键关联。在关联的两个实体中直接使用注解 @PrimaryKeyJoinColumn 注释。 |
懒加载 LAZY 和实时加载 EAGER 的目的是,实现关联数据的选择性加载。
- 懒加载 是一种延迟加载策略,即在真正需要访问对象的属性时,才从数据库中加载数据。当 Hibernate 在查询数据库时遇到关联对象(如一对多、多对一、多对多等关系),并不会立即加载关联对象的数据,而只会加载主键 ID。只有当程序真正需要访问关联对象的属性时,Hibernate 才会发出 SQL 语句去加载数据。这种策略的优点在于,如果程序并不需要访问关联对象的所有属性,那么就可以节省数据库访问的开销,提高程序性能。但是,如果在使用懒加载的对象后关闭了 Session,那么在访问其关联对象的属性时,就会抛出 LazyInitializationException 异常,因为 Hibernate 需要在 Session 的作用范围内才能发出 SQL 语句加载数据。
- 实时加载 是一种积极加载策略,即在加载一个对象时,会立即加载与其关联的所有对象。当 Hibernate 在查询数据库时遇到关联对象,会立即发出 SQL 语句加载关联对象的数据,并将其全部加载到内存中。这种策略的优点在于,程序可以随时访问关联对象的属性,而无需担心 Session 是否已经关闭。但是,如果关联对象的数据量很大,那么就会消耗大量的内存,并可能导致性能下降。
在 Spring Data JPA 中,要控制 Session 的生命周期,否则会出现 could not initialize proxy [xxxx#18] - no Session
错误。可以在配置文件中配置以下代码来控制 Session 的生命周期:
spring.jpa.open-in-view=true
spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true
5.用 JPA 构建实体数据表
下面通过实例来体验如何通过 JPA 构建对象/关系映射的实体模型。
package com.example.demo.entity;import lombok.Data;import javax.persistence.*;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Size;
import java.io.Serializable;
import java.util.Arrays;
import java.util.List;@Entity
@Data
public class Article implements Serializable {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private long id;@Column(nullable = false, unique = true)@NotEmpty(message = "标题不能为空")private String title;@Column(columnDefinition = "enum('图','图文','文')")private String type;private Boolean available = Boolean.FALSE;@Size(min = 0, max = 20)private String keyword;@Size(max = 255)private String description;@Column(nullable = false)private String body;@Transientprivate List keywordlists;public List getKeywordlists() {return Arrays.asList(this.keyword.trim().split("|"));}public void setKeywordlists(List keywordlists) {this.keywordlists = keywordlists;}
}
如果想创建虚拟字段,则通过在属性上加注解 @Transient 来解决。
运行项目后会自动生成数据表。完成后的数据表如下图所示。