源码
一、ORM
1.1 回顾JDBC
在以往的与数据库的交互中,我们一般是如下图操作的

问题
- 操作繁琐
- 占位符赋值麻烦
那么如何解决呢?
- 建立实体类和表的关系
- 建立实体类中属性和表中字段的关系
这样,我们就可以直接通过调用方法,来实现增删改查。比如增加
1.2 ORM思想
思想:操作实体类就相当于操作数据库表
建立两个映射关系
- 实体类和表的映射关系
- 实体类中属性和表中字段的映射关系
好处:不用关注SQL语句
目前市面上,有好多实现ORM思想的框架
- mybatis
- hibernate
- jpa
1.3 Hibernate框架
Hibernate是一个开放源代码的对象关系映射框架。
它对JDBC进行了非常轻量级的对象封装,它将POJO与数据库表建立映射关系,是一个全自动的orm框架。
Hibernate可以自动生成SQL语句,自动执行,使得Java程序员可以随心所欲的使用对象编程思维来操纵数据库。
二、JPA概述
2.1 JPA规范
市面上有很多ORM框架,Sun公司为了实现统一,自己定义了一套ORM规范,就叫JPA(Java Persistence API)。内部是由接口和抽象类组成。
JDBC规范,如果要操纵数据库,需要用到驱动,具体实现的逻辑由驱动提供。JDBC规范的好处是,如果我们更换了数据库,只需更换数据库驱动即可,Java代码仍然可以复用。
JPA规范,实现方式有Hibernate、topLink等。JPA规范的好处是,更换实现方式,代码仍然可以复用,只需要修改实现方式即可。
2.2 JPA优势
优势
- 任何符合JPA标准的框架都遵循同样的架构,提供相同的访问API,这保证了基于JPA开发的应用经过少量的修改就能在不同的JPA框架下运行。
- JPA框架支持大数据集、事务、并发等容器级事务。
- JPA的查询语言是面向对象而非面向数据库的,它以面向对象的自然语法构造查询语句。JPA定义了JPQL,它是针对实体的一种查询语言,操作对象是实体,而不是关系数据库的表,而且能够支持批量更新和修改、Join、Group By、Having等通常只有SQL才能够提供的高级查询特性,甚至还能够支持子查询。
- JPA中能够支持面向对象的高级特性,如类之间的继承、多态和类之间的复杂关系。
2.3 JPA与Hibernate的关系
JPA规范本质上就是一种ORM规范,注意不是ORM框架,因为JPA并未提供ORM实现,它只是制定了一些规范,提供了一些编程的API接口,但是具体的实现则由服务厂商来提供实现。

三、JPA入门案例
3.1 需求
保存客户到数据库的客户表中
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 
 |  CREATE TABLE customer (
 cust_id BIGINT ( 32 ) NOT NULL AUTO_INCREMENT COMMENT '客户编号(主键)',
 cust_name VARCHAR ( 32 ) NOT NULL COMMENT '客户名称(公司名称)',
 cust_source VARCHAR ( 32 ) DEFAULT NULL COMMENT '客户信息来源',
 cust_industry VARCHAR ( 32 ) DEFAULT NULL COMMENT '客户所属行业',
 cust_level VARCHAR ( 32 ) DEFAULT NULL COMMENT '客户级别',
 cust_address VARCHAR ( 128 ) DEFAULT NULL COMMENT '客户联系地址',
 cust_phone VARCHAR ( 64 ) DEFAULT NULL COMMENT '客户联系电话',
 PRIMARY KEY ( `cust_id` )
 ) ENGINE = INNODB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8;
 
 | 
3.2 环境
步骤
- maven导入坐标
- 添加log4j配置文件
- 配置jpa的核心配置文件- 位置:配置到类路径下的一个叫做META-INF的文件夹下
- 命名:persistence.xml
- 约束配置:idea右键new->EditFileTemplate->Other->JPA->Deployment descriptors
 
