认识ORM
在认识JPA之前我们先来了解一下ORM。ORM(Object Relation Mapping)是对象/关系的映射。它提供了概念性的、易于理解的数据模型,将数据库中的表和内存中的对象建立映射关系。它是随着面向对象软件开发方法的发展而产生的,面向对象的开发方法依然是当前主流的开发方法。
对象和关系型数据是业务实体的两种表现形式。业务实体在内存中表现为对象,在数据库中表现为关系型数据。内存中的对象不会被永久保存,只有关系型数据库(或非关系型数据库NoSQL,或文件)中的对象会被永久保存。
对象/关系映射(ORM)系统一般以中间件的形式存在,因为内存中的对象之间存在关联和继承关系,而在数据库中,关系型数据无法直接表达多对多的关联和继承关系。对象、数据库通过ORM映射的关系如下图所示。
目前比较常用的ORM是国外非常流行的JPA和国内非常流行的MyBatis
认识Spring Data
Spring Data是Spring的一个子项目,旨在统一和简化各类型数据的持久化存储方式,而不拘泥于是关系型数据库还是非关系型数据库。
无论是哪种持久化存储方式,数据访问对象(Data Access Object, DAO)都会提供对对象的增加、删除、修改和查询的方法,以及排序和分页的方法等。
Spring Data提供了基于这些层面的统一接口(如:CurdRepository、PagingAndSortingRepository),以实现持久化的存储。
Spring Data包含多个子模块,主要分为主模块和社区模块。
主要模块
- Spring Data Commons: 提供共享的基础框架,适合各个子项目使用,支持跨数据库持久化
- Spring Data JDBC: 提供了对JDBC的支持,其中封装了JDBCTemplate
- Spring Data JDBC Ext: 提供了对JDBC的支持,并扩展了标准的JDBC,支持OracleRAD、高级队列和高级数据类型
- Spring Data JPA:简化创建JPA数据访问层和跨存储的持久层功能。
- Spring Data KeyValue: 集成了Redis和Riak,提供多个常用场景下的简单封装,便于构建key-value模块
- Spring Data LDAP: 集成了Spring Data repository 对Spring LDAP的支持。
- Spring Data MongDB:集成了对数据库MongoDB的支持。
- Spring Data Redis: 集成了对Redis的支持。
- Spring Data REST:集成了对RESTful的支持。
- Spring Data for Apace Cassandra: 集成了对大规模、高可用数据源Apache Cassandra的支持。
- Spring Data for Apace Geode:集成了对Apace Geode的支持。
- Spring Data for Apace Solr:集成了对Apace Solr的支持。
- Spring Data for pivotal Gemfire:集成了对Pivotal GemFire的支持。
社区模块
- 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的支持。
认识JPA
JPA(Java Persistence API)是 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
要使用JPA,只要加入它的依赖,然后配置数据库连接信息。
- 添加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>
- 配置数据库的连接信息
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=123456
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=true”:开发工具控制台是否显示SQL语句,建议打开
- “spring.jpa.properties.hibernate.hbm2ddl.auto”:hibernate的配置属性,其主要作用是:自动创建、更新、验证数据库表结构。参数几种配置如下图所示:
属性 | 说明 |
---|---|
create | 每次加载Hibernate时都会删除上一次生成的表,然后根据Model类在重新生成新表,哪怕没有任何改变也会这样执行,这会导致数据库数据的丢失 |
create-drop | 每次加载Hibernate时会根据Model类生成表,但是sessionFactory一旦关闭,表会自动被删除 |
update | 最常用的属性。每一次加载Hibernate时会根据Model类自动建立表结构(前提是先建立好数据库)。以后加载Hibernate时,会根据Model类自动更新表结构,即使表的结构改变了,但表中的数据仍然存在,不会被删除。要注意的是,当部署到服务器后,表结构是不会被马上建立起来的,要等应用程序第一次运行起来后才会建立。Updata表示如果Entity实体的字段发生了变化,那么直接在数据库中进行更新 |
validate | 每次加载Hibernate时,会验证数据库的表结构,只会和数据库中的表进行比较,不会创建新表,但是会插入新值 |
了解JPA注解和属性
JPA常用注解
注解 | 说明 |
---|---|
@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) unque comment ‘电话号码’ ") |
@SequenceGenerator | 指定在@GeneratedValue注解中指定的属性的值,它创建一个序列 |
@TableGenerator | 在数据库生成一张表来管理主键生成策略 |
@ AccessType | 这种类型的注释用于设置访问类型。如果设置@AccessType(FIELD),则可以直接访问变量,并且不需要使用Getter和Setter方法,但必须为public属性。如果设置@AccessType(PROPERTY),则通过Getter和Setter方法访问Entity的变量 |
@UniqueConstraint | 指定的字段用于主要或辅助表的唯一约束 |
@Columnresuly | 可以参考使用select子句的SQL查询中的列名 |
@NamedQueries | 指定命名查询的列表 |
@namedQuery | 指定使用静态名称查询 |
@Basic | 指定实体属性的加载方式,如@Basic(fetch=FetchType.LAZY) |
@JsonIgnore | 作用是JSON序列化时将Java Bean 中的一些属性忽略掉,序列化和反序列化都受影响 |
映射关系的注解
注解 | 说明 |
---|---|
@JoinColumn | 指定一个实体组织或实体集合。用在“多对一” 和“一对多”的关联中 |
@OneToOne | 定义表之间“一对一”的关系 |
@OneToMany | 定义表之间“一对多”的关系 |
@ManyToOne | 定义表之间“多对一”的关系 |
@ManyToMany | 定义表之间“多对多”的关系 |
映射关系的属性
属性名 | 说明 |
---|---|
targetEntity | 表示默认关联的实体类型,默认为当前标注的实体类 |
cascade | 表示与此实体一对一关联的实体的级联样式类型,以及当对实体进行操作时的策略。在定义关系时经常会涉及是否定义Cascade(级联处理)属性,如果担心级联处理容易造成负面影响,则可以不定义。它的类型包括CascadeType.PERSIST(级联新建)、CascadeType.REMOVE(级联删除)、CascadeType.REFRESH(级联刷新)、CascadeType.MERGE(级联更新)、CascedeType.ALL(级联新建、更新、删除、刷新) |
fetch | 该实体的加载方式,包括LAZY和EAGER |
optional | 表示关联的实体是否能够存在null值。默认为true,表示可以存在null值。如果为false,则需要同时配合使用@JoinColumn |
mappedBy | 双向关联实体时使用,标注在不保存关系的实体中 |
JoinColumn | 关联指定列。该属性值可接受多个@JoinColumn。用于配置连接表中外键列的信息。@JoinColumn配置的外键列参照当前实体对应表的主键列 |
JoinTable | 两张表通过中间的关联表建立连接时使用,即多对多关系。 |
primaryKeyJoinColumn | 主键关联。在关联的两个实体中直接使用注解@PrimaryKeyJoinColumn注释 |
懒加载LAZY和实体加载EAGER的目的是,实现关联数据的选择性加载。
懒加载是在属性被引用时才生成查询语句,抽取相关数据。
实体加载则是执行完主查询后,不管是否被引用,都会马上执行后续的关联数据查询
使用懒加载来调用关联数据,必须要保证主查询的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
案例
1.新建一个空的数据库
2.运行代码
1)创建book数据库;
2)配置pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
- 配置pom.xml
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=123456
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.thymeleaf.cache=false
server.port=8080
4)编写映射实体
@Entity
@Data
public class Article implements Serializable {
@Id
/**
* Description: 由数据库控制,auto是程序统一控制
*/
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
@Column(nullable = false, unique = true)
@NotEmpty(message = "标题不能为空")
private String title;
/**
* Description: 枚举类型
*/
@Column(columnDefinition="enum('图','图文','文')")
private String type;//类型
/**
* Description: Boolean类型默认false
*/
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;
/**
* Description: 创建虚拟字段
*/
@Transient
private List keywordlists;
public List getKeywordlists() {
return Arrays.asList(this.keyword.trim().split("|"));
}
public void setKeywordlists(List keywordlists) {
this.keywordlists = keywordlists;
}
}
运行结果
《Spring Boot 实战派》