JPA概述
JPA是Java Persistence API的简称,中文名Java持久层API,是JDK 5.0注解或XML描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中。
Sun引入新的JPA ORM规范出于两个原因:其一,简化现有Java EE和Java SE应用开发工作;其二,Sun希望整合ORM技术,实现天下归一。
入门案例
在开始之前,我们先用JPA写一个入门案例。
在Eclipse中创建一个JPA Project:
JPA version选择2.0即可。
项目创建好后,先导入项目jar包,这里我们用HIbernate作为JPA的实现产品,所以导入Hibernate的jar包、JPA的jar包和MySQL数据库的驱动。然后在src目录下创建持久化类Customer:
package com.wwj.jpa.helloworld;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
@Table(name = "JPA_CUSTOMERS")
@Entity
public class Customer {
private Integer id;
private String lastName;
private String email;
private Integer age;
@GeneratedValue(strategy = GenerationType.AUTO)
@Id
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
@Column(name = "LAST_NAME")
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 Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
接着我们打开META-INF目录下的persistence.xml文件,这是JPA的配置文件,对其做如下配置:
<?xml version="1.0" encoding="UTF-8"?>
<persistence 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" version="2.0"> <persistence-unit name="jpa_demo" transaction-type="RESOURCE_LOCAL">
<!-- 声明JPA的实现产品为Hibernate,如果项目中的实现产品只有一种,则可以不写该配置 -->
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<!-- 添加持久化类 -->
<class>com.wwj.jpa.helloworld.Customer</class>
<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实现产品的基本属性 -->
<property name="hibernate.hbm2ddl.auto" value="update"/>
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.format_sql" value="true"/>
</properties>
</persistence-unit>
</persistence>
最后来编写测试代码:
package com.wwj.jpa.helloworld;
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) {
//1、创建EntityManagerFactory
EntityManagerFactory factory = Persistence.createEntityManagerFactory("jpa_demo");
//2、创建EntityManager
EntityManager manager = factory.createEntityManager();
//3、开启事务
EntityTransaction transaction = manager.getTransaction();
transaction.begin();
//4、进行持久化操作
Customer customer = new Customer();
customer.setAge(20);
customer.setEmail("zhangsan@163.com");
customer.setLastName("zhangsan");
manager.persist(customer);
//5、提交事务
transaction.commit();
//6、释放资源
manager.close();
factory.close();
}
}
有Hibernate基础的话你会发现,JPA的实现和Hibernate如出一辙,先获取工厂实例,然后通过工厂获得EntityManager,接着开启事务,进行持久化操作,然后提交事务,最后释放资源。
我们运行测试代码,然后查询数据表:
到这里入门案例就编写完成了。
基本注解
上面的案例中用到了一些基本注解,容我一一介绍。
- @Entity:该注解用于实体类声明语句之前, 指出该Java类为实体类,将该实体类映射到数据表中。如果声明一个Customer类,它将映射到数据库中的customer表上
- @Table:当实体类与其映射的数据库表名不同名时,需要使用@Table注解说明,该注解与@Entity注解并列使用,置于实体类声明语句之前,可写于单独语句行,也可与声明语句同行。@Table注解的常用属性为name,用于指定映射的数据库表名
- @Id:该注解用于声明一个实体类的属性映射为数据库的主键列,该属性通常置于属性声明语句之前,可与声明语句同行,也可写在单独行上,@Id还可以置于属性的getter方法之前
- @GeneratedValue:该注解用于声明主键生成策略,通过strategy属性指定。默认情况下,JPA会自动选择一个最适合底层数据库的主键生成策略:SqlServer对应identity,MySQL对应auto increment。strategy属性提供了几种可选择的策略。
一 IDENTITY:采用数据库ID自增长的方式来自增主键字段,注:Oracle不支持该方式
一 AUTO:JPA自动选择合适的策略
一 SEQUENCE:通过序列产生主键,通过@SequenceGenerator注解指定序列名,注:MySQL不支持该方式
一 TABLE:通过表产生主键,框架借由表模拟序列产生主键,使用该策略可以使应用更易于数据库移植 - @Basic:该注解表示一个简单的属性到数据库表的字段映射,对于没有任何注解的getXXX()方法,默认注解为@Basic,比如上述案例中,email、age属性都没有添加注解,则其默认注解了@Basic
- @Column:当实体类的属性与其映射的数据库表的列不同名时,需要使用@Column注解进行声明,该注解通常置于实体类的属性声明语句之前,还可与@Id注解一起使用。@Column注解的常用属性是name,用于设置映射数据库表的列名。此外,该注解还包含其它属性,如:nullable、length、unique等
@Transient注解
在某些特定的场景下,例如需要在实体类中添加一个提供实体类信息的工具方法。而在前面我们说到,在实体类中,对于没有添加任何注解的属性,JPA会默认为其添加@Basic注解,所以该工具方法也会被映射到数据库表中,但显然我们并不希望这样,这个时候我们就需要用到@Transient注解。
该注解表示该属性并非一个需要映射到数据库表的属性,ORM框架将会忽略它。所以需要注意的是,如果一个属性并不需要映射到数据库表中,就必须将其注解为@Transient,如果不添加注解,JPA将会默认添加@Basic注解。@Transient public String getMessage() { return "lastname:" + lastName + "age" + age; }
@Temporal注解
有时候需要将用户的生日作为属性映射到数据库表中,于是你在Customer类中添加Date类型的birthday属性。但是运行后你会发现,数据表所对应的birthday列的类型为datetime类型。我们知道,datetime类型是包含日期和时间信息的值,而生日仅仅只是日期而并不需要时间,那么怎么对映射列的类型做修改呢?我们需要用到@Temporal注解。
@Temporal(TemporalType.Date)
public Date getBirthday() {
return birthday;
}
该注解可以在进行属性映射的时候调整Date类型的精度。
到这里JPA的注解就讲完了,相信屏幕前的你也没有看过瘾吧,没关系,我们继续深入了解一下JPA的API。
Persistence
Persistence类是用于获取EntityManagerFactory实例的,该类包含了一个名为createEntityManagerFactory的静态方法。
createEntityManagerFactory方法有如下两个重载:
- 带有一个参数的方法,方法参数为persistence.xml中的持久化单元名
该单元名可任意,前面编写的案例就是用了这样一种获取方式得到EntityManagerFactory对象 - 带有两个参数的方法,第一个参数同样为persistence.xml中的持久化单元名,后一个参数是一个Map类型,用于设置JPA的相关属性,如果通过Map集合设置了属性,JPA将忽略其它地方设置的属性。Map集合中存储的对象属性名必须是JPA实现库提供商的名称空间约定的属性名
Map<String,Object> properties = new HashMap<String,Object>();
properties.put("hibernate.show_sql",false);
EntityManagerFactory factory = Persistence.createEntityManagerFactory("jpa_demo", properties);
通过这样的方式也可以获得EntityManagerFactory 对象,我们运行程序,程序正常运行,并且控制台中并未发现sql语句的输出,说明我们在Map集合中添加的属性配置生效了
EntityManagerFactory
EntityManagerFactory与Hibernate中的SessionFactory是类似的,该接口定义了如下方法:
- createEntityManager():该方法用于创建实体管理器对象实例
- EntityManager(Map map):该方法用于创建实体管理器对象实例的重载方法,Map参数用于提供EntityManager的属性
- isOpen():该方法用于检查EntityManagerFactory是否处于打开状态,事实上,实体管理器工厂创建后一直处于打开状态,除非调用close()方法将其关闭
- close():该方法用于关闭EntityManagerFactory,EntityManagerFactory 关闭后将释放所有资源,调用isOpen()方法将返回false,其它方法不能调用,否则抛出异常
EntityManager
EntityManager类的方法非常简单,有Hibernate基础的话看一眼就掌握了,我就直接通过代码演示了。
find()方法
@Test
public void testFind() {
Customer customer = entityManager.find(Customer.class,1);
System.out.println(customer);
}
运行结果:
Customer [id=1, lastName=zhangsan, email=zhangsan@163.com, age=20, birthday=2019-10-03]
该方法类似Hibernate中Session对象的get()方法
getReference()方法
@Test
public void testGetReference() {
Customer customer = entityManager.getReference(Customer.class,1);
System.out.println(customer);
}
运行结果:
Customer [id=1, lastName=zhangsan, email=zhangsan@163.com, age=20, birthday=2019-10-03]
虽然这两个方法的效果一样,但是getReference()方法其实获得的是代理对象,只有真正去使用该对象才会去发送sql语句,该方法类似于Hibernate中Session对象的load()方法,所以该方法同样会出现懒加载异常问题, 就是在使用该对象之前把EntityManager关闭,就会抛出懒加载异常。
persist()方法
@Test
public void testPersist() {
Customer customer = new Customer();
customer.setAge(20);
customer.setBirthday(new Date());
customer.setEmail("lisi@163.com");
customer.setLastName("lisi");
entityManager.persist(customer);
}
运行后查询数据库表:
该方法类似于Hibernate中Session对象的save()方法,会将对象由瞬时态变为持久态。 但两者也有不同之处,如果为对象设置了id属性,persist()方法将会抛出异常,而save()方法并不会。
remove()方法
@Test
public void testRemove() {
Customer customer = entityManager.find(Customer.class, 2);
entityManager.remove(customer);
}
运行后查询数据表:
该方法类似于Hibernate中Session对象的delete()方法,但和delete()方法不同的是,remove()方法只能删除持久化对象,所以上面的删除操作需要先通过find()方法获得要删除的对象,然后才能删除。
merge()方法
merge()方法比较复杂,我们来仔细研究一下,先看第一种情况:
@Test
public void testMerge() {
Customer customer = new Customer();
customer.setAge(20);
customer.setBirthday(new Date());
customer.setEmail("wangwu@163.com");
customer.setLastName("wangwu");
Customer cst = entityManager.merge(customer);
System.out.println(customer);
System.out.println(cst);
}
运行结果:
Customer [id=null, lastName=wangwu, email=wangwu@163.com, age=20, birthday=Thu Oct 03 21:29:20 CST 2019]
Customer [id=3, lastName=wangwu, email=wangwu@163.com, age=20, birthday=Thu Oct 03 21:29:20 CST 2019]
在这个程序中,JPA会创建一个新的对象,把临时对象的属性复制到新的对象中,然后对新的对象执行持久化操作,所以新的对象中有id,但之前的临时对象没有id。
再看第二种情况:
@Test
public void testMerge2() {
Customer customer = new Customer();
customer.setAge(20);
customer.setBirthday(new Date());
customer.setEmail("zhaoliu@163.com");
customer.setLastName("zhaoliu");
//设置id属性
customer.setId(1024);
Customer cst = entityManager.merge(customer);
System.out.println(customer);
System.out.println(cst);
}
运行结果:
Customer [id=1024, lastName=zhaoliu, email=zhaoliu@163.com, age=20, birthday=Thu Oct 03 21:35:07 CST 2019]
Customer [id=4, lastName=zhaoliu, email=zhaoliu@163.com, age=20, birthday=Thu Oct 03 21:35:07 CST 2019]
在这个程序中,我们创建了一个游离对象,因为该对象有id,但还未与数据库产生关联,然后对其执行持久化操作,这时候需要分三种情况。
第一种情况:
- 若在EntityManager缓存中没有该对象,
- 若在数据库中也没有对应的记录
- JPA会创建一个新的对象,然后把当前游离对象的属性复制到新的对象中
- 对新创建的对象执行持久化操作
第二种情况:
- 若在EntityManager缓存中没有该对象,
- 若在数据库有对应的记录
- JPA会查询对应的记录,然后返回该记录对应的对象,最后把游离对象的属性复制到返回的对象中
- 对返回的对象执行持久化操作
第三种情况:
- 若在EntityManager缓存中有该对象,
- JPA会把游离对象的属性复制到缓存对象中
- 对缓存对象执行持久化操作
EntityManager还有一些不是特别重要的方法,在这里直接列举,就不做代码演示了:
- flush():该方法用于同步持久上下文环境,即:将持久上下文环境的所有未保存实体的状态信息保存到数据库中
- setFlushMode():设置持久上下文环境的Flush模式
- getFlushMode():获取持久上下文环境的Flush模式,返回FlushModeType类的枚举值
- refresh():用数据库实体记录的值更新实体对象的状态
- clear():清除持久上下文环境,断开所有关联的实体,如果这时还有未提交的更新则会被撤销
- contains():判断一个实例是否属于当前上下文环境管理的实体
EntityTransaction
事务相信大家再熟悉不过了,MySQL、JDBC、Hibernate,一直都在学事务,就直接列举它的一些方法了:
- begin():用于启动一个事务,此后的多个数据库操作将作为整体被提交或撤销,若这时事务已经启动则会抛出异常
- commit():用于提交当前事务,即:将事务启动后的所有数据库更新操作持久化到数据库中
- rollback():用于回滚当前事务,即:撤销事务启动后的所有数据库更新,从而不对数据库产生影响
- setRollbackOnly():使当前事务只能被回滚
- getRollbackOnly():查看当前事务是否设置了只能回滚标志
- isActive():查看当前事务是否是活动的,如果返回true,则不能调用begin()方法,否则将抛出异常;如果返回false,则不能调用commit()、rollback()、setRollbackOnly()、getRollbackOnly()等方法,否则将抛出异常