0%

JPA使用指南-1

本文采用Hibernate 3.2+作为JPA的实现方法,脱离常用的框架对 JPA 的 API 进行测试使用

Java Persistence API:用于对象持久化的 API
Java EE 5.0 平台标准的 ORM 规范,使得应用程序以统一的方式访问持久层

目前Hibernate 3.2+、TopLink 10.1+ 以及 OpenJPA 都提供了 JPA 的实现

测试项目的目录结构

文件罗列

JPA配置文件

classpath:META-INF/persistence.xml

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
31
32
33
34
35
36
37
38
39
40
41
42
43
<?xml version="1.0" encoding="utf-8" ?>
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence
http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd" version="2.1">
<!--配置持久化单元(可以配置多个,名称不能重复)W
name:用于指定持久化单元的名称
transcation-type:指定事务的类型。
JTA:Java Transcation API
RESOURCE_LOCAL:指的是本地代码事务
-->
<persistence-unit name="myPersistenceUnit-1" transaction-type="RESOURCE_LOCAL">
<!--JPA规范提供商,可以不写-->
<!-- <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>-->

<!--指定Jpa注解的实体类型位置,可以不写-->
<class>com.sust.entity.Customer</class>

<!--连接相关的一些配置,都是用hibernate的。-->
<properties>
<!--第一部分,连接数据库信息-->
<property name="hibernate.connection.driver_class" value="com.mysql.cj.jdbc.Driver"/>
<property name="hibernate.connection.url"
value="jdbc:mysql://192.168.0.113:3306/jpa?characterEncoding=utf8&amp;useUnicode=true&amp;useSSL=false&amp;serverTimezone=Asia/Shanghai"/>
<property name="hibernate.connection.username" value="root"/>
<property name="hibernate.connection.password" value="89321"/>
<!--说明:数据库的方言,用于存放不同数据库之间的SQL语句差异。-->
<property name="hibernate.dialect" value="org.hibernate.dialect.MySQL8Dialect"/>

<!--第二部分,hibernate的可选配置-->
<!--是否显示hiberante的生成的SQL语句-->
<property name="hibernate.show_sql" value="true"/>
<!--是否使用格式化输出SQL语句到控制台-->
<property name="hibernate.format_sql" value="true"/>

<!--采用何种方式生成DDL语句,update表示检测实体类的映射配置与数据库表结构是否一致,不一致,则更新数据库。-->
<property name="hibernate.hbm2ddl.auto" value="update"/>

<!--连接池的配置,这里使用的是c3p0连接池,常用的还有druid-->
<!-- <property name="hibernate.connection.provider_class"-->
<!-- value="org.hibernate.c3p0.internal.C3P0ConnectionProvider"/>-->
</properties>
</persistence-unit>
</persistence>

实体类配置

Customer.java

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
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
package com.sust.entity;

import lombok.Data;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.DynamicInsert;
import org.hibernate.annotations.DynamicUpdate;
import org.hibernate.annotations.UpdateTimestamp;

import javax.persistence.*;
import java.util.Date;

@Data
@Entity
@Table(name = "jpa_customers")
/*
*动态插入和删除
*/
@DynamicUpdate
@DynamicInsert
public class Customer {
/*-------------------------------------------------------------*/
/*
* @GeneratedValue(strategy = GenerationType.XXX)
* 1.IDENTITY:采用数据库 ID自增长的方式来自增主键字段,Oracle 不支持这种方式;
* 2.AUTO: JPA自动选择合适的策略,是默认选项;
* 3.SEQUENCE:通过序列产生主键,通过 @SequenceGenerator 注解指定序列名,MySql 不支持这种方式
* 4.TABLE:通过表产生主键,框架借由表模拟序列产生主键,使用该策略可以使应用更易于数据库移植。
* 若 @GeneratedValue 中 strategy = GenerationType.TABLE ,
* 就还要配置 generator属性与 @TableGenerator注解
*/
@GeneratedValue(strategy = GenerationType.TABLE, generator = "CustomerPKGenerated")
@TableGenerator(
name = "CustomerPKGenerated", // 与GeneratedValue中generator属性相同即可
table = "auto_increment_table", // 增长辅助表表名
pkColumnName = "PK_NAME", // 主键列
pkColumnValue = "customer_id",// 主键列中代表此表主键的字段值
valueColumnName = "PK_VALUE", //value种子的列名
allocationSize = 5 //每次增长数
)
@Id
private Integer id;
/*-------------------------------------------------------------*/
/*
* @Basic 表示一个简单的属性到数据库表的字段的映射,
* 对于没有任何标注的 getXxxx() 方法,默认即为@Basic,即普通字段可以省略
* 1.fetch: 表示该属性的读取策略,有 EAGER 和 LAZY 两种,
* 分别表示主支抓取和延迟加载,默认为 EAGER.
* 2.optional:表示该属性是否允许为null, 默认为true
*/
@Basic(fetch = FetchType.EAGER, optional = true)//这里都用了默认值
private String email;
/*-------------------------------------------------------------*/
/*
* 当实体的属性与其映射的数据库表的列不同名时需要使用@Column 标注说明
* 该属性通常置于实体的属性声明语句之前,还可与 @Id 标注一起使用,
* @Column标注也可置于属性的getter方法之前
*
* 1.name属性:用于设置映射数据库表的列名。此外,该标注还包含其它多个属性,
* 如:unique 、nullable、length 等。
*
* 2.columnDefinition 属性: 表示该字段在数据库中的实际类型.
* 通常 ORM 框架可以根据属性类型自动判断数据库中字段的类型
* 如将Integet类型变量的数据库类型指定为TINYINT:
* @Column(columnDefinition = "TINYINT")
* 但此属性对于Date类型,仍无法确定数据库中字段类型究竟是DATE,TIME还是TIMESTAMP.
* 处理需要用到@Temporal(TemporalType.XXX)来指定具体类型
*/
@Column(name = "last_name", nullable = false)//JPA生成表时不会自动转换驼峰命名
private String lastName;
@Column(length = 100, columnDefinition = "TINYINT")
private Integer age;
/*-------------------------------------------------------------*/
@CreationTimestamp
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "create_time")
private Date createTime;