- 编写客户实体类
- 配置实体类和表、类中属性和表中字段的映射关系
- 保存客户到数据库中
maven中导入坐标
| 12
 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
 
 | <?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>
 
 <groupId>top.meethigher</groupId>
 <artifactId>SpringDataJPA-notes</artifactId>
 <version>1.0</version>
 
 <properties>
 <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 <project.hibernate.version>5.4.30.Final</project.hibernate.version>
 </properties>
 
 <dependencies>
 <dependency>
 <groupId>junit</groupId>
 <artifactId>junit</artifactId>
 <version>4.13</version>
 <scope>test</scope>
 </dependency>
 
 <dependency>
 <groupId>org.hibernate</groupId>
 <artifactId>hibernate-entitymanager</artifactId>
 <version>${project.hibernate.version}</version>
 </dependency>
 
 <dependency>
 <groupId>org.hibernate</groupId>
 <artifactId>hibernate-c3p0</artifactId>
 <version>${project.hibernate.version}</version>
 </dependency>
 
 <dependency>
 <groupId>log4j</groupId>
 <artifactId>log4j</artifactId>
 <version>1.2.17</version>
 </dependency>
 
 <dependency>
 <groupId>mysql</groupId>
 <artifactId>mysql-connector-java</artifactId>
 <version>8.0.12</version>
 </dependency>
 
 </dependencies>
 
 </project>
 
 | 
添加log4j配置文件
在resources目录下添加即可
可以参照mybatis中的
| 12
 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
 
 | 
 
 
 
 
 log4j.rootLogger=debug,CONSOLE,logfile
 
 log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
 
 log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
 
 log4j.appender.CONSOLE.layout.ConversionPattern=[frame] %d{yyyy-MM-dd HH:mm:ss,SSS} - %-4r %-5p [%t] %C:%L %x - %m%n
 
 
 
 
 
 
 log4j.appender.logfile=org.apache.log4j.RollingFileAppender
 
 log4j.appender.logfile.Encoding=UTF-8
 
 log4j.appender.logfile.File=logs/root.log
 
 log4j.appender.logfile.MaxFileSize=10MB
 
 log4j.appender.logfile.MaxBackupIndex=3
 
 log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
 log4j.appender.logfile.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %F %p %m%n
 
 
 
 
 
 
 log4j.logger.club.bagedate=DEBUG,bagedate
 
 log4j.additivity.club.bagedate=false
 
 log4j.appender.bagedate=org.apache.log4j.RollingFileAppender
 log4j.appender.bagedate.Encoding=UTF-8
 log4j.appender.bagedate.File=logs/bagedate.log
 log4j.appender.bagedate.MaxFileSize=10MB
 log4j.appender.bagedate.MaxBackupIndex=3
 log4j.appender.bagedate.layout=org.apache.log4j.PatternLayout
 log4j.appender.bagedate.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %F %p %m%n
 
 | 
☆配置jpa核心文件
在Maven下的resources文件夹下创建META-INF文件夹。
在META-INF文件夹下添加persistence.xml。
利用idea中的jpa模板来复制xml约束,步骤如图。

配置持久化单元节点persistence-unit
- name:持久化单元名称
- transaction-type:事务管理方式- JTA:分布式事务管理。适用于不同的表存在于不同的数据库中
- RESOURCE_LOCAL:本地事务管理。所有的表都存在于一个数据库中
 
配置jpa实现方式
- provider标签:org.hibernate.jpa.HibernatePersistenceProvider
配置数据库信息property
- javax.persistence.jdbc.user:用户名 
- javax.persistence.jdbc.password:密码 
- javax.persistence.jdbc.driver:驱动 
- javax.persistence.jdbc.url:数据库地址 
配置jpa实现方(hibernate)信息property
- hibernate.show_sql:显示sql,值为true或false,默认false
- hibernate.hbm2ddl.auto:自动创建数据库,值有create、update、none,默认update- create:程序运行时创建表,如果有表,那就先删除再创建
- update:程序运行时创建表,如果有表,不会创建表
- none:不会创建表
 
