1. 程式人生 > >JPA基本介紹以及使用

JPA基本介紹以及使用

JPA即Java Persistence Architecture,Java持久化規範,從EJB2.x版本中原來的實體Bean分離出來的,EJB3.x中不再有實體Bean,而是將實體Bean放到JPA中來實現。可以說,JPA借鑑了Hibernate的設計,JPA的設計者就是Hibernate框架的作者。 JPA的底層實現是一些流行的ORM框架,比如Hibernate,EclipseLink,OpenJPA等實現方式。

  EntityManagerFactory作為EntityManager的工廠類,建立並管理多個EntityManager例項;EntityManager介面管理持久化操作的物件,其中包含了對實體(Entity,儲存在資料庫中的記錄物件表示)的所有增刪改查操作。

  EntityManagerFactory是一個spi實現的工廠類,可以建立多個不同的EntityManager;每個EntityManager中只有一個EntityTransaction例項,但可以建立多個不同的Query例項,並執行查詢,管理多個Entity實體。 JPA中比較核心的配置檔案是persistence.xml檔案,基本樣式如下:
<?xml version="1.0"?>
<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_1_0.xsd" version="1.0">
<persistence-unit name="hsqldb-unit" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<properties>
<property name="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect"/>
<property name="hibernate.connection.driver_class" value="org.hsqldb.jdbcDriver"/>
<property name="hibernate.connection.username" value="sa"/>
<property name="hibernate.connection.password" value=""/>
<property name="hibernate.connection.url" value="jdbc:hsqldb:hsql://localhost/test"/>
<property name="hibernate.max_fetch_depth" value="3"/>
<property name="hibernate.hbm2ddl.auto" value="update"/>
<property name="hibernate.jdbc.fetch_size" value="18"/>
<property name="hibernate.jdbc.batch_size" value="10"/>
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.format_sql" value="true"/>
</properties>
</persistence-unit>
</persistence>
  本例程中使用了Hibernate3.6作為JPA的實現,並以hsqldb作為測試資料庫進行配置,在使用Spring容器的場景下通過下面的配置方式進行設定:
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="jpaVendorAdapter" ref="hibernateJpaVendorAdaptor"/>
<property name="packagesToScan" value="xxx"/>
<property name="loadTimeWeaver">
<bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver"/>
</property>
<property name="persistenceXmlLocation" value="/WEB-INF/config/persistence.xml"/>
</bean>

<bean id="hibernateJpaVendorAdaptor" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="databasePlatform" value="org.hibernate.dialect.H2Dialect"/>
</bean>

<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
 

JPA的持久化對映的資料表

作為ORM框架,肯定是有一套對應Java物件實體到關係資料庫對映的工具。在Hibernate誕生之初使用的是.hibernate.xml配置檔案的方式,而最近通過註解工具的發展(Annotation,@),已經可以通過更便於管理的方式定義這種關係(當然還可以採取更加靈活的註解+配置檔案的混合方式)。 使用@Entity來標識一個持久化物件,所有對應資料庫表的持久化物件都必須宣告該註解,並可以定義name欄位的名稱來自定義資料表的內容。 使用@Id來標識主鍵,持久化物件必須含有主鍵,主鍵一般使用與業務無關的自增/隨機欄位來表示(不建議使用聯合主鍵的方式),以獲得更好的擴充套件性。 可以使用@GenerateValue方式來宣告主鍵自動生成的策略,有下面四種方式:
  • GenerationType.TABLE:使用獨立表來記錄主鍵,適用於各種資料庫,但效能會差一些,因其每次進行insert時都會額外發出一次主鍵查詢;
  • GenerationType.SEQUENCE:只適用於oracle資料庫;
  • GenerationType.IDENTITY:適用於Sqlserver等資料庫;
  • GenerationType.AUTO:由JPA實現來探測底層使用的資料庫,自動採取一種主鍵自增的策略,可以指定生成器。如對Oracle採用SEQUENCE策略,對Sqlserver採用IDENTITY策略。AUTO策略支援目前絕大多數已知的資料庫,這是預設的策略也是我們目前採用的策略。
