Hibernate 注解配置
近幾年來,注解方式的配置因其簡單易用的特點深受廣大程序員的青睞,Hibernate也添加了對注解配置的支持。接下來我們就以論壇系統為例來講解基于注解配置實體類和表的映射關系,以及實體和實體的關聯關系。
核心技能部分
?
1.1?創建SessionFactory
基于xml配置的配置信息位于實體類映射文件中,如Category.hbm.xml;基于注解配置配置信息位于類源代碼中,如Category.class。由于配置信息位置不同,導致SessionFactory的配置也產生了變化。請看示例3.1:
示例3.1
<!-- xml配置方式 --> <hibernate-configuration> <session-factory>…… <mapping resource="com/hibernate/ch3/entity/Category.hbm.xml" /> ……</session-factory> </hibernate-configuration><!-- 注解配置方式 --> <hibernate-configuration> <session-factory>……<mapping class="com.hibernate.ch3.entity.Category" /> ……</session-factory> </hibernate-configuration>如示例3.1所示,xml配置方式的mapping元素通過resource屬性指定xml映射文件的位置,注解配置方式的mapping元素通過class屬性指定實體類。
另外, Hibernate針對注解配置設計了AnnotationConfiguration類,該類擴展自Configuration類,用于加載配置文件,創建SessionFactory對象,請看示例3.2:
示例3.2
Configuration configuration = new AnnotationConfiguration(); configuration.configure("/hibernate.cfg.xml"); SessionFactory sessionFactory = configuration.buildSessionFactory();1.1?映射實體
1.1.1?聲明實體
@Entity用于將一個類聲明為一個實體類,即持久化類。使用方法請看示例3.3:
示例3.3
@Entity
public class Category implements java.io.Serializable {?…… }
@Entity唯一的一個配置選項叫做name,用于為實體類指定別名,以和其它同名不同包的實體類進行區別,請看示例3.4:
示例3.4
@Entity (name="版塊分類")
public class Category implements java.io.Serializable {?…… }
示例3.4將 Category?實體類的別名指定為“版塊分類”,這樣我們就可以根據此別名編寫HQL語句執行查詢,請看示例3.5:
示例3.5
Session se = HibernateSessionFactory.getSession();
List<Category> list = se.createQuery(" from 版塊分類").list();
for (Category category : list) {
System.out.println(category.getName());
}
HibernateSessionFactory.closeSession();
執行示例3.5,控制臺輸出如下:
Hibernate:
????select
????????category0_.id as id4_,
????????category0_.dateCreated as dateCrea2_4_,
????????category0_.deleted as deleted4_,
????????category0_.name as name4_,
????????category0_.version as version4_
????from
????????forum.category category0_
文學
軍事
財經
歷史
1.1.1?實體類映射表
@Table 用于在實體類和表之間建立映射關系,它有以下配置選項:
l?name,用于指定實體映射的數據庫表名稱
l?schema,用于指定數據庫表所在的用戶模式,如oracle的scott用戶模式,sqlserver的dbo用戶模式
l?catalog用于指定數據庫的名字
請看@Table的應用示例3.6:
示例3.6
@Entity
@Table(name = "category", schema="scott",catalog = "forum")
public class Category implements java.io.Serializable {?…… }
1.1.2?映射普通屬性
?@Column?注解用于聲明實體類屬性到數據庫表的列的映射。該注解有如下配置選項:??
l?name?可選,列名(默認值是屬性名) ??
l?unique?可選,是否在該列上設置唯一約束(默認值false) ??
l?nullable?可選,是否設置該列的值可以為空(默認值false) ??
l?insertable?可選,該列是否作為生成的insert語句中的一個列(默認值true) ??
l?updatable?可選,該列是否作為生成的update語句中的一個列(默認值true) ??
l?columnDefinition?可選,為這個特定列覆蓋sql?ddl片段(這可能導致無法在不同數據庫間移植) ??
l?table?可選,定義對應的表(默認為主表) ??
l?length?可選,列長度(默認值255) ??
l?precision?可選,列十進制精度(decimal?precision)(默認值0) ??
l?scale?可選,如果列十進制數值范圍(decimal?scale)可用,在此設置(默認值0)
@Column?注解需要聲明在指定屬性的getter方法上面,請看示例3.7,將Category類的name屬性(版塊分類名稱)映射到了“name”列,并聲明該屬性為非空、不可編輯、唯一的,限制長度為200:
示例3.7
@Column(name="name",?updatable=false,?nullable=false, unique=true,length=200)
public String getName() {
return this.name;
}
1.1.3?映射標識符屬性
在第二章,我們用<id>元素在實體映射文件中映射一個標識符屬性(主鍵),如示例3.8所示:
示例3.8
<id name="id" type="java.lang.Integer">
<column name="id" />
<generator class="native">
<param name="sequence">SEQ_ID</param>
</generator>
</id>
下面我們用注解來重新進行上述的配置。
我們可以用注解@Id來聲明某屬性為一個標識符屬性,該注解無任何配置選項。
接著用@GeneratedValue注解聲明主鍵的生成策略,該注解有如下配置選項:
l?strategy?指定主鍵值生成策略(由JPA定義),通過GenerationTyped的常量提供:
n?GenerationType.AUTO,默認的生成策略,生成器采用native,取決于底層數據庫的能力,使用該生成器保證映射元數據可以移植到不同的數據庫管理系統。
n?GenerationType.TABLE,使用一個特定的數據庫表格來保存主鍵
n?GenerationType.IDENTITY,生成器采用identity,適用于DB2、MySql、MS SqlServer等直接支持主鍵自動增長的數據庫系統,主鍵值由數據庫自動生成。返回的標示符類型為long、short或int
n?GenerationType.SEQUENCE,生成器采用sequence,適用于DB2、?ORACLE等通過序列對象提供有序數列來作為主鍵值的數據庫。(這個生成策略要與generator一起使用)
l?generator?指定生成主鍵使用的生成器,例如采用orcale時指定序列名稱,。
最后用@Column注解映射屬性到列,請看示例3.9,在Category類的id屬性的getter方法之上使用上述3個注解映射標示符屬性:
示例3.9
@Id
@SequenceGenerator(name="SEQ_ID",allocationSize=1, sequenceName="SEQ_ID")
@GeneratedValue(strategy = GenerationType.SEQUENCE,generator="SEQ_ID")
@Column(name = "id", unique = true, nullable = false)
public Integer getId() {
return this.id;
}
如果SEQ_ID不存在,Hibernate會創建該序列;allocationSize=1配置每次增加的數,默認值: 50;默認情況下,JPA 持續性提供程序使用的分配大小為 50。如果此分配大小與應用程序要求或數據庫性能參數不匹配,請將 allocationSize 設置為所需的 int 值。initialValue,默認值: 0.默認情況下,JPA 持續性提供程序假設持續性提供程序將所有主鍵值的起始值設置為 0。如果這與現有數據模型不匹配,請將 initialValue 設置為所需的 int 值。如果是Hibernate創建的序列,即使指定initialValue=100,序列也不會從100開始;而是從1開始,因為Hibernate創建序列的時候指定的最小值是1。
sequenceName?: JPA 持續性提供程序分配它自己創建的序列名。如果要使用事先存在或預定義的序列,請將 sequenceName 設置為所需的 String 名稱。
name 必需 SequenceGenerator 的名稱必須匹配其 startegy 設置為 SEQUENCE 的 GeneratedValue 的名稱。
?
如果是采用的MySql、MS SqlServer等直接支持主鍵自動增長的數據庫系統,并在建表時設置主鍵為自動增長的列,則@GeneratedValue的strategy選項指定為GenerationType.AUTO即可,無需指定generator選項。因為GeneratedValue默認采用GenerationType.AUTO策略,故strategy選項也可以省略,示例3.9簡寫為:
@Id
@GeneratedValue
@Column(name = "id", unique = true, nullable = false)
public Integer getId() {
return this.id;
}
到這里我們就能夠利用注解完整實現一個實體類的映射配置了,Category類完整代碼如示例3.10所示:
示例3.10
@Entity
@Table(name = "category", schema="scott",catalog = "forum")
public class Category implements java.io.Serializable {
……省略構造函數
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE,generator?=?"SEQ_ID")
@Column(name = "id", unique = true, nullable = false)
public Integer getId() {
return this.id;
}
?
@Column(name = "dateCreated", length = 19)
public Timestamp getDateCreated() {
return this.dateCreated;
}
?
@Column(name = "deleted", nullable = false)
public Boolean getDeleted() {
return this.deleted;
}
?
@Column(name = "version", nullable = false)
public Integer getVersion() {
return this.version;
}
?
@Column(name = "name",updatable = false, nullable = false, length=200)
public String getName() {
return this.name;
}
?????……省略屬性的setter方法
}
1.1?實體關聯關系映射
Hibernate提供了以下注解用于配置實體關聯關系:
l?@OneToOne,用于配置一對一關系
l?@OneToMany,用于配置一對多關系
l?@ManyToOne,用于配置多對一關系
l?@ManyToMany,用于配置多對多關系
這四個注解擁有如下共有配置選項:
l?targetEntity,指定關聯實體的類型
l?cascade,指級聯級別,有以下幾種選項:
n?CascadeType.PERSIST(級聯保存)
n?CascadeType.REMOVE(級聯刪除)
n?CascadeType.REFRESH(級聯刷新)
n?CascadeType.MERGE(級聯更新)
n?CascadeType.ALL(全部四項) ??
l?fetch,指定數據抓取策略,有以下幾種選項:
n?FetchType.LAZY,表示延遲加載,
n?FetchType.EAGER表示立即加載
單向一對多、單向多對一和雙向一對多,我們將采用論壇系統版塊分類和版塊之間的關系為例來講解。從圖3.1.1 category(版塊分類表)和board(版塊表)E-R關系圖我們可以看出board表通過category_id列和category表建立了外鍵關系,由此形成了版塊分類到版塊一對多的關系(即一個版塊分類可以擁有多個版塊)和版塊到版塊分類的多對一關系(多個版塊可以同屬一個版塊分類,且一個版塊只能屬于某一個版塊分類)。
?
圖3.1.1 category(版塊分類表)和board(版塊表)E-R關系圖
1.1.1?單向一對多關聯
一對多關系需要使用@OneToMany來聲明,該注解除了共有屬性外還擁有一個叫做mappedBy的配置選項,在雙向一對多關系中使用,作用和xml映射文件中<set/>標簽的inverse屬性作用相同,在一的一端中設置mappedBy,說明多端反向控制一端。
另外,我們還需要用到@JoinColumn注解,它有一個name屬性,用于指定數據庫表中的外鍵列名稱。
接下來我們來配置從Category類到Board類的單向一對多關系,請看示例3.11:
示例3.11
@Entity
@Table(name = "category", schema="scott",catalog = "forum")
public class Category implements java.io.Serializable {
……
private Set<Board> boards = new HashSet<Board>(0);
?
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JoinColumn(name = "category_id")?// board表列,category表外鍵
public Set<Board> getBoards() {
return this.boards;
}
?????……
}
?
@Entity
@Table(name = "board",schema="scott",catalog = "forum")
public class Board implements java.io.Serializable {
……
//對應于board表category_id列,作為外鍵參考category表的主鍵id列值
private int categoryId;
?
@Column(name = "category_id")
public int getCategoryId() {
return categoryId;
}
……
}
示例3.12通過調用Category類的getBoards()方法執行關聯屬性操作來測試單向一對多關聯關系是否成立:
示例3.12
Session?se = HibernateSessionFactory.getSession()
Category category = (Category)se.get(Category.class, 1);
System.out.println(category.getName());
Set<Board> list= category.getBoards();
for (Board board : list) {
System.out.println(board.getName());
}
HibernateSessionFactory.closeSession();
執行示例3.12在控制臺輸出內容如下,說明單向一對多關系配置正確:
Hibernate:
????Select ……
????from
????????forum.category category0_
????where
????????category0_.id=?
文學
Hibernate:
????Select?……
????from
????????forum.board boards0_
????where
????????boards0_.category_id=?
蓮蓬鬼話
奇幻文學
示例3.11 在配置一對多關系時并沒有使用targetEntity屬性來指定關聯實體的類型,那么Hibernate怎么知道關聯關系實體類型是Board呢?因為我們在聲明getBoards()方法的返回的類型時為Set指定了泛型信息,即Set<Board>。Hibernate通過反射獲取返回類型的泛型信息便知關聯關系類型了。
如果我們將getBoards()方法的返回的類型從Set<Board>修改為Set,執行示例3.12將會拋出如下異常信息:
org.hibernate.AnnotationException: Collection has neither generic type or OneToMany.targetEntity() defined: com.hibernate.ch3.entity.Category.boards
從異常信息可以看出我們有兩個選擇,一是為集合指定泛型信息,另一個是通過@OneToMany的targetEntity屬性指定關聯實體類型,我們需要二選其一。
1.1.2?單向多對一關聯
@ManyToOne注解用來配置多對一關系,該注解除了共有屬性外還擁有一個叫做optional的配置選項,設置為true時,即使外鍵為空仍可以向表中添加數據。
從板塊(Board)到版塊分類(Category)是多對一關系,下面我們就以此為例來配置單向多對一關系。請看示例3.13,我們將Category類的boards屬性和其getter/settter方法去掉,將Board類的categoryId屬性和getter/settter方法換成Category類型的屬性category,并在其getter方法上進行多對一配置:
示例3.13
@Entity
@Table(name = "category", schema="scott",catalog = "forum")
public class Category implements java.io.Serializable {
……
}
?
@Entity
@Table(name = "board",schema="scott",catalog = "forum")
public class Board implements java.io.Serializable {
……
private Category category;
?
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "category_id")
public Category getCategory() {
return this.category;
}……
}
示例3.14通過調用Board類的getCategory?()方法執行關聯屬性操作來測試單向多對一關聯關系是否成立:
示例3.14
Board board = (Board)HibernateSessionFactory.getSession().get(Board.class, 1);
System.out.println(board.getName());
System.out.println(board.getCategory().getName());
HibernateSessionFactory.closeSession();
執行示例3.14,控制臺輸出如下,證明我么成功配置了單向多對一關系:
Hibernate:
????Select?……
????from
????????forum.board board0_
????where
????????board0_.id=?
蓮蓬鬼話
Hibernate:
????Select?……
????from
????????forum.category category0_
????where
????????category0_.id=?
文學
1.1.3?雙向一對多關聯
雙向關聯關系即同時在一方配置一對過關系,在多方配置多對一關系即可。請看示例3.15:
示例3.15
@Entity
@Table(name = "category", schema="scott",catalog = "forum")
public class Category implements java.io.Serializable {
……
private Set<Board> boards = new HashSet<Board>(0);
?
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JoinColumn(name = "category_id")?// board表列,category表外鍵
public Set<Board> getBoards() {
return this.boards;
}
?????……
}
?
@Entity
@Table(name = "board",schema="scott",catalog = "forum")
public class Board implements java.io.Serializable {
……
private Category category;
?
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "category_id")
public Category getCategory() {
return this.category;
}……
}
?
由于是雙向一對多關系,Category的一對多聲明也可以不必通過@JoinColumn指定外鍵列,而是改為配置@OneToMany的mappedBy 為Board的屬性 "category",如示例3.16所示:
示例3.16
@Entity
@Table(name = "category", schema="scott",catalog = "forum")
public class Category implements java.io.Serializable {
……
private Set<Board> boards = new HashSet<Board>(0);
?
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
public Set<Board> getBoards() {
return this.boards;
}
?????……
}
1.1.4?多對多關聯
在論壇系統中,版塊和版主屬于多對多關系,請看圖3.1.2,版塊表(board)、用戶表(person,版主即某用戶)和版塊用戶關系表(board_administrator)的E-R關系圖:
?
圖3.1.2,表board、person和board_administrator?E-R關系圖
在數據庫中,多對多關系必須借助中間表來完成,版塊用戶關系表(board_administrator)就是這樣一個角色。它僅擁有board_id和person_id兩個列,且分別是版塊表和用戶表的外鍵。我們在設計實體類時只需設計Board類來映射board表,Person類來映射person表,無需設計類來映射board_administrator 表。
在實體類中配置多對多關聯關系需要使用@ManyToMany注解,該注解的配置選項和?@OneToMany一模一樣。同時通過 @JoinTable 注解描述中間關聯表和通過中間表關聯到兩方的外鍵。
下面我們來配置Board類和Person類之間的雙向多對多關聯關系,請看示例3.17:
示例3.17
@Entity
@Table(name = "board",schema="scott",catalog = "forum")
public class Board implements java.io.Serializable {
……
private Set<Person> persons = new HashSet<Person>(0);
?
@ManyToMany(cascade=CascadeType.ALL,fetch=FetchType.LAZY,mappedBy="boards")
public Set<Person> getPersons() {
return this.persons;
}
……
}
?
@Entity
@Table(name = "person", schema="scott",catalog = "forum")
public class Person implements java.io.Serializable {
……
private Set<Board> boards = new HashSet<Board>(0);
?
@ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JoinTable(name = "board_administrator", schema="scott",catalog = "forum",
joinColumns = { @JoinColumn(name = "person_id") },
inverseJoinColumns = { @JoinColumn(name = "board_id") })
public Set<Board> getBoards() {
return this.boards;
}
……
}
我們通過示例3.18來測試一下Board類和Person類之間的雙向多對多關聯關系:
示例3.18
Board board = (Board)HibernateSessionFactory.getSession().get(Board.class, 1);
Set<Person> admins = board.getPersons();
System.out.println("版塊:"+board.getName()+",該版塊擁有版主位數"+admins.size());
for (Person admin : admins) {
System.out.println("版主名稱:"+admin.getName());
}
?
Person person = (Person)HibernateSessionFactory.getSession().get(Person.class, 1);
Set<Board> boards = person.getBoards();
System.out.println("用戶:"+person.getName()+",他是以下版塊的版主");
for (Board b : boards) {
System.out.println(b.getName());
}
HibernateSessionFactory.closeSession();
執行示例3.18,在控制臺輸出如下:
Hibernate:
????Select?
…… ?
????from
????????forum.board board0_
????where
????????board0_.id=?
Hibernate:
????select
????????……
????from
????????forum.board_administrator persons0_
????left outer join
????????forum.person person1_
????????????on persons0_.person_id=person1_.id
????where
????????persons0_.board_id=?
版塊:蓮蓬鬼話,該版塊擁有版主位數:3
版主名稱:lee
版主名稱:茶農
版主名稱:三叔
?
用戶:lee,他是以下版塊的版主
Hibernate:
????select
???????……
????from
????????forum.board_administrator boards0_
????left outer join
????????forum.board board1_
????????????on boards0_.board_id=board1_.id
????where
????????boards0_.person_id=?
蓮蓬鬼話
鞏固練習
?
選擇題
1.?Hibernate提供了哪些注解用于配置實體關聯關系()。
A.?@OneToOne,用于配置一對一關系
B.?@OneToMany,用于配置一對多關系
C.?@ManyToOne,用于配置多對一關系
D.?@ManyToMany,用于配置多對多關系
2.?下列哪些配置選項是@OneToOne、@OneToMany、@ManyToOne、@ManyToMany共有屬性()
A.?targetEntity
B.?cascade
C.?fetch
D.?mappedBy
3.?cascade,指級聯級別,以下那些選項是cascade正確可用的選項值?():
A.?CascadeType.SAVE(級聯保存)
B.?CascadeType.REMOVE(級聯刪除)
C.?CascadeType.UPDATE(級聯更新)
D.?CascadeType.ALL(全部四項) ??
4.?fetch,指定數據抓取策略,以下那兩個選項是fetch正確可用的選項值?():
A.?FetchType.LAZY,表示延遲加載,
B.?FetchType.EAGER,表示立即加載
C.?FetchType.JOIN,表示關聯加載
D.?FetchType.BATCH,表示批量加載
5.?@GeneratedValue注解用于聲明主鍵的生成策略,下面哪些配置選項屬于該注解?()
A.?GenerationType.AUTO,默認的生成策略,生成器采用native,取決于底層數據庫的能力,使用該生成器保證映射元數據可以移植到不同的數據庫管理系統。
B.?GenerationType.IDENTITY,生成器采用identity,適用于DB2、MySql、MS SqlServer等直接支持主鍵自動增長的數據庫系統,主鍵值由數據庫自動生成。返回的標示符類型為long、short或int。
C.?GenerationType.SEQUENCE,生成器采用sequence,適用于DB2、?ORACLE等通過序列對象提供有序數列來作為主鍵值的數據庫。
D.?GenerationType.INCREMENT,生成器采用INCREMENT,適用于MySql,主鍵值由數據庫自動生成。返回的標示符類型為long、short或int。
上機練習
角色和權限屬于多對多關聯關系,一個角色可以包含多個權限,一個權限可以屬于多個角色。請參考設置版塊版主實現新角色的創建。
總結
以上是生活随笔為你收集整理的Hibernate 注解配置的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java Bag模型模拟
- 下一篇: Hibernate检索1