課程內容
1 HelloWorld
a) Xml
b) annotation
2 Hibernate原理模擬 - 什么是O/R Mapping以及為什么要有O/R Mapping
3 常見的0/R框架(了解)
4 hibernate基礎配置(重點)
5 ID生成策略(重點 AUTO)
6 Hibernate核心開發接口介紹(重點)
7 對象的三種狀態(了解)
8 關系映射(重點)
9 Hibernate査詢(HQL)
10 在Struts基礎上繼續完善BBS200
11 性能優化(重點)
12 補充話題
風格
1 先脈絡,后細節
2 先操作,后原理
3 重Annotation,輕xml配置文件
a) JPA
b) hibernate – extension
資源
1 http://www. hibernate.org
2 hibernate zh_CN文檔
3 hibernate annotation references
環境准備
1 下載hibernate-distribution-3.3.2.GA-dist
2 下載hibernate-annotations-3.4.0.GA
3 注意閱讀hibernate compatibility matrix(hibernate 網站download)
4 下載slf4jl.5.8
Hibernate HelloWorld
1 建立新java 項目,名為hibernate_0100_HelloWorld
2 學習建User-library-hibernate,並加入相應的jar包
a) 項目右鍵-buildpath-configure build path-add library—
b) 選擇User-library,在其中新建 libraray,命名為 hibernate
c) 在該library中加入hibernate所需jar包
i. hibernate core
ii. /required
iii. slf-nop jar
3 引入mysql的JDBC驅動包
4 在mysql中建立對應的數據庫以及表
a) create database hibernate;
b) use hibernate;
c) create table Student (id int primary key, namevarchar(20), age int);
5 建立hibernate 配置文件hibernate.cfg.xml
c) 注釋掉暫時用不上的內容
6 建立Student 類
7 建立Student 映射文件 Student.hbm.xml
a) 參考文檔
8 將映射文件加入到hibernate.cfg.xml中
9 寫測試類Main,在Main中對Student對象進行直接的存儲測試
10 FAQ:
a) 要調用 new Configuration().configure().buildSessionFactory(),而不是
要省略 configure,否則會出 hibernate dialect must be set 的異常
11 Note:
b) 重要的是:
iii. 主動學習,砍棄被動接受灌輸的習慣!
a) 錯誤讀完整
b) 讀—昔誤的關鍵行
c) 排除法
d) 比較法
e) google
建立 Annotation 版本的 HelloWorld
1 創建teacher 表,create table teacher (id int primary key, name varhcar(20), title varchar(lO));
2 創建Teacher 類
3 在hibernate lib 中加入annotation的jar包
a) hibernate annotaion jar
b) ejb3 persistence jar
c) hibernate common-annotations.jar
d) 注意文襠中沒有提到hibernate-common-annotations.jar 文件
4 參考Annotaion文檔建立對應的注解
5 在hibernate.cfg.xml中建立映射<mapping class:.../〉
6 參考文襠進行測試(注意文襠中缺少configure()的小bug)
7 FAQ: @不給提示
a) 配置eclipse屬性信息content assist-activation--加上@
What is and Why 0/R Mapping
1 JDBC操作數據庫很繁瑣
2 Sql語句編寫並不是面向對象的
3 可以在對象和關系表之間建立關聯來簡化編程
4 0/R Mapping 簡化編程
5 0/R Mapping跨越數據庫平台
6 Hibernate_0200_OR_Mapping_Simulation
0/R Mapping Frameworks
1 hibernate
2 toplink
3 jdo
4 JPA
a) 意願統一天下
Hibernate基礎配置
1 對應項目:Hibernate_0300_BasicConfiguration
2 介紹MSQL的圖形化客戶端
3 hibernate.cfg.xml: hbni2ddl.auto:create、update。。。。
a) 先建表還是先建實體類—先建表
4 搭建日志環境並配置顯示DDL語句
a) slf4j與log4j的關系:slf4j像是一個大管家,可以管理許多的日志框架,log4j是其中之一
b) 加入slf4j-log4j.jar,加入 log4j 的 jar 包,去掉 slf4-nop.jar
c) 從hibernate/project/etc 目錄 copy log4j.properties
d) 査詢hibernate文襠,日志部分,調整日志的輸出策略
5 搭建jUnit環境
a) 需要注意jUnit的Bug
6 hibernate.cfg.xml:show_sql 是否輸出SQL語句
7 hibernate.cfg.xml:format_sql 格式化SQL語句,美化SQL語句
<!-- 格式化顯示輸出sql -->
<property name="format_sql">true</property>
8 表名和類名不同,對表名進行配置
a) Annotation: @Table
b)xml:自己査詢
9 字段名和屬性相同
a) 不用寫@column 與默認的@Basic效果一樣
b) Xml中不用寫 column
10 字段名和屬性名不同
a) Annotation: @Column
b) xml:自己査詢
11 不需要psersistence的字段(不用列)
a) Annotation:@Transient 定義不寫入數據庫,屬性透明
b) xml不寫
12 映射日期與時間類型,指定時間精度
a) Annotation:@Temporal(參數) 參數有3種 只顯示時間,只顯示日期,時間日期都顯示
//@Temporal(TemporalType.DATE) 只顯示日期
//@Temporal(TemporalType.TIME) 只顯示時間
//@Temporal(TemporalType.TIMESTAMP) 顯示日期與時間
b) xml:指定 type
<class name="Teacher" table="Teacher" >
<id name="id" column="id"></id>
<property name="name" type="time" />
</class>
13 映射枚舉類型( 比較少用)
a) @Enumerated
@Enumerated(EnumType.ORDINAL) 枚舉類型按位置數,如:0,1,2 ...存儲
@Enumerated(EnumType.STRING) 枚舉類型按設定值存儲
b) xml:麻煩
14 字段映射的位置(field或者get方法)
a) best practice:保持 field(變量定義) 和 get set 方法的一致
15 @Lob
16 課外:CLOBBLOB類型的數據存取
17 課外:Hibernate自定義數據類型
18 hibernate 類型
ID生成策略
1 對應項目:hibernate_0400_ID
2 注意:
a) 我們觀察hibernate生成表的結構並不是為了將來就用它生成,(可能還有自己的擴展,比如index等)而是為了明白我們應該建立什么樣的表和實體類映射
3 xml生成id
a) generator
<id name="id" >
<generator class="native"></generator>
</id>
b) 常用四個:native identity sequence uuid
4 注解方式:@GeneratedValue
a) 自定義ID
b) AUTO(直接寫 @GeneratedValue 相當如native) (@GeneratedValue(strategy=GenerationType.AUTO))
i. 默認:對 MySQL,使用auto_increment
ii. 對 Oracle使用hibernate_sequence(名稱固定)
c) IDENTITY(@GeneratedValue(strategy=GenerationType.IDENTITY))
d) SEQUENCE(@GeneratedValue(strategy=GenerationType.SEQUENCE))
i. @SequenceGenerator(可自定義在數據庫生成指定的sequence名)
@Id
//在@GeneratedValue中增加 generator="teacherSEQ"
@GeneratedValue(strategy=GenerationType.SEQUENCE,generator="teacherSEQ")
//"teacherSEQ"為@SequenceGenerator的標識名
//"teacherSEQ_DB"為指定到數據庫生成的Sequence名
@SequenceGenerator(name="teacherSEQ", sequenceName="teacherSEQ_DB")
public int getId() {
return id;
}
e) TABLE (可以忘記)
i. @TableGenerator
@TableGenerator(
name="teacherID", //被調用的TABLE名字
table="teacherID_DB", //數據庫建立的表名
pkColumnName="key_value",
pkColumnValue="pk_value",
valueColumnName="teacher", //pkColumnValue對應類名
allocationSize=1 //pkColumnValue對應類名
)
@GeneratedValue(strategy=GenerationType.TABLE,generator=" teacherID ")
注:如果使用注解方式的uuid 如下:
@Id
@GeneratedValue(generator="teacherUUID")
@GenericGenerator(name="teacherUUID", strategy="uuid")
5 FAQ;
a) 用Junit測試時Hibernate Session Factory初始化異常不提示.疑似一個bug
b) 用main來做測試
6 聯合主鍵
a) Xml方式: composite-id
i. 將聯合主鍵的屬性提取出來,重新編寫一個pojo類(原pojo類中的id,name要刪除並新加入屬性“StudentPK”)
public class StudentPK implements Serializable {
private String id;
private String name;
… …
ii. 新建pojo類必須實現 java.io.Serializable 序列化接口
iii. 新pojo類要重寫equals和hashCode方法
@Override
public boolean equals(Object o) {
if(o instanceof StudentPk) {
StudentPk pk = (StudentPk)o;
if(this.id == pk.getId() && this.name.equals(pk.getName())) {
return true;
}
}
return false;
}
@Override
public int hashCode() {
return this.name.hashCode();
}
iv. 聯合主鍵生成策略XML配置方法
<hibernate-mapping>
<class name="com.bjsxt.pojo.Student" >
<composite-id name="studentPK" class="com.bjsxt.pojo.StudentPK">
<key-property name="id"></key-property>
<key-property name="name"></key-property>
</composite-id>
<property name="age" />
<property name="sex" />
<property name="good" type="yes_no"></property>
</class>
</hibernate-mapping>
b) Annotation
i. 前三步與Xml方式前三步一樣 都要建立新pojo類 都要實現Serializable接口 重寫equals和hashCode方法.
ii. 方法1在新類前寫@Embeddable,在原pojo類的新屬性“TercherPK”的get方法前寫@ld,如下
@ Embeddable
public class TeacherPK implements Serializable {
private String id;
private String name;
… …
@Entity
public class Teacher {
private TeacherPK teacherPK ;
@Id
public TeacherPK getTeacherPK() {
return teacherPK;
}
… …
iii. 方法2:@EmbeddedlD(*) 新pojo類無需加注解,只需在原pojo類新屬性“TercherPK”的get方法前寫@EmbeddedlD即可
iv. 方法3:@Id @IdClass(*) 新pojo類無需加注解,原pojo類的id,name屬性保留不變,也無需新增“TercherPK”屬性。 只在id,name的get方法前都加@Id,並在原pojo類前加“@IdClass(TeacherPK).class)”,如下
@Entity
@IdClass(TeacherPK.class)
public class Teacher {
private String id;
private String name;
@Id
public String getId() {
return id;
}
@Id
public String getName() {
return name;
}
... ...
核心幵發接口介紹
1 hibernate_0500_CoreAPI
2 Hibernate API文檔需要單獨下載
3 Configuration
a) AnnotationConfiguration
b) 進行配置信息的管理
c) 用來產生SessionFactory
d) 可以在configure方法中指定hibernate配置文件
e) 只氣關注一個方法即:buildSessionFactory
4 SessoinFactor
a) 用來產生和管理Session
b) 通常情況下每個應用只需要一個SessionFactory
c) 除非要訪間多個數據庫的情況
d) 關注兩個方法即:openSession getCurrentsession
i. open session每次都是新的,需要close
ii. getCurrentsession從上下文找,如果有,用舊的,如果沒有,建新的
1. 用途,界定事務邊界
2. 事務提交自動close
3. 上下文配置可參見xml文件中
<property name="current_session_context_classs">thread</property>
4. current_session_context_class (jta、thread常用 managed、custom.Class少用)
a) thread 使用connection 但數據庫連接管理事務
b)jta (全稱java transaction api)-java分布式事務管理(多數據庫訪問)
jta由中間件提供(jboss WebLogic等,tomcat不支持)
5 Session
a) 管理一個數據庫的任務單元(簡單說就是增刪 改 查)
b) 方法(CRUD)
i. Save() session.save(對象);
ii. Delete session.delete(對象);
iii. Load Student s1=(Student)session.load(Student.class, 1);
iv. Get Student s1=(Student)session.get(Student.class, 1);
v. get與load的區別(面試重點,原理)
1. 不存在對應記錄時表現不一樣
2. load返回的是代理對象,等到真正用到對象的內容時才發出sql語句
3. get直接從數據庫加載,不會延遲
vi. updates session.update(對象);
1. 用來更新detached對象,更新完成后轉為persistent狀態
2. 更新transient對象會報錯
3. 更新自己設定id的transient對象可以(數據庫有對應記錄)
4. persistent狀態的對象只要設定(如:t.setName…)不同字段就會發生更新
5. 更新部分更改的字段
a) xml 設定 property 標簽的 update 屬性,annotation 設定@Column 的 updatable
屬性,不過這種方式很少用,因為不靈活(忘記)
b) 使用xml中的dynamic-update,JPA1.0 Annotation 沒有對應的屬性,hibernate 擴
展?
i. 同一個session可以,跨session不行,不過可以用merge()(不重要)
c) 使用 HQL(EjBQL)(建議)
vii. saveOrUpdate() session.saveOrUpdate(對象);
viii. clear方法 session.clear();
1.無論是load還是get,都會首先査找緩存(一級緩存),如果沒有,才會去數據庫査找,調用
clear()方法可以強制清除session緩存
ix. flush()方法 session.flush();
1. 當session的事務提交后,會強制將內存(session緩存)與數據庫同步.默認情況下是session的事務提交(commit)時才同步!
2. session的FlushMode設置,可以設定在什么時候同步緩存與數據庫(很少用)
例如: session.setFlushMode(FlushMode.AUTO)
x. find方法已經過時!
6 SchemaExport (自動建表)
new SchemaExport(new AnnotationConfiguration().configure()).create(false, true);
7 Query 接口
a) 參考Hibernate査詢(HQLEJBQL)的內容
8 Note:
a) Hibernate中涉及很多非常非常細節的區別,但在實際應用中用得極少,請大家先享受寫項目的樂
趣,再來探討這些細節問題
i. 比如save和persist的區別
ii. merge、evict 等方法
iii. 比如 refresh、lock 等
b) 建議的學習方法,動手實驗
c) 細節問題參考補充視頻
三種對象狀態
1 上一個 project
2 三種狀態的區分關鍵在於
a) 有沒有ID
b) ID在數據庫中有沒有
c) 在內存中有沒有(session緩存)
3 三種狀態:
a) transient:內存中一個對象,沒ID,緩存中也沒有
b) persistent:內存中有,緩存中有,數據庫有(ID)
c) detached:內存有,緩存沒有,數據庫有,ID
4 對這三種狀態需要關注的問題是在該狀態下如果進行數據庫的操作會發生什么結果,比 如改變屬性的
值會不會發出update語句?
a) 強烈建議動手實驗
b) 進行正常人的思考
c) 絕對不要去背這些東西!背過也並不代表你有多牛!
關系映射(重要)
對象之間的關系
1 這里的關系映射指的是對象之間的關系,並不是指數據庫的關系,本章解決的問題是當對象之間處於
下列關系之一時,數據庫表該如何映射,編程上該如何對待(紅色為重點中的重點)
2 簡化問題:
a) 怎么寫 Annotation
b) 增刪改査CRUD怎么寫
3 —對一
a) 單向(主鍵、外鍵)
b) 雙向(主鍵、外鍵)
c) 中間表
4 —對多
a) 一張主表,多張子表
5 組件映射
a) @Embeddable
b) @ Embedded
一對一關聯
1 一對一單向外鍵關聯
a) 項目名稱:hibernate_0600_one2one_uni_fk
b) Annotation: 在被約束表字段的get方法上加@0ne20ne @JoinColumn
@OneToOne
@JoinColumn(name="wifeid") //指定生成的數據庫字段名
public Wife getWife() {
return wife;
}
c) xml: 在被約束表的xml配置文件中加<many-to-one unique
<class name="com.bjsxt.pojo.StuIdCard">
<id name="id">
<generator class="native"></generator>
</id>
<property name="num"/>
<many-to-one name="student" column="studentId" unique="true">
</many-to-one>
</class>
unique="true"是保證生成的字段唯一,這樣<many-to-one 也達到了一對一的效果
2 一對一雙向外鍵關聯
a) 項目名稱:hibernate_0700_one2one_bi_fk^
b) Annotation: @0ne20ne(mappedBy=”另一個類里定義的屬性名”)
規律:凡是雙向關聯,必設mappedBy
在Wife類中 寫Husband對象屬性並添加注解@OneToOne(mappedBy="wife") mappedBy作用
是指定這個一對一關聯是被Husband類的 wife屬性(准確說是getWife方法)做的映射
@OneToOne(mappedBy="wife")
public Husband getHusband() {
return husband;
}
在 類中寫Wife對象屬性
@OneToOne
@JoinColumn(name="wifeid") //指定生成的數據庫字段名
public Wife getWife() {
return wife;
}
此注釋將由Husband表中生成wifeid字段作為fk外鍵,wife表中不生成額外的Husbandid字段
c) xml: many-to-one unique <one-to-one property-ref
在Student類中寫StuIdCard屬性, StuIdCard類中寫Student屬性
StuIdCard.hbm.xml文件中加
<many-to-one name="student" column="studentId" unique="true"></many-to-one>
Student.hbm.xml文件中加
<one-to-one name="stuIdCard" property-ref="student"></one-to-one>
其中, property-ref 相當於mappedBy
此方式生成的StuIdCard表中包含studentid字段作為fk外鍵, Student表中不生成額外的字段
特別說明: 一對一單向外鍵關聯與一對一雙向外鍵關聯在數據庫的表的格式是一樣的,區別在於
java程序中. 雙向外鍵關聯可通過Hibernate在兩個類間互相調用彼此,而單向外鍵關聯只能單方向調用.
3 一對一單向主鍵關聯(不重要,忘記)
a) 項目名稱:hibernate_0800_one2one_uni_pk
b) @primaryKeyJoinColumn
c) xml: <one-to-one id 使用 foreign class
4 一對一雙向主鍵關聯(不重要,忘記)
a) 項目名稱:hibernate_0900_one2one_bi_pk
b) @primaryKeyJoinColumn(不常用,了解)
c) xml: <one-to-one id 使用foreign class和<one-to-one property-ref
5 聯合主鍵
a) 項目名稱:hibernate_1000_one2one_uni_fk_composite
Wife類中建立聯合主鍵,建立方式參考 ID生成策略中的聯合主鍵部分
Husband類中寫Wife對象屬性,並在其get方法上寫@OneToOne即可完成一對一外鍵映射
若想要指定生成的外鍵名 則需使用@JoinColumns注解,如下:
@OneToOne
@JoinColumns( { @JoinColumn(name = "wifeid", referencedColumnName = "id"),
@JoinColumn(name = "wifename", referencedColumnName = "name") })
/*@JoinColumns用於在一對一外鍵關聯存在聯合主鍵情況時指定生成的外鍵字段名稱
@JoinColumns的參數為@JoinColumn數組 @JoinColumn內除需指定name屬性外還需指定
referencedColumnName屬性值 作用是可指定生成的字段名所對應的目標表字段名*/
public Wife getWife() {……}
組件映射
1 項目:hibernate_1100_component
2 對象關系:一個對象是另外一個對象的一部分
3 數據庫表:一張表
4 annotation: @ Embeddable @Embbeded
對象模型
Husband(id,name,wife)
Wife(name,age)
Annotation:
在Husband的wife屬性上建立注解
@Embedded 表明該對象是從別的位置嵌入過來的,是不需要單獨映射的表.
這種方式生成的表為husband(id,name,wifename,wifeage),不會生成wife表.
@Embedded
Public Wift getWife(){
}
@AttributeOverride注解需要寫在getWife方法上,可以重新指定生成的Wife類組件生成的字段名,例如:Husband與Wife兩個類中都有name字段,這樣在生成表的時候會有沖突,此時采用@AttributeOverride注解可以指定Wife類中的name屬性對應新的字段名—“wifename”,不過@AttributeOverride注解不常用,因為有更好的解決方法. 1:不要在組件的兩個映射類中寫同名屬性;2:如果真的有重復,那么可以在分類中(此處為Wife類)的重復名稱的屬性上使用如下內容以指定新的字段名:
@Column(name="wifename")
public String getName() {
return name;
}
另外,@ Embeddable注解好像是寫在分類(Wife類)的類名前的,不過好像不寫也行
@Embeddable
public class Wife {… …}
5 xml: 使用<component,例如:
<class name="Husband" >
<id name="id">
<generator class="native"/>
</id>
<property name="name"></property>
<component name="wife">
<property name="wifeName"/>
<property name="wifeAge"/>
</component>
</class>
多對一與一對多
a) 項目名稱:hibernate_1200_many2one_uni
實體模型(User多對一Group)
User(id,name,group)多
Group(id,name)一
perosnid |
person name |
dreamid |
1 |
zhangsan |
1 |
1 |
zhangsan |
2 |
dreamid |
dreamdescr |
|
1 |
earn money |
|
2 |
eat a lot |
|
c) annotaion: @Many2One
只需要在多的一端User屬性group進行注解配置
@ManyToOne //多對一關聯 User是多的一方 Group是一的一方
@JoinColumn(name="groupid") //指定User表中生成與Group對應的字段名
public Group getGroup() {
return group;
}
d) xml: <many-to-one
<many-to-one name="group" column="groupId" />
標簽會在”多”的一端添加外鍵,相當於在數據庫中添加外鍵
生成的表為user(id,name,groupid),t_group(id,groupname)
屬性cascade
<many-to-one name="group" column="groupid" cascade="all"/>
取值all,none,save-update,delete,對象間的級聯操作,只對增刪改起作用.
在存儲時User時,設置了cascade="all"會自動存儲相應的t_group.而不用管user關聯的對象(通常情況下會優先存儲關聯的對象,然后再存儲user).
a) 項目名稱:hibernate_1300_one2many_uni
模型(group一對多user)
Group(id,name,users)一
User(id,name)多
設計時在一的這一端存在着多的集合,生成的數據庫表通常是在多的一端生成外鍵.
Set<User> users = new HashSet<User>();
d) Annotation:@One2Many
在一的這一端Group端users屬性上進行注解配置
@OneToMany //一對多關聯 Group是一的一方 User是多的一方
@JoinColumn(name="groupid") //指定User表中生成與Group對應的字段名 注意此處與多對一配置方式不同
public Set<User> getUsers(){ ……. }
Hibernate默認將OneToMany理解為ManyToMany的特殊形式,如果不指定生成的外鍵列@JoinColumn(name="groupId"),則會默認生成多對多的關系,產生一張中間表。
e) xml:<set <one2many
XML配置中配置一的那一端Group
<class name="com.hibernate.Group" table="t_group">
<id name="id">
<generator class="native"/>
</id>
<property name="name"/>
<set name="users">
<key column="groupId"/>指定生成外鍵字段的名字
<one-to-many class="com.pojo.User"/>
</set>
</class>
一對多與多對一的雙向關聯是同一種情況.
關系模型(group一對多user)
Group(id,name,users)一
User(id,name,group)多
Set<User> users=new HashSet<User>()
配置規則:一般以多的一端為主,先配置多的一端
在多的一端User端配置group
@ManyToOne
@JoinColumn(name="groupid")
在一的一端Group端配置時,在users只需要加個mappedBy="groupid"
@OneToMany(mappedBy="group")
XML配置
Group中
<set name="users">
<key column="groupId"/>
<one-to-many class="com.hibernate.User"/>
</set>
在User中
<many-to-one name="group" column="groupId"/>
務必確保在多的一端生成的生成的外鍵和一的一方生成的外鍵的名字相同,都為groupId.
如果名字不同則會在多的一端生成多余的外鍵
多對多
1 單向關聯:
a) 項目:hibernate_1500_many2many_uni
關系模型(Teache多對多Student),從Teacher這一端能關聯到students.
Teacher(id,name,students)多
Student(id,name)多
Set<Student> students=new HashSet<Student>()
在Teacher那一端配置
b) 例如:老師和學生的關系,老師需要知道自己教了哪些學生
c) 數據庫:生成中間表
d) Annotation:@Many2Many
Teacher類中寫:
private Set<Student> students = new HashSet<Student>();
@ManyToMany //多對多關聯 Teacher是主的一方 Student是附屬的一方
@JoinTable(
name="t_s", //指定中間表表名
joinColumns={@JoinColumn(name="teacherid")},//本類主鍵在中間表生成的對應字段名
inverseJoinColumns={@JoinColumn(name="studentid")}//對方類主鍵在中間表生成的對應字段名
)
public Set<Student> getStudents(){……}
e) XML:<many2many
<class name="com.xxx.Teacher">
<id name="id">
<generator class="native"/>
</id>
<property name="name"/>
<set name="students" table="t_s">table定義中間表的表名
<key column="teacher_id"></key>
<many-to-many class="com.xxx.Student" column="student_id"/>
</set>
</class>
2 雙向關聯:
a) 項目:hibernate_1600_many2many_bi
多對多雙向配置只需要在兩端類進行配置就才可以.
關系模型(Teache多對多Student)
Teacher(id,name,students)多
Student(id,name,teachers)多
Set<Student> students = new HashSet<Student>()
Set<Teacher> teachers = new HashSet<Teacher>();
b) 老師知道自己教了哪些學生,學生也知道教自己的有哪些老師
c) 數據庫:生成中間表
d) Annotation:
在Teacher這一端的students上配置
@ManyToMany
@JoinTable(name="t_s",
joinColumns={@JoinColumn(name="teacher_id")},
inverseJoinColumns={@JoinColumn(name="student_id")}
)
在Student一端的teachers只需要配置
@ManyToMany(mappedBy="students")
注意:mappedBy 與 @JoinTable等一類的配置要分開,不然表字段可能亂
e) XML:
XML配置方式:兩端配置一樣,注意表名和生成的中間表的字段屬性名要一致
Teacher那一端配置
<set name="students" table="t_s">
<key column="teacher_id"/>
<many-to-many class="com.xxx.Student" column="student_id"/>
</set>
在Student那一端配置
<set name="teachers" table="t_s">
<key column="student_id"></key>
<many-to-many class="com.xxx.Teacher" column="teacher_id"/>
</set>
生成的數據庫表和上面是一樣的
關聯關系中的CRUD_Cascade_Fetch
1 hibernate_1700_one2many_many2one_bi_crud
2 設定cascade以設定在持久化時對於關聯對象的操作(CUD,R歸Fetch管)
3 cascade僅僅是幫我們省了編程的麻煩而已,不要把它的作用看的太大
a) Cascade的屬性是數組格式,指明做什么操作的時候關聯對象是綁在一起的
b) refresh = A里面需要讀B改過之后的數據
cascade={CascadeType.ALL}
CascadeType取值
ALL Cascade all operations所有情況
MERGE Cascade merge operation合並(merge=save+update)
PERSIST Cascade persist operation存儲 persist()
REFRESH Cascade refresh operation刷新
REMOVE Cascade remove operation刪除
4 鐵律:雙向關系在程序中要設定雙向關聯
5 鐵律:雙向mappedBy
6 fetch
a) 鐵律:雙向不要兩邊設置Eager(會有多余的査詢語句發出)
b) 對多方設置fetch的時候要謹慎,結合具體應用,一般用Lazy不用eager,特殊情況(多方數量不多的時候可以考慮,提高效率的時候可以考慮)
@OneToMany(mappedBy="group",
cascade={CascadeType.ALL}, //控制增刪改(即CUD)
fetch=FetchType.EAGER //控制查詢(即R) EAGER值代表取出關聯 LAZY值為不取關聯
//多的一方fetch取值默認為LAZY 一的一方默認為EAGER
)
另外:如果User類(即多的一方)中設置fetch=FetchType.LAZY 則在調用多(即Group)的對象值的時候
類似延遲加載 即需要在commit();之前 session還存在時調用 如:
System.out.println(user.getGroup().getName());
session.getTransaction().commit();
7 Update時@ManyToOne()中的cascade參數關系
session.beginTransaction();
User user = (User)session.load(User.class,1);
//user對象屬性改變 事務commit時自動判斷與數據庫原有數據不同 可自動update
//此時的update與@ManyToOne()中的cascade或fetch參數取值無關
user.setName("user1");
user.getGroup().setName("group1");
session.getTransaction().commit();
如果user改變在commit()之后 且想要執行Update方法時 user與group表同時更新則,則User類的cascade={CascadeType.ALL},並在程序中寫如下代碼:
session.beginTransaction();
User user = (User)session.get(User.class,1);
session.getTransaction().commit();
user.setName("user1");
user.getGroup().setName("group1");
Session session2 = sessionFactory.getCurrentSession();
session2.beginTransaction();
session2.update(user);
session2.getTransaction().commit();
8 Delete時@ManyToOne()中的cascade關系
如果User及Group類中均設為@ManyToOne(cascade={CascadeType.All}),那么在執行如下:
session.beginTransaction();
User user = (User)session.load(User.class,1);
session.delete(user);
session.getTransaction().commit();
注意:此處刪除的是 多對一(即User對Group) 中的“多”的一方(User類)
會刪除user及user對應的group,再反向對應group的user都會刪除,原因就是設置了@ManyToOne(cascade={CascadeType.All})
三種方法可避免全部刪除的情況:
1. 去掉@ManyToOne(cascade={CascadeType.All})設置;
2. 直接寫Hql語句執行刪除;
3. 將user對象的group屬性設為null,相當於打斷User與Group間的關聯,代碼如下
session.beginTransaction();
User user = (User)session.load(User.class,1);
user.setGroup(null);
session.delete(user);
session.getTransaction().commit();
注意:如果刪除的是 多對一中的“一”的一方(Group類)時,如果使用第3種方式(user屬性設為null)來打斷兩個對象間的關聯的話,代碼與之前不同,如下:
session.beginTransaction();
Group group = (Group)session.load(Group.class,1);
//循環將group中的set集合下的各個user對象設為null
//相當於先將數據庫中user表中與group表關聯的字段(即groupid)設為null
for(User user :group.getUsers()){
System.out.println(user.getName());
user.setGroup(null);
}
//再將group的set集合設為null,相當於將group表中與user表關聯的字段(即userid)設為null
//此句的前提是user表中的關聯字段(groupid)已經為null,如沒有則相當於破壞了一對多關聯,會報錯
group.setUsers(null);
session.delete(group);
session.getTransaction().commit();
9 O/RMapping 編程模型
a) 映射模型
i. jpa annotation(java提供的annotation配置--常用)
ii. hibernate annotation extension(Hibernate擴展的annotation配置--較少用)
iii. hibernate xml(Hibernate的xml配置方式--常用)
iv. jpa xml(java提供的xml配置--較少用)
b) 編程接口
i. Jpa(不常用)
ii. hibernate(現在用)
c) 數據査詢語言
i. HQL
ii. EJBQL(JPQL)
10 要想刪除或者更新先做load,除了精確知道ID之外
11 如果想消除關聯關系,先設定關系為null.再刪除對應記錄,如果不刪記錄,該記錄變成垃圾數據
12 練習:多對多的CRUD
teacher |
student |
t1 |
s1 |
t1 |
s2 |
t2 |
s1 |
t2 |
s2 |
關系映射總結
1 什么樣的關系,設計什么樣的表,進行什么樣的映射
集合映射(不太重要)
1 項目名稱:hibernate_1800_Collections_Mapping
2 Set
3 List (與Set差不多 多個@OrderBy)
a) @OrderBy
繼承映射(不太重要)
a) 一張總表SINGLE_TABLE
i. hibernate_1900_lnheritence_Mapping_Single_Table
b) 每個類分別一張表TABLE_PER_CLASS
i. hibernate_2000_lnheritence_Mapping_Table_Per_Class
c) 每個子類一張表jOINED
i. hibernate_2100_lnheritence_Mapping_JOINED
父類上加注解@Inheritance(strategy=InheritanceType.JOINED)
@Inheritance(strategy=InheritanceType.JOINED)
public class Person {... ...}
作業:
1 學生課程、分數的設計(重要)
a) 使用聯合主鍵@Embeddedld
i. 實現 Serializable 接口
b) 不使用聯合主鍵(視頻中實際例子采用此類方法)
注:自己的項目與馬老的方式不同,但可實現同樣的功能(除從學生查課程外),具體見項目hibernate_2300_Stu_Course_Score
2 設計:
a) 實體類(表)
b) 導航(編程方便)
c) 確定了編程方式
3 樹狀結構的設計(至關重要)
a) 在同—個類中使用One2Many和Many20ne
關系模型(Tree)
Tree(int id,String name,Tree parent,List children)多
private List<Tree> children = new ArrayList<Tree>();
@Id
@GeneratedValue
public int getId(){
return id;
}
@ManyToOne
@JoinColumn(name="parent_id")
public Tree getParent(){
return parent;
}
//fetch=FetchType.EAGER可省略 即為@OneToMany的默認值fetch=FetchType.LAZY
//若樹形較小 可使用EAGER 一次全部載入內存
//若為LAZY則查詢樹形時不會一次全部載入內存(適用於較大的樹形),會每取一個葉子節點就select
一次
@OneToMany(mappedBy="parent",
cascade={CascadeType.ALL},
fetch=FetchType.EAGER )
public List<Tree> getChildren() {
return children;
}
Hibernate 查詢(Query Language)
HQL vs EJBQL
1 NativeSQL >HQL.> EJBQL(JPQL 1.0) > QBC(Query By Criteria) > QBE(Query By Example)"
2 總結:QL應該和導航關系結合,共同為査詢提供服務。
1 注意session.clear()的運用,尤其在不斷分頁循環的時候
a) 在一個大集合中進行遍歷,遍歷msg,取出其中的含有敏感字樣的對象
b) 另外一種形式的內存泄露 ( //面試題:Java有內存泄漏嗎?語法級別沒有 但是可由java引起,例如:連接池不關閉,或io讀取后不關閉)
2 1+N問題 (典型的面試題) (詳見hibernate_2800_Hibernate_1+N項目)
a) @ManyToOne(fetch=FetchType.LAZY)
//fetch=FetchType.LAZY 解決N+1問題 說明如下:
//當多對一(@ManyToOne)已經設定屬性" fetch=FetchType.LAZY "時
//只有當需要時(如:t.getCategory().getName()時)才會去獲取關聯表中數據 可以解決N+1問題
b) @BatchSize
//@BatchSize 解決N+1問題 說明如下:
//在與查詢表(此例中為Topic類)關聯的表類(此例中為Category類)頭處加@BatchSize(size=5)
//表示每次可查出5條記錄 從而減少了select語句的個數
c) join fetch
//join fetch 解決N+1問題 說明如下:
//修改hql語句為--" from Topic t left join fetch t.category c "
d) QBC
//QBC(Query By Criteria) 解決N+1問題 說明如下:
//使用QBC的 createCriteria(*.class)執行查詢 也可避免N+1問題
3 list和iterate不同之處(//主要為了面試 詳見hibernate_2900_Hibernate_list_iterate)
a) list取所有
b) iterate先取 ID,等用到的時候再根據ID來取對象
c) session中list第二次發出,仍會到數據庫査詢
d) iterate 第二次,首先找session 級緩存
4 一級緩存和二級緩存和査詢緩存(面試題)(詳見hibernate_3000_Hibernate_3KindsOf_Cache)
a) 什么是緩存
b) 什么是一級緩存,session級別的緩存
c) I什么是二級緩存,SessionFactory級別的緩存,可以跨越session存在
i. 經常被訪間
ii. 改動不大不會經常改動
iii. 數重有限
d) 打開二級緩存
i. hibernate.cfg.xml 設定:
<property
name= "cache.use_second_level_cache">true</property>
<property
name="cache.provider_class">org.hibernate.cache.EhCacheProvider</property>
ii. @Cache注解(由hibernate擴展提供)
@Cache(usage=CacheConcurrencyStrategy.READ_WRITE)
注:使用EhCache二級緩存 需要導入ehcache-1.2.3.jar及commons-logging-1.0.4.jar包
e) load默認使用二級緩存,iterate默認使用二級緩存
f) list默認往二級緩存加數據,但是查詢的時候不使用
g) 如果要query用二級緩存,需打開查詢緩存
<property name="cache.use_query_cache">true</property>
調用Query的setCachable (true)方法指明使用二級緩存
例如:session.createQuery("from Category").setCacheable(true).list();
h) 緩存算法:(純為了面試)
i. LRU LFU FIFO
1. Least Recently Used –最近很少被使用
2. Least Frequently Used (命中率高低)
3. First In First Out 按順序替換
ii. memoryStoreEvictionPolicy = "LRU" (ehcache.xml中配置)
5 事務並發處理(面試的意義更大)
a) 事務:ACID
i. Atomic Consistency Itegrity Durability
b) 事務並發時可能出現的問題:
第一類丟失更新(Lost Update)
時間 |
取款事務A |
存款事務B |
T1 |
開始事務 |
|
T2 |
|
開始事務 |
T3 |
查詢賬戶余額為1000元 |
|
T4 |
|
查詢賬戶余額為1000元 |
T5 |
|
匯入100元把余額改為1100元 |
T6 |
|
提交事務 |
T7 |
取出100元把余額改為900 元 |
|
T8 |
撤銷事務 |
|
T9 |
余額恢復為1000元(丟失更新) |
|
dirty read臟讀(讀到了另一個事務在處理中還未提交的數據)
時間 |
取款事務A |
存款事務B |
T1 |
開始事務 |
|
T2 |
|
開始事務 |
T3 |
|
查詢賬戶余額為1000元 |
T4 |
|
匯入100元把余額改為1100元 |
T5 |
查詢賬戶余額為1100元(讀取臟數據) |
|
T6 |
|
回滾 |
T7 |
取款1100 |
|
T8 |
提交事務失敗 |
|
non-repeatable read 不可重復讀
時間 |
取款事務A |
存款事務B |
T1 |
開始事務 |
|
T2 |
|
開始事務 |
T3 |
查詢賬戶余額為1000元 |
|
T5 |
|
匯入100元把余額改為1100元 |
T5 |
|
提交事務 |
T6 |
查詢帳戶余額為1100元 |
|
T8 |
提交事務 |
|
second lost update problem 第二類丟失更新(不可重復讀的特殊情況)
時間 |
取款事務A |
存款事務B |
T1 |
|
開始事務 |
T2 |
開始事務 |
|
T3 |
|
查詢賬戶余額為1000元 |
T4 |
查詢賬戶余額為1000元 |
|
T5 |
|
取出100元把余額改為900元 |
T6 |
|
提交事務 |
T7 |
匯入100元 |
|
T8 |
提交事務 |
|
T9 |
把余額改為1100元(丟失更新) |
|
phantom read 幻讀
時間 |
查詢學生事務A |
插入新學生事務B |
T1 |
開始事務 |
|
T2 |
|
開始事務 |
T3 |
查詢學生為10人 |
|
T4 |
|
插入1個學生 |
T5 |
查詢學生為11人 |
|
T6 |
|
提交事務 |
T7 |
提交事務 |
|
c) 數據庫的事務隔離機制
i. 查看 java.sql.Connection 文檔
ii. 1:read-uncommitted 2:read-committed 4:repeatable read 8:serializable(數字代表對應值)
為什么取值要使用 1 2 4 8 而不是 1 2 3 4
1=0000 2=0010 4=0100 8=1000(位移計算效率高)
1. 只要數據庫支持事務,就不可能出現第一類丟失更新
2. read-uncommitted(允許讀取未提交的數據) 會出現dirty read, phantom-read,
non-repeatable read 問題
3. read-commited(讀取已提交的數據 項目中一般都使用這個)不會出現dirty read,因為只有另
一個事務提交才會讀出來結果,但仍然會出現 non-repeatable read 和 phantom-read
使用read-commited機制可用悲觀鎖 樂觀鎖來解決non-repeatable read 和 phantom-read問題
4. repeatable read(事務執行中其他事務無法執行修改或插入操作 較安全)
5. serializable解決一切問題(順序執行事務 不並發,實際中很少用)
d) 設定hibernate的事務隔離級別(使用hibernate.connection.isolation配置取值1、2、4、8)
i. hibernate.connection.isolation = 2(如果不設默認依賴數據庫本身的級別)
ii. 用悲觀鎖解決repeatable read的問題(依賴於數據庫的鎖)
(詳見項目 hibernate_3100_Hibernate_Concurrency_Pessimistic_Lock)
1. select ... for update
2. 使用另一種load方法--load(xx.class , i , LockMode.Upgrade)
a) LockMode.None無鎖的機制,Transaction結束時,切換到此模式
b) LockMode.read在査詢的時候hibernate會自動獲取鎖
c) LockMode.write insert update hibernate 會自動獲取鎖
d) 以上3種鎖的模式,是hibernate內部使用的(不需要設)
e) LockMode.UPGRADE_NOWAIT是 ORACLE 支持的鎖的方式
e) Hibernate(JPA)樂觀鎖定(ReadCommitted)
(詳見項目hibernate_3200_Hibernate_Concurrency_Optimistic_Lock)
實體類中增加version屬性(數據庫也會對應生成該字段,初始值為0),並在其get方法前加
@Version注解,則在操作過程中沒更新一次該行數據則version值加1,即可在事務提交前判斷該數據是否被其他事務修改過.
@Version
時間 |
轉賬事務A |
取款事務B |
T1 |
|
開始事務 |
T2 |
開始事務 |
|
T3 |
查詢學生為10人 |
查詢賬戶余額為1000 version=0 |
T4 |
查詢賬戶余額為1000 version=0 |
|
T5 |
|
取出100 把余額改為900 version=1 |
T6 |
|
提交事務 |
T7 |
匯入100元 |
|
T8 |
提交事務 ? version>0 throw Exception |
|
T9 |
把余額改為1100元(丟失更新) |
|
That’s all.