源码
一、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 需求
保存客户到数据库的客户表中
1 2 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中导入坐标
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
| <?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中的
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
|
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
1 2 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>
|
编写客户实体类
根据数据库来创建实体类
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 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:配置实体类属性和表中字段的映射关系
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 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时,添加实体类字段,会添加相应的字段。区别在于一个是直接在原有数据基础上添加字段,另一个是删除表再添加
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
| 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 实现
测试类
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
| 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 操作
首先准备单元测试的环境
1 2 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(也可以省略全类名,只用类名即可)
1 2 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(也可以省略全类名,只用类名即可)
1 2 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 (也可以省略全类名,只用类名即可)
1 2 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 (也可以省略全类名,只用类名即可)
1 2 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(也可以省略全类名,只用类名即可)
1 2 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); } }
|