persistence.xml
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 
 | <?xml version="1.0" encoding="UTF-8"?><persistence xmlns="http://java.sun.com/xml/ns/persistence" version="1.0">
 
 <persistence-unit name="myJpa" transaction-type="RESOURCE_LOCAL">
 
 <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
 
 <properties>
 <property name="javax.persistence.jdbc.user" value="root"/>
 <property name="javax.persistence.jdbc.password" value="root"/>
 <property name="javax.persistence.jdbc.driver" value="com.mysql.cj.jdbc.Driver"/>
 <property name="javax.persistence.jdbc.url " value="jdbc:mysql://localhost:3306/springdatajpa?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai"/>
 <property name="hibernate.show_sql" value="true"/>
 <property name="hibernate.hbm2ddl.auto" value="create"/>
 </properties>
 </persistence-unit>
 </persistence>
 
 | 
编写客户实体类
根据数据库来创建实体类
| 12
 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
 
 | package top.meethigher.demo01.domain;
 
 
 
 
 
 
 
 
 public class Customer {
 private Long custId;
 private String custName;
 private String custSource;
 private String custIndustry;
 private String custLevel;
 private String custAddress;
 private String custPhone;
 
 public Long getCustId() {
 return custId;
 }
 
 public void setCustId(Long custId) {
 this.custId = custId;
 }
 
 public String getCustName() {
 return custName;
 }
 
 public void setCustName(String custName) {
 this.custName = custName;
 }
 
 public String getCustSource() {
 return custSource;
 }
 
 public void setCustSource(String custSource) {
 this.custSource = custSource;
 }
 
 public String getCustIndustry() {
 return custIndustry;
 }
 
 public void setCustIndustry(String custIndustry) {
 this.custIndustry = custIndustry;
 }
 
 public String getCustLevel() {
 return custLevel;
 }
 
 public void setCustLevel(String custLevel) {
 this.custLevel = custLevel;
 }
 
 public String getCustAddress() {
 return custAddress;
 }
 
 public void setCustAddress(String custAddress) {
 this.custAddress = custAddress;
 }
 
 public String getCustPhone() {
 return custPhone;
 }
 
 public void setCustPhone(String custPhone) {
 this.custPhone = custPhone;
 }
 
 @Override
 public String toString() {
 return "Customer{" +
 "custId=" + custId +
 ", custName='" + custName + '\'' +
 ", custSource='" + custSource + '\'' +
 ", custIndustry='" + custIndustry + '\'' +
 ", custLevel='" + custLevel + '\'' +
 ", custAddress='" + custAddress + '\'' +
 ", custPhone='" + custPhone + '\'' +
 '}';
 }
 }
 
 | 
☆配置映射关系
使用注解配置
- 实体类和表的映射关系
- 类中属性和表中字段的映射关系
注解
- @Entity:声明该类是一个实体类
- @Table:配置实体类和表的映射关系
- @Id:表示当前属性或者变量是主键
- @GeneratedValue- strategy:配置主键的生成策略- GenerationType.IDENTITY,表示自增,前提是底层数据库必须支持自动增长,如mysql,postgresql
- GenerationType.SEQUENCE,表示序列,前提是底层数据库必须支持序列,如oracle
- GenerationType.TABLE,jpa提供的一种机制,通过在数据库中多建立一张数据库表的形式,用来存储下一个的值,帮助我们完成自增(就跟我当初做的住房公积金系统的需求一样)
- GenerationType.AUTO,由程序自动选择策略
 
 
- @Column:配置实体类属性和表中字段的映射关系
| 12
 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
 
 | @Entity@Table(name="customer")
 public class Customer {
 @Id
 @GeneratedValue(strategy = GenerationType.IDENTITY)
 @Column(name = "cust_id")
 private Long custId;
 @Column(name = "cust_name")
 private String custName;
 @Column(name = "cust_source")
 private String custSource;
 @Column(name = "cust_industry")
 private String custIndustry;
 @Column(name = "cust_level")
 private String custLevel;
 @Column(name = "cust_address")
 private String custAddress;
 @Column(name = "cust_phone")
 private String custPhone;
 
 public Long getCustId() {
 return custId;
 }
 
 public void setCustId(Long custId) {
 this.custId = custId;
 }
 
 public String getCustName() {
 return custName;
 }
 
 public void setCustName(String custName) {
 this.custName = custName;
 }
 
 public String getCustSource() {
 return custSource;
 }
 
 public void setCustSource(String custSource) {
 this.custSource = custSource;
 }
 
 public String getCustIndustry() {
 return custIndustry;
 }
 
 public void setCustIndustry(String custIndustry) {
 this.custIndustry = custIndustry;
 }
 
 public String getCustLevel() {
 return custLevel;
 }
 
 public void setCustLevel(String custLevel) {
 this.custLevel = custLevel;
 }
 
 public String getCustAddress() {
 return custAddress;
 }
 
 public void setCustAddress(String custAddress) {
 this.custAddress = custAddress;
 }
 
 public String getCustPhone() {
 return custPhone;
 }
 
 public void setCustPhone(String custPhone) {
 this.custPhone = custPhone;
 }
 
 @Override
 public String toString() {
 return "Customer{" +
 "custId=" + custId +
 ", custName='" + custName + '\'' +
 ", custSource='" + custSource + '\'' +
 ", custIndustry='" + custIndustry + '\'' +
 ", custLevel='" + custLevel + '\'' +
 ", custAddress='" + custAddress + '\'' +
 ", custPhone='" + custPhone + '\'' +
 '}';
 }
 }
 
 | 
