一、前言
本文仅代表作者的个人观点;
本文的内容仅限于技术探讨,不能作为指导生产环境的素材;
本文素材是红帽公司产品技术和手册;
本文分为系列文章,将会有多篇,初步预计将会有16篇。
二、Java对持久数据的访问方式
前文已经提到,Java应用对应用数据的访问,最终通过ORM方式实现。
而ORM的实现,通过JPA的标准,底层使用Hibernate等技术。
JPA中的几个重要的API:
JPA的API有主要以下几个:实体(entity)、持久性单元(persistence units)、持久性上下文( persistence context)、Entity Manager。我们先看Entity Manager。
几者之间的关系:
一个entity其实就是一个class,只是定了与数据库表的对应。如上图,class叫大魏,数据库中也有一张表叫大魏(类的名称可以和数据库表名不同,使用@Table指定即可)。
大魏这个类,在被生成对象时,会从数据库表中读数据,然后可能会对数据修改,修改的这些数据,会存到持久性上下文中(运行在内存中),在默写情况下,会被存回数据库表中(例如提交)。
java对数据库表的操作,实际上是使用entity manager调用CRUD完成的。而entity manager之所以能对数据库做操作,是因为其底层调用Hibernate,封装了JDBC。而Hibernate相关定义的静态配置,是存放到persistence units中的。
(默认模式下)entity manager是运行到EJB container中,也就是中间件中的。
EntityManagerFactory
EntityManagerFactory 接口主要用来创建 EntityManager 实例。EM 是一个接口,创建的话要 new 它的实现类,工厂类里有好多静态方法,调运时返回一个 EM
EntityManagerFactory该接口约定了如下4个方法:
三、实体类对数据的两种访问方式
实体类与标准POJO类相似,但实体有几个重要的区别,需要由EntityManager进行管理。 要将POJO类转换为实体,请在类头中添加@Entity注释。 另外,应该通过使用getter和setter方法来访问每个实例变量。 最后,类必须至少有一个没有参数的构造函数,尽管类仍然可以有其他构造函数接受参数。
以下是一个实体类的例子:
@Entity
public abstract class Customer {
@Id
private int custId;
private String custName;
....
public Customer(){ } // No argument constructor
//setter and getter methods
public String getCustName() {
return custName;
}
public void setCustName(String custName) {
this.custName = custName;
}
...
}
实体字段和属性
实体类中的非瞬态数据会持久保存到数据库表中。 JPA提供者既可以将数据库表中的数据加载到实体类中,也可以将实体类中的数据存储到数据库表中。 提供者访问状态的方式称为访问模式。 有两种访问模式:基于字段的访问和基于属性的访问。
基于字段的访问Field-based:
这种方式是:通过注释字段提供基于字段的访问。 实体类中的持久字段必须声明为私有,受保护或包级别访问。 持久字段是以下类型之一:
基于字段的访问的示例如下所示:
@Entity
public class Customer implements Serializable {
// Note that the fields are annotated
@Id
protected int custId;
protected String custName;
@Temporal(TemporalType.DATE)
protected Date registrationDate;
@Column(name="address")
protected Address custAddress;
.....
}
基于字段的访问提供了额外的灵活性。
基于属性的访问--Property-based Access
为了提供基于属性的访问,getter和setter方法必须在Java实体类中定义。因为只能通过方法访问,可以说基于属性的访问提供了更好的封装。 通过注解getter方法提供基于属性的访问。 getter方法的返回类型决定了属性的类型。 getter方法的返回类型必须与传递给setter方法的参数的类型相同。 getter和setter方法必须是public或protected,并且必须遵循Java bean的命名约定。 基于属性的访问的一个例子如下:
@Entity
public class Customer implements Serializable {
protected int custId;
protected String custName;
protected Date registrationDate;
protected Address custAddress;
....
//Note the getter methods are annotated @Id
public int getCustId(){
return custId;
}
public String getCustName(){
return custName;
}
@Temporal(TemporalType.DATE)
public Date getRegistrationDate(){
return registrationDate;
}
@Column(name="address")
public Address getCustAddress(){
return custAddress;
}
....
//Setter methods
}
四、实体的四种状态
实体的四种类型:
New State: 使用Java新运算符创建的实体实例处于新状态或瞬态状态。 实体实例不具有持久性标识,并且尚未与持久性上下文相关联。
Managed State:具有持久性标识、并与持久性状态关联的实体实例、处于受管状态或持久状态。 当对管理实体字段中的数据进行更改时,它将与数据库表数据同步。 应用程序调用实体管理器的持久性,查找或合并方法后,实体实例处于受管状态。
Removed State:持久实体可以通过多种方式从数据库表中删除。 当提交事务或调用实体管理器的remove方法时,可以从数据库表中删除一个托管实体实例。 一个实体然后处于移除状态。
Detached State: 实体具有持久性实体标识,但不与持久性上下文相关联。 当实体被序列化或在事务结束时会发生这种情况。 这种状态被称为实体的分离状态。
五、EntityManager接口和关键方法
javax.persistence.EntityManager接口用于与持久性上下文进行交互。 实体实例及其生命周期在持久性上下文中进行管理。
javax.persistence.EntityManager API用于创建新的实体实例,通过主键查找实体实例,通过实体实例进行查询以及删除现有的实体实例。 EntityManager的关键方法是:
persist()方法持久化一个实体并使其得到管理。 persist()方法在数据库表中插入一行。 如果persist操作失败,persist()方法将抛出PersistenceException。
@Stateless
public class CustomerServices {
....
public void saveCustomer(Customer customer) {
...
try{ entityManager.persist(customer);
}catch(PersistenceException persistenceException){
// code to handle PersistenceException
}
}
}
find()方法通过主键搜索特定类的实体并返回一个托管实体实例。 如果找不到对象,则返回null。
@Stateless
public class CustomerServices {
....
public void getCustomer(Customer customer) {
...
Customer customer;
try{
customer = entityManager.find(Customer.class,custId);
if (customer != null){
System.out.print(customer.getCustName());
} else }
System.out.print("Not Found");
}
}catch(Exception exception){
// code to handle PersistenceException
}
}
}
contains()方法将一个实例作为参数并检查实例是否在持久化上下文中:
@Stateless
public class CustomerServices {
....
public boolean saveCustomer(Customer customer) {
...
entityManager.persist(customer);
return entityManager.contains(customer);
}
}
merge()方法更新现有分离实体的表中的数据。 merge()方法为处于新状态或瞬态状态的实体在数据库表中插入新行。 合并操作之后,实体处于受管理状态。
@Stateless
public class CustomerServices {
....
public void updateCustomer(Customer customer) {
...
Customer customer;
try{
customer = entityManager.find(Customer.class,custId); entityManager.merge(customer);
}catch(Exception exception){
// code to handle PersistenceException
}
}
}
remove()方法删除一个托管实体。 要删除分离的实体,请调用一个返回受管实例的find()方法,然后调用remove()方法。
@Stateless
public class CustomerServices {
....
public void deleteCustomer(Customer customer) {
...
Customer customer;
try{
customer = entityManager.find(Customer.class,custId); entityManager.remove(customer);
}catch(Exception exception){
// code to handle PersistenceException
}
}
}
clear()方法清除持久性上下文。 完成此操作后,所有受管实体都处于分离状态。
...
try{ entityManager.clear();
}catch(Exception exception){
// code to handle PersistenceException
}
refresh()方法从数据库表中刷新实体实例的状态。 实体实例中的当前数据被从数据库表中提取的数据覆盖。
...
try{ entityManager.refresh(customer);
}catch(Exception exception){
// code to handle PersistenceException
}
persistence.xml文件是一个包含持久性单元的标准配置文件。 每个持久性单元都有一个唯一的名称。
1持久性单元名称是持久性单元的名称。持久性单元的名称用于获取EntityManager。
2事务类型可以是JTA或RESOURCE_LOCAL。事务类型定义了应用程序打算执行什么类型的事务。容器事务使用每个Java EE应用程序服务器中提供的Java事务API(JTA)。在JTA类型的事务中,容器负责创建和跟踪实体管理器。在RESOURCE_LOCAL中,您负责创建和跟踪实体管理器。
3jta-data-source是数据源的名称。每个持久性单元都必须有一个数据库连接。 JPA提供程序在启动时使用JNDI查找服务按名称查找数据源。
4可以在属性元素中设置其他标准或特定于供应商的属性。 hibernate.Dialect属性指定使用哪个数据库。具有更新值的hibernate.hbm2ddl.auto属性会自动更新模式。具有值为true的hibernate.show-sql属性可以将SQL语句记录到控制台。
六、实战:应用对持久数据的访问
通过JBDS导入一个已经存在maven项目:
查看一个包的 com.redhat.training.model java的源码: Person.java
修改源码如下位置:
增加import库并 @Entity注释:
通过以上操作,将一个普通的POJO变成了Entity。
Person实体类必须实现Serializable接口。 导入并实现Serializable接口。
将@Id和@GeneratedValue(strategy = GenerationType.IDENTITY)注释添加到Person类的id属性中,使其成为主键,并将其生成为IDENTITY。
将@Column(name =“name”)注释添加到personName属性,以将其映射到数据库表中的名称字段。 导入所需的库。
在com.redhat.training.services包中打开PersonService类并添加持久性功能以将Person保存到数据库并从数据库中查找人员。
需要EntityManager对象来执行PersonService类中的持久性操作。 添加@PersistenceContext注释以获取EntityManager对象:
使用实体管理器将Person持久化到数据库中,将以下代码添加到公共String hello(String name)方法中,如下所示:
找到使用id的人的名字,将方法getPerson(Long id)添加到PersonService类。 在return语句中,使用实体管理器的find()方法根据id返回Person的name属性。
观察getAllPersons()方法,该方法返回存储在数据库中的所有Person对象:
在com.redhat.training.ui包中打开Hello类。 取消注释getPerson()和getPersons()方法,以添加前端功能以查看存储在数据库中的单个人员姓名和所有姓名。
将
修改为:
启动EAP:
接下来,构建和部署应用。
接下来,在EAP上部署应用:
部署成功:
通过浏览器访问应用:
输入名字:david wei,点击提交:
点击view all names:
说明姓名已经被insert到数据库表中。
参考文档:
魏新宇