1、配置映射關系的xml方式
我們知道,Hibernate是一個典型的ORM框架,用以解決對象和關系的不匹配。其思想就是將關系數據庫中表的記錄映射成為對象,以對象形式展現,這樣一來,就可以把對數據庫的操作轉化為對對象的操作。
而ORM一般是采用xml的格式保存對象與關系數據表的映射,我們也可以從下面示例中看到hibernate中的一個映射配置表的表現形式:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.zker.model.job.SysJob" table="SYS_JOB" lazy="true">
<id name="jobId" column="JOB_ID">
<generator class="sequence">
<param name="sequence">SEQ_SYS_JOB</param>
</generator>
</id>
<property name="jobName" type="string" column="JOB_NAME" />
<property name="jobDesc" type="string" column="JOB_DESC" />
<property name="lastModity" type="timestamp" column="LAST_MODIFY" />
<!--與用戶關聯-->
<set name="sysUsers" table="SYS_USER">
<key column="JOB_ID" />
<one-to-many class="com.zker.model.user.SysUser" />
</set>
</class>
</hibernate-mapping>
22
1
2
3
4
5
<hibernate-mapping>
6
<class name="com.zker.model.job.SysJob" table="SYS_JOB" lazy="true">
7
<id name="jobId" column="JOB_ID">
8
<generator class="sequence">
9
<param name="sequence">SEQ_SYS_JOB</param>
10
</generator>
11
</id>
12
<property name="jobName" type="string" column="JOB_NAME" />
13
<property name="jobDesc" type="string" column="JOB_DESC" />
14
<property name="lastModity" type="timestamp" column="LAST_MODIFY" />
15
<!--與用戶關聯-->
16
<set name="sysUsers" table="SYS_USER">
17
<key column="JOB_ID" />
18
<one-to-many class="com.zker.model.user.SysUser" />
19
</set>
20
21
</class>
22
</hibernate-mapping>
其中對應的實體類和數據庫表結構如下:
public class SysJob {
/**職位的主鍵ID*/
private int jobId;
/**職位的名稱*/
private String jobName;
/**職位的描述*/
private String jobDesc;
/**職位的修改時間*/
private Timestamp lastModity;
/**職位所對應的用戶*/
private Set<SysUser> sysUsers = new HashSet<SysUser>();
public int getJobId() {
return jobId;
}
public void setJobId(int jobId) {
this.jobId = jobId;
}
public String getJobName() {
return jobName;
}
public void setJobName(String jobName) {
this.jobName = jobName;
}
public String getJobDesc() {
return jobDesc;
}
public void setJobDesc(String jobDesc) {
this.jobDesc = jobDesc;
}
public Timestamp getLastModity() {
return lastModity;
}
public void setLastModity(Timestamp lastModity) {
this.lastModity = lastModity;
}
public Set<SysUser> getSysUsers() {
return sysUsers;
}
public void setSysUsers(Set<SysUser> sysUsers) {
this.sysUsers = sysUsers;
}
}
56
1
public class SysJob {
2
/**職位的主鍵ID*/
3
private int jobId;
4
5
/**職位的名稱*/
6
private String jobName;
7
8
/**職位的描述*/
9
private String jobDesc;
10
11
/**職位的修改時間*/
12
private Timestamp lastModity;
13
14
/**職位所對應的用戶*/
15
private Set<SysUser> sysUsers = new HashSet<SysUser>();
16
17
public int getJobId() {
18
return jobId;
19
}
20
21
public void setJobId(int jobId) {
22
this.jobId = jobId;
23
}
24
25
public String getJobName() {
26
return jobName;
27
}
28
29
public void setJobName(String jobName) {
30
this.jobName = jobName;
31
}
32
33
public String getJobDesc() {
34
return jobDesc;
35
}
36
37
public void setJobDesc(String jobDesc) {
38
this.jobDesc = jobDesc;
39
}
40
41
public Timestamp getLastModity() {
42
return lastModity;
43
}
44
45
public void setLastModity(Timestamp lastModity) {
46
this.lastModity = lastModity;
47
}
48
49
public Set<SysUser> getSysUsers() {
50
return sysUsers;
51
}
52
53
public void setSysUsers(Set<SysUser> sysUsers) {
54
this.sysUsers = sysUsers;
55
}
56
}
2、注解的優點
而完成這個ORM映射關系配置的方式,還有一種,就是使用注解。
- 充分利用 Java 的反射機制獲取類結構信息,這些信息可以有效減少配置的工作
- 注釋和 Java 代碼位於一個文件中,有助於增強程序的內聚性,便於程序員開發
我們可以看如下的一個示例來感受這種形式:
@Entity
@Table(name = "t_student")
@Domain(name = "學生")
@Generated
@DataIdentify(identifies = "number")
public class Student extends BaseDomain<Student> implements Addable, Modifiable<Student>, Deletable {
private String name;
private String number;
private Sex sex;
@Basic
@NotSemanticNull(groups = {Groups.Add.class, Groups.Update.class})
@Property(name = "姓名")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
...
}
24
1
2
(name = "t_student")
3
(name = "學生")
4
5
(identifies = "number")
6
public class Student extends BaseDomain<Student> implements Addable, Modifiable<Student>, Deletable {
7
private String name;
8
private String number;
9
private Sex sex;
10
11
12
(groups = {Groups.Add.class, Groups.Update.class})
13
(name = "姓名")
14
public String getName() {
15
return name;
16
}
17
18
public void setName(String name) {
19
this.name = name;
20
}
21
22
...
23
24
}
3、常用注解的使用方法
3.1 類級別的注解
- @Entity 映射實體類
- @Table 映射數句庫表
@Entity(name = "tableName") -
必須,注解將一個類聲明為一個實體bean,即指出該Java 類為實體類,將映射到指定的數據庫表
- 屬性:
- name - 可選,對應數據庫中的一個表。若表名與實體類名相同,則可以省略
@Table(name = "", catalog = "", schema = "") - 可選,通常和@Entity 配合使用,只能標注在實體的 class 定義處,表示實體對應的數據庫表的信息
- 屬性:
- name - 可選,表示表的名稱,默認的表名和實體名稱一致,只有在不一致的情況下才需要指定表名
- catalog - 可選,表示Catalog 名稱,默認為 Catalog("")
- schema - 可選 , 表示 Schema 名稱 , 默認為Schema("")
3.2 屬性級別的注解
- @Id 映射生成主鍵
- @GeneratedValue 定義主鍵生成策略
- @SequenceGenerator 聲明了一個數據庫序列
- @Version 定義樂觀鎖
- @Basic 聲明屬性的存取策略
- @Column 映射表的列
- @Transient 定義暫態屬性
屬性級別的注解,都是放在其對應的getter前。
3.2.1 與主鍵相關注解
@Id -
必須,定義了映射到數據庫表的主鍵的屬性,一個實體只能有一個屬性被映射為主鍵,置於 getXxx() 前
@GeneratedValue(strategy = GenerationType , generator="") - 可選,用於定義主鍵生成策略
- 屬性:
- strategy - 表示主鍵生成策略,取值有:
- GenerationType.AUTO 根據底層數據庫自動選擇(默認),若數據庫支持自動增長類型,則為自動增長
- GenerationType.INDENTITY 根據數據庫的Identity字段生成,支持DB2、MySQL、MS、SQL Server、SyBase與HyperanoicSQL數據庫的Identity類型主鍵
- GenerationType.SEQUENCE 使用Sequence來決定主鍵的取值,適合Oracle、DB2等支持Sequence的數據庫,一般結合@SequenceGenerator使用(Oracle沒有自動增長類型,只能用Sequence)
- GenerationType.TABLE 使用指定表來決定主鍵取值,結合@TableGenerator使用
- generator - 表示主鍵生成器的名稱,這個屬性通常和ORM框架相關 , 例如:Hibernate 可以指定 uuid 等主鍵生成方式
@SequenceGenerator — 注解聲明了一個數據庫序列
- 屬性:
- name - 表示該表主鍵生成策略名稱,它被引用在@GeneratedValue中設置的“gernerator”值中
- sequenceName - 表示生成策略用到的數據庫序列名稱
- initialValue - 表示主鍵初始值,默認為0
- allocationSize - 每次主鍵值增加的大小,例如設置成1,則表示每次創建新記錄后自動加1,默認為50
3.2.2 與非主鍵相關注解
@Version - 可以在實體bean中使用@Version注解,通過這種方式可添加對樂觀鎖定的支持(見參考鏈接)
@Basic - 用於聲明屬性的存取策略:
- @Basic(fetch=FetchType.EAGER) 即時獲取(默認的存取策略)
- @Basic(fetch=FetchType.LAZY) 延遲獲取
@Column - 可將屬性映射到列,使用該注解來覆蓋默認值,@Column描述了數據庫表中該字段的詳細定義
- 屬性:
- name - 可選,表示數據庫表中該字段的名稱,默認情形屬性名稱一致
- nullable - 可選,表示該字段是否允許為 null,默認為 true
- unique - 可選,表示該字段是否是唯一標識,默認為 false
- length - 可選,表示該字段的大小,僅對 String 類型的字段有效,默認值255
- insertable - 可選,表示在ORM框架執行插入操作時,該字段是否應出現INSETRT語句中,默認為 true
- updateable - 可選,表示在ORM 框架執行更新操作時,該字段是否應該出現在UPDATE 語句中,默認為 true。對於一經創建就不可以更改的字段,該屬性非常有用,如對於 birthday 字段
- columnDefinition - 可選,表示該字段在數據庫中的實際類型。通常ORM 框架可以根據屬性類型自動判斷數據庫中字段的類型,但是對於Date 類型仍無法確定數據庫中字段類型究竟是 DATE,TIME 還是 TIMESTAMP. 此外 ,String 的默認映射類型為 VARCHAR, 如果要將 String 類型映射到特定數據庫的 BLOB或 TEXT 字段類型,該屬性非常有用
@Transient - 可選,表示該屬性並非一個到數據庫表的字段的映射,ORM框架將忽略該屬性,如果一個屬性並非數據庫表的字段映射,就務必將其標示為@Transient,否則ORM框架默認其注解為 @Basic
3.3 映射實體類的關聯關系
單向一對多:一方有集合屬性,包含多個多方,而多方沒有一方的引用。
用戶--->電子郵件
單向多對一:多方有一方的引用,一方沒有多方的引用。
論文類別---> 類別
雙向一對多:兩邊都有多方的引用,方便查詢。
班級---> 學生
雙向多對一:兩邊都有多方的引用,方便查詢。
單向多對多:需要一個中間表來維護兩個實體表。
論壇--->文章
單向一對一:數據唯一,數據庫數據也是一對一。
艦船---> 水手
主鍵相同的一對一:使用同一個主鍵,省掉外鍵關聯。
客戶---> 地址
3.3.1 關聯映射的一些共有屬性
@JoinColumn - 可選,用於描述一個關聯的字段。
@JoinColumn和@Column類似,介量描述的不是一個簡單字段,而是一個關聯字段,例如描述一個 @ManyToOne 的字段。(即用來
定義外鍵在我們這個表中的屬性名,例如實體Order有一個User user屬性來關聯實體User,則Order的user屬性為一個外鍵
)
- 屬性:
- name - 該字段的名稱,由於@JoinColumn描述的是一個關聯字段,如ManyToOne, 則默認的名稱由其關聯的實體決定
@OneToOne、@OneToMany、@ManyToOne、ManyToMany 的共有屬性:
- fetch - 配置加載方式。取值有:
- Fetch.EAGER - 及時加載,多對一默認是Fetch.EAGER
- Fetch.LAZY - 延遲加載,一對多默認是Fetch.LAZY
- cascade - 設置級聯方式,取值有:
- CascadeType.PERSIST - 保存 - 調用JPA規范中的persist(),不適用於Hibernate的save()方法
- CascadeType.REMOVE - 刪除 - 調用JPA規范中的remove()時,適用於Hibernate的delete()方法
- CascadeType.MERGE - 修改 - 調用JPA規范中merge()時,不適用於Hibernate的update()方法
- CascadeType.REFRESH - 刷新 - 調用JPA規范中的refresh()時,適用於Hibernate的flush()方法
- CascadeType.ALL - 全部 - JPA規范中的所有持久化方法
- targetEntity - 配置集合屬性類型,如:@OneToMany(targetEntity=Book.class)
@OneToOne – 表示一個一對一的映射
主表類A與從表類B的主鍵值相對應
主表:
@OneToOne(cascade = CascadeType.ALL)
@PrimaryKeyJoinColumn
public B getB(){
return b;
}
從表:無
9
1
主表類A與從表類B的主鍵值相對應
2
主表:
3
(cascade = CascadeType.ALL)
4
5
public B getB(){
6
return b;
7
}
8
9
從表:無
主表A中有一個從表屬性是B類型的b
主表:
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name="主表外鍵") //這里指定的是數據庫中的外鍵字段。
public B getB(){
return b;
}
從表:無
9
1
主表A中有一個從表屬性是B類型的b
2
主表:
3
(cascade = CascadeType.ALL)
4
(name="主表外鍵") //這里指定的是數據庫中的外鍵字段。
5
public B getB(){
6
return b;
7
}
8
9
從表:無
主表A中有一個從表屬性是B類型的b,同時,從表B中有一個主表屬性是A類型的a
主表:
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name="主表外鍵") //這里指定的是數據庫中的外鍵字段。
public B getB(){
return b;
}
從表:
@OneToOne(mappedBy = "主表類中的從表屬性")
public 主表類 get主表類(){
return 主表對象
}
13
1
主表A中有一個從表屬性是B類型的b,同時,從表B中有一個主表屬性是A類型的a
2
主表:
3
(cascade = CascadeType.ALL)
4
(name="主表外鍵") //這里指定的是數據庫中的外鍵字段。
5
public B getB(){
6
return b;
7
}
8
9
從表:
10
(mappedBy = "主表類中的從表屬性")
11
public 主表類 get主表類(){
12
return 主表對象
13
}
@ManyToOne - 表示一個多對一的映射,該注解標注的屬性通常是數據庫表的外鍵
單向多對一:多方有一方的引用,一方沒有多方的引用
在多方
@ManyToOne(targetEntity=XXXX.class) //指定關聯對象
@JoinColumn(name="") //指定產生的外鍵字段名
4
1
單向多對一:多方有一方的引用,一方沒有多方的引用
2
在多方
3
(targetEntity=XXXX.class) //指定關聯對象
4
(name="") //指定產生的外鍵字段名
雙向多對一:配置方式同雙向一對多
1
1
雙向多對一:配置方式同雙向一對多
// 示例
// 訂單 Order 和用戶 User 是一個 ManyToOne 的關系
// 在 Order 類中定義
@ManyToOne()
@JoinColumn(name="USER")
public User getUser() {
return user;
}
8
1
// 示例
2
// 訂單 Order 和用戶 User 是一個 ManyToOne 的關系
3
// 在 Order 類中定義
4
()
5
(name="USER")
6
public User getUser() {
7
return user;
8
}
@OneToMany - 描述一個一對多的關聯,該屬性應該為集合類型,在數據庫中並沒有實際字段
單向一對多:一方有集合屬性,包含多個多方,而多方沒有一方的引用
@OneToMany 默認會使用連接表做一對多關聯
添加@JoinColumn(name="xxx_id") 后,就會使用外鍵關聯,而不使用連接表了
3
1
單向一對多:一方有集合屬性,包含多個多方,而多方沒有一方的引用
2
默認會使用連接表做一對多關聯
3
添加(name="xxx_id") 后,就會使用外鍵關聯,而不使用連接表了
雙向一對多:
1)在多方
@ManyToOne
@JoinColumn(name="自己的數據庫外鍵列名")
2)在一方
@OneToMany(mappedBy="多端的關聯屬性名") //mappedBy相當於inverse,維護外鍵的控制權,不能和JoinColumn同時使用
@JoinColumn(name="對方的數據庫外鍵列名")
8
1
雙向一對多:
2
1)在多方
3
4
(name="自己的數據庫外鍵列名")
5
6
2)在一方
7
(mappedBy="多端的關聯屬性名") //mappedBy相當於inverse,維護外鍵的控制權,不能和JoinColumn同時使用
8
(name="對方的數據庫外鍵列名")
注意:對於外鍵的維護,如果是雙向一對多,希望雙方均可以維護外鍵,則不能使用mappedBy,而應該雙方都使用@JoinColumn
@ManyToMany - 可選,描述一個多對多的關聯
- 屬性:
- targetEntity - 表示多對多關聯的另一個實體類的全名,例如:package.Book.class
- mappedBy - 用在雙向關聯中,把關系的維護權翻轉。
單向多對多關聯:
在主控方加入@ManyToMany注解即可。
2
1
單向多對多關聯:
2
在主控方加入注解即可。
雙向多對多關聯:
兩個實體間互相關聯的屬性必須標記為@ManyToMany,並相互指定targetEntity屬性。
有且只有一個實體的@ManyToMany注解需要指定mappedBy屬性,指向targetEntity的集合屬性名稱。
3
1
雙向多對多關聯:
2
兩個實體間互相關聯的屬性必須標記為,並相互指定targetEntity屬性。
3
有且只有一個實體的注解需要指定mappedBy屬性,指向targetEntity的集合屬性名稱。
3.3.2 關聯映射的其他補充
@JoinTable其實同時也是配合
@ManyToMany使用的,
@ManyToMany注釋表示該對象是多對多關系的一端,然后利用@JoinTable來定義關聯關系(利用中間表來建立聯系,
原因戳這里),其中name屬性指定中間表名稱,j
oinColumns定義中間表與該表的外鍵關系,inverseJoinColumns屬性定義了中間表與另外一端的外鍵關系。
@JoinTable - 定義關聯表, 該關聯表包含了指回實體表的外鍵(通過@JoinTable.joinColumns) 以及指向目標實體表的外鍵(通過@JoinTable.inverseJoinColumns)
e.g.
如下表示:該屬性對應字段為"resource_type",該字段實際在另外一張名"r_role_x_resource_type"表中,與該表("p_role")通過"role_id"進行外鍵鏈接
(class Role --> table "p_role")
@ElementCollection
@JoinTable(name = "r_role_x_resource_type", joinColumns = @JoinColumn(name = "role_id"))
@Column(name = "resource_type")
@JSONField(serialize = false)
@Cache(usage = CacheConcurrencyStrategy.NONE)
public List<Class<? extends Resourceable>> getResourceTypeList() {
return resourceTypeList;
}
12
1
e.g.
2
如下表示:該屬性對應字段為"resource_type",該字段實際在另外一張名"r_role_x_resource_type"表中,與該表("p_role")通過"role_id"進行外鍵鏈接
3
4
(class Role --> table "p_role")
5
6
(name = "r_role_x_resource_type", joinColumns = (name = "role_id"))
7
(name = "resource_type")
8
(serialize = false)
9
(usage = CacheConcurrencyStrategy.NONE)
10
public List<Class<? extends Resourceable>> getResourceTypeList() {
11
return resourceTypeList;
12
}
e.g.
如下表示:關聯的表為“r_role_x_permission",其中關聯外鍵為"role_id",該表又通過"permission_id"鏈接第三張表,即目標實體表
(class Role --> table "p_role")
@ManyToMany
@JoinTable(name = "r_role_x_permission", joinColumns = @JoinColumn(name = "role_id"),
inverseJoinColumns = @JoinColumn(name = "permission_id"))
public List<Permission> getPermissionList() {
return permissionList;
}
x
1
e.g.
2
如下表示:關聯的表為“r_role_x_permission",其中關聯外鍵為"role_id",該表又通過"permission_id"鏈接第三張表,即目標實體表
3
4
(class Role --> table "p_role")
5
6
(name = "r_role_x_permission", joinColumns = (name = "role_id"),
7
inverseJoinColumns = (name = "permission_id"))
8
public List<Permission> getPermissionList() {
9
return permissionList;
10
}
4、其他注解
@DiscriminatorValue - 一張表對應一整棵類繼承樹時,該類別對應的“表part”
首先參考這篇文章,很重要:
hibernate映射繼承關系(一):一張表對應一整棵類繼承樹,從文中可以知道,用一個表來存儲對應的整個類別的數據,比如有Cat和Animal,Cat是Animal的子類,我僅用Animal一個表來存儲Animal和Cat的字段和數據,而不是分成兩個表。那么當我進行映射關系的時候,假如我要Cat類映射到Animal中Cat的部分,如何處理?在Animal中定義一個字段用來區分不同的表,比如Animal表中我額外增加字段名為Type,那么在Animal這一張表中,我們本屬於Animal表內容的,該字段我們設置為animal,本屬於Cat表的,該字段我們設置為cat。你可以理解為,新增加字段來用以在同一個表中區分不同類別的內容。
所以對應在注解上的使用的一個映射關系表示,就是這樣的:對於”父類“,即准備用來囊括所有內容的那個表,我們需要定義這個對應的類為 @DiscriminatorColumn(name = "xxx", discriminatorType = DiscriminatorType.xxx) ,這里的
name就是指定表中用來區別各類內容的字段
,而對於”子類“,我們需要注解標明@DiscriminatorValue(xxx),這里的xxx即對應了父類中的 “區別用字段” 里的標識。
舉例來說,就是假如我們希望將Animal和Cat的內容都只存儲在Animal這張表里,那么為了區分內容,我們對於Animal這個表新增某字段如 type;Animal的類,注解為@DiscriminatorColumn(name = "type", discriminatorType = DiscriminatorType.STRING) ,同時設置@DiscriminatorValue("animal");Cat extends Animal,Cat的類,注解為@DiscriminatorValue(“cat");那么Animal這個表中,字段type中,為animal的元組映射Animal類,為cat的元組映射Cat類。
而這種方式,多用於數據庫字典概念。
@Transient
如果某個屬性不需要被持久化,可以加上 @javax.persistence.Transient 注解或者使用 java 的 transient 關鍵字。
@Lob
實體BLOB、CLOB類型的注解:
- BLOB類型屬性聲明為byte[]或者java.sql.Blob,多用來直接將文件存儲在數據庫字段中(如圖片);
- CLOB類型的屬性聲明為String或java.sql.Clob (詳可見參考鏈接中《Hibernate的Annotation實體BLOB、CLOB類型注解》)