如果使用AUTO,最好將主鍵欄位設定為long(int也可以);如果設定為generator=uuid,那麼主鍵欄位型別使用String;如果不設定@GenerationType,那麼應用程式中需要指定主鍵。 在@Entity物件中定義的所有成員變數,都會對映到資料表中(預設名稱即為該欄位的名稱),可以使用@Column註解顯示地將該成員變數定義為資料表字段,並通過name欄位顯示地指定該欄位的名稱;也可以使用@Transient來標識該成員變數為非資料庫欄位; 此外,static(不屬於具體的物件例項)或final(不能更改其引用)欄位由於其特殊性,並不能作為資料庫欄位。 如果欄位需要使用Java中的列舉型別,可以將欄位設定為Enum型別,並設定@Enumerated,如果想要將該欄位對映為String(列舉表示的String),可以將其value設定為EnumType.String。 對於想要設定為日期Date型別的欄位,可以使用@Temporal來標識,可以選擇Date,Time,TimeStamp(預設,如果不標識,也預設使用TimeStamp)。 對於大的文字,想要使用資料庫中的Clob或Blob欄位,那麼可以使用@Lob來標識:
  • Clob:java.lang.String, java.lang.Character[],java.lang.char[], java.sql.Clob可對映為Clob;
  • Blob:java.lang.byte[], java.lang.Byte[], java.sql.Blob及實現了Serializable物件的類可對映為Blob。

資料表之間的關聯關係對映

資料表之間的關聯關係在JPA中有三種:

@OneToOne

一對一對映分為主鍵關聯和外來鍵關聯兩種方式,如果使用主鍵關聯,那麼從表的的主鍵同時也是外來鍵,主從表中關聯的記錄主鍵完全一致,需要在被維護端使用@PrimaryKeyJoinColumn指定對應的關係,比如下面的例子:
@Entity
public class Husband {
    @Id
    @GeneratedValue
    private Long id;
    
    private String name;

    @OneToOne(mappedBy="husband", cascade=CascadeType.ALL)
    private Wife wife;

    ...
}
 
@Entity
public class Wife {
    @Id
    private Long id;
    
    private String name;

    @OneToOne(cascade=CascadeType.ALL)
    @PrimaryKeyJoinColumn
    @MapsId
    private Husband husband;
}
  如果是外來鍵關聯,則會使用一個額外的外來鍵欄位來建立關聯關係,比如下面的例子:
@Entity
public class Car {
    @Id
    @GeneratedValue
    private Long id;

    private String name;
 
    @OneToOne(cascade={CascadeType.PERSIST, CascadeType.MERGE}, fetch=FetchType.LAZY)                 @JoinColumn(name="engine_id")//外來鍵關聯欄位
    private Engine engine;

@Entity
public class Engine {

    @Id
    @GeneratedValue
    private Long id;

    private String name;

    @OneToOne(mappedBy="engine", fetch=FetchType.LAZY, cascade={CascadeType.PERSIST, CascadeType.MERGE})
    private Car car;
}
 

@OneToMany @ManyToOne

一對多/多對一對映註解,在雙向一對多關係中,一端是關係維護端,只能在另一端中新增mapped屬性,多端是關係被維護端,建表時在關係被維護端(多的那一端)中建立一個外來鍵指向關係維護端的主鍵一列。 比如:
@OneToMany(mappedBy = "executableSubTaskDefinition", fetch = FetchType.LAZY)
private List<ExecutableSubTaskStatus> executableSubTaskStatusList = new ArrayList<>();
  注意,mappedBy即為ExecuteableSubTaskStatus中的@ManyToOne成員變數名稱而非資料庫欄位名稱,那麼在另一端,
@ManyToOne(cascade = CascadeType.ALL, optional = false)
private ExecutableSubTaskDefinition executableSubTaskDefinition;
 

@ManyToMany

多對多對映是相對來說最為複雜的一種對映關係,會採取中間表連線的對映策略,建立的中間表分別引入兩邊的主鍵作為外來鍵,形成兩個一對多關係。 在雙向的多對多關係中,在關係維護端(owner side)的 @ManyToMany 註解中新增 mappedBy 屬性,另一方是關係的被維護端(inverse side),關係的被維護端不能加 mappedBy 屬性,建表時,根據兩個多端的主鍵生成一箇中間表,中間表的外來鍵是兩個多端的主鍵: 關係維護端——> @ManyToMany(mappedBy="另一方的關係引用屬性") 關係被維護端——> @ManyToMany(cascade=CascadeType.ALL ,fetch = FetchType.Lazy) 比如下面的例項:
@ManyToMany
@JoinColumn(name = "task_data_range")
private List<SubTaskDefinition> subTaskDefinitions = new ArrayList<>();
 
@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(name = "sub_task_data_range", joinColumns = @JoinColumn(name = "sd_task_id", referencedColumnName = "sub_task_definition_id"),
inverseJoinColumns = @JoinColumn(name = "sd_data_id", referencedColumnName = "task_data_range_id"))
private List<TaskDataRange> taskDataRanges = new ArrayList<>();