测试保存客户到数据库中
如果是update或者create时,添加实体类字段,会添加相应的字段。区别在于一个是直接在原有数据基础上添加字段,另一个是删除表再添加
| 12
 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
 
 | public class JPATest01 {@Test
 public void testSave() {
 
 EntityManagerFactory factory = Persistence.createEntityManagerFactory("myJpa");
 
 EntityManager em = factory.createEntityManager();
 
 EntityTransaction ts = em.getTransaction();
 ts.begin();
 
 Customer customer=new Customer();
 customer.setCustName("美杜莎");
 customer.setCustIndustry("蛇人族");
 customer.setCustAddress("斗破苍穹");
 customer.setCustLevel("斗皇强者");
 customer.setCustPhone("保密");
 customer.setCustSource("保密");
 em.persist(customer);
 
 ts.commit();
 
 em.close();
 factory.close();
 
 }
 }
 
 | 
四、JPA基本操作
4.1 步骤
jpa操作步骤
- 加载配置文件创建实体管理工厂- Persistence:静态方法,createEntityManagerFactory根据持久化单元名称创建实体管理工厂
 
- 根据实体管理工厂,创建实体管理器- EntityManagerFactory:获取EntityManagerFactory对象,createEntityManager创建实体管理器- 内部维护了数据库信息
- 内部维护了缓存信息
- 维护了所有的实体管理对象
- 在创建EntityManagerFactory的过程中,会根据配置创建数据库表
- EntityManagerFactory的创建过程比较浪费资源、耗时
- 特点:EntityManagerFactory是一个线程安全的对象,也就是多个线程访问同一个EntityManagerFactory不会有线程安全问题
- 如何解决EntityManagerFactory的创建过程浪费资源、耗时的问题?- 静态代码块的形式创建一个公共的EntityManagerFactory的对象
 
 
 
- 创建事务对象,开启事务- EntityManager对象:实体类管理器- beginTransaction:创建事务对象
- presist:保存
- merge:更新
- remove:删除
- find/getReference:根据id查询- class参数:查询的数据结果需要包装成的实体类类型的的字节码
- id参数:要查询的主键的取值
- getReference获取到的是一个动态代理对象,可以通过断点看到。而且,该方法采用的是懒加载的方式。
- find获取到的就是对象本身,是立即加载。
 
 
- Transaction对象:事务- begin:开启事务
- commit:提交事务
- rollback:回滚
 
 
- 增删改查操作
- 提交事务
- 释放资源
4.2 实现
测试类
| 12
 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
 
 | public class JPATest02 {
 
 
 @Test
 public void testSave() {
 EntityManager em = JpaUtils.getEntityManager();
 EntityTransaction ts = em.getTransaction();
 ts.begin();
 Customer customer=new Customer();
 customer.setCustName("美杜莎");
 customer.setCustIndustry("蛇人族");
 customer.setCustAddress("斗破苍穹");
 customer.setCustLevel("斗皇强者");
 customer.setCustPhone("保密");
 customer.setCustSource("保密");
 em.persist(customer);
 ts.commit();
 em.close();
 }
 
 
 
 
 @Test
 public void testFind() {
 EntityManager em = JpaUtils.getEntityManager();
 EntityTransaction ts = em.getTransaction();
 ts.begin();
 
 
 
 
 
 Customer customer = em.getReference(Customer.class, 1L);
 ts.commit();
 em.close();
 }
 
 
 
 
 
 @Test
 public void testDelete() {
 EntityManager em = JpaUtils.getEntityManager();
 EntityTransaction ts = em.getTransaction();
 ts.begin();
 
 
 
 
 
 Customer reference = em.getReference(Customer.class, 1L);
 em.remove(reference);
 ts.commit();
 em.close();
 }
 
 
 
 
 
 @Test
 public void testUpdate(){
 EntityManager em = JpaUtils.getEntityManager();
 EntityTransaction ts = em.getTransaction();
 ts.begin();
 
 
 
 
 
 Customer reference = em.getReference(Customer.class, 3L);
 reference.setCustName("胡列娜");
 em.merge(reference);
 ts.commit();
 em.close();
 }
 }
 
 | 
