摘要:
JPA定义了Java ORM及实体操作API的标准。本文摘录了JPA的一些关键信息以备查阅。如果有hibernate的基础,通过本文也可以快速掌握JPA的基本概念及使用。
JPA(Java Persistence API,Java持久化API),定义了对象-关系映射(ORM)以及实体对象持久化的标准接口。
JPA1.0是JSR-220(EJB3.0)规范的一部分,在JSR-220中规定实体对象(EntityBean)由JPA进行支持。
后来,JPA2.0迁移到JSR-317(Java Persistence 2.0),已经不局限于EJB3.0,而是作为POJO持久化的标准规范,可以脱离容器独立运行,开发和测试更加方便。
JPA在应用中的位置如下图所示:
JPA维护一个Persistence Context(持久化上下文),在持久化上下文中维护实体的生命周期。主要包含三个方面的内容:
JPA的主要API都定义在javax.persistence包中。
如果你熟悉Hibernate,可以很容易做出对应:
org.hibernate | javax.persistence | 说明 |
---|---|---|
cfg.Configuration | Persistence | 读取配置信息 |
SessionFactory | EntityManagerFactory | 用于创建会话/实体管理器的工厂类 |
Session | EntityManager | 提供实体操作API,管理事务,创建查询 |
Transaction | EntityTransaction | 管理事务 |
Query | Query | 执行查询 |
实体生命周期是JPA中非常重要的概念,描述了实体对象从创建到受控、从删除到游离的状态变换。对实体的操作主要就是改变实体的状态。
JPA中实体的生命周期如下图:
EntityManager提供一系列的方法管理实体对象的生命周期,包括:
如果使用了事务管理,则事务的commit/rollback也会改变实体的状态。
对象端 | 数据库端 | annotion | 可选annotion |
---|---|---|---|
Class | Table | @Entity | @Table(name="table_name") |
property | column | – | @Column(name = "column_name") |
property | primary key | @Id | @GeneratedValue 详见ID生成策略 |
property | NONE | @Transient |
ID对应数据库表的主键,是保证唯一性的重要属性。JPA提供了以下几种ID生成策略:
GenerationType.TABLE,使用指定的数据库表记录ID的增长 需要定义一个TableGenerator,在@GeneratedValue中引用。例如:
@TableGenerator( name="myGenerator", table="GENERATORTABLE", pkColumnName = "ENTITYNAME", pkColumnValue="MyEntity", valueColumnName = "PKVALUE", allocationSize=1 )
@GeneratedValue(strategy = GenerationType.TABLE,generator="myGenerator")
JPA定义了one-to-one、one-to-many、many-to-one、many-to-many 4种关系。
对于数据库来说,通常在一个表中记录对另一个表的外键关联;对应到实体对象,持有关联数据的一方称为owning-side,另一方称为inverse-side。
为了编程的方便,我们经常会希望在inverse-side也能引用到owning-side的对象,此时就构建了双向关联关系。 在双向关联中,需要在inverse-side定义mappedBy属性,以指明在owning-side是哪一个属性持有的关联数据。
对关联关系映射的要点如下:
关系类型 | Owning-Side | Inverse-Side |
---|---|---|
one-to-one | @OneToOne | @OneToOne(mappedBy="othersideName") |
one-to-many / many-to-one | @ManyToOne | @OneToMany(mappedBy="xxx") |
many-to-many | @ManyToMany | @ManyToMany(mappedBy ="xxx") |
其中 many-to-many关系的owning-side可以使用@JoinTable声明自定义关联表,比如Book和Author之间的关联表:
@JoinTable(name = "BOOKAUTHOR", joinColumns = { @JoinColumn(name = "BOOKID", referencedColumnName = "id") }, inverseJoinColumns = { @JoinColumn(name = "AUTHORID", referencedColumnName = "id") })
关联关系还可以定制延迟加载和级联操作的行为(owning-side和inverse-side可以分别设置):
通过设置fetch=FetchType.LAZY 或 fetch=FetchType.EAGER来决定关联对象是延迟加载或立即加载。
通过设置cascade={options}可以设置级联操作的行为,其中options可以是以下组合:
JPA通过在父类增加@Inheritance(strategy=InheritanceType.xxx)来声明继承关系。A支持3种继承策略:
其中1和2能够支持多态,但是1需要允许字段为NULL,2需要多个JOIN关系;3最适合关系数据库,对多态支持不好。具体应用时根据需要取舍。
在领域驱动设计(DDD)中,值对象不具有唯一标识,而是依附在实体上,只能通过实体获取。
通常,值对象的字段作为所嵌入的实体对象对应的表中的字段。如有必要,也可以通过@SecondaryTable
和@AttributeOverrides
注解将值对象的字段放到关联表中。
JPA使用@Embeddable
标记一个类为值对象;在实体对象中使用@Embedded
使用值对象作为属性。
比如:
@Embeddable
public class Address {
private String street;
private String city;
private String state;
@Column(name="ZIP_CODE")
private String zip;
……
}
@Entity
@Table(name="EMP")
@SecondaryTable(name="EMP_ADDRESS",
pkJoinColumns=@PrimaryKeyJoinColumn(name="EMP_ID"))
public class Professor {
@Id private int id;
private String name;
@Embedded
@AttributeOverrides({
@AttributeOverride(name="street", column=@Column(table="EMP_ADDRESS")),
@AttributeOverride(name="city", column=@Column(name="CITY", table="EMP_ADDRESS")),
@AttributeOverride(name="state", column=@Column(name="STATE", table="EMP_ADDRESS")),
@AttributeOverride(name="zip",
column=@Column(name="ZIP_CODE", table="EMP_ADDRESS"))
})
private Address address;
……
}
JPA中,可以使用@EmbeddedId
主键将一个值对象声明为复合主键。作为复合主键的值对象要实现Serializable
接口, 并覆盖equals
和hashCode
方法:
比如:
@Embeddable
public class SectionPK implements Serializable{
@ManyToOne
private Security target;
private Interval interval;
private Date datetime;
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((target == null) ? 0 : target.hashCode());
result = prime * result
+ ((datetime == null) ? 0 : datetime.hashCode());
result = prime * result +
((interval == null) ? 0 : interval.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
SectionPK other = (SectionPK) obj;
if(!isEqual(this.getDatetime(),other.getDatetime()))
return false;
if(!isEqual(this.getInterval(),other.getInterval()))
return false;
if(!isEqual(this.getTarget(),other.getTarget()))
return false;
return true;
}
private boolean isEqual(Object obj1,Object obj2){
if(obj1==null){
if(obj2!=null)
return false;
}else if(!obj1.equals(obj2)){
return false;
}
return true;
}
}
@Entity
public class Section {
@EmbeddedId
SectionPK key;
……
}
从JPA2.0开始,不仅支持实体集合的映射,还支持基本类型(如String,Integer等)集合以及值对象(Embeded)集合的映射。
JPA通过@ElementCollection
注解集合映射,可以自动检测元素类型,也可以通过targetClass
参数指定。
通过@CollectionTable
注解可以指定集合表的详细信息。当然,也可以使用@JoinTable
注解进行指定。
比如,基本类型集合的映射:
//Set
@ElementCollection
@JoinTable(name = "item", joinColumns = { @JoinColumn(nullable = false, name = "item_id", referencedColumnName = "id") })
@Column(name = "filename", nullable = false)
private Set<String> images = new HashSet<String>();
//List
@ElementCollection(targetClass = java.lang.String.class)
@JoinTable(name = "item", joinColumns = { @JoinColumn(nullable = false, name = "item_id") })
@IndexColumn(name = "position", base = 1)
@Column(name = "filename", nullable = false)
@OrderBy(clause="item_id desc")
private List<String> listImages = new ArrayList<String>();
//Map
@ElementCollection
@JoinTable(name="item", joinColumns={ @JoinColumn(nullable=false, name="item_id")})
@MapKeyColumn(name="map_key")
@Column(name="filename", nullable=false)
private Map<String, String> mapImages = new HashMap<String, String>();
值对象集合映射:
public enum FeatureType { AC, CRUISE, PWR, BLUETOOTH, TV, ... }
@Embeddable
public class ServiceVisit {
……
}
@Entity
public class Vehicle {
@Id
int vin;
@ElementCollection
@CollectionTable(name="VEH_OPTNS")
@Column(name="FEAT")
Set<FeatureType> optionalFeatures;
@ElementCollection
@CollectionTable(name="VEH_SVC")
@OrderBy("serviceDate")
List<ServiceVisit> serviceHistory;
通过在实体的方法上标注@PrePersist,@PostPersist等声明即可在事件发生时触发这些方法。
JPA提供两种查询方式,一种是根据主键查询,使用EntityManager的find方法:
T find(Class entityClass, Object primaryKey)
另一种就是使用JPQL查询语言。JPQL是完全面向对象的,具备继承、多态和关联等特性,和hibernate HQL很相似。
使用EntityManager的createQuery方法:
Query createQuery(String qlString)
可以在JPQL语句中使用参数。JPQL支持命名参数和位置参数两种参数,但是在一条JPQL语句中所有的参数只能使用同一种类型。
举例如下:
命令参数
Query query = em.createQuery("select p from Person p where p.personid=:Id");
query.setParameter("Id",new Integer(1));
位置参数
Query query = em.createQuery("select p from Person p where p.personid=?1");
query.setParameter(1,new Integer(1));
如果某个JPQL语句需要在多个地方使用,还可以使用@NamedQuery
或者 @NamedQueries
在实体对象上预定义命名查询。
在需要调用的地方只要引用该查询的名字即可。
例如:
@NamedQuery(name="getPerson", query= "FROM Person WHERE personid=?1")
@NamedQueries({ @NamedQuery(name="getPerson1", query= "FROM Person WHERE personid=?1"), @NamedQuery(name="getPersonList", query= "FROM Person WHERE age>?1") })
Query query = em.createNamedQuery("getPerson");
JPQL也支持排序,类似于SQL中的语法。例如:
Query query = em.createQuery("select p from Person p order by p.age, p.birthday desc");
JPQL支持AVG、SUM、COUNT、MAX、MIN五个聚合函数。例如:
Query query = em.createQuery("select max(p.age) from Person p");
Object result = query.getSingleResult(); String maxAge = result.toString();
JPQL不仅用于查询,还可以用于批量更新和删除。如:
Query query = em.createQuery("update Order as o set o.amount=o.amount+10"); //update 的记录数 int result = query.executeUpdate();
Query query = em.createQuery("delete from OrderItem item where item.order in(from Order as o where o.amount<100)");
query.executeUpdate();
query = em.createQuery("delete from Order as o where o.amount<100");
query.executeUpdate();//delete的记录数
与SQL类似,JPQL还涉及到更多的语法,可以参考这里。
JPA支持本地事务管理(RESOURCELOCAL)和容器事务管理(JTA),容器事务管理只能用在EJB/Web容器环境中。
事务管理的类型可以在persistence.xml文件中的“transaction-type”元素配置。
JPA中通过EntityManager的getTransaction()方法获取事务的实例(EntityTransaction),之后可以调用事务的begin()、commit()、rollback()方法。