JPA概要

 转自:jpa-内心求法

JPA概要摘要:JPA定义了Java ORM及实体操作API的标准。本文摘录了JPA的一些关键信息以备查阅。如果有hibernate的基础,通过本文也可以快速掌握JPA的基本概念及使用。

1.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维护一个Persistence Context(持久化上下文),在持久化上下文中维护实体的生命周期。主要包含三个方面的内容:

1.ORM元数据。JPA支持annotion或xml两种形式描述对象-关系映射。

2.实体操作API。实现对实体对象的CRUD操作。

3.查询语言。约定了面向对象的查询语言JPQL(Java Persistence Query Language)。

JPA的主要API都定义在javax.persistence包中。如果你熟悉Hibernate,可以很容易做出对应: 

org.hibernate                           javax.persistence                               说明

cfg.Configuration                        Persistence                               读取配置信息

SessionFactory                       EntityManagerFactory            用于创建会话/实体管理器的工厂类

Session                                       EntityManager                 提供实体操作API,管理事务,创建查询

Transaction                               EntityTransaction                                 管理事务

Query                                               Query                                           执行查询

2 .实体生命周期

实体生命周期是JPA中非常重要的概念,描述了实体对象从创建到受控、从删除到游离的状态变换。对实体的操作主要就是改变实体的状态。

JPA中实体的生命周期如下图:


1.New ,      新建,新创建的实体对象,没有主键(identity)值

2.Managed,持久化,对象处于Persistence Context(持久化上下文)中,被EntityManager管理

3.Detached,分离,对象已经游离到Persistence Context之外,进入Application Domain

4.Removed, 删除,实体对象被删除

EntityManager提供一系列的方法管理实体对象的生命周期,包括:

1.persist, jian c将新创建的或已删除的实体转变为Managed状态,数据存入数据库。

2.remove,删除受控实体

3.merge,将游离实体转变为Managed状态,数据存入数据库。

如果使用了事务管理,则事务的commit/rollback也会改变实体的状态。

3 实体关系映射(ORM)

3.1 基本映射 

对象端             数据库端                        annotion               可选annotion

Class                Table                          @Entity              @Table(name="table_name")

property           column                             –                     @Column(name = "column_name")

property            primary key                 @Id                        @GeneratedValue 详见ID生成策略

property                 NONE                  @Transient  

3.2 ID生成策略

ID对应数据库表的主键,是保证唯一性的重要属性。JPA提供了以下几种ID生成策略:

1.GeneratorType.AUTO ,由JPA自动生成

2.GenerationType.IDENTITY,使用数据库的自增长字段,需要数据库的支持(如SQL Server、MySQL、DB2、Derby等)

3.GenerationType.SEQUENCE,使用数据库的序列号,需要数据库的支持(如Oracle)

4.GenerationType.TABLE,使用指定的数据库表记录ID的增长 需要定义一个TableGenerator,在@GeneratedValue中引用。

            例如:@TableGenerator( name="myGenerator", table="GENERATORTABLE", pkColumnName = "ENTITYNAME", pkColumnValue="MyEntity", valueColumnName = "PKVALUE", allocationSize=1 )

               @GeneratedValue(strategy = GenerationType.TABLE,generator="myGenerator")

3.3 关联关系

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 = "BOOK_AUTHOR", joinColumns = { @JoinColumn(name = "BOOK_ID", referencedColumnName = "id") }, inverseJoinColumns = { @JoinColumn(name = "AUTHOR_ID", referencedColumnName = "id") })

关联关系还可以定制延迟加载和级联操作的行为(owning-side和inverse-side可以分别设置):

通过设置fetch=FetchType.LAZY 或 fetch=FetchType.EAGER来决定关联对象是延迟加载或立即加载。

通过设置cascade={options}可以设置级联操作的行为,其中options可以是以下组合:

CascadeType.MERGE 级联更新

CascadeType.PERSIST 级联保存

CascadeType.REFRESH 级联刷新

CascadeType.REMOVE 级联删除

CascadeType.ALL 级联上述4种操作

3.4 继承关系

JPA通过在父类增加 @Inheritance(strategy=InheritanceType.xxx)来声明继承关系。A支持3种继承策略:

1.单表继承(InheritanceType.SINGLETABLE),所有继承树上的类共用一张表,在父类指定(@DiscriminatorColumn)声明并在每个类指定@DiscriminatorValue来区分类型。

2.类表继承(InheritanceType.JOINED),父子类共同的部分公用一张表,其余部分保存到各自的表,通过join进行关联。

3.具体表继承(InheritanceType.TABLEPERCLASS),每个具体类映射到自己的表。

其中1和2能够支持多态,但是1需要允许字段为NULL,2需要多个JOIN关系;3最适合关系数据库,对多态支持不好。具体应用时根据需要取舍。

3.5 值对象、关联表

在领域驱动设计(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;   

                ……

}

3.6 复合主键

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;  ……}

3.7 集合映射

从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 Setimages = new HashSet(); 

//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 ListlistImages = new ArrayList(); 

//Map    

           @ElementCollection    

          @JoinTable(name="item", joinColumns={ @JoinColumn(nullable=false, name="item_id")})

           @MapKeyColumn(name="map_key")    

          @Column(name="filename", nullable=false)     

          private MapmapImages = new HashMap(); 

值对象集合映射:

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;




4 事件及监听


通过在实体的方法上标注@PrePersist,@PostPersist等声明即可在事件发生时触发这些方法。

5 Query Language 查询语言

JPA提供两种查询方式,一种是根据主键查询,使用EntityManager的find方法:

T find(Class entityClass, Object primaryKey)

另一种就是使用JPQL查询语言。JPQL是完全面向对象的,具备继承、多态和关联等特性,和hibernate HQL很相似。

使用EntityManager的createQuery方法:

Query createQuery(String qlString)

5.1 使用参数

可以在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));

5.2 命名查询

如果某个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");

5.3 排序

JPQL也支持排序,类似于SQL中的语法。例如:

Query query = em.createQuery("select p from Person p order by p.age, p.birthday desc");

5.4 聚合查询

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();

5.5 更新和删除

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的记录数

5.6 更多

与SQL类似,JPQL还涉及到更多的语法,可以参考这里

6 事务管理

JPA支持本地事务管理(RESOURCELOCAL)和容器事务管理(JTA),容器事务管理只能用在EJB/Web容器环境中。

事务管理的类型可以在persistence.xml文件中的“transaction-type”元素配置。

JPA中通过EntityManager的getTransaction()方法获取事务的实例(EntityTransaction),之后可以调用事务的begin()、commit()、rollback()方法。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,552评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,666评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,519评论 0 334
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,180评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,205评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,344评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,781评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,449评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,635评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,467评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,515评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,217评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,775评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,851评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,084评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,637评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,204评论 2 341

推荐阅读更多精彩内容