持久化是位於JDBC之上的一個更高層抽象。持久層將對象映射到數據庫,以便在查詢、裝載、更新或刪除對象的時候,無須使用像JDBC那樣繁瑣的API。EJB的早期版本中,持久化是EJB平台的一部分。EJB3.0開始,持久化已經自成規范,被稱為Java Persistence API。
Java Persistence API定義了一種定義,可以將常規的普通Java對象(有時被稱作POJO)映射到數據庫。這些普通Java對象被稱作Entity Bean。除了是用Java Persistence元數據將其映射到數據庫外,Entity Bean與其他Java類沒有任何區別。事實上,創建一個Entity Bean對象相當於新建一條記錄,刪除一個Entity Bean會同時從數據庫中刪除對應記錄,修改一個Entity Bean時,容器會自動將Entity Bean的狀態和數據庫同步。
Java Persistence API還定義了一種查詢語言(JPQL),具有與SQL相類似的特征,只不過做了裁減,以便處理Java對象而非原始的關系表。
59.1、持久化persistence.xml配置文件
一個實體Bean應用由實體類和persistence.xml文件組成。persistence.xml文件在jar文件的META-INF目錄。persistence.xml文件指定實體Bean使用的數據源及EntityManager對象的默認行為。persistence.xml文件的配置說明如下:
<?xml version="1.0" encoding="UTF-8"?>
<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="test" transaction-type="JTA">
<jta-data-source>java:/user</jta-data-source>
<properties>
<property name="hibernate.dialect" value="org.hibernate.dialect.SQLServerDialect"/>
</properties>
</persistence-unit>
</persistence>
persistence-unit節點可以有一個或多個,每個persistence-unit節點定義了持久化內容名稱、使用的數據源及持久化產品專有屬性。name屬性定義持久化名稱。jta-data-source節點指定實體Bean使用的數據源JNDI名稱,如果應用發布在JBoss下數據源名稱帶有java:/前綴,數據源名稱大小寫敏感。properties節點用作指定持久化產品的各項屬性,各個應用服務器使用的持久化產品都不一樣,如JBoss使用Hibernate,WebLogic10使用Kodo。
59.2、JBoss數據源的配置
各種數據庫德數據源配置模版可以在[JBoss安裝目錄]\docs\examples\jca目錄中找到,默認名稱為:數據庫名+-ds.xml。
不管使用哪種數據庫都需要把它的驅動類jar包放置在[JBoss安裝目錄]\server\default\lib目錄下,放置后需要啟動JBoss服務器。
數據源文件配置好后需要放置在[JBoss安裝目錄]/server/default/deploy目錄。
SQL Server配置代碼:
<?xml version="1.0" encoding="UTF-8"?>
<datasources>
<local-tx-datasource>
<jndi-name>user</jndi-name>
<connection-url>jdbc:sqlserver://localhost:1433;DatabaseName=rep</connection-url>
<driver-class>com.microsoft.sqlserver.jdbc.SQLServerDriver</driver-class>
<user-name>sa</user-name> <password>123</password>
<metadata>
<type-mapping>MS SQLSERVER2000</type-mapping>
</metadata>
</local-tx-datasource>
</datasources>
數據源發布后,可以在http://localhost:8080/jmx-console/找到配置,如下圖:
59.3、單表映射的實體Bean
59.3.1、實體Bean代碼
@Entity
@Table(name="tbl_user")
publicclass User implements Serializable{
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name="id")
private Integer id;
@Column(name="name")
private String name;
@Column(name="age")
private String age;
public String getAge() { returnage; }
publicvoid setAge(String age) { this.age = age; }
public String getName() { returnname; }
publicvoid setName(String name) { this.name = name; }
public User() { }
public Integer getId() { returnthis.id; }
publicvoid setId(Integer id) { this.id = id; }
}
從上面代碼來看開發實體Bean非常簡單,比起普通的JavaBean就是多了些注釋。@Entity注釋指名這是一個實體Bean,@Table注釋指定了Entity所要映射帶數據庫表,其中@Table.name()用來指定映射表的表名。如果缺省@Table注釋,系統默認采用類名作為映射表的表名。實體Bean的每個實例代表數據表中的一行數據,行中的一列對應實例中的一個屬性。
@Column注釋定義了將成員屬性映射到關系表中的哪一列和該列的結構信息,屬性如下:
1)name:映射的列名。如:映射tbl_user表的name列,可以在name屬性的上面或getName方法上面加入;
2)unique:是否唯一;
3)nullable:是否允許為空;
4)length:對於字符型列,length屬性指定列的最大字符長度;
5)insertable:是否允許插入;
6)updatetable:是否允許更新;
7)columnDefinition:定義建表時創建此列的DDL;
8)secondaryTable:從表名。如果此列不建在主表上(默認是主表),該屬性定義該列所在從表的名字。
@Id注釋指定表的主鍵,它可以有多種生成方式:
1)TABLE:容器指定用底層的數據表確保唯一;
2)SEQUENCE:使用數據庫德SEQUENCE列萊保證唯一(Oracle數據庫通過序列來生成唯一ID);
3)IDENTITY:使用數據庫的IDENTITY列萊保證唯一;
4)AUTO:由容器挑選一個合適的方式來保證唯一;
5)NONE:容器不負責主鍵的生成,由程序來完成。
@GeneratedValue注釋定義了標識字段生成方式。
@Temporal注釋用來指定java.util.Date或java.util.Calender屬性與數據庫類型date、time或timestamp中的那一種類型進行映射。
@Temporal(value=TemporalType.TIME)
private Date birthday;
59.3.2、會話Bean代碼
@Stateless
publicclass UserDAO implements UserDAORemote {
@PersistenceContext(unitName="test")
private EntityManager em;
publicboolean insertUser(User user){
try{
em.persist(user);
returntrue;
}catch(Exception e){
returnfalse;
}
}
}
上面使用了一個對象:EntityManager,EntityManager是由EJB容器自動地管理和配置的,不需要用戶自己創建,它用作操作實體Bean。
如果persistence.xml文件中配置了多個不同的持久化內容。在注入EntityManager對象時必須指定持久化名稱,可以通過@PersistenceContext注釋的unitName屬性進行制定。例如:
@PersistenceContext(unitName="test")
private EntityManager em;
59.4、屬性映射
如果不想讓一些成員屬性映射成數據庫字段,可以使用@Transient注釋進行標注。
@Transient
private String sex;
如果想映射枚舉對象到數據庫就需要使用@Enumerated注釋進行標注。
@Enumerated(EnumType.STRING)
@Column(name="address_type")
private AddressType type;//地址類型
有時可能需要存放一些文本或大文本數據進數據庫,JDBC使用java.sql.Blob類型存放二進制數據,java.sql.Clob類型存放字符數據,這些數據都是非常占內存的,@Lob注釋用作映射這些大數據類型,當屬性的類型為byte[],Byte[]或java.io.Serializable時,@Lob注釋映射為數據庫的Blob類型,當屬性的類型為char[],Character[]或java.lang.String時,@Lob注釋將映射為數據庫的Clob類型。
對於加了@Lob注釋的大數據類型,為了避免每次加載實體時占用大量內存,有必要對該屬性進行延時加載,這是需要用到@Basic注釋。@Basic注釋的定義:FetchType屬性指定是否延時加載,默認為立即加載,optional屬性指定在生成數據庫結構時字段能否為null。
@Lob
@Basic(fetch=FetchType.LAZY)
@Column(name="info")
private String content;
59.5、持久化實體管理器EntityManager
EntityManager是用來對實體Bean進行操作的輔助類。可以用來產生/刪除持久化的實體Bean,通過主鍵查找實體Bean,也可以通過EJB3QL語言查找滿足條件的實體Bean。實體Bean被EntityManager管理時,EntityManager跟蹤他的狀態改變,在任何決定更新實體Bean的時候便會把發生改變的值同步到數據庫中。當實體Bean從EntityManager分離后,他是不受管理的,EntityManager無法跟蹤他的任何狀態改變。
59.5.1、Entity獲取find()或getReference()
如果知道Entity的唯一標識符,可以用find()或getReference()方法獲取Entity。
public User findUser(Integer userid){
returnem.find(User.class, userid);
}
當在數據庫中沒有找到記錄時,getReference()和find()是有區別的,find()方法會返回null,而getReference()方法會拋出javax.persistence.EntityNotFoundException例外,另外getReference()方法不保證實體Bean已被初始化。如果傳遞進getReference()或find()方法的參數不是實體Bean,都會引發IllegalArgumentException。
59.5.2、添加persist()
@PersistenceContext(unitName="test")
private EntityManager em;
publicboolean insertUser(User user){
try{
em.persist(user);
returntrue;
}catch(Exception e){
returnfalse;
}
}
如果傳遞進persist()方法的參數不是實體Bean,都會引發IllegalArgumentException例外。
59.5.3、更新實體
當實體正在被容器管理時,可以調用實體的set方法對數據進行修改,在容器決定flush時,更新的數據才會同步到數據庫。如果希望修改后的數據實時同步到數據庫,可以執行EntityManager.flush()方法。
publicboolean updateUser(Integer userid){
User user=(User)em.find(User.class,userid);
try{
user.setName("yyyyyy");
returntrue;
}catch(Exception e){
returnfalse;
}
}
59.5.4、合並merge()
merge()方法是在實體Bean已經脫離了EntityManager的管理時使用,當容器決定flush時,數據將會同步到數據庫中。
publicboolean updateUser(User user){
try{
em.merge(user);
returntrue;
}catch(Exception e){
returnfalse;
}
}
執行em.merge(user)方法時,容器的工作規則:1)如果此時容器中已經存在一個受容器管理的具有相同ID的user實例,容器就會把參數user的內容拷貝進這個受管理的實例,merge()方法返回受管理的實例,但參數user仍然是分離的不受管理的。容器在決定Flush時把實例同步到數據庫中;2)容器中不存在具有相同ID的user實例。容器根據傳入的user參數拷貝出一個受容器管理的person實例,同時merge()會返回出這個受管理的實例,但是參數user仍然是分離的不受管理的。容器在決定Flush時把實例同步到數據庫中。
如果傳入merge()方法的參數不是實體Bean,會引發一個IllegalArgumentException例外。
59.5.5、刪除remove()
publicboolean deleteUser(Integer id){
try{
User user=em.find(User.class, id);
em.remove(user);
returntrue;
}catch(Exception e){
returnfalse;
}
}
59.6、執行JPQL操作createQuery()
除了使用find()或getReference()方法來獲得Entity Bean之外,還可以通過JPQL得到實體Bean。要執行JPQL語句,必須通過EntityManager的createQuery或createNamedQuery()方法創建一個Query對象。
public List queryList(){
Query query=em.createQuery("select u from User u where u.id=3");
return query.getResultList();
}
59.6.1、執行SQL操作createNativeQuery()
這里操作的是SQL語句,並非JPQL。
Query query=em.createQuery("select * from tbl_user u");
59.6.2、刷新實體refresh()
如果懷疑當前被管理的實體已經不是數據庫中最新的數據,可以通過refresh()方法刷新實體,容器會把數據庫中的新值重寫進實體。這種情況一般發生在獲取實體之后,有人更新了數據庫中的記錄,這是需要得到最新數據。當然再次調用find或getReference()方法也可以得到最新數據,但這種做法並不優雅。
em.refresh(user);
59.6.3、檢測實體當前是否在被管理中contains()
contains()方法是用一個實體作為參數,如果這個實體對象當前正被持久化內容管理,返回為true,否則為false。
try{
User user=em.find(User.class, id);
if(em.contains(user)){
System.out.println("被管理中");
}else{
System.out.println("沒有管理");
}
returntrue;
}catch(Exception e){
returnfalse;
}
59.6.4、分離所有當前正在被管理的實體clear()
在處理大量實體的時候,如果不把已經處理過的實體從EntityManager中分離出來,將會消耗大量的內存。調用EntityManager的clear()方法后,所有正在被管理的實體將會從持久化內容中分離出來。
有一點需要注意,在事務沒有提交前,如果調用clear()方法之前對實體所作的任何改變將會丟失,所以建議在調用clear()方法之前先調用flush()方法保存更改。
59.6.5、將實體的改變立刻刷新到數據庫中flush()
當實體管理器對象在一個Session Bean中使用時,它是和服務器的事務上下文綁定的。實體管理器在服務器的事務提交時並且同步內容。在一個Session Bean中,服務器的事務默認地會在調用堆棧的最后提交。為了只在當事務提交時才將改變更新到數據庫中,容器將所有數據庫操作集中到一個批量中,這樣就減少了與數據庫的交互。
當調用persist()、merge()或remove()這些方法時,更新並不會立刻同步到數據庫中,直到容器決定刷新到數據庫中時才會執行,默認情況下,容器決定刷新是在“相關查詢”執行前或事務提交時發生,當然“相關查詢”除find()和getReference()之外,這兩個方法是不會引起容器觸發刷新動作的,默認的刷新模式是可以改變的。
59.6.6、改變實體管理器的Flush模式setFlushMode()
默認情況下,實體管理器的Flush模式為AUTO。
兩者的區別及使用場合:
FlushModeType.AUTO:刷新在查詢語句執行前(除了find()和getReference())或事務提交時才發生,在大量更新數據的過程中沒有任何查詢語句的執行時使用。
FlushModeType.COMMIT:刷新只在事務提交時才發生,在大量更新數據的過程中存在查詢的執行時使用。
59.6.7、獲取持久化實現者的引用getDelegate()
可以獲取EntityManager持久化實現者的引用。
@PersistenceContext(unitName="test")
private EntityManager em;
HibernateEntityManager hibernate=(HibernateEntityManager)em.getDelegate();
59.7、關系/對象映射
59.7.1、映射的表名或列名與數據庫保留字同名時的處理
當映射的表名或列名於數據庫保留字同名時,持久化引擎轉譯后的SQL在執行時將會出錯。
如:
@Entity
@Table(name=”Order”)
public class Order implements Serializable{}
表名Order與排序保留字“Order”相同,導致SQL語法出錯。
針對上面的情況,在JBoss持久化產品中沒有解決方案。一種變通的方法就是加上單引號或者方括號的解決方案。該方法針對具體數據庫,不利於數據庫移植。建議大家不要使用保留字作為表名或列名。
59.7.2、一對一映射
一對一關系,需要在關系維護端的@OneToOne注釋中定義mappedBy屬性。在關系被維護端建立外鍵列指向關系維護的主鍵列。
User代碼:
@Entity
@Table(name="tbl_user")
publicclass User implements Serializable{
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
@Column(name="name")
private String name;
private String sex;
private String age;
@Temporal(value=TemporalType.DATE)
private Date birthday;
@OneToOne(optional=true,cascade=CascadeType.ALL,mappedBy="user")
private Card card;
public User() { }
public Card getCard() { returncard; }
publicvoid setCard(Card card) { this.card = card; }
public String getAge() { returnage; }
publicvoid setAge(String age) { this.age = age; }
public Date getBirthday() { returnbirthday; }
publicvoid setBirthday(Date birthday) { this.birthday = birthday; }
public String getName() { returnname; }
publicvoid setName(String name) { this.name = name; }
public String getSex() { returnsex; }
publicvoid setSex(String sex) { this.sex = sex; }
public Integer getId() { returnthis.id; }
publicvoid setId(Integer id) { this.id = id; }
publicint hashCode() { return (this.id == null) ? 0 : this.id.hashCode(); }
publicboolean equals(Object object) {
if (object instanceof User) {
final User obj = (User) object;
return (this.id != null) ? this.id.equals(obj.id) : (obj.id == null);
}
returnfalse;
}
}
@OneToOne注釋指明User與Card為一對一關系,@OneToOne注釋有5個屬性:targetEntity、cascade、fetch、optional和mappedBy。
1)targetEntity:Class類型的屬性
2)mappedBy:String類型的屬性。定義類之間的雙向關聯。如果類之間是單向關系,不需要提供定義,如果類和類之間形成雙向關系。就需要使用這個屬性進行定義,否則可能引起數據一致性的問題。
3)cascade:CascadeType類型。該屬性定義類和類之間的級聯關系。定義級聯關系將被容器視為當前類對象及其關聯類對象采取相同的操作,而且這種關系是遞歸的。cascade的值只能從CascadeType.PERSIST(級聯新建)、CascadeType.REMOVE(級聯刪除)、CascadeType.REFRESH(級聯刷新)、Cascade.MERGE(級聯更新)中選擇一個或多個。還有一個選擇是使用CascadeType.ALL,表示選擇全部四項。
4)fetch:FetchType類型的屬性。可選擇項包括:FetchType.EAGER和FetchType.LAZY。前者表示關系類在主體類加載的時候同時加載,后者表示關系類在被訪問時才加載。默認值是FetchType.LAZY。
5)optional:表示被維護對象是否需要存在。如果為真,說明card屬性可以null,也就是允許沒有身份證,未成年人就是沒有身份證。
Card代碼:
@Entity
@Table(name="tbl_card")
publicclass Card implements Serializable{
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name="id")
private Integer id;
@Column(name="cardno")
private String cardno;
@OneToOne(optional=false,cascade=CascadeType.REFRESH)
@JoinColumn(referencedColumnName="id")
private User user;
public Card() { }
public User getUser() { returnuser; }
publicvoid setUser(User user) { this.user = user; }
public String getCardno() { returncardno; }
publicvoid setCardno(String cardno) { this.cardno = cardno; }
public Integer getId() { returnthis.id; }
publicvoid setId(Integer id) { this.id = id; }
publicint hashCode() { return (this.id == null) ? 0 : this.id.hashCode(); }
publicboolean equals(Object object) {
if (object instanceof Card) {
final Card obj = (Card) object;
return (this.id != null) ? this.id.equals(obj.id) : (obj.id == null);
}
returnfalse;
}
}
@OneToOne注釋指明Card與User為一對一關系,Card是關系被維護端,optional=false設置user屬性值不能為空,也就是身份證必須有對應的主人。@JoinColumn(name=”user_id” referencedColumnName=”id” unique=”true”)指明tbl_card表的user_id列作為外鍵與tbl_user表的person_id列進行關聯,unique=true指明user_id列的值不可重復。
59.7.3、一對多及多對一映射
@Entity
@Table(name="tbl_order")
publicclass Order implements Serializable{
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name="order_id")
private Integer id;
private String name;
@OneToMany(targetEntity=OrderItem.class,cascade=CascadeType.ALL,mappedBy="order")
private Set set=new HashSet();
public Order() { }
public String getName() { returnname; }
publicvoid setName(String name) { this.name = name; }
public Set getSet() { returnset; }
publicvoid setSet(Set set) { this.set = set; }
public Integer getId() { returnthis.id; }
publicvoid setId(Integer id) { this.id = id; }
publicint hashCode() { return (this.id == null) ? 0 : this.id.hashCode(); }
publicboolean equals(Object object) {
if (object instanceof Order) {
final Order obj = (Order) object;
return (this.id != null) ? this.id.equals(obj.id) : (obj.id == null);
}
returnfalse;
}
}
---------------------------------------------------------------------------------------------------------
@Entity
@Table(name="tbl_item")
publicclass OrderItem implements Serializable{
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name="item_id")
private Integer id;
private String goodsname;
@ManyToOne(cascade=CascadeType.REFRESH,optional=false)
@JoinColumn(name="item_order_id",referencedColumnName="order_id")
private Order order;
public OrderItem() { }
public String getGoodsname() { returngoodsname; }
publicvoid setGoodsname(String goodsname) { this.goodsname = goodsname; }
public Order getOrder() { returnorder; }
publicvoid setOrder(Order order) { this.order = order; }
public Integer getId() { returnthis.id; }
publicvoid setId(Integer id) { this.id = id; }
publicint hashCode() { return (this.id == null) ? 0 : this.id.hashCode(); }
publicboolean equals(Object object) {
if (object instanceof OrderItem) {
final OrderItem obj = (OrderItem) object;
return (this.id != null) ? this.id.equals(obj.id) : (obj.id == null);
}
returnfalse;
}
}
59.7.4、多對多映射
@Entity
@Table(name="tbl_student")
publicclass Student implements Serializable{、
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name="student_id")
private Integer id;
private String name;
@ManyToMany(cascade=CascadeType.ALL,targetEntity=Teacher.class)
@JoinTable(name="tbl_stu_teacher",inverseJoinColumns={
@JoinColumn(name="teacher_id",referencedColumnName="teacher_id")},
joinColumns={@JoinColumn(name="student_id",referencedColumnName="student_id")})
private Set set=new HashSet();
public Student() { }
public String getName() { returnname; }
publicvoid setName(String name) { this.name = name; }
public Set getSet() { returnset; }
publicvoid setSet(Set set) { this.set = set; }
public Integer getId() { returnthis.id; }
publicvoid setId(Integer id) { this.id = id; }
publicint hashCode() { return (this.id == null) ? 0 : this.id.hashCode(); }
publicboolean equals(Object object) {
if (object instanceof Student) {
final Student obj = (Student) object;
return (this.id != null) ? this.id.equals(obj.id) : (obj.id == null);
}
returnfalse;
}
}
-------------------------------------------------------------------------------------------------------------
@Entity
@Table(name="tbl_teacher")
publicclass Teacher implements Serializable{
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name="teacher_id")
private Integer id;
private String name;
@ManyToMany(targetEntity=Student.class,mappedBy="set")
private Set set=new HashSet();
public Teacher() { }
public String getName() { returnname; }
publicvoid setName(String name) { this.name = name; }
public Set getSet() { returnset; }
publicvoid setSet(Set set) { this.set = set; }
public Integer getId() { returnthis.id; }
publicvoid setId(Integer id) { this.id = id; }
publicint hashCode() { return (this.id == null) ? 0 : this.id.hashCode(); }
publicboolean equals(Object object) {
if (object instanceof Teacher) {
final Teacher obj = (Teacher) object;
return (this.id != null) ? this.id.equals(obj.id) : (obj.id == null);
}
returnfalse;
}
}