一、JPA是什么?
Java Persistence API:用于对象持久化的 API
Java EE 5.0 平台标准的 ORM 规范,使得应用程序以统一的方式访问持久层
JPA和hibernate之间的关系
JPA 是 hibernate 的一个抽象(就像JDBC和JDBC驱动的关系):
JPA 是规范:JPA 本质上就是一种 ORM 规范,不是ORM 框架 —— 因为 JPA 并未提供 ORM 实现,它只是制订了一些规范,提供了一些编程的 API 接口,但具体实现则由 ORM 厂商提供实现
Hibernate 是实现:Hibernate 除了作为 ORM 框架之外,它也是一种 JPA 实现
从功能上来说, JPA 是 Hibernate 功能的一个子集
JPA供应商
JPA 的目标之一是制定一个可以由很多供应商实现的 API,目前Hibernate 3.2+、TopLink 10.1+ 以及 OpenJPA 都提供了 JPA 的实现
Hibernate
JPA 的始作俑者就是 Hibernate 的作者
Hibernate 从 3.2 开始兼容 JPA
OpenJPA
OpenJPA 是 Apache 组织提供的开源项目
TopLink
TopLink 以前需要收费,如今开源了
JPA的优势
1·标准化: 提供相同的 API,这保证了基于JPA 开发的企业应用能够经过少量的修改就能够在不同的 JPA 框架下运行。
2·简单易用,集成方便: JPA 的主要目标之一就是提供更加简单的编程模型,在 JPA 框架下创建实体和创建 Java 类一样简单,只需要使用 javax.persistence.Entity 进行注释;JPA 的框架和接口也都非常简单,
3·可媲美JDBC的查询能力: JPA的查询语言是面向对象的,JPA定义了独特的JPQL,而且能够支持批量更新和修改、JOIN、GROUP BY、HAVING 等通常只有 SQL 才能够提供的高级查询特性,甚至还能够支持子查询。
4·支持面向对象的高级特性: JPA 中能够支持面向对象的高级特性,如类之间的继承、多态和类之间的复杂关系,最大限度的使用面向对象的模型
JPA 包括 3方面的技术
1·ORM 映射元数据:JPA 支持 XML
和 JDK 5.0 注解两种元数据的形式,元数据描述对象和表之间的映射关系,框架据此将实体对象持久化到数据库表中。
2·JPA 的 API:用来操作实体对象,执行CRUD操作,框架在后台完成所有的事情,开发者从繁琐的 JDBC和 SQL代码中解脱出来。
3·查询语言(JPQL):这是持久化操作中很重要的一个方面,通过面向对象而非面向数据库的查询语言查询数据,避免程序和具体的 SQL 紧密耦合。
persistence.xml
配置信息:
- 配置什么ORM产品,来实现JPA【1,实际上配置的是 javax.persistence.spi.PersistenceProvider接口 2,若JPA项目中只有一个JPS实现产品,则也可以不配置该节点。】
- 配置数据库连接信息
- 配置JPA实现产品的基本属性(配置hiBenate的基本属性)
其中:
transaction-type:指定JPA的事务处理策略,属性值:(下面2种)
RESOURCE_LOCAL:数据库级别的事务,只能支持一种数据库。
JTA:支持分布式事务
使用JPA持久化对象的步骤
1·创建 persistence.xml, 在这个文件中配置持久化单元
2·需要指定跟哪个数据库进行交互;
3·需要指定 JPA 使用哪个持久化的框架以及配置该框架的基本属性
4·创建实体类, 使用 annotation 来描述实体类跟数据库表之间的映射关系.
5·使用 JPA API 完成数据增加、删除、修改和查询操作
6·创建 EntityManagerFactory (对应 Hibernate 中的 SessionFactory);
7·创建 EntityManager (对应 Hibernate 中的Session);
二、创建表的代码
package com.lq.jpa.demo1;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
public class Main {
public static void main(String[] args) {
/*
* - 创建EntityManagerFactory
- 创建EntityManager
- 开启事物
- 进行持久化事物
- 提交事务
- 关闭EntityManager
- 关闭EntityManagerFactory
*/
//创建EntityManagerFactory EntityManager类似于Hibernate的sessionFactory
String persistenceUnitName = "jpa-1";
EntityManagerFactory emf = Persistence.createEntityManagerFactory(persistenceUnitName);
EntityManager em = emf.createEntityManager();
//开启事物
EntityTransaction transaction = em.getTransaction();
transaction.begin();
//进行持久化事物
Customer c= new Customer();
c.setAge(1);
c.setEmail("[email protected]");
c.setLastName("哈喽JPA");
//数据保存
em.persist(c);
//提交事务
transaction.commit();
//关闭
em.close();
emf.close();
}
}
//Customer类
//table如果没有写,表名就是实体类的名称
//get方法上面加入列名。其他可以不写,默认添加了@Basic注解,列名是实体类属性名
@Entity
@Table(name="JPA_CUSTOMERS")
public class Customer {
private Integer id;
private String lastName;
private String email;
private int age;
@GeneratedValue(strategy=GenerationType.AUTO)
@Id
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
@Column(name="LAST_NAME",length=50,nullable=false)
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Temporal(TemporalType.TIMESTAMP)
public Date getCreatedTime() {
return createdTime;
}
public void setCreatedTime(Date createdTime) {
this.createdTime = createdTime;
}
@Temporal(TemporalType.DATE)//调整时间精度
public Date getBirth() {
return birth;
}
public void setBirth(Date birth) {
this.birth = birth;
}
//@Transient注解:这个属性不是数据库表中的字段。必须加这个注解,否则运行不通过。
@Transient
public String getinfo() {
return "info";
}
}
@Entity
@Table(name=”JPA_CUSTOMERS”)
//调整时间精度
@Temporal(TemporalType.TIMESTAMP)
@Temporal(TemporalType.DATE)
//@Transient注解:这个属性不是数据库表中的字段。必须加这个注解,否则运行不通过。
@Transient
//自增主键【不用set值,否则出错】
@GeneratedValue(strategy=GenerationType.AUTO)
@Id
JPA的测试,init()中先进行三个对象的建立以及事物的开启,destory()中进行事物的提交,和2个对象的关闭。测试方法中,对JPA操作数据库的方法进行测试。前面2个方法,加上@Before和@After注解
EntityManager:
在JPA规范中,EntityManager是完成持久化操作的核心对象。在调用EntityManager:将实体(也就是普通java对象)
将其持久化后,变成持久化对象。EntityManager对象在实体类和数据源之间进行映射管理,可以来管理
更新Entity Bean,根据主键查找Entity Bean,还可以根据JPQL语句查询实体。
实体的状态有:
1.新建状态:新建的对象,尚未拥有持久性主键。
2.持久性状态:已经拥有持久性主键,和持久化建立上下文环境
3.游离状态:拥有持久化主键,但是没有和持久化,建立上下文环境。
4.删除状态:拥有持久化主键,已经和持久化建立上下文环境,但是从数据库删除。
==============================
entityManager.merge()类似于hibernate中session的saveAndUpdate()
1.传入的是临时对象
把临时对象【没有设置id属性值】持久化到数据库:
首先创建一个临时对象,并进行赋值,把对象传入merge()方法中,生成持久化对象。
临时对象没有id属性值,持久化对象有id值。
2.传入的是游离对象【设置有id属性值】
即传入的对象有 OID.
//1. 若在 EntityManager 缓存中没有该对象【如果有对象,游离对象属性复制到缓存中,对缓存中的对象,进行update】
//2. 若在数据库中也没有对应的记录【如果有记录,复制到新对象中,进行update操作】
//3. JPA 会创建一个新的对象, 然后把当前游离对象的属性复制到新创建的对象中
//4. 对新创建的对象执行 insert 操作.
【2个ID相同的对象,进行关联,JPA可以,它中有一个复制的功能,entityManager始终是对一个对象进行关联。
hibernate中session不能和2个对象进行关联,会报错。】
flush方法同hibernate中session的flush()方法,会发送update语句,但是没有提交。
reflush方法同session中的reflush方法。
JDQL查询时:EntityManager 使用的方法有:
createQuery(String sqlString)创建一个查询对象
createNamedQuery(String name)根据命名查询语句,创建查询对象。
createNativeQuery(String sqlString)使用标准SQL语句
createNativeQuery(String sqlString,String resultSetMapping)使用标准SQL,并指定结果集Map的名称。
EntityTransaction接口管理事务。
三、table主键生成策略
2种方式:
第一:上面代码所示,数据库自
增的方式,生成的简单逐1递增的主键
第二:如下所示,通过第2张表来生成主键。自定义步长。
【创建第二张表,和实体类中方法注解】
@TableGenerator(name="ID_GENERATOR",
table="jpa_id_generators",
pkColumnName="PK_NAME",//列
pkColumnValue="CUSTOMER_ID",//行
valueColumnName="PK_VALUE",//点
allocationSize=100)
@GeneratedValue(strategy=GenerationType.TABLE,generator="ID_GENERATOR")
//@GeneratedValue(strategy=GenerationType.AUTO)
@Id
public Integer getId() {
return id;
}
1,类似于get方法的find()方法。使用类似load的方法,getReference()具体使用到属性的时候,才向数据库发送sql。
在使用的属性的时候,会创建代理对象,出现懒加载异常问题。因为他底层使用的是hibernate相应的方法。
2,persist()方法,类似hibernate的save()方法,把对象从临时状态,转化为持久化状态。
这里:如果对象中有id属性,不能执行insert操作,否则会抛出异常。但是在hibernate中,没问题。
3,entityManger.remove()方法,只能移除持久化对象,而hibernate可以移除游离对象和持久化对象。
三、多对一关系映射
实体类:创建多的一方的表:
private Customer customer;
//映射单向 n-1 的关联关系
//使用 @ManyToOne 来映射多对一的关联关系
//使用 @JoinColumn 来映射外键.
//可使用 @ManyToOne 的 fetch 属性来修改默认的关联属性的加载策略
@JoinColumn(name="CUSTOMER_ID")
@ManyToOne(fetch=FetchType.LAZY)
public Customer getCustomer() {
return customer;
}
测试类:
//删除时候,不能删除1 的一端。
//默认情况下, 使用左外连接的方式来获取 n 的一端的对象和其关联的 1 的一端的对象.
//可使用 @ManyToOne 的 fetch 属性来修改默认的关联属性的加载策略
@Test
public void testManyToOneFind(){
Order order = entityManager.find(Order.class, 1);
System.out.println(order.getOrderName()); System.out.println(order.getCustomer().getLastName());
}
四、单向一对多映射
persist()
先保存1,再保存n。或者是相反的顺序,都没有用的。
一定会出现多个update语句,因为n端的插入时,不会插入外键列。
find()
对关联的多的一方,默认使用懒加载的策略。
可以通过@OneToMany中fetch属性,来修改加载策略。
remove()
删除1的一方,默认情况下,先把外键值设置null,再删除n表中的记录。
可以通过@OneToMany中cascade属性,来修改删除策略。
update()
@Test
public void testUpdate(){
Customer customer = entityManager.find(Customer.class, 10);
customer.getOrders().iterator().next().setOrderName("O-XXX-10");
}
五、双向多对一映射
保证实体类里面属性 列名设置一样。
不维护外间关系,直接操作数据库,会出现多条SQL语句的麻烦。
解决:
由多的一方来维护外键关系,
//1端的类:
private Set<Order> orders = new HashSet<>();
//映射单向 1-n 的关联关系
//使用 @OneToMany 来映射 1-n 的关联关系
//使用 @JoinColumn 来映射外键列的名称
//可以使用 @OneToMany 的 fetch 属性来修改默认的加载策略
//可以通过 @OneToMany 的 cascade 属性来修改默认的删除策略.
//注意: 若在 1 的一端的 @OneToMany 中使用 mappedBy 属性, 则 @OneToMany 端就不能再使用 @JoinColumn 属性了.(mappedBy 中是 多的一端里面的属性customer,多的一方维护。自己不维护。)
// @JoinColumn(name="CUSTOMER_ID")
@OneToMany(fetch=FetchType.LAZY,cascade={CascadeType.REMOVE},mappedBy="customer")
public Set<Order> getOrders() {
return orders;
}
六、双向一对一映射
Manager .java
//对于不维护关联关系, 没有外键的一方, 使用 @OneToOne 来进行映射, 建议设置 mappedBy=true,有外键的一方维护关联关系,自己不维护。
//先保存不维护外键关系的一方。就是 先保存没有外键的一方。
@OneToOne(mappedBy="mgr")
public Department getDept() {
return dept;
}
Department.java
//使用 @OneToOne 来映射 1-1 关联关系。
//若需要在当前数据表中添加主键则需要使用 @JoinColumn 来进行映射. 注意, 1-1 关联关系, 所以需要添加 unique=true
@JoinColumn(name="MGR_ID", unique=true)
@OneToOne(fetch=FetchType.LAZY)
public Manager getMgr() {
return mgr;
}
//1. 默认情况下, 若获取不维护关联关系的一方, 则也会通过左外连接获取其关联的对象.
//可以通过 @OneToOne 的 fetch 属性来修改加载策略. 但依然会再发送 SQL 语句来初始化其关联的对象
//这说明在不维护关联关系的一方, 不建议修改 fetch 属性.
@Test
public void testOneToOneFind2(){
Manager mgr = entityManager.find(Manager.class, 1);
System.out.println(mgr.getMgrName());
System.out.println(mgr.getDept().getClass().getName());
}
七、多对多的保存
//Category 类
private Set<Item> items = new HashSet<>();
@ManyToMany(mappedBy="categories")
public Set<Item> getItems() {
return items;
}
//Item 类:
private Set<Category> categories = new HashSet<>();
//使用 @ManyToMany 注解来映射多对多关联关系
//使用 @JoinTable 来映射中间表
//1. name 指向中间表的名字
//2. joinColumns 映射当前类所在的表在中间表中的外键
//2.1 name 指定外键列的列名
//2.2 referencedColumnName 指定外键列关联当前表的哪一列
//3. inverseJoinColumns 映射关联的类所在中间表的外键
@JoinTable(name="ITEM_CATEGORY",
joinColumns={@JoinColumn(name="ITEM_ID", referencedColumnName="ID")},
inverseJoinColumns={@JoinColumn(name="CATEGORY_ID", referencedColumnName="ID")})
@ManyToMany
public Set<Category> getCategories() {
return categories;
}
//测试类:
//对于关联的集合对象, 默认使用懒加载的策略.
//使用维护关联关系的一方获取, 还是使用不维护关联关系的一方获取, SQL 语句相同.
@Test
public void testManyToManyFind(){
// Item item = entityManager.find(Item.class, 5);
// System.out.println(item.getItemName());
//
// System.out.println(item.getCategories().size());
Category category = entityManager.find(Category.class, 3);
System.out.println(category.getCategoryName());
System.out.println(category.getItems().size());
}
八、二级缓存
首先,persistence.xml中进行配置,和hibernate的二级缓存,配置相同。
步骤:导包,添加一个新的配置文件,配置中添加代码,实体类上添加注解@Cacheable(true)。
persistence.xml代码如下:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0"
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">
<persistence-unit name="jpa-1" transaction-type="RESOURCE_LOCAL">
<!--
配置使用什么 ORM 产品来作为 JPA 的实现
1. 实际上配置的是 javax.persistence.spi.PersistenceProvider 接口的实现类
2. 若 JPA 项目中只有一个 JPA 的实现产品, 则也可以不配置该节点.
-->
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<!-- 添加持久化类 -->
<class>com.atguigu.jpa.helloworld.Customer</class>
<class>com.atguigu.jpa.helloworld.Order</class>
<class>com.atguigu.jpa.helloworld.Department</class>
<class>com.atguigu.jpa.helloworld.Manager</class>
<class>com.atguigu.jpa.helloworld.Item</class>
<class>com.atguigu.jpa.helloworld.Category</class>
<!--
配置二级缓存的策略 【放在后面】
ALL:所有的实体类都被缓存
NONE:所有的实体类都不被缓存.
ENABLE_SELECTIVE:标识 @Cacheable(true) 注解的实体类将被缓存
DISABLE_SELECTIVE:缓存除标识 @Cacheable(false) 以外的所有实体类
UNSPECIFIED:默认值,JPA 产品默认值将被使用
-->
<shared-cache-mode>ENABLE_SELECTIVE</shared-cache-mode>
<properties>
<!-- 连接数据库的基本信息 -->
<property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
<property name="javax.persistence.jdbc.url" value="jdbc:mysql:///jpa"/>
<property name="javax.persistence.jdbc.user" value="root"/>
<property name="javax.persistence.jdbc.password" value="1230"/>
<!-- 配置 JPA 实现产品的基本属性. 配置 hibernate 的基本属性 -->
<property name="hibernate.format_sql" value="true"/>
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.hbm2ddl.auto" value="update"/>
<!-- 二级缓存相关 -->
<property name="hibernate.cache.use_second_level_cache" value="true"/>
<property name="hibernate.cache.region.factory_class" value="org.hibernate.cache.ehcache.EhCacheRegionFactory"/>
<property name="hibernate.cache.use_query_cache" value="true"/>
</properties>
</persistence-unit>
</persistence>