JPA spring @Entity @Table 標籤使用介紹
原文地址:http://blog.csdn.net/lyq123333321/article/details/44217409
持久化是位於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>
59.3、單表對映的實體Bean
59.3.1、實體Bean程式碼
@Entity
@Table(name="tbl_user")
publicclassUserimplementsSerializable{
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name="id")
privateIntegerid;
@Column(name="name")
privateStringname;
@Column(name="age")
privateStringage;
publicString getAge() { returnage; }
publicvoidsetAge(String age) { this.age= age; }
publicString getName() { returnname; }
publicvoidsetName(String name) { this.name= name; }
publicUser() { }
publicInteger getId() { returnthis.id; }
publicvoidsetId(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)
privateDatebirthday;
59.3.2、會話Bean程式碼
@Stateless
publicclassUserDAOimplementsUserDAORemote {
@PersistenceContext(unitName="test")
privateEntityManagerem;
publicbooleaninsertUser(User user){
try{
em.persist(user);
returntrue;
}catch(Exception e){
returnfalse;
}
}
}
上面使用了一個物件:EntityManager,EntityManager是由EJB容器自動地管理和配置的,不需要使用者自己建立,它用作操作實體Bean。
如果persistence.xml檔案中配置了多個不同的持久化內容。在注入EntityManager物件時必須指定持久化名稱,可以通過@PersistenceContext註釋的unitName屬性進行制定。例如:
@PersistenceContext(unitName="test")
privateEntityManagerem;
59.4、屬性對映
如果不想讓一些成員屬性對映成資料庫欄位,可以使用@Transient註釋進行標註。
@Transient
privateStringsex;
如果想對映列舉物件到資料庫就需要使用@Enumerated註釋進行標註。
@Enumerated(EnumType.STRING)
@Column(name="address_type")
privateAddressTypetype;//地址型別
有時可能需要存放一些文字或大文字資料進資料庫,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")
privateStringcontent;
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。
publicUser 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")
privateEntityManagerem;
publicbooleaninsertUser(User user){
try{
em.persist(user);
returntrue;
}catch(Exception e){
returnfalse;
}
}
如果傳遞進persist()方法的引數不是實體Bean,都會引發IllegalArgumentException例外。
59.5.3、更新實體
當實體正在被容器管理時,可以呼叫實體的set方法對資料進行修改,在容器決定flush時,更新的資料才會同步到資料庫。如果希望修改後的資料實時同步到資料庫,可以執行EntityManager.flush()方法。
publicbooleanupdateUser(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時,資料將會同步到資料庫中。
publicbooleanupdateUser(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()
publicbooleandeleteUser(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物件。
publicList queryList(){
Query query=em.createQuery("select u from User u where u.id=3");
returnquery.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")
privateEntityManagerem;
HibernateEntityManager hibernate=(HibernateEntityManager)em.getDelegate();
59.7、關係/物件對映
59.7.1、對映的表名或列名與資料庫保留字同名時的處理
當對映的表名或列名於資料庫保留字同名時,持久化引擎轉譯後的SQL在執行時將會出錯。
如:
@Entity
@Table(name=”Order”)
public classOrderimplements Serializable{}
表名Order與排序保留字“Order”相同,導致SQL語法出錯。
針對上面的情況,在JBoss持久化產品中沒有解決方案。一種變通的方法就是加上單引號或者方括號的解決方案。該方法針對具體資料庫,不利於資料庫移植。建議大家不要使用保留字作為表名或列名。
59.7.2、一對一對映
一對一關係,需要在關係維護端的@OneToOne註釋中定義mappedBy屬性。在關係被維護端建立外來鍵列指向關係維護的主鍵列。
User程式碼:
@Entity
@Table(name="tbl_user")
publicclassUser implementsSerializable{
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
privateIntegerid;
@Column(name="name")
privateStringname;
privateStringsex;
privateStringage;
@Temporal(value=TemporalType.DATE)
privateDatebirthday;
@OneToOne(optional=true,cascade=CascadeType.ALL,mappedBy="user")
privateCardcard;
publicUser() { }
publicCard getCard() { returncard; }
publicvoidsetCard(Card card) { this.card= card; }
publicString getAge() { returnage; }
publicvoidsetAge(String age) { this.age= age; }
publicDate getBirthday() { returnbirthday; }
publicvoidsetBirthday(Date birthday) {this.birthday= birthday; }
publicString getName() { returnname; }
publicvoidsetName(String name) { this.name= name; }
publicString getSex() { returnsex; }
publicvoidsetSex(String sex) { this.sex= sex; }
publicInteger getId() { returnthis.id; }
publicvoidsetId(Integer id) { this.id= id; }
publicinthashCode() { return(this.id==null) ? 0 :this.id.hashCode(); }
publicbooleanequals(Object object) {
if(objectinstanceofUser) {
finalUser 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")
publicclassCardimplementsSerializable{
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name="id")
privateIntegerid;
@Column(name="cardno")
privateStringcardno;
@OneToOne(optional=false,cascade=CascadeType.REFRESH)
@JoinColumn(referencedColumnName="id")
privateUseruser;
publicCard() { }
publicUser getUser() { returnuser; }
publicvoidsetUser(User user) { this.user= user; }
publicString getCardno() { returncardno; }
publicvoidsetCardno(String cardno) { this.cardno= cardno; }
publicInteger getId() { returnthis.id; }
publicvoidsetId(Integer id) { this.id= id; }
publicinthashCode() { return(this.id==null) ? 0 : this.id.hashCode(); }
publicbooleanequals(Object object) {
if(objectinstanceofCard) {
finalCard 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")
publicclassOrderimplementsSerializable{
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name="order_id")
privateIntegerid;
privateStringname;
@OneToMany(targetEntity=OrderItem.class,cascade=CascadeType.ALL,mappedBy="order")
privateSetset=newHashSet();
publicOrder() {}
publicString getName() { returnname; }
publicvoidsetName(String name) { this.name= name; }
publicSet getSet() { returnset; }
publicvoidsetSet(Set set) { this.set= set; }
publicInteger getId() { returnthis.id; }
publicvoidsetId(Integer id) { this.id= id; }
publicinthashCode() { return(this.id==null) ? 0 :this.id.hashCode(); }
publicbooleanequals(Object object) {
if(objectinstanceofOrder) {
finalOrder obj = (Order) object;
return(this.id!=null) ? this.id.equals(obj.id) : (obj.id==null);
}
returnfalse;
}
}
---------------------------------------------------------------------------------------------------------
@Entity
@Table(name="tbl_item")
publicclassOrderItemimplementsSerializable{
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name="item_id")
privateIntegerid;
privateStringgoodsname;
@ManyToOne(cascade=CascadeType.REFRESH,optional=false)
@JoinColumn(name="item_order_id",referencedColumnName="order_id")
privateOrderorder;
publicOrderItem() { }
publicString getGoodsname() { returngoodsname; }
publicvoidsetGoodsname(String goodsname) { this.goodsname= goodsname; }
publicOrder getOrder() { returnorder; }
publicvoidsetOrder(Order order) {this.order= order; }
publicInteger getId() { returnthis.id; }
publicvoidsetId(Integer id) { this.id= id; }
publicinthashCode() { return(this.id==null) ? 0 : this.id.hashCode(); }
publicbooleanequals(Object object) {
if(objectinstanceofOrderItem) {
finalOrderItem obj = (OrderItem) object;
return(this.id!=null) ? this.id.equals(obj.id) : (obj.id==null);
}
returnfalse;
}
}
59.7.4、多對多對映
@Entity
@Table(name="tbl_student")
publicclassStudentimplementsSerializable{、
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name="student_id")
privateIntegerid;
privateStringname;
@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")})
privateSetset=newHashSet();
publicStudent() { }
publicString getName() { returnname;}
publicvoidsetName(String name) { this.name= name; }
publicSet getSet() { returnset; }
publicvoidsetSet(Set set) { this.set= set; }
publicInteger getId() { returnthis.id; }
publicvoidsetId(Integer id) { this.id= id; }
publicinthashCode() { return(this.id==null) ? 0 :this.id.hashCode(); }
publicbooleanequals(Object object) {
if(objectinstanceofStudent) {
finalStudent obj = (Student) object;
return(this.id!=null) ? this.id.equals(obj.id) : (obj.id==null);
}
returnfalse;
}
}
-------------------------------------------------------------------------------------------------------------
@Entity
@Table(name="tbl_teacher")
publicclassTeacherimplementsSerializable{
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name="teacher_id")
privateIntegerid;
privateStringname;
@ManyToMany(targetEntity=Student.class,mappedBy="set")
privateSetset=newHashSet();
publicTeacher() { }
publicString getName() { returnname; }
publicvoidsetName(String name) { this.name= name; }
publicSet getSet() { returnset; }
publicvoidsetSet(Set set) { this.set= set; }
publicInteger getId() { returnthis.id; }
publicvoidsetId(Integer id) { this.id= id; }
publicinthashCode() { return(this.id==null) ? 0 :this.id.hashCode(); }
publicbooleanequals(Object object) {
if(objectinstanceofTeacher) {
finalTeacher obj = (Teacher) object;
return(this.id!=null) ? this.id.equals(obj.id) : (obj.id==null);
}
returnfalse;
}
}