今后三天的課程內容為:JPA與EJB。我們的班型是JAVAEE精品就業班,我們學習的是JAVAEE嗎?JAVAEE是分布式企業級應用,我們之前學習的JSP與Servlet都是JAVAEE的內容,是基礎內容。那么JAVAEE的核心是什么?沒有學習JAVAEE的核心還算是學習過JAVAEE嗎?JAVAEE的核心是EJB!
EJB具體是做什么的?目前還不清楚,這部分內容在明天和后天會學習。今日的重點內容是JPA,什么是JPA呢?JPA是EJB3中持久化部分。
Sun引入新的JPA ORM規范出于兩個原因:其一,簡化現有Java EE和Java SE應用的對象持久化的開發工作;其二,Sun希望整合對ORM技術,實現天下歸一。
JPA由EJB 3.0軟件專家組開發,作為JSR-220實現的一部分。但它不囿于EJB 3.0,你可以在Web應用、甚至桌面應用中使用。JPA的宗旨是為POJO提供持久化標準規范,由此可見,經過這幾年的實踐探索,能夠脫離容器獨立運行,方便開發和測試的理念已經深入人心了。目前Hibernate 3.2、TopLink 10.1.3以及OpenJPA都提供了JPA的實現。
JPA的總體思想和現有Hibernate、TopLink,JDO等ORM框架大體一致。
一、搭建JPA環境
我們之前已經學習過Hibernate,學習JPA就比較容易了。因為Hibernate是JPA的一種實現。
1.創建一個普通的JAVA工程。
2.導入“hibernate-distribution-3.3.1.GA\jpa”目錄下的所有Jar包,還有數據庫驅動包。我們使用的是MySQL。
3.在“SRC”上右鍵創建一個“META-INF”目錄,“META-INF”實際位置在classpath下。
4.在“META-INF”目錄中添加一個“persistence.xml”文件,這個是JPA的配置文件哦。文件內容:
<?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="jpa" transaction-type="RESOURCE_LOCAL"> <class>cn.itcast.cc.jpa.entity.Customer</class> <!-- JPA實現,使用hibernate --> <properties> <property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver"/> <property name="hibernate.connection.url" value="jdbc:mysql://localhost:3306/jpa"/> <property name="hibernate.connection.username" value="root"/> <property name="hibernate.connection.password" value="root"/> <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect"/> <property name="hibernate.hbm2ddl.auto" value="create"/> <property name="hibernate.show_sql" value="true"/> </properties> </persistence-unit> </persistence> |
5.創建“cn.itcast.cc.jpa.entity.Customer”實體類:
package cn.itcast.cc.jpa.entity; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; @Entity // 必須指定 public class Customer { @Id // 必須指定 @GeneratedValue private Integer id; private String name; // getters And setters... } |
其中“@Entity”與“@Id”必須指定,JPA通過這些注解生成相應的表和字段。加在屬性上的注解也可以加在屬性的getter或setter方法。這些用于設置字段的注解,要么全加在屬性上,要么全加在屬性方法上。JPA以“@Id”所在位置為參照,判斷應該使用屬性上的注解還是屬性方法上的注解。
6.編寫測試類:
package cn.itcast.cc.jpa; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.EntityTransaction; import javax.persistence.Persistence; import cn.itcast.cc.jpa.entity.Customer; public class App { public static void main(String[] args){ // 創建實體管理對象工廠,相當于Hiberante的SessionFactory。 EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpa"); // 創建實體管理對象,相當于Hiberante的Session。 EntityManager em = emf.createEntityManager(); // 獲取實體事務對象 EntityTransaction et = em.getTransaction(); // 開始事務 et.begin(); // 創建Customer實體對象 Customer customer = new Customer(); customer.setName("changcheng"); // 持久化customer實體對象(保存) em.persist(customer); // 提交事務 et.commit(); // 關閉 em.close(); emf.close(); } } |
Very簡單吧!強悍的地方是我們可以將hibernate更換為其他實現了JPA接口的框架,然后修改一個persistence.xml文件的配置信息,其他地方無需修改。
二、JPA的CURD
JPA的CURD與Hibernate的CURD操作不大相似,但也十分簡單。
1.插入記錄
上邊的例子,我們正是使用到插入的方法,將實體保存到數據庫:
@Test public void create(){ EntityManager em = emf.createEntityManager(); EntityTransaction et = em.getTransaction(); et.begin(); Customer customer = new Customer(); customer.setName("china"); // 保存到數據庫 em.persist(customer); et.commit(); em.close(); emf.close(); } |
2.更新記錄
JPA中沒有update方法,我們需要先取出記錄。然后再對記錄進行修改,持久化對象可以自動更新到數據庫中。
@Test public void update(){ EntityManager em = emf.createEntityManager(); EntityTransaction et = em.getTransaction(); et.begin(); // 查詢數據 Customer customer = em.find(Customer.class, new Integer(1)); // 更新數據 customer.setName("new name"); et.commit(); em.close(); emf.close(); } |
還可以使用“customer = em.getReference(Customer.class, new Integer(1));”。find方法相當于Hibernate的get方法,getReference相當于Hibernate的load方法。
3.查詢記錄
查詢可以使用find和getReference方法,這里不列出了。
4.刪除記錄
JPA有一個remove方法,用來刪除查詢到的實體。
@Test public void remove(){ EntityManager em = emf.createEntityManager(); EntityTransaction et = em.getTransaction(); et.begin(); // 查詢數據 Customer customer = em.getReference(Customer.class, new Integer(1)); // 刪除數據 em.remove(customer); et.commit(); em.close(); emf.close(); } |
JPA還有一個Merge的方法,用于同步游離狀態。“cus = em.merge(customer);” merge返回的cus為緩存中持久化對象的引用,customer則不是。
三、JPA中的注解
我們在學習hibernate時使用entity.hbm.xml映射文件,描述對象與對象的關系,對象與數據表格的配置。那是一件十分郁悶的工作,JPA中支持使用注解描述以上關系,Hibernate也支持。使用注解描述,更直觀、簡單。
1.Table
Table用來定義entity主表的name,catalog,schema等屬性。
元數據屬性說明:
· name: 表名
· catalog: 對應關系數據庫中的catalog
· schema:對應關系數據庫中的schema
· UniqueConstraints:定義一個UniqueConstraint數組,指定需要建唯一約束的列
2.SecondaryTable
一個entity class可以映射到多表,SecondaryTable用來定義單個從表的名字,主鍵名字等屬性。使用SecondaryTable可以實現一對一關系的主鍵關聯。
元數據屬性說明:
· name: 表名
· catalog: 對應關系數據庫中的catalog
· schema:對應關系數據庫中的schema
· pkJoin: 定義一個PrimaryKeyJoinColumn數組,指定從表的主鍵列
· UniqueConstraints:定義一個UniqueConstraint數組,指定需要建唯一約束的列
下面的代碼說明Customer類映射到兩個表,主表名是CUSTOMER,從表名是CUST_DETAIL,從表的主鍵列和主表的主鍵列類型相同,列名為CUST_ID。
@Entity @Table(name="CUST") public class Customer { ... } |
3.SecondaryTables
當一個entity class映射到一個主表和多個從表時,用SecondaryTables來定義各個從表的屬性。
元數據屬性說明:
· value: 定義一個SecondaryTable數組,指定每個從表的屬性。
@Entity @Table(name="CUSTOMER") @SecondaryTable(name="CUST_DETAIL",pkJoin=@PrimaryKeyJoinColumn(name="CUST_ID")) public class Customer { ... } |
4.UniqueConstraint
UniqueConstraint定義在Table或SecondaryTable元數據里,用來指定建表時需要建唯一約束的列。
元數據屬性說明:
· columnNames:定義一個字符串數組,指定要建唯一約束的列名。
@Table(name = "CUSTOMER") @SecondaryTables(value = { @SecondaryTable(name = "CUST_NAME", pkJoin = { @PrimaryKeyJoinColumn(name = "STMO_ID", referencedColumnName = "id") }), @SecondaryTable(name = "CUST_ADDRESS", pkJoin = { @PrimaryKeyJoinColumn(name = "STMO_ID", referencedColumnName = "id") }) }) public class Customer {} |
5.Column
Column元數據定義了映射到數據庫的列的所有屬性:列名,是否唯一,是否允許為空,是否允許更新等。
元數據屬性說明:
· name:列名。
· unique: 是否唯一
· nullable: 是否允許為空
· insertable: 是否允許插入
· updatable: 是否允許更新
· columnDefinition: 定義建表時創建此列的DDL
· secondaryTable: 從表名。如果此列不建在主表上(默認建在主表),該屬性定義該列所在從表的名字。
@Entity @Table(name="EMPLOYEE", uniqueConstraints={@UniqueConstraint(columnNames={"EMP_ID", "EMP_NAME"})}) public class Employee { ... } |
6.JoinColumn
如果在entity class的field上定義了關系(one2one或one2many等),我們通過JoinColumn來定義關系的屬性。JoinColumn的大部分屬性和Column類似。
元數據屬性說明:
· name:列名。
· referencedColumnName:該列指向列的列名(建表時該列作為外鍵列指向關系另一端的指定列)
· unique: 是否唯一
· nullable: 是否允許為空
· insertable: 是否允許插入
· updatable: 是否允許更新
· columnDefinition: 定義建表時創建此列的DDL
· secondaryTable: 從表名。如果此列不建在主表上(默認建在主表),該屬性定義該列所在從表的名字。
下面的代碼說明Custom和Order是一對一關系。在Order對應的映射表建一個名為CUST_ID的列,該列作為外鍵指向Custom對應表中名為ID的列。
public class Person { @Column(name = "PERSONNAME", unique = true, nullable = false, updatable = true) private String name; @Column(name = "PHOTO", columnDefinition = "BLOB NOT NULL", secondaryTable = "PER_PHOTO") private byte[] picture; } |
7.JoinColumns
如果在entity class的field上定義了關系(one2one或one2many等),并且關系存在多個JoinColumn,用JoinColumns定義多個JoinColumn的屬性。
元數據屬性說明:
· value: 定義JoinColumn數組,指定每個JoinColumn的屬性。
下面的代碼說明Custom和Order是一對一關系。在Order對應的映射表建兩列,一列名為CUST_ID,該列作為外鍵指向Custom對應表中名為ID的列,另一列名為CUST_NAME,該列作為外鍵指向Custom對應表中名為NAME的列。
public class Custom { @OneToOne @JoinColumn(name = "CUST_ID", referencedColumnName = "ID", unique = true, nullable = true, updatable = true) public Order getOrder() { return order; } } |
8.Id
聲明當前field為映射表中的主鍵列。id值的獲取方式有五種:TABLE, SEQUENCE, IDENTITY, AUTO, NONE。Oracle和DB2支持SEQUENCE,SQL Server和Sybase支持IDENTITY,mysql支持AUTO。所有的數據庫都可以指定為AUTO,我們會根據不同數據庫做轉換。NONE (默認)需要用戶自己指定Id的值。元數據屬性說明:
· generate():主鍵值的獲取類型
· generator():TableGenerator的名字(當generate=GeneratorType.TABLE才需要指定該屬性)
下面的代碼聲明Task的主鍵列id是自動增長的。(Oracle和DB2從默認的SEQUENCE取值,SQL Server和Sybase該列建成IDENTITY,mysql該列建成auto increment。)
public class Custom { @OneToOne @JoinColumns( { @JoinColumn(name = "CUST_ID", referencedColumnName = "ID"), @JoinColumn(name = "CUST_NAME", referencedColumnName = "NAME") }) public Order getOrder() { return order; } } |
9.IdClass
當entity class使用復合主鍵時,需要定義一個類作為id class。id class必須符合以下要求:類必須聲明為public,并提供一個聲明為public的空構造函數。必須實現Serializable接,覆寫 equals()和hashCode()方法。entity class的所有id field在id class都要定義,且類型一樣。
元數據屬性說明:
· value: id class的類名
@Entity @Table(name = "OTASK") public class Task { @Id(generate = GeneratorType.AUTO) public Integer getId() { return id; } } |
public class EmployeePK implements java.io.Serializable { String empName; Integer empAge; public EmployeePK() {......} public boolean equals(Object obj){......} public int hashCode(){......} } @IdClass(value = com.acme.EmployeePK.class) @Entity(access = FIELD) public class Employee { @Id String empName; @Id Integer empAge; } |
10.MapKey
在一對多,多對多關系中,我們可以用Map來保存集合對象。默認用主鍵值做key,如果使用復合主鍵,則用id class的實例做key,如果指定了name屬性,就用指定的field的值做key。
元數據屬性說明:
· name: 用來做key的field名字
下面的代碼說明Person和Book之間是一對多關系。Person的books字段是Map類型,用Book的isbn字段的值作為Map的key。
11.OrderBy
在一對多,多對多關系中,有時我們希望從數據庫加載出來的集合對象是按一定方式排序的,這可以通過OrderBy來實現,默認是按對象的主鍵升序排列。
元數據屬性說明:
· value: 字符串類型,指定排序方式。格式為"fieldName1 [ASC|DESC],fieldName2 [ASC|DESC],......",排序類型可以不指定,默認是ASC。
下面的代碼說明Person和Book之間是一對多關系。集合books按照Book的isbn升序,name降序排列。
12.PrimaryKeyJoinColumn
在三種情況下會用到PrimaryKeyJoinColumn。
· 繼承。
· entity class映射到一個或多個從表。從表根據主表的主鍵列(列名為referencedColumnName值的列),建立一個類型一樣的主鍵列,列名由name屬性定義。
· one2one關系,關系維護端的主鍵作為外鍵指向關系被維護端的主鍵,不再新建一個外鍵列。
元數據屬性說明:
· name:列名。
· referencedColumnName:該列引用列的列名
· columnDefinition: 定義建表時創建此列的DDL
下面的代碼說明Customer映射到兩個表,主表CUSTOMER,從表CUST_DETAIL,從表需要建立主鍵列CUST_ID,該列和主表的主鍵列id除了列名不同,其他定義一樣。
@Table(name = "PERSON") public class Person { @OneToMany(targetEntity = Book.class, cascade = CascadeType.ALL, mappedBy = "person") @MapKey(name = "isbn") private Map books = new HashMap(); } |
@Table(name = "MAPKEY_PERSON") public class Person { @OneToMany(targetEntity = Book.class, cascade = CascadeType.ALL, mappedBy = "person") @OrderBy(name = "isbn ASC, name DESC") private List books = new ArrayList(); } |
下面的代碼說明Employee和EmployeeInfo是一對一關系,Employee的主鍵列id作為外鍵指向EmployeeInfo的主鍵列INFO_ID。
13.PrimaryKeyJoinColumns
如果entity class使用了復合主鍵,指定單個PrimaryKeyJoinColumn不能滿足要求時,可以用PrimaryKeyJoinColumns來定義多個PrimaryKeyJoinColumn。
元數據屬性說明:
· value: 一個PrimaryKeyJoinColumn數組,包含所有PrimaryKeyJoinColumn。
下面的代碼說明了Employee和EmployeeInfo是一對一關系。他們都使用復合主鍵,建表時需要在Employee表建立一個外鍵,從Employee的主鍵列id,name指向EmployeeInfo的主鍵列INFO_ID和INFO_NAME。
@Entity @Table(name="CUSTOMER") @SecondaryTable(name="CUST_DETAIL",pkJoin=@PrimaryKeyJoinColumn(name="CUST_ID",referencedColumnName="id")) public class Customer { @Id(generate = GeneratorType.AUTO) public Integer getId() { return id; } } |
@Table(name = "Employee") public class Employee { @OneToOne @PrimaryKeyJoinColumn(name = "id", referencedColumnName = "INFO_ID") EmployeeInfo info; } |
@Entity @IdClass(EmpPK.class) @Table(name = "EMPLOYEE") public class Employee { private int id; private String name; private String address; @OneToOne(cascade = CascadeType.ALL) @PrimaryKeyJoinColumns( { @PrimaryKeyJoinColumn(name = "id", referencedColumnName = "INFO_ID"), @PrimaryKeyJoinColumn(name = "name", referencedColumnName = "INFO_NAME") }) EmployeeInfo info; } @Entity @IdClass(EmpPK.class) @Table(name = "EMPLOYEE_INFO") public class EmployeeInfo { @Id @Column(name = "INFO_ID") private int id; @Id @Column(name = "INFO_NAME") private String name; }
|
14.Transient
Transient用來注釋entity的屬性,指定的這些屬性不會被持久化,也不會為這些屬性建表。
15.Version
Version指定實體類在樂觀事務中的version屬性。在實體類重新由EntityManager管理并且加入到樂觀事務中時,保證完整性。每一個類只能有一個屬性被指定為version,version屬性應該映射到實體類的主表上。
下面的代碼說明versionNum屬性作為這個類的version,映射到數據庫中主表的列名是OPTLOCK。
16.Lob
Lob指定一個屬性作為數據庫支持的大對象類型在數據庫中存儲。使用LobType這個枚舉來定義Lob是二進制類型還是字符類型。
LobType枚舉類型說明:
· BLOB 二進制大對象,Byte[]或者Serializable的類型可以指定為BLOB。
· CLOB 字符型大對象,char[]、Character[]或String類型可以指定為CLOB。
元數據屬性說明:
· fetch: 定義這個字段是lazy loaded還是eagerly fetched。數據類型是FetchType枚舉,默認為LAZY,即lazy loaded.
· type: 定義這個字段在數據庫中的JDBC數據類型。數據類型是LobType枚舉,默認為BLOB。
下面的代碼定義了一個BLOB類型的屬性和一個CLOB類型的屬性。
17.JoinTable
JoinTable在many-to-many關系的所有者一邊定義。如果沒有定義JoinTable,使用JoinTable的默認值。
元數據屬性說明:
· table:這個join table的Table定義。
· joinColumns:定義指向所有者主表的外鍵列,數據類型是JoinColumn數組。
· inverseJoinColumns:定義指向非所有者主表的外鍵列,數據類型是JoinColumn數組。
下面的代碼定義了一個連接表CUST和PHONE的join table。join table的表名是CUST_PHONE,包含兩個外鍵,一個外鍵是CUST_ID,指向表CUST的主鍵ID,另一個外鍵是PHONE_ID,指向表PHONE的主鍵ID。
18.TableGenerator
TableGenerator定義一個主鍵值生成器,在Id這個元數據的generate=TABLE時,generator屬性中可以使用生成器的名字。生成器可以在類、方法或者屬性上定義。
生成器是為多個實體類提供連續的ID值的表,每一行為一個類提供ID值,ID值通常是整數。
元數據屬性說明:
· name:生成器的唯一名字,可以被Id元數據使用。
· table:生成器用來存儲id值的Table定義。
· pkColumnName:生成器表的主鍵名稱。
· valueColumnName:生成器表的ID值的列名稱。
· pkColumnValue:生成器表中的一行數據的主鍵值。
· initialValue:id值的初始值。
· allocationSize:id值的增量。
下面的代碼定義了兩個生成器empGen和addressGen,生成器的表是ID_GEN。
@Transient private String name; |
@Version @Column("OPTLOCK") protected int getVersionNum() { return versionNum; } |
@Lob @Column(name="PHOTO" columnDefinition="BLOB NOT NULL") protected JPEGImage picture; @Lob(fetch=EAGER, type=CLOB) @Column(name="REPORT") protected String report; |
19.SequenceGenerator
SequenceGenerator定義一個主鍵值生成器,在Id這個元數據的generator屬性中可以使用生成器的名字。生成器可以在類、方法或者屬性上定義。生成器是數據庫支持的sequence對象。
元數據屬性說明:
· name:生成器的唯一名字,可以被Id元數據使用。
· sequenceName:數據庫中,sequence對象的名稱。如果不指定,會使用提供商指定的默認名稱。
· initialValue:id值的初始值。
· allocationSize:id值的增量。
下面的代碼定義了一個使用提供商默認名稱的sequence生成器。
20.DiscriminatorColumn
DiscriminatorColumn定義在使用SINGLE_TABLE或JOINED繼承策略的表中區別不繼承層次的列。
元數據屬性說明:
· name:column的名字。默認值為TYPE。
· columnDefinition:生成DDL的sql片斷。
· length:String類型的column的長度,其他類型使用默認值10。
下面的代碼定義了一個列名為DISC,長度為20的String類型的區別列。
@JoinTable(table = @Table(name = CUST_PHONE), joinColumns = @JoinColumn(name = "CUST_ID", referencedColumnName = "ID"), inverseJoinColumns = @JoinColumn(name = "PHONE_ID", referencedColumnName = "ID")) |
@Entity public class Employee { // ... @TableGenerator(name = "empGen", table = @Table(name = "ID_GEN"), pkColumnName = "GEN_KEY", valueColumnName = "GEN_VALUE", pkColumnValue = "EMP_ID", allocationSize = 1) @Id(generate = TABLE, generator = "empGen") public int id; // ... } @Entity public class Address { // ... @TableGenerator(name = "addressGen", table = @Table(name = "ID_GEN"), pkColumnValue = "ADDR_ID") @Id(generate = TABLE, generator = "addressGen") public int id; // ... } |
這部分資料并非本人一一總結,而是取自互聯網。
其中有一部分的內容,在明天的課程中才會講到。