一、前言
本文仅代表作者的个人观点;
本文的内容仅限于技术探讨,不能作为指导生产环境的素材;
本文素材是红帽公司产品技术和手册;
本文分为系列文章,将会有多篇,初步预计将会有9篇。
二、对象关系映射
当应用程序将数据存储在永久性存储中(例如flat file,XML文件或数据库的持久性数据)时,它被称为数据的持久性。 关系数据库是企业应用程序用来保存数据以供重用的最常见的数据存储之一。
Java EE企业应用程序中的业务数据被定义为Java对象。 这些对象保存在相应的数据库表中。 Java对象和数据库表使用不同的数据类型(例如Java中的String和数据库中的Varchar)来存储业务数据。 随着数据在应用程序和数据库之间由于写入操作而移动,它可能会导致对象模型和关系模型之间的差异。 这种差异称为 阻抗失协(impedance mismatch)。
处理阻抗失协有两种方法:
1.由数据持久性提供程序处理
2.应用程序开发人员必须编写代码来解决此问题。
能够自动化解决 阻抗失协的技术称为对象关系映射(ORM)。 ORM软件使用元数据来描述应用程序中定义的类与数据库表的模式之间的映射。 映射在XML配置文件或注释中提供。
例如,您想要将TodoItem类对象存储在TodoItem数据库表中; ORM将Java类名映射到数据库表名,并且该类中的属性将自动映射到表中的相应字段。
Java EE提供了由各种ORM提供者实现的Java持久性API(JSR 338)规范。 市场上有许多ORM软件产品,比如EclipseLink和Hibernate。 除了对象持久性之外,完全实现的ORM还提供了优化技术、缓存、数据库可移植性、查询语言。 与Java Persistence API相关的三个关键概念是实体( entity)、持久性单元(persistence units,)、持久性上下文( persistence context.)。
三个概念听起来有点抽象,用大白话来说:
实体( entity)就是一个类,它定义了与数据库表的对应关系
持久性单元(persistence units)定了访问数据库的方式
持久性上下文( persistence context.):访问数据表的实例。
三、实体
一个entity是一个可持久化的、轻量级的域对象。 entity class映射到关系数据库中的表。 entity class的每个实例都有一个主键字段。 主键字段用于将实体实例映射到数据库表中的行。 所有非瞬态属性都映射到数据库表中的字段。 在数据库表中,entity的每个持久实例都有一个持久性标识,该标识在表中唯一标识。 在Java中,entity是一个简单的旧Java对象(POJO)类,它使用@Entity注释进行了注释。 entity类中的所有字段默认存储在数据库中,并称为持久字段。 声明为临时的属性不存储在数据库表中,并且被称为非持久性。
声明实体类
实体类声明如下:
import javax.persistence.*;
import java.io.*;
@Entitypublic class TodoItem implements Serializable
{ @Id
private int id; //primary key -- required for an Entity class
private String item;
private String status;
public TodoItem(){ } //No argument constructor
// other constructor
public TodoItem(String item,String status) {
this.item=item;
this.status=status;
}
//Setter and Getter methods
public String getItem() {
return item;
}
public void setItem(String item) {
this.item = item;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
}
Default Entity与table的映射关系
Entity | Table |
---|---|
Entity class | Table name |
Attributes of entity class | Columns in a database table |
Entity instance | Record or row in a database table |
四、使用JPA注释
我们使用注释的方法,来修饰java类、配置、查询、元数据的映射方法映射,配置,查询,验证等。这些信息在运行时被编译并提供。 以下是一些常用的注释:
@Entity
@Entity注释指定一个类是一个实体。如果不使用@Entity,我们将一个类配置成实体,通过将其映射到orm.xml配置文件中(这种方法更老一点)。 orm.xml包含将Java类声明为实体所需的所有配置细节。
@Table
@Table注解用于指定实体类和表之间的映射。 当实体类的名称与数据库中的表名不同时使用它。
@Entity
@Table(name="ThingsToDo")
public class TodoItem {
...
}
TodoItem实体类被映射到thigStoDo表。
@Column
@列注释用于将字段或属性映射到数据库中的列。
@Entity
@Table(name="ThingsToDo")
public class TodoItems implements Serializable {
@Column(name="itemname")
private String item;
...
项属性映射到表中的列ITENMENT。
@Temporal
@Temporal注释与Date类型的属性一起使用。数据库以不同于Java类的方式存储日期。时间注释管理java.util.Date或java.util.Calendar类型的映射,并将其转换为数据库中适当的日期类型。
@Entity
public class TodoItem implements Serializable {
...
@Temporal(TemporalType.DATE)
private Date completionDate;
@Transient
瞬态注释用于指定非持久性字段
@Entity
public class TodoItem implements Serializable {
...
@Transient
private int countPending;
countPending字段不会被保存到数据库表中。
@Id
@Id注释用于指定主键。 id字段用于标识数据库表中的唯一行。
@Entity
public class TodoItem implements Serializable {
@Id
private int id;
...
}
主键可以是简单的Java类型或复合值,由多个字段组成。对于组合主键,定义了主键类。 @EmbeddedId或@ IdClass注释用于指定组合主键。
每个实体实例都映射到数据库表中的一行。 表格中的每一行都是唯一的,并由唯一的ID标识为持久实体标识。 永久实体标识是从主键字段生成的。 主键字段在每个实体类中都是必需的。 一个简单的主键应该是以下类型之一:
@Id注释用于指定一个简单的主键。
@GeneratedValue注释应用于主键字段或属性以指定主键生成策略。
@GeneratedValue注释提供枚举类型的GenerationType元素。
四种主要密钥生成策略如下:
1. GenerationType.AUTO
AUTO策略是默认的ID生成策略,并且意味着JPA提供者使用其选择的任何策略来生成主键。 Hibernate根据数据库特定的方言选择生成策略。
@Entity
public class TodoItem implements Serializable {
@Id
@GeneratedValue(GenerationType.AUTO)
private int id;
...
}
2.GenerationType.SEQUENCE
SEQUENCE策略意味着JPA提供者使用数据库序列来生成主键。 序列必须在数据库中创建,并且序列名称在生成器元素中提供。
/* ITEMS_SEQ sequence
create sequence ITEMS_SEQ
MINVALUE 1
START WITH 1
INCREMENT BY 1
*/
@Entity
public class TodoItem implements Serializable {
@Id
@GeneratedValue(GenerationType.SEQUENCE, generator="ITEMS_SEQ"))
private int id;
...
}
3.GenerationType.IDENTITY
IDENTITY策略意味着JPA提供程序使用数据库标识列来生成主键。
@Entity
public class TodoItem {
@Id
@GeneratedValue(GenerationType.IDENTITY)
private int id;
...
}
4.GenerationType.TABLE
TABLE策略意味着JPA提供者使用数据库ID生成表。 这是用于生成ID值的单独表格。 ID生成表格有两列。 第一列是标识生成器序列的字符串,第二列是存储ID序列的整数值。
@Entity
public class TodoItem implements Serializable {
@TableGenerator(name="Items_gen",
table="ITEM_ID_GEN",
pkColumnName="GEN_NAME",
valueColumnName="GEN_VAL",
pkColumnValue="ITEM_ID",
allocationSize=60)
@Id @GeneratedValue(Generator="Items_gen")
private int id;
...
}
EntityManager是JPA中用于增删改查的接口,它的作用相当于一座桥梁,连接内存中的java对象和数据库的数据存储。
EntityManager API被定义为执行持久性操作。 实体管理器获取对实体的引用,并对数据库执行实际的CRUD(创建,读取,更新和删除)操作。 一个EntityManager实例可以从一个EntityManagerFactory对象获得。 实体管理器在一组管理实体实例中工作。 这些被管实体实例被称为实体管理器的持久性上下文。 您可以将持久性上下文视为持久性单元的唯一实例。 持久性单元是存储在应用程序存档中的所有实体类和persistence.xml文件的集合。 persistence.xml是一个配置文件,其中包含有关实体类,数据源,事务类型和其他配置信息的信息。
在EJB中创建实体管理器
为持久单元创建一个EntityManagerFactory对象,并且该对象用于获取EntityManager的一个实例
@Statelesspublic class ItemService {
//ItemPU is the name of the persistence unit
EntityManagerFactory emFactory = Persistence.createEntityManagerFactory("ItemPU");
EntityManager em = emFactory.createEntityManager();
....
}
在Java EE托管对象(如EJB)中获得EntityManager实例的另一种方法是生产者技术。 一个对象可以使用上下文依赖注入(CDI)注入。 CDI是一组允许类型安全的依赖注入的组件管理服务。 生产者类定义了一个生产者方法,它返回注入到另一个类的数据类型。
public class EMProducer {
@Produces
@PersistenceContext(unitName= "ItemPU")
private EntityManager em;
}
一个EJB类可以使用@Inject注解注入EntityManager。
@Stateless
public class ItemService{
@Inject
private EntityManager em;
public void registerItem(Item item) throws Exception {
...
em.persist(item);
....
}
public void removeItem(Long id) throws Exception {
...
em.remove(findById(id));
....
}
public void updateItem(Item item) {
em.merge(item);
}
}
持久性单元描述与数据源、事务、具体类和对象关系映射相关的配置设置。 持久性单元在应用程序的META-INF目录中的persistence.xml文件中配置。 每个使用持久性的应用程序都至少有一个持久性单元。 持久性单元包含有关持久性单元名称,数据源和事务类型的信息。
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1"
xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence
http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"> <persistence-unit name="Items" transaction-type="JTA">
<jta-data-source>java:jboss/datasources/MySQLDS</jta-data-source>
<properties>
<property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect" />
<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>
七、交易对数据持久性的影响
交易是由一系列操作组成的单一工作单元。如果任何一个操作在事务中失败,那么整个事务将在事务开始之前回滚到其原始状态。如果所有操作都能够执行,那么交易将被提交并且不需要回滚。在使用持久性时,事务确保数据库的更改不会由于操作失败而部分完成。 JPA使用两种交易方法为JPA资源上的操作提供交易行为:
资源本地事务是跨越单个资源(如数据源)的范围的事务。例如,如果应用程序配置为使用资源本地事务,则与非JTA数据源关联的实体管理器将使用EntityTransaction类来管理事务。但是,此事务仅适用于基于实体管理器的单个数据源上的操作,这限制了跨越多个数据源或消息传递系统的更复杂的事务。
相比之下,JTA(Java Transaction API)事务跨越一个容器中的所有资源。 JTA不是从实体管理器中引用EntityTransaction,而是使用UserTransaction类,它允许您独立于资源或资源启动,提交或回滚事务。事务与单个资源的这种分离允许事务包含跨越多个资源的复杂操作,例如多个数据源和JMS消息传递系统。
魏新宇