JPA
Java Persistence API:用于对象持久化的 API
Java EE 5.0 平台标准的 ORM 规范,使得应用程序以统一的方式访问持久层。
JPA与Hibernate
JPA 是 hibernate 的一个抽象(就像JDBC和JDBC驱动的关系),不是ORM 框架。因为JPA 并未提供 ORM 实现,它只是制订了一些规范,提供了一些编程的 API 接口,但具体实现则由 ORM 厂商提供实现
Hibernate 是实现:Hibernate 除了作为 ORM 框架之外,它也是一种 JPA 实现
JPA的优势
标准化: 提供相同的 API,这保证了基于JPA 开发的企业应用能够经过少量的修改就能够在不同的 JPA 框架下运行。
简单易用,集成方便: JPA 的主要目标之一就是提供更加简单的编程模型,在 JPA 框架下创建实体和创建 Java 类一样简单,只需要使用 javax.persistence.Entity 进行注释;JPA 的框架和接口也都非常简单,
可媲美JDBC的查询能力: JPA的查询语言是面向对象的,JPA定义了独特的JPQL,而且能够支持批量更新和修改、JOIN、GROUP BY、HAVING 等通常只有 SQL 才能够提供的高级查询特性,甚至还能够支持子查询。
支持面向对象的高级特性: JPA 中能够支持面向对象的高级特性,如类之间的继承、多态和类之间的复杂关系,最大限度的使用面向对象的模型
JPA 技术
ORM 映射元数据:JPA 支持 XML 和 JDK 5.0 注解两种元数据的形式,元数据描述对象和表之间的映射关系,框架据此将实体对象持久化到数据库表中。
JPA 的 API:用来操作实体对象,执行CRUD操作,框架在后台完成所有的事情,开发者从繁琐的 JDBC和 SQL代码中解脱出来。
查询语言(JPQL):这是持久化操作中很重要的一个方面,通过面向对象而非面向数据库的查询语言查询数据,避免程序和具体的 SQL 紧密耦合。
JPA 注解
@Entity
@Entity 标注在实体类上,将映射到指定的数据库表。
@Table
@Table 标注与 @Entity 标注并列使用,@Table的name指定数据库表名。
@Table标注的catalog 和 schema 用于设置表所属的数据库目录或模式,通常为数据库名。
uniqueConstraints 选项用于设置约束条件。
@Table(name="JPA_CATEGORIES")
@Entity
public class Category {
}
@Id
@Id 标注用于声明一个实体类的属性映射为数据库的主键列。
@Id标注也可置于属性的getter方法之前。
@GeneratedValue(strategy=GenerationType.AUTO)
@Id
public Integer getId() {
return id;
}
@GeneratedValue
@GeneratedValue 用于标注主键的生成策略,通过 strategy 属性指定。
默认情况下,JPA 自动选择一个最适合底层数据库的主键生成策略:SqlServer 对应 identity,MySQL 对应 auto increment。
GenerationType:
IDENTITY:采用数据库 ID自增长的方式来自增主键字段,Oracle 不支持这种方式;
AUTO: JPA自动选择合适的策略,是默认选项;
SEQUENCE:通过序列产生主键,通过 @SequenceGenerator 注解指定序列名,MySql 不支持这种方式
TABLE:通过表产生主键,框架借由表模拟序列产生主键,使用该策略可以使应用更易于数据库移植。
@Basic
@Basic 表示一个简单的属性到数据库表的字段的映射。没有任何标注的 getXxxx() 方法,默认即为@Basic。
fetch: 表示该属性的读取策略,有 EAGER 和 LAZY 两种,分别表示主支抓取和延迟加载,默认为 EAGER.
optional:表示该属性是否允许为null, 默认为true。
@Column
映射的数据库表的列。不指定时与实体类对应。可与 @Id 与@Basic标注一起使用。
属性:
name:数据库对应列名
columnDefinition: 表示该字段在数据库中的实际类型.
@Transient
表示该属性并非一个到数据库表的字段的映射,ORM框架将忽略该属性.
@Temporal
对应Date 类型的数据有 DATE, TIME, 和 TIMESTAMP 三种精度,在进行属性映射时可使用@Temporal注解来调整精度.
@TableGenerator
@TableGenerator(name="ID_GENERATOR",
table="jpa_id_generators",
pkColumnName="PK_NAME",
pkColumnValue="CUSTOMER_ID",
valueColumnName="PK_VALUE",
allocationSize=100)
@TableGenerator 表示只想一张单独保存主键id的表
name 属性表示该主键生成策略的名称,它被引用在@GeneratedValue中设置的generator 值中
table 属性表示表生成策略所持久化的表名
pkColumnName 属性的值表示在持久化表中,该主键生成策略所对应键值的名称valueColumnName 属性的值表示在持久化表中,该主键当前所生成的值,它的值将会随着每次创建累加
pkColumnValue 属性的值表示在持久化表中,该生成策略所对应的主键
allocationSize 表示每次主键值增加的大小, 默认值为 50
JPA API
Persistence
Persistence 类是用于获取 EntityManagerFactory 实例。
entityManagerFactory = Persistence.createEntityManagerFactory(arg1,arg2);
1 带有一个参数的方法以 JPA 配置文件 persistence.xml 中的持久化单元名为参数
2 带有两个参数的方法:前一个参数含义相同,后一个参数 Map类型,用于设置 JPA 的相关属性,这时将忽略其它地方设置的属性。Map 对象的属性名必须是 JPA 实现库提供商的名字空间约定的属性名。
EntityManagerFactory
createEntityManager():用于创建实体管理器对象实例。
createEntityManager(Map map):用于创建实体管理器对象实例的重载方法,Map 参数用于提供 EntityManager 的属性。
isOpen():检查 EntityManagerFactory 是否处于打开状态。实体管理器工厂创建后一直处于打开状态,除非调用close()方法将其关闭。
close():关闭 EntityManagerFactory 。 EntityManagerFactory 关闭后将释放所有资源,isOpen()方法测试将返回 false,其它方法将不能调用,否则将导致IllegalStateException异常。
EntityManager
EntityManager 是完成持久化操作的核心对象。实体作为普通 Java 对象,只有在调用 EntityManager 将其持久化后才会变成持久化对象。EntityManager 对象在一组实体类与底层数据源之间进行 O/R 映射的管理。它可以用来管理和更新 Entity Bean, 根椐主键查找 Entity Bean, 还可以通过JPQL语句查询实体。
实体的状态:
新建状态: 新创建的对象,尚未拥有持久性主键。
持久化状态:已经拥有持久性主键并和持久化建立了上下文环境
游离状态:拥有持久化主键,但是没有与持久化建立上下文环境
删除状态: 拥有持久化主键,已经和持久化建立上下文环境,但是从数据库中删除。
方法:
find:返回指定的 OID 对应的实体类对象,若不存在返回null
getReference:与find类似,不同之处在不会立即加载数据库中的信息,只有第一次真正使用此 Entity 的属性才加载,所以如果此 OID 在数据库不存在,getReference() 不会返回 null 值, 而是抛出EntityNotFoundException
persist :持久化对象,将新创建的 Entity 纳入到 EntityManager 的管理。
remove :删除实例.
merge :用于处理 Entity 的同步。即数据库的插入和更新操作
flush ():同步持久上下文环境,即将持久上下文环境的所有未保存实体的状态信息保存到数据库中。
setFlushMode (FlushModeType flushMode):设置持久上下文环境的Flush模式。
FlushModeType.AUTO 为自动更新数据库实体,
FlushModeType.COMMIT 为直到提交事务时才更新数据库记录。
getFlushMode ():获取持久上下文环境的Flush模式。
EntityTransaction
用来管理资源层实体管理器的事务操作。
关系映射
双向一对多及多对一映射
可以在 one 方指定 @OneToMany 注释并设置 mappedBy 属性,以指定它是这一关联中的被维护端,many 为维护端。
在 many 方指定 @ManyToOne 注释,并使用 @JoinColumn 指定外键名称
1-n
可以使用 @OneToMany 的 fetch 属性来修改默认的加载策略
可以通过 @OneToMany 的 cascade 属性来修改默认的删除策略.
注意: 若在 1 的一端的 @OneToMany 中使用 mappedBy 属性, 则 @OneToMany 端就不能再使用 @JoinColumn 属性了.
@OneToMany(fetch=FetchType.LAZY,cascade={CascadeType.REMOVE},mappedBy="customer")
public Set<Order> getOrders() {
return orders;
}
n-1
映射单向 n-1 的关联关系
使用 @ManyToOne 来映射多对一的关联关系
使用 @JoinColumn 来映射外键.
可使用 @ManyToOne 的 fetch 属性来修改默认的关联属性的加载策略
@JoinColumn(name="CUSTOMER_ID")
@ManyToOne(fetch=FetchType.LAZY)
public Customer getCustomer() {
return customer;
}
双向一对一映射
在双向的一对一关联中,需要在关系被维护端(inverse side)中的 @OneToOne 注释中指定 mappedBy,以指定是这一关联中的被维护端。同时需要在关系维护端(owner side)建立外键列指向关系被维护端的主键列。
对于不维护关联关系, 没有外键的一方, 使用 @OneToOne 来进行映射, 建议设置 mappedBy
@OneToOne(mappedBy="mgr")
public Department getDept() {
return dept;
}
使用 @OneToOne 来映射 1-1 关联关系。
若需要在当前数据表中添加主键则需要使用 @JoinColumn 来进行映射. 注意, 1-1 关联关系, 所以需要添加 unique=true
@JoinColumn(name="MGR_ID", unique=true)
@OneToOne(fetch=FetchType.LAZY)
public Manager getMgr() {
return mgr;
}
双向多对多关联关系
在双向多对多关系中,必须指定一个关系维护端(owner side),可以通过 @ManyToMany 注释中指定 mappedBy 属性来标识其为关系维护端。
被维护端:
@ManyToMany(mappedBy="categories")
public Set<Item> getItems() {
return items;
}
维护端:
使用 @ManyToMany 注解来映射多对多关联关系
使用 @JoinTable 来映射中间表
1. name 指向中间表的名字
2. joinColumns 映射当前类所在的表在中间表中的外键
name 指定外键列的列名
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;
}
缓存
一级缓存
JPA在同一个EntityManager下查询同一条记录只执行一次查询。
Customer customer = entityManager.find(Customer.class, 1);
Customer customer2 = entityManager.find(Customer.class, 1);
第二次从第一次查询过后缓存中拿取。
二级缓存
<shared-cache-mode> 节点配置
ALL:所有的实体类都被缓存
NONE:所有的实体类都不被缓存.
ENABLE_SELECTIVE:标识 @Cacheable(true) 注解的实体类将被缓存
DISABLE_SELECTIVE:缓存除标识 @Cacheable(false) 以外的所有实体类
UNSPECIFIED:默认值,JPA 产品默认值将被使用
例:
<shared-cache-mode>ENABLE_SELECTIVE</shared-cache-mode>
然后再实体类上标注@Cacheable(true)
JPQL
Java Persistence Query Language 的简称。
Query接口封装了执行数据库查询的相关方法。
String jpql = "FROM Customer c WHERE c.age > ?";
Query query = entityManager.createQuery(jpql);
query.setParameter(1, 1);
List<Customer> customers = query.getResultList();
使用缓存查询
query = entityManager.createQuery(jpql).setHint(QueryHints.HINT_CACHEABLE, true);
获取单个结果
Query query = entityManager.createQuery(
"select max(o.id) from Orders o");
Object result = query.getSingleResult();
Long max = (Long)result;
JPQL函数
concat(String s1, String s2):字符串合并/连接函数。
substring(String s, int start, int length):取字串函数。
trim([leading|trailing|both,] [char c,] String s):从字符串中去掉首/尾指定的字符或空格。
lower(String s):将字符串转换成小写形式。
upper(String s):将字符串转换成大写形式。
length(String s):求字符串的长度。
locate(String s1, String s2[, int start]):从第一个字符串中查找第二个字符串(子串)出现的位置。若未找到则返回0。
算术函数主要有 abs、mod、sqrt、size 等。Size 用于求集合的元素个数。
日期函数主要为三个,即 current_date、current_time、current_timestamp.
使用JPA持久化对象的步骤
1 添加依赖jar包
hibernate-release-xxx.Final\lib\required\*.jar
hibernate-release-xxx.Final\lib\jpa\*.jar
Mysql-connector-xxx.jar
2 在SRC/META-INF下创建 persistence.xml, 在这个文件中配置持久化单元
需要指定跟哪个数据库进行交互;
需要指定 JPA 使用哪个持久化的框架以及配置该框架的基本属性
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.jpa.helloworld.Customer</class>
<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="123456"/>
<!-- 配置 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>
3 创建实体类
Customer:
@Cacheable(true)
@Table(name="JPA_CUTOMERS")
@Entity
public class Customer {
private Integer id;
private String lastName;
private String email;
private int age;
private Date createdTime;
private Date birth;
public Customer() {}
public Customer(String lastName, int age) {
this.lastName = lastName;
this.age = 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
public String getInfo(){
return "lastName: " + lastName + ", email: " + email;
}
}
4 使用 JPA API 完成数据增加、删除、修改和查询操作
@Test
public void testFind() {
EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("jpa-1");
EntityManager entityManager = entityManagerFactory.createEntityManager();
Customer customer = entityManager.find(Customer.class, 1);
}