五、JPQL复杂查询
5.1 概念
JPQL全称Java Persistence Query Language,Java持久化查询语言,以面向对象的表达式,将SQL语法和简单语义绑定在一起,可以被编译成所有主流数据库服务器上的SQL。
SQL:查询的是表和表中的字段
JPQL:查询的是实体类和表中的属性
5.2 操作
首先准备单元测试的环境
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 
 | public class JPQLTest {private static EntityManager em;
 private static EntityTransaction ts;
 @BeforeClass
 public static void beforeClass() throws Exception {
 em = JpaUtils.getEntityManager();
 ts = em.getTransaction();
 ts.begin();
 System.out.println("获取对象并开启事务");
 }
 
 @AfterClass
 public static void afterClass() throws Exception {
 ts.commit();
 em.close();
 System.out.println("提交事务并释放对象");
 }
 }
 
 | 
查询全部
SQL:select * from customer
JPQL:from top.meethigher.Customer(也可以省略全类名,只用类名即可)
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 
 | 
 
 @Test
 public void testFindAll() {
 String jpql="from Customer";
 
 Query query = em.createQuery(jpql);
 
 List resultList = query.getResultList();
 
 for (Object o :
 resultList) {
 System.out.println(o);
 }
 }
 
 | 
排序查询
SQL:select * from customer order by cust_id desc
JPQL:from top.meethigher.Customer order by custId desc(也可以省略全类名,只用类名即可)
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 
 | 
 
 @Test
 public void testFindOrById() {
 String jpql="from Customer order by custId desc ";
 
 Query query = em.createQuery(jpql);
 
 List resultList = query.getResultList();
 
 for (Object o :
 resultList) {
 System.out.println(o);
 }
 }
 
 | 
统计查询
SQL:select count(cust_id) from customer
JPQL:select count(custId) from top.meethigher.Customer (也可以省略全类名,只用类名即可)
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 
 | 
 
 @Test
 public void testFindCount() {
 String jpql="select count(custId) from Customer";
 Query query = em.createQuery(jpql);
 Object singleResult = query.getSingleResult();
 System.out.println(singleResult);
 }
 
 | 
分页查询
SQL
- 标准写法:select * from customer limit ?,?
- 省略写法:如果是从0开始,那么可省略。select * from customer limit ?
JPQL:from top.meethigher.Customer (也可以省略全类名,只用类名即可)
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 
 | 
 
 @Test
 public void testFindByPage() {
 String jpql="from Customer";
 Query query = em.createQuery(jpql);
 
 
 query.setFirstResult(1);
 
 query.setMaxResults(2);
 List resultList = query.getResultList();
 for (Object o :
 resultList) {
 System.out.println(o);
 }
 }
 
 | 
条件查询
查询名称含有xx关键字的客户
SQL:select * from customer where cust_name like ?
JPQL:from top.meethigher.Customer where custName like ?1(也可以省略全类名,只用类名即可)
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 
 | 
 
 @Test
 public void testFindByCondition() {
 String jpql="from Customer  where custName like ?1";
 Query query = em.createQuery(jpql);
 
 
 
 
 
 
 query.setParameter(1,"%美%");
 List resultList = query.getResultList();
 for (Object o :
 resultList) {
 System.out.println(o);
 }
 }
 
 |