@UpdateTimestamp
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "update_time")
private Date updateTime;

@Temporal(TemporalType.DATE)
private Date birthday;
/*-------------------------------------------------------------*/
}

CustomerRepo.java

这里并没有实现CURD操作,只是以插入数据的方式来展示流程,测试配置

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
31
32
33
34
package com.sust.repo;

import com.sust.entity.Customer;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;

public class CustomerRepo {
public void test() {
//1.创建 EntityManagerFactory
String persistenceUnitName = "myPersistenceUnit-1";
EntityManagerFactory entityManagerFactory =
Persistence.createEntityManagerFactory(persistenceUnitName);
//2.创建 EntityManager 对象
EntityManager entityManager = entityManagerFactory.createEntityManager();
//3.开启事务
EntityTransaction transaction = entityManager.getTransaction();
transaction.begin();
//4.执行持久化操作
Customer customer = new Customer();
customer.setLastName("Test_1");
customer.setEmail("999@sust.edu.cn");
customer.setAge(66);
entityManager.persist(customer);
//5.提交事务
transaction.commit();
//6.关闭 EntityManager 与 EntityManagerFactory
entityManager.close();
entityManagerFactory.close();
}
}

POM.xml

项目依赖jar

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.3.10.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate.javax.persistence</groupId>
<artifactId>hibernate-jpa-2.1-api</artifactId>
<version>1.0.0.Final</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.17</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
</dependency>

运行测试

运行前状态:

  • 数据库结构:

  • 自增辅助表(auto_increment_table)DDL:

    1
    2
    3
    4
    5
    6
    CREATE TABLE `auto_increment_table` (
    `id` int(11) NOT NULL AUTO_INCREMENT,
    `PK_NAME` varchar(100) NOT NULL,
    `PK_VALUE` int(11) NOT NULL,
    PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
  • auto_increment_table表数据:

id PK_NAME PK_VALUE
1 customer_id 1

运行5次 new CustomerRepo().test()后:

  • 数据库结构:

  • auto_increment_table更新为

    没有搞懂PK_VALUE的增长策略

id PK_NAME PK_VALUE
1 customer_id 31
  • 自动创建的数据表(jpa_customers)DDL:(注意主键并没有自增属性)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    CREATE TABLE `jpa_customers` (
    `id` int(11) NOT NULL,
    `age` tinyint(4) DEFAULT NULL,//注意这里的tinyint
    `birthday` date DEFAULT NULL,//注意这里的date
    `create_time` datetime(6) DEFAULT NULL,
    `email` varchar(255) DEFAULT NULL,
    `last_name` varchar(255) NOT NULL,
    `update_time` datetime(6) DEFAULT NULL,
    PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
  • jpa_customers数据:

    jpa_customers表中的主键也并不是像@TableGenerator注解中allocationSize=5属性配置的每次都增长5,而是第一次增长6,以后才是5。。。很奇怪

id age birthday create_time email last_name update_time
1 66 2019-08-22 2019-08-23 05:39:01 999@sust.edu.cn Test_1 2019-08-23 05:39:01
7 66 2019-08-22 2019-08-23 05:39:02 999@sust.edu.cn Test_1 2019-08-23 05:39:02
12 66 2019-08-22 2019-08-23 05:39:02 999@sust.edu.cn Test_1 2019-08-23 05:39:02
17 66 2019-08-22 2019-08-23 05:39:02 999@sust.edu.cn Test_1 2019-08-23 05:39:02
22 66 2019-08-22 2019-08-23 05:39:02 999@sust.edu.cn Test_1 2019-08-23 05:39:02
  • 第一次console中的sql:

    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
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    Hibernate: 
    select
    tbl.PK_VALUE
    from
    auto_increment_table tbl
    where
    tbl.PK_NAME=? for update

    Hibernate:
    insert
    into
    auto_increment_table
    (PK_NAME, PK_VALUE)
    values
    (?,?)
    Hibernate:
    update
    auto_increment_table
    set
    PK_VALUE=?
    where
    PK_VALUE=?
    and PK_NAME=?
    Hibernate:
    select
    tbl.PK_VALUE
    from
    auto_increment_table tbl
    where
    tbl.PK_NAME=? for update

    Hibernate:
    update
    auto_increment_table
    set
    PK_VALUE=?
    where
    PK_VALUE=?
    and PK_NAME=?
    Hibernate:
    insert
    into
    jpa_customers
    (age, birthday, create_time, email, last_name, update_time, id)
    values
    (?, ?, ?, ?, ?, ?, ?)

  • 之后的sql:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    Hibernate: 
    select
    tbl.PK_VALUE
    from
    auto_increment_table tbl
    where
    tbl.PK_NAME=? for update

    Hibernate:
    update
    auto_increment_table
    set
    PK_VALUE=?
    where
    PK_VALUE=?
    and PK_NAME=?
    Hibernate:
    insert
    into
    jpa_customers
    (age, birthday, create_time, email, last_name, update_time, id)
    values
    (?, ?, ?, ?, ?, ?, ?)