【大话Hibernate】Hibernate两种实体关系映射详解
?
實體類與數據庫之間存在某種映射關系,Hibernate依據這種映射關系完成數據的存取,因此映射關系的配置在Hibernate中是最關鍵的。Hibernate支持xml配置文件與@注解配置兩種方式。xml配置文件是最基礎的配置,而@注解是Java的官方JPA(Java?Persistence?API)提供的。本章分別使用@注解與xml講解Hibernate的映射配置。
一、實體類映射:
?
從Java的角度講,實體類就是普通的Java封裝類(有人稱為POJO有人稱為VO)。僅從實體類中的代碼信息,Hibernate并不能得知該實體類對應哪個數據表,因此還需要以某種方式配置一下。常用的方式有*.hbm.xml文件配置與@注解配置兩種。
hbm.xml文件就是普通的xml文件,hbm為HibernateMapping的縮寫,這樣從文件名上就能判斷該文件為Hibernate實體類配置文件。在JPA出現之前,Hibernate都使用hbm.xml文件配置。JPA出現后,推薦使用JPA的@注解配置,因為對于所有的ORM框架,@注解都是通用的。
?
1、@注解配置:
實體類一般有ID、普通屬性、集合屬性等,分別對應數據庫的主鍵、普通列、外鍵。在@注解配置中,實體類用@Entity注解,用@Table指定對應的數據表,用@Id配置主鍵,用@Column配置普通屬性,用@OneToMany、@ManyToOne、@OneToOne、@ManyToMany配置是實體間的關系。
?
2、XML配置:
多個實體類可以配置在一個XML文件中。Hibernate推薦用一個同名的XML文件配置一個實體類,便于閱讀和維護。XML文件一般以“*.hbm.xml”結尾,便于辨認,也可以直接使用“*.xml”結尾。
實體類還需要配置到hibernate.cfg.xml中,以便hibernate初始化實體類與數據庫表的映射關系。如果只配置了映射關系,而沒有配置到hibernate.cfg.xml中,hibernate仍然不會知道哪些類是實體類,因為hibernate無法通過遍歷所有的類來決定哪些是實體類。如果實體類是用@注解配置的,需要用<mappingclass=”” />配置,而如果是用XML文件配置的,則需要使用<mappingresource=”” />配置XML配置文件。
?
?
二、主鍵映射:
實體類最好有主鍵列,并有對應的getter和setter方法,這是hibernate推薦的。逐漸盡量使用可以為null值的類型,例如Integer、Long、String等,而不要使用int、long等。因為如果主鍵為null,則表示該實體類還沒保存到數據庫,是一個臨時狀態(Transient),而int、long等原始類型則不具備該功能。
1、@注解配置主鍵
?????? Hibernate中用@Id聲明該列為主鍵列,同時用@Column聲明該列的列名。當列名與屬性名相同時,@Column配置可省略。@GeneratedValue用于指定主鍵的生成策略。Hibernate支持多種逐漸生成規則,例如自增長、由某個表決定、由Sequence決定等等。如果不配置
?
@GeneratedValue,則必須手動設置ID值。 @Id @Column(name = "id") // 設置主鍵類型, auto表示主鍵是自增長類型 @GeneratedValue(strategy = GenerationType.AUTO) private Integer id;?
2.??XML文件中配置主鍵
如果使用XML配置,主鍵用<id />配置,name指定實體類的主鍵屬性,column指定數據表中的主鍵列名。使用嵌套的<generator />配置主鍵生成策略,native表示使用數據庫自己的策略,在MySQL中就是自增長類型,如果不用自動增長類型,則可以用assigned,例如:
?
<id name="id" column="id"> <generator class="native" /> </id>?
?主鍵生成規則
上面說了主鍵的配置,在配置主鍵過程中,配置了主鍵是否是自動生成的。@Id配置主鍵的同時,也要用@GeneratedValue配置主鍵生成規則。主鍵生成規則也成為主鍵生成策略,負責維護新實體的主鍵值。用的最多的策略是自增長策略。Hibernate還支持其他的多種主鍵生成規則。這些生成規則有些是數據庫提供的,有些是Hibernate提供的。
?
1.??使用@注解配置主鍵生成規則
到目前為止,@注解只支持四種逐漸生成策略:GenerationType.AUTO、GenerationType.TABLE、GenerationType.SEQUENCE、GenerationType.IDENTITY,意義分別為:
qGenerationType.AUTO:自動方式,根據底層數據庫自動選擇。如果為MySQL等支持自增長類型的數據庫,則為自增長類型(auto_increment)。
?
qGenerationType.TABLE:使用指定的表來決定主鍵的取值,一般結合@TableGenerator使用,示例代碼如下:
?
@Id @TableGenerator(name = "tb_cat_gen", allocationSize= 1) @GeneratedValue(strategy = GenerationType.TABLE, generator= "tb_cat_gen") private Integer id;?
qGenerationType.SEQUENCE:使用Sequence來決定主鍵的取值,適合Oracle、DB2、PostgreSQL、SAPDB等支持Sequence的數據庫,一般結合@SequenceGenerator使用。注意某些數據庫如Oracle等沒有自增長類型,只能使用Sequence,示例代碼如下:
?
@Id @SequenceGenerator(name = "seq_cat", allocationSize= 25) @GeneratedValue(strategy = GenerationType.SEQUENCE, generator= "seq_cat") @Column(name = "id") private Integer id;?
qGenerationType.IDENTITY:支持DB2、MySQL、MSSQLServer、Sybase與HypersonicSQL數據庫的identit類型主鍵。
?
2.??使用XML文件配置主鍵生成規則
?
XML配置中支持的主鍵生成規則,比使用@注解的配置的主鍵生成規則要多,XML配置中支持的主鍵生成規則有以下幾種:
qnative:取決于數據庫,相當于GenerationType.AUTO。
?
qidentity:使用identity類型,相當于GenerationType.IDENTITY。
?
qsequence:使用sequence,相當于GenerationType.SEQUENCE。需要指定sequence的名稱,示例代碼如下:
?
<id name="id" type=" java.lang.Integer" column=" id"><generatorclass="sequence"><!--使用sequence主鍵生成規則 --><paramname="sequence">id_sequence</param></generator> </id>?
qincrement:自增長類型,由Hibernate而不是數據庫維護,因此即使Oracle等不支持自增長類型的數據庫也可以使用。
?
qhilo:hi/low算法,使用指定的表給主鍵賦值,相當于GenerationType.TABLE。需要指定表名、列名等,實例代碼如下:
?
<id name="id" type=" java.lang.Integer" column=" id"><generatorclass="hilo"><paramname="table">users</param><paramname="column">id</param><paramname="max_lo">200</param></generator> </id>?
qseqhilo:基于sequence的hilo算法,例如:
?
<id name="id" type=" java.lang.Integer" column="id"><generatorclass="seqhilo"><param name="sequence">hi_value</param><paramname="max_lo">200</param></generator> </id>?
quuid:使用128位的UUID算法計算一個唯一的值,會使用IP地址及相關的計算機硬件信息。計算結果為32位的16進制數,對應的主鍵類型必須為String。
?
qguid:使用MySQL或者MSSQLServer等數據庫提供的GUID值。
?
qassigned:默認值,不使用任何策略,在保存進數據庫之前必須使用setter方法賦值。
?
qselect:使用數據庫觸發器賦值。
?
qforeign:使用外鍵賦值,在一對一實體關系時,可保證關系雙方的Id保持一致。
MySQL數據庫與Hibernate都提供自增長策略,但是原理是不太一樣的。如果采用MySQL的自增長,插入數據時Hibernate生成的SQL語句中將不包含id主鍵列數據。該主鍵的當前值、下一個值由數據庫自己維護。如果使用Hibernate的自增長,插入數據時Hibernate生成的SQL語句將包含id主鍵列,并由Hibernate維護該主鍵的當前值以及下一個值。
對于普通的應用來說,數據庫自增長與Hibernate自增長在使用上沒有區別。但是如果某數據庫同時被兩個Hibernate程序使用,那么此時使用Hibernate自增長將會出現錯誤。例如如果當前主鍵值為101,那么Hibernate會認為下個主鍵值為102,兩個Hibernate程序插入數據時都會將主鍵值設為101,這時會因為主鍵沖突而導致其中一個寫數據失敗。
?
?
????? 三、普通屬性映射:
?????? 普通屬性是指除了主鍵外的、java基本類型的屬性,例如Integer(int)、Long(long)、Short(short)、Boolean(boolean)、Double(double)、Float(float)、String(string)、Date等類型屬性。注意Integer類型與int類型是不同的,Integer默認為null,在數據庫中也表現為null,而int默認為0,在數據庫中也表現為0.
?????
? 1、@配置普通屬性
普通屬性使用@Column與@Basic配置。二者都可以省略。如果省略,則全部按照默認的規則配置,@Column與@Basic的用法如下:
?
@Column中可指定nullable(是否允許為null)、unique(是否唯一)、insertable(是否允許插入)、updatable(是否允許更新)、length(列長度)、columnDefinition(列類型)、scale(整數長度)、precision(小數點精度)等。這些屬性用于生成DDL建表語句。如果屬性對應的列名與屬性名一致,@Column可以省略。
?
q@Basic可為普通屬性配置加載方式,默認為即時加載。如果列數據比較大,例如大文本類型或者LOB類型,可配置為延遲加載。optional配置該列是否可為null。如果為true,表示該屬性是可選的,可以為null,否則不可以為null。
@Column與@Basic使用的代碼示例如下:
?
@Column(name = "usersName", nullable = true, columnDefinition ="varchar", insertable= true, length = 255, unique = true, updatable= true, precision = 2, scale = 4) @Basic(fetch = FetchType.LAZY, optional=true) private String usersName;?
2 、使用XML文件配置普通屬性映射
XML中使用<property/>標簽配置普通屬性。type屬性指定列類型,相當于@Column中的columnDefinition。例如,如果設置type=”text”可以為String類型屬性設置為大文本類型列。不同于@注解中的@Column,如果屬性名與列名一致,column屬性可省略,xml配置中的<property>必須配置,否則視為不參與持久化的列。配置為:
?
<property name="salary" precision="2"scale="10" length="255" column="salary" type="string" update="true"insert="true" lazy="false" unique="false" not-null="false"> </property>?
在使用@注解配置中,如果沒有對普通屬性進行配置,則默認該屬性名與數據表列名相同;而xml文件配置中,如果對普通屬性沒有配置,則認為該屬性沒有對應的數據庫列,不參與持久化。二者是截然不同的。
?
?
四、日期屬性配置:
?????? 日期屬性也屬于普通屬性,普通屬性的配置規則也適用于日期屬性。日期屬性又包括只有日期沒有時間(java.sql.Date)、沒有日期只有時間(java.sql.Time)。既有日期又有時間(java.sql.Timestamp)等3種情況,因此要多一些配置。由于java.sql.Date、java.sql.Time、java.sql.Timestamp都是java.util.Date的子類,所以日期屬性直接使用父類java.util.Date就可以了,hibernate會根據日期屬性決定該類型是java.sql.Date、java.sql.Time還是java.sql.Timestamp。
?????? 1、使用@注解配置普通屬性映射
?????? 日期屬性也是普通的屬性,需要用@Basic聲明加載方式、@Column等指定列名,二者都可省略。另外,如果日期屬性是java.util.Date類型的,必須要用@Temporal配置日期類型,取值可以為Date、Time或者Timestemp。否則Hibernate將無法區分該類型是到底是java.sql.Date(只有年月日等日期信息)類型還是java.sql.Time(只有時分秒等時間信息)類型、還是java.sql.TimeStamp(既有日期信息、又有時間信息)類型。例如:
?
@Temporal(TemporalType.TIMESTAMP)// 日期類型為DATE, TIME或者TIMESTEMP。 @Column(name = " birthday") private java.util.Date birthday;在配置日期屬性時,如果屬性類型是java.util.Date類型,需要用@Temporal聲明日期類型。但是如果是java.sql.Time、java.sql.Date或者java.sql.TimeStamp類型的,類型本身就已經很明確了,不再需要@Temporal聲明了。
?
2 、使用XML文件配置日期屬性映射
在配置日期類型屬性時,type屬性中指定日期類型,取值可以為date、time、timestamp等簡寫方式,也可以為java.sql.Date、java.sql.Time、java.sql.Timestamp等全寫方式。
同樣的道理,如果Java中屬性類型為java.util.Date類型,必須指定是java.sql.Date(只有年月日等日期信息)類型還是java.sql.Time(只有時分秒等時間信息)類型、還是java.sql.TimeStamp(既有日期信息、又有時間信息)類型。示例代碼如下:
?
<property name=" birthday" type="date"></property>五、臨時屬性映射:
?????? 實體類可能有一些臨時屬性,在JPA中被稱為Transient屬性。這些屬性用于方便計算等其他用途,而不是保存數據到數據庫中。這些屬性必須被標記為Transient,以便hibernate把他們區別對待。否則hibernate會試圖往數據庫寫該屬性,可能會因對應的列不存在而拋出異常。
?????? 1、使用@注解配置臨時屬性映射
Java標注中,臨時屬性必須使用@Transient標注,既可以配置在臨時屬性上,也可以配置在對應的getter、setter方法上。例如:
?
@Transient public int getCount() {// 臨時屬性,用于計算總記錄數 return name == null ? 0 :count; }如果只有形如getter、setter的方法,但是沒有對應的屬性,Hibernate仍然會認為該屬性存在。因此也需要用@Transient標注。
??
2 、使用XML文件配置臨時屬性映射
在XML配置中,所有沒有配置到XML文件中的屬性都被視為臨時屬性。如果某屬性漏配置了,該屬性值將不被保存到數據庫中。
?
?
?????? 六.版本屬性配置:
?????? Hibernate中有一種特殊的屬性:版本(Version)屬性。版本屬性不參與業務邏輯,只用來保證不會有兩個線程同時對該數據進行寫操作。版本屬性是樂觀鎖的一種實現方法。樂觀鎖是相對于悲觀鎖而言的。
樂觀鎖與悲觀鎖:悲觀鎖與樂觀鎖都是保證數據準確性的機制。
為保證數據的準確性,程序必須保證在一個線程修改數據的時候,該數據沒有被其他線程修改。在傳統的數據庫編程中,程序修改數據時先鎖定該數據行,使其他程序無法修改該行數據,修改完畢后釋放數據鎖,以此保證數據準確性。由于該機制需要鎖定數據行,被鎖定的數據只能被一個線程使用,因此被稱為悲觀鎖。
樂觀鎖使用完全不同的方式。樂觀鎖通過Version列保存當前數據的版本,如果程序修改了數據,就將版本加1。反過來,如果版本列有了變化,說明該數據被修改過了。程序保存數據時會檢查數據的Version列。如果Version列已經發生了變化,程序會重新讀取、修改并保存數據。由于該機制不需要鎖定數據行,允許多條線程同時訪問同一條數據,因此被稱為樂觀鎖。樂觀鎖的效率要高于悲觀鎖,因襲從現代編程中更傾向于樂觀鎖。
在現代的web編程中,開發者已經不需要關心悲觀鎖、樂觀鎖的實現細節,只需要配置一下即可,框架底層會自動實現。
1、悲觀鎖的使用
Hibernate的悲觀鎖是使用SQL語句或者HQL語句實現的,下面是一個典型的倚賴數據庫的悲觀鎖調用:
select * from users wherename=”Jack” for update
這條 sql語句鎖定了users表中所有符合檢索條件(name=”Jack”)的記錄。本次事務提交之前(事務提交時會釋放事務過程中的鎖),外界無法修改這些記錄。Hibernate的悲觀鎖,也是基于數據庫的鎖機制實現。
下面的代碼實現了對查詢記錄的加鎖:
String hql ="fromUsers where name='Jack';
Query query = session.createQuery(hql);
query.setLockMode("users",LockMode.UPGRADE); //對users表進行加鎖
List list = query.list();// 執行查詢,獲取數據
query.setLockMode 對查詢語句中,特定數據庫表的記錄進行加鎖(這里是users表),這里也就是對返回的所有?users記錄進行加鎖。
觀察運行期 Hibernate生成的SQL語句:
select users0_.id asid, users0_.name as name, users0_.group_id
as group_id, users0_.user_type as user_type, users0_.sex as sex
from users users0_?where users0_.name='Jack' ) for update
這里 Hibernate通過使用數據庫的for update子句實現了悲觀鎖機制。
2、樂觀鎖的使用:
Hibernate支持樂觀鎖,保存數據時Hibernate會自動完成檢查Version列、修改數據、更新Version列等工作。Hibernate隱藏了所有的Version操作細節,只需要指定實體類的Version列即可。實體類中可用@Version配置版本屬性。版本列一般為數字類型屬性。例如:
總結
以上是生活随笔為你收集整理的【大话Hibernate】Hibernate两种实体关系映射详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【Hibernate】Hibernate
- 下一篇: 【Hibernate】Hibernate