JPA基础
# JPA 基础
# JDBC 操作数据库及思考 🔥
有一个用户表
create table t_user (
id bigint auto_increament,
username varchar(100),
address varchar(255)
)
2
3
4
5
用户表对应的 model
@Data
public class User {
private Long userId;
private String username;
private String address;
}
2
3
4
5
6
7
JDBC 保存用户操作(只关注步骤,api 可能不对,且没有关注事务)
String sql = "insert into t_user (username, address) values (?,?)";
// 1 获取连接
Connection conn = DriverManager.getConnection(url, username, password);
// 2 创建 statement 对象
PreparedStatement ps = conn.preparedStatment(sql);
// 3 对占位符赋值
ps.setString(1, user.getUsername());
ps.setString(2, user.getAddress());
// 4 保存
ps.executeUpdate();
2
3
4
5
6
7
8
9
10
11
上述操作总的来说很麻烦,当然可以封装一个工具类,只写 sql、占位符赋值、执行操作。但是占位符赋值没有用到面向对象的思想,那么怎么使用面向对象的思想来操作呢?
例如调用 repository.save(user) 即可保存到数据库中,从该语句中可以得出,save 动词可拼接出 sql:insert into __ values __,那么表名和占位符赋值则直接使用 user 对象可以获取到,然后执行操作即可。
所以,只需建立 user 对象和 t_user 表的映射关系,user 的属性和 t_user 表字段的映射关系即可使用面向对象来操作数据库了
# ORM & JPA 与 Hibernate
# ORM 思想
ORM(Object-Relational Mapping) 表示对象关系映射。简单的说:ORM就是建立实体类和数据库表之间的关系(包括表和字段),从而达到操作实体类就相当于操作数据库表的目的。解决了可能会写特别多数据访问层的代码、从数据库保存数据、修改数据、删除数据等重复代码问题。
常见的 ORM 框架:Mybatis(ibatis)、Hibernate
# JPA 及其优势
JPA(Java Persistence API), 即Java 持久化API,是SUN公司推出的一套基于ORM的规范(操作ORM框架,JDBC是操作不同数据库),内部是由一系列的接口和抽象类构成。注意不是框架!
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 中能够支持面向对象的高级特性,如类之间的继承、多态和类之间的复杂关系,这样的支持能够让开发者最大限度的使用面向对象的模型设计企业应用,而不需要自行处理这些特性在关系数据库的持久化。
# Hibernate 简介
Hibernate是一个开放源代码的对象关系映射框架,它对JDBC进行了非常轻量级的对象封装,实现类了 JPA 规范,它将POJO与数据库表建立映射关系,是一个全自动的ORM框架。Hibernate可以自动生成SQL语句,自动执行,使得Java程序员可以随心所欲的使用对象编程思维来操纵数据库。
# JPA、Hibernate、JDBC 关系 🔥
JPA和Hibernate的关系就像JDBC和JDBC驱动的关系,JPA是规范,Hibernate除了作为ORM框架之外,它也是一种JPA实现。JPA怎么取代Hibernate呢?JDBC规范可以驱动底层数据库吗?答案都是否定的,也就是说,如果使用JPA规范进行数据库操作,底层需要Hibernate作为其实现类完成数据持久化工作。

# JPA 环境准备
# 目标
实现功能是搭建 JPA 环境,并实现一条数据的增删改查
# 准备数据库环境
-- 准备数据库,创建一张文章表备用
CREATE TABLE `article` (
`aid` INT ( 11 ) NOT NULL auto_increment COMMENT '主键',
`author` VARCHAR ( 255 ) DEFAULT NULL COMMENT '作者',
`title` VARCHAR ( 255 ) DEFAULT NULL COMMENT '标题',
`createTime` datetime DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY ( `aid` )
);
2
3
4
5
6
7
8
# 创建 Maven 工程
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.19</version>
</dependency>
<!--包括 hibernate-core-->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>5.4.25.Final</version>
</dependency>
<!--<dependency>-->
<!-- <groupId>log4j</groupId>-->
<!-- <artifactId>log4j</artifactId>-->
<!-- <version>1.2.17</version>-->
<!--</dependency>-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.6.2</version>
<scope>test</scope>
</dependency>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
其余的 JDK 8 之类的参考 Maven 笔记!
# JPA 的核心配置文件
在 Maven 工程的resources路径下创建一个名为META-INF的文件夹,在文件夹下创建一个名为persistence.xml的配置文件。注意:META-INF文件夹名称不能修改,persistence.xml文件名称不能改。
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
version="2.0">
<!--持久化单元,可配置多个
name 持久化单元的名称 唯一
transaction-type 事务类型
RESOURCE_LOCAL 本地事务
JTA 分布式事务
-->
<persistence-unit name="jpa01" transaction-type="RESOURCE_LOCAL">
<!--配置 JPA 规范的服务提供商,当项目中依赖的只有一个JPA的实现时(如 Hibernate),此选项可省略-->
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<!--指定实体类,此选项可省略-->
<class>top.conanan.domain.Article</class>
<properties>
<!--跟数据库相关的信息 驱动 url 用户名 密码。可从 AvailableSettings 类中找到name的值-->
<property name="javax.persistence.jdbc.driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="javax.persistence.jdbc.url" value="jdbc:mysql:///demo?serverTimezone=GMT%2B8"/>
<property name="javax.persistence.jdbc.user" value="root"/>
<property name="javax.persistence.jdbc.password" value="w111111"/>
<!--jpa的核心配置中兼容hibernate的配置(只能 Hibernate 使用)-->
<!--是否显示SQL-->
<property name="hibernate.show_sql" value="true"/>
<!--是否格式化显示的SQL-->
<property name="hibernate.format_sql" value="true"/>
<!--
自动建表
update 如果数据库存在数据表,就使用;不存在,就创建
create 不管数据库有没有数据表,每次SQL请求都会重新建表
-->
<property name="hibernate.hbm2ddl.auto" value="update"/>
</properties>
</persistence-unit>
</persistence>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# 配置实体类与表的映射关系
@Getter
@Setter
@ToString
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Entity // 告诉 JPA 这是一个实体类,需要把它跟数据库中的表做映射
@Table(name = "article")//@Table建立了实体类和数据表的关系,name指向表名。当类名和数据表的名一致时,此注解可省略
public class Article {
@Id//标识这是主键字段
//指定主键生成策略,GenerationType.IDENTITY就是对应到mysql中的数据自增策略
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long aid;
//使用@Column映射类的属性和数据表的字段关系 name指定表中的字段名
//当类的属性名和数据表的字段名一致时,此注解可省略
@Column(name = "author")
private String author;
private String title;
private LocalDateTime createTime;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 工具类 🔥
public final class JpaUtil {
// JPA的实体管理器工厂:相当于Hibernate的SessionFactory。工厂类的创建十分消耗资源,一般在项目中配置为单例
private static final EntityManagerFactory em;
// 使用静态代码块赋值
static {
// 注意:该方法参数必须和persistence.xml中persistence-unit标签name属性取值一致
em = Persistence.createEntityManagerFactory("jpa01");
}
/**
* 使用管理器工厂生产一个管理器对象
*/
public static EntityManager getEntityManager() {
return em.createEntityManager();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# JPA 增删改查 🔥
/**
* Jpa的操作步骤
* 1.加载配置文件创建工厂(实体管理器工厂)对象 EntityManagerFactory
* 2.通过实体管理器工厂获取实体管理器 EntityManager
* 3.获取事务对象,开启事务 EntityTransaction
* 4.完成增删改查操作(查询不需要事务)
* 5.提交事务(回滚事务)
* 6.释放 EntityManager 资源
*/
class TestJpa {
private EntityManager entityManager;
private EntityTransaction transaction;
@BeforeEach
void init() {
//通过工具类获取EntityManager对象
entityManager = JpaUtil.getEntityManager();
//获取事务对象,根据需要调用 transaction.begin() 开启事务
transaction = entityManager.getTransaction();
}
@AfterEach
void destroy() {
//释放资源,工厂对象不用释放,其他方法还需要使用
entityManager.close();
}
@Test
void testPersist() {
// 创建对象
Article article = Article.builder().author("男哥").title("测试文章").createTime(LocalDateTime.now()).build();
try {
// 开启事务
transaction.begin();
// 保存
entityManager.persist(article);
// 提交事务
transaction.commit();
} catch (Exception e) {
e.printStackTrace();
transaction.rollback();
}
}
/**
* find为立即加载
*/
@Test
void testFind() {
// 第一个参数为要封装的对象的字节码,第二个参数为主键
Article article = entityManager.find(Article.class, 1L);
System.out.println(article);
}
/**
* getReference为延迟加载(懒加载),使用的时候才查询数据库。一般使用这个(序列化时可能报错?)
* IDEA需要去掉Debug中几个选项才可以看到
*/
@Test
void testReference() {
Article article = entityManager.getReference(Article.class, 1L);
System.out.println(article);
}
@Test
void testMerge() {
Article article = entityManager.find(Article.class, 3L);
article.setAuthor("男神");
try {
// 开启事务
transaction.begin();
// 更新(注意,实体类中当前为null的字段也会被更新为null,可以配置更改该策略)
entityManager.merge(article);
// 提交事务
transaction.commit();
} catch (Exception e) {
e.printStackTrace();
transaction.rollback();
}
}
@Test
void testRemove() {
Article article = entityManager.find(Article.class, 3L);
try {
// 开启事务
transaction.begin();
// 删除
entityManager.remove(article);
// 提交事务
transaction.commit();
} catch (Exception e) {
e.printStackTrace();
transaction.rollback();
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# JPQL 增删改查 🔥
此处示例只演示查询
JPQL(Java Persistence Query Language)。基于首次在EJB2.0中引入的EJB查询语言(EJB QL),Java持久化查询语言(JPQL)是一种可移植的查询语言,旨在以面向对象表达式语言的表达式,将SQL语法和简单查询语义绑定在一起,使用这种语言编写的查询是可移植的,可以被编译成所有主流数据库服务器上的SQL。避免了程序与数据库 SQL 语句耦合严重,比较适合跨数据源的场景(一会儿 MySQL,一会儿 Oracle 等)
其特征与原生SQL语句类似,并且完全面向对象,通过类名和属性访问,而不是表名和表中字段。不支持SELECT *,但是支持COUNT(*)
/**
* Jpa的操作步骤
* 1.加载配置文件创建工厂(实体管理器工厂)对象 EntityManagerFactory
* 2.通过实体管理器工厂获取实体管理器 EntityManager
* 3.获取事务对象,开启事务 EntityTransaction
* 4.完成增删改查操作(查询不需要事务)
* 5.提交事务(回滚事务)
* 6.释放 EntityManager 资源
*/
class TestJpql {
private EntityManager entityManager;
private EntityTransaction transaction;
@BeforeEach
void init() {
//通过工具类获取EntityManager对象
entityManager = JpaUtil.getEntityManager();
//获取事务对象,根据需要调用 transaction.begin() 开启事务
transaction = entityManager.getTransaction();
}
@AfterEach
void destroy() {
//释放资源,工厂对象不用释放,其他方法还需要使用
entityManager.close();
}
/**
* 查找所有
*/
@Test
void testFindAll() {
String jpql = "from Article";//也可以省略掉全限定类名,只写类名
//创建Query查询对象,这个对象才是执行jpql的对象
TypedQuery<Article> query = entityManager.createQuery(jpql, Article.class);
List<Article> articles = query.getResultList();
articles.forEach(System.out::println);
}
/**
* 排序查询(根据ID倒序查询所有文章)
*/
@Test
void testOrder() {
String jpql = "from Article order by aid desc";//也可以省略掉全限定类名,只写类名。这里的aid也可以写id,因为是@Id注解了
//创建Query查询对象,这个对象才是执行jpql的对象
TypedQuery<Article> query = entityManager.createQuery(jpql, Article.class);
List<Article> articles = query.getResultList();
articles.forEach(System.out::println);
}
/**
* 统计查询
*/
@Test
void testCount() {
String jpql = "select count(id) from Article ";//也可以省略掉全限定类名,只写类名
//创建Query查询对象,这个对象才是执行jpql的对象
TypedQuery<Long> query = entityManager.createQuery(jpql, Long.class);
Long count = query.getSingleResult();
System.out.println(count);
}
/**
* 分页查询
*/
@Test
void testPage() {
String jpql = "from Article ";//也可以省略掉全限定类名,只写类名
//创建Query查询对象,这个对象才是执行jpql的对象
TypedQuery<Article> query = entityManager.createQuery(jpql, Article.class);
query.setFirstResult(0);//起始索引
query.setMaxResults(5);//每页查询条数
List<Article> articles = query.getResultList();//此处为查询前5条
articles.forEach(System.out::println);
}
/**
* 条件查询
*/
@Test
void testCondition() {
String jpql = "from Article where author like ?2";//也可以省略掉全限定类名,只写类名
//创建Query查询对象,这个对象才是执行jpql的对象
TypedQuery<Article> query = entityManager.createQuery(jpql, Article.class);
query.setParameter(2, "女%");//第1个参数:占位符索引位置,根据?n写即可;第2个参数:值
List<Article> articles = query.getResultList();
articles.forEach(System.out::println);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# 注意
# find 与 getReference 区别
- find:立即加载。debug执行发现调用方法后就会发送sql查询数据库,且查询得到的对象就是该对象本身
- getReference:延迟加载(懒加载)。debug执行发现调用方法后不会立即查询数据库,只会在使用该对象时才发送sql查询数据库,并且查询得到的对象是动态代理生成的对象!
注意,IDEA比较智能,调用方法后debug会显示值,所以会立即发送sql。可以修改设置中的 Debugger - Data Views - Java 中的配置,去掉所有选项基本就可以了
# JPA 重要 API 介绍
# Persistence
Persistence对象主要作用是用于获取EntityManagerFactory对象的 。通过调用该类的createEntityManagerFactory()静态方法,根据配置文件中持久化单元名称创建EntityManagerFactory。
# EntityManagerFactory 🔥
EntityManagerFactory 接口主要用createEntityManager()来创建 EntityManager 实例,由于它是一个线程安全的对象(即多个线程访问同一个该对象不会有线程安全问题)
其内部维护了很多内容:
- 数据库信息
- 缓存信息
- 所有的实体管理器对象
因此创建EntityManagerFactory 极其浪费资源,所以在使用 JPA 编程时,我们可以对其创建进行优化(它是线程安全的),只需要做到一个工程只存在一个EntityManagerFactory ,即在程序启动时创建好
# EntityManager 🔥
在 JPA 规范中, EntityManager是完成持久化操作的核心对象。实体类作为普通 Java 对象,只有在调用EntityManager将其持久化后才会变成持久化对象。EntityManager 对象在一组实体类与底层数据源之间进行 O/R 映射的管理。它可以用来管理和更新 Entity Bean, 根椐主键查找 Entity Bean, 还可以通过 JPQL 语句查询实体等。
其内部维护了
我们可以通过调用EntityManager的方法完成获取事务,以及持久化数据库的操作
getTransaction: 获取事务对象persist: 保存操作find/getReference: 根据 id 查询merge: 更新操作remove: 删除操作
# EntityTransaction
在 JPA 规范中 EntityTransaction 是完成事务操作的核心对象,对于 EntityTransaction 在我们的 Java 代码中承接的功能比较简单
begin:开启事务commit:提交事务rollback:回滚事务
# JPA 常用注解
# @Entity
作用:指定当前类是实体类。
# @Table
作用:指定实体类和表之间的对应关系。
属性:
name:指定数据库表的名称,不指定则为类名首字母小写
# @Id
作用:指定当前字段是主键。
# @GeneratedValue
作用:指定主键的生成策略。
属性:
strategy:指定主键生成策略。JPA提供的四种标准用法为TABLE,SEQUENCE,IDENTITY,AUTO。
strategy 值介绍:
IDENTITY:主键由数据库自动生成(主要是自动增长型,必须数据库底层支持,如MySQL)@Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long custId;1
2
3SEQUENCE:根据底层数据库的序列来生成主键,条件是数据库支持序列,如Oracle。@Id @GeneratedValue(strategy = GenerationType.SEQUENCE,generator="payablemoney_seq") @SequenceGenerator(name="payablemoney_seq", sequenceName="seq_payment") private Long custId;1
2
3
4//@SequenceGenerator源码中的定义 @Target({TYPE, METHOD, FIELD}) @Retention(RUNTIME) public @interface SequenceGenerator { //表示该表主键生成策略的名称,它被引用在@GeneratedValue中设置的“generator”值中 String name(); //属性表示生成策略用到的数据库序列名称。 String sequenceName() default ""; //表示主键初识值,默认为0 int initialValue() default 0; //表示每次主键值增加的大小,例如设置1,则表示每次插入新记录后自动加1,默认为50 int allocationSize() default 50; }1
2
3
4
5
6
7
8
9
10
11
12
13TABLE:使用一个特定的数据库表格来保存主键(好麻烦。。。)@Id @GeneratedValue(strategy = GenerationType.TABLE, generator="payablemoney_gen") @TableGenerator(name = "pk_gen", table="tb_generator", pkColumnName="gen_name", valueColumnName="gen_value", pkColumnValue="PAYABLEMOENY_PK", allocationSize=1 ) private Long custId;1
2
3
4
5
6
7
8
9
10//@TableGenerator的定义: @Target({TYPE, METHOD, FIELD}) @Retention(RUNTIME) public @interface TableGenerator { //表示该表主键生成策略的名称,它被引用在@GeneratedValue中设置的“generator”值中 String name(); //表示表生成策略所持久化的表名,例如,这里表使用的是数据库中的“tb_generator”。 String table() default ""; //catalog和schema具体指定表所在的目录名或是数据库名 String catalog() default ""; String schema() default ""; //属性的值表示在持久化表中,该主键生成策略所对应键值的名称。例如在“tb_generator”中将“gen_name”作为主键的键值 String pkColumnName() default ""; //属性的值表示在持久化表中,该主键当前所生成的值,它的值将会随着每次创建累加。例如,在“tb_generator”中将“gen_value”作为主键的值 String valueColumnName() default ""; //属性的值表示在持久化表中,该生成策略所对应的主键。例如在“tb_generator”表中,将“gen_name”的值为“CUSTOMER_PK”。 String pkColumnValue() default ""; //表示主键初识值,默认为0。 int initialValue() default 0; //表示每次主键值增加的大小,例如设置成1,则表示每次创建新记录后自动加1,默认为50。 int allocationSize() default 50; UniqueConstraint[] uniqueConstraints() default {}; } /*==================================================================*/ //这里应用表tb_generator,定义为 : CREATE TABLE tb_generator ( id NUMBER NOT NULL, gen_name VARCHAR2(255) NOT NULL, gen_value NUMBER NOT NULL, PRIMARY KEY(id) )1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31AUTO:主键由程序自动控制,如数据库,环境等(测试了下MySQL使用的是TABLE)@Id @GeneratedValue(strategy = GenerationType.AUTO) private Long custId;1
2
3
# @Column
作用:指定实体类属性和数据库表之间的对应关系
属性:
name:指定数据库表的列名称。不指定则为属性名unique:是否唯一nullable:是否可以为空inserttable:是否可以插入updateable:是否可以更新columnDefinition: 定义建表时创建此列的DDLsecondaryTable: 从表名。如果此列不建在主表上(默认建在主表),该属性定义该列所在从表的名字搭建开发环境🔥