Hibernate Reference

HIBERNATE - Relational
Persistence for Idiomatic Java
1
Hibernate Reference
Documentation
3.6.10.Final
由 Gavin King、Christian Bauer、Max Rydahl
Andersen、Emmanuel Bernard、Steve Ebersole和Hardy Ferentschik
and thanks to James Cobb (Graphic Design)、Cheyenne
Weaver (Graphic Design)和Cao RedSaga Xiaoganghtml

iii
前言 ............................................................................ xi
1. 教程 ......................................................................... 1
1.1. 第一部分 - 第一個 Hibernate 應用程序 ................................. 1
1.1.1. 設置 ............................................................ 1
1.1.2. 第一個 class ................................................... 3
1.1.3. 映射文件 ........................................................ 4
1.1.4. Hibernate 配置 .................................................. 7
1.1.5. 用 Maven 構建 .................................................. 9
1.1.6. 啓動和輔助類 .................................................... 9
1.1.7. 加載並存儲對象 ................................................. 10
1.2. 第二部分 - 關聯映射 ................................................. 13
1.2.1. 映射 Person 類 ................................................ 13
1.2.2. 單向 Set-based 的關聯 ......................................... 14
1.2.3. 使關聯工做 ..................................................... 15
1.2.4. 值類型的集合 ................................................... 17
1.2.5. 雙向關聯 ....................................................... 18
1.2.6. 使雙向連起來 ................................................... 19
1.3. 第三部分 - EventManager web 應用程序 ................................. 20
1.3.1. 編寫基本的 servlet ............................................. 20
1.3.2. 處理與渲染 ..................................................... 21
1.3.3. 部署與測試 ..................................................... 23
1.4. 總結 ................................................................. 24
2. 體系結構(Architecture) ..................................................... 25
2.1. 概況(Overview) ...................................................... 25
2.1.1. Minimal architecture ........................................... 25
2.1.2. Comprehensive architecture ..................................... 26
2.1.3. Basic APIs .................................................... 27
2.2. JMX 整合 ............................................................. 28
2.3. 上下文相關的會話(Contextual Session) ................................ 28
3. 配置 ........................................................................ 31
3.1. 可編程的配置方式 ...................................................... 31
3.2. 得到 SessionFactory .................................................. 32
3.3. JDBC 鏈接 ............................................................ 32
3.4. 可選的配置屬性 ........................................................ 34
3.4.1. SQL 方言 ...................................................... 41
3.4.2. 外鏈接抓取(Outer Join Fetching) .............................. 42
3.4.3. 二進制流(Binary Streams) ..................................... 42
3.4.4. 二級緩存與查詢緩存 ............................................. 42
3.4.5. 查詢語言中的替換 ............................................... 42
3.4.6. Hibernate 的統計(statistics)機制 ............................. 43
3.5. 日誌 ................................................................. 43
3.6. 實現 NamingStrategy .................................................. 43
3.7. Implementing a PersisterClassProvider ................................. 44
3.8. XML 配置文件 ......................................................... 44
HIBERNATE - Relational Persis...
iv
3.9. Java EE Application Server integration ............................... 46
3.9.1. 事務策略配置 ................................................... 46
3.9.2. JNDI 綁定的 SessionFactory ..................................... 47
3.9.3. 在 JTA 環境下使用 Current Session context(當前 session 上下文)
管理 .................................................................. 48
3.9.4. JMX 部署 ...................................................... 48
4. 持久化類(Persistent Classes) .............................................. 51
4.1. 一個簡單的 POJO 例子 ................................................. 51
4.1.1. 實現一個默認的(即無參數的)構造方法(constructor) ............. 52
4.1.2. Provide an identifier property ................................. 53
4.1.3. Prefer non-final classes (semi-optional) ....................... 53
4.1.4. 爲持久化字段聲明訪問器(accessors)和是否可變的標誌(mutators)
(可選) .............................................................. 54
4.2. 實現繼承(Inheritance) ............................................... 54
4.3. 實現 equals() 和 hashCode() 方法: ................................... 55
4.4. 動態模型(Dynamic models) ............................................ 56
4.5. 元組片段映射(Tuplizers) ............................................. 58
4.6. EntityNameResolvers ................................................... 59
5. 對象/關係數據庫映射基礎(Basic O/R Mapping) ................................. 63
5.1. 映射定義(Mapping declaration) ....................................... 63
5.1.1. Entity ......................................................... 66
5.1.2. Identifiers .................................................... 71
5.1.3. Optimistic locking properties (optional) ....................... 89
5.1.4. Property ....................................................... 92
5.1.5. Embedded objects (aka components) ............................. 101
5.1.6. Inheritance strategy .......................................... 104
5.1.7. Mapping one to one and one to many associations ............... 115
5.1.8. 天然 ID(natural-id) ......................................... 123
5.1.9. Any ........................................................... 124
5.1.10. 屬性(Properties) ........................................... 126
5.1.11. Some hbm.xml specificities ................................... 128
5.2. Hibernate 的類型 .................................................... 131
5.2.1. 實體(Entities)和值(values) ................................ 131
5.2.2. 基本值類型 .................................................... 132
5.2.3. 自定義值類型 .................................................. 134
5.3. 屢次映射同一個類 ..................................................... 135
5.4. SQL 中引號包圍的標識符 ............................................... 135
5.5. 數據庫生成屬性(Generated Properties) ............................... 136
5.6. Column transformers: read and write expressions ...................... 136
5.7. 輔助數據庫對象(Auxiliary Database Objects) ......................... 137
6. Types ...................................................................... 139
6.1. Value types ......................................................... 139
6.1.1. Basic value types ............................................ 139
6.1.2. Composite types ............................................... 146
v
6.1.3. Collection types .............................................. 146
6.2. Entity types ........................................................ 146
6.3. Significance of type categories ..................................... 147
6.4. Custom types ........................................................ 147
6.4.1. Custom types using org.hibernate.type.Type .................... 147
6.4.2. Custom types using org.hibernate.usertype.UserType ............ 149
6.4.3. Custom types using org.hibernate.usertype.CompositeUserType .... 150
6.5. Type registry ....................................................... 151
7. 集合映射(Collection mappings) ............................................ 153
7.1. 持久化集合類(Persistent collections) ............................... 153
7.2. How to map collections .............................................. 154
7.2.1. 集合外鍵(Collection foreign keys) ........................... 157
7.2.2. 索引集合類(Indexed collections) ............................. 158
7.2.3. Collections of basic types and embeddable objects ............. 164
7.3. 高級集合映射(Advanced collection mappings) ......................... 166
7.3.1. 有序集合(Sorted collections) ................................ 166
7.3.2. 雙向關聯(Bidirectional associations) ........................ 167
7.3.3. 雙向關聯,涉及有序集合類 ...................................... 172
7.3.4. 三重關聯(Ternary associations) .............................. 173
7.3.5. Using an <idbag> ............................................. 174
7.4. 集合例子(Collection example) ....................................... 174
8. 關聯關係映射 ............................................................... 181
8.1. 介紹 ................................................................ 181
8.2. 單向關聯(Unidirectional associations) .............................. 181
8.2.1. 多對一(many-to-one) ......................................... 181
8.2.2. 一對一(One-to-one) .......................................... 181
8.2.3. 一對多(one-to-many) ......................................... 182
8.3. 使用鏈接表的單向關聯(Unidirectional associations with join tables) .. 183
8.3.1. 一對多(one-to-many) ......................................... 183
8.3.2. 多對一(many-to-one) ......................................... 184
8.3.3. 一對一(One-to-one) .......................................... 184
8.3.4. 多對多(many-to-many) ........................................ 185
8.4. 雙向關聯(Bidirectional associations) ............................... 186
8.4.1. 一對多(one to many)/多對一(many to one) .................... 186
8.4.2. 一對一(One-to-one) .......................................... 187
8.5. 使用鏈接表的雙向關聯(Bidirectional associations with join tables) ... 188
8.5.1. 一對多(one to many)/多對一(many to one) .................... 188
8.5.2. 一對一(one to one) ......................................... 189
8.5.3. 多對多(many-to-many) ........................................ 190
8.6. 更復雜的關聯映射 ..................................................... 190
9. 組件(Component)映射 ....................................................... 193
9.1. 依賴對象(Dependent objects) ........................................ 193
9.2. 在集合中出現的依賴對象(Collections of dependent objects) ........... 195
9.3. 組件做爲 Map 的索引(Components as Map indices ) .................... 196
HIBERNATE - Relational Persis...
vi
9.4. 組件做爲聯合標識符(Components as composite identifiers) ............ 196
9.5. 動態組件(Dynamic components) ....................................... 198
10. 繼承映射(Inheritance Mapping) ........................................... 201
10.1. 三種策略 ............................................................ 201
10.1.1. 每一個類分層結構一張表(Table per class hierarchy) ............ 201
10.1.2. 每一個子類一張表(Table per subclass) ......................... 202
10.1.3. 每一個子類一張表(Table per subclass),使用辨別標誌
(Discriminator) .................................................... 202
10.1.4. 混合使用「每一個類分層結構一張表」和「每一個子類一張表」 .......... 203
10.1.5. 每一個具體類一張表(Table per concrete class) ................. 204
10.1.6. 每一個具體類一張表,使用隱式多態 ............................... 205
10.1.7. 隱式多態和其餘繼承映射混合使用 ............................... 206
10.2. 限制 ............................................................... 206
11. 與對象共事 ................................................................ 209
11.1. Hibernate 對象狀態(object states) ................................. 209
11.2. 使對象持久化 ........................................................ 209
11.3. 裝載對象 ............................................................ 210
11.4. 查詢 ............................................................... 212
11.4.1. 執行查詢 ..................................................... 212
11.4.2. 過濾集合 ..................................................... 216
11.4.3. 條件查詢(Criteria queries) ................................. 217
11.4.4. 使用原生 SQL 的查詢 ......................................... 217
11.5. 修改持久對象 ........................................................ 218
11.6. 修改脫管(Detached)對象 ............................................ 218
11.7. 自動狀態檢測 ........................................................ 219
11.8. 刪除持久對象 ........................................................ 220
11.9. 在兩個不一樣數據庫間複製對象 .......................................... 221
11.10. Session 刷出(flush) .............................................. 221
11.11. 傳播性持久化(transitive persistence) ............................. 222
11.12. 使用元數據 ......................................................... 225
12. Read-only entities ........................................................ 227
12.1. Making persistent entities read-only ................................ 228
12.1.1. Entities of immutable classes ................................ 228
12.1.2. Loading persistent entities as read-only ..................... 228
12.1.3. Loading read-only entities from an HQL query/criteria ......... 229
12.1.4. Making a persistent entity read-only ......................... 231
12.2. Read-only affect on property type .................................. 232
12.2.1. Simple properties ............................................ 233
12.2.2. Unidirectional associations .................................. 233
12.2.3. Bidirectional associations ................................... 235
13. 事務和併發 ................................................................ 237
13.1. Session 和事務範圍(transaction scope) ............................. 237
13.1.1. 操做單元(Unit of work) ..................................... 237
13.1.2. 長對話 ....................................................... 238
vii
13.1.3. 關注對象標識(Considering object identity) .................. 239
13.1.4. 常見問題 ..................................................... 240
13.2. 數據庫事務聲明 ...................................................... 240
13.2.1. 非託管環境 ................................................... 241
13.2.2. 使用 JTA .................................................... 242
13.2.3. 異常處理 ..................................................... 244
13.2.4. 事務超時 ..................................................... 244
13.3. 樂觀併發控制(Optimistic concurrency control) ...................... 245
13.3.1. 應用程序級別的版本檢查(Application version checking) ........ 245
13.3.2. 擴展週期的 session 和自動版本化 .............................. 246
13.3.3. 脫管對象(deatched object)和自動版本化 ...................... 247
13.3.4. 定製自動版本化行爲 ........................................... 247
13.4. 悲觀鎖定(Pessimistic Locking) ..................................... 248
13.5. 鏈接釋放模式(Connection Release Modes) ............................ 248
14. 攔截器與事件(Interceptors and events) ................................... 251
14.1. 攔截器(Interceptors) .............................................. 251
14.2. 事件系統(Event system) ............................................ 253
14.3. Hibernate 的聲明式安全機制 .......................................... 254
15. 批量處理(Batch processing) .............................................. 255
15.1. 批量插入(Batch inserts) ........................................... 255
15.2. 批量更新(Batch updates) ........................................... 256
15.3. StatelessSession(無狀態 session)接口 .............................. 256
15.4. DML(數據操做語言)風格的操做(DML-style operations) ............... 257
16. HQL: Hibernate 查詢語言 ................................................... 261
16.1. 大小寫敏感性問題 .................................................... 261
16.2. from 子句 .......................................................... 261
16.3. 關聯(Association)與鏈接(Join) ................................... 262
16.4. join 語法的形式 .................................................... 263
16.5. 引用 identifier 屬性 ............................................... 264
16.6. select 子句 ........................................................ 264
16.7. 彙集函數 ............................................................ 265
16.8. 多態查詢 ............................................................ 266
16.9. where 子句 ......................................................... 267
16.10. 表達式 ............................................................. 269
16.11. order by 子句 ..................................................... 272
16.12. group by 子句 ..................................................... 273
16.13. 子查詢 ............................................................. 274
16.14. HQL 示例 .......................................................... 274
16.15. 批量的 UPDATE 和 DELETE ........................................... 277
16.16. 小技巧 & 小竅門 ................................................... 277
16.17. 組件 .............................................................. 278
16.18. Row value 構造函數語法 ............................................ 279
17. 條件查詢(Criteria Queries) .............................................. 281
17.1. 建立一個 Criteria 實例 ............................................. 281
HIBERNATE - Relational Persis...
viii
17.2. 限制結果集內容 ...................................................... 281
17.3. 結果集排序 .......................................................... 282
17.4. 關聯 ............................................................... 282
17.5. 動態關聯抓取 ........................................................ 284
17.6. 查詢示例 ............................................................ 284
17.7. 投影(Projections)、聚合(aggregation)和分組(grouping) ........... 285
17.8. 離線(detached)查詢和子查詢 ........................................ 286
17.9. 根據天然標識查詢(Queries by natural identifier) ................... 287
18. Native SQL 查詢 ........................................................... 289
18.1. 使用 SQLQuery ...................................................... 289
18.1.1. 標量查詢(Scalar queries) ................................... 289
18.1.2. 實體查詢(Entity queries) ................................... 290
18.1.3. 處理關聯和集合類(Handling associations and collections) ..... 290
18.1.4. 返回多個實體(Returning multiple entities) .................. 291
18.1.5. 返回非受管實體(Returning non-managed entities) .............. 293
18.1.6. 處理繼承(Handling inheritance) ............................. 293
18.1.7. 參數(Parameters) ........................................... 293
18.2. 命名 SQL 查詢 ...................................................... 293
18.2.1. 使用 return-property 來明確地指定字段/別名 .................. 299
18.2.2. 使用存儲過程來查詢 ........................................... 300
18.3. 定製 SQL 用來 create,update 和 delete ............................. 301
18.4. 定製裝載 SQL ....................................................... 304
19. 過濾數據 .................................................................. 307
19.1. Hibernate 過濾器(filters) ......................................... 307
20. XML 映射 .................................................................. 311
20.1. 用 XML 數據進行工做 ................................................ 311
20.1.1. 指定同時映射 XML 和類 ........................................ 311
20.1.2. 只定義 XML 映射 ............................................. 312
20.2. XML 映射元數據 ..................................................... 312
20.3. 操做 XML 數據 ...................................................... 314
21. 提高性能 .................................................................. 317
21.1. 抓取策略(Fetching strategies) ..................................... 317
21.1.1. 操做延遲加載的關聯 ........................................... 318
21.1.2. 調整抓取策略(Tuning fetch strategies) ...................... 318
21.1.3. 單端關聯代理(Single-ended association proxies) .............. 319
21.1.4. 實例化集合和代理(Initializing collections and proxies) ...... 321
21.1.5. 使用批量抓取(Using batch fetching) ......................... 322
21.1.6. 使用子查詢抓取(Using subselect fetching) ................... 323
21.1.7. Fetch profile(抓取策略) .................................... 323
21.1.8. 使用延遲屬性抓取(Using lazy property fetching) ............. 325
21.2. 二級緩存(The Second Level Cache) ................................. 326
21.2.1. 緩存映射(Cache mappings) ................................... 327
21.2.2. 策略:只讀緩存(Strategy:read only) ........................ 329
21.2.3. 策略:讀寫/緩存(Strategy:read/write) ...................... 329
ix
21.2.4. 策略:非嚴格讀/寫緩存(Strategy:nonstrict read/write) ....... 330
21.2.5. 策略:事務緩存(transactional) .............................. 330
21.2.6. 各類緩存提供商/緩存併發策略的兼容性 .......................... 330
21.3. 管理緩存(Managing the caches) ..................................... 330
21.4. 查詢緩存(The Query Cache) ........................................ 332
21.4.1. 啓用查詢緩存 ................................................. 332
21.4.2. 查詢緩存區 ................................................... 333
21.5. 理解集合性能(Understanding Collection performance) ................ 333
21.5.1. 分類(Taxonomy) ............................................. 333
21.5.2. Lists,maps 和 sets 用於更新效率最高 ......................... 334
21.5.3. Bag 和 list 是反向集合類中效率最高的 ......................... 334
21.5.4. 一次性刪除(One shot delete) ................................ 335
21.6. 監測性能(Monitoring performance) .................................. 335
21.6.1. 監測 SessionFactory .......................................... 335
21.6.2. 數據記錄(Metrics) .......................................... 336
22. 工具箱指南 ................................................................ 339
22.1. Schema 自動生成(Automatic schema generation) ...................... 339
22.1.1. 對 schema 定製化(Customizing the schema) ................... 339
22.1.2. 運行該工具 ................................................... 342
22.1.3. 屬性(Properties) ........................................... 343
22.1.4. 使用 Ant(Using Ant) ........................................ 343
22.1.5. 對 schema 的增量更新(Incremental schema updates) ........... 344
22.1.6. 用 Ant 來增量更新 schema(Using Ant for incremental schema
updates) ............................................................ 344
22.1.7. Schema 校驗 .................................................. 345
22.1.8. 使用 Ant 進行 schema 校驗 ................................... 345
23. Additional modules ........................................................ 347
23.1. Bean Validation .................................................... 347
23.1.1. Adding Bean Validation ....................................... 347
23.1.2. Configuration ................................................ 347
23.1.3. Catching violations .......................................... 349
23.1.4. Database schema .............................................. 349
23.2. Hibernate Search ................................................... 350
23.2.1. Description .................................................. 350
23.2.2. Integration with Hibernate Annotations ....................... 350
24. 示例:父子關係(Parent/Child) ............................................. 351
24.1. 關於 collections 須要注意的一點 ..................................... 351
24.2. 雙向的一對多關係(Bidirectional one-to-many) ....................... 351
24.3. 級聯生命週期(Cascading lifecycle) ................................. 353
24.4. 級聯與未保存值(unsaved-value) ..................................... 354
24.5. 結論 ............................................................... 355
25. 示例:Weblog 應用程序 ..................................................... 357
25.1. 持久化類(Persistent Classes) ...................................... 357
25.2. Hibernate 映射 ..................................................... 358
HIBERNATE - Relational Persis...
x
25.3. Hibernate 代碼 ..................................................... 360
26. 示例:複雜映射實例 ........................................................ 365
26.1. Employer(僱主)/Employee(僱員) ................................... 365
26.2. Author(做家)/Work(做品) ......................................... 367
26.3. Customer(客戶)/Order(訂單)/Product(產品) ...................... 369
26.4. 雜例 ............................................................... 371
26.4.1. "Typed" 一對一關聯 ........................................... 371
26.4.2. 組合鍵示例 ................................................... 371
26.4.3. 共有組合鍵屬性的多對多(Many-to-many with shared composite key
attribute) .......................................................... 373
26.4.4. 基於內容的識別 ............................................... 374
26.4.5. 備用鍵的聯合 ................................................. 375
27. 最佳實踐(Best Practices) ................................................ 377
28. 數據庫移植性考量 .......................................................... 381
28.1. 移植性基礎 .......................................................... 381
28.2. Dialect ............................................................. 381
28.3. 方言的使用 .......................................................... 381
28.4. 標識符的生成 ........................................................ 382
28.5. 數據庫函數 .......................................................... 383
28.6. 類型映射 ............................................................ 383
參考資料 ....................................................................... 385
xi
前言
Working with both Object-Oriented software and Relational Databases can be cumbersome
and time consuming. Development costs are significantly higher due to a paradigm mismatch
between how data is represented in objects versus relational databases. Hibernate is an
Object/Relational Mapping solution for Java environments. The term Object/Relational
Mapping refers to the technique of mapping data from an object model representation to
a relational data model representation (and visa versa). See http://en.wikipedia.org/
wiki/Object-relational_mapping for a good high-level discussion.
注意
While having a strong background in SQL is not required to use Hibernate,
having a basic understanding of the concepts can greatly help you
understand Hibernate more fully and quickly. Probably the single best
background is an understanding of data modeling principles. You might
want to consider these resources as a good starting point:
•http://www.agiledata.org/essays/dataModeling101.html
•http://en.wikipedia.org/wiki/Data_modeling
Hibernate not only takes care of the mapping from Java classes to database tables (and
from Java data types to SQL data types), but also provides data query and retrieval
facilities. It can significantly reduce development time otherwise spent with manual
data handling in SQL and JDBC. Hibernate’s design goal is to relieve the developer
from 95% of common data persistence-related programming tasks by eliminating the need
for manual, hand-crafted data processing using SQL and JDBC. However, unlike many other
persistence solutions, Hibernate does not hide the power of SQL from you and guarantees
that your investment in relational technology and knowledge is as valid as always.
Hibernate may not be the best solution for data-centric applications that only use
stored-procedures to implement the business logic in the database, it is most useful
with object-oriented domain models and business logic in the Java-based middle-tier.
However, Hibernate can certainly help you to remove or encapsulate vendor-specific
SQL code and will help with the common task of result set translation from a tabular
representation to a graph of objects.
若是你對 Hibernate 和對象/關係型數據庫映射仍是個新手,甚至對 Java 也不熟悉,請按照下
面的步驟來學習。
1. Read 第 1 章 教程 for a tutorial with step-by-step instructions. The source code for
the tutorial is included in the distribution in the doc/reference/tutorial/ directory.
2. Read 第 2 章 體系結構(Architecture) to understand the environments where Hibernate
can be used.
前言
xii
3. 查看 Hibernate 發行包中的 eg/ 目錄,裏面有個一簡單的獨立運行的程序。把你的 JDBC 驅
動複製到 lib/ 目錄並修改一下 etc/hibernate.properties,指定數據庫的信息。而後進入命令
行,切換到發行包的目錄,輸入 ant eg(使用 Ant),或者在 Windows 系統下使用 build eg。
4. Use this reference documentation as your primary source of information. Consider
reading [JPwH] if you need more help with application design, or if you prefer a
step-by-step tutorial. Also visit http://caveatemptor.hibernate.org and download the
example application from [JPwH].
5. 在 Hibernate 網站上能夠找到問題和解答(FAQ)。
6. 在 Hibernate 網站上還有第三方的演示、示例和教程的連接。
7. Hibernate 網站的社區是討論關於設計模式以及不少整合方案(Tomcat、JBoss
AS、Struts、EJB 等)的好地方。
There are a number of ways to become involved in the Hibernate community, including
•Trying stuff out and reporting bugs. See http://hibernate.org/issuetracker.html
details.
•Trying your hand at fixing some bugs or implementing enhancements. Again, see http://
hibernate.org/issuetracker.html details.
•http://hibernate.org/community.html list a few ways to engage in the community.
•There are forums for users to ask questions and receive help from the community.
•There are also IRC [http://en.wikipedia.org/wiki/Internet_Relay_Chat] channels for
both user and developer discussions.
•Helping improve or translate this documentation. Contact us on the developer mailing
list if you have interest.
•Evangelizing Hibernate within your organization.
第 1
1
教程
面向新用戶,從一個簡單的使用內存數據庫的例子開始,本章提供對 Hibernate 的逐步介紹。本
教程基於 Michael Gloegl 早期編寫的手冊。全部代碼都包含在 tutorials/web 目錄下。
重要
本教程指望用戶具有 Java 和 SQL 知識。若是你這方面的知識有限,咱們建議你
在學習 Hibernate 以前先好好了解這些技術。
注意
本版本在源代碼目錄 tutorial/eg 下還包含另一個例程。
1.1. 第一部分 - 第一個 Hibernate 應用程序
在這個例子裏,咱們將設立一個小應用程序能夠保存咱們但願參加的活動(events)和這些活動
主辦方的相關信息。(譯者注:在本教程的後面部分,咱們將直接使用 event 而不是它的中文翻
譯「活動」,以避免混淆。)
注意
雖然你能夠使用任何數據庫,咱們仍是用 HSQLDB [http://hsqldb.org/](一個用
Java 編寫的內存數據庫)來避免花費篇章對數據庫服務器的安裝/配置進行解釋。
1.1.1. 設置
咱們須要作的第一件事情是設置開發環境。咱們將使用許多構建工具如 Maven [http://
maven.org] 所鼓吹的「標準格式」。特別是 Maven,它的資源對這個格式(layout) [http://
maven.apache.org/guides/introduction/introduction-to-the-standard-directorylayout.html]有着很好的描述。由於本教程使用的是
web 應用程序,我麼將建立和使用 src/
main/java、src/main/resources 和 src/main/webapp 目錄。
在本教程裏咱們將使用 Maven,利用其 transitive dependency 管理以及根據 Maven 描述符用
IDE 自動設置項目的能力。
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/
maven-4.0.0.xsd">
<modelVersion
第 1 章 教程
2
>4.0.0</modelVersion>
<groupId
>org.hibernate.tutorials</groupId>
<artifactId
>hibernate-tutorial</artifactId>
<version
>1.0.0-SNAPSHOT</version>
<name
>First Hibernate Tutorial</name>
<build>
<!-- we dont want the version to be part of the generated war file name -->
<finalName
>${artifactId}</finalName>
</build>
<dependencies>
<dependency>
<groupId
>org.hibernate</groupId>
<artifactId
>hibernate-core</artifactId>
</dependency>
<!-- Because this is a web app, we also have a dependency on the servlet api. -->
<dependency>
<groupId
>javax.servlet</groupId>
<artifactId
>servlet-api</artifactId>
</dependency>
<!-- Hibernate uses slf4j for logging, for our purposes here use the simple backend -->
<dependency>
<groupId
>org.slf4j</groupId>
<artifactId
>slf4j-simple</artifactId>
</dependency>
<!-- Hibernate gives you a choice of bytecode providers between cglib and javassist -->
<dependency>
<groupId
>javassist</groupId>
<artifactId
>javassist</artifactId>
</dependency>
</dependencies>
</project
>
第一個 class
3
提示
It is not a requirement to use Maven. If you wish to use something
else to build this tutorial (such as Ant), the layout will remain the
same. The only change is that you will need to manually account for
all the needed dependencies. If you use something like Ivy [http://
ant.apache.org/ivy/] providing transitive dependency management you would
still use the dependencies mentioned below. Otherwise, you'd need to
grab all dependencies, both explicit and transitive, and add them to the
project's classpath. If working from the Hibernate distribution bundle,
this would mean hibernate3.jar, all artifacts in the lib/required directory
and all files from either the lib/bytecode/cglib or lib/bytecode/javassist
directory; additionally you will need both the servlet-api jar and one
of the slf4j logging backends.
把這個文件保存爲項目根目錄下的 pom.xml。
1.1.2. 第一個 class
接下來咱們建立一個類,用來表明那些咱們但願儲存在數據庫裏的 event,這是一個具備一些屬
性的簡單 JavaBean 類:
package org.hibernate.tutorial.domain;
import java.util.Date;
public class Event {
private Long id;
private String title;
private Date date;
public Event() {}
public Long getId() {
return id;
}
private void setId(Long id) {
this.id = id;
}
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
第 1 章 教程
4
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}
你能夠看到這個類對屬性的存取方法(getter and setter method)使用了標準
JavaBean 命名約定,同時把類屬性(field)的訪問級別設成私有的(private)。這是推薦的
設計,但並非必須的。Hibernate 也能夠直接訪問這些 field,而使用訪問方法(accessor
method)的好處是提供了重構時的健壯性(robustness)。
對一特定的 event, id 屬性持有惟一的標識符(identifier)的
值。若是咱們但願使用 Hibernate 提供的全部特性,那麼全部的持久化實體
(persistent entity)類(這裏也包括一些次要依賴類)都須要一個這樣的標識符屬性。而事實
上,大多數應用程序(特別是 web 應用程序)都須要經過標識符來區別對象,因此你應該考慮使
用標識符屬性而不是把它看成一種限制。然而,咱們一般不會操做對象的標識(identity),因
此它的 setter 方法的訪問級別應該聲明 private。這樣當對象被保存的時候,只有 Hibernate
能夠爲它分配標識符值。你可看到Hibernate能夠直接訪問 public,private 和 protected 的訪
問方法和 field。因此選擇哪一種方式徹底取決於你,你能夠使你的選擇與你的應用程序設計相吻
合。
全部的持久化類(persistent classes)都要求有無參的構造器,由於 Hibernate 必須使用 Java
反射機制來爲你建立對象。構造器(constructor)的訪問級別能夠是 private,然而當生成運行
時代理(runtime proxy)的時候則要求使用至少是 package 級別的訪問控制,這樣在沒有字節
碼指令(bytecode instrumentation)的狀況下,從持久化類裏獲取數據會更有效率。
把這個文件保存到 src/main/java/org/hibernate/tutorial/domain 目錄下。
1.1.3. 映射文件
Hibernate 須要知道怎樣去加載(load)和存儲(store)持久化類的對象。這正是 Hibernate 映
射文件發揮做用的地方。映射文件告訴 Hibernate 它應該訪問數據庫(database)裏面的哪一個表
(table)及應該使用表裏面的哪些字段(column)。
一個映射文件的基本結構看起來像這樣:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="org.hibernate.tutorial.domain">
[...]
</hibernate-mapping
>
映射文件
5
注意 Hibernate 的 DTD 是很是複雜的。你的編輯器或者 IDE 裏使用它來自動完成那些用來映
射的 XML 元素(element)和屬性(attribute)。你也能夠在文本編輯器裏打開 DTD — 這是
最簡單的方式來概覽全部的元素和 attribute,並查看它們的缺省值以及註釋。注意 Hibernate
不會從 web 加載 DTD 文件,但它會首先在應用程序的 classpath 中查找。DTD 文件已包括在
hibernate3.jar 裏,同時也在 Hibernate 發佈包的 src/ 目錄下。
重要
爲縮短代碼長度,在之後的例子裏咱們會省略 DTD 的聲明。固然,在實際的應用
程序中,DTD 聲明是必需的。
在 hibernate-mapping 標籤(tag)之間, 含有一個 class 元素。全部的持久化實體類(再次聲
明,或許接下來會有依賴類,就是那些次要的實體)都須要一個這樣的映射,來把類對象映射到
SQL 數據庫裏的表:
<hibernate-mapping package="org.hibernate.tutorial.domain">
<class name="Event" table="EVENTS">
</class>
</hibernate-mapping
>
到目前爲止,咱們告訴了 Hibernate 怎樣把 Events 類的對象
持久化到數據庫的 EVENTS 表裏,以及怎樣從 EVENTS 表加載到
Events 類的對象。每一個實例對應着數據庫表中的一行。如今咱們將繼續討論有關惟一標識符屬性
到數據庫表的映射。另外,因爲咱們不關心怎樣處理這個標識符,咱們就配置由 Hibernate 的標
識符生成策略來產生代理主鍵字段:
<hibernate-mapping package="org.hibernate.tutorial.domain">
<class name="Event" table="EVENTS">
<id name="id" column="EVENT_ID">
<generator class="native"/>
</id>
</class>
</hibernate-mapping
>
id 元素是對 identifier 屬性的聲明。name="id" 映射屬性聲明瞭 JavaBean 屬性的名稱並告訴
Hibernate 使用 getId() 和 setId() 方法來訪問這個屬性。column 屬性告訴 Hibernate EVENTS
表的哪一個字段持有主鍵值。
第 1 章 教程
6
嵌套的 generator 元素指定標識符的生成策略(也就是標識符值是怎麼產生的)。在這個例子
裏,咱們選擇 native,它提供了取決於數據庫方言的可移植性。Hibernate 數據庫生成的、全局
性惟一的以及應用程序分配的標識符。標識符值的生成也是 Hibernate 的擴展功能之一,你能夠
插入本身的策略。
提示
native is no longer consider the best strategy in terms of portability.
for further discussion, see 第 28.4 節 「標識符的生成」
最後咱們在映射文件裏面包含須要持久化屬性的聲明。默認狀況下,類裏面的屬性都被視爲非持
久化的:
<hibernate-mapping package="org.hibernate.tutorial.domain">
<class name="Event" table="EVENTS">
<id name="id" column="EVENT_ID">
<generator class="native"/>
</id>
<property name="date" type="timestamp" column="EVENT_DATE"/>
<property name="title"/>
</class>
</hibernate-mapping
>
和 id 元素同樣,property 元素的 name 屬性告訴 Hibernate 使用哪一個 getter 和 setter 方法。
在此例中,Hibernate 會尋找 getDate()、setDate()、getTitle() 和 setTitle() 方法。
注意
爲何 date 屬性的映射含有 column attribute,而 title 卻沒有?當沒有設定
column attribute 的時候,Hibernate 缺省地使用 JavaBean 的屬性名做爲字段
名。對於 title,這樣工做得很好。然而,date 在多數的數據庫裏,是一個保留關
鍵字,因此咱們最好把它映射成一個不一樣的名字。
另外一有趣的事情是 title 屬性缺乏一個 type attribute。咱們在映射文件裏聲明並使用的類型,
卻不是咱們指望的那樣,是 Java 數據類型,同時也不是 SQL 數據庫的數據類型。這些類型就是
所謂的 Hibernate 映射類型(mapping types),它們能把 Java 數據類型轉換到 SQL 數據類
型,反之亦然。再次重申,若是在映射文件中沒有設置 type 屬性的話,Hibernate 會本身試着去
肯定正確的轉換類型和它的映射類型。在某些狀況下這個自動檢測機制(在 Java 類上使用反射
機制)不會產生你所期待或須要的缺省值。date 屬性就是個很好的例子,Hibernate 沒法知道這
個屬性(java.util.Date 類型的)應該被映射成:SQL date,或 timestamp,仍是 time 字段。在
此例中,把這個屬性映射成 timestamp 轉換器,這樣咱們預留了日期和時間的所有信息。
Hibernate 配置
7
提示
當處理映射文件時,Hibernate 用反射(reflection)來決定這個映射類型。這需
要時間和資源,因此若是你注重啓動性能,你應該考慮顯性地定義所用的類型。
把這個映射文件保存爲 src/main/resources/org/hibernate/tutorial/domain/Event.hbm.xml。
1.1.4. Hibernate 配置
此時,你應該有了持久化類和它的映射文件。如今是配置 Hibernate 的時候了。首先讓咱們設立
HSQLDB 使其運行在「服務器模式」。
注意
數據在程序運行期間須要保持有效。
在開發的根目錄下建立一個 data 目錄 - 這是 HSQL DB 存儲數據文件的地方。此時在 data 目
錄中運行 java -classpath ../lib/hsqldb.jar org.hsqldb.Server 就可啓動數據庫。你能夠在 log
中看到它的啓動,及綁定到 TCP/IP 套接字,這正是咱們的應用程序稍後會鏈接的地方。若是你
但願在本例中運行一個全新的數據庫,就在窗口中按下 CTRL + C 來關閉 HSQL 數據庫,並刪除
data/ 目錄下的全部文件,再從新啓動 HSQL 數據庫。
Hibernate 將爲你的應用程序鏈接到數據庫,因此它須要知道如何獲取鏈接。在這個教程裏,我
們使用一個獨立鏈接池(和 javax.sql.DataSource 相反)。Hibernate 支持兩個第三方的開源
JDBC 鏈接池:c3p0 [https://sourceforge.net/projects/c3p0] 和 proxool [http://
proxool.sourceforge.net/]。然而,在本教程裏咱們將使用 Hibernate 內置的鏈接池。
當心
嵌入的 Hibernate 鏈接池不用於產品環境。它缺少鏈接池裏的幾個功能。
爲了保存 Hibernate 的配置,咱們能夠使用一個簡單的 hibernate.properties 文件,或者一個
稍微複雜的 hibernate.cfg.xml,甚至能夠徹底使用程序來配置 Hibernate。多數用戶更喜歡使用
XML 配置文件:
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
第 1 章 教程
8
<!-- Database connection settings -->
<property name="connection.driver_class"
>org.hsqldb.jdbcDriver</property>
<property name="connection.url"
>jdbc:hsqldb:hsql://localhost</property>
<property name="connection.username"
>sa</property>
<property name="connection.password"
></property>
<!-- JDBC connection pool (use the built-in) -->
<property name="connection.pool_size"
>1</property>
<!-- SQL dialect -->
<property name="dialect"
>org.hibernate.dialect.HSQLDialect</property>
<!-- Enable Hibernate's automatic session context management -->
<property name="current_session_context_class"
>thread</property>
<!-- Disable the second-level cache -->
<property name="cache.provider_class"
>org.hibernate.cache.NoCacheProvider</property>
<!-- Echo all executed SQL to stdout -->
<property name="show_sql"
>true</property>
<!-- Drop and re-create the database schema on startup -->
<property name="hbm2ddl.auto"
>update</property>
<mapping resource="org/hibernate/tutorial/domain/Event.hbm.xml"/>
</session-factory>
</hibernate-configuration
>
注意
請注意,這個配置文件指定了一個不一樣的 DTD。
注意這個 XML 配置使用了一個不一樣的 DTD。在這裏,咱們配置了 Hibernate 的SessionFactory
— 一個關聯於特定數據庫全局的工廠(factory)。若是你要使用多個數據庫,就要用多個的
<session-factory>,一般把它們放在多個配置文件中(爲了更容易啓動)。
簽名 4 個 property 元素包含了 JDBC 鏈接所必需的配置。方言 property 元素指定了 Hibernate
生成的特定 SQL 語句。
用 Maven 構建
9
提示
In most cases, Hibernate is able to properly determine which dialect to
use. See 第 28.3 節 「方言的使用」 for more information.
最開始的 4 個 property 元素包含必要的 JDBC 鏈接信息。方言(dialect)的 property 元素指
明 Hibernate 生成的特定 SQL 變量。你很快會看到,Hibernate 對持久化上下文的自動 session
管理就會派上用場。 打開 hbm2ddl.auto 選項將自動生成數據庫模式(schema)- 直接加入數據
庫中。固然這個選項也能夠被關閉(經過去除這個配置選項)或者經過 Ant 任務 SchemaExport 的
幫助來把數據庫 schema 重定向到文件中。最後,在配置中爲持久化類加入映射文件。
把這個文件保存爲 src/main/resources 目錄下的 hibernate.cfg.xml。
1.1.5. 用 Maven 構建
咱們將用 Maven 構建這個教程。你將須要安裝 Maven;你能夠從 Maven 下載頁面 [http://
maven.apache.org/download.html]得到 Maven。Maen 將讀取咱們先前建立的 /pom.xml 並知道
執行基本的項目任務。首先,讓咱們運行 compile 目標來確保咱們能夠編譯到目前爲止的全部程
序:
[hibernateTutorial]$ mvn compile
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Building First Hibernate Tutorial
[INFO] task-segment: [compile]
[INFO] ------------------------------------------------------------------------
[INFO] [resources:resources]
[INFO] Using default encoding to copy filtered resources.
[INFO] [compiler:compile]
[INFO] Compiling 1 source file to /home/steve/projects/sandbox/hibernateTutorial/target/classes
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2 seconds
[INFO] Finished at: Tue Jun 09 12:25:25 CDT 2009
[INFO] Final Memory: 5M/547M
[INFO] ------------------------------------------------------------------------
1.1.6. 啓動和輔助類
是時候來加載和儲存一些 Event 對象了,但首先咱們得編寫一些基礎的代碼以完成設置。咱們必
須啓動 Hibernate,此過程包括建立一個全局的 SessoinFactory,並把它儲存在應用程序代碼容易
訪問的地方。SessionFactory 能夠建立並打開新的 Session。一個 Session 表明一個單線程的單元
操做,org.hibernate.SessionFactory 則是個線程安全的全局對象,只須要被實例化一次。
咱們將建立一個 HibernateUtil 輔助類(helper class)來負責啓動 Hibernate 和更方便地操做
org.hibernate.SessionFactory。讓咱們來看一下它的實現:
第 1 章 教程
10
package org.hibernate.tutorial.util;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
public class HibernateUtil {
private static final SessionFactory sessionFactory = buildSessionFactory();
private static SessionFactory buildSessionFactory() {
try {
// Create the SessionFactory from hibernate.cfg.xml
return new Configuration().configure().buildSessionFactory();
}
catch (Throwable ex) {
// Make sure you log the exception, as it might be swallowed
System.err.println("Initial SessionFactory creation failed." + ex);
throw new ExceptionInInitializerError(ex);
}
}
public static SessionFactory getSessionFactory() {
return sessionFactory;
}
}
把這段代碼保存爲 src/main/java/org/hibernate/tutorial/util/HibernateUtil.java。
這個類不但在它的靜態初始化過程(僅當加載這個類的時候被 JVM 執行一次)中產生全局的
org.hibernate.SessionFactory,並且隱藏了它使用了靜態 singleton 的事實。它也可能在應用程
序服務器中的 JNDI 查找 org.hibernate.SessionFactory。
若是你在配置文件中給 org.hibernate.SessionFactory 一個名字,在 它建立後,Hibernate 會試
着把它綁定到 JNDI。要徹底避免這樣的代碼,你也能夠使用 JMX 部署,讓具備 JMX 能力的容器
來實例化 HibernateService 並把它綁定到 JNDI。這些高級可選項在後面的章節中會討論到。
再次編譯這個應用程序應該不會有問題。最後咱們須要配置一個日誌(logging)系統 —
Hibernate 使用通用日誌接口,容許你在 Log4j 和 JDK 1.4 日誌之間進行選擇。多數開發者更
喜歡 Log4j:從 Hibernate 的發佈包中(它在 etc/ 目錄下)拷貝 log4j.properties 到你的 src
目錄,與 hibernate.cfg.xml 放在一塊兒。看一下配置示例,若是你但願看到更加詳細的輸出信息,
你能夠修改配置。默認狀況下,只有 Hibernate 的啓動信息纔會顯示在標準輸出上。
示例的基本框架完成了 — 如今咱們能夠用 Hibernate 來作些真正的工做。
1.1.7. 加載並存儲對象
We are now ready to start doing some real work with Hibernate. Let's start by writing
an EventManager class with a main() method:
package org.hibernate.tutorial;
加載並存儲對象
11
import org.hibernate.Session;
import java.util.*;
import org.hibernate.tutorial.domain.Event;
import org.hibernate.tutorial.util.HibernateUtil;
public class EventManager {
public static void main(String[] args) {
EventManager mgr = new EventManager();
if (args[0].equals("store")) {
mgr.createAndStoreEvent("My Event", new Date());
}
HibernateUtil.getSessionFactory().close();
}
private void createAndStoreEvent(String title, Date theDate) {
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
session.beginTransaction();
Event theEvent = new Event();
theEvent.setTitle(title);
theEvent.setDate(theDate);
session.save(theEvent);
session.getTransaction().commit();
}
}
在 createAndStoreEvent() 來裏咱們建立了一個新的 Event 對象並把它傳遞給 Hibernate。如今
Hibernate 負責與 SQL 打交道,並把 INSERT 命令傳給數據庫。
A org.hibernate.Session is designed to represent a single unit of work (a single
atomic piece of work to be performed). For now we will keep things simple and assume
a one-to-one granularity between a Hibernate org.hibernate.Session and a database
transaction. To shield our code from the actual underlying transaction system we use
the Hibernate org.hibernate.Transaction API. In this particular case we are using JDBCbased
transactional semantics, but it could also run with JTA.
sessionFactory.getCurrentSession() 是幹什麼的呢?首先,只要你持有
org.hibernate.SessionFactory,大可在任什麼時候候、任何地點調用這個方法。getCurrentSession() 方
法總會返回「當前的」工做單元。記得咱們在 src/main/resources/hibernate.cfg.xml 中把這一
配置選項調整爲 "thread" 了嗎?所以,所以,當前工做單元被綁定到當前執行咱們應用程序的
Java 線程。可是,這並不是是徹底準確的,你還得考慮工做單元的生命週期範圍(scope),它什麼時候
開始,又什麼時候結束。
第 1 章 教程
12
重要
Hibernate 提供三種跟蹤當前會話的方法。基於「線程」的方法不適合於產品環
境,它僅用於 prototyping 和教學用途。後面將更詳細地討論會話跟蹤。
org.hibernate.Session 在第一次被使用的時候,即第一次調用 getCurrentSession() 的時候,其
生命週期就開始。而後它被 Hibernate 綁定到當前線程。當事務結束的時候,無論是提交仍是回
滾,Hibernate 會自動把 org.hibernate.Session 從當前線程剝離,而且關閉它。倘若你再次調
用 getCurrentSession(),你會獲得一個新的 org.hibernate.Session,而且開始一個新的工做單
元。
和工做單元的生命週期這個話題相關,Hibernate org.hibernate.Session 是否被應該用來執
行屢次數據庫操做?上面的例子對每一次操做使用了一個 org.hibernate.Session,這徹底是巧
合,這個例子不是很複雜,沒法展現其餘方式。Hibernate org.hibernate.Session 的生命週期
能夠很靈活,可是你毫不要把你的應用程序設計成爲每一次數據庫操做都用一個新的 Hibernate
org.hibernate.Session。所以就算下面的例子(它們都很簡單)中你能夠看到這種用法,記住每
次操做一個 session 是一個反模式。在本教程的後面會展現一個真正的(web)程序。
See 第 13 章 事務和併發 for more information about transaction handling and demarcation.
The previous example also skipped any error handling and rollback.
要運行它,咱們將使用 Maven exec 插件以及必要的 classpath 設置來進行調用:mvn exec:java
-Dexec.mainClass="org.hibernate.tutorial.EventManager" -Dexec.args="store"。
注意
你可能須要先執行 mvn compile。
你應該會看到,編譯之後,Hibernate 根據你的配置啓動,併產生一大堆的輸出日誌。在日誌最
後你會看到下面這行:
[java] Hibernate: insert into EVENTS (EVENT_DATE, title, EVENT_ID) values (?, ?, ?)
執行 HQL INSERT 語句的例子以下:
咱們想要列出全部已經被存儲的 events,就要增長一個條件分支選項到 main 方法中:
if (args[0].equals("store")) {
mgr.createAndStoreEvent("My Event", new Date());
}
else if (args[0].equals("list")) {
List events = mgr.listEvents();
for (int i = 0; i < events.size(); i++) {
Event theEvent = (Event) events.get(i);
第二部分 - 關聯映射
13
System.out.println(
"Event: " + theEvent.getTitle() + " Time: " + theEvent.getDate()
);
}
}
咱們也增長一個新的 listEvents() 方法:
private List listEvents() {
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
session.beginTransaction();
List result = session.createQuery("from Event").list();
session.getTransaction().commit();
return result;
}
Here, we are using a Hibernate Query Language (HQL) query to load all existing Event
objects from the database. Hibernate will generate the appropriate SQL, send it to the
database and populate Event objects with the data. You can create more complex queries
with HQL. See 第 16 章 HQL: Hibernate 查詢語言 for more information.
如今咱們能夠再次用 Maven exec plugin - mvn exec:java -
Dexec.mainClass="org.hibernate.tutorial.EventManager" -Dexec.args="list" 調用新的功能了。
1.2. 第二部分 - 關聯映射
咱們已經映射了一個持久化實體類到表上。讓咱們在這個基礎上增長一些類之間的關聯。首先我
們往應用程序裏增長人(people)的概念,並存儲他們所參與的一個 Event 列表。(譯者注:與
Event 同樣,咱們在後面將直接使用 person 來表示「人」而不是它的中文翻譯)
1.2.1. 映射 Person 類
最初簡單的 Person 類:
package org.hibernate.tutorial.domain;
public class Person {
private Long id;
private int age;
private String firstname;
private String lastname;
public Person() {}
// Accessor methods for all properties, private setter for 'id'
}
第 1 章 教程
14
把它保存爲文件 src/main/java/org/hibernate/tutorial/domain/Person.java。
而後,建立新的映射文件 src/main/resources/org/hibernate/tutorial/domain/Person.hbm.xml。
<hibernate-mapping package="org.hibernate.tutorial.domain">
<class name="Person" table="PERSON">
<id name="id" column="PERSON_ID">
<generator class="native"/>
</id>
<property name="age"/>
<property name="firstname"/>
<property name="lastname"/>
</class>
</hibernate-mapping>
最後,把新的映射加入到 Hibernate 的配置中:
<mapping resource="org/hibernate/tutorial/domain/Event.hbm.xml"/>
<mapping resource="org/hibernate/tutorial/domain/Person.hbm.xml"/>
如今咱們在這兩個實體之間建立一個關聯。顯然,persons 能夠參與一系列 events,
而 events 也有不一樣的參加者(persons)。咱們須要處理的設計問題是關聯方向
(directionality),階數(multiplicity)和集合(collection)的行爲。
1.2.2. 單向 Set-based 的關聯
咱們將向 Person 類增長一連串的 events。那樣,經過調用 aPerson.getEvents(),就能夠輕鬆地
導航到特定 person 所參與的 events,而不用去執行一個顯式的查詢。咱們使用 Java 的集合類
(collection):Set,由於 set 不包含重複的元素及與咱們無關的排序。
public class Person {
private Set events = new HashSet();
public Set getEvents() {
return events;
}
public void setEvents(Set events) {
this.events = events;
}
}
在映射這個關聯以前,先考慮一下此關聯的另一端。很顯然,咱們能夠保持這個關聯是單向
的。或者,咱們能夠在 Event 裏建立另一個集合,若是但願可以雙向地導航,
如:anEvent.getParticipants()。從功能的角度來講,這並非必須的。由於你總能夠顯式地執行
使關聯工做
15
一個查詢,以得到某個特定 event 的全部參與者。這是個在設計時須要作出的選擇,徹底由你來
決定,但此討論中關於關聯的階數是清楚的:即兩端都是「多」值的,咱們把它叫作多對多
(many-to-many)關聯。於是,咱們使用 Hibernate 的多對多映射:
<class name="Person" table="PERSON">
<id name="id" column="PERSON_ID">
<generator class="native"/>
</id>
<property name="age"/>
<property name="firstname"/>
<property name="lastname"/>
<set name="events" table="PERSON_EVENT">
<key column="PERSON_ID"/>
<many-to-many column="EVENT_ID" class="Event"/>
</set>
</class>
Hibernate 支持各類各樣的集合映射,<set> 使用的最爲廣泛。對於多對多關聯(或叫 n:m 實體
關係), 須要一個關聯表(association table)。表裏面的每一行表明從 person 到 event 的一
個關聯。表名是由 set 元素的 table 屬性配置的。關聯裏面的標識符字段名,對於 person 的一
端,是由 <key> 元素定義,而 event 一端的字段名是由 <many-to-many> 元素的 column 屬性定義。
你也必須告訴 Hibernate 集合中對象的類(也就是位於這個集合所表明的關聯另一端的類)。
於是這個映射的數據庫 schema 是:
_____________ __________________
| | | | _____________
| EVENTS | | PERSON_EVENT | | |
|_____________| |__________________| | PERSON |
| | | | |_____________|
| *EVENT_ID | <--> | *EVENT_ID | | |
| EVENT_DATE | | *PERSON_ID | <--> | *PERSON_ID |
| TITLE | |__________________| | AGE |
|_____________| | FIRSTNAME |
| LASTNAME |
|_____________|
1.2.3. 使關聯工做
咱們把一些 people 和 events 一塊兒放到 EventManager 的新方法中:
private void addPersonToEvent(Long personId, Long eventId) {
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
session.beginTransaction();
Person aPerson = (Person) session.load(Person.class, personId);
第 1 章 教程
16
Event anEvent = (Event) session.load(Event.class, eventId);
aPerson.getEvents().add(anEvent);
session.getTransaction().commit();
}
在加載一 Person 和 Event 後,使用普通的集合方法就可容易地修改咱們定義的集合。如你所見,
沒有顯式的 update() 或 save(),Hibernate 會自動檢測到集合已經被修改並須要更新回數據庫。
這叫作自動髒檢查(automatic dirty checking),你也能夠嘗試修改任何對象的 name 或者
date 屬性,只要他們處於持久化狀態,也就是被綁定到某個 Hibernate 的 Session 上(如:
他們剛剛在一個單元操做被加載或者保存),Hibernate 監視任何改變並在後臺隱式寫的方式執
行 SQL。同步內存狀態和數據庫的過程,一般只在單元操做結束的時候發生,稱此過程爲清理緩
存(flushing)。在咱們的代碼中,工做單元由數據庫事務的提交(或者回滾)來結束——這是
由 CurrentSessionContext 類的 thread 配置選項定義的。
固然,你也能夠在不一樣的單元操做裏面加載 person 和 event。或在
Session 之外修改不是處在持久化(persistent)狀態下的對象(若是該對象之前曾經被持久化,
那麼咱們稱這個狀態爲脫管(detached))。你甚至能夠在一個集合被脫管時修改它:
private void addPersonToEvent(Long personId, Long eventId) {
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
session.beginTransaction();
Person aPerson = (Person) session
.createQuery("select p from Person p left join fetch p.events where p.id = :pid")
.setParameter("pid", personId)
.uniqueResult(); // Eager fetch the collection so we can use it detached
Event anEvent = (Event) session.load(Event.class, eventId);
session.getTransaction().commit();
// End of first unit of work
aPerson.getEvents().add(anEvent); // aPerson (and its collection) is detached
// Begin second unit of work
Session session2 = HibernateUtil.getSessionFactory().getCurrentSession();
session2.beginTransaction();
session2.update(aPerson); // Reattachment of aPerson
session2.getTransaction().commit();
}
對 update 的調用使一個脫管對象從新持久化,你能夠說它被綁定到一個新的單元操做上,因此在
脫管狀態下對它所作的任何修改都會被保存到數據庫裏。這也包括你對這個實體對象的集合所做
的任何改動(增長/刪除)。
這對咱們當前的情形不是頗有用,但它是很是重要的概念,你能夠把它融入到你本身的應用程序
設計中。在EventManager的 main 方法中添加一個新的動做,並從命令行運行它來完成咱們所作的
值類型的集合
17
練習。若是你須要 person 及 event 的標識符 — 那就用 save() 方法返回它(你可能須要修改
前面的一些方法來返回那個標識符):
else if (args[0].equals("addpersontoevent")) {
Long eventId = mgr.createAndStoreEvent("My Event", new Date());
Long personId = mgr.createAndStorePerson("Foo", "Bar");
mgr.addPersonToEvent(personId, eventId);
System.out.println("Added person " + personId + " to event " + eventId);
}
上面是個關於兩個同等重要的實體類間關聯的例子。像前面所提到的那樣,在特定的模型中也存
在其它的類和類型,這些類和類型一般是「次要的」。你已看到過其中的一些,像 int 或
String。咱們稱這些類爲值類型(value type),它們的實例依賴(depend)在某個特定的實體
上。這些類型的實例沒有它們本身的標識(identity),也不能在實體間被共享(好比,兩個
person 不能引用同一個 firstname 對象,即便他們有相同的 first name)。固然,值類型並不只
僅在 JDK 中存在(事實上,在一個 Hibernate 應用程序中,全部的 JDK 類都被視爲值類型),
並且你也能夠編寫你本身的依賴類,例如 Address,MonetaryAmount。
你也能夠設計一個值類型的集合,這在概念上與引用其它實體的集合有很大的不一樣,可是在 Java
裏面看起來幾乎是同樣的。
1.2.4. 值類型的集合
讓咱們在 Person 實體裏添加一個電子郵件的集合。這將以 java.lang.String 實例的 java.util.Set
出現:
private Set emailAddresses = new HashSet();
public Set getEmailAddresses() {
return emailAddresses;
}
public void setEmailAddresses(Set emailAddresses) {
this.emailAddresses = emailAddresses;
}
這個 Set 的映射以下:
<set name="emailAddresses" table="PERSON_EMAIL_ADDR">
<key column="PERSON_ID"/>
<element type="string" column="EMAIL_ADDR"/>
</set>
比較此次和此前映射的差異,主要在於 element 部分,此次並無包含對其它實體引用的集
合,而是元素類型爲 String 的集合(在映射中使用小寫的名字」string「是向你代表它是一個
Hibernate 的映射類型或者類型轉換器)。和以前同樣,set 元素的 table 屬性決定了用於集合
第 1 章 教程
18
的表名。key 元素定義了在集合表中外鍵的字段名。element 元素的 column 屬性定義用於實際保
存 String 值的字段名。
看一下修改後的數據庫 schema。
_____________ __________________
| | | | _____________
| EVENTS | | PERSON_EVENT | | | ___________________
|_____________| |__________________| | PERSON | | |
| | | | |_____________| | PERSON_EMAIL_ADDR |
| *EVENT_ID | <--> | *EVENT_ID | | | |___________________|
| EVENT_DATE | | *PERSON_ID | <--> | *PERSON_ID | <--> | *PERSON_ID |
| TITLE | |__________________| | AGE | | *EMAIL_ADDR |
|_____________| | FIRSTNAME | |___________________|
| LASTNAME |
|_____________|
你能夠看到集合表的主鍵其實是個複合主鍵,同時使用了兩個字段。這也暗示了對於同一個
person 不能有重複的 email 地址,這正是 Java 裏面使用 Set 時候所須要的語義(Set 裏元
素不能重複)。
你如今能夠試着把元素加入到這個集合,就像咱們在以前關聯 person 和 event 的那樣。其實現
的 Java 代碼是相同的:
private void addEmailToPerson(Long personId, String emailAddress) {
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
session.beginTransaction();
Person aPerson = (Person) session.load(Person.class, personId);
// adding to the emailAddress collection might trigger a lazy load of the collection
aPerson.getEmailAddresses().add(emailAddress);
session.getTransaction().commit();
}
此次咱們沒有使用 fetch 查詢來初始化集合。所以,調用其 getter 方法會觸發另外一附加的
select 來初始化集合,這樣咱們才能把元素添加進去。檢查 SQL log,試着經過預先抓取來優化
它。
1.2.5. 雙向關聯
接下來咱們將映射雙向關聯(bi-directional association)— 在 Java 裏
讓 person 和 event 能夠從關聯的任何一端訪問另外一端。固然,數據庫
schema 沒有改變,咱們仍然須要多對多的階數。一個關係型數據庫要比網絡編程語言更加靈活,
因此它並不須要任何像導航方向(navigation direction)的東西 — 數據能夠用任何可能的方
式進行查看和獲取。
使雙向連起來
19
注意
關係型數據庫比網絡編程語言更爲靈活,由於它不須要方向導航,其數據能夠用
任何可能的方式進行查看和提取。
首先,把一個參與者(person)的集合加入 Event 類中:
private Set participants = new HashSet();
public Set getParticipants() {
return participants;
}
public void setParticipants(Set participants) {
this.participants = participants;
}
在 Event.hbm.xml 裏面也映射這個關聯。
<set name="participants" table="PERSON_EVENT" inverse="true">
<key column="EVENT_ID"/>
<many-to-many column="PERSON_ID" class="Person"/>
</set
>
如你所見,兩個映射文件裏都有普通的 set 映射。注意在兩個映射文件中,互換了 key 和 manyto-many
的字段名。這裏最重要的是 Event 映射文件裏增長了 set 元素的 inverse="true" 屬性。
這意味着在須要的時候,Hibernate 能在關聯的另外一端 — Person 類獲得兩個實體間關聯的信
息。這將會極大地幫助你理解雙向關聯是如何在兩個實體間被建立的。
1.2.6. 使雙向連起來
首先請記住,Hibernate 並不影響一般的 Java 語義。 在單向關聯的例子中,咱們是怎樣在
Person 和 Event 之間建立聯繫的?咱們把 Event 實例添加到 Person 實例內的 event 引用集合
裏。所以很顯然,若是咱們要讓這個關聯能夠雙向地工做,咱們須要在另一端作一樣的事情 -
把 Person 實例加入 Event 類內的 Person 引用集合。這「在關聯的兩端設置聯繫」是徹底必要
的並且你都得這麼作。
許多開發人員防護式地編程,建立管理關聯的方法來保證正確的設置了關聯的兩端,好比在
Person 裏:
protected Set getEvents() {
return events;
}
第 1 章 教程
20
protected void setEvents(Set events) {
this.events = events;
}
public void addToEvent(Event event) {
this.getEvents().add(event);
event.getParticipants().add(this);
}
public void removeFromEvent(Event event) {
this.getEvents().remove(event);
event.getParticipants().remove(this);
}
注意如今對於集合的 get 和 set 方法的訪問級別是 protected
— 這容許在位於同一個包(package)中的類以及繼承自這個類的子類能夠訪問這些方法,但禁
止其餘任何人的直接訪問,避免了集合內容的混亂。你應儘量地在另外一端也把集合的訪問級別
設成 protected。
inverse 映射屬性究竟表示什麼呢?對於你和 Java 來講,一個雙向關聯僅僅是在兩端
簡單地正確設置引用。然而,Hibernate 並無足夠的信息去正確地執行 INSERT 和
UPDATE 語句(以免違反數據庫約束),因此它須要一些幫助來正確的處理雙向關聯。把關聯的
一端設置爲 inverse 將告訴 Hibernate 忽略關聯的這一端,把這端當作是另一端的一個鏡象
(mirror)。這就是所需的所有信息,Hibernate 利用這些信息來處理把一個有嚮導航模型轉移
到數據庫 schema 時的全部問題。你只須要記住這個直觀的規則:全部的雙向關聯須要有一端被
設置爲 inverse。在一對多關聯中它必須是表明多(many)的那端。而在多對多(many-to-many)
關聯中,你能夠任意選取一端,由於兩端之間並無差異。
1.3. 第三部分 - EventManager web 應用程序
Hibernate web 應用程序使用 Session 和 Transaction 的方式幾乎和獨立應用程序是同樣
的。可是,有一些常見的模式(pattern)很是有用。如今咱們編寫一個 EventManagerServlet。這
個 servlet 能夠列出數據庫中保存的全部的 events,還提供一個 HTML 表單來增長新的 events。
1.3.1. 編寫基本的 servlet
這個 servlet 只處理 HTTP GET 請求,所以,咱們要實現的是 doGet() 方法:
package org.hibernate.tutorial.web;
// Imports
public class EventManagerServlet extends HttpServlet {
protected void doGet(
HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
SimpleDateFormat dateFormatter = new SimpleDateFormat( "dd.MM.yyyy" );
try {
處理與渲染
21
// Begin unit of work
HibernateUtil.getSessionFactory().getCurrentSession().beginTransaction();
// Process request and render page...
// End unit of work
HibernateUtil.getSessionFactory().getCurrentSession().getTransaction().commit();
}
catch (Exception ex) {
HibernateUtil.getSessionFactory().getCurrentSession().getTransaction().rollback();
if ( ServletException.class.isInstance( ex ) ) {
throw ( ServletException ) ex;
}
else {
throw new ServletException( ex );
}
}
}
}
把這個 servlet 保存爲 src/main/java/org/hibernate/tutorial/web/EventManagerServlet.java。
咱們稱這裏應用的模式爲每次請求一個 session(session-per-request)。當有請求到達這個
servlet 的時候,經過對 SessionFactory 的第一次調用,打開一個新的 Hibernate Session。然
後啓動一個數據庫事務 — 全部的數據訪問都是在事務中進行,無論是讀仍是寫(咱們在應用程
序中不使用 auto-commit 模式)。
不要爲每次數據庫操做都使用一個新的 Hibernate Session。將 Hibernate Session 的範圍設置爲
整個請求。要用 getCurrentSession(),這樣它自動會綁定到當前 Java 線程。
下一步,對請求的可能動做進行處理,渲染出反饋的 HTML。咱們很快就會涉及到那部分。
最後,當處理與渲染都結束的時候,這個工做單元就結束了。倘若在處理或渲染的時候有任何錯
誤發生,會拋出一個異常,回滾數據庫事務。這樣,session-per-request 模式就完成了。爲了避
免在每一個 servlet 中都編寫事務邊界界定的代碼,能夠考慮寫一個 servlet 過濾器(filter)
來更好地解決。關於這一模式的更多信息,請參閱 Hibernate 網站和 Wiki,這一模式叫作 Open
Session in View — 只要你考慮用JSP來渲染你的視圖(view),而不是在servlet中,你就會很
快用到它。
1.3.2. 處理與渲染
咱們來實現處理請求以及渲染頁面的工做。
// Write HTML header
PrintWriter out = response.getWriter();
out.println("<html><head><title>Event Manager</title></head><body>");
// Handle actions
if ( "store".equals(request.getParameter("action")) ) {
String eventTitle = request.getParameter("eventTitle");
String eventDate = request.getParameter("eventDate");
第 1 章 教程
22
if ( "".equals(eventTitle) || "".equals(eventDate) ) {
out.println("<b><i>Please enter event title and date.</i></b>");
}
else {
createAndStoreEvent(eventTitle, dateFormatter.parse(eventDate));
out.println("<b><i>Added event.</i></b>");
}
}
// Print page
printEventForm(out);
listEvents(out, dateFormatter);
// Write HTML footer
out.println("</body></html>");
out.flush();
out.close();
必須認可,這種編碼風格把 Java 和 HTML 混在一塊兒,在更復雜的應用程序裏不該該大量使用 —
記住,在本章裏咱們僅僅是展現了 Hibernate 的基本概念。這段代碼打印出了 HTML 頁眉和頁
腳,在這個頁面裏,還打印了一個輸入 events 條目的表單單並列出了數據庫裏的有的 events。
第一個方法微不足道,僅僅是輸出 HTML:
private void printEventForm(PrintWriter out) {
out.println("<h2>Add new event:</h2>");
out.println("<form>");
out.println("Title: <input name='eventTitle' length='50'/><br/>");
out.println("Date (e.g. 24.12.2009): <input name='eventDate' length='10'/><br/>");
out.println("<input type='submit' name='action' value='store'/>");
out.println("</form>");
}
listEvents() 方法使用綁定到當前線程的 Hibernate Session 來執行查詢:
private void listEvents(PrintWriter out, SimpleDateFormat dateFormatter) {
List result = HibernateUtil.getSessionFactory()
.getCurrentSession().createCriteria(Event.class).list();
if (result.size() > 0) {
out.println("<h2>Events in database:</h2>");
out.println("<table border='1'>");
out.println("<tr>");
out.println("<th>Event title</th>");
out.println("<th>Event date</th>");
out.println("</tr>");
Iterator it = result.iterator();
while (it.hasNext()) {
Event event = (Event) it.next();
out.println("<tr>");
out.println("<td>" + event.getTitle() + "</td>");
out.println("<td>" + dateFormatter.format(event.getDate()) + "</td>");
out.println("</tr>");
部署與測試
23
}
out.println("</table>");
}
}
最後,store 動做會被導向到 createAndStoreEvent() 方法,它也使用當前線程的 Session:
protected void createAndStoreEvent(String title, Date theDate) {
Event theEvent = new Event();
theEvent.setTitle(title);
theEvent.setDate(theDate);
HibernateUtil.getSessionFactory()
.getCurrentSession().save(theEvent);
}
大功告成,這個 servlet 寫完了。Hibernate 會在單一的 Session 和 Transaction 中處理到
達的 servlet 請求。如同在前面的獨立應用程序中那樣,Hibernate 能夠自動的把這些對象綁
定到當前運行的線程中。這給了你用任何你喜歡的方式來對代碼分層及訪問 SessionFactory 的自
由。一般,你會用更加完備的設計,把數據訪問代碼轉移到數據訪問對象中(DAO 模式)。請參
見 Hibernate Wiki,那裏有更多的例子。
1.3.3. 部署與測試
要部署這個應用程序以進行測試,咱們必須出具一個 Web ARchive (WAR)。首先咱們必須定義 WAR
描述符爲 src/main/webapp/WEB-INF/web.xml。
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/webapp_2_4.xsd">
<servlet>
<servlet-name>Event Manager</servlet-name>
<servlet-class>org.hibernate.tutorial.web.EventManagerServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Event Manager</servlet-name>
<url-pattern>/eventmanager</url-pattern>
</servlet-mapping>
</web-app>
在你的開發目錄中,調用 ant war 來構建、打包,而後把 hibernate-tutorial.war 文件拷貝到你
的 tomcat 的 webapps 目錄下。倘若你還沒安裝 Tomcat,就去下載一個,按照指南來安裝。對此
應用的發佈,你不須要修改任何 Tomcat 的配置。
第 1 章 教程
24
注意
If you do not have Tomcat installed, download it from http://
tomcat.apache.org/ and follow the installation instructions. Our
application requires no changes to the standard Tomcat configuration.
在部署完,啓動 Tomcat 以後,經過 http://localhost:8080/hibernate-tutorial/eventmanager 進行
訪問你的應用,在第一次 servlet 請求發生時,請在 Tomcat log 中確認你看到 Hibernate 被
初始化了(HibernateUtil 的靜態初始化器被調用),倘若有任何異常拋出,也能夠看到詳細的輸
出。
1.4. 總結
本章覆蓋瞭如何編寫一個簡單獨立的 Hibernate 命令行應用程序及小型的 Hibernate web 應用
程序的基本要素。更多的教程能夠在 website [http://hibernate.org] 上找到。
第 2
25
體系結構(Architecture)
2.1. 概況(Overview)
下面的圖表提供了 Hibernate 體系結構的高層視圖:
Application
Persistent Objects
Database
HIBERNATE
hibernate.
properties XML Mapping
Unfortunately we cannot provide a detailed view of all possible runtime architectures.
Hibernate is sufficiently flexible to be used in a number of ways in many, many
architectures. We will, however, illustrate 2 specifically since they are extremes.
2.1.1. Minimal architecture
The "minimal" architecture has the application manage its own JDBC connections
and provide those connections to Hibernate; additionally the application manages
transactions for itself. This approach uses a minimal subset of Hibernate APIs.
第 2 章 體系結構(Architecture)
26
Transient Objects Application
Database
SessionFactory
Persistent
Objects
Session JDBC JNDI JTA
2.1.2. Comprehensive architecture
「全面解決」的體系結構方案,將應用層從底層的 JDBC/JTA API 中抽象出來,而讓 Hibernate
來處理這些細節。
Application
Database
SessionFactory
Session Transaction
TransactionFactory ConnectionProvider
JNDI JDBC JTA
Transient Objects
Persistent
Objects
Basic APIs
27
2.1.3. Basic APIs
Here are quick discussions about some of the API objects depicted in the preceding
diagrams (you will see them again in more detail in later chapters).
SessionFactory (org.hibernate.SessionFactory)
A thread-safe, immutable cache of compiled mappings for a single
database. A factory for org.hibernate.Session instances. A client of
org.hibernate.connection.ConnectionProvider. Optionally maintains a second level cache
of data that is reusable between transactions at a process or cluster level.
Session (org.hibernate.Session)
A single-threaded, short-lived object representing a conversation between the
application and the persistent store. Wraps a JDBC java.sql.Connection. Factory for
org.hibernate.Transaction. Maintains a first level cache of persistent the application's
persistent objects and collections; this cache is used when navigating the object
graph or looking up objects by identifier.
持久的對象及其集合
Short-lived, single threaded objects containing persistent state and business
function. These can be ordinary JavaBeans/POJOs. They are associated with exactly
one org.hibernate.Session. Once the org.hibernate.Session is closed, they will be
detached and free to use in any application layer (for example, directly as data
transfer objects to and from presentation). 第 11 章 與對象共事 discusses transient,
persistent and detached object states.
瞬態(transient)和脫管(detached)的對象及其集合
Instances of persistent classes that are not currently associated with a
org.hibernate.Session. They may have been instantiated by the application and not
yet persisted, or they may have been instantiated by a closed org.hibernate.Session.
第 11 章 與對象共事 discusses transient, persistent and detached object states.
Transaction (org.hibernate.Transaction)
(Optional) A single-threaded, short-lived object used by the application to specify
atomic units of work. It abstracts the application from the underlying JDBC, JTA or
CORBA transaction. A org.hibernate.Session might span several org.hibernate.Transactions
in some cases. However, transaction demarcation, either using the underlying API
or org.hibernate.Transaction, is never optional.
ConnectionProvider (org.hibernate.connection.ConnectionProvider)
(Optional) A factory for, and pool of, JDBC connections. It abstracts the application
from underlying javax.sql.DataSource or java.sql.DriverManager. It is not exposed to
application, but it can be extended and/or implemented by the developer.
TransactionFactory (org.hibernate.TransactionFactory)
(Optional) A factory for org.hibernate.Transaction instances. It is not exposed to
the application, but it can be extended and/or implemented by the developer.
第 2 章 體系結構(Architecture)
28
Extension Interfaces
Hibernate 提供了不少可選的擴展接口,你能夠經過實現它們來定製你的持久層的行爲。具
體請參考 API 文檔。
2.2. JMX 整合
JMX 是管理 Java 組件的 J2EE 標準。Hibernate 能夠經過一個 JMX 標準服務來管理。在這個發
行版本中,咱們提供了一個 MBean 接口的實現,即 org.hibernate.jmx.HibernateService。
Another feature available as a JMX service is runtime Hibernate statistics. See
第 3.4.6 節 「Hibernate 的統計(statistics)機制」 for more information.
2.3. 上下文相關的會話(Contextual Session)
使用 Hibernate 的大多數應用程序須要某種形式的「上下文相關的」會話,特定的會話在整個特
定的上下文範圍內始終有效。然而,對不一樣類型的應用程序而言,要爲何是組成這種「上下
文」下一個定義一般是困難的;不一樣的上下文對「當前」這個概念定義了不一樣的範圍。在 3.0 版
本以前,使用 Hibernate 的程序要麼採用自行編寫的基於 ThreadLocal 的上下文會話,要麼採用
HibernateUtil 這樣的輔助類,要麼採用第三方框架(好比 Spring 或 Pico),它們提供了基於代
理(proxy)或者基於攔截器(interception)的上下文相關的會話。
從 3.0.1 版本開始,Hibernate 增長了 SessionFactory.getCurrentSession() 方法。一開始,它假
定了採用 JTA 事務,JTA 事務定義了當前 session 的範圍和上下文(scope 和 context)。因
爲有好幾個獨立的 JTA TransactionManager 實現穩定可用,不管是否被部署到一個 J2EE 容器中,
大多數(倘若不是全部的)應用程序都應該採用 JTA 事務管理。基於這一點,採用 JTA 的上下文
相關的會話能夠知足你一切須要。
更好的是,從 3.1 開始,SessionFactory.getCurrentSession() 的後臺實現是可拔插的。所以,我
們引入了新的擴展接口(org.hibernate.context.CurrentSessionContext)和新的配置參數
(hibernate.current_session_context_class),以便對什麼是當前會話的範圍(scope)和上下文
(context)的定義進行拔插。
請參閱 org.hibernate.context.CurrentSessionContext 接口的 Javadoc,那裏有關於它的契約的詳
細討論。它定義了單一的方法,currentSession(),特定的實現用它來負責跟蹤當前的上下文相關
的會話。Hibernate 內置了此接口的三種實現:
•org.hibernate.context.JTASessionContext:當前會話根據 JTA 來跟蹤和界定。這和之前的僅支
持 JTA 的方法是徹底同樣的。詳情請參閱 Javadoc。
•org.hibernate.context.ThreadLocalSessionContext:當前會話經過當前執行的線程來跟蹤和界
定。詳情也請參閱 Javadoc。
•org.hibernate.context.ManagedSessionContext:當前會話經過當前執行的線程來跟蹤和界定。但
是,你須要負責使用這個類的靜態方法將 Session 實例綁定、或者取消綁定,它並不會打開
(open)、flush 或者關閉(close)任何 Session。
The first two implementations provide a "one session - one database transaction"
programming model. This is also known and used as session-per-request. The beginning
上下文相關的會話(Contextual Session)
29
and end of a Hibernate session is defined by the duration of a database transaction. If
you use programmatic transaction demarcation in plain JSE without JTA, you are advised
to use the Hibernate Transaction API to hide the underlying transaction system from your
code. If you use JTA, you can utilize the JTA interfaces to demarcate transactions. If
you execute in an EJB container that supports CMT, transaction boundaries are defined
declaratively and you do not need any transaction or session demarcation operations in
your code. Refer to 第 13 章 事務和併發 for more information and code examples.
hibernate.current_session_context_class 配置參數定義了應該採用哪一個
org.hibernate.context.CurrentSessionContext 實現。注意,爲了向下兼容,若是未配置
此參數,可是存在 org.hibernate.transaction.TransactionManagerLookup 的配
置,Hibernate 會採用org.hibernate.context.JTASessionContext。通常而言,此參數的值指明瞭要
使用的實現類的全名,但那三種內置的實現能夠使用簡寫,即 "jta"、"thread" 和 "managed"。
30
第 3
31
配置
因爲 Hibernate 是爲了能在各類不一樣環境下工做而設計的,所以存在着大量的配置參數。
幸運的是多數配置參數都有比較直觀的默認值,並有隨 Hibernate 一同分發的配置樣例
hibernate.properties(位於 etc/)來展現各類配置選項。所需作的僅僅是將這個樣例文件複製到
類路徑(classpath)下並進行定製。
3.1. 可編程的配置方式
org.hibernate.cfg.Configuration 實例表明了一個應用程序中 Java 類型
到SQL數據庫映射的完整集合。org.hibernate.cfg.Configuration 被用來構建一個(不可變的
(immutable))org.hibernate.SessionFactory。映射定義則由不一樣的 XML 映射定義文件編譯而
來。
你能夠直接實例化 org.hibernate.cfg.Configuration 來獲取一個實例,併爲它指定 XML 映射定義
文件。若是映射定義文件在類路徑(classpath)中,請使用 addResource()。例如:
Configuration cfg = new Configuration()
.addResource("Item.hbm.xml")
.addResource("Bid.hbm.xml");
一個替代方法(有時是更好的選擇)是,指定被映射的類,讓 Hibernate 幫你尋找映射定義文
件:
Configuration cfg = new Configuration()
.addClass(org.hibernate.auction.Item.class)
.addClass(org.hibernate.auction.Bid.class);
Hibernate 將會在類路徑(classpath)中尋找名字爲 /org/hibernate/auction/Item.hbm.xml
和 /org/hibernate/auction/Bid.hbm.xml 映射定義文件。這種方式消除了任何對文件名的硬編碼
(hardcoded)。
org.hibernate.cfg.Configuration > 也容許你指定配置屬性。例如:
Configuration cfg = new Configuration()
.addClass(org.hibernate.auction.Item.class)
.addClass(org.hibernate.auction.Bid.class)
.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQLInnoDBDialect")
.setProperty("hibernate.connection.datasource", "java:comp/env/jdbc/test")
.setProperty("hibernate.order_updates", "true");
固然這不是惟一的傳遞 Hibernate 配置屬性的方式,其餘可選方式還包括:
1. 傳一個 java.util.Properties 實例給 Configuration.setProperties()。
第 3 章 配置
32
2. 將 hibernate.properties 放置在類路徑(classpath)的根目錄下(root directory)。
3. 經過 java -Dproperty=value 來設置系統(System)屬性。
4. 在 hibernate.cfg.xml 中加入元素 <property>(稍後討論)。
若是你想快速上路,hibernate.properties 就是最容易的途徑。
org.hibernate.cfg.Configuration 實例被設計成啓動期間(startup-time)對象,一旦
SessionFactory 建立完成它就被丟棄了。
3.2. 得到 SessionFactory
當全部映射定義被 org.hibernate.cfg.Configuration 解析後,應用程序必須得到一個用於構造
org.hibernate.Session 實例的工廠。這個工廠將被應用程序的全部線程共享:
SessionFactory sessions = cfg.buildSessionFactory();
Hibernate 容許你的應用程序建立多個 org.hibernate.SessionFactory 實例。這對 使用多個數據
庫的應用來講頗有用。
3.3. JDBC 鏈接
一般你但願 org.hibernate.SessionFactory 來爲你建立和緩存(pool)JDBC 鏈接。若是你採用這
種方式,只須要以下例所示那樣,打開一個 org.hibernate.Session:
Session session = sessions.openSession(); // open a new Session
一旦你須要進行數據訪問時,就會從鏈接池(connection pool)得到一個 JDBC 鏈接。
爲了使這種方式工做起來,咱們須要向 Hibernate 傳遞一些 JDBC 鏈接的屬性。全部 Hibernate
屬性的名字和語義都在 org.hibernate.cfg.Environment 中定義。咱們如今將描述 JDBC 鏈接配置
中最重要的設置。
若是你設置以下屬性,Hibernate 將使用 java.sql.DriverManager 來得到(和緩存)JDBC 鏈接:
表 3.1. Hibernate JDBC 屬性
屬性名 用途
hibernate.connection.driver_class JDBC driver class
hibernate.connection.url JDBC URL
hibernate.connection.username database user
hibernate.connection.password 數據庫用戶密碼
hibernate.connection.pool_size maximum number of pooled connections
JDBC 鏈接
33
但 Hibernate 自帶的鏈接池算法至關不成熟。它只是爲了讓你快些上手,並不適合用於產品系
統或性能測試中。 出於最佳性能和穩定性考慮你應該使用第三方的鏈接池。只須要用特定鏈接池
的設置替換 hibernate.connection.pool_size 便可。這將關閉 Hibernate 自帶的鏈接池。例如,
你可能會想用 C3P0。
C3P0 是一個隨 Hibernate 一同分發的開源的 JDBC 鏈接池,它位於 lib目錄下。 若是你設置了
hibernate.c3p0.* 相關的屬性,Hibernate將使用 C3P0ConnectionProvider 來緩存 JDBC 鏈接。如
果你更原意使用 Proxool,請參考發行包中的 hibernate.properties 併到 Hibernate 網站獲取更
多的信息。
這是一個使用 C3P0 的 hibernate.properties 樣例文件:
hibernate.connection.driver_class = org.postgresql.Driver
hibernate.connection.url = jdbc:postgresql://localhost/mydatabase
hibernate.connection.username = myuser
hibernate.connection.password = secret
hibernate.c3p0.min_size=5
hibernate.c3p0.max_size=20
hibernate.c3p0.timeout=1800
hibernate.c3p0.max_statements=50
hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect
爲了能在應用程序服務器(application server)中使用 Hibernate,應當老是將 Hibernate 配
置成從註冊在 JNDI 中的 Datasource 處得到鏈接,你至少須要設置下列屬性中的一個:
表 3.2. Hibernate 數據源屬性
屬性名 用途
hibernate.connection.datasource 數據源 JNDI 名字
hibernate.jndi.url JNDI 提供者的 URL(可選)
hibernate.jndi.class JNDI InitialContextFactory 類(可選)
hibernate.connection.username 數據庫用戶(可選)
hibernate.connection.password 數據庫密碼(可選)
這是一個使用應用程序服務器提供的 JNDI 數據源的 hibernate.properties 樣例文件:
hibernate.connection.datasource = java:/comp/env/jdbc/test
hibernate.transaction.factory_class = \
org.hibernate.transaction.JTATransactionFactory
hibernate.transaction.manager_lookup_class = \
org.hibernate.transaction.JBossTransactionManagerLookup
hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect
從 JNDI 數據源得到的 JDBC 鏈接將自動參與到應用程序服務器中容器管理的事務(containermanaged
transactions)中去。
第 3 章 配置
34
任何鏈接(connection)屬性的屬性名都要以 "hibernate.connnection" 開頭。例如,你可能會使
用 hibernate.connection.charSet 來指定 charSet 鏈接屬性。
經過實現 org.hibernate.connection.ConnectionProvider 接口,你能夠定義屬於你本身的得到JDBC
鏈接的插件策略。經過設置hibernate.connection.provider_class,你能夠選擇一個自定義的實
現。
3.4. 可選的配置屬性
有大量屬性能用來控制 Hibernate 在運行期的行爲。它們都是可選的,並擁有適當的默認值。
警告
其中一些屬性是"系統級(system-level)的"。系統級屬性只能經過java -
Dproperty=value 或 hibernate.properties 來設置,而不能用上面描述的其餘方法來
設置。
表 3.3. Hibernate 配置屬性
屬性名 用途
hibernate.dialect 容許 Hibernate 針對特定的關係數據庫生成優
化的 SQL 的 org.hibernate.dialect.Dialect 的
類名。
例如:full.classname.of.Dialect
在大多數狀況下,Hibernate 能夠根據 JDBC
驅動返回的 JDBC metadata 選擇正確的
org.hibernate.dialect.Dialect 實現。
hibernate.show_sql 輸出全部 SQL 語句到控制檯。有一個另外的選
擇是把 org.hibernate.SQL 這個 log category
設爲 debug。
例如:true | false
hibernate.format_sql 在 log 和 console 中打印出更漂亮的 SQL。
例如:true | false
hibernate.default_schema 在生成的 SQL 中,將給定的 schema/
tablespace 附加於非全限定名的表名上。
例如:SCHEMA_NAME
hibernate.default_catalog 在生成的 SQL 中,將給定的 catalog 附加於
非全限定名的表名上。
例如:CATALOG_NAME
可選的配置屬性
35
屬性名 用途
hibernate.session_factory_name org.hibernate.SessionFactory 建立後,將自動
使用這個名字綁定到 JNDI 中。
例如:jndi/composite/name
hibernate.max_fetch_depth 爲單向關聯(一對一,多對一)的外鏈接抓取
(outer join fetch)樹設置最大深度。值爲
0 意味着將關閉默認的外鏈接抓取。
例如: 建議在 0 到 3 之間取值
hibernate.default_batch_fetch_size 爲 Hibernate 關聯的批量抓取設置默認數量。
例如:建議的取值爲 4,8,和 16
hibernate.default_entity_mode 爲由這個 SessionFactory 打開的全部 Session
指定默認的實體表現模式。
取值dynamic-map,dom4j,pojo
hibernate.order_updates 強制 Hibernate 按照被更新數據的主鍵,爲
SQL 更新排序。這麼作將減小在高併發系統中
事務的死鎖。
例如:true | false
hibernate.generate_statistics 若是開啓,Hibernate 將收集有助於性能調節
的統計數據。
例如:true | false
hibernate.use_identifier_rollback 若是開啓,在對象被刪除時生成的標識屬性將
被重設爲默認值。
例如:true | false
hibernate.use_sql_comments 若是開啓,Hibernate 將在 SQL 中生成有助於
調試的註釋信息,默認值爲 false。
例如:true | false
hibernate.id.new_generator_mappings Setting is relevant when using
@GeneratedValue. It indicates whether
or not the new IdentifierGenerator
implementations are used
for javax.persistence.GenerationType.AUTO,
javax.persistence.GenerationType.TABLE and
javax.persistence.GenerationType.SEQUENCE.
Default to false to keep backward
compatibility.
例如:true | false
第 3 章 配置
36
注意
We recommend all new projects which make use of to use @GeneratedValue to
also set hibernate.id.new_generator_mappings=true as the new generators are
more efficient and closer to the JPA 2 specification semantic. However
they are not backward compatible with existing databases (if a sequence
or a table is used for id generation).
表 3.4. Hibernate JDBC 和鏈接(connection)屬性
屬性名 用途
hibernate.jdbc.fetch_size 非零值,指定 JDBC 抓取數量的大小(調用
Statement.setFetchSize())。
hibernate.jdbc.batch_size 非零值,容許 Hibernate 使用 JDBC2 的批量
更新。
例如:建議取 5 到 30 之間的值
hibernate.jdbc.batch_versioned_data Set this property to true if your JDBC
driver returns correct row counts from
executeBatch(). It is usually safe to turn
this option on. Hibernate will then use
batched DML for automatically versioned
data. Defaults to false.
例如:true | false
hibernate.jdbc.factory_class 選擇一個自定義的 Batcher。多數應用程序不
須要這個配置屬性。
例如:classname.of.Batcher
hibernate.jdbc.use_scrollable_resultset 容許 Hibernate 使用 JDBC2 的可滾動結果集。
只有在使用用戶提供的 JDBC 鏈接時,這個選
項纔是必要的,不然 Hibernate 會使用鏈接的
元數據。
例如:true | false
hibernate.jdbc.use_streams_for_binary 在 JDBC 讀寫 binary 或 serializable 的類型
時使用流(stream)(系統級屬性)。
例如:true | false
hibernate.jdbc.use_get_generated_keys 在數據插入數據庫以後,容許使用 JDBC3
PreparedStatement.getGeneratedKeys() 來獲取
數據庫生成的 key(鍵)。須要 JDBC3+ 驅
動和 JRE1.4+,若是你的數據庫驅動在使用
可選的配置屬性
37
屬性名 用途
Hibernate 的標識生成器時遇到問題,請將此
值設爲 false。默認狀況下將使用鏈接的元數
據來斷定驅動的能力。
例如:true | false
hibernate.connection.provider_class 自定義 ConnectionProvider 的類名,此類用來
向 Hibernate 提供 JDBC 鏈接。
例如:classname.of.ConnectionProvider
hibernate.connection.isolation 設置 JDBC 事務隔離級別。查看
java.sql.Connection 來了解各個值的具體意
義,但請注意多數數據庫都不支持全部的隔離
級別。
例如:1, 2, 4, 8
hibernate.connection.autocommit 容許被緩存的 JDBC 鏈接開啓自動提交
(autocommit)(不推薦)。
例如:true | false
hibernate.connection.release_mode 指定 Hibernate 在什麼時候釋放 JDBC 連
接。默認狀況下,直到 Session 被顯式關閉
或被斷開鏈接時,纔會釋放 JDBC 鏈接。對於
應用程序服務器的 JTA 數據源,你應當使用
after_statement,這樣在每次 JDBC 調用後,
都會主動的釋放鏈接。對於非 JTA 的鏈接,使
用 after_transaction 在每一個事務結束時釋放連
接是合理的。auto 將爲 JTA 和 CMT 事務策
略選擇 after_statement,爲JDBC事務策略選擇
after_transaction。
例如:auto (默認) | on_close |
after_transaction | after_statement
This setting only affects Sessions
returned from SessionFactory.openSession.
For Sessions obtained through
SessionFactory.getCurrentSession, the
CurrentSessionContext implementation
configured for use controls the connection
release mode for those Sessions. See
第  2.3 節 「上下文相關的會話(Contextual
Session)」
第 3 章 配置
38
屬性名 用途
hibernate.connection.<propertyName> 把 JDBC 屬性 <propertyName> 傳遞給
DriverManager.getConnection()。
hibernate.jndi.<propertyName> 把 <propertyName> 屬性傳遞給 JNDI
InitialContextFactory。
表 3.5. Hibernate 緩存屬性
屬性名 用途
hibernate.cache.provider_class 自定義的 CacheProvider 的類名。
例如:classname.of.CacheProvider
hibernate.cache.use_minimal_puts 以頻繁的讀操做爲代價,優化二級緩存來最小
化寫操做。在 Hibernate3 中,這個設置對的
集羣緩存很是有用,對集羣緩存的實現而言,
默認是開啓的。
例如:true | false
hibernate.cache.use_query_cache 容許查詢緩存,個別查詢仍然須要被設置爲可
緩存的。
例如:true | false
hibernate.cache.use_second_level_cache 能用來徹底禁止使用二級緩存。對那些在類的
映射定義中指定 <cache> 的類,會默認開啓二
級緩存。
例如:true | false
hibernate.cache.query_cache_factory 自定義實現 QueryCache 接口的類名,默認爲內
建的 StandardQueryCache。
例如:classname.of.QueryCache
hibernate.cache.region_prefix 二級緩存區域名的前綴。
例如:prefix
hibernate.cache.use_structured_entries 強制 Hibernate 以更人性化的格式將數據存入
二級緩存。
例如:true | false
hibernate.cache.default_cache_concurrency_strategy Setting used to give
the name of the default
org.hibernate.annotations.CacheConcurrencyStrategy
to use when either @Cacheable or @Cache
is used. @Cache(strategy="..") is used to
override this default.
可選的配置屬性
39
表 3.6. Hibernate 事務屬性
屬性名 用途
hibernate.transaction.factory_class 一個 TransactionFactory 的類名,用
於 Hibernate Transaction API(默認爲
JDBCTransactionFactory)。
例如:classname.of.TransactionFactory
jta.UserTransaction 一個 JNDI 名字,被 JTATransactionFactory 用
來從應用服務器獲取 JTA UserTransaction。
例如:jndi/composite/name
hibernate.transaction.manager_lookup_class 一個 TransactionManagerLookup 的類名 — 當使
用 JVM 級緩存,或在 JTA 環境中使用 hilo 生
成器的時候須要該類。
例如:classname.of.TransactionManagerLookup
hibernate.transaction.flush_before_completion If enabled, the session will be
automatically flushed during the before
completion phase of the transaction.
Built-in and automatic session context
management is preferred, see 第 2.3 節 「上
下文相關的會話(Contextual Session)」.
例如:true | false
hibernate.transaction.auto_close_session If enabled, the session will be
automatically closed during the after
completion phase of the transaction.
Built-in and automatic session context
management is preferred, see 第 2.3 節 「上
下文相關的會話(Contextual Session)」.
例如:true | false
表 3.7. 其餘屬性
屬性名 用途
hibernate.current_session_context_class Supply a custom strategy for the scoping of
the "current" Session. See 第 2.3 節 「上下
文相關的會話(Contextual Session)」 for
more information about the built-in
strategies.
例如:jta | thread | managed | custom.Class
hibernate.query.factory_class 選擇 HQL 解析器的實現。
第 3 章 配置
40
屬性名 用途

如:org.hibernate.hql.ast.ASTQueryTranslatorFactory

org.hibernate.hql.classic.ClassicQueryTranslatorFactory
hibernate.query.substitutions 將 Hibernate 查詢中的符號映射到 SQL 查詢
中的符號(符號多是函數名或常量名字)。
例如:hqlLiteral=SQL_LITERAL,
hqlFunction=SQLFUNC
hibernate.hbm2ddl.auto 在 SessionFactory 建立時,自動檢查數據庫
結構,或者將數據庫 schema 的 DDL 導出
到數據庫。使用 create-drop 時,在顯式關閉
SessionFactory 時,將刪除掉數據庫 schema。
例如:validate | update | create | createdrop
hibernate.hbm2ddl.import_files Comma-separated names of the optional
files containing SQL DML statements
executed during the SessionFactory
creation. This is useful for testing or
demoing: by adding INSERT statements for
example you can populate your database
with a minimal set of data when it is
deployed.
File order matters, the statements of
a give file are executed before the
statements of the following files. These
statements are only executed if the schema
is created ie if hibernate.hbm2ddl.auto is
set to create or create-drop.
e.g. /humans.sql,/dogs.sql
hibernate.bytecode.use_reflection_optimizer Enables the use of bytecode manipulation
instead of runtime reflection. This is a
System-level property and cannot be set in
hibernate.cfg.xml. Reflection can sometimes
be useful when troubleshooting. Hibernate
always requires either CGLIB or javassist
even if you turn off the optimizer.
例如:true | false
SQL 方言
41
屬性名 用途
hibernate.bytecode.provider Both javassist or cglib can be used as
byte manipulation engines; the default is
javassist.
e.g. javassist | cglib
3.4.1. SQL 方言
你應當老是爲你的數據庫將 hibernate.dialect 屬性設置成正確的 org.hibernate.dialect.Dialect
子類。若是你指定一種方言,Hibernate 將爲上面列出的一些屬性使用合理的默認值,這樣你就
不用手工指定它們。
表 3.8. Hibernate SQL 方言(hibernate.dialect)
RDBMS Dialect
DB2 org.hibernate.dialect.DB2Dialect
DB2 AS/400 org.hibernate.dialect.DB2400Dialect
DB2 OS390 org.hibernate.dialect.DB2390Dialect
PostgreSQL org.hibernate.dialect.PostgreSQLDialect
MySQL5 org.hibernate.dialect.MySQL5Dialect
MySQL5 with InnoDB org.hibernate.dialect.MySQL5InnoDBDialect
MySQL with MyISAM org.hibernate.dialect.MySQLMyISAMDialect
Oracle(any version) org.hibernate.dialect.OracleDialect
Oracle 9i org.hibernate.dialect.Oracle9iDialect
Oracle 10g org.hibernate.dialect.Oracle10gDialect
Oracle 11g org.hibernate.dialect.Oracle10gDialect
Sybase org.hibernate.dialect.SybaseASE15Dialect
Sybase Anywhere org.hibernate.dialect.SybaseAnywhereDialect
Microsoft SQL Server 2000 org.hibernate.dialect.SQLServerDialect
Microsoft SQL Server 2005 org.hibernate.dialect.SQLServer2005Dialect
Microsoft SQL Server 2008 org.hibernate.dialect.SQLServer2008Dialect
SAP DB org.hibernate.dialect.SAPDBDialect
Informix org.hibernate.dialect.InformixDialect
HypersonicSQL org.hibernate.dialect.HSQLDialect
H2 Database org.hibernate.dialect.H2Dialect
Ingres org.hibernate.dialect.IngresDialect
Progress org.hibernate.dialect.ProgressDialect
Mckoi SQL org.hibernate.dialect.MckoiDialect
第 3 章 配置
42
RDBMS Dialect
Interbase org.hibernate.dialect.InterbaseDialect
Pointbase org.hibernate.dialect.PointbaseDialect
FrontBase org.hibernate.dialect.FrontbaseDialect
Firebird org.hibernate.dialect.FirebirdDialect
3.4.2. 外鏈接抓取(Outer Join Fetching)
若是你的數據庫支持 ANSI、Oracle 或 Sybase 風格的外鏈接,外鏈接抓取一般能經過限制往返數
據庫次數(更多的工做交由數據庫本身來完成)來提升效率。外鏈接抓取容許在單個 SELECT SQL
語句中, 經過 many-to-one、one-to-many、many-to-many 和 one-to-one 關聯獲取鏈接對象的
整個對象圖。
將 hibernate.max_fetch_depth 設爲 0 能在全局 範圍內禁止外鏈接抓取。設爲 1 或更高值能啓用
one-to-one 和 many-to-oneouter 關聯的外鏈接抓取,它們經過 fetch="join" 來映射。
See 第 21.1 節 「抓取策略(Fetching strategies)」 for more information.
3.4.3. 二進制流(Binary Streams)
Oracle 限制那些經過 JDBC 驅動傳輸的字節數組的數目。若是你但願使用二進值(binary)或 可
序列化的(serializable)類型的大對象,你應該開啓 hibernate.jdbc.use_streams_for_binary 屬
性。這是系統級屬性。
3.4.4. 二級緩存與查詢緩存
The properties prefixed by hibernate.cache allow you to use a process or cluster scoped
second-level cache system with Hibernate. See the 第 21.2 節 「二級緩存(The Second
Level Cache)」 for more information.
3.4.5. 查詢語言中的替換
你能夠使用 hibernate.query.substitutions 在 Hibernate 中定義新的查詢符號。例如:
hibernate.query.substitutions true=1#false=0
將致使符號 true 和 false 在生成的 SQL 中被翻譯成整數常量。
hibernate.query.substitutions toLowercase=LOWER
將容許你重命名 SQL 中的 LOWER 函數。
Hibernate 的統計(statistics)機制
43
3.4.6. Hibernate 的統計(statistics)機制
若是你開啓 hibernate.generate_statistics,那麼當你經過 SessionFactory.getStatistics() 調整
正在運行的系統時,Hibernate 將導出大量有用的數據。Hibernate 甚至能被配置成經過 JMX 導
出這些統計信息。參考 org.hibernate.stats 中接口的 Javadoc,以得到更多信息。
3.5. 日誌
Hibernate 利用 Simple Logging Facade for Java [http://www.slf4j.org/]
(SLF4J) 來記錄不一樣系統事件的日誌。SLF4J 能夠根據你選擇的綁定把日誌輸出到幾個日誌框架
(NOP、Simple、log4j version 1.二、JDK 1.4 logging、JCL 或 logback)上。爲了設置日誌,
你須要在 classpath 里加入 slf4j-api.jar 和你選擇的綁定的 JAR 文件(使用 Log4J 時加入
slf4j-log4j12.jar)。更多的細節請參考 SLF4J 文檔 [http://www.slf4j.org/manual.html]。要
使用 Log4j,你也須要在 classpath 里加入 log4j.properties 文件。Hibernate 裏的 src/ 目錄
裏帶有一個屬性文件的例子。
咱們強烈建議你熟悉一下 Hibernate 的日誌消息。在不失可讀性的前提下,咱們作了不少工做,
使 Hibernate 的日誌可能地詳細。這是必要的查錯利器。最使人感興趣的日誌分類有以下這些:
表 3.9. Hibernate 日誌類別
類別 功能
org.hibernate.SQL 在全部 SQL DML 語句被執行時爲它們記錄日誌
org.hibernate.type 爲全部 JDBC 參數記錄日誌
org.hibernate.tool.hbm2ddl 在全部 SQL DDL 語句執行時爲它們記錄日誌
org.hibernate.pretty 在 session 清洗(flush)時,爲全部與其關聯的實體(最多 20
個)的狀態記錄日誌
org.hibernate.cache 爲全部二級緩存的活動記錄日誌
org.hibernate.transaction 爲事務相關的活動記錄日誌
org.hibernate.jdbc 爲全部 JDBC 資源的獲取記錄日誌
org.hibernate.hql.ast.AST 在解析查詢的時候,記錄 HQL 和 SQL 的 AST 分析日誌
org.hibernate.secure 爲 JAAS 認證請求作日誌
org.hibernate 爲任何 Hibernate 相關信息記錄日誌(信息量較大,但對查錯很是
有幫助)
在使用 Hibernate 開發應用程序時,你應當老是爲 org.hibernate.SQL 開啓 debug 級別的日誌記
錄,或者開啓 hibernate.show_sql 屬性。
3.6. 實現 NamingStrategy
org.hibernate.cfg.NamingStrategy 接口容許你爲數據庫中的對象和 schema 元素指定一個「命名標
準」。
第 3 章 配置
44
你可能會提供一些經過 Java 標識生成數據庫標識或將映射定義文件中"邏輯"表/列名處理成"物
理"表/列名的規則。這個特性有助於減小冗長的映射定義文件,消除重複內容(如 TBL_ 前
綴)。Hibernate 使用的缺省策略是至關精簡的。
在加入映射定義前,你能夠調用 Configuration.setNamingStrategy() 指定一個不一樣的命名策略:
SessionFactory sf = new Configuration()
.setNamingStrategy(ImprovedNamingStrategy.INSTANCE)
.addFile("Item.hbm.xml")
.addFile("Bid.hbm.xml")
.buildSessionFactory();
org.hibernate.cfg.ImprovedNamingStrategy 是一個內建的命名策略,對一些應用程序而言,多是
很是有用的起點。
3.7. Implementing a PersisterClassProvider
You can configure the persister implementation used to persist your entities and
collections:
•by default, Hibernate uses persisters that make sense in a relational model and
follow Java Persistence's specification
•you can define a PersisterClassProvider implementation that provides the persister
class used of a given entity or collection
•finally, you can override them on a per entity and collection basis in the mapping
using @Persister or its XML equivalent
The latter in the list the higher in priority.
You can pass the PersisterClassProvider instance to the Configuration object.
SessionFactory sf = new Configuration()
.setPersisterClassProvider(customPersisterClassProvider)
.addAnnotatedClass(Order.class)
.buildSessionFactory();
The persister class provider methods, when returning a non null persister class,
override the default Hibernate persisters. The entity name or the collection role are
passed to the methods. It is a nice way to centralize the overriding logic of the
persisters instead of spreading them on each entity or collection mapping.
3.8. XML 配置文件
另外一個配置方法是在 hibernate.cfg.xml 文件中指定一套完整的配置。這個文件能夠當成
hibernate.properties 的替代。若兩個文件同時存在,它將覆蓋前者的屬性。
XML 配置文件
45
XML 配置文件被默認是放在 CLASSPATH 的根目錄下。下面是一個例子:
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<!-- a SessionFactory instance listed as /jndi/name -->
<session-factory
name="java:hibernate/SessionFactory">
<!-- properties -->
<property name="connection.datasource">java:/comp/env/jdbc/MyDB</property>
<property name="dialect">org.hibernate.dialect.MySQLDialect</property>
<property name="show_sql">false</property>
<property name="transaction.factory_class">
org.hibernate.transaction.JTATransactionFactory
</property>
<property name="jta.UserTransaction">java:comp/UserTransaction</property>
<!-- mapping files -->
<mapping resource="org/hibernate/auction/Item.hbm.xml"/>
<mapping resource="org/hibernate/auction/Bid.hbm.xml"/>
<!-- cache settings -->
<class-cache class="org.hibernate.auction.Item" usage="read-write"/>
<class-cache class="org.hibernate.auction.Bid" usage="read-only"/>
<collection-cache collection="org.hibernate.auction.Item.bids" usage="read-write"/>
</session-factory>
</hibernate-configuration>
如你所見,這個方法優點在於,在配置文件中指出了映射定義文件的名字。一旦你須要調整
Hibernate 的緩存,hibernate.cfg.xml 也是更方便。注意,使用 hibernate.properties 仍是
hibernate.cfg.xml 徹底是由你來決定,除了上面提到的 XML 語法的優點以外,二者是等價的。
使用 XML 配置,使得啓動 Hibernate 變的異常簡單:
SessionFactory sf = new Configuration().configure().buildSessionFactory();
你能夠使用以下代碼來添加一個不一樣的 XML 配置文件:
SessionFactory sf = new Configuration()
.configure("catdb.cfg.xml")
.buildSessionFactory();
第 3 章 配置
46
3.9. Java EE Application Server integration
針對 J2EE 體系,Hibernate 有以下幾個集成的方面:
•容器管理的數據源(Container-managed datasources): Hibernate 能使用經過容器管理,
並由 JNDI 提供的 JDBC 鏈接。一般,特別是當處理多個數據源的分佈式事務的時候,由一個
JTA 兼容的 TransactionManager 和一個 ResourceManager 來處理事務管理(CMT,容器管理的事
務)。固然你能夠經過 編程方式來劃分事務邊界(BMT,Bean 管理的事務)。或者爲了代碼的
可移植性,你也也許會想使用可選的 Hibernate Transaction API。
•自動 JNDI 綁定:Hibernate 能夠在啓動後將 SessionFactory 綁定到 JNDI。
•JTA Session 綁定: Hibernate Session 能夠自動綁定到 JTA 事務做用的範圍。只需簡單地
從 JNDI 查找 SessionFactory 並得到當前的 Session。當 JTA 事務完成時,讓 Hibernate來
處理 Session 的清洗(flush)與關閉。事務的劃分能夠是聲明式的(CMT),也能夠是編程式的
(BMT/UserTransaction)。
•JMX 部署: 若是你使用支持 JMX 應用程序服務器(如,JBoss AS),那麼你能夠選擇將
Hibernate 部署成託管 MBean。這將爲你省去一行從Configuration 構建 SessionFactory 的啓
動代碼。容器將啓動你的 HibernateService,並完美地處理好服務間的依賴關係(在 Hibernate
啓動前,數據源必須是可用的,等等)。
若是應用程序服務器拋出 "connection containment" 異常,根據你的環境,也許該將配置屬性
hibernate.connection.release_mode 設爲 after_statement。
3.9.1. 事務策略配置
在你的架構中,Hibernate 的 Session API 是獨立於任何事務分界系統的。若是你讓 Hibernate
經過鏈接池直接使用 JDBC,你須要調用 JDBC API 來打開和關閉你的事務。若是你運行在
J2EE 應用程序服務器中,你也許想用 Bean 管理的事務並在須要的時候調用 JTA API 和
UserTransaction。
爲了讓你的代碼在兩種(或其餘)環境中能夠移植,咱們建議使用可選的 Hibernate Transaction
API,它包裝並隱藏了底層系統。你必須經過設置 Hibernate 配置屬性
hibernate.transaction.factory_class 來指定一個 Transaction 實例的工廠類。
有三個標準(內建)的選擇:
org.hibernate.transaction.JDBCTransactionFactory
委託給數據庫(JDBC)事務(默認)
org.hibernate.transaction.JTATransactionFactory
若是在上下文環境中存在運行着的事務(如,EJB 會話 Bean 的方法),則委託給容器管理的
事務。不然,將啓動一個新的事務,並使用 Bean 管理的事務。
JNDI 綁定的 SessionFactory
47
org.hibernate.transaction.CMTTransactionFactory
委託給容器管理的 JTA 事務
你也能夠定義屬於你本身的事務策略(如,針對 CORBA 的事務服務)。
Hibernate 的一些特性(好比二級緩存,Contextual Sessions with JTA 等等)須要訪問在託管
環境中的 JTA TransactionManager。因爲 J2EE 沒有標準化一個單一的機制,Hibernate 在應用程
序服務器中,你必須指定 Hibernate 如何得到 TransactionManager 的引用:
表 3.10. JTA TransactionManagers
Transaction 工廠類 應用程序服務器
org.hibernate.transaction.JBossTransactionManagerLookup JBoss AS
org.hibernate.transaction.WeblogicTransactionManagerLookup Weblogic
org.hibernate.transaction.WebSphereTransactionManagerLookup WebSphere
org.hibernate.transaction.WebSphereExtendedJTATransactionLookup WebSphere 6
org.hibernate.transaction.OrionTransactionManagerLookup Orion
org.hibernate.transaction.ResinTransactionManagerLookup Resin
org.hibernate.transaction.JOTMTransactionManagerLookup JOTM
org.hibernate.transaction.JOnASTransactionManagerLookup JOnAS
org.hibernate.transaction.JRun4TransactionManagerLookup JRun4
org.hibernate.transaction.BESTransactionManagerLookup Borland ES
org.hibernate.transaction.JBossTSStandaloneTransactionManagerLookup JBoss TS used
standalone (ie.
outside JBoss AS and
a JNDI environment
generally).
Known to work for
org.jboss.jbossts:jbossjta:4.11.0.Final
3.9.2. JNDI 綁定的 SessionFactory
與 JNDI 綁定的 Hibernate 的 SessionFactory 能簡化工廠的查詢,簡化建立新的 Session。須要
注意的是這與 JNDI 綁定 Datasource 沒有關係,它們只是恰巧用了相同的註冊表。
若是你但願將 SessionFactory 綁定到一個 JNDI 的名字空間,用屬性
hibernate.session_factory_name 指定一個名字(如,java:hibernate/SessionFactory)。若是不設
置這個屬性,SessionFactory 將不會被綁定到 JNDI 中(在以只讀 JNDI 爲默認實現的環境中,這
個設置尤爲有用,如 Tomcat)。
在將 SessionFactory 綁定至 JNDI 時,Hibernate 將使用 hibernate.jndi.url,和
hibernate.jndi.class 的值來實例化初始環境(initial context)。若是它們沒有被指定,將使
用默認的 InitialContext。
第 3 章 配置
48
在你調用 cfg.buildSessionFactory()後,Hibernate 會自動將 SessionFactory 註冊
到 JNDI。這意味這你至少須要在你應用程序的啓動代碼(或工具類)中完成這個調用,除非你使
用 HibernateService 來作 JMX 部署(見後面討論)。
倘若你使用 JNDI SessionFactory,EJB 或者任何其它類均可以從 JNDI 中找到此 SessionFactory。
It is recommended that you bind the SessionFactory to JNDI in a managed environment and
use a static singleton otherwise. To shield your application code from these details,
we also recommend to hide the actual lookup code for a SessionFactory in a helper class,
such as HibernateUtil.getSessionFactory(). Note that such a class is also a convenient
way to startup Hibernate—see chapter 1.
3.9.3. 在 JTA 環境下使用 Current Session context(當前
session 上下文)管理
The easiest way to handle Sessions and transactions is Hibernate's automatic "current"
Session management. For a discussion of contextual sessions see 第 2.3 節 「上下文相關的
會話(Contextual Session)」. Using the "jta" session context, if there is no Hibernate
Session associated with the current JTA transaction, one will be started and associated
with that JTA transaction the first time you call sessionFactory.getCurrentSession(). The
Sessions retrieved via getCurrentSession() in the "jta" context are set to automatically
flush before the transaction completes, close after the transaction completes, and
aggressively release JDBC connections after each statement. This allows the Sessions
to be managed by the life cycle of the JTA transaction to which it is associated,
keeping user code clean of such management concerns. Your code can either use JTA
programmatically through UserTransaction, or (recommended for portable code) use the
Hibernate Transaction API to set transaction boundaries. If you run in an EJB container,
declarative transaction demarcation with CMT is preferred.
3.9.4. JMX 部署
爲了將 SessionFactory 註冊到 JNDI 中,cfg.buildSessionFactory() 這行代碼仍需在某處被執行。
你可在一個 static 初始化塊(像 HibernateUtil 中的那樣)中執行它或將 Hibernate 部署爲一
個託管的服務。
爲了部署在一個支持 JMX 的應用程序服務器上,Hibernate 和
org.hibernate.jmx.HibernateService 一同分發,如 Jboss AS。 實際的部署和配置是由應用程序服
務器提供者指定的。這裏是 JBoss 4.0.x 的 jboss-service.xml 樣例:
<?xml version="1.0"?>
<server>
<mbean code="org.hibernate.jmx.HibernateService"
name="jboss.jca:service=HibernateFactory,name=HibernateFactory">
<!-- Required services -->
<depends>jboss.jca:service=RARDeployer</depends>
JMX 部署
49
<depends>jboss.jca:service=LocalTxCM,name=HsqlDS</depends>
<!-- Bind the Hibernate service to JNDI -->
<attribute name="JndiName">java:/hibernate/SessionFactory</attribute>
<!-- Datasource settings -->
<attribute name="Datasource">java:HsqlDS</attribute>
<attribute name="Dialect">org.hibernate.dialect.HSQLDialect</attribute>
<!-- Transaction integration -->
<attribute name="TransactionStrategy">
org.hibernate.transaction.JTATransactionFactory</attribute>
<attribute name="TransactionManagerLookupStrategy">
org.hibernate.transaction.JBossTransactionManagerLookup</attribute>
<attribute name="FlushBeforeCompletionEnabled">true</attribute>
<attribute name="AutoCloseSessionEnabled">true</attribute>
<!-- Fetching options -->
<attribute name="MaximumFetchDepth">5</attribute>
<!-- Second-level caching -->
<attribute name="SecondLevelCacheEnabled">true</attribute>
<attribute name="CacheProviderClass">org.hibernate.cache.EhCacheProvider</attribute>
<attribute name="QueryCacheEnabled">true</attribute>
<!-- Logging -->
<attribute name="ShowSqlEnabled">true</attribute>
<!-- Mapping files -->
<attribute name="MapResources">auction/Item.hbm.xml,auction/Category.hbm.xml</attribute>
</mbean>
</server>
這個文件是部署在 META-INF 目錄下的,並會被打包到以 .sar(service archive)爲擴展名的
JAR 文件中。同時,你須要將 Hibernate、它所須要的第三方庫、你編譯好的持久化類以及你的
映射定義文件打包進同一個文檔。你的企業 Bean(通常爲會話 Bean)可能會被打包成它們本身
的 JAR 文件,但你也許會將 EJB JAR 文件一同包含進能獨立(熱)部署的主服務文檔。參考
JBoss AS 文檔以瞭解更多的 JMX服務與 EJB 部署的信息。
50
第 4
51
持久化類(Persistent Classes)
Persistent classes are classes in an application that implement the entities of the
business problem (e.g. Customer and Order in an E-commerce application). The term
"persistent" here means that the classes are able to be persisted, not that they are
in the persistent state (see 第 11.1 節 「Hibernate 對象狀態(object states)」 for
discussion).
Hibernate works best if these classes follow some simple rules, also known as the
Plain Old Java Object (POJO) programming model. However, none of these rules are hard
requirements. Indeed, Hibernate assumes very little about the nature of your persistent
objects. You can express a domain model in other ways (using trees of java.util.Map
instances, for example).
4.1. 一個簡單的 POJO 例子
例 4.1. Simple POJO representing a cat
package eg;
import java.util.Set;
import java.util.Date;
public class Cat {
private Long id; // identifier
private Date birthdate;
private Color color;
private char sex;
private float weight;
private int litterId;
private Cat mother;
private Set kittens = new HashSet();
private void setId(Long id) {
this.id=id;
}
public Long getId() {
return id;
}
void setBirthdate(Date date) {
birthdate = date;
}
public Date getBirthdate() {
return birthdate;
}
void setWeight(float weight) {
this.weight = weight;
}
第 4 章 持久化類(Persistent Classes)
52
public float getWeight() {
return weight;
}
public Color getColor() {
return color;
}
void setColor(Color color) {
this.color = color;
}
void setSex(char sex) {
this.sex=sex;
}
public char getSex() {
return sex;
}
void setLitterId(int id) {
this.litterId = id;
}
public int getLitterId() {
return litterId;
}
void setMother(Cat mother) {
this.mother = mother;
}
public Cat getMother() {
return mother;
}
void setKittens(Set kittens) {
this.kittens = kittens;
}
public Set getKittens() {
return kittens;
}
// addKitten not needed by Hibernate
public void addKitten(Cat kitten) {
kitten.setMother(this);
kitten.setLitterId( kittens.size() );
kittens.add(kitten);
}
}
在後續的章節裏咱們將介紹持久性類的 4 個主要規則的更多細節。
4.1.1. 實現一個默認的(即無參數的)構造方法(constructor)
Cat has a no-argument constructor. All persistent classes must have a default
constructor (which can be non-public) so that Hibernate can instantiate them using
java.lang.reflect.Constructor.newInstance(). It is recommended that this constructor be
defined with at least package visibility in order for runtime proxy generation to
work properly.
Provide an identifier property
53
4.1.2. Provide an identifier property
注意
Historically this was considered option. While still not (yet) enforced,
this should be considered a deprecated feature as it will be completely
required to provide a identifier property in an upcoming release.
Cat has a property named id. This property maps to the primary key column(s) of the
underlying database table. The type of the identifier property can be any "basic" type
(see ???). See 第 9.4 節 「組件做爲聯合標識符(Components as composite identifiers)」
for information on mapping composite (multi-column) identifiers.
注意
Identifiers do not necessarily need to identify column(s) in the database
physically defined as a primary key. They should just identify columns
that can be used to uniquely identify rows in the underlying table.
咱們建議你對持久化類聲明命名一致的標識屬性。咱們還建議你使用一個能夠爲空(也就是說,
不是原始類型)的類型。
4.1.3. Prefer non-final classes (semi-optional)
A central feature of Hibernate, proxies (lazy loading), depends upon the persistent
class being either non-final, or the implementation of an interface that declares all
public methods. You can persist final classes that do not implement an interface with
Hibernate; you will not, however, be able to use proxies for lazy association fetching
which will ultimately limit your options for performance tuning. To persist a final
class which does not implement a "full" interface you must disable proxy generation. See
例 4.2 「Disabling proxies in hbm.xml」 and 例 4.3 「Disabling proxies in annotations」.
例 4.2. Disabling proxies in hbm.xml
<class name="Cat" lazy="false"...>...</class>
例 4.3. Disabling proxies in annotations
@Entity @Proxy(lazy=false) public class Cat { ... }
第 4 章 持久化類(Persistent Classes)
54
If the final class does implement a proper interface, you could alternatively tell
Hibernate to use the interface instead when generating the proxies. See 例 4.4 「Proxying
an interface in hbm.xml」 and 例 4.5 「Proxying an interface in annotations」.
例 4.4. Proxying an interface in hbm.xml
<class name="Cat" proxy="ICat"...>...</class>
例 4.5. Proxying an interface in annotations
@Entity @Proxy(proxyClass=ICat.class) public class Cat implements ICat { ... }
You should also avoid declaring public final methods as this will again limit the ability
to generate proxies from this class. If you want to use a class with public final
methods, you must explicitly disable proxying. Again, see 例 4.2 「Disabling proxies
in hbm.xml」 and 例 4.3 「Disabling proxies in annotations」.
4.1.4. 爲持久化字段聲明訪問器(accessors)和是否可變的標誌
(mutators)(可選)
Cat declares accessor methods for all its persistent fields. Many other ORM tools
directly persist instance variables. It is better to provide an indirection between
the relational schema and internal data structures of the class. By default, Hibernate
persists JavaBeans style properties and recognizes method names of the form getFoo, isFoo
and setFoo. If required, you can switch to direct field access for particular properties.
Properties need not be declared public. Hibernate can persist a property declared with
package, protected or private visibility as well.
4.2. 實現繼承(Inheritance)
子類也必須遵照第一條和第二條規則。它從超類 Cat 繼承了標識屬性。例如:
package eg;
public class DomesticCat extends Cat {
private String name;
public String getName() {
return name;
}
protected void setName(String name) {
this.name=name;
}
實現 equals() 和 hashCode() 方法:
55
}
4.3. 實現 equals() 和 hashCode() 方法:
若是你有以下需求,你必須重載 equals() 和 hashCode() 方法:
•想把持久類的實例放入 Set 中(當表示多值關聯時,推薦這麼作),並且
•想重用脫管實例
Hibernate 保證,僅在特定會話範圍內,持久化標識(數據庫的行)和 Java 標識是等價的。
所以,一旦咱們混合了從不一樣會話中獲取的實例,若是但願 Set 有明確的語義,就必須實現
equals() 和 hashCode()。
實現 equals()/hashCode() 最顯而易見的方法是比較兩個對象 標識符的值。若是值相同,則兩個
對象對應於數據庫的同一行,所以它們是相等的(若是都被添加到 Set,則在 Set 中只有一個元
素)。不幸的是,對生成的標識不能 使用這種方法。Hibernate 僅對那些持久化對象賦標識值,
一個新建立的實例將不會有任何標識值。此外, 若是一個實例沒有被保存(unsaved),而且它
當前正在一個 Set 中,保存它將會給這個對象賦一個標識值。若是 equals() 和 hashCode() 是基
於標識值 實現的,則其哈希碼將會改變,這違反了 Set 的契約。建議去 Hibernate 的站點閱讀
關於這個問題的所有討論。注意,這不是 Hibernate 的問題,而是通常的 Java 對象標識和 Java
對象等價的語義問題。
咱們建議使用業務鍵值相等(Business key equality)來實現 equals() 和 hashCode()。業務鍵
值相等的意思是,equals() 方法僅僅比較造成業務鍵的屬性,它能在現實世界裏標識咱們的實例
(是一個天然的候選碼)。
public class Cat {
...
public boolean equals(Object other) {
if (this == other) return true;
if ( !(other instanceof Cat) ) return false;
final Cat cat = (Cat) other;
if ( !cat.getLitterId().equals( getLitterId() ) ) return false;
if ( !cat.getMother().equals( getMother() ) ) return false;
return true;
}
public int hashCode() {
int result;
result = getMother().hashCode();
result = 29 * result + getLitterId();
return result;
}
}
第 4 章 持久化類(Persistent Classes)
56
A business key does not have to be as solid as a database primary key candidate (see
第  13.1.3  節 「關注對象標識(Considering object identity)」). Immutable or unique
properties are usually good candidates for a business key.
4.4. 動態模型(Dynamic models)
注意
The following features are currently considered experimental and may
change in the near future.
運行期的持久化實體沒有必要必定表示爲像 POJO 類或 JavaBean 對象那樣的形式。Hibernate 也
支持動態模型 (在運行期使用 Map 的 Map)和象 DOM4J 的樹模型那樣的實體表示。使用這種方
法,你不用寫持久化類,只寫映射文件就好了。
By default, Hibernate works in normal POJO mode. You can set a default entity
representation mode for a particular SessionFactory using the default_entity_mode
configuration option (see 表 3.3 「Hibernate 配置屬性」).
下面是用 Map 來表示的例子。首先,在映射文件中,要聲明 entity-name 來代替一個類名(或做
爲一種附屬)。
<hibernate-mapping>
<class entity-name="Customer">
<id name="id"
type="long"
column="ID">
<generator class="sequence"/>
</id>
<property name="name"
column="NAME"
type="string"/>
<property name="address"
column="ADDRESS"
type="string"/>
<many-to-one name="organization"
column="ORGANIZATION_ID"
class="Organization"/>
<bag name="orders"
inverse="true"
lazy="false"
cascade="all">
<key column="CUSTOMER_ID"/>
<one-to-many class="Order"/>
</bag>
動態模型(Dynamic models)
57
</class>java

</hibernate-mapping>
注意,雖然是用目標類名來聲明關聯的,可是關聯的目標類型除了是 POJO 以外,也能夠是一個
動態的實體。
在使用 dynamic-map 爲 SessionFactory 設置了默認的實體模式以後,能夠在運行期使用 Map 的
Map:
Session s = openSession();
Transaction tx = s.beginTransaction();
// Create a customer
Map david = new HashMap();
david.put("name", "David");
// Create an organization
Map foobar = new HashMap();
foobar.put("name", "Foobar Inc.");
// Link both
david.put("organization", foobar);
// Save both
s.save("Customer", david);
s.save("Organization", foobar);
tx.commit();
s.close();
動態映射的好處是,變化所須要的時間少了,由於原型不須要實現實體類。然而,你沒法進行編
譯期的類型檢查,並可能由此會處理不少的運行期異常。幸好有了 Hibernate 映射,它使得數據
庫的 schema 能容易的規格化和合理化,並容許稍後在此之上添加合適的領域模型實現。
實體表示模式也能在每一個 Session 的基礎上設置:
Session dynamicSession = pojoSession.getSession(EntityMode.MAP);
// Create a customer
Map david = new HashMap();
david.put("name", "David");
dynamicSession.save("Customer", david);
...
dynamicSession.flush();
dynamicSession.close()
...
// Continue on pojoSession
第 4 章 持久化類(Persistent Classes)
58
請注意,用 EntityMode 調用 getSession() 是在 Session 的 API 中,而不是 SessionFactory。 這
樣,新的 Session 共享底層的 JDBC 鏈接,事務,和其餘的上下文信息。這意味着,你不須要在
第二個 Session 中調用 flush() 和 close(),一樣的,把事務和鏈接的處理交給原來的工做單元。
More information about the XML representation capabilities can be found in 第 20 章
XML 映射.
4.5. 元組片段映射(Tuplizers)
org.hibernate.tuple.Tuplizer and its sub-interfaces are responsible for managing
a particular representation of a piece of data given that representation's
org.hibernate.EntityMode. If a given piece of data is thought of as a data structure,
then a tuplizer is the thing that knows how to create such a data structure, how to
extract values from such a data structure and how to inject values into such a data
structure. For example, for the POJO entity mode, the corresponding tuplizer knows
how create the POJO through its constructor. It also knows how to access the POJO
properties using the defined property accessors.
There are two (high-level) types of Tuplizers:
•org.hibernate.tuple.entity.EntityTuplizer which is responsible for managing the above
mentioned contracts in regards to entities
•org.hibernate.tuple.component.ComponentTuplizer which does the same for components
Users can also plug in their own tuplizers. Perhaps you require that java.util.Map
implementation other than java.util.HashMap be used while in the dynamic-map entity-mode.
Or perhaps you need to define a different proxy generation strategy than the one used by
default. Both would be achieved by defining a custom tuplizer implementation. Tuplizer
definitions are attached to the entity or component mapping they are meant to manage.
Going back to the example of our Customer entity, 例  4.6 「Specify custom tuplizers
in annotations」 shows how to specify a custom org.hibernate.tuple.entity.EntityTuplizer
using annotations while 例 4.7 「Specify custom tuplizers in hbm.xml」 shows how to
do the same in hbm.xml
例 4.6. Specify custom tuplizers in annotations
@Entity
@Tuplizer(impl = DynamicEntityTuplizer.class)
public interface Cuisine {
@Id
@GeneratedValue
public Long getId();
public void setId(Long id);
public String getName();
public void setName(String name);
EntityNameResolvers
59
@Tuplizer(impl = DynamicComponentTuplizer.class)
public Country getCountry();
public void setCountry(Country country);
}
例 4.7. Specify custom tuplizers in hbm.xml
<hibernate-mapping>
<class entity-name="Customer">
<!--
Override the dynamic-map entity-mode
tuplizer for the customer entity
-->
<tuplizer entity-mode="dynamic-map"
class="CustomMapTuplizerImpl"/>
<id name="id" type="long" column="ID">
<generator class="sequence"/>
</id>
<!-- other properties -->
...
</class>
</hibernate-mapping>
4.6. EntityNameResolvers
org.hibernate.EntityNameResolver is a contract for resolving the entity name of a given
entity instance. The interface defines a single method resolveEntityName which is passed
the entity instance and is expected to return the appropriate entity name (null is
allowed and would indicate that the resolver does not know how to resolve the entity name
of the given entity instance). Generally speaking, an org.hibernate.EntityNameResolver is
going to be most useful in the case of dynamic models. One example might be using
proxied interfaces as your domain model. The hibernate test suite has an example of
this exact style of usage under the org.hibernate.test.dynamicentity.tuplizer2. Here
is some of the code from that package for illustration.
/**
* A very trivial JDK Proxy InvocationHandler implementation where we proxy an
* interface as the domain model and simply store persistent state in an internal
* Map. This is an extremely trivial example meant only for illustration.
*/
public final class DataProxyHandler implements InvocationHandler {
private String entityName;
private HashMap data = new HashMap();
public DataProxyHandler(String entityName, Serializable id) {
this.entityName = entityName;
data.put( "Id", id );
}
第 4 章 持久化類(Persistent Classes)
60
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
if ( methodName.startsWith( "set" ) ) {
String propertyName = methodName.substring( 3 );
data.put( propertyName, args[0] );
}
else if ( methodName.startsWith( "get" ) ) {
String propertyName = methodName.substring( 3 );
return data.get( propertyName );
}
else if ( "toString".equals( methodName ) ) {
return entityName + "#" + data.get( "Id" );
}
else if ( "hashCode".equals( methodName ) ) {
return new Integer( this.hashCode() );
}
return null;
}
public String getEntityName() {
return entityName;
}
public HashMap getData() {
return data;
}
}
public class ProxyHelper {
public static String extractEntityName(Object object) {
// Our custom java.lang.reflect.Proxy instances actually bundle
// their appropriate entity name, so we simply extract it from there
// if this represents one of our proxies; otherwise, we return null
if ( Proxy.isProxyClass( object.getClass() ) ) {
InvocationHandler handler = Proxy.getInvocationHandler( object );
if ( DataProxyHandler.class.isAssignableFrom( handler.getClass() ) ) {
DataProxyHandler myHandler = ( DataProxyHandler ) handler;
return myHandler.getEntityName();
}
}
return null;
}
// various other utility methods ....
}
/**
* The EntityNameResolver implementation.
*
* IMPL NOTE : An EntityNameResolver really defines a strategy for how entity names
* should be resolved. Since this particular impl can handle resolution for all of our
* entities we want to take advantage of the fact that SessionFactoryImpl keeps these
* in a Set so that we only ever have one instance registered. Why? Well, when it
* comes time to resolve an entity name, Hibernate must iterate over all the registered
* resolvers. So keeping that number down helps that process be as speedy as possible.
* Hence the equals and hashCode implementations as is
*/
EntityNameResolvers
61
public class MyEntityNameResolver implements EntityNameResolver {
public static final MyEntityNameResolver INSTANCE = new MyEntityNameResolver();
public String resolveEntityName(Object entity) {
return ProxyHelper.extractEntityName( entity );
}
public boolean equals(Object obj) {
return getClass().equals( obj.getClass() );
}
public int hashCode() {
return getClass().hashCode();
}
}
public class MyEntityTuplizer extends PojoEntityTuplizer {
public MyEntityTuplizer(EntityMetamodel entityMetamodel, PersistentClass mappedEntity) {
super( entityMetamodel, mappedEntity );
}
public EntityNameResolver[] getEntityNameResolvers() {
return new EntityNameResolver[] { MyEntityNameResolver.INSTANCE };
}
public String determineConcreteSubclassEntityName(Object entityInstance, SessionFactoryImplementor factory) {
String entityName = ProxyHelper.extractEntityName( entityInstance );
if ( entityName == null ) {
entityName = super.determineConcreteSubclassEntityName( entityInstance, factory );
}
return entityName;
}
...
爲了註冊 org.hibernate.EntityNameResolver,用戶必須:
1. Implement a custom tuplizer (see 第  4.5  節 「元組片段映射(Tuplizers)」),
implementing the getEntityNameResolvers method
2. 用 registerEntityNameResolver 方法註冊到 org.hibernate.impl.SessionFactoryImpl(它是
org.hibernate.SessionFactory 的實現類)。
62
第 5
63
對象/關係數據庫映射基礎(Basic O/R
Mapping)
5.1. 映射定義(Mapping declaration)
Object/relational mappings can be defined in three approaches:
•using Java 5 annotations (via the Java Persistence 2 annotations)
•using JPA 2 XML deployment descriptors (described in chapter XXX)
•using the Hibernate legacy XML files approach known as hbm.xml
Annotations are split in two categories, the logical mapping annotations (describing
the object model, the association between two entities etc.) and the physical mapping
annotations (describing the physical schema, tables, columns, indexes, etc). We will
mix annotations from both categories in the following code examples.
JPA annotations are in the javax.persistence.* package. Hibernate specific extensions
are in org.hibernate.annotations.*. You favorite IDE can auto-complete annotations and
their attributes for you (even without a specific "JPA" plugin, since JPA annotations
are plain Java 5 annotations).
Here is an example of mapping
package eg;
@Entity
@Table(name="cats") @Inheritance(strategy=SINGLE_TABLE)
@DiscriminatorValue("C") @DiscriminatorColumn(name="subclass", discriminatorType=CHAR)
public class Cat {
@Id @GeneratedValue
public Integer getId() { return id; }
public void setId(Integer id) { this.id = id; }
private Integer id;
public BigDecimal getWeight() { return weight; }
public void setWeight(BigDecimal weight) { this.weight = weight; }
private BigDecimal weight;
@Temporal(DATE) @NotNull @Column(updatable=false)
public Date getBirthdate() { return birthdate; }
public void setBirthdate(Date birthdate) { this.birthdate = birthdate; }
private Date birthdate;
@org.hibernate.annotations.Type(type="eg.types.ColorUserType")
@NotNull @Column(updatable=false)
public ColorType getColor() { return color; }
第 5 章 對象/關係數據庫映射基礎(Basic O/R ...
64
public void setColor(ColorType color) { this.color = color; }
private ColorType color;
@NotNull @Column(updatable=false)
public String getSex() { return sex; }
public void setSex(String sex) { this.sex = sex; }
private String sex;
@NotNull @Column(updatable=false)
public Integer getLitterId() { return litterId; }
public void setLitterId(Integer litterId) { this.litterId = litterId; }
private Integer litterId;
@ManyToOne @JoinColumn(name="mother_id", updatable=false)
public Cat getMother() { return mother; }
public void setMother(Cat mother) { this.mother = mother; }
private Cat mother;
@OneToMany(mappedBy="mother") @OrderBy("litterId")
public Set<Cat> getKittens() { return kittens; }
public void setKittens(Set<Cat> kittens) { this.kittens = kittens; }
private Set<Cat> kittens = new HashSet<Cat>();
}
@Entity @DiscriminatorValue("D")
public class DomesticCat extends Cat {
public String getName() { return name; }
public void setName(String name) { this.name = name }
private String name;
}
@Entity
public class Dog { ... }
The legacy hbm.xml approach uses an XML schema designed to be readable and handeditable.
The mapping language is Java-centric, meaning that mappings are constructed
around persistent class declarations and not table declarations.
請注意,雖然不少 Hibernate 用戶選擇手寫 XML 映射文檔,但也有一些工具能夠用來生成映射
文檔,包括 XDoclet、Middlegen 和 AndroMDA。
下面是一個映射的例子:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="eg">
<class name="Cat"
table="cats"
discriminator-value="C">
映射定義(Mapping declaration)
65
<id name="id">
<generator class="native"/>
</id>
<discriminator column="subclass"
type="character"/>
<property name="weight"/>
<property name="birthdate"
type="date"
not-null="true"
update="false"/>
<property name="color"
type="eg.types.ColorUserType"
not-null="true"
update="false"/>
<property name="sex"
not-null="true"
update="false"/>
<property name="litterId"
column="litterId"
update="false"/>
<many-to-one name="mother"
column="mother_id"
update="false"/>
<set name="kittens"
inverse="true"
order-by="litter_id">
<key column="mother_id"/>
<one-to-many class="Cat"/>
</set>
<subclass name="DomesticCat"
discriminator-value="D">
<property name="name"
type="string"/>
</subclass>
</class>
<class name="Dog">
<!-- mapping for Dog could go here -->
</class>
</hibernate-mapping>
We will now discuss the concepts of the mapping documents (both annotations and XML).
We will only describe, however, the document elements and attributes that are used by
Hibernate at runtime. The mapping document also contains some extra optional attributes
第 5 章 對象/關係數據庫映射基礎(Basic O/R ...
66
and elements that affect the database schemas exported by the schema export tool (for
example, the not-null attribute).
5.1.1. Entity
An entity is a regular Java object (aka POJO) which will be persisted by Hibernate.
To mark an object as an entity in annotations, use the @Entity annotation.
@Entity
public class Flight implements Serializable {
Long id;
@Id
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
}
That's pretty much it, the rest is optional. There are however any options to tweak
your entity mapping, let's explore them.
@Table lets you define the table the entity will be persisted into. If undefined, the
table name is the unqualified class name of the entity. You can also optionally define
the catalog, the schema as well as unique constraints on the table.
@Entity
@Table(name="TBL_FLIGHT",
schema="AIR_COMMAND",
uniqueConstraints=
@UniqueConstraint(
name="flight_number",
columnNames={"comp_prefix", "flight_number"} ) )
public class Flight implements Serializable {
@Column(name="comp_prefix")
public String getCompagnyPrefix() { return companyPrefix; }
@Column(name="flight_number")
public String getNumber() { return number; }
}
The constraint name is optional (generated if left undefined). The column names
composing the constraint correspond to the column names as defined before the Hibernate
NamingStrategy is applied.
@Entity.name lets you define the shortcut name of the entity you can used in JP-QL and
HQL queries. It defaults to the unqualified class name of the class.
Hibernate goes beyond the JPA specification and provide additional configurations. Some
of them are hosted on @org.hibernate.annotations.Entity:
Entity
67
•dynamicInsert / dynamicUpdate (defaults to false): specifies that INSERT / UPDATE SQL
should be generated at runtime and contain only the columns whose values are not
null. The dynamic-update and dynamic-insert settings are not inherited by subclasses.
Although these settings can increase performance in some cases, they can actually
decrease performance in others.
•selectBeforeUpdate (defaults to false): specifies that Hibernate should never perform
an SQL UPDATE unless it is certain that an object is actually modified. Only when a
transient object has been associated with a new session using update(), will Hibernate
perform an extra SQL SELECT to determine if an UPDATE is actually required. Use
of select-before-update will usually decrease performance. It is useful to prevent
a database update trigger being called unnecessarily if you reattach a graph of
detached instances to a Session.
•polymorphisms (defaults to IMPLICIT): determines whether implicit or explicit query
polymorphisms is used. Implicit polymorphisms means that instances of the class
will be returned by a query that names any superclass or implemented interface or
class, and that instances of any subclass of the class will be returned by a query
that names the class itself. Explicit polymorphisms means that class instances will
be returned only by queries that explicitly name that class. Queries that name
the class will return only instances of subclasses mapped. For most purposes, the
default polymorphisms=IMPLICIT is appropriate. Explicit polymorphisms is useful when
two different classes are mapped to the same table This allows a "lightweight" class
that contains a subset of the table columns.
•persister: specifies a custom ClassPersister. The persister attribute lets you customize
the persistence strategy used for the class. You can, for example, specify your
own subclass of org.hibernate.persister.EntityPersister, or you can even provide a
completely new implementation of the interface org.hibernate.persister.ClassPersister
that implements, for example, persistence via stored procedure calls, serialization
to flat files or LDAP. See org.hibernate.test.CustomPersister for a simple example of
"persistence" to a Hashtable.
•optimisticLock (defaults to VERSION): determines the optimistic locking strategy. If
you enable dynamicUpdate, you will have a choice of optimistic locking strategies:
•version(版本檢查):檢查 version/timestamp 字段
•all(所有):檢查所有字段
•dirty(髒檢查):只檢察修改過的字段,容許某些並行更新
•none(不檢查):不使用樂觀鎖定
咱們強烈建議你在 Hibernate 中使用 version/timestamp 字段來進行樂觀鎖定。這個選擇可
以優化性能,且可以處理對脫管實例的修改(例如:在使用 Session.merge() 的時候)。
第 5 章 對象/關係數據庫映射基礎(Basic O/R ...
68
提示
Be sure to import @javax.persistence.Entity to mark a class as an entity. It's
a common mistake to import @org.hibernate.annotations.Entity by accident.
Some entities are not mutable. They cannot be updated or deleted by the application.
This allows Hibernate to make some minor performance optimizations.. Use the @Immutable
annotation.
You can also alter how Hibernate deals with lazy initialization for this class. On
@Proxy, use lazy=false to disable lazy fetching (not recommended). You can also specify
an interface to use for lazy initializing proxies (defaults to the class itself): use
proxyClass on @Proxy. Hibernate will initially return proxies (Javassist or CGLIB) that
implement the named interface. The persistent object will load when a method of the
proxy is invoked. See "Initializing collections and proxies" below.
@BatchSize specifies a "batch size" for fetching instances of this class by identifier.
Not yet loaded instances are loaded batch-size at a time (default 1).
You can specific an arbitrary SQL WHERE condition to be used when retrieving objects
of this class. Use @Where for that.
In the same vein, @Check lets you define an SQL expression used to generate a multirow
check constraint for automatic schema generation.
There is no difference between a view and a base table for a Hibernate mapping. This is
transparent at the database level, although some DBMS do not support views properly,
especially with updates. Sometimes you want to use a view, but you cannot create one in
the database (i.e. with a legacy schema). In this case, you can map an immutable and readonly
entity to a given SQL subselect expression using @org.hibernate.annotations.Subselect:
@Entity
@Subselect("select item.name, max(bid.amount), count(*) "
+ "from item "
+ "join bid on bid.item_id = item.id "
+ "group by item.name")
@Synchronize( {"item", "bid"} ) //tables impacted
public class Summary {
@Id
public String getId() { return id; }
...
}
定義這個實體用到的表爲同步(synchronize),確保自動刷新(auto-flush)正確執行,而且依
賴原實體的查詢不會返回過時數據。在屬性元素和嵌套映射元素中均可使用 <subselect>。
Entity
69
We will now explore the same options using the hbm.xml structure. You can declare a
persistent class using the class element. For example:
<class
name="ClassName"
table="tableName"
discriminator-value="discriminator_value"
mutable="true|false"
schema="owner"
catalog="catalog"
proxy="ProxyInterface"
dynamic-update="true|false"
dynamic-insert="true|false"
select-before-update="true|false"
polymorphism="implicit|explicit"
where="arbitrary sql where condition"
persister="PersisterClass"
batch-size="N"
optimistic-lock="none|version|dirty|all"
lazy="true|false" (16)
entity-name="EntityName" (17)
check="arbitrary sql check condition" (18)
rowid="rowid" (19)
subselect="SQL expression" (20)
abstract="true|false" (21)
node="element-name"
/>
name(可選):持久化類(或者接口)的 Java 全限定名。 若是這個屬性不存在,Hibernate
將假定這是一個非 POJO 的實體映射。
table(可選 — 默認是類的非全限定名):對應的數據庫表名。
discriminator-value(可選 — 默認和類名同樣):一個用於區分不一樣的子類的值,在多態行
爲時使用。它能夠接受的值包括 null 和 not null。
mutable(可選,默認值爲 true):代表該類的實例是可變的或者不可變的。
schema(可選):覆蓋在根 <hibernate-mapping> 元素中指定的 schema 名字。
catalog(可選):覆蓋在根 <hibernate-mapping> 元素中指定的 catalog 名字。
proxy(可選):指定一個接口,在延遲裝載時做爲代理使用。你能夠在這裏使用該類本身的
名字。
dynamic-update(可選,默認爲 false):指定用於 UPDATE 的 SQL 將會在運行時動態生成,
而且只更新那些改變過的字段。
dynamic-insert(可選,默認爲 false):指定用於 INSERT 的 SQL 將會在運行時動態生成,
而且只包含那些非空值字段。
第 5 章 對象/關係數據庫映射基礎(Basic O/R ...
70
select-before-update(可選,默認爲 false):指定 Hibernate 除非肯定對象真正被修改了
(若是該值爲 true — 譯註),不然不會執行 SQL UPDATE 操做。在特定場合(實際上,它
只在一個瞬時對象(transient object)關聯到一個新的 session 中時執行的 update() 中
生效),這說明 Hibernate 會在 UPDATE 以前執行一次額外的 SQL SELECT 操做來決定是否
確實須要執行 UPDATE。
polymorphisms (optional - defaults to implicit): determines whether implicit or
explicit query polymorphisms is used.
where(可選)指定一個附加的 SQL WHERE 條件,在抓取這個類的對象時會一直增長這個條
件。
persister(可選):指定一個定製的 ClassPersister。
batch-size(可選,默認是 1)指定一個用於 根據標識符(identifier)抓取實例時使用的
"batch size"(批次抓取數量)。
optimistic-lock(樂觀鎖定)(可選,默認是 version):決定樂觀鎖定的策略。
16 lazy(可選):經過設置 lazy="false",全部的延遲加載(Lazy fetching)功能將被所有禁
用(disabled)。
17 entity-name (optional - defaults to the class name): Hibernate3 allows a class to
be mapped multiple times, potentially to different tables. It also allows entity
mappings that are represented by Maps or XML at the Java level. In these cases,
you should provide an explicit arbitrary name for the entity. See 第 4.4 節 「動
態模型(Dynamic models)」 and 第 20 章 XML 映射 for more information.
18 check(可選):這是一個 SQL 表達式, 用於爲自動生成的 schema 添加多行(multi-row)
約束檢查。
19 rowid(可選):Hibernate 能夠使用數據庫支持的所謂的 ROWIDs,例如:Oracle 數據庫,
若是你設置這個可選的 rowid,Hibernate 能夠使用額外的字段 rowid 實現快速更新。ROWID
是這個功能實現的重點,它表明了一個存儲元組(tuple)的物理位置。
20 subselect(可選):它將一個不可變(immutable)而且只讀的實體映射到一個數據庫的子
查詢中。當你想用視圖代替一張基本表的時候,這是有用的,但最好不要這樣作。更多的介
紹請看下面內容。
21 abstract(可選):用於在 <union-subclass> 的層次結構(hierarchies)中標識抽象超類。
若指明的持久化類其實是一個接口,這也是徹底能夠接受的。以後你能夠用元素 <subclass> 來
指定該接口的實際實現類。你能夠持久化任何 static(靜態的)內部類。你應該使用標準的類名
格式來指定類名,好比:Foo$Bar。
Here is how to do a virtual view (subselect) in XML:
<class name="Summary">
<subselect>
select item.name, max(bid.amount), count(*)
from item
join bid on bid.item_id = item.id
group by item.name
</subselect>
<synchronize table="item"/>
<synchronize table="bid"/>
<id name="name"/>
...
Identifiers
71
</class>
The <subselect> is available both as an attribute and a nested mapping element.
5.1.2. Identifiers
Mapped classes must declare the primary key column of the database table. Most classes
will also have a JavaBeans-style property holding the unique identifier of an instance.
Mark the identifier property with @Id.
@Entity
public class Person {
@Id Integer getId() { ... }
...
}
In hbm.xml, use the <id> element which defines the mapping from that property to the
primary key column.
<id
name="propertyName"
type="typename"
column="column_name"
unsaved-value="null|any|none|undefined|id_value"
access="field|property|ClassName">
node="element-name|@attribute-name|element/@attribute|."
<generator class="generatorClass"/>
</id>
name(可選):標識屬性的名字。
type(可選):一個 Hibernate 類型的名字。
column(可選 — 默認爲屬性名):主鍵字段的名字。
unsaved-value(可選 — 默認爲一個切合實際(sensible)的值):一個特定的標識屬性
值,用來標誌該實例是剛剛建立的,還沒有保存。這能夠把這種實例和從之前的 session 中裝
載過(可能又作過修改--譯者注)但未再次持久化的實例區分開來。
access(可選 — 默認爲 property):Hibernate 用來訪問屬性值的策略。
若是 name 屬性不存在,會認爲這個類沒有標識屬性。
The unsaved-value attribute is almost never needed in Hibernate3 and indeed has no
corresponding element in annotations.
第 5 章 對象/關係數據庫映射基礎(Basic O/R ...
72
You can also declare the identifier as a composite identifier. This allows access to
legacy data with composite keys. Its use is strongly discouraged for anything else.
5.1.2.1. Composite identifier
You can define a composite primary key through several syntaxes:
•use a component type to represent the identifier and map it as a property in the
entity: you then annotated the property as @EmbeddedId. The component type has to
be Serializable.
•map multiple properties as @Id properties: the identifier type is then the entity
class itself and needs to be Serializable. This approach is unfortunately not standard
and only supported by Hibernate.
•map multiple properties as @Id properties and declare an external class to be the
identifier type. This class, which needs to be Serializable, is declared on the entity
via the @IdClass annotation. The identifier type must contain the same properties as
the identifier properties of the entity: each property name must be the same, its
type must be the same as well if the entity property is of a basic type, its type
must be the type of the primary key of the associated entity if the entity property
is an association (either a @OneToOne or a @ManyToOne).
As you can see the last case is far from obvious. It has been inherited from the
dark ages of EJB 2 for backward compatibilities and we recommend you not to use it
(for simplicity sake).
Let's explore all three cases using examples.
5.1.2.1.1. id as a property using a component type
Here is a simple example of @EmbeddedId.
@Entity
class User {
@EmbeddedId
@AttributeOverride(name="firstName", column=@Column(name="fld_firstname")
UserId id;
Integer age;
}
@Embeddable
class UserId implements Serializable {
String firstName;
String lastName;
}
You can notice that the UserId class is serializable. To override the column mapping,
use @AttributeOverride.
Identifiers
73
An embedded id can itself contains the primary key of an associated entity.
@Entity
class Customer {
@EmbeddedId CustomerId id;
boolean preferredCustomer;
@MapsId("userId")
@JoinColumns({
@JoinColumn(name="userfirstname_fk", referencedColumnName="firstName"),
@JoinColumn(name="userlastname_fk", referencedColumnName="lastName")
})
@OneToOne User user;
}
@Embeddable
class CustomerId implements Serializable {
UserId userId;
String customerNumber;
//implements equals and hashCode
}
@Entity
class User {
@EmbeddedId UserId id;
Integer age;
}
@Embeddable
class UserId implements Serializable {
String firstName;
String lastName;
//implements equals and hashCode
}
In the embedded id object, the association is represented as the identifier of the
associated entity. But you can link its value to a regular association in the entity via
the @MapsId annotation. The @MapsId value correspond to the property name of the embedded
id object containing the associated entity's identifier. In the database, it means
that the Customer.user and the CustomerId.userId properties share the same underlying
column (user_fk in this case).
提示
The component type used as identifier must implement equals() and
hashCode().
In practice, your code only sets the Customer.user property and the user id value is
copied by Hibernate into the CustomerId.userId property.
第 5 章 對象/關係數據庫映射基礎(Basic O/R ...
74
警告
The id value can be copied as late as flush time, don't rely on it until
after flush time.
While not supported in JPA, Hibernate lets you place your association directly in the
embedded id component (instead of having to use the @MapsId annotation).
@Entity
class Customer {
@EmbeddedId CustomerId id;
boolean preferredCustomer;
}
@Embeddable
class CustomerId implements Serializable {
@OneToOne
@JoinColumns({
@JoinColumn(name="userfirstname_fk", referencedColumnName="firstName"),
@JoinColumn(name="userlastname_fk", referencedColumnName="lastName")
})
User user;
String customerNumber;
//implements equals and hashCode
}
@Entity
class User {
@EmbeddedId UserId id;
Integer age;
}
@Embeddable
class UserId implements Serializable {
String firstName;
String lastName;
//implements equals and hashCode
}
Let's now rewrite these examples using the hbm.xml syntax.
<composite-id
name="propertyName"
class="ClassName"
mapped="true|false"
access="field|property|ClassName"
node="element-name|.">
Identifiers
75
<key-property name="propertyName" type="typename" column="column_name"/>
<key-many-to-one name="propertyName" class="ClassName" column="column_name"/>
......
</composite-id>
First a simple example:
<class name="User">
<composite-id name="id" class="UserId">
<key-property name="firstName" column="fld_firstname"/>
<key-property name="lastName"/>
</composite-id>
</class>
Then an example showing how an association can be mapped.
<class name="Customer">
<composite-id name="id" class="CustomerId">
<key-property name="firstName" column="userfirstname_fk"/>
<key-property name="lastName" column="userfirstname_fk"/>
<key-property name="customerNumber"/>
</composite-id>
<property name="preferredCustomer"/>
<many-to-one name="user">
<column name="userfirstname_fk" updatable="false" insertable="false"/>
<column name="userlastname_fk" updatable="false" insertable="false"/>
</many-to-one>
</class>
<class name="User">
<composite-id name="id" class="UserId">
<key-property name="firstName"/>
<key-property name="lastName"/>
</composite-id>
<property name="age"/>
</class>
Notice a few things in the previous example:
•the order of the properties (and column) matters. It must be the same between the
association and the primary key of the associated entity
•the many to one uses the same columns as the primary key and thus must be marked as
read only (insertable and updatable to false).
•unlike with @MapsId, the id value of the associated entity is not transparently copied,
check the foreign id generator for more information.
第 5 章 對象/關係數據庫映射基礎(Basic O/R ...
76
The last example shows how to map association directly in the embedded id component.
<class name="Customer">
<composite-id name="id" class="CustomerId">
<key-many-to-one name="user">
<column name="userfirstname_fk"/>
<column name="userlastname_fk"/>
</key-many-to-one>
<key-property name="customerNumber"/>
</composite-id>
<property name="preferredCustomer"/>
</class>
<class name="User">
<composite-id name="id" class="UserId">
<key-property name="firstName"/>
<key-property name="lastName"/>
</composite-id>
<property name="age"/>
</class>
This is the recommended approach to map composite identifier. The following options
should not be considered unless some constraint are present.
5.1.2.1.2. Multiple id properties without identifier type
Another, arguably more natural, approach is to place @Id on multiple properties of your
entity. This approach is only supported by Hibernate (not JPA compliant) but does not
require an extra embeddable component.
@Entity
class Customer implements Serializable {
@Id @OneToOne
@JoinColumns({
@JoinColumn(name="userfirstname_fk", referencedColumnName="firstName"),
@JoinColumn(name="userlastname_fk", referencedColumnName="lastName")
})
User user;
@Id String customerNumber;
boolean preferredCustomer;
//implements equals and hashCode
}
@Entity
class User {
@EmbeddedId UserId id;
Integer age;
}
Identifiers
77
@Embeddable
class UserId implements Serializable {
String firstName;
String lastName;
//implements equals and hashCode
}
In this case Customer is its own identifier representation: it must implement Serializable
and must implement equals() and hashCode().
In hbm.xml, the same mapping is:
<class name="Customer">
<composite-id>
<key-many-to-one name="user">
<column name="userfirstname_fk"/>
<column name="userlastname_fk"/>
</key-many-to-one>
<key-property name="customerNumber"/>
</composite-id>
<property name="preferredCustomer"/>
</class>
<class name="User">
<composite-id name="id" class="UserId">
<key-property name="firstName"/>
<key-property name="lastName"/>
</composite-id>
<property name="age"/>
</class>
5.1.2.1.3. Multiple id properties with with a dedicated identifier
type
@IdClass on an entity points to the class (component) representing the identifier of the
class. The properties marked @Id on the entity must have their corresponding property
on the @IdClass. The return type of search twin property must be either identical for
basic properties or must correspond to the identifier class of the associated entity
for an association.
警告
This approach is inherited from the EJB 2 days and we recommend against
its use. But, after all it's your application and Hibernate supports it.
第 5 章 對象/關係數據庫映射基礎(Basic O/R ...
78
@Entity
@IdClass(CustomerId.class)
class Customer implements Serializable {
@Id @OneToOne
@JoinColumns({
@JoinColumn(name="userfirstname_fk", referencedColumnName="firstName"),
@JoinColumn(name="userlastname_fk", referencedColumnName="lastName")
})
User user;
@Id String customerNumber;
boolean preferredCustomer;
}
class CustomerId implements Serializable {
UserId user;
String customerNumber;
//implements equals and hashCode
}
@Entity
class User {
@EmbeddedId UserId id;
Integer age;
//implements equals and hashCode
}
@Embeddable
class UserId implements Serializable {
String firstName;
String lastName;
//implements equals and hashCode
}
Customer and CustomerId do have the same properties customerNumber as well as user.
CustomerId must be Serializable and implement equals() and hashCode().
While not JPA standard, Hibernate let's you declare the vanilla associated property
in the @IdClass.
@Entity
@IdClass(CustomerId.class)
class Customer implements Serializable {
@Id @OneToOne
@JoinColumns({
@JoinColumn(name="userfirstname_fk", referencedColumnName="firstName"),
@JoinColumn(name="userlastname_fk", referencedColumnName="lastName")
})
User user;
@Id String customerNumber;
Identifiers
79
boolean preferredCustomer;
}
class CustomerId implements Serializable {
@OneToOne User user;
String customerNumber;
//implements equals and hashCode
}
@Entity
class User {
@EmbeddedId UserId id;
Integer age;
//implements equals and hashCode
}
@Embeddable
class UserId implements Serializable {
String firstName;
String lastName;
}
This feature is of limited interest though as you are likely to have chosen the @IdClass
approach to stay JPA compliant or you have a quite twisted mind.
Here are the equivalent on hbm.xml files:
<class name="Customer">
<composite-id class="CustomerId" mapped="true">
<key-many-to-one name="user">
<column name="userfirstname_fk"/>
<column name="userlastname_fk"/>
</key-many-to-one>
<key-property name="customerNumber"/>
</composite-id>
<property name="preferredCustomer"/>
</class>
<class name="User">
<composite-id name="id" class="UserId">
<key-property name="firstName"/>
<key-property name="lastName"/>
</composite-id>
<property name="age"/>
</class>
第 5 章 對象/關係數據庫映射基礎(Basic O/R ...
80
5.1.2.2. Identifier generator
Hibernate can generate and populate identifier values for you automatically. This is
the recommended approach over "business" or "natural" id (especially composite ids).
Hibernate offers various generation strategies, let's explore the most common ones
first that happens to be standardized by JPA:
•IDENTITY: supports identity columns in DB2, MySQL, MS SQL Server, Sybase and
HypersonicSQL. The returned identifier is of type long, short or int.
•SEQUENCE (called seqhilo in Hibernate): uses a hi/lo algorithm to efficiently generate
identifiers of type long, short or int, given a named database sequence.
•TABLE (called MultipleHiLoPerTableGenerator in Hibernate) : uses a hi/lo algorithm to
efficiently generate identifiers of type long, short or int, given a table and column
as a source of hi values. The hi/lo algorithm generates identifiers that are unique
only for a particular database.
•AUTO: selects IDENTITY, SEQUENCE or TABLE depending upon the capabilities of the
underlying database.
重要
We recommend all new projects to use the new enhanced identifier
generators. They are deactivated by default for entities using annotations
but can be activated using hibernate.id.new_generator_mappings=true. These
new generators are more efficient and closer to the JPA 2 specification
semantic.
However they are not backward compatible with existing Hibernate based
application (if a sequence or a table is used for id generation). See
XXXXXXX ??? for more information on how to activate them.
To mark an id property as generated, use the @GeneratedValue annotation. You can specify
the strategy used (default to AUTO) by setting strategy.
@Entity
public class Customer {
@Id @GeneratedValue
Integer getId() { ... };
}
@Entity
public class Invoice {
@Id @GeneratedValue(strategy=GenerationType.IDENTITY)
Identifiers
81
Integer getId() { ... };
}
SEQUENCE and TABLE require additional configurations that you can set using
@SequenceGenerator and @TableGenerator:
•name: name of the generator
•table / sequenceName: name of the table or the sequence (defaulting respectively to
hibernate_sequences and hibernate_sequence)
•catalog / schema:
•initialValue: the value from which the id is to start generating
•allocationSize: the amount to increment by when allocating id numbers from the generator
In addition, the TABLE strategy also let you customize:
•pkColumnName: the column name containing the entity identifier
•valueColumnName: the column name containing the identifier value
•pkColumnValue: the entity identifier
•uniqueConstraints: any potential column constraint on the table containing the ids
To link a table or sequence generator definition with an actual generated property,
use the same name in both the definition name and the generator value generator as
shown below.
@Id
@GeneratedValue(
strategy=GenerationType.SEQUENCE,
generator="SEQ_GEN")
@javax.persistence.SequenceGenerator(
name="SEQ_GEN",
sequenceName="my_sequence",
allocationSize=20
)
public Integer getId() { ... }
The scope of a generator definition can be the application or the class. Classdefined
generators are not visible outside the class and can override application
level generators. Application level generators are defined in JPA's XML deployment
descriptors (see XXXXXX ???):
第 5 章 對象/關係數據庫映射基礎(Basic O/R ...
82
<table-generator name="EMP_GEN"
table="GENERATOR_TABLE"
pk-column-name="key"
value-column-name="hi"
pk-column-value="EMP"
allocation-size="20"/>
//and the annotation equivalent
@javax.persistence.TableGenerator(
name="EMP_GEN",
table="GENERATOR_TABLE",
pkColumnName = "key",
valueColumnName = "hi"
pkColumnValue="EMP",
allocationSize=20
)
<sequence-generator name="SEQ_GEN"
sequence-name="my_sequence"
allocation-size="20"/>
//and the annotation equivalent
@javax.persistence.SequenceGenerator(
name="SEQ_GEN",
sequenceName="my_sequence",
allocationSize=20
)
If a JPA XML descriptor (like META-INF/orm.xml) is used to define the generators, EMP_GEN
and SEQ_GEN are application level generators.
注意
Package level definition is not supported by the JPA specification.
However, you can use the @GenericGenerator at the package level (see ???).
These are the four standard JPA generators. Hibernate goes beyond that and provide
additional generators or additional options as we will see below. You can also write
your own custom identifier generator by implementing org.hibernate.id.IdentifierGenerator.
To define a custom generator, use the @GenericGenerator annotation (and its plural
counter part @GenericGenerators) that describes the class of the identifier generator
or its short cut name (as described below) and a list of key/value parameters.
When using @GenericGenerator and assigning it via @GeneratedValue.generator, the
@GeneratedValue.strategy is ignored: leave it blank.
@Id @GeneratedValue(generator="system-uuid")
Identifiers
83
@GenericGenerator(name="system-uuid", strategy = "uuid")
public String getId() {
@Id @GeneratedValue(generator="trigger-generated")
@GenericGenerator(
name="trigger-generated",
strategy = "select",
parameters = @Parameter(name="key", value = "socialSecurityNumber")
)
public String getId() {
The hbm.xml approach uses the optional <generator> child element inside <id>. If any
parameters are required to configure or initialize the generator instance, they are
passed using the <param> element.
<id name="id" type="long" column="cat_id">
<generator class="org.hibernate.id.TableHiLoGenerator">
<param name="table">uid_table</param>
<param name="column">next_hi_value_column</param>
</generator>
</id>
5.1.2.2.1. Various additional generators
全部的生成器都實現 org.hibernate.id.IdentifierGenerator 接口。這是一個很是簡單的接口;某
些應用程序能夠選擇提供他們本身特定的實現。固然,Hibernate 提供了不少內置的實現。下面
是一些內置生成器的快捷名字:
increment
用於爲 long, short 或者 int 類型生成 惟一標識。只有在沒有其餘進程往同一張表中插入數
據時才能使用。在集羣下不要使用。
identity
對 DB2,MySQL,MS SQL Server,Sybase 和 HypersonicSQL 的內置標識字段提供支持。返回
的標識符是 long,short 或者 int 類型的。
sequence
在 DB2,PostgreSQL,Oracle,SAP DB,McKoi 中使用序列(sequence), 而在 Interbase
中使用生成器(generator)。返回的標識符是 long,short 或者 int 類型的。
hilo
使用一個高/低位算法高效的生成 long,short 或者 int 類型的標識符。給定一個表和字段
(默認分別是 hibernate_unique_key 和 next_hi)做爲高位值的來源。高/低位算法生成的標
識符只在一個特定的數據庫中是惟一的。
seqhilo
使用一個高/低位算法來高效的生成 long,short 或者 int 類型的標識符,給定一個數據庫序
列(sequence)的名字。
第 5 章 對象/關係數據庫映射基礎(Basic O/R ...
84
uuid
Generates a 128-bit UUID based on a custom algorithm. The value generated is
represented as a string of 32 hexidecimal digits. Users can also configure it to use
a separator (config parameter "separator") which separates the hexidecimal digits
into 8{sep}8{sep}4{sep}8{sep}4. Note specifically that this is different than the
IETF RFC 4122 representation of 8-4-4-4-12. If you need RFC 4122 compliant UUIDs,
consider using "uuid2" generator discussed below.
uuid2
Generates a IETF RFC 4122 compliant (variant 2) 128-bit UUID. The exact
"version" (the RFC term) generated depends on the pluggable "generation strategy"
used (see below). Capable of generating values as java.util.UUID, java.lang.String
or as a byte array of length 16 (byte[16]). The "generation strategy" is defined
by the interface org.hibernate.id.UUIDGenerationStrategy. The generator defines 2
configuration parameters for defining which generation strategy to use:
uuid_gen_strategy_class
Names the UUIDGenerationStrategy class to use
uuid_gen_strategy
Names the UUIDGenerationStrategy instance to use
Out of the box, comes with the following strategies:
•org.hibernate.id.uuid.StandardRandomStrategy (the default) - generates "version
3" (aka, "random") UUID values via the randomUUID method of java.util.UUID
•org.hibernate.id.uuid.CustomVersionOneStrategy - generates "version 1" UUID values,
using IP address since mac address not available. If you need mac
address to be used, consider leveraging one of the existing third party
UUID generators which sniff out mac address and integrating it via the
org.hibernate.id.UUIDGenerationStrategy contract. Two such libraries known at time
of this writing include http://johannburkard.de/software/uuid/ and http://
commons.apache.org/sandbox/id/uuid.html
guid
在 MS SQL Server 和 MySQL 中使用數據庫生成的 GUID 字符串。
native
根據底層數據庫的能力選擇 identity、sequence 或者 hilo 中的一個。
assigned
讓應用程序在調用 save() 以前爲對象分配一個標識符。這是 <generator> 元素沒有指定時的
默認生成策略。
select
經過數據庫觸發器選擇一些惟一主鍵的行並返回主鍵值來分配一個主鍵。
Identifiers
85
foreign
使用另一個相關聯的對象的標識符。它一般和 <one-to-one> 聯合起來使用。
sequence-identity
一種特別的序列生成策略,它使用數據庫序列來生成實際值,但將它和 JDBC3 的
getGeneratedKeys 結合在一塊兒,使得在插入語句執行的時候就返回生成的值。目前爲止只有
面向 JDK 1.4 的 Oracle 10g 驅動支持這一策略。因爲 Oracle 驅動程序的一個 bug,這些
插入語句的註釋被關閉了。
5.1.2.2.2. 高/低位算法(Hi/Lo Algorithm)
hilo 和 seqhilo 生成器給出了兩種 hi/lo 算法的實現, 這是一種很使人滿意的標識符生成算
法。第一種實現須要一個「特殊」的數據庫表來保存下一個可用的「hi」值。 第二種實現使用一
個 Oracle 風格的序列(在被支持的狀況下)。
<id name="id" type="long" column="cat_id">
<generator class="hilo">
<param name="table">hi_value</param>
<param name="column">next_value</param>
<param name="max_lo">100</param>
</generator>
</id>
<id name="id" type="long" column="cat_id">
<generator class="seqhilo">
<param name="sequence">hi_value</param>
<param name="max_lo">100</param>
</generator>
</id>
惋惜的是,你在爲 Hibernate 自行提供 Connection 時沒法使用 hilo。 當 Hibernate 使用 JTA 獲
取應用服務器的數據源鏈接時,你必須正確地配置 hibernate.transaction.manager_lookup_class。
5.1.2.2.3. UUID 算法(UUID Algorithm )
UUID 包含:IP 地址、JVM 的啓動時間(精確到 1/4 秒)、系統時間和一個計數器值(在 JVM 中
惟一)。 在 Java 代碼中不可能得到 MAC 地址或者內存地址,因此這已是咱們在不使用 JNI
的前提下的能作的最好實現了。
5.1.2.2.4. 標識字段和序列(Identity columns and Sequences)
對於內部支持標識字段的數據庫(DB二、MySQL、Sybase 和 MS SQL),你能夠使用 identity 關
鍵字生成。對於內部支持序列的數據庫(DB二、Oracle、PostgreSQL、Interbase、McKoi 和 SAP
DB),你能夠使用 sequence 風格的關鍵字生成。這兩種方式對於插入一個新的對象都須要兩次
SQL 查詢。例如:
<id name="id" type="long" column="person_id">
第 5 章 對象/關係數據庫映射基礎(Basic O/R ...
86
<generator class="sequence">
<param name="sequence">person_id_sequence</param>
</generator>
</id>
<id name="id" type="long" column="person_id" unsaved-value="0">
<generator class="identity"/>
</id>
對於跨平臺開發,native 策略會從 identity、sequence 和 hilo 中進行選擇,選擇哪個,這取
決於底層數據庫的支持能力。
5.1.2.2.5. 程序分配的標識符(Assigned Identifiers)
If you want the application to assign identifiers, as opposed to having Hibernate
generate them, you can use the assigned generator. This special generator uses the
identifier value already assigned to the object's identifier property. The generator
is used when the primary key is a natural key instead of a surrogate key. This is the
default behavior if you do not specify @GeneratedValue nor <generator> elements.
當選擇 assigned 生成器時,除非有一個 version 或 timestamp 屬性,或者你定義
了 Interceptor.isUnsaved(),不然須要讓 Hiberante 使用 unsaved-value="undefined",強制
Hibernatet 查詢數據庫來肯定一個實例是瞬時的(transient) 仍是脫管的(detached)。
5.1.2.2.6. 觸發器實現的主鍵生成器(Primary keys assigned by triggers)
僅僅用於遺留的 schema 中(Hibernate 不能用觸發器生成 DDL)。
<id name="id" type="long" column="person_id">
<generator class="select">
<param name="key">socialSecurityNumber</param>
</generator>
</id>
在上面的例子中,類定義了一個命名爲 socialSecurityNumber 的具備惟一值的屬性,它是一個自
然鍵(natural key),命名爲 person_id 的代理鍵(surrogate key)的值由觸發器生成。
5.1.2.2.7. Identity copy (foreign generator)
Finally, you can ask Hibernate to copy the identifier from another associated entity.
In the Hibernate jargon, it is known as a foreign generator but the JPA mapping reads
better and is encouraged.
@Entity
class MedicalHistory implements Serializable {
@Id @OneToOne
@JoinColumn(name = "person_id")
Identifiers
87
Person patient;
}
@Entity
public class Person implements Serializable {
@Id @GeneratedValue Integer id;
}
Or alternatively
@Entity
class MedicalHistory implements Serializable {
@Id Integer id;
@MapsId @OneToOne
@JoinColumn(name = "patient_id")
Person patient;
}
@Entity
class Person {
@Id @GeneratedValue Integer id;
}
In hbm.xml use the following approach:
<class name="MedicalHistory">
<id name="id">
<generator class="foreign">
<param name="property">patient</param>
</generator>
</id>
<one-to-one name="patient" class="Person" constrained="true"/>
</class>
5.1.2.3. 加強的標識符生成器
從 3.2.3 版本開始,有兩個表明不一樣標識符生成概念的新的生成器。第一個概念是數據庫移植
性;第二個是優化。優化表示你不需對每一個新標識符的請求都查詢數據庫。從 3.3.x 開始,這兩
個新的生成器都是用來取代上面所述的生成器的。然而,它們也包括在當前版本里且能夠由 FQN
進行引用。
這些生成器的第一個是 org.hibernate.id.enhanced.SequenceStyleGenerator,首先,它是做爲
sequence 生成器的替代物,其次,它是比 native 具備更好移植性的生成器。這是由於 native 通
常在 identity 和 sequence 之間選擇,它有差異很大的 semantic,在移植時會致使潛在的問題。
然而,org.hibernate.id.enhanced.SequenceStyleGenerator 以不一樣的方式實現移植性。它根據所使
用的方言的能力,在數據庫表或序列之間選擇以存儲其增量。這和 native 的區別是基於表或序列
的存儲具備剛好相同的 semantic。實際上,序列就是 Hibernate 試圖用基於表的生成器來模擬
的。這個生成器有以下的配置參數:
第 5 章 對象/關係數據庫映射基礎(Basic O/R ...
88
•sequence_name(可選 — 默認爲 hibernate_sequence):序列或表的名字
•initial_value(可選,默認爲 1):從序列/表裏獲取的初始值。按照序列建立的術語,這等同
於子句 "STARTS WITH"。
•increment_size(可選 - 缺省爲 1):對序列/表的調用應該區分的值。按照序列建立的術語,
這等同於子句 "INCREMENT BY"。
•force_table_use(可選 - 缺省爲 false):即便方言可能支持序列,是否也應該強制把表用做
後臺結構。
•value_column(可選 - 缺省爲 next_val):只和表結構相關,它是用於保存值的字段的名稱。
•optimizer (optional - defaults to none): See 第 5.1.2.3.1 節 「標識符生成器的優化」
新生成器的第二個是 org.hibernate.id.enhanced.TableGenerator,它的目的首先是替代 table 生成
器,即便它實際上比 org.hibernate.id.MultipleHiLoPerTableGenerator 功能要強得多;其次,做爲
利用可插拔 optimizer 的 org.hibernate.id.MultipleHiLoPerTableGenerator 的替代品。基本上這
個生成器定義了一個能夠利用多個不一樣的鍵值記錄存儲大量不一樣增量值的表。這個生成器有以下
的配置參數:
•table_name(可選 — 默認是 hibernate_sequences):所用的表的名稱。
•value_column_name(可選 — 默認爲 next_val):用於存儲這些值的表的字段的名字。
•segment_column_name(可選,默認爲 sequence_name):用於保存 "segment key" 的字段的名
稱。這是標識使用哪一個增量值的值。
•segment_value(可選,默認爲 default):咱們爲這個生成器獲取增量值的 segment 的
"segment key"。
•segment_value_length(可選 — 默認爲 255):用於 schema 生成;建立 Segment Key 字段
的字段大小。
•initial_value(可選 — 默認是 1):從表裏獲取的初始值。
•increment_size(可選 — 默認是 1):對錶隨後的調用應該區分的值。
•optimizer (optional - defaults to ??): See 第 5.1.2.3.1 節 「標識符生成器的優化」.
5.1.2.3.1. 標識符生成器的優化
For identifier generators that store values in the database, it is inefficient for
them to hit the database on each and every call to generate a new identifier value.
Instead, you can group a bunch of them in memory and only hit the database when
you have exhausted your in-memory value group. This is the role of the pluggable
optimizers. Currently only the two enhanced generators (第 5.1.2.3 節 「加強的標識符
生成器」 support this operation.
•none(若是沒有指定 optimizer,一般這是缺省配置):這不會執行任何優化,在每次請求時
都訪問數據庫。
•hilo:對從數據庫獲取的值應用 hi/lo 算法。用於這個 optimizer 的從數據庫獲取的值應該是
有序的。它們代表「組編號」。increment_size 將乘之內存裏的值來定義組的「hi 值」。
•pooled:和 hilo 同樣,這個 optimizer 試圖最小化對數據庫的訪問。然而,咱們只是簡單地
把「下一組」的起始值而不是把序列值和分組算法的組合存入到數據庫結構裏。在這
裏,increment_size 表示數據庫裏的值。
Optimistic locking properties (optional)
89
5.1.2.4. Partial identifier generation
Hibernate supports the automatic generation of some of the identifier properties.
Simply use the @GeneratedValue annotation on one or several id properties.
警告
The Hibernate team has always felt such a construct as fundamentally
wrong. Try hard to fix your data model before using this feature.
@Entity
public class CustomerInventory implements Serializable {
@Id
@TableGenerator(name = "inventory",
table = "U_SEQUENCES",
pkColumnName = "S_ID",
valueColumnName = "S_NEXTNUM",
pkColumnValue = "inventory",
allocationSize = 1000)
@GeneratedValue(strategy = GenerationType.TABLE, generator = "inventory")
Integer id;
@Id @ManyToOne(cascade = CascadeType.MERGE)
Customer customer;
}
@Entity
public class Customer implements Serializable {
@Id
private int id;
}
You can also generate properties inside an @EmbeddedId class.
5.1.3. Optimistic locking properties (optional)
When using long transactions or conversations that span several database transactions,
it is useful to store versioning data to ensure that if the same entity is updated by
two conversations, the last to commit changes will be informed and not override the
other conversation's work. It guarantees some isolation while still allowing for good
scalability and works particularly well in read-often write-sometimes situations.
You can use two approaches: a dedicated version number or a timestamp.
一個脫管(detached)實例的 version 或 timestamp 屬性不能爲空(null),由於 Hibernate
無論 unsaved-value 被指定爲什麼種策略,它將任何屬性爲空的 version 或 timestamp 實例看做爲
瞬時(transient)實例。 避免 Hibernate 中的傳遞重附(transitive reattachment)問題的
第 5 章 對象/關係數據庫映射基礎(Basic O/R ...
90
一個簡單方法是 定義一個不能爲空的 version 或 timestamp 屬性,特別是在人們使用程序分配
的標識符(assigned identifiers) 或複合主鍵時很是有用。
5.1.3.1. Version number
You can add optimistic locking capability to an entity using the @Version annotation:
@Entity
public class Flight implements Serializable {
...
@Version
@Column(name="OPTLOCK")
public Integer getVersion() { ... }
}
The version property will be mapped to the OPTLOCK column, and the entity manager will
use it to detect conflicting updates (preventing lost updates you might otherwise see
with the last-commit-wins strategy).
The version column may be a numeric. Hibernate supports any kind of type provided that
you define and implement the appropriate UserVersionType.
The application must not alter the version number set up by Hibernate in
any way. To artificially increase the version number, check in Hibernate
Entity Manager's reference documentation LockModeType.OPTIMISTIC_FORCE_INCREMENT or
LockModeType.PESSIMISTIC_FORCE_INCREMENT.
If the version number is generated by the database (via a trigger for example), make
sure to use @org.hibernate.annotations.Generated(GenerationTime.ALWAYS).
To declare a version property in hbm.xml, use:
<version
column="version_column"
name="propertyName"
type="typename"
access="field|property|ClassName"
unsaved-value="null|negative|undefined"
generated="never|always"
insert="true|false"
node="element-name|@attribute-name|element/@attribute|."
/>
column(可選 — 默認爲屬性名):指定持有版本號的字段名。
name:持久化類的屬性名。
type(可選 — 默認是 integer):版本號的類型。
Optimistic locking properties (optional)
91
access(可選 — 默認爲 property):Hibernate 用來訪問屬性值的策略。
unsaved-value(可選 — 默認是 undefined):用於標明某個實例時剛剛被實例化的(還沒有保
存)版本屬性值,依靠這個值就能夠把這種狀況 和已經在先前的 session 中保存或裝載的
脫管(detached)實例區分開來。(undefined 指明應被使用的標識屬性值。)
generated(可選 — 默認是 never):代表此版本屬性值是否其實是由數據庫生成的。請
參閱 generated properties 部分的討論。
insert(可選 — 默認是 true):代表此版本列應該包含在 SQL 插入語句中。只有當數據庫
字段有默認值 0 的時候,才能夠設置爲 false。
5.1.3.2. Timestamp
Alternatively, you can use a timestamp. Timestamps are a less safe implementation
of optimistic locking. However, sometimes an application might use the timestamps in
other ways as well.
Simply mark a property of type Date or Calendar as @Version.
@Entity
public class Flight implements Serializable {
...
@Version
public Date getLastUpdate() { ... }
}
When using timestamp versioning you can tell Hibernate where to retrieve the timestamp
value from - database or JVM - by optionally adding the @org.hibernate.annotations.Source
annotation to the property. Possible values for the value attribute of the annotation
are org.hibernate.annotations.SourceType.VM and org.hibernate.annotations.SourceType.DB. The
default is SourceType.DB which is also used in case there is no @Source annotation at all.
Like in the case of version numbers, the timestamp can also be
generated by the database instead of Hibernate. To do that, use
@org.hibernate.annotations.Generated(GenerationTime.ALWAYS).
In hbm.xml, use the <timestamp> element:
<timestamp
column="timestamp_column"
name="propertyName"
access="field|property|ClassName"
unsaved-value="null|undefined"
source="vm|db"
generated="never|always"
node="element-name|@attribute-name|element/@attribute|."
/>
第 5 章 對象/關係數據庫映射基礎(Basic O/R ...
92
column(可選 — 默認爲屬性名):存有時間戳的字段名。
name:在持久化類中的 JavaBeans 風格的屬性名,其 Java 類型是 Date 或者 Timestamp 的。
access(可選 — 默認爲 property):Hibernate 用來訪問屬性值的策略。
unsaved-value(可選 — 默認是 null):用於標明某個實例時剛剛被實例化的(還沒有保存)
版本屬性值,依靠這個值就能夠把這種狀況和已經在先前的 session 中保存或裝載的脫管
(detached)實例區分開來。(undefined 指明使用標識屬性值進行這種判斷。)
source(可選 — 默認是 vm):Hibernate 如何才能獲
取到時間戳的值呢?從數據庫,仍是當前 JVM?從數據庫獲取會帶來一些負擔,由於
Hibernate 必須訪問數據庫來得到「下一個值」,可是在集羣環境中會更安全些。還要注
意,並非全部的 Dialect(方言)都支持得到數據庫的當前時間戳的,而支持的數據庫中又
有一部分由於精度不足,用於鎖定是不安全的(例如 Oracle 8)。
generated(可選 - 默認是 never):指出時間戳值是否其實是由數據庫生成的。請參閱
generated properties 的討論。
注意
注意,<timestamp> 和 <version type="timestamp"> 是等價的。而且 <timestamp
source="db"> 和 <version type="dbtimestamp"> 是等價的。
5.1.4. Property
You need to decide which property needs to be made persistent in a given entity. This
differs slightly between the annotation driven metadata and the hbm.xml files.
5.1.4.1. Property mapping with annotations
In the annotations world, every non static non transient property (field or method
depending on the access type) of an entity is considered persistent, unless you annotate
it as @Transient. Not having an annotation for your property is equivalent to the
appropriate @Basic annotation.
The @Basic annotation allows you to declare the fetching strategy for a property. If set
to LAZY, specifies that this property should be fetched lazily when the instance variable
is first accessed. It requires build-time bytecode instrumentation, if your classes are
not instrumented, property level lazy loading is silently ignored. The default is EAGER.
You can also mark a property as not optional thanks to the @Basic.optional attribute.
This will ensure that the underlying column are not nullable (if possible). Note that a
better approach is to use the @NotNull annotation of the Bean Validation specification.
Let's look at a few examples:
public transient int counter; //transient property
private String firstname; //persistent property
Property
93
@Transient
String getLengthInMeter() { ... } //transient property
String getName() {... } // persistent property
@Basic
int getLength() { ... } // persistent property
@Basic(fetch = FetchType.LAZY)
String getDetailedComment() { ... } // persistent property
@Temporal(TemporalType.TIME)
java.util.Date getDepartureTime() { ... } // persistent property
@Enumerated(EnumType.STRING)
Starred getNote() { ... } //enum persisted as String in database
counter, a transient field, and lengthInMeter, a method annotated as @Transient, and will
be ignored by the Hibernate. name, length, and firstname properties are mapped persistent
and eagerly fetched (the default for simple properties). The detailedComment property
value will be lazily fetched from the database once a lazy property of the entity is
accessed for the first time. Usually you don't need to lazy simple properties (not to
be confused with lazy association fetching). The recommended alternative is to use the
projection capability of JP-QL (Java Persistence Query Language) or Criteria queries.
JPA support property mapping of all basic types supported by Hibernate (all basic Java
types , their respective wrappers and serializable classes). Hibernate Annotations
supports out of the box enum type mapping either into a ordinal column (saving
the enum ordinal) or a string based column (saving the enum string representation):
the persistence representation, defaulted to ordinal, can be overridden through the
@Enumerated annotation as shown in the note property example.
In plain Java APIs, the temporal precision of time is not defined. When dealing with
temporal data you might want to describe the expected precision in database. Temporal
data can have DATE, TIME, or TIMESTAMP precision (ie the actual date, only the time, or
both). Use the @Temporal annotation to fine tune that.
@Lob indicates that the property should be persisted in a Blob or a Clob depending on the
property type: java.sql.Clob, Character[], char[] and java.lang.String will be persisted
in a Clob. java.sql.Blob, Byte[], byte[] and Serializable type will be persisted in a Blob.
@Lob
public String getFullText() {
return fullText;
}
@Lob
public byte[] getFullCode() {
return fullCode;
}
第 5 章 對象/關係數據庫映射基礎(Basic O/R ...
94
If the property type implements java.io.Serializable and is not a basic type, and if the
property is not annotated with @Lob, then the Hibernate serializable type is used.
5.1.4.1.1. Type
You can also manually specify a type using the @org.hibernate.annotations.Type and some
parameters if needed. @Type.type could be:
1. Hibernate 基本類型名(好比:integer, string, character,date, timestamp, float, binary,
serializable, object, blob)。
2. 一個 Java 類的名字,這個類屬於一種默認基礎類型(好比:int, float,char,
java.lang.String, java.util.Date, java.lang.Integer, java.sql.Clob)。
3. 一個能夠序列化的 Java 類的名字。
4. 一個自定義類型的類的名字。(好比:com.illflow.type.MyCustomType)。
If you do not specify a type, Hibernate will use reflection upon the named property
and guess the correct Hibernate type. Hibernate will attempt to interpret the name of
the return class of the property getter using, in order, rules 2, 3, and 4.
@org.hibernate.annotations.TypeDef and @org.hibernate.annotations.TypeDefs allows you to
declare type definitions. These annotations can be placed at the class or package level.
Note that these definitions are global for the session factory (even when defined at the
class level). If the type is used on a single entity, you can place the definition on
the entity itself. Otherwise, it is recommended to place the definition at the package
level. In the example below, when Hibernate encounters a property of class PhoneNumer, it
delegates the persistence strategy to the custom mapping type PhoneNumberType. However,
properties belonging to other classes, too, can delegate their persistence strategy to
PhoneNumberType, by explicitly using the @Type annotation.
注意
Package level annotations are placed in a file named package-info.java
in the appropriate package. Place your annotations before the package
declaration.
@TypeDef(
name = "phoneNumber",
defaultForType = PhoneNumber.class,
typeClass = PhoneNumberType.class
)
@Entity
public class ContactDetails {
[...]
private PhoneNumber localPhoneNumber;
@Type(type="phoneNumber")
private OverseasPhoneNumber overseasPhoneNumber;
Property
95
[...]
}
The following example shows the usage of the parameters attribute to customize the
TypeDef.
//in org/hibernate/test/annotations/entity/package-info.java
@TypeDefs(
{
@TypeDef(
name="caster",
typeClass = CasterStringType.class,
parameters = {
@Parameter(name="cast", value="lower")
}
)
}
)
package org.hibernate.test.annotations.entity;
//in org/hibernate/test/annotations/entity/Forest.java
public class Forest {
@Type(type="caster")
public String getSmallText() {
...
}
When using composite user type, you will have to express column definitions. The @Columns
has been introduced for that purpose.
@Type(type="org.hibernate.test.annotations.entity.MonetaryAmountUserType")
@Columns(columns = {
@Column(name="r_amount"),
@Column(name="r_currency")
})
public MonetaryAmount getAmount() {
return amount;
}
public class MonetaryAmount implements Serializable {
private BigDecimal amount;
private Currency currency;
...
}
5.1.4.1.2. Access type
By default the access type of a class hierarchy is defined by the position of the
@Id or @EmbeddedId annotations. If these annotations are on a field, then only fields
are considered for persistence and the state is accessed via the field. If there
第 5 章 對象/關係數據庫映射基礎(Basic O/R ...
96
annotations are on a getter, then only the getters are considered for persistence and
the state is accessed via the getter/setter. That works well in practice and is the
recommended approach.
注意
The placement of annotations within a class hierarchy has to be consistent
(either field or on property) to be able to determine the default access
type. It is recommended to stick to one single annotation placement
strategy throughout your whole application.
However in some situations, you need to:
•force the access type of the entity hierarchy
•override the access type of a specific entity in the class hierarchy
•override the access type of an embeddable type
The best use case is an embeddable class used by several entities that might not
use the same access type. In this case it is better to force the access type at the
embeddable class level.
To force the access type on a given class, use the @Access annotation as showed below:
@Entity
public class Order {
@Id private Long id;
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
@Embedded private Address address;
public Address getAddress() { return address; }
public void setAddress() { this.address = address; }
}
@Entity
public class User {
private Long id;
@Id public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
private Address address;
@Embedded public Address getAddress() { return address; }
public void setAddress() { this.address = address; }
}
@Embeddable
@Access(AcessType.PROPERTY)
public class Address {
private String street1;
Property
97
public String getStreet1() { return street1; }
public void setStreet1() { this.street1 = street1; }
private hashCode; //not persistent
}
You can also override the access type of a single property while keeping the other
properties standard.
@Entity
public class Order {
@Id private Long id;
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
@Transient private String userId;
@Transient private String orderId;
@Access(AccessType.PROPERTY)
public String getOrderNumber() { return userId + ":" + orderId; }
public void setOrderNumber() { this.userId = ...; this.orderId = ...; }
}
In this example, the default access type is FIELD except for the orderNumber property.
Note that the corresponding field, if any must be marked as @Transient or transient.
@org.hibernate.annotations.AccessType
The annotation @org.hibernate.annotations.AccessType should be considered
deprecated for FIELD and PROPERTY access. It is still useful however if
you need to use a custom access type.
5.1.4.1.3. Optimistic lock
It is sometimes useful to avoid increasing the version number even if a given property
is dirty (particularly collections). You can do that by annotating the property (or
collection) with @OptimisticLock(excluded=true).
More formally, specifies that updates to this property do not require acquisition of
the optimistic lock.
5.1.4.1.4. Declaring column attributes
The column(s) used for a property mapping can be defined using the @Column annotation.
Use it to override default values (see the JPA specification for more information on the
defaults). You can use this annotation at the property level for properties that are:
•not annotated at all
第 5 章 對象/關係數據庫映射基礎(Basic O/R ...
98
•annotated with @Basic
•annotated with @Version
•annotated with @Lob
•annotated with @Temporal
@Entity
public class Flight implements Serializable {
...
@Column(updatable = false, name = "flight_name", nullable = false, length=50)
public String getName() { ... }
The name property is mapped to the flight_name column, which is not nullable, has a
length of 50 and is not updatable (making the property immutable).
This annotation can be applied to regular properties as well as @Id or @Version
properties.
@Column(
name="columnName";
boolean unique() default false;
boolean nullable() default true;
boolean insertable() default true;
boolean updatable() default true;
String columnDefinition() default "";
String table() default "";
int length() default 255;
int precision() default 0; // decimal precision
int scale() default 0; // decimal scale
name (optional): the column name (default to the property name)
unique (optional): set a unique constraint on this column or not (default false)
nullable (optional): set the column as nullable (default true).
insertable (optional): whether or not the column will be part of the insert statement
(default true)
updatable (optional): whether or not the column will be part of the update statement
(default true)
columnDefinition (optional): override the sql DDL fragment for this particular column
(non portable)
table (optional): define the targeted table (default primary table)
length (optional): column length (default 255)
Property
99
precision (optional): column decimal precision (default 0)
scale (optional): column decimal scale if useful (default 0)
5.1.4.1.5. Formula
Sometimes, you want the Database to do some computation for you rather than in the JVM,
you might also create some kind of virtual column. You can use a SQL fragment (aka
formula) instead of mapping a property into a column. This kind of property is read
only (its value is calculated by your formula fragment).
@Formula("obj_length * obj_height * obj_width")
public long getObjectVolume()
The SQL fragment can be as complex as you want and even include subselects.
5.1.4.1.6. Non-annotated property defaults
If a property is not annotated, the following rules apply:
•If the property is of a single type, it is mapped as @Basic
•Otherwise, if the type of the property is annotated as @Embeddable, it is mapped
as @Embedded
•Otherwise, if the type of the property is Serializable, it is mapped as @Basic in a
column holding the object in its serialized version
•Otherwise, if the type of the property is java.sql.Clob or java.sql.Blob, it is mapped
as @Lob with the appropriate LobType
5.1.4.2. Property mapping with hbm.xml
<property> 元素爲類定義了一個持久化的、JavaBean 風格的屬性。
<property
name="propertyName"
column="column_name"
type="typename"
update="true|false"
insert="true|false"
formula="arbitrary SQL expression"
access="field|property|ClassName"
lazy="true|false"
unique="true|false"
第 5 章 對象/關係數據庫映射基礎(Basic O/R ...
100
not-null="true|false"
optimistic-lock="true|false"
generated="never|insert|always"
node="element-name|@attribute-name|element/@attribute|."
index="index_name"
unique_key="unique_key_id"
length="L"
precision="P"
scale="S"
/>
name:屬性的名字,以小寫字母開頭。
column(可選 — 默認爲屬性名字):對應的數據庫字段名。 也能夠經過嵌套的 <column> 元
素指定。
type(可選):一個 Hibernate 類型的名字。
update, insert(可選 — 默認爲 true): 代表用於 UPDATE 和/或 INSERT 的 SQL
語句中是否包含這個被映射了的字段。這兩者若是都設置爲 false 則代表這是一個「外源性
(derived)」的屬性,它的值來源於映射到同一個(或多個) 字段的某些其餘屬性,或者
經過一個 trigger(觸發器)或其餘程序生成。
formula(可選):一個 SQL 表達式,定義了這個計算 (computed) 屬性的值。計算屬性沒
有和它對應的數據庫字段。
access(可選 — 默認爲 property):Hibernate 用來訪問屬性值的策略。
lazy(可選 — 默認爲 false):指定 指定實例變量第一次被訪問時,這個屬性是否延遲抓
取(fetched lazily)( 須要運行時字節碼加強)。
unique(可選):使用 DDL 爲該字段添加惟一的約束。一樣,容許它做爲 property-ref 引用
的目標。
not-null(可選):使用 DDL 爲該字段添加能否爲空(nullability)的約束。
optimistic-lock(可選 — 默認爲 true):指定這個屬性在作更新時是否須要得到樂觀鎖定
(optimistic lock)。換句話說,它決定這個屬性發生髒數據時版本(version)的值是否
增加。
generated(可選 — 默認爲 never):代表此屬性值是否其實是由數據庫生成的。請參閱
generated properties 的討論。
typename 能夠是以下幾種:
1. Hibernate 基本類型名(好比:integer, string, character,date, timestamp, float, binary,
serializable, object, blob)。
2. 一個 Java 類的名字,這個類屬於一種默認基礎類型(好比:int, float,char,
java.lang.String, java.util.Date, java.lang.Integer, java.sql.Clob)。
3. 一個能夠序列化的 Java 類的名字。
4. 一個自定義類型的類的名字。(好比:com.illflow.type.MyCustomType)。
若是你沒有指定類型,Hibernarte 會使用反射來獲得這個名字的屬性,以此來猜想正確的
Hibernate 類型。Hibernate 會按照規則 2,3,4 的順序對屬性讀取器(getter方法)的
返回類進行解釋。然而,這還不夠。 在某些狀況下你仍然須要 type 屬性。(好比,爲了區
別Hibernate.DATE 和Hibernate.TIMESTAMP,或者爲了指定一個自定義類型。)
Embedded objects (aka components)
101
access 屬性用來讓你控制 Hibernate 如何在運行時訪問屬性。在默認狀況下,Hibernate 會
使用屬性的 get/set 方法對(pair)。若是你指明 access="field",Hibernate 會忽略 get/
set 方法對,直接使用反射來訪問成員變量。你也能夠指定你本身的策略,這就須要你本身實現
org.hibernate.property.PropertyAccessor 接口,再在 access 中設置你自定義策略類的名字。
衍生屬性(derive propertie)是一個特別強大的特徵。這些屬性應該定義爲只讀,屬性值在裝
載時計算生成。 你用一個 SQL 表達式生成計算的結果,它會在這個實例轉載時翻譯成一個 SQL
查詢的 SELECT 子查詢語句。
<property name="totalPrice"
formula="( SELECT SUM (li.quantity*p.price) FROM LineItem li, Product p
WHERE li.productId = p.productId
AND li.customerId = customerId
AND li.orderNumber = orderNumber )"/>
注意,你能夠使用實體本身的表,而不用爲這個特別的列定義別名(上面例子中的
customerId)。同時注意,若是你不喜歡使用屬性, 你能夠使用嵌套的 <formula> 映射元素。
5.1.5. Embedded objects (aka components)
Embeddable objects (or components) are objects whose properties are mapped to the
same table as the owning entity's table. Components can, in turn, declare their own
properties, components or collections
It is possible to declare an embedded component inside an entity and even override
its column mapping. Component classes have to be annotated at the class level with the
@Embeddable annotation. It is possible to override the column mapping of an embedded
object for a particular entity using the @Embedded and @AttributeOverride annotation in
the associated property:
@Entity
public class Person implements Serializable {
// Persistent component using defaults
Address homeAddress;
@Embedded
@AttributeOverrides( {
@AttributeOverride(name="iso2", column = @Column(name="bornIso2") ),
@AttributeOverride(name="name", column = @Column(name="bornCountryName") )
} )
Country bornIn;
...
}
@Embeddable
public class Address implements Serializable {
String city;
第 5 章 對象/關係數據庫映射基礎(Basic O/R ...
102
Country nationality; //no overriding here
}
@Embeddable
public class Country implements Serializable {
private String iso2;
@Column(name="countryName") private String name;
public String getIso2() { return iso2; }
public void setIso2(String iso2) { this.iso2 = iso2; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
...
}
An embeddable object inherits the access type of its owning entity (note that you can
override that using the @Access annotation).
The Person entity has two component properties, homeAddress and bornIn. homeAddress property
has not been annotated, but Hibernate will guess that it is a persistent component by
looking for the @Embeddable annotation in the Address class. We also override the mapping
of a column name (to bornCountryName) with the @Embedded and @AttributeOverride annotations
for each mapped attribute of Country. As you can see, Country is also a nested component
of Address, again using auto-detection by Hibernate and JPA defaults. Overriding columns
of embedded objects of embedded objects is through dotted expressions.
@Embedded
@AttributeOverrides( {
@AttributeOverride(name="city", column = @Column(name="fld_city") ),
@AttributeOverride(name="nationality.iso2", column = @Column(name="nat_Iso2") ),
@AttributeOverride(name="nationality.name", column = @Column(name="nat_CountryName") )
//nationality columns in homeAddress are overridden
} )
Address homeAddress;
Hibernate Annotations supports something that is not explicitly supported by the JPA
specification. You can annotate a embedded object with the @MappedSuperclass annotation to
make the superclass properties persistent (see @MappedSuperclass for more informations).
You can also use association annotations in an embeddable object (ie @OneToOne,
@ManyToOne, @OneToMany or @ManyToMany). To override the association columns you can use
@AssociationOverride.
If you want to have the same embeddable object type twice in the same entity, the column
name defaulting will not work as several embedded objects would share the same set of
columns. In plain JPA, you need to override at least one set of columns. Hibernate,
Embedded objects (aka components)
103
however, allows you to enhance the default naming mechanism through the NamingStrategy
interface. You can write a strategy that prevent name clashing in such a situation.
DefaultComponentSafeNamingStrategy is an example of this.
If a property of the embedded object points back to the owning entity, annotate it
with the @Parent annotation. Hibernate will make sure this property is properly loaded
with the entity reference.
In XML, use the <component> element.
<component
name="propertyName"
class="className"
insert="true|false"
update="true|false"
access="field|property|ClassName"
lazy="true|false"
optimistic-lock="true|false"
unique="true|false"
node="element-name|."
>
<property ...../>
<many-to-one .... />
........
</component>
name:屬性名。
class(可選 — 默認爲經過反射獲得的屬性類型):組件(子)類的名字。
insert:被映射的字段是否出如今 SQL 的 INSERT 語句中?
update:被映射的字段是否出如今 SQL 的 UPDATE 語句中?
access(可選 — 默認爲 property):Hibernate 用來訪問屬性值的策略。
lazy(可選 — 默認是 false):代表此組件應在實例變量第一次被訪問的時候延遲加載(需
要編譯時字節碼裝置器)。
optimistic-lock(可選 — 默認是 true):代表更新此組件是否須要獲取樂觀鎖。換句話
說,當這個屬性變髒時,是否增長版本號(Version)。
unique(可選 — 默認是 false):代表組件映射的全部字段上都有惟一性約束。
其 <property> 子標籤爲子類的一些屬性與表字段之間創建映射。
<component> 元素容許加入一個 <parent> 子元素,在組件類內部就能夠有一個指向其容器的實體
的反向引用。
The <dynamic-component> element allows a Map to be mapped as a component, where the
property names refer to keys of the map. See 第 9.5 節 「動態組件(Dynamic components)」
for more information. This feature is not supported in annotations.
第 5 章 對象/關係數據庫映射基礎(Basic O/R ...
104
5.1.6. Inheritance strategy
Java is a language supporting polymorphism: a class can inherit from another. Several
strategies are possible to persist a class hierarchy:
•Single table per class hierarchy strategy: a single table hosts all the instances
of a class hierarchy
•Joined subclass strategy: one table per class and subclass is present and each table
persist the properties specific to a given subclass. The state of the entity is then
stored in its corresponding class table and all its superclasses
•Table per class strategy: one table per concrete class and subclass is present and
each table persist the properties of the class and its superclasses. The state of
the entity is then stored entirely in the dedicated table for its class.
5.1.6.1. Single table per class hierarchy strategy
With this approach the properties of all the subclasses in a given mapped class hierarchy
are stored in a single table.
Each subclass declares its own persistent properties and subclasses. Version and id
properties are assumed to be inherited from the root class. Each subclass in a hierarchy
must define a unique discriminator value. If this is not specified, the fully qualified
Java class name is used.
@Entity
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(
name="planetype",
discriminatorType=DiscriminatorType.STRING
)
@DiscriminatorValue("Plane")
public class Plane { ... }
@Entity
@DiscriminatorValue("A320")
public class A320 extends Plane { ... }
In hbm.xml, for the table-per-class-hierarchy mapping strategy, the <subclass>
declaration is used. For example:
<subclass
name="ClassName"
discriminator-value="discriminator_value"
proxy="ProxyInterface"
lazy="true|false"
Inheritance strategy
105
dynamic-update="true|false"
dynamic-insert="true|false"
entity-name="EntityName"
node="element-name"
extends="SuperclassName">
<property .... />
.....
</subclass>
name:子類的全限定名。
discriminator-value(辨別標誌)(可選 — 默認爲類名):一個用於區分每一個獨立的子類的
值。
proxy(可選):指定一個類或者接口,在延遲裝載時做爲代理使用。
lazy(可選,默認是 true):設置爲 lazy="false" 禁止使用延遲裝載。
For information about inheritance mappings see 第 10 章 繼承映射(Inheritance Mapping)
.
5.1.6.1.1. 鑑別器(discriminator)
Discriminators are required for polymorphic persistence using the table-per-classhierarchy
mapping strategy. It declares a discriminator column of the table. The
discriminator column contains marker values that tell the persistence layer what
subclass to instantiate for a particular row. Hibernate Core supports the follwoing
restricted set of types as discriminator column: string, character, integer, byte, short,
boolean, yes_no, true_false.
Use the @DiscriminatorColumn to define the discriminator column as well as the
discriminator type.
注意
The enum DiscriminatorType used in javax.persitence.DiscriminatorColumn only
contains the values STRING, CHAR and INTEGER which means that not all
Hibernate supported types are available via the @DiscriminatorColumn
annotation.
You can also use @DiscriminatorFormula to express in SQL a virtual discriminator column.
This is particularly useful when the discriminator value can be extracted from one or
more columns of the table. Both @DiscriminatorColumn and @DiscriminatorFormula are to be
set on the root entity (once per persisted hierarchy).
@org.hibernate.annotations.DiscriminatorOptions allows to optionally specify Hibernate
specific discriminator options which are not standardized in JPA. The available options
are force and insert. The force attribute is useful if the table contains rows with
"extra" discriminator values that are not mapped to a persistent class. This could for
第 5 章 對象/關係數據庫映射基礎(Basic O/R ...
106
example occur when working with a legacy database. If force is set to true Hibernate
will specify the allowed discriminator values in the SELECT query, even when retrieving
all instances of the root class. The second option - insert - tells Hibernate whether
or not to include the discriminator column in SQL INSERTs. Usually the column should be
part of the INSERT statement, but if your discriminator column is also part of a mapped
composite identifier you have to set this option to false.
提示
There is also a @org.hibernate.annotations.ForceDiscriminator annotation which
is deprecated since version 3.6. Use @DiscriminatorOptions instead.
Finally, use @DiscriminatorValue on each class of the hierarchy to specify the value stored
in the discriminator column for a given entity. If you do not set @DiscriminatorValue
on a class, the fully qualified class name is used.
@Entity
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(
name="planetype",
discriminatorType=DiscriminatorType.STRING
)
@DiscriminatorValue("Plane")
public class Plane { ... }
@Entity
@DiscriminatorValue("A320")
public class A320 extends Plane { ... }
In hbm.xml, the <discriminator> element is used to define the discriminator column or
formula:
<discriminator
column="discriminator_column"
type="discriminator_type"
force="true|false"
insert="true|false"
formula="arbitrary sql expression"
/>
column(可選 — 默認爲 class)discriminator 器字段的名字。
type(可選 — 默認爲 string)一個 Hibernate 字段類型的名字
force(強制)(可選 — 默認爲 false)"強制" Hibernate 指定容許的鑑別器值,即便當取得
的全部實例都是根類的。
Inheritance strategy
107
insert(可選 - 默認爲true)若是你的鑑別器字段也是映射爲複合標識(composite
identifier)的一部分,則需將這個值設爲 false。(告訴 Hibernate 在作 SQL INSERT 時
不包含該列)
formula(可選)一個 SQL 表達式,在類型判斷(判斷是父類仍是具體子類 — 譯註)時執
行。可用於基於內容的鑑別器。
鑑別器字段的實際值是根據 <class> 和 <subclass> 元素中的 discriminator-value 屬性得來的。
使用 formula 屬性你能夠定義一個 SQL 表達式,用來判斷一行數據的類型。
<discriminator
formula="case when CLASS_TYPE in ('a', 'b', 'c') then 0 else 1 end"
type="integer"/>
5.1.6.2. Joined subclass strategy
Each subclass can also be mapped to its own table. This is called the table-persubclass
mapping strategy. An inherited state is retrieved by joining with the table
of the superclass. A discriminator column is not required for this mapping strategy.
Each subclass must, however, declare a table column holding the object identifier. The
primary key of this table is also a foreign key to the superclass table and described
by the @PrimaryKeyJoinColumns or the <key> element.
@Entity @Table(name="CATS")
@Inheritance(strategy=InheritanceType.JOINED)
public class Cat implements Serializable {
@Id @GeneratedValue(generator="cat-uuid")
@GenericGenerator(name="cat-uuid", strategy="uuid")
String getId() { return id; }
...
}
@Entity @Table(name="DOMESTIC_CATS")
@PrimaryKeyJoinColumn(name="CAT")
public class DomesticCat extends Cat {
public String getName() { return name; }
}
注意
The table name still defaults to the non qualified class name. Also if
@PrimaryKeyJoinColumn is not set, the primary key / foreign key columns are
assumed to have the same names as the primary key columns of the primary
table of the superclass.
第 5 章 對象/關係數據庫映射基礎(Basic O/R ...
108
In hbm.xml, use the <joined-subclass> element. For example:
<joined-subclass
name="ClassName"
table="tablename"
proxy="ProxyInterface"
lazy="true|false"
dynamic-update="true|false"
dynamic-insert="true|false"
schema="schema"
catalog="catalog"
extends="SuperclassName"
persister="ClassName"
subselect="SQL expression"
entity-name="EntityName"
node="element-name">
<key .... >
<property .... />
.....
</joined-subclass>
name:子類的全限定名。
table:子類的表名。
proxy(可選):指定一個類或者接口,在延遲裝載時做爲代理使用。
lazy(可選,默認是 true):設置爲 lazy="false" 禁止使用延遲裝載。
Use the <key> element to declare the primary key / foreign key column. The mapping at
the start of the chapter would then be re-written as:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="eg">
<class name="Cat" table="CATS">
<id name="id" column="uid" type="long">
<generator class="hilo"/>
</id>
<property name="birthdate" type="date"/>
<property name="color" not-null="true"/>
<property name="sex" not-null="true"/>
<property name="weight"/>
<many-to-one name="mate"/>
<set name="kittens">
<key column="MOTHER"/>
<one-to-many class="Cat"/>
</set>
Inheritance strategy
109
<joined-subclass name="DomesticCat" table="DOMESTIC_CATS">
<key column="CAT"/>
<property name="name" type="string"/>
</joined-subclass>
</class>
<class name="eg.Dog">
<!-- mapping for Dog could go here -->
</class>
</hibernate-mapping>
For information about inheritance mappings see 第 10 章 繼承映射(Inheritance Mapping)
.
5.1.6.3. Table per class strategy
A third option is to map only the concrete classes of an inheritance hierarchy to tables.
This is called the table-per-concrete-class strategy. Each table defines all persistent
states of the class, including the inherited state. In Hibernate, it is not necessary
to explicitly map such inheritance hierarchies. You can map each class as a separate
entity root. However, if you wish use polymorphic associations (e.g. an association to
the superclass of your hierarchy), you need to use the union subclass mapping.
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class Flight implements Serializable { ... }
Or in hbm.xml:
<union-subclass
name="ClassName"
table="tablename"
proxy="ProxyInterface"
lazy="true|false"
dynamic-update="true|false"
dynamic-insert="true|false"
schema="schema"
catalog="catalog"
extends="SuperclassName"
abstract="true|false"
persister="ClassName"
subselect="SQL expression"
entity-name="EntityName"
node="element-name">
<property .... />
.....
第 5 章 對象/關係數據庫映射基礎(Basic O/R ...
110
</union-subclass>
name:子類的全限定名。
table:子類的表名。
proxy(可選):指定一個類或者接口,在延遲裝載時做爲代理使用。
lazy(可選,默認是 true):設置爲 lazy="false" 禁止使用延遲裝載。
這種映射策略不須要指定辨別標誌(discriminator)字段。
For information about inheritance mappings see 第 10 章 繼承映射(Inheritance Mapping)
.
5.1.6.4. Inherit properties from superclasses
This is sometimes useful to share common properties through a technical or a business
superclass without including it as a regular mapped entity (ie no specific table for
this entity). For that purpose you can map them as @MappedSuperclass.
@MappedSuperclass
public class BaseEntity {
@Basic
@Temporal(TemporalType.TIMESTAMP)
public Date getLastUpdate() { ... }
public String getLastUpdater() { ... }
...
}
@Entity class Order extends BaseEntity {
@Id public Integer getId() { ... }
...
}
In database, this hierarchy will be represented as an Order table having the id,
lastUpdate and lastUpdater columns. The embedded superclass property mappings are copied
into their entity subclasses. Remember that the embeddable superclass is not the root
of the hierarchy though.
注意
Properties from superclasses not mapped as @MappedSuperclass are ignored.
注意
The default access type (field or methods) is used, unless you use the
@Access annotation.
Inheritance strategy
111
注意
The same notion can be applied to @Embeddable objects to persist properties
from their superclasses. You also need to use @MappedSuperclass to do that
(this should not be considered as a standard EJB3 feature though)
注意
It is allowed to mark a class as @MappedSuperclass in the middle of the
mapped inheritance hierarchy.
注意
Any class in the hierarchy non annotated with @MappedSuperclass nor @Entity
will be ignored.
You can override columns defined in entity superclasses at the root entity level using
the @AttributeOverride annotation.
@MappedSuperclass
public class FlyingObject implements Serializable {
public int getAltitude() {
return altitude;
}
@Transient
public int getMetricAltitude() {
return metricAltitude;
}
@ManyToOne
public PropulsionType getPropulsion() {
return metricAltitude;
}
...
}
@Entity
@AttributeOverride( name="altitude", column = @Column(name="fld_altitude") )
@AssociationOverride(
name="propulsion",
joinColumns = @JoinColumn(name="fld_propulsion_fk")
)
public class Plane extends FlyingObject {
...
第 5 章 對象/關係數據庫映射基礎(Basic O/R ...
112
}
The altitude property will be persisted in an fld_altitude column of table Plane and the
propulsion association will be materialized in a fld_propulsion_fk foreign key column.
You can define @AttributeOverride(s) and @AssociationOverride(s) on @Entity classes,
@MappedSuperclass classes and properties pointing to an @Embeddable object.
In hbm.xml, simply map the properties of the superclass in the <class> element of the
entity that needs to inherit them.
5.1.6.5. Mapping one entity to several tables
While not recommended for a fresh schema, some legacy databases force your to map a
single entity on several tables.
Using the @SecondaryTable or @SecondaryTables class level annotations. To express that a
column is in a particular table, use the table parameter of @Column or @JoinColumn.
@Entity
@Table(name="MainCat")
@SecondaryTables({
@SecondaryTable(name="Cat1", pkJoinColumns={
@PrimaryKeyJoinColumn(name="cat_id", referencedColumnName="id")
),
@SecondaryTable(name="Cat2", uniqueConstraints={@UniqueConstraint(columnNames={"storyPart2"})})
})
public class Cat implements Serializable {
private Integer id;
private String name;
private String storyPart1;
private String storyPart2;
@Id @GeneratedValue
public Integer getId() {
return id;
}
public String getName() {
return name;
}
@Column(table="Cat1")
public String getStoryPart1() {
return storyPart1;
}
@Column(table="Cat2")
public String getStoryPart2() {
return storyPart2;
}
}
Inheritance strategy
113
In this example, name will be in MainCat. storyPart1 will be in Cat1 and storyPart2 will
be in Cat2. Cat1 will be joined to MainCat using the cat_id as a foreign key, and Cat2
using id (ie the same column name, the MainCat id column has). Plus a unique constraint
on storyPart2 has been set.
There is also additional tuning accessible via the @org.hibernate.annotations.Table
annotation:
•fetch: If set to JOIN, the default, Hibernate will use an inner join to retrieve
a secondary table defined by a class or its superclasses and an outer join for a
secondary table defined by a subclass. If set to SELECT then Hibernate will use a
sequential select for a secondary table defined on a subclass, which will be issued
only if a row turns out to represent an instance of the subclass. Inner joins will
still be used to retrieve a secondary defined by the class and its superclasses.
•inverse: If true, Hibernate will not try to insert or update the properties defined
by this join. Default to false.
•optional: If enabled (the default), Hibernate will insert a row only if the properties
defined by this join are non-null and will always use an outer join to retrieve
the properties.
•foreignKey: defines the Foreign Key name of a secondary table pointing back to the
primary table.
Make sure to use the secondary table name in the appliesto property
@Entity
@Table(name="MainCat")
@SecondaryTable(name="Cat1")
@org.hibernate.annotations.Table(
appliesTo="Cat1",
fetch=FetchMode.SELECT,
optional=true)
public class Cat implements Serializable {
private Integer id;
private String name;
private String storyPart1;
private String storyPart2;
@Id @GeneratedValue
public Integer getId() {
return id;
}
public String getName() {
return name;
}
@Column(table="Cat1")
public String getStoryPart1() {
第 5 章 對象/關係數據庫映射基礎(Basic O/R ...
114
return storyPart1;
}
@Column(table="Cat2")
public String getStoryPart2() {
return storyPart2;
}
}
In hbm.xml, use the <join> element.
<join
table="tablename"
schema="owner"
catalog="catalog"
fetch="join|select"
inverse="true|false"
optional="true|false">
<key ... />
<property ... />
...
</join>
table:被鏈接表的名稱。
schema(可選):覆蓋在根 <hibernate-mapping> 元素中指定的 schema 名字。
catalog(可選):覆蓋在根 <hibernate-mapping> 元素中指定的 catalog 名字。
fetch(可選 — 默認是 join):若是設置爲默認值 join,Hibernate 將使用一個內鏈接來
獲得這個類或其超類定義的 <join>,而使用一個外鏈接來獲得其子類定義的 <join>。若是設
置爲 select,則 Hibernate 將爲子類定義的 <join> 使用順序選擇。這僅在一行數據表示一
個子類的對象的時候纔會發生。對這個類和其超類定義的 <join>,依然會使用內鏈接獲得。
inverse(可選 — 默認是 false):若是打開,Hibernate 不會插入或者更新此鏈接定義的
屬性。
optional(可選 — 默認是 false):若是打開,Hibernate 只會在此鏈接定義的屬性非空時
插入一行數據,而且老是使用一個外鏈接來獲得這些屬性。
例如,一我的(person)的地址(address)信息能夠被映射到單獨的表中(並保留全部屬性的值
類型語義):
<class name="Person"
table="PERSON">
<id name="id" column="PERSON_ID">...</id>
<join table="ADDRESS">
<key column="ADDRESS_ID"/>
Mapping one to one and one to many associations
115
<property name="address"/>
<property name="zip"/>
<property name="country"/>
</join>
...
此特性經常對遺留數據模型有用,咱們推薦表個數比類個數少,以及細粒度的領域模型。然而,
在單獨的繼承樹上切換繼承映射策略是有用的,後面會解釋這點。
5.1.7. Mapping one to one and one to many associations
To link one entity to an other, you need to map the association property as a to one
association. In the relational model, you can either use a foreign key or an association
table, or (a bit less common) share the same primary key value between the two entities.
To mark an association, use either @ManyToOne or @OnetoOne.
@ManyToOne and @OneToOne have a parameter named targetEntity which describes the target
entity name. You usually don't need this parameter since the default value (the type of
the property that stores the association) is good in almost all cases. However this is
useful when you want to use interfaces as the return type instead of the regular entity.
Setting a value of the cascade attribute to any meaningful value other than nothing
will propagate certain operations to the associated object. The meaningful values are
divided into three categories.
1. basic operations, which include: persist, merge, delete, save-update, evict, replicate,
lock and refresh;
2. special values: delete-orphan or all ;
3. comma-separated combinations of operation names: cascade="persist,merge,evict" or
cascade="all,delete-orphan". See 第 11.11 節 「傳播性持久化(transitive persistence)」
for a full explanation. Note that single valued many-to-one associations do not
support orphan delete.
By default, single point associations are eagerly fetched in JPA 2. You can mark it
as lazily fetched by using @ManyToOne(fetch=FetchType.LAZY) in which case Hibernate will
proxy the association and load it when the state of the associated entity is reached.
You can force Hibernate not to use a proxy by using @LazyToOne(NO_PROXY). In this case, the
property is fetched lazily when the instance variable is first accessed. This requires
build-time bytecode instrumentation. lazy="false" specifies that the association will
always be eagerly fetched.
With the default JPA options, single-ended associations are loaded with a subsequent
select if set to LAZY, or a SQL JOIN is used for EAGER associations. You can however
adjust the fetching strategy, ie how data is fetched by using @Fetch. FetchMode can be
第 5 章 對象/關係數據庫映射基礎(Basic O/R ...
116
SELECT (a select is triggered when the association needs to be loaded) or JOIN (use a
SQL JOIN to load the association while loading the owner entity). JOIN overrides any
lazy attribute (an association loaded through a JOIN strategy cannot be lazy).
5.1.7.1. Using a foreign key or an association table
An ordinary association to another persistent class is declared using a
•@ManyToOne if several entities can point to the the target entity
•@OneToOne if only a single entity can point to the the target entity
and a foreign key in one table is referencing the primary key column(s) of the target
table.
@Entity
public class Flight implements Serializable {
@ManyToOne( cascade = {CascadeType.PERSIST, CascadeType.MERGE} )
@JoinColumn(name="COMP_ID")
public Company getCompany() {
return company;
}
...
}
The @JoinColumn attribute is optional, the default value(s) is the concatenation of the
name of the relationship in the owner side, _ (underscore), and the name of the primary
key column in the owned side. In this example company_id because the property name is
company and the column id of Company is id.
@Entity
public class Flight implements Serializable {
@ManyToOne( cascade = {CascadeType.PERSIST, CascadeType.MERGE}, targetEntity=CompanyImpl.class )
@JoinColumn(name="COMP_ID")
public Company getCompany() {
return company;
}
...
}
public interface Company {
...
}
You can also map a to one association through an association table. This association
table described by the @JoinTable annotation will contains a foreign key referencing
back the entity table (through @JoinTable.joinColumns) and a a foreign key referencing
the target entity table (through @JoinTable.inverseJoinColumns).
Mapping one to one and one to many associations
117
@Entity
public class Flight implements Serializable {
@ManyToOne( cascade = {CascadeType.PERSIST, CascadeType.MERGE} )
@JoinTable(name="Flight_Company",
joinColumns = @JoinColumn(name="FLIGHT_ID"),
inverseJoinColumns = @JoinColumn(name="COMP_ID")
)
public Company getCompany() {
return company;
}
...
}
注意
You can use a SQL fragment to simulate a physical join column using
the @JoinColumnOrFormula / @JoinColumnOrformulas annotations (just like you
can use a SQL fragment to simulate a property column via the @Formula
annotation).
@Entity
public class Ticket implements Serializable {
@ManyToOne
@JoinColumnOrFormula(formula="(firstname + ' ' + lastname)")
public Person getOwner() {
return person;
}
...
}
You can mark an association as mandatory by using the optional=false attribute. We
recommend to use Bean Validation's @NotNull annotation as a better alternative however.
As a consequence, the foreign key column(s) will be marked as not nullable (if possible).
When Hibernate cannot resolve the association because the expected associated element
is not in database (wrong id on the association column), an exception is raised. This
might be inconvenient for legacy and badly maintained schemas. You can ask Hibernate
to ignore such elements instead of raising an exception using the @NotFound annotation.
例 5.1. @NotFound annotation
@Entity
public class Child {
...
@ManyToOne
@NotFound(action=NotFoundAction.IGNORE)
public Parent getParent() { ... }
第 5 章 對象/關係數據庫映射基礎(Basic O/R ...
118
...
}
Sometimes you want to delegate to your database the deletion of cascade when a given
entity is deleted. In this case Hibernate generates a cascade delete constraint at
the database level.
例 5.2. @OnDelete annotation
@Entity
public class Child {
...
@ManyToOne
@OnDelete(action=OnDeleteAction.CASCADE)
public Parent getParent() { ... }
...
}
Foreign key constraints, while generated by Hibernate, have a fairly unreadable name.
You can override the constraint name using @ForeignKey.
例 5.3. @ForeignKey annotation
@Entity
public class Child {
...
@ManyToOne
@ForeignKey(name="FK_PARENT")
public Parent getParent() { ... }
...
}
alter table Child add constraint FK_PARENT foreign key (parent_id) references Parent
Sometimes, you want to link one entity to an other not by the target entity primary
key but by a different unique key. You can achieve that by referencing the unique key
column(s) in @JoinColumn.referenceColumnName.
@Entity
class Person {
@Id Integer personNumber;
String firstName;
@Column(name="I")
String initial;
String lastName;
}
@Entity
Mapping one to one and one to many associations
119
class Home {
@ManyToOne
@JoinColumns({
@JoinColumn(name="first_name", referencedColumnName="firstName"),
@JoinColumn(name="init", referencedColumnName="I"),
@JoinColumn(name="last_name", referencedColumnName="lastName"),
})
Person owner
}
This is not encouraged however and should be reserved to legacy mappings.
In hbm.xml, mapping an association is similar. The main difference is that a @OneToOne
is mapped as <many-to-one unique="true"/>, let's dive into the subject.
<many-to-one
name="propertyName"
column="column_name"
class="ClassName"
cascade="cascade_style"
fetch="join|select"
update="true|false"
insert="true|false"
property-ref="propertyNameFromAssociatedClass"
access="field|property|ClassName"
unique="true|false"
not-null="true|false"
optimistic-lock="true|false"
lazy="proxy|no-proxy|false"
not-found="ignore|exception"
entity-name="EntityName"
formula="arbitrary SQL expression"
node="element-name|@attribute-name|element/@attribute|."
embed-xml="true|false"
index="index_name"
unique_key="unique_key_id"
foreign-key="foreign_key_name"
/>
name:屬性名。
column(可選):外鍵字段的名稱。也能夠經過嵌套的 <column> 指定。
class(可選 — 默認是經過反射獲得的屬性類型):被關聯的類的名字。
cascade(級聯)(可選)代表操做是否從父對象級聯到被關聯的對象。
fetch(可選 — 默認爲 select):在外鏈接抓取(outer-join fetching)和序列選擇抓取
(sequential select fetching)二者中選擇其一。
第 5 章 對象/關係數據庫映射基礎(Basic O/R ...
120
update, insert(可選 — 默認爲 true)指定對應的字段是否包含在用於 UPDATE 和/或 INSERT
的 SQL 語句中。若是兩者都是false,則這是一個純粹的 「外源性(derived)」關聯,它的
值是經過映射到同一個(或多個)字段的某些其餘屬性獲得 或者經過 trigger(觸發器)、
或其餘程序生成。
property-ref:(可選)被關聯到此外鍵的類中的對應屬性的名字。若是沒有指定,被關聯
類的主鍵將被使用。
access(可選 — 默認爲 property):Hibernate 用來訪問屬性值的策略。
unique(可選):使用 DDL 爲外鍵字段生成一個惟一約束。此外, 這也能夠用做 propertyref
的目標屬性。這使關聯同時具備一對一的效果。
not-null(可選):使用 DDL 爲外鍵字段生成一個非空約束。
optimistic-lock(可選 — 默認爲 true):指定這個屬性在作更新時是否須要得到樂觀鎖定
(optimistic lock)。換句話說,它決定這個屬性發生髒數據時版本(version)的值是否
增加。
lazy(可選 — 默認爲 proxy):默認狀況下,單點關聯是通過代理的。lazy="no-proxy" 指
定此屬性應該在實例變量第一次被訪問時應該延遲抓取(fetche lazily)(須要運行時字
節碼的加強)。lazy="false" 指定此關聯老是被預先抓取。
not-found(可選 - 默認爲exception):指定如何處理引用缺失行的外鍵:ignore 會把缺失
的行做爲一個空關聯處理。
entity-name(可選):被關聯的類的實體名。
formula(可選):SQL 表達式,用於定義 computed(計算出的)外鍵值。
Setting a value of the cascade attribute to any meaningful value other than none will
propagate certain operations to the associated object. The meaningful values are divided
into three categories. First, basic operations, which include: persist, merge, delete,
save-update, evict, replicate, lock and refresh; second, special values: delete-orphan; and
third,all comma-separated combinations of operation names: cascade="persist,merge,evict"
or cascade="all,delete-orphan". See 第 11.11 節 「傳播性持久化(transitive persistence)」
for a full explanation. Note that single valued, many-to-one and one-to-one,
associations do not support orphan delete.
一個典型的簡單 many-to-one 定義例子:
<many-to-one name="product" class="Product" column="PRODUCT_ID"/>
property-ref 屬性只應該用來對付遺留下來的數據庫系統,可能有外鍵指向對方關聯表的是個非
主鍵字段(可是應該是一個唯一關鍵字)的狀況下。這是一種十分醜陋的關係模型。好比說,假
設 Product 類有一個唯一的序列號,它並非主鍵。(unique 屬性控制 Hibernate 經過
SchemaExport 工具進行的 DDL 生成。)
<property name="serialNumber" unique="true" type="string" column="SERIAL_NUMBER"/>
那麼關於 OrderItem 的映射多是:
Mapping one to one and one to many associations
121
<many-to-one name="product" property-ref="serialNumber" column="PRODUCT_SERIAL_NUMBER"/>
固然,咱們決不鼓勵這種用法。
若是被引用的惟一主鍵由關聯實體的多個屬性組成,你應該在名稱爲 <properties> 的元素 裏面映
射全部關聯的屬性。
倘若被引用的惟一主鍵是組件的屬性,你能夠指定屬性路徑:
<many-to-one name="owner" property-ref="identity.ssn" column="OWNER_SSN"/>
5.1.7.2. Sharing the primary key with the associated entity
The second approach is to ensure an entity and its associated entity share the same
primary key. In this case the primary key column is also a foreign key and there is
no extra column. These associations are always one to one.
例 5.4. One to One association
@Entity
public class Body {
@Id
public Long getId() { return id; }
@OneToOne(cascade = CascadeType.ALL)
@MapsId
public Heart getHeart() {
return heart;
}
...
}
@Entity
public class Heart {
@Id
public Long getId() { ...}
}
注意
Many people got confused by these primary key based one to one
associations. They can only be lazily loaded if Hibernate knows that the
other side of the association is always present. To indicate to Hibernate
that it is the case, use @OneToOne(optional=false).
第 5 章 對象/關係數據庫映射基礎(Basic O/R ...
122
In hbm.xml, use the following mapping.
<one-to-one
name="propertyName"
class="ClassName"
cascade="cascade_style"
constrained="true|false"
fetch="join|select"
property-ref="propertyNameFromAssociatedClass"
access="field|property|ClassName"
formula="any SQL expression"
lazy="proxy|no-proxy|false"
entity-name="EntityName"
node="element-name|@attribute-name|element/@attribute|."
embed-xml="true|false"
foreign-key="foreign_key_name"
/>
name:屬性名。
class(可選 — 默認是經過反射獲得的屬性類型):被關聯的類的名字。
cascade(級聯)(可選)代表操做是否從父對象級聯到被關聯的對象。
constrained(約束)(可選)代表該類對應的表對應的數據庫表,和被關聯的對象所對應的
數據庫表之間,經過一個外鍵引用對主鍵進行約束。這個選項影響 save() 和 delete() 在級
聯執行時的前後順序以及決定該關聯可否被委託(也在 schema export tool 中被使用)。
fetch(可選 — 默認爲 select):在外鏈接抓取(outer-join fetching)和序列選擇抓取
(sequential select fetching)二者中選擇其一。
property-ref:(可選)指定關聯類的屬性名,這個屬性將會和本類的主鍵相對應。若是沒
有指定,會使用對方關聯類的主鍵。
access(可選 — 默認爲 property):Hibernate 用來訪問屬性值的策略。
formula (可選):絕大多數一對一的關聯都指向其實體的主鍵。在一些少見的狀況中,你
可能會指向其餘的一個或多個字段,或者是一個表達式,這些狀況下,你能夠用一個 SQL 公
式來表示。(能夠在 org.hibernate.test.onetooneformula 找到例子)
lazy(可選 — 默認爲 proxy):默認狀況下,單點關聯是通過代理的。lazy="no-proxy"指定
此屬性應該在實例變量第一次被訪問時應該延遲抓取(fetche lazily)(須要運行時字節
碼的加強)。 lazy="false"指定此關聯老是被預先抓取。注意,若是constrained="false", 不
可能使用代理,Hibernate會採起預先抓取。
entity-name(可選):被關聯的類的實體名。
主鍵關聯不須要額外的表字段;若是兩行是經過這種一對一關係相關聯的,那麼這兩行就共享同
樣的主關鍵字值。因此若是你但願兩個對象經過主鍵一對一關聯,你必須確認它們被賦予一樣的
標識值。
好比說,對下面的 Employee 和 Person 進行主鍵一對一關聯:
天然 ID(natural-id)
123
<one-to-one name="person" class="Person"/>
<one-to-one name="employee" class="Employee" constrained="true"/>
如今咱們必須確保 PERSON 和 EMPLOYEE 中相關的字段是相等的。咱們使用一個被成爲 foreign 的
特殊的 hibernate 標識符生成策略:
<class name="person" table="PERSON">
<id name="id" column="PERSON_ID">
<generator class="foreign">
<param name="property">employee</param>
</generator>
</id>
...
<one-to-one name="employee"
class="Employee"
constrained="true"/>
</class>
一個剛剛保存的 Person 實例被賦予和該 Person 的 employee 屬性所指向的 Employee 實例一樣
的關鍵字值。
5.1.8. 天然 ID(natural-id)
Although we recommend the use of surrogate keys as primary keys, you should try to
identify natural keys for all entities. A natural key is a property or combination
of properties that is unique and non-null. It is also immutable. Map the properties
of the natural key as @NaturalId or map them inside the <natural-id> element. Hibernate
will generate the necessary unique key and nullability constraints and, as a result,
your mapping will be more self-documenting.
@Entity
public class Citizen {
@Id
@GeneratedValue
private Integer id;
private String firstname;
private String lastname;
@NaturalId
@ManyToOne
private State state;
@NaturalId
private String ssn;
...
}
第 5 章 對象/關係數據庫映射基礎(Basic O/R ...
124
//and later on query
List results = s.createCriteria( Citizen.class )
.add( Restrictions.naturalId().set( "ssn", "1234" ).set( "state", ste ) )
.list();
Or in XML,
<natural-id mutable="true|false"/>
<property ... />
<many-to-one ... />
......
</natural-id>
咱們強烈建議你實現 equals() 和 hashCode() 方法,來比較實體的天然鍵屬性。
這一映射不是爲了把天然鍵做爲主鍵而準備的。
•mutable(可選,默認爲 false):默認狀況下,天然標識屬性被假定爲不可變的(常量)。
5.1.9. Any
There is one more type of property mapping. The @Any mapping defines a polymorphic
association to classes from multiple tables. This type of mapping requires more than
one column. The first column contains the type of the associated entity. The remaining
columns contain the identifier. It is impossible to specify a foreign key constraint for
this kind of association. This is not the usual way of mapping polymorphic associations
and you should use this only in special cases. For example, for audit logs, user
session data, etc.
The @Any annotation describes the column holding the metadata information. To link the
value of the metadata information and an actual entity type, The @AnyDef and @AnyDefs
annotations are used. The metaType attribute allows the application to specify a custom
type that maps database column values to persistent classes that have identifier
properties of the type specified by idType. You must specify the mapping from values
of the metaType to class names.
@Any( metaColumn = @Column( name = "property_type" ), fetch=FetchType.EAGER )
@AnyMetaDef(
idType = "integer",
metaType = "string",
metaValues = {
@MetaValue( value = "S", targetEntity = StringProperty.class ),
@MetaValue( value = "I", targetEntity = IntegerProperty.class )
} )
@JoinColumn( name = "property_id" )
public Property getMainProperty() {
Any
125
return mainProperty;
}
Note that @AnyDef can be mutualized and reused. It is recommended to place it as a
package metadata in this case.
//on a package
@AnyMetaDef( name="property"
idType = "integer",
metaType = "string",
metaValues = {
@MetaValue( value = "S", targetEntity = StringProperty.class ),
@MetaValue( value = "I", targetEntity = IntegerProperty.class )
} )
package org.hibernate.test.annotations.any;
//in a class
@Any( metaDef="property", metaColumn = @Column( name = "property_type" ), fetch=FetchType.EAGER )
@JoinColumn( name = "property_id" )
public Property getMainProperty() {
return mainProperty;
}
The hbm.xml equivalent is:
<any name="being" id-type="long" meta-type="string">
<meta-value value="TBL_ANIMAL" class="Animal"/>
<meta-value value="TBL_HUMAN" class="Human"/>
<meta-value value="TBL_ALIEN" class="Alien"/>
<column name="table_name"/>
<column name="id"/>
</any>
注意
You cannot mutualize the metadata in hbm.xml as you can in annotations.
<any
name="propertyName"
id-type="idtypename"
meta-type="metatypename"
cascade="cascade_style"
access="field|property|ClassName"
optimistic-lock="true|false"
第 5 章 對象/關係數據庫映射基礎(Basic O/R ...
126
>
<meta-value ... />
<meta-value ... />
.....
<column .... />
<column .... />
.....
</any>
name:屬性名
id-type:標識符類型
meta-type(可選 -默認是 string):容許辨別標誌(discriminator)映射的任何類型。
cascade(可選 — 默認是none):級聯的類型。
access(可選 — 默認爲 property):Hibernate 用來訪問屬性值的策略。
optimistic-lock(可選 — 默認是 true):代表更新此組件是否須要獲取樂觀鎖。換句話
說,當這個屬性變髒時,是否增長版本號(Version)。
5.1.10. 屬性(Properties)
<properties> 元素容許定義一個命名的邏輯分組(grouping)包含一個類中的多個屬性。這個元
素最重要的用處是容許多個屬性的組合做爲 property-ref 的目標(target)。這也是定義多字段
惟一約束的一種方便途徑。例如:
<properties
name="logicalName"
insert="true|false"
update="true|false"
optimistic-lock="true|false"
unique="true|false"
>
<property ...../>
<many-to-one .... />
........
</properties>
name:分組的邏輯名稱 — 不是 實際屬性的名稱。
insert:被映射的字段是否出如今 SQL 的 INSERT 語句中?
update:被映射的字段是否出如今 SQL 的 UPDATE 語句中?
optimistic-lock(可選 — 默認是 true):代表更新此組件是否須要獲取樂觀鎖。換句話
說,當這個屬性變髒時,是否增長版本號(Version)。
unique(可選 — 默認是 false):代表組件映射的全部字段上都有惟一性約束。
例如,若是咱們有以下的 <properties> 映射:
<class name="Person">
屬性(Properties)
127
<id name="personNumber"/>
...
<properties name="name"
unique="true" update="false">
<property name="firstName"/>
<property name="initial"/>
<property name="lastName"/>
</properties>
</class>
而後,咱們可能有一些遺留的數據關聯,引用 Person 表的這個惟一鍵,而不是主鍵:
<many-to-one name="owner"
class="Person" property-ref="name">
<column name="firstName"/>
<column name="initial"/>
<column name="lastName"/>
</many-to-one>
注意
When using annotations as a mapping strategy, such construct is not
necessary as the binding between a column and its related column on the
associated table is done directly
@Entity
class Person {
@Id Integer personNumber;
String firstName;
@Column(name="I")
String initial;
String lastName;
}
@Entity
class Home {
@ManyToOne
@JoinColumns({
@JoinColumn(name="first_name", referencedColumnName="firstName"),
@JoinColumn(name="init", referencedColumnName="I"),
@JoinColumn(name="last_name", referencedColumnName="lastName"),
})
Person owner
}
咱們並不推薦這樣使用,除非在映射遺留數據的狀況下。
第 5 章 對象/關係數據庫映射基礎(Basic O/R ...
128
5.1.11. Some hbm.xml specificities
The hbm.xml structure has some specificities naturally not present when using
annotations, let's describe them briefly.
5.1.11.1. Doctype
全部的 XML 映射都須要定義如上所示的 doctype。DTD 能夠從上述 URL 中獲取,也能夠從
hibernate-x.x.x/src/org/hibernate 目錄中、或 hibernate.jar 文件中找到。Hibernate 老是會首
先在它的 classptah 中搜索 DTD 文件。若是你發現它是經過鏈接 Internet 查找 DTD 文件,就
對照你的 classpath 目錄檢查 XML 文件裏的 DTD 聲明。
5.1.11.1.1. EntityResolver
Hibernate 首先試圖在其 classpath 中解析 DTD。這是依靠在系統中註冊的
org.xml.sax.EntityResolver 的一個具體實現,SAXReader 依靠它來讀取 xml 文件。這個自定義的
EntityResolver 能辨認兩種不一樣的 systenId 命名空間:
•a hibernate namespace is recognized whenever the resolver encounters a systemId starting
with http://www.hibernate.org/dtd/. The resolver attempts to resolve these entities via
the classloader which loaded the Hibernate classes.
•若 resolver 遇到了一個使用 classpath:// URL 協議的 systemId,它會辨認出這是 user
namespace,resolver 試圖經過(1) 當前線程上下文的 classloader 和(2) 加載 Hibernate
class 的 classloader 來查找這些實體。
下面是一個使用用戶命名空間(user namespace)的例子:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" [
<!ENTITY types SYSTEM "classpath://your/domain/types.xml">
]>
<hibernate-mapping package="your.domain">
<class name="MyEntity">
<id name="id" type="my-custom-id-type">
...
</id>
<class>
&types;
</hibernate-mapping>
這裏的 types.xml 是 your.domain 包中的一個資源,它包含了一個自定義的 typedef。
5.1.11.2. Hibernate-mapping
這個元素包括一些可選的屬性。schema 和 catalog 屬性, 指明瞭這個映射所鏈接(refer)的表
所在的 schema 和/或 catalog 名稱。倘若指定了這個屬性,表名會加上所指定的 schema 和
Some hbm.xml specificities
129
catalog 的名字擴展爲全限定名。倘若沒有指定,表名就不會使用全限定名。default-cascade 指
定了未明確註明 cascade 屬性的 Java 屬性和 集合類 Hibernate 會採起什麼樣的默認級聯風
格。auto-import 屬性默認讓咱們在查詢語言中能夠使用非全限定名的類名。
<hibernate-mapping
schema="schemaName"
catalog="catalogName"
default-cascade="cascade_style"
default-access="field|property|ClassName"
default-lazy="true|false"
auto-import="true|false"
package="package.name"
/>
schema(可選):數據庫 schema 的名稱。
catalog(可選):數據庫 catalog 的名稱。
default-cascade(可選 — 默認爲 none):默認的級聯風格。
default-access(可選 — 默認爲 property):Hibernate 用來訪問全部屬性的策略。能夠通
過實現 PropertyAccessor 接口自定義。
default-lazy(可選 — 默認爲 true):指定了未明確註明 lazy 屬性的 Java 屬性和集合
類,Hibernate 會採起什麼樣的默認加載風格。
auto-import(可選 — 默認爲 true):指定咱們是否能夠在查詢語言中使用非全限定的類名
(僅限於本映射文件中的類)。
package(可選):指定一個包前綴,若是在映射文檔中沒有指定全限定的類名,就使用這個
做爲包名。
倘若你有兩個持久化類,它們的非全限定名是同樣的(就是兩個類的名字同樣,所在的包不同
— 譯者注),你應該設置 auto-import="false"。若是你把一個「導入過」的名字同時對應兩個
類,Hibernate 會拋出一個異常。
注意 hibernate-mapping 元素容許你嵌套多個如上所示的 <class> 映射。可是最好的作法(也許一
些工具須要的)是一個持久化類(或一個類的繼承層次)對應一個映射文件,並以持久化的超類
名稱命名,例如:Cat.hbm.xml、Dog.hbm.xml,或者若是使用繼承,Animal.hbm.xml。
5.1.11.3. Key
The <key> element is featured a few times within this guide. It appears anywhere the
parent mapping element defines a join to a new table that references the primary key
of the original table. It also defines the foreign key in the joined table:
<key
column="columnname"
on-delete="noaction|cascade"
第 5 章 對象/關係數據庫映射基礎(Basic O/R ...
130
property-ref="propertyName"
not-null="true|false"
update="true|false"
unique="true|false"
/>
column(可選):外鍵字段的名稱。也能夠經過嵌套的 <column> 指定。
on-delete(可選,默認是 noaction):代表外鍵關聯是否打開數據庫級別的級聯刪除。
property-ref(可選):代表外鍵引用的字段不是原表的主鍵(提供給遺留數據)。
not-null(可選):代表外鍵的字段不可爲空(這意味着不管什麼時候外鍵都是主鍵的一部
分)。
update(可選):代表外鍵決不該該被更新(這意味着不管什麼時候外鍵都是主鍵的一部分)。
unique(可選):代表外鍵應有惟一性約束(這意味着不管什麼時候外鍵都是主鍵的一部分)。
對那些看重刪除性能的系統,咱們推薦全部的鍵都應該定義爲 on-delete="cascade",這樣
Hibernate 將使用數據庫級的 ON CASCADE DELETE 約束,而不是多個 DELETE 語句。注意,這個特
性會繞過 Hibernate 一般對版本數據(versioned data)採用的樂觀鎖策略。
not-null 和 update 屬性在映射單向一對多關聯的時候有用。若是你映射一個單向一對多關聯到非
空的(non-nullable)外鍵,你必須 用 <key not-null="true"> 定義此鍵字段。
5.1.11.4. 引用(import)
假設你的應用程序有兩個一樣名字的持久化類,可是你不想在 Hibernate 查詢中使用他們的全限
定名。除了依賴 auto-import="true" 之外,類也能夠被顯式地「import(引用)」。你甚至能夠
引用沒有被明確映射的類和接口。
<import class="java.lang.Object" rename="Universe"/>
<import
class="ClassName"
rename="ShortName"
/>
class:任何 Java 類的全限定名。
rename(可選 — 默認爲類的全限定名):在查詢語句中能夠使用的名字。
注意
This feature is unique to hbm.xml and is not supported in annotations.
Hibernate 的類型
131
5.1.11.5. 字段和規則元素(column and formula elements)
任何接受 column 屬性的映射元素均可以選擇接受 <column> 子元素。一樣的,formula 子元素也
能夠替換 <formula> 屬性。
<column
name="column_name"
length="N"
precision="N"
scale="N"
not-null="true|false"
unique="true|false"
unique-key="multicolumn_unique_key_name"
index="index_name"
sql-type="sql_type_name"
check="SQL expression"
default="SQL expression"
read="SQL expression"
write="SQL expression"/>
<formula>SQL expression</formula>
column 上的大多數屬性都提供了在自動模式生成過程當中對 DDL 進行裁剪的方法。read 和 write
屬性容許你指定 Hibernate 用於訪問字段值的自定義的 SQL。關於更多的內容,請參考 column
read and write expressions。
column 和 formula 屬性甚至能夠在同一個屬性或關聯映射中被合併來表達,例如,一些奇異的連
接條件。
<many-to-one name="homeAddress" class="Address"
insert="false" update="false">
<column name="person_id" not-null="true" length="10"/>
<formula>'MAILING'</formula>
</many-to-one>
5.2. Hibernate 的類型
5.2.1. 實體(Entities)和值(values)
和持久化服務相比,Java 級別的對象分爲兩個組別:
實體entity 獨立於任何持有實體引用的對象。與一般的 Java 模型相比,再也不被引用的對象會被
看成垃圾收集掉。實體必須被顯式的保存和刪除(除非保存和刪除是從父實體向子實體引起的級
聯)。這和 ODMG 模型中關於對象經過可觸及保持持久性有一些不一樣 — 比較起來更加接近應用程
序對象一般在一個大系統中的使用方法。實體支持循環引用和交叉引用,它們也能夠加上版本信
息。
第 5 章 對象/關係數據庫映射基礎(Basic O/R ...
132
一個實體的持久狀態包含指向其餘實體和值類型實例的引用。值能夠是原始類型,集合(不是集
閤中的對象),組件或者特定的不可變對象。與實體不一樣,值(特別是集合和組件)是經過可觸
及性來進行持久化和刪除的。由於值對象(和原始類型數據)是隨着包含他們的實體而被持久化
和刪除的,他們不能被獨立的加上版本信息。值沒有獨立的標識,因此他們不能被兩個實體或者
集合共享。
直到如今,咱們都一直使用術語「持久類」(persistent class)來表明實體。咱們仍然會這麼
作。然而嚴格說來,不是全部的用戶自定義的,帶有持久化狀態的類都是實體。組件就是用戶自
定義類,倒是值語義的。java.lang.String 類型的 java 屬性也是值語義的。給了這個定義之後,
咱們能夠說全部 JDK 提供的類型(類)都是值類型的語義,而用於自定義類型可能被映射爲實體
類型或值類型語義。採用哪一種類型的語義取決於開發人員。在領域模型中,尋找實體類的一個好
線索是共享引用指向這個類的單一實例,而組合或聚合一般被轉化爲值類型。
咱們會在本文檔中重複碰到這兩個概念。
挑戰在於將 java 類型系統(和開發者定義的實體和值類型)映射到 SQL/數據庫類型系
統。Hibernate 提供了鏈接兩個系統之間的橋樑:對於實體類型,咱們使用 <class>,<subclass>
等等。對於值類型,咱們使用 <property>,<component> 及其餘,一般跟隨着 type 屬性。這個屬
性的值是Hibernate 的映射類型的名字。Hibernate 提供了許多現成的映射(標準的 JDK 值類
型)。你也能夠編寫本身的映射類型並實現自定義的變換策略,隨後咱們會看到這點。
全部的 Hibernate 內建類型,除了 collections 之外,都支持空(null)語義。
5.2.2. 基本值類型
內置的 basic mapping types 能夠大體地分類爲:
integer, long, short, float, double, character, byte, boolean, yes_no, true_false
這些類型都對應 Java 的原始類型或者其封裝類,來符合(特定廠商的)SQL 字段類
型。boolean, yes_no 和 true_false 都是 Java 中 boolean 或者 java.lang.Boolean 的另外
說法。
string
從 java.lang.String 到 VARCHAR(或者 Oracle 的 VARCHAR2)的映射。
date, time, timestamp
從 java.util.Date 和其子類到 SQL 類型 DATE,TIME 和 TIMESTAMP(或等價類型)的映射。
calendar, calendar_date
從 java.util.Calendar 到 SQL 類型 TIMESTAMP 和 DATE(或等價類型)的映射。
big_decimal, big_integer
從 java.math.BigDecimal 和 java.math.BigInteger 到 NUMERIC(或者 Oracle 的 NUMBER類型)
的映射。
locale, timezone, currency
從 java.util.Locale,java.util.TimeZone 和 java.util.Currency 到 VARCHAR(或者 Oracle 的
VARCHAR2 類型)的映射。Locale 和 Currency 的實例被映射爲它們的 ISO 代碼。TimeZone 的
實例被影射爲它的 ID。
基本值類型
133
class
從 java.lang.Class 到 VARCHAR(或者 Oracle 的 VARCHAR2 類型)的映射。Class 被映射爲
它的全限定名。
binary
把字節數組(byte arrays)映射爲對應的 SQL 二進制類型。
text
Maps long Java strings to a SQL LONGVARCHAR or TEXT type.
image
Maps long byte arrays to a SQL LONGVARBINARY.
serializable
把可序列化的 Java 類型映射到對應的 SQL 二進制類型。你也能夠爲一個並不是默認爲基本類
型的可序列化 Java 類或者接口指定 Hibernate 類型 serializable。
clob, blob
JDBC 類 java.sql.Clob 和 java.sql.Blob的映射。某些程序可能不適合使用這個類型,由於
blob 和 clob 對象可能在一個事務以外是沒法重用的。(並且, 驅動程序對這種類型的支持
充滿着補丁和先後矛盾。)
materialized_clob
Maps long Java strings to a SQL CLOB type. When read, the CLOB value is immediately
materialized into a Java string. Some drivers require the CLOB value to be read
within a transaction. Once materialized, the Java string is available outside of
the transaction.
materialized_blob
Maps long Java byte arrays to a SQL BLOB type. When read, the BLOB value is immediately
materialized into a byte array. Some drivers require the BLOB value to be read
within a transaction. Once materialized, the byte array is available outside of
the transaction.
imm_date, imm_time, imm_timestamp, imm_calendar, imm_calendar_date, imm_serializable, imm_binary
通常來講,映射類型被假定爲是可變的 Java 類型,只有對不可變 Java 類
型,Hibernate 會採起特定的優化措施,應用程序會把這些對象做爲不可變對象處理。比
如,你不該該對做爲 imm_timestamp 映射的 Date 執行 Date.setTime()。要改變屬性的值,並
且保存這一改變,應用程序必須對這一屬性從新設置一個新的(不同的)對象。
實體及其集合的惟一標識能夠是除了 binary、 blob 和 clob 以外的任何基礎類型。(聯合標識也
是容許的,後面會說到。)
在 org.hibernate.Hibernate 中,定義了基礎類型對應的 Type 常量。好比,Hibernate.STRING 代
表 string 類型。
第 5 章 對象/關係數據庫映射基礎(Basic O/R ...
134
5.2.3. 自定義值類型
開發者建立屬於他們本身的值類型也是很容易的。好比說,你可能但願持久化
java.lang.BigInteger 類型的屬性,持久化成爲 VARCHAR 字段。Hibernate沒有內置這樣一種類
型。自定義類型可以映射一個屬性(或集合元素)到不止一個數據庫表字段。好比說,你可能有這
樣的 Java 屬性:getName()/setName(),這是 java.lang.String 類型的,對應的持久化到三個字
段:FIRST_NAME,INITIAL,SURNAME。
要實現一個自定義類型,能夠實現 org.hibernate.UserType 或 org.hibernate.CompositeUserType
中的任一個,而且使用類型的 Java 全限定類名來定義屬性。請查看
org.hibernate.test.DoubleStringType 這個例子,看看它是怎麼作的。
<property name="twoStrings" type="org.hibernate.test.DoubleStringType">
<column name="first_string"/>
<column name="second_string"/>
</property>
注意使用 <column> 標籤來把一個屬性映射到多個字段的作法。
CompositeUserType,EnhancedUserType,UserCollectionType 和 UserVersionType 接口爲更特殊的使
用方式提供支持。
你甚至能夠在一個映射文件中提供參數給一個 UserType。 爲了這樣作,你的 UserType 必須實現
org.hibernate.usertype.ParameterizedType 接口。爲了給自定義類型提供參數,你能夠在映射文件
中使用 <type> 元素。
<property name="priority">
<type name="com.mycompany.usertypes.DefaultValueIntegerType">
<param name="default">0</param>
</type>
</property>
如今,UserType 能夠從傳入的 Properties 對象中獲得 default 參數的值。
若是你很是頻繁地使用某一 UserType,能夠爲他定義一個簡稱。這能夠經過使用 <typedef> 元素
來實現。Typedefs 爲一自定義類型賦予一個名稱,而且若是此類型是參數化的,還能夠包含一系
列默認的參數值。
<typedef class="com.mycompany.usertypes.DefaultValueIntegerType" name="default_zero">
<param name="default">0</param>
</typedef>
<property name="priority" type="default_zero"/>
屢次映射同一個類
135
也能夠根據具體案例經過屬性映射中的類型參數覆蓋在 typedef 中提供的參數。
儘管 Hibernate 內建的豐富的類型和對組件的支持意味着你可能不多 須要使用自定義類型。不
過,爲那些在你的應用中常常出現的(非實體)類使用自定義類型也是一個好方法。例如,一個
MonetaryAmount 類使用 CompositeUserType 來映射是不錯的選擇,雖然他能夠很容易地被映射成組
件。這樣作的動機之一是抽象。使用自定義類型,之後倘若你改變表示金額的方法時,它能夠保
證映射文件不須要修改。
5.3. 屢次映射同一個類
對特定的持久化類,映射屢次是容許的。這種情形下,你必須指定 entity name 來區別不一樣映射
實體的對象實例。(默認狀況下,實體名字和類名是相同的。) Hibernate 在操做持久化對象、
編寫查詢條件,或者把關聯映射到指定實體時,容許你指定這個 entity name(實體名字)。
<class name="Contract" table="Contracts"
entity-name="CurrentContract">
...
<set name="history" inverse="true"
order-by="effectiveEndDate desc">
<key column="currentContractId"/>
<one-to-many entity-name="HistoricalContract"/>
</set>
</class>
<class name="Contract" table="ContractHistory"
entity-name="HistoricalContract">
...
<many-to-one name="currentContract"
column="currentContractId"
entity-name="CurrentContract"/>
</class>
注意這裏關聯是如何用 entity-name 來代替 class 的。
注意
This feature is not supported in Annotations
5.4. SQL 中引號包圍的標識符
你可經過在映射文檔中使用反向引號(`)把表名或者字段名包圍起來,以強制 Hibernate 在生
成的 SQL 中把標識符用引號包圍起來。Hibernate 會使用相應的 SQLDialect(方言)來使用正確
的引號風格(一般是雙引號,可是在 SQL Server 中是括號,MySQL 中是反向引號)。
@Entity @Table(name="`Line Item`")
class LineItem {
@id @Column(name="`Item Id`") Integer id;
第 5 章 對象/關係數據庫映射基礎(Basic O/R ...
136
@Column(name="`Item #`") int itemNumber
}
<class name="LineItem" table="`Line Item`">
<id name="id" column="`Item Id`"/><generator class="assigned"/></id>
<property name="itemNumber" column="`Item #`"/>
...
</class>
5.5. 數據庫生成屬性(Generated Properties)
Generated properties 指的是其值由數據庫生成的屬性。通常來講,若是對象有任何屬性由數據
庫生成值,Hibernate 應用程序須要進行刷新(refresh)。但若是把屬性標明爲 generated,就可
以轉由 Hibernate 來負責這個動做。實際上。對定義了 generated properties 的實體,每當
Hibernate 執行一條 SQL INSERT 或者 UPDATE 語句,會馬上執行一條 select 來得到生成的值。
被標明爲 generated 的屬性還必須是 non-insertable 和 non-updateable 的。只有
versions、timestamps 和 simple properties 能夠被標明爲 generated。
never(默認)標明此屬性值不是從數據庫中生成。
insert — 標明此屬性值在 insert 的時候生成,可是不會在隨後的 update 時從新生成。好比
說建立日期就歸屬於這類。注意雖然 version 和 timestamp 屬性能夠被標註爲 generated,但
是不適用這個選項。
always — 標明此屬性值在 insert 和 update 時都會被生成。
To mark a property as generated, use @Generated.
5.6. Column transformers: read and write expressions
Hibernate allows you to customize the SQL it uses to read and write the values of
columns mapped to simple properties. For example, if your database provides a set of
data encryption functions, you can invoke them for individual columns like this:
@Entity
class CreditCard {
@Column(name="credit_card_num")
@ColumnTransformer(
read="decrypt(credit_card_num)",
write="encrypt(?)")
public String getCreditCardNumber() { return creditCardNumber; }
public void setCreditCardNumber(String number) { this.creditCardNumber = number; }
private String creditCardNumber;
}
or in XML
<property name="creditCardNumber">
輔助數據庫對象(Auxiliary Database Objects)
137
<column
name="credit_card_num"
read="decrypt(credit_card_num)"
write="encrypt(?)"/>
</property>
注意
You can use the plural form @ColumnTransformers if more than one columns
need to define either of these rules.
If a property uses more that one column, you must use the forColumn attribute to specify
which column, the expressions are targeting.
@Entity
class User {
@Type(type="com.acme.type.CreditCardType")
@Columns( {
@Column(name="credit_card_num"),
@Column(name="exp_date") } )
@ColumnTransformer(
forColumn="credit_card_num",
read="decrypt(credit_card_num)",
write="encrypt(?)")
public CreditCard getCreditCard() { return creditCard; }
public void setCreditCard(CreditCard card) { this.creditCard = card; }
private CreditCard creditCard;
}
每當屬性在查詢裏被引用時,Hibernate 都自動應用自定義的表達式。這種功能和 derivedproperty
formula 類似,但有兩個不一樣的地方:
•屬性由一個或多個屬性組成,它做爲自動模式生成的一部分導出。
•屬性是可讀寫的,非只讀的。
若是指定了 write 表達式,它必須只包含一個「?」佔位符。
5.7. 輔助數據庫對象(Auxiliary Database Objects)
容許 CREATE 和 DROP 任意數據庫對象,與 Hibernate 的 schema 交互工具組合起
來,能夠提供在 Hibernate 映射文件中徹底定義用戶 schema 的能力。雖然這是爲建立和
銷燬 trigger(觸發器)或stored procedure(存儲過程)等特別設計的,實際上任何能夠在
java.sql.Statement.execute() 方法中執行的 SQL 命令均可以在此使用(好比ALTER, INSERT,等
等)。本質上有兩種模式來定義輔助數據庫對象...
第一種模式是在映射文件中顯式聲明 CREATE 和 DROP 命令:
第 5 章 對象/關係數據庫映射基礎(Basic O/R ...
138
<hibernate-mapping>
...
<database-object>
<create>CREATE TRIGGER my_trigger ...</create>
<drop>DROP TRIGGER my_trigger</drop>
</database-object>
</hibernate-mapping>
第二種模式是提供一個類,這個類知道如何組織 CREATE 和 DROP 命令。這個特別類必須實現
org.hibernate.mapping.AuxiliaryDatabaseObject 接口。
<hibernate-mapping>
...
<database-object>
<definition class="MyTriggerDefinition"/>
</database-object>
</hibernate-mapping>
還有,這些數據庫對象能夠特別指定爲僅在特定的方言中才使用。
<hibernate-mapping>
...
<database-object>
<definition class="MyTriggerDefinition"/>
<dialect-scope name="org.hibernate.dialect.Oracle9iDialect"/>
<dialect-scope name="org.hibernate.dialect.Oracle10gDialect"/>
</database-object>
</hibernate-mapping>
注意
This feature is not supported in Annotations
第 6
139
Types
As an Object/Relational Mapping solution, Hibernate deals with both the Java and JDBC
representations of application data. An online catalog application, for example, most
likely has Product object with a number of attributes such as a sku, name, etc. For these
individual attributes, Hibernate must be able to read the values out of the database
and write them back. This 'marshalling' is the function of a Hibernate type, which
is an implementation of the org.hibernate.type.Type interface. In addition, a Hibernate
type describes various aspects of behavior of the Java type such as "how is equality
checked?" or "how are values cloned?".
重要
A Hibernate type is neither a Java type nor a SQL datatype; it provides
a information about both.
When you encounter the term type in regards to Hibernate be aware that
usage might refer to the Java type, the SQL/JDBC type or the Hibernate
type.
Hibernate categorizes types into two high-level groups: value types (see 第  6.1 節
「Value types」) and entity types (see 第 6.2 節 「Entity types」).
6.1. Value types
The main distinguishing characteristic of a value type is the fact that they do not
define their own lifecycle. We say that they are "owned" by something else (specifically
an entity, as we will see later) which defines their lifecycle. Value types are
further classified into 3 sub-categories: basic types (see 第 6.1.1 節 「Basic value
types」), composite types (see 第 6.1.2 節 「Composite types」) amd collection types
(see 第 6.1.3 節 「Collection types」).
6.1.1. Basic value types
The norm for basic value types is that they map a single database value (column) to a
single, non-aggregated Java type. Hibernate provides a number of built-in basic types,
which we will present in the following sections by the Java type. Mainly these follow
the natural mappings recommended in the JDBC specification. We will later cover how to
override these mapping and how to provide and use alternative type mappings.
第 6 章 Types
140
6.1.1.1. java.lang.String
org.hibernate.type.StringType
Maps a string to the JDBC VARCHAR type. This is the standard mapping for a string
if no Hibernate type is specified.
Registered under string and java.lang.String in the type registry (see 第  6.5 節
「Type registry」).
org.hibernate.type.MaterializedClob
Maps a string to a JDBC CLOB type
Registered under materialized_clob in the type registry (see 第  6.5  節 「Type
registry」).
org.hibernate.type.TextType
Maps a string to a JDBC LONGVARCHAR type
Registered under text in the type registry (see 第 6.5 節 「Type registry」).
6.1.1.2. java.lang.Character (or char primitive)
org.hibernate.type.CharacterType
Maps a char or java.lang.Character to a JDBC CHAR
Registered under char and java.lang.Character in the type registry (see 第  6.5 節
「Type registry」).
6.1.1.3. java.lang.Boolean (or boolean primitive)
org.hibernate.type.BooleanType
Maps a boolean to a JDBC BIT type
Registered under boolean and java.lang.Boolean in the type registry (see 第 6.5 節
「Type registry」).
org.hibernate.type.NumericBooleanType
Maps a boolean to a JDBC INTEGER type as 0 = false, 1 = true
Registered under numeric_boolean in the type registry (see 第  6.5  節 「Type
registry」).
org.hibernate.type.YesNoType
Maps a boolean to a JDBC CHAR type as ('N' | 'n') = false, ( 'Y' | 'y' ) = true
Registered under yes_no in the type registry (see 第 6.5 節 「Type registry」).
Basic value types
141
org.hibernate.type.TrueFalseType
Maps a boolean to a JDBC CHAR type as ('F' | 'f') = false, ( 'T' | 't' ) = true
Registered under true_false in the type registry (see 第 6.5 節 「Type registry」).
6.1.1.4. java.lang.Byte (or byte primitive)
org.hibernate.type.ByteType
Maps a byte or java.lang.Byte to a JDBC TINYINT
Registered under byte and java.lang.Byte in the type registry (see 第 6.5 節 「Type
registry」).
6.1.1.5. java.lang.Short (or short primitive)
org.hibernate.type.ShortType
Maps a short or java.lang.Short to a JDBC SMALLINT
Registered under short and java.lang.Short in the type registry (see 第 6.5 節 「Type
registry」).
6.1.1.6. java.lang.Integer (or int primitive)
org.hibernate.type.IntegerTypes
Maps an int or java.lang.Integer to a JDBC INTEGER
Registered under int and java.lang.Integerin the type registry (see 第 6.5 節 「Type
registry」).
6.1.1.7. java.lang.Long (or long primitive)
org.hibernate.type.LongType
Maps a long or java.lang.Long to a JDBC BIGINT
Registered under long and java.lang.Long in the type registry (see 第 6.5 節 「Type
registry」).
6.1.1.8. java.lang.Float (or float primitive)
org.hibernate.type.FloatType
Maps a float or java.lang.Float to a JDBC FLOAT
Registered under float and java.lang.Float in the type registry (see 第 6.5 節 「Type
registry」).
第 6 章 Types
142
6.1.1.9. java.lang.Double (or double primitive)
org.hibernate.type.DoubleType
Maps a double or java.lang.Double to a JDBC DOUBLE
Registered under double and java.lang.Double in the type registry (see 第  6.5 節
「Type registry」).
6.1.1.10. java.math.BigInteger
org.hibernate.type.BigIntegerType
Maps a java.math.BigInteger to a JDBC NUMERIC
Registered under big_integer and java.math.BigInteger in the type registry (see
第 6.5 節 「Type registry」).
6.1.1.11. java.math.BigDecimal
org.hibernate.type.BigDecimalType
Maps a java.math.BigDecimal to a JDBC NUMERIC
Registered under big_decimal and java.math.BigDecimal in the type registry (see
第 6.5 節 「Type registry」).
6.1.1.12. java.util.Date or java.sql.Timestamp
org.hibernate.type.TimestampType
Maps a java.sql.Timestamp to a JDBC TIMESTAMP
Registered under timestamp, java.sql.Timestamp and java.util.Date in the type registry
(see 第 6.5 節 「Type registry」).
6.1.1.13. java.sql.Time
org.hibernate.type.TimeType
Maps a java.sql.Time to a JDBC TIME
Registered under time and java.sql.Time in the type registry (see 第 6.5 節 「Type
registry」).
6.1.1.14. java.sql.Date
org.hibernate.type.DateType
Maps a java.sql.Date to a JDBC DATE
Basic value types
143
Registered under date and java.sql.Date in the type registry (see 第 6.5 節 「Type
registry」).
6.1.1.15. java.util.Calendar
org.hibernate.type.CalendarType
Maps a java.util.Calendar to a JDBC TIMESTAMP
Registered under calendar, java.util.Calendar and java.util.GregorianCalendar in the
type registry (see 第 6.5 節 「Type registry」).
org.hibernate.type.CalendarDateType
Maps a java.util.Calendar to a JDBC DATE
Registered under calendar_date in the type registry (see 第 6.5 節 「Type registry」).
6.1.1.16. java.util.Currency
org.hibernate.type.CurrencyType
Maps a java.util.Currency to a JDBC VARCHAR (using the Currency code)
Registered under currency and java.util.Currency in the type registry (see 第 6.5 節
「Type registry」).
6.1.1.17. java.util.Locale
org.hibernate.type.LocaleType
Maps a java.util.Locale to a JDBC VARCHAR (using the Locale code)
Registered under locale and java.util.Locale in the type registry (see 第  6.5 節
「Type registry」).
6.1.1.18. java.util.TimeZone
org.hibernate.type.TimeZoneType
Maps a java.util.TimeZone to a JDBC VARCHAR (using the TimeZone ID)
Registered under timezone and java.util.TimeZone in the type registry (see 第 6.5 節
「Type registry」).
6.1.1.19. java.net.URL
org.hibernate.type.UrlType
Maps a java.net.URL to a JDBC VARCHAR (using the external form)
第 6 章 Types
144
Registered under url and java.net.URL in the type registry (see 第  6.5 節 「Type
registry」).
6.1.1.20. java.lang.Class
org.hibernate.type.ClassType
Maps a java.lang.Class to a JDBC VARCHAR (using the Class name)
Registered under class and java.lang.Class in the type registry (see 第 6.5 節 「Type
registry」).
6.1.1.21. java.sql.Blob
org.hibernate.type.BlobType
Maps a java.sql.Blob to a JDBC BLOB
Registered under blob and java.sql.Blob in the type registry (see 第 6.5 節 「Type
registry」).
6.1.1.22. java.sql.Clob
org.hibernate.type.ClobType
Maps a java.sql.Clob to a JDBC CLOB
Registered under clob and java.sql.Clob in the type registry (see 第 6.5 節 「Type
registry」).
6.1.1.23. byte[]
org.hibernate.type.BinaryType
Maps a primitive byte[] to a JDBC VARBINARY
Registered under binary and byte[] in the type registry (see 第  6.5  節 「Type
registry」).
org.hibernate.type.MaterializedBlobType
Maps a primitive byte[] to a JDBC BLOB
Registered under materialized_blob in the type registry (see 第  6.5  節 「Type
registry」).
org.hibernate.type.ImageType
Maps a primitive byte[] to a JDBC LONGVARBINARY
Basic value types
145
Registered under image in the type registry (see 第 6.5 節 「Type registry」).
6.1.1.24. Byte[]
org.hibernate.type.BinaryType
Maps a java.lang.Byte[] to a JDBC VARBINARY
Registered under wrapper-binary, Byte[] and java.lang.Byte[] in the type registry (see
第 6.5 節 「Type registry」).
6.1.1.25. char[]
org.hibernate.type.CharArrayType
Maps a char[] to a JDBC VARCHAR
Registered under characters and char[] in the type registry (see 第 6.5 節 「Type
registry」).
6.1.1.26. Character[]
org.hibernate.type.CharacterArrayType
Maps a java.lang.Character[] to a JDBC VARCHAR
Registered under wrapper-characters, Character[] and java.lang.Character[] in the type
registry (see 第 6.5 節 「Type registry」).
6.1.1.27. java.util.UUID
org.hibernate.type.UUIDBinaryType
Maps a java.util.UUID to a JDBC BINARY
Registered under uuid-binary and java.util.UUID in the type registry (see 第 6.5 節
「Type registry」).
org.hibernate.type.UUIDCharType
Maps a java.util.UUID to a JDBC CHAR (though VARCHAR is fine too for existing
schemas)
Registered under uuid-char in the type registry (see 第 6.5 節 「Type registry」).
org.hibernate.type.PostgresUUIDType
Maps a java.util.UUID to the PostgreSQL UUID data type (through Types#OTHER which
is how the PostgreSQL JDBC driver defines it).
Registered under pg-uuid in the type registry (see 第 6.5 節 「Type registry」).
第 6 章 Types
146
6.1.1.28. java.io.Serializable
org.hibernate.type.SerializableType
Maps implementors of java.lang.Serializable to a JDBC VARBINARY
Unlike the other value types, there are multiple instances of this type. It gets
registered once under java.io.Serializable. Additionally it gets registered under the
specific java.io.Serializable implementation class names.
6.1.2. Composite types
注意
The Java Persistence API calls these embedded types, while Hibernate
traditionally called them components. Just be aware that both terms are
used and mean the same thing in the scope of discussing Hibernate.
Components represent aggregations of values into a single Java type. For example, you
might have an Address class that aggregates street, city, state, etc information or a
Name class that aggregates the parts of a person's Name. In many ways a component looks
exactly like an entity. They are both (generally speaking) classes written specifically
for the application. They both might have references to other application-specific
classes, as well as to collections and simple JDK types. As discussed before, the only
distinguishing factory is the fact that a component does not own its own lifecycle
nor does it define an identifier.
6.1.3. Collection types
重要
It is critical understand that we mean the collection itself, not its
contents. The contents of the collection can in turn be basic, component or
entity types (though not collections), but the collection itself is owned.
Collections are covered in 第 7 章 集合映射(Collection mappings).
6.2. Entity types
The definition of entities is covered in detail in 第  4  章 持久化類(Persistent
Classes). For the purpose of this discussion, it is enough to say that entities
are (generally application-specific) classes which correlate to rows in a table.
Specifically they correlate to the row by means of a unique identifier. Because of this
Significance of type categories
147
unique identifier, entities exist independently and define their own lifecycle. As an
example, when we delete a Membership, both the User and Group entities remain.
注意
This notion of entity independence can be modified by the application
developer using the concept of cascades. Cascades allow certain operations
to continue (or "cascade") across an association from one entity to
another. Cascades are covered in detail in 第 8 章 關聯關係映射.
6.3. Significance of type categories
Why do we spend so much time categorizing the various types of types? What is the
significance of the distinction?
The main categorization was between entity types and value types. To review we said
that entities, by nature of their unique identifier, exist independently of other
objects whereas values do not. An application cannot "delete" a Product sku; instead,
the sku is removed when the Product itself is deleted (obviously you can update the
sku of that Product to null to make it "go away", but even there the access is done
through the Product).
Nor can you define an association to that Product sku. You can define an association
to Product based on its sku, assuming sku is unique, but that is totally different.
TBC...
6.4. Custom types
Hibernate makes it relatively easy for developers to create their own value types. For
example, you might want to persist properties of type java.lang.BigInteger to VARCHAR
columns. Custom types are not limited to mapping values to a single table column.
So, for example, you might want to concatenate together FIRST_NAME, INITIAL and SURNAME
columns into a java.lang.String.
There are 3 approaches to developing a custom Hibernate type. As a means of illustrating
the different approaches, lets consider a use case where we need to compose a
java.math.BigDecimal and java.util.Currency together into a custom Money class.
6.4.1. Custom types using org.hibernate.type.Type
The first approach is to directly implement the org.hibernate.type.Type interface (or
one of its derivatives). Probably, you will be more interested in the more specific
org.hibernate.type.BasicType contract which would allow registration of the type (see
第 6.5 節 「Type registry」). The benefit of this registration is that whenever the
metadata for a particular property does not specify the Hibernate type to use, Hibernate
第 6 章 Types
148
will consult the registry for the exposed property type. In our example, the property
type would be Money, which is the key we would use to register our type in the registry:
例 6.1. Defining and registering the custom Type
public class MoneyType implements BasicType {
public String[] getRegistrationKeys() {
return new String[] { Money.class.getName() };
}
public int[] sqlTypes(Mapping mapping) {
// We will simply use delegation to the standard basic types for BigDecimal and
Currency for many of the
// Type methods...
return new int[] {
BigDecimalType.INSTANCE.sqlType(),
CurrencyType.INSTANCE.sqlType(),
};
// we could also have honored any registry overrides via...
//return new int[] {
//
mappings.getTypeResolver().basic( BigDecimal.class.getName() ).sqlTypes( mappings )[0],
//
mappings.getTypeResolver().basic( Currency.class.getName() ).sqlTypes( mappings )[0]
//};
}
public Class getReturnedClass() {
return Money.class;
}
public Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor session, Object owner) throws SQLException {
assert names.length == 2;
BigDecimal amount = BigDecimalType.INSTANCE.get( names[0] ); // already handles null check
Currency currency = CurrencyType.INSTANCE.get( names[1] ); // already handles null check
return amount == null && currency == null
? null
: new Money( amount, currency );
}
public void nullSafeSet(PreparedStatement st, Object value, int index, boolean[] settable, SessionImplementor session)
throws SQLException {
if ( value == null ) {
BigDecimalType.INSTANCE.set( st, null, index );
CurrencyType.INSTANCE.set( st, null, index+1 );
}
else {
final Money money = (Money) value;
BigDecimalType.INSTANCE.set( st, money.getAmount(), index );
CurrencyType.INSTANCE.set( st, money.getCurrency(), index+1 );
}
}
...
}
Configuration cfg = new Configuration();
Custom types using org.hibernate.usertype.UserType
149
cfg.registerTypeOverride( new MoneyType() );
cfg...;
重要
It is important that we registered the type before adding mappings.
6.4.2. Custom types using org.hibernate.usertype.UserType
注意
Both org.hibernate.usertype.UserType and
org.hibernate.usertype.CompositeUserType were originally added to isolate
user code from internal changes to the org.hibernate.type.Type interfaces.
The second approach is the use the org.hibernate.usertype.UserType interface, which
presents a somewhat simplified view of the org.hibernate.type.Type interface. Using a
org.hibernate.usertype.UserType, our Money custom type would look as follows:
例 6.2. Defining the custom UserType
public class MoneyType implements UserType {
public int[] sqlTypes() {
return new int[] {
BigDecimalType.INSTANCE.sqlType(),
CurrencyType.INSTANCE.sqlType(),
};
}
public Class getReturnedClass() {
return Money.class;
}
public Object nullSafeGet(ResultSet rs, String[] names, Object owner) throws SQLException {
assert names.length == 2;
BigDecimal amount = BigDecimalType.INSTANCE.get( names[0] ); // already handles null check
Currency currency = CurrencyType.INSTANCE.get( names[1] ); // already handles null check
return amount == null && currency == null
? null
: new Money( amount, currency );
}
public void nullSafeSet(PreparedStatement st, Object value, int index) throws SQLException {
if ( value == null ) {
BigDecimalType.INSTANCE.set( st, null, index );
CurrencyType.INSTANCE.set( st, null, index+1 );
}
else {
final Money money = (Money) value;
第 6 章 Types
150
BigDecimalType.INSTANCE.set( st, money.getAmount(), index );
CurrencyType.INSTANCE.set( st, money.getCurrency(), index+1 );
}
}
...
}
There is not much difference between the org.hibernate.type.Type example and the
org.hibernate.usertype.UserType example, but that is only because of the snippets shown.
If you choose the org.hibernate.type.Type approach there are quite a few more methods you
would need to implement as compared to the org.hibernate.usertype.UserType.
6.4.3. Custom types using org.hibernate.usertype.CompositeUserType
The third and final approach is the use the org.hibernate.usertype.CompositeUserType
interface, which differs from org.hibernate.usertype.UserType in that it gives us
the ability to provide Hibernate the information to handle the composition
within the Money class (specifically the 2 attributes). This would give us the
capability, for example, to reference the amount attribute in an HQL query. Using a
org.hibernate.usertype.CompositeUserType, our Money custom type would look as follows:
例 6.3. Defining the custom CompositeUserType
public class MoneyType implements CompositeUserType {
public String[] getPropertyNames() {
// ORDER IS IMPORTANT! it must match the order the columns are defined in the
property mapping
return new String[] { "amount", "currency" };
}
public Type[] getPropertyTypes() {
return new Type[] { BigDecimalType.INSTANCE, CurrencyType.INSTANCE };
}
public Class getReturnedClass() {
return Money.class;
}
public Object getPropertyValue(Object component, int propertyIndex) {
if ( component == null ) {
return null;
}
final Money money = (Money) component;
switch ( propertyIndex ) {
case 0: {
return money.getAmount();
}
case 1: {
return money.getCurrency();
}
default: {
Type registry
151
throw new HibernateException( "Invalid property index [" + propertyIndex + "]" );
}
}
}
public void setPropertyValue(Object component, int propertyIndex, Object value) throws HibernateException {
if ( component == null ) {
return;
}
final Money money = (Money) component;
switch ( propertyIndex ) {
case 0: {
money.setAmount( (BigDecimal) value );
break;
}
case 1: {
money.setCurrency( (Currency) value );
break;
}
default: {
throw new HibernateException( "Invalid property index [" + propertyIndex + "]" );
}
}
}
public Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor session, Object owner) throws SQLException {
assert names.length == 2;
BigDecimal amount = BigDecimalType.INSTANCE.get( names[0] ); // already handles null check
Currency currency = CurrencyType.INSTANCE.get( names[1] ); // already handles null check
return amount == null && currency == null
? null
: new Money( amount, currency );
}
public void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session) throws SQLException {
if ( value == null ) {
BigDecimalType.INSTANCE.set( st, null, index );
CurrencyType.INSTANCE.set( st, null, index+1 );
}
else {
final Money money = (Money) value;
BigDecimalType.INSTANCE.set( st, money.getAmount(), index );
CurrencyType.INSTANCE.set( st, money.getCurrency(), index+1 );
}
}
...
}
6.5. Type registry
Internally Hibernate uses a registry of basic types (see 第  6.1.1 節 「Basic value
types」) when it needs to resolve the specific org.hibernate.type.Type to use in certain
situations. It also provides a way for applications to add extra basic type registrations
as well as override the standard basic type registrations.
第 6 章 Types
152
To register a new type or to override an existing type registration, applications would
make use of the registerTypeOverride method of the org.hibernate.cfg.Configuration class
when bootstrapping Hibernate. For example, lets say you want Hibernate to use your
custom SuperDuperStringType; during bootstrap you would call:
例 6.4. Overriding the standard StringType
Configuration cfg = ...;
cfg.registerTypeOverride( new SuperDuperStringType() );
The argument to registerTypeOverride is a org.hibernate.type.BasicType which is a
specialization of the org.hibernate.type.Type we saw before. It adds a single method:
例 6.5. Snippet from BasicType.java
/**
* Get the names under which this type should be registered in the type registry.
*
* @return The keys under which to register this type.
*/
public String[] getRegistrationKeys();
One approach is to use inheritance (SuperDuperStringType extends
org.hibernate.type.StringType); another is to use delegation.
第 7
153
集合映射(Collection mappings)
7.1. 持久化集合類(Persistent collections)
Naturally Hibernate also allows to persist collections. These persistent collections
can contain almost any other Hibernate type, including: basic types, custom types,
components and references to other entities. The distinction between value and reference
semantics is in this context very important. An object in a collection might be handled
with "value" semantics (its life cycle fully depends on the collection owner), or it
might be a reference to another entity with its own life cycle. In the latter case, only
the "link" between the two objects is considered to be a state held by the collection.
As a requirement persistent collection-valued fields must be declared as an interface
type (see 例  7.2 「Collection mapping using @OneToMany and @JoinColumn」). The
actual interface might be java.util.Set, java.util.Collection, java.util.List, java.util.Map,
java.util.SortedSet, java.util.SortedMap or anything you like ("anything you like" means
you will have to write an implementation of org.hibernate.usertype.UserCollectionType).
Notice how in 例  7.2 「Collection mapping using @OneToMany and @JoinColumn」 the
instance variable parts was initialized with an instance of HashSet. This is the best
way to initialize collection valued properties of newly instantiated (non-persistent)
instances. When you make the instance persistent, by calling persist(), Hibernate will
actually replace the HashSet with an instance of Hibernate's own implementation of Set.
Be aware of the following error:
例 7.1. Hibernate uses its own collection implementations
Cat cat = new DomesticCat();
Cat kitten = new DomesticCat();
....
Set kittens = new HashSet();
kittens.add(kitten);
cat.setKittens(kittens);
session.persist(cat);
kittens = cat.getKittens(); // Okay, kittens collection is a Set
(HashSet) cat.getKittens(); // Error!
根據不一樣的接口類型,被 Hibernate 注射的持久化集合類的表現相似
HashMap、HashSet、TreeMap、TreeSet 或 ArrayList。
集合類實例具備值類型的一般行爲。當被持久化對象引用後,他們會自動被持久化,當再也不被引
用後,自動被刪除。倘若實例被從一個持久化對象傳遞到另外一個,它的元素可能從一個錶轉移到
另外一個表。兩個實體不能共享同一個集合類實例的引用。由於底層關係數據庫模型的緣由,集合
值屬性沒法支持空值語義;Hibernate 對空的集合引用和空集合不加區別。
第 7 章 集合映射(Collection mappings)
154
注意
Use persistent collections the same way you use ordinary Java collections.
However, ensure you understand the semantics of bidirectional associations
(see 第 7.3.2 節 「雙向關聯(Bidirectional associations)」).
7.2. How to map collections
Using annotations you can map Collections, Lists, Maps and Sets of associated entities
using @OneToMany and @ManyToMany. For collections of a basic or embeddable type use
@ElementCollection. In the simplest case a collection mapping looks like this:
例 7.2. Collection mapping using @OneToMany and @JoinColumn
@Entity
public class Product {
private String serialNumber;
private Set<Part> parts = new HashSet<Part>();
@Id
public String getSerialNumber() { return serialNumber; }
void setSerialNumber(String sn) { serialNumber = sn; }
@OneToMany
@JoinColumn(name="PART_ID")
public Set<Part> getParts() { return parts; }
void setParts(Set parts) { this.parts = parts; }
}
@Entity
public class Part {
...
}
Product describes a unidirectional relationship with Part using the join column PART_ID.
In this unidirectional one to many scenario you can also use a join table as seen in
例 7.3 「Collection mapping using @OneToMany and @JoinTable」.
例 7.3. Collection mapping using @OneToMany and @JoinTable
@Entity
public class Product {
private String serialNumber;
private Set<Part> parts = new HashSet<Part>();
How to map collections
155
@Id
public String getSerialNumber() { return serialNumber; }
void setSerialNumber(String sn) { serialNumber = sn; }
@OneToMany
@JoinTable(
name="PRODUCT_PARTS",
joinColumns = @JoinColumn( name="PRODUCT_ID"),
inverseJoinColumns = @JoinColumn( name="PART_ID")
)
public Set<Part> getParts() { return parts; }
void setParts(Set parts) { this.parts = parts; }
}
@Entity
public class Part {
...
}
Without describing any physical mapping (no @JoinColumn or @JoinTable), a unidirectional
one to many with join table is used. The table name is the concatenation of the owner
table name, _, and the other side table name. The foreign key name(s) referencing the
owner table is the concatenation of the owner table, _, and the owner primary key
column(s) name. The foreign key name(s) referencing the other side is the concatenation
of the owner property name, _, and the other side primary key column(s) name. A unique
constraint is added to the foreign key referencing the other side table to reflect
the one to many.
Lets have a look now how collections are mapped using Hibernate mapping files. In this
case the first step is to chose the right mapping element. It depends on the type of
interface. For example, a <set> element is used for mapping properties of type Set.
例 7.4. Mapping a Set using <set>
<class name="Product">
<id name="serialNumber" column="productSerialNumber"/>
<set name="parts">
<key column="productSerialNumber" not-null="true"/>
<one-to-many class="Part"/>
</set>
</class>
In 例  7.4 「Mapping a Set using <set>」 a one-to-many association links the Product
and Part entities. This association requires the existence of a foreign key column
and possibly an index column to the Part table. This mapping loses certain semantics
of normal Java collections:
•一個被包含的實體的實例只能被包含在一個集合的實例中。
•一個被包含的實體的實例只能對應於集合索引的一個值中。
第 7 章 集合映射(Collection mappings)
156
Looking closer at the used <one-to-many> tag we see that it has the following options.
例 7.5. options of <one-to-many> element
<one-to-many
class="ClassName"
not-found="ignore|exception"
entity-name="EntityName"
node="element-name"
embed-xml="true|false"
/>
class(必需):被關聯類的名稱。
not-found(可選 - 默認爲exception):指明若緩存的標示值關聯的行缺失,該如何處
理:ignore 會把缺失的行做爲一個空關聯處理。
entity-name(可選):被關聯的類的實體名,做爲 class 的替代。
注意:<one-to-many> 元素不須要定義任何字段。也不須要指定表名。
警告
If the foreign key column of a <one-to-many> association is declared
NOT NULL, you must declare the <key> mapping not-null="true" or
use a bidirectional association with the collection mapping marked
inverse="true". See 第 7.3.2 節 「雙向關聯(Bidirectional associations)」.
Apart from the <set> tag as shown in 例 7.4 「Mapping a Set using <set>」, there is also
<list>, <map>, <bag>, <array> and <primitive-array> mapping elements. The <map> element
is representative:
例 7.6. Elements of the <map> mapping
<map
name="propertyName"
table="table_name"
schema="schema_name"
lazy="true|extra|false"
inverse="true|false"
cascade="all|none|save-update|delete|all-delete-orphan|delete-orphan"
sort="unsorted|natural|comparatorClass"
order-by="column_name asc|desc"
where="arbitrary sql where condition"
集合外鍵(Collection foreign keys)
157
fetch="join|select|subselect"
batch-size="N"
access="field|property|ClassName"
optimistic-lock="true|false"
mutable="true|false"
node="element-name|."
embed-xml="true|false"
>
<key .... />
<map-key .... />
<element .... />
</map>
name:集合屬性的名稱
table(可選——默認爲屬性的名稱)這個集合表的名稱(不能在一對多的關聯關係中使
用)。
schema(可選):表的 schema 的名稱,他將覆蓋在根元素中定義的 schema
lazy(可選--默認爲 true)能夠用來關閉延遲加載(false):指定一直使用預先抓取,或
者打開 "extra-lazy" 抓取,此時大多數操做不會初始化集合類(適用於很是大的集合)。
inverse(可選 — 默認爲 false)標記這個集合做爲雙向關聯關係中的方向一端。
cascade(可選 — 默認爲 none)讓操做級聯到子實體。
sort(可選)指定集合的排序順序,其能夠爲天然的(natural)或者給定一個用來比較的
類。
order-by (optional): specifies a table column or columns that define the iteration
order of the Map, Set or bag, together with an optional asc or desc.
where(可選):指定任意的 SQL where 條件,該條件將在從新載入或者刪除這個集合時使
用(當集合中的數據僅僅是全部可用數據的一個子集時這個條件很是有用)。
fetch(可選,默認爲 select):用於在外鏈接抓取、經過後續 select 抓取和經過後續
subselect 抓取之間選擇。
batch-size(可選,默認爲 1):指定經過延遲加載取得集合實例的批處理塊大小("batch
size")。
access(可選-默認爲屬性 property):Hibernate 取得集合屬性值時使用的策略。
樂觀鎖(可選 - 默認爲 true):對集合的狀態的改變會是否致使其所屬的實體的版本增加
(對一對多關聯來講,關閉這個屬性經常是有理的)。
mutable(可變)(可選 — 默認爲 true):若值爲 false,代表集合中的元素不會改變(在
某些狀況下能夠進行一些小的性能優化)。
After exploring the basic mapping of collections in the preceding paragraphs we will now
focus details like physical mapping considerations, indexed collections and collections
of value types.
7.2.1. 集合外鍵(Collection foreign keys)
On the database level collection instances are distinguished by the foreign key of the
entity that owns the collection. This foreign key is referred to as the collection key
第 7 章 集合映射(Collection mappings)
158
column, or columns, of the collection table. The collection key column is mapped by
the @JoinColumn annotation respectively the <key> XML element.
There can be a nullability constraint on the foreign key column. For most collections,
this is implied. For unidirectional one-to-many associations, the foreign key column
is nullable by default, so you may need to specify
@JoinColumn(nullable=false)
or
<key column="productSerialNumber" not-null="true"/>
The foreign key constraint can use ON DELETE CASCADE. In XML this can be expressed via:
<key column="productSerialNumber" on-delete="cascade"/>
In annotations the Hibernate specific annotation @OnDelete has to be used.
@OnDelete(action=OnDeleteAction.CASCADE)
See 第 5.1.11.3 節 「Key」 for more information about the <key> element.
7.2.2. 索引集合類(Indexed collections)
In the following paragraphs we have a closer at the indexed collections List and Map
how the their index can be mapped in Hibernate.
7.2.2.1. Lists
Lists can be mapped in two different ways:
•as ordered lists, where the order is not materialized in the database
•as indexed lists, where the order is materialized in the database
To order lists in memory, add @javax.persistence.OrderBy to your property. This annotation
takes as parameter a list of comma separated properties (of the target entity) and
orders the collection accordingly (eg firstname asc, age desc), if the string is empty,
the collection will be ordered by the primary key of the target entity.
索引集合類(Indexed collections)
159
例 7.7. Ordered lists using @OrderBy
@Entity
public class Customer {
@Id @GeneratedValue public Integer getId() { return id; }
public void setId(Integer id) { this.id = id; }
private Integer id;
@OneToMany(mappedBy="customer")
@OrderBy("number")
public List<Order> getOrders() { return orders; }
public void setOrders(List<Order> orders) { this.orders = orders; }
private List<Order> orders;
}
@Entity
public class Order {
@Id @GeneratedValue public Integer getId() { return id; }
public void setId(Integer id) { this.id = id; }
private Integer id;
public String getNumber() { return number; }
public void setNumber(String number) { this.number = number; }
private String number;
@ManyToOne
public Customer getCustomer() { return customer; }
public void setCustomer(Customer customer) { this.customer = customer; }
private Customer number;
}
-- Table schema
|-------------| |----------|
| Order | | Customer |
|-------------| |----------|
| id | | id |
| number | |----------|
| customer_id |
|-------------|
To store the index value in a dedicated column, use the @javax.persistence.OrderColumn
annotation on your property. This annotations describes the column name and attributes
of the column keeping the index value. This column is hosted on the table containing
the association foreign key. If the column name is not specified, the default is the
name of the referencing property, followed by underscore, followed by ORDER (in the
following example, it would be orders_ORDER).
例 7.8. Explicit index column using @OrderColumn
@Entity
public class Customer {
@Id @GeneratedValue public Integer getId() { return id; }
public void setId(Integer id) { this.id = id; }
第 7 章 集合映射(Collection mappings)
160
private Integer id;
@OneToMany(mappedBy="customer")
@OrderColumn(name="orders_index")
public List<Order> getOrders() { return orders; }
public void setOrders(List<Order> orders) { this.orders = orders; }
private List<Order> orders;
}
@Entity
public class Order {
@Id @GeneratedValue public Integer getId() { return id; }
public void setId(Integer id) { this.id = id; }
private Integer id;
public String getNumber() { return number; }
public void setNumber(String number) { this.number = number; }
private String number;
@ManyToOne
public Customer getCustomer() { return customer; }
public void setCustomer(Customer customer) { this.customer = customer; }
private Customer number;
}
-- Table schema
|--------------| |----------|
| Order | | Customer |
|--------------| |----------|
| id | | id |
| number | |----------|
| customer_id |
| orders_order |
|--------------|
注意
We recommend you to convert the legacy @org.hibernate.annotations.IndexColumn
usages to @OrderColumn unless you are making use of the base property. The
base property lets you define the index value of the first element (aka
as base index). The usual value is 0 or 1. The default is 0 like in Java.
Looking again at the Hibernate mapping file equivalent, the index of an array or list
is always of type integer and is mapped using the <list-index> element. The mapped column
contains sequential integers that are numbered from zero by default.
例 7.9. index-list element for indexed collections in xml mapping
<list-index
column="column_name"
索引集合類(Indexed collections)
161
base="0|1|..."/>
column_name(必需):持有集合索引值的字段的名稱。
base(可選 — 默認爲 0)對應列表或隊列的第一個元素的索引字段的值。
倘若你的表沒有一個索引字段,當你仍然但願使用 List 做爲屬性類型,你應該把此屬性映射爲
Hibernate <bag>。從數據庫中獲取的時候,bag 不維護其順序,但也可選擇性的進行排序。
7.2.2.2. Maps
The question with Maps is where the key value is stored. There are everal options. Maps
can borrow their keys from one of the associated entity properties or have dedicated
columns to store an explicit key.
To use one of the target entity property as a key of the map, use
@MapKey(name="myProperty"), where myProperty is a property name in the target entity. When
using @MapKey without the name attribuate, the target entity primary key is used. The
map key uses the same column as the property pointed out. There is no additional column
defined to hold the map key, because the map key represent a target property. Be aware
that once loaded, the key is no longer kept in sync with the property. In other words, if
you change the property value, the key will not change automatically in your Java model.
例 7.10. Use of target entity property as map key via @MapKey
@Entity
public class Customer {
@Id @GeneratedValue public Integer getId() { return id; }
public void setId(Integer id) { this.id = id; }
private Integer id;
@OneToMany(mappedBy="customer")
@MapKey(name="number")
public Map<String,Order> getOrders() { return orders; }
public void setOrders(Map<String,Order> order) { this.orders = orders; }
private Map<String,Order> orders;
}
@Entity
public class Order {
@Id @GeneratedValue public Integer getId() { return id; }
public void setId(Integer id) { this.id = id; }
private Integer id;
public String getNumber() { return number; }
public void setNumber(String number) { this.number = number; }
private String number;
@ManyToOne
public Customer getCustomer() { return customer; }
public void setCustomer(Customer customer) { this.customer = customer; }
private Customer number;
}
第 7 章 集合映射(Collection mappings)
162
-- Table schema
|-------------| |----------|
| Order | | Customer |
|-------------| |----------|
| id | | id |
| number | |----------|
| customer_id |
|-------------|
Alternatively the map key is mapped to a dedicated column or columns. In order to
customize the mapping use one of the following annotations:
•@MapKeyColumn if the map key is a basic type. If you don't specify the column name,
the name of the property followed by underscore followed by KEY is used (for example
orders_KEY).
•@MapKeyEnumerated / @MapKeyTemporal if the map key type is respectively an enum or a Date.
•@MapKeyJoinColumn/@MapKeyJoinColumns if the map key type is another entity.
•@AttributeOverride/@AttributeOverrides when the map key is a embeddable object. Use key.
as a prefix for your embeddable object property names.
You can also use @MapKeyClass to define the type of the key if you don't use generics.
例 7.11. Map key as basic type using @MapKeyColumn
@Entity
public class Customer {
@Id @GeneratedValue public Integer getId() { return id; }
public void setId(Integer id) { this.id = id; }
private Integer id;
@OneToMany @JoinTable(name="Cust_Order")
@MapKeyColumn(name="orders_number")
public Map<String,Order> getOrders() { return orders; }
public void setOrders(Map<String,Order> orders) { this.orders = orders; }
private Map<String,Order> orders;
}
@Entity
public class Order {
@Id @GeneratedValue public Integer getId() { return id; }
public void setId(Integer id) { this.id = id; }
private Integer id;
public String getNumber() { return number; }
public void setNumber(String number) { this.number = number; }
private String number;
@ManyToOne
public Customer getCustomer() { return customer; }
索引集合類(Indexed collections)
163
public void setCustomer(Customer customer) { this.customer = customer; }
private Customer number;
}
-- Table schema
|-------------| |----------| |---------------|
| Order | | Customer | | Cust_Order |
|-------------| |----------| |---------------|
| id | | id | | customer_id |
| number | |----------| | order_id |
| customer_id | | orders_number |
|-------------| |---------------|
注意
We recommend you to migrate from @org.hibernate.annotations.MapKey /
@org.hibernate.annotation.MapKeyManyToMany to the new standard approach
described above
Using Hibernate mapping files there exists equivalent concepts to the descibed
annotations. You have to use <map-key>, <map-key-many-to-many> and <composite-map-key>.
<map-key> is used for any basic type, <map-key-many-to-many> for an entity reference and
<composite-map-key> for a composite type.
例 7.12. map-key xml mapping element
<map-key
column="column_name"
formula="any SQL expression"
type="type_name"
node="@attribute-name"
length="N"/>
column(可選):持有集合索引值的字段的名稱。
formula(可選):用於對錶鍵求值的 SQL 公式。
type(必需):映射鍵的類型。
例 7.13. map-key-many-to-many
<map-key-many-to-many
column="column_name"
formula="any SQL expression"
class="ClassName"
/>
第 7 章 集合映射(Collection mappings)
164
column(可選):用於集合索引值的外鍵字段的名稱。
formula(可選):用於對映射鍵的外鍵求值的 SQL 公式。
class(必需):用做映射鍵的實體類的名稱。
7.2.3. Collections of basic types and embeddable objects
In some situations you don't need to associate two entities but simply create a
collection of basic types or embeddable objects. Use the @ElementCollection for this case.
例 7.14. Collection of basic types mapped via @ElementCollection
@Entity
public class User {
[...]
public String getLastname() { ...}
@ElementCollection
@CollectionTable(name="Nicknames", joinColumns=@JoinColumn(name="user_id"))
@Column(name="nickname")
public Set<String> getNicknames() { ... }
}
The collection table holding the collection data is set using the @CollectionTable
annotation. If omitted the collection table name defaults to the concatenation of the
name of the containing entity and the name of the collection attribute, separated by
an underscore. In our example, it would be User_nicknames.
The column holding the basic type is set using the @Column annotation. If omitted, the
column name defaults to the property name: in our example, it would be nicknames.
But you are not limited to basic types, the collection type can be any embeddable
object. To override the columns of the embeddable object in the collection table, use
the @AttributeOverride annotation.
例 7.15. @ElementCollection for embeddable objects
@Entity
public class User {
[...]
public String getLastname() { ...}
@ElementCollection
@CollectionTable(name="Addresses", joinColumns=@JoinColumn(name="user_id"))
@AttributeOverrides({
@AttributeOverride(name="street1", column=@Column(name="fld_street"))
})
public Set<Address> getAddresses() { ... }
}
@Embeddable
Collections of basic types and embeddable objects
165
public class Address {
public String getStreet1() {...}
[...]
}
Such an embeddable object cannot contains a collection itself.
注意
in @AttributeOverride, you must use the value. prefix to override properties
of the embeddable object used in the map value and the key. prefix to
override properties of the embeddable object used in the map key.
@Entity
public class User {
@ElementCollection
@AttributeOverrides({
@AttributeOverride(name="key.street1", column=@Column(name="fld_street")),
@AttributeOverride(name="value.stars", column=@Column(name="fld_note"))
})
public Map<Address,Rating> getFavHomes() { ... }
注意
We recommend you to migrate from
@org.hibernate.annotations.CollectionOfElements to the new @ElementCollection
annotation.
Using the mapping file approach a collection of values is mapped using the <element>
tag. For example:
例 7.16. <element> tag for collection values using mapping files
<element
column="column_name"
formula="any SQL expression"
type="typename"
length="L"
precision="P"
scale="S"
not-null="true|false"
unique="true|false"
node="element-name"
/>
第 7 章 集合映射(Collection mappings)
166
column(可選):持有集合元素值的字段的名稱。
formula(可選):用於對元素求值的 SQL 公式。
type(必需):集合元素的類型。
7.3. 高級集合映射(Advanced collection mappings)
7.3.1. 有序集合(Sorted collections)
Hibernate supports collections implementing java.util.SortedMap and java.util.SortedSet.
With annotations you declare a sort comparator using @Sort. You chose between the
comparator types unsorted, natural or custom. If you want to use your own comparator
implementation, you'll also have to specify the implementation class using the comparator
attribute. Note that you need to use either a SortedSet or a SortedMap interface.
例 7.17. Sorted collection with @Sort
@OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
@JoinColumn(name="CUST_ID")
@Sort(type = SortType.COMPARATOR, comparator = TicketComparator.class)
public SortedSet<Ticket> getTickets() {
return tickets;
}
Using Hibernate mapping files you specify a comparator in the mapping file with <sort>:
例 7.18. Sorted collection using xml mapping
<set name="aliases"
table="person_aliases"
sort="natural">
<key column="person"/>
<element column="name" type="string"/>
</set>
<map name="holidays" sort="my.custom.HolidayComparator">
<key column="year_id"/>
<map-key column="hol_name" type="string"/>
<element column="hol_date" type="date"/>
</map>
sort 屬性中容許的值包括 unsorted,natural 和某個實現了 java.util.Comparator 的類的名稱。
提示
分類集合的行爲事實上象 java.util.TreeSet 或者 java.util.TreeMap。
雙向關聯(Bidirectional associations)
167
If you want the database itself to order the collection elements, use the order-by
attribute of set, bag or map mappings. This solution is implemented using LinkedHashSet
or LinkedHashMap and performs the ordering in the SQL query and not in the memory.
例 7.19. Sorting in database using order-by
<set name="aliases" table="person_aliases" order-by="lower(name) asc">
<key column="person"/>
<element column="name" type="string"/>
</set>
<map name="holidays" order-by="hol_date, hol_name">
<key column="year_id"/>
<map-key column="hol_name" type="string"/>
<element column="hol_date type="date"/>
</map>
注意
注意:這個 order-by 屬性的值是一個 SQL 排序子句而不是 HQL 的。
關聯還能夠在運行時使用集合 filter() 根據任意的條件來排序:
例 7.20. Sorting via a query filter
sortedUsers = s.createFilter( group.getUsers(), "order by this.name" ).list();
7.3.2. 雙向關聯(Bidirectional associations)
雙向關聯容許經過關聯的任一端訪問另一端。在 Hibernate 中,支持兩種類型的雙向關聯:
一對多(one-to-many)
Set 或者 bag 值在一端,單獨值(非集合)在另一端
多對多(many-to-many)
兩端都是 set 或 bag 值
Often there exists a many to one association which is the owner side of a bidirectional
relationship. The corresponding one to many association is in this case annotated by
@OneToMany(mappedBy=...)
例 7.21. Bidirectional one to many with many to one side as association
owner
@Entity
第 7 章 集合映射(Collection mappings)
168
public class Troop {
@OneToMany(mappedBy="troop")
public Set<Soldier> getSoldiers() {
...
}
@Entity
public class Soldier {
@ManyToOne
@JoinColumn(name="troop_fk")
public Troop getTroop() {
...
}
Troop has a bidirectional one to many relationship with Soldier through the troop
property. You don't have to (must not) define any physical mapping in the mappedBy side.
To map a bidirectional one to many, with the one-to-many side as the owning side, you
have to remove the mappedBy element and set the many to one @JoinColumn as insertable
and updatable to false. This solution is not optimized and will produce additional
UPDATE statements.
例 7.22. Bidirectional associtaion with one to many side as owner
@Entity
public class Troop {
@OneToMany
@JoinColumn(name="troop_fk") //we need to duplicate the physical information
public Set<Soldier> getSoldiers() {
...
}
@Entity
public class Soldier {
@ManyToOne
@JoinColumn(name="troop_fk", insertable=false, updatable=false)
public Troop getTroop() {
...
}
How does the mappping of a bidirectional mapping look like in Hibernate mapping xml?
There you define a bidirectional one-to-many association by mapping a one-to-many
association to the same table column(s) as a many-to-one association and declaring the
many-valued end inverse="true".
例 7.23. Bidirectional one to many via Hibernate mapping files
<class name="Parent">
<id name="id" column="parent_id"/>
....
雙向關聯(Bidirectional associations)
169
<set name="children" inverse="true">
<key column="parent_id"/>
<one-to-many class="Child"/>
</set>
</class>
<class name="Child">
<id name="id" column="child_id"/>
....
<many-to-one name="parent"
class="Parent"
column="parent_id"
not-null="true"/>
</class>
在「一」這一端定義 inverse="true" 不會影響級聯操做,兩者是正交的概念。
A many-to-many association is defined logically using the @ManyToMany annotation. You
also have to describe the association table and the join conditions using the @JoinTable
annotation. If the association is bidirectional, one side has to be the owner and one
side has to be the inverse end (ie. it will be ignored when updating the relationship
values in the association table):
例 7.24. Many to many association via @ManyToMany
@Entity
public class Employer implements Serializable {
@ManyToMany(
targetEntity=org.hibernate.test.metadata.manytomany.Employee.class,
cascade={CascadeType.PERSIST, CascadeType.MERGE}
)
@JoinTable(
name="EMPLOYER_EMPLOYEE",
joinColumns=@JoinColumn(name="EMPER_ID"),
inverseJoinColumns=@JoinColumn(name="EMPEE_ID")
)
public Collection getEmployees() {
return employees;
}
...
}
@Entity
public class Employee implements Serializable {
@ManyToMany(
cascade = {CascadeType.PERSIST, CascadeType.MERGE},
mappedBy = "employees",
targetEntity = Employer.class
)
public Collection getEmployers() {
return employers;
}
第 7 章 集合映射(Collection mappings)
170
}
In this example @JoinTable defines a name, an array of join columns, and an array of
inverse join columns. The latter ones are the columns of the association table which
refer to the Employee primary key (the "other side"). As seen previously, the other
side don't have to (must not) describe the physical mapping: a simple mappedBy argument
containing the owner side property name bind the two.
As any other annotations, most values are guessed in a many to many relationship.
Without describing any physical mapping in a unidirectional many to many the following
rules applied. The table name is the concatenation of the owner table name, _ and
the other side table name. The foreign key name(s) referencing the owner table is
the concatenation of the owner table name, _ and the owner primary key column(s).
The foreign key name(s) referencing the other side is the concatenation of the owner
property name, _, and the other side primary key column(s). These are the same rules
used for a unidirectional one to many relationship.
例 7.25. Default values for @ManyToMany (uni-directional)
@Entity
public class Store {
@ManyToMany(cascade = CascadeType.PERSIST)
public Set<City> getImplantedIn() {
...
}
}
@Entity
public class City {
... //no bidirectional relationship
}
A Store_City is used as the join table. The Store_id column is a foreign key to the Store
table. The implantedIn_id column is a foreign key to the City table.
Without describing any physical mapping in a bidirectional many to many the following
rules applied. The table name is the concatenation of the owner table name, _ and
the other side table name. The foreign key name(s) referencing the owner table is the
concatenation of the other side property name, _, and the owner primary key column(s).
The foreign key name(s) referencing the other side is the concatenation of the owner
property name, _, and the other side primary key column(s). These are the same rules
used for a unidirectional one to many relationship.
例 7.26. Default values for @ManyToMany (bi-directional)
@Entity
public class Store {
雙向關聯(Bidirectional associations)
171
@ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
public Set<Customer> getCustomers() {
...
}
}
@Entity
public class Customer {
@ManyToMany(mappedBy="customers")
public Set<Store> getStores() {
...
}
}
A Store_Customer is used as the join table. The stores_id column is a foreign key to the
Store table. The customers_id column is a foreign key to the Customer table.
Using Hibernate mapping files you can map a bidirectional many-to-many association by
mapping two many-to-many associations to the same database table and declaring one
end as inverse.
注意
You cannot select an indexed collection.
例 7.27 「Many to many association using Hibernate mapping files」 shows a bidirectional
many-to-many association that illustrates how each category can have many items and
each item can be in many categories:
例 7.27. Many to many association using Hibernate mapping files
<class name="Category">
<id name="id" column="CATEGORY_ID"/>
...
<bag name="items" table="CATEGORY_ITEM">
<key column="CATEGORY_ID"/>
<many-to-many class="Item" column="ITEM_ID"/>
</bag>
</class>
<class name="Item">
<id name="id" column="ITEM_ID"/>
...
<!-- inverse end -->
<bag name="categories" table="CATEGORY_ITEM" inverse="true">
<key column="ITEM_ID"/>
<many-to-many class="Category" column="CATEGORY_ID"/>
</bag>
</class>
第 7 章 集合映射(Collection mappings)
172
若是隻對關聯的反向端進行了改變,這個改變不會被持久化。 這表示 Hibernate 爲每一個雙向關
聯在內存中存在兩次表現,一個從 A 鏈接到 B,另外一個從 B 鏈接到 A。若是你回想一下 Java 對
象模型,咱們是如何在 Java 中建立多對多關係的,這可讓你更容易理解:
例  7.28.  Effect of inverse vs. non-inverse side of many to many
associations
category.getItems().add(item); // The category now "knows" about the relationship
item.getCategories().add(category); // The item now "knows" about the relationship
session.persist(item); // The relationship won't be saved!
session.persist(category); // The relationship will be saved
非反向端用於把內存中的表示保存到數據庫中。
7.3.3. 雙向關聯,涉及有序集合類
There are some additional considerations for bidirectional mappings with indexed
collections (where one end is represented as a <list> or <map>) when using Hibernate
mapping files. If there is a property of the child class that maps to the index column
you can use inverse="true" on the collection mapping:
例 7.29. Bidirectional association with indexed collection
<class name="Parent">
<id name="id" column="parent_id"/>
....
<map name="children" inverse="true">
<key column="parent_id"/>
<map-key column="name"
type="string"/>
<one-to-many class="Child"/>
</map>
</class>
<class name="Child">
<id name="id" column="child_id"/>
....
<property name="name"
not-null="true"/>
<many-to-one name="parent"
class="Parent"
column="parent_id"
not-null="true"/>
</class>
可是,倘若子類中沒有這樣的屬性存在,咱們不能認爲這個關聯是真正的雙向關聯(信息不對
稱,在關聯的一端有一些另一端沒有的信息)。在這種狀況下,咱們不能使用
inverse="true"。咱們須要這樣用:
三重關聯(Ternary associations)
173
例 7.30. Bidirectional association with indexed collection, but no index
column
<class name="Parent">
<id name="id" column="parent_id"/>
....
<map name="children">
<key column="parent_id"
not-null="true"/>
<map-key column="name"
type="string"/>
<one-to-many class="Child"/>
</map>
</class>
<class name="Child">
<id name="id" column="child_id"/>
....
<many-to-one name="parent"
class="Parent"
column="parent_id"
insert="false"
update="false"
not-null="true"/>
</class>
注意在這個映射中,關聯中集合類"值"一端負責來更新外鍵。
7.3.4. 三重關聯(Ternary associations)
有三種可能的途徑來映射一個三重關聯。第一種是使用一個 Map,把一個關聯做爲其索引:
例 7.31. Ternary association mapping
@Entity
public class Company {
@Id
int id;
...
@OneToMany // unidirectional
@MapKeyJoinColumn(name="employee_id")
Map<Employee, Contract> contracts;
}
// or
<map name="contracts">
<key column="employer_id" not-null="true"/>
<map-key-many-to-many column="employee_id" class="Employee"/>
<one-to-many class="Contract"/>
</map>
第 7 章 集合映射(Collection mappings)
174
A second approach is to remodel the association as an entity class. This is the most
common approach. A final alternative is to use composite elements, which will be
discussed later.
7.3.5. Using an <idbag>
The majority of the many-to-many associations and collections of values shown previously
all map to tables with composite keys, even though it has been suggested that entities
should have synthetic identifiers (surrogate keys). A pure association table does not
seem to benefit much from a surrogate key, although a collection of composite values
might. For this reason Hibernate provides a feature that allows you to map many-tomany
associations and collections of values to a table with a surrogate key.
<idbag> 屬性讓你使用 bag 語義來映射一個 List (或 Collection)。
<idbag name="lovers" table="LOVERS">
<collection-id column="ID" type="long">
<generator class="sequence"/>
</collection-id>
<key column="PERSON1"/>
<many-to-many column="PERSON2" class="Person" fetch="join"/>
</idbag>
你能夠理解,<idbag> 人工的 id 生成器,就好像是實體類同樣!集合的每一行都有一個不一樣的人
造關鍵字。可是,Hibernate 沒有提供任何機制來讓你取得某個特定行的人造關鍵字。
注意 <idbag> 的更新性能要比普通的 <bag> 高得多!Hibernate 能夠有效的定位到不一樣的行,分
別進行更新或刪除工做,就如同處理一個 list,map 或者 set 同樣。
在目前的實現中,還不支持使用 identity 標識符生成器策略來生成 <idbag> 集合的標識符。
7.4. 集合例子(Collection example)
集合例子(Collection example)。
下面的代碼是用來添加一個新的 Child:
例 7.32. Example classes Parent and Child
public class Parent {
private long id;
private Set<Child> children;
// getter/setter
...
}
public class Child {
集合例子(Collection example)
175
private long id;
private String name
// getter/setter
...
}
這個類有一個 Child 的實例集合。若是每個子實例至多有一個父實例,那麼最天然的映射是一
個 one-to-many 的關聯關係:
例  7.33.  One to many unidirectional Parent-Child relationship using
annotations
public class Parent {
@Id
@GeneratedValue
private long id;
@OneToMany
private Set<Child> children;
// getter/setter
...
}
public class Child {
@Id
@GeneratedValue
private long id;
private String name;node

// getter/setter
...
}
例 7.34. One to many unidirectional Parent-Child relationship using mapping
files
<hibernate-mapping>
<class name="Parent">
<id name="id">
<generator class="sequence"/>
</id>
<set name="children">
<key column="parent_id"/>
<one-to-many class="Child"/>
</set>
第 7 章 集合映射(Collection mappings)
176
</class>
<class name="Child">
<id name="id">
<generator class="sequence"/>
</id>
<property name="name"/>
</class>
</hibernate-mapping>
在如下的表定義中反應了這個映射關係:
例 7.35. Table definitions for unidirectional Parent-Child relationship
create table parent ( id bigint not null primary key )
create table child ( id bigint not null primary key, name varchar(255), parent_id bigint )
alter table child add constraint childfk0 (parent_id) references parent
若是父親是必須的,那麼就能夠使用雙向 one-to-many 的關聯了:
例  7.36.  One to many bidirectional Parent-Child relationship using
annotations
public class Parent {
@Id
@GeneratedValue
private long id;
@OneToMany(mappedBy="parent")
private Set<Child> children;
// getter/setter
...
}
public class Child {
@Id
@GeneratedValue
private long id;
private String name;
@ManyToOne
private Parent parent;git

// getter/setter
...
}
集合例子(Collection example)
177
例 7.37. One to many bidirectional Parent-Child relationship using mapping
files
<hibernate-mapping>
<class name="Parent">
<id name="id">
<generator class="sequence"/>
</id>
<set name="children" inverse="true">
<key column="parent_id"/>
<one-to-many class="Child"/>
</set>
</class>
<class name="Child">
<id name="id">
<generator class="sequence"/>
</id>
<property name="name"/>
<many-to-one name="parent" class="Parent" column="parent_id" not-null="true"/>
</class>
</hibernate-mapping>
請注意 NOT NULL 的約束:
例 7.38. Table definitions for bidirectional Parent-Child relationship
create table parent ( id bigint not null primary key )
create table child ( id bigint not null
primary key,
name varchar(255),
parent_id bigint not null )
alter table child add constraint childfk0 (parent_id) references parent
Alternatively, if this association must be unidirectional you can enforce the NOT NULL
constraint.
例 7.39. Enforcing NOT NULL constraint in unidirectional relation using
annotations
public class Parent {
@Id
@GeneratedValue
private long id;
@OneToMany(optional=false)
private Set<Child> children;
第 7 章 集合映射(Collection mappings)
178
// getter/setter
...
}
public class Child {
@Id
@GeneratedValue
private long id;
private String name;web

// getter/setter
...
}
例 7.40. Enforcing NOT NULL constraint in unidirectional relation using
mapping files
<hibernate-mapping>
<class name="Parent">
<id name="id">
<generator class="sequence"/>
</id>
<set name="children">
<key column="parent_id" not-null="true"/>
<one-to-many class="Child"/>
</set>
</class>
<class name="Child">
<id name="id">
<generator class="sequence"/>
</id>
<property name="name"/>
</class>
</hibernate-mapping>
On the other hand, if a child has multiple parents, a many-to-many association is
appropriate.
例 7.41. Many to many Parent-Child relationship using annotations
public class Parent {
@Id
@GeneratedValue
private long id;
@ManyToMany
集合例子(Collection example)
179
private Set<Child> children;
// getter/setter
...
}
public class Child {
@Id
@GeneratedValue
private long id;
private String name;算法

// getter/setter
...
}
例 7.42. Many to many Parent-Child relationship using mapping files
<hibernate-mapping>
<class name="Parent">
<id name="id">
<generator class="sequence"/>
</id>
<set name="children" table="childset">
<key column="parent_id"/>
<many-to-many class="Child" column="child_id"/>
</set>
</class>
<class name="Child">
<id name="id">
<generator class="sequence"/>
</id>
<property name="name"/>
</class>
</hibernate-mapping>
表定義:
例 7.43. Table definitions for many to many releationship
create table parent ( id bigint not null primary key )
create table child ( id bigint not null primary key, name varchar(255) )
create table childset ( parent_id bigint not null,
child_id bigint not null,
primary key ( parent_id, child_id ) )
alter table childset add constraint childsetfk0 (parent_id) references parent
第 7 章 集合映射(Collection mappings)
180
alter table childset add constraint childsetfk1 (child_id) references child
For more examples and a complete explanation of a parent/child relationship mapping,
see 第  24 章 示例:父子關係(Parent/Child) for more information. Even more complex
association mappings are covered in the next chapter.
第 8
181
關聯關係映射
8.1. 介紹
關聯關係映射一般狀況是最難配置正確的。在這個部分中,咱們從單向關係映射開始,而後考慮
雙向關係映射,逐步講解典型的案例。在全部的例子中,咱們都使將用 Person 和 Address。
咱們根據映射關係是否涉及鏈接表以及多樣性(multiplicity)來劃分關聯類型。
在傳統的數據建模中,容許爲 Null 值的外鍵被認爲是一種很差的實踐,所以咱們全部的例子中
都使用不容許爲 Null 的外鍵。這並非 Hibernate的 要求,即便你刪除掉不容許爲 Null 的約
束,Hibernate 映射同樣能夠工做的很好。
8.2. 單向關聯(Unidirectional associations)
8.2.1. 多對一(many-to-one)
單向 many-to-one 關聯是最多見的單向關聯關係。
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<many-to-one name="address"
column="addressId"
not-null="true"/>
</class>
<class name="Address">
<id name="id" column="addressId">
<generator class="native"/>
</id>
</class
>
create table Person ( personId bigint not null primary key, addressId bigint not null )
create table Address ( addressId bigint not null primary key )sql

8.2.2. 一對一(One-to-one)
基於外鍵關聯的單向一對一關聯和單向多對一關聯幾乎是同樣的。惟一的不一樣就是單向一對一關
聯中的外鍵字段具備惟一性約束。
<class name="Person">
第 8 章 關聯關係映射
182
<id name="id" column="personId">
<generator class="native"/>
</id>
<many-to-one name="address"
column="addressId"
unique="true"
not-null="true"/>
</class>
<class name="Address">
<id name="id" column="addressId">
<generator class="native"/>
</id>
</class
>
create table Person ( personId bigint not null primary key, addressId bigint not null unique )
create table Address ( addressId bigint not null primary key )數據庫

基於主鍵關聯的單向一對一關聯一般使用一個特定的 id 生成器,然而在這個例子中咱們掉換了
關聯的方向:
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
</class>
<class name="Address">
<id name="id" column="personId">
<generator class="foreign">
<param name="property"
>person</param>
</generator>
</id>
<one-to-one name="person" constrained="true"/>
</class
>
create table Person ( personId bigint not null primary key )
create table Address ( personId bigint not null primary key )express

8.2.3. 一對多(one-to-many)
基於外鍵關聯的單向一對多關聯是一種不多見的狀況,咱們不推薦使用它。
使用鏈接表的單向關聯(Unidirectional associations with join tables)
183
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<set name="addresses">
<key column="personId"
not-null="true"/>
<one-to-many class="Address"/>
</set>
</class>
<class name="Address">
<id name="id" column="addressId">
<generator class="native"/>
</id>
</class
>
create table Person ( personId bigint not null primary key )
create table Address ( addressId bigint not null primary key, personId bigint not null )apache

咱們認爲對於這種關聯關係最好使用鏈接表。
8.3. 使用鏈接表的單向關聯(Unidirectional
associations with join tables)
8.3.1. 一對多(one-to-many)
基於鏈接表的單向一對多關聯 應該優先被採用。請注意,經過指定unique="true",咱們能夠把多
樣性從多對多改變爲一對多。
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<set name="addresses" table="PersonAddress">
<key column="personId"/>
<many-to-many column="addressId"
unique="true"
class="Address"/>
</set>
</class>
<class name="Address">
<id name="id" column="addressId">
<generator class="native"/>
</id>
</class
第 8 章 關聯關係映射
184
>
create table Person ( personId bigint not null primary key )
create table PersonAddress ( personId not null, addressId bigint not null primary key )
create table Address ( addressId bigint not null primary key )

8.3.2. 多對一(many-to-one)
基於鏈接表的單向多對一關聯在關聯關係可選的狀況下應用也很廣泛。例如:
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<join table="PersonAddress"
optional="true">
<key column="personId" unique="true"/>
<many-to-one name="address"
column="addressId"
not-null="true"/>
</join>
</class>
<class name="Address">
<id name="id" column="addressId">
<generator class="native"/>
</id>
</class
>
create table Person ( personId bigint not null primary key )
create table PersonAddress ( personId bigint not null primary key, addressId bigint not null )
create table Address ( addressId bigint not null primary key )

8.3.3. 一對一(One-to-one)
基於鏈接表的單向一對一關聯也是可行的,但很是少見。
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<join table="PersonAddress"
optional="true">
多對多(many-to-many)
185
<key column="personId"
unique="true"/>
<many-to-one name="address"
column="addressId"
not-null="true"
unique="true"/>
</join>
</class>
<class name="Address">
<id name="id" column="addressId">
<generator class="native"/>
</id>
</class
>
create table Person ( personId bigint not null primary key )
create table PersonAddress ( personId bigint not null primary key, addressId bigint not null
unique )
create table Address ( addressId bigint not null primary key )

8.3.4. 多對多(many-to-many)
最後,這裏是一個單向多對多關聯的例子。
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<set name="addresses" table="PersonAddress">
<key column="personId"/>
<many-to-many column="addressId"
class="Address"/>
</set>
</class>
<class name="Address">
<id name="id" column="addressId">
<generator class="native"/>
</id>
</class
>
create table Person ( personId bigint not null primary key )
create table PersonAddress ( personId bigint not null, addressId bigint not null, primary key
(personId, addressId) )
create table Address ( addressId bigint not null primary key )
第 8 章 關聯關係映射
186

8.4. 雙向關聯(Bidirectional associations)
8.4.1. 一對多(one to many)/多對一(many to one)
雙向多對一關聯 是最多見的關聯關係。下面的例子解釋了這種標準的父/子關聯關係。
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<many-to-one name="address"
column="addressId"
not-null="true"/>
</class>
<class name="Address">
<id name="id" column="addressId">
<generator class="native"/>
</id>
<set name="people" inverse="true">
<key column="addressId"/>
<one-to-many class="Person"/>
</set>
</class
>
create table Person ( personId bigint not null primary key, addressId bigint not null )
create table Address ( addressId bigint not null primary key )

若是你使用 List(或者其餘有序集合類),你須要設置外鍵對應的 key 列爲 not null。Hibernate
將從集合端管理關聯,維護每一個元素的索引,並經過設置 update="false" 和 insert="false" 來對
另外一端反向操做。
<class name="Person">
<id name="id"/>
...
<many-to-one name="address"
column="addressId"
not-null="true"
insert="false"
update="false"/>
</class>
<class name="Address">
<id name="id"/>
一對一(One-to-one)
187
...
<list name="people">
<key column="addressId" not-null="true"/>
<list-index column="peopleIdx"/>
<one-to-many class="Person"/>
</list>
</class
>
倘若集合映射的 <key> 元素對應的底層外鍵字段是 NOT NULL 的,那麼爲這一 key 元素定義 notnull="true"
是很重要的。不要僅僅爲可能的嵌套 <column>元素定義 not-null="true",<key> 元素
也是須要的。
8.4.2. 一對一(One-to-one)
基於外鍵關聯的雙向一對一關聯也很常見。
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<many-to-one name="address"
column="addressId"
unique="true"
not-null="true"/>
</class>
<class name="Address">
<id name="id" column="addressId">
<generator class="native"/>
</id>
<one-to-one name="person"
property-ref="address"/>
</class
>
create table Person ( personId bigint not null primary key, addressId bigint not null unique )
create table Address ( addressId bigint not null primary key )

基於主鍵關聯的一對一關聯須要使用特定的 id 生成器:
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<one-to-one name="address"/>
</class>
第 8 章 關聯關係映射
188
<class name="Address">
<id name="id" column="personId">
<generator class="foreign">
<param name="property"
>person</param>
</generator>
</id>
<one-to-one name="person"
constrained="true"/>
</class
>
create table Person ( personId bigint not null primary key )
create table Address ( personId bigint not null primary key )

8.5. 使用鏈接表的雙向關聯(Bidirectional associations
with join tables)
8.5.1. 一對多(one to many)/多對一(many to one)
下面是一個基於鏈接表的雙向一對多關聯的例子。注意 inverse="true" 能夠出如今關聯的任意一
端,即 collection 端或者 join 端。
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<set name="addresses"
table="PersonAddress">
<key column="personId"/>
<many-to-many column="addressId"
unique="true"
class="Address"/>
</set>
</class>
<class name="Address">
<id name="id" column="addressId">
<generator class="native"/>
</id>
<join table="PersonAddress"
inverse="true"
optional="true">
<key column="addressId"/>
<many-to-one name="person"
column="personId"
not-null="true"/>
</join>
</class
一對一(one to one)
189
>
create table Person ( personId bigint not null primary key )
create table PersonAddress ( personId bigint not null, addressId bigint not null primary key )
create table Address ( addressId bigint not null primary key )

8.5.2. 一對一(one to one)
基於鏈接表的雙向一對一關聯也是可行的,但極爲罕見。
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<join table="PersonAddress"
optional="true">
<key column="personId"
unique="true"/>
<many-to-one name="address"
column="addressId"
not-null="true"
unique="true"/>
</join>
</class>
<class name="Address">
<id name="id" column="addressId">
<generator class="native"/>
</id>
<join table="PersonAddress"
optional="true"
inverse="true">
<key column="addressId"
unique="true"/>
<many-to-one name="person"
column="personId"
not-null="true"
unique="true"/>
</join>
</class
>
create table Person ( personId bigint not null primary key )
create table PersonAddress ( personId bigint not null primary key, addressId bigint not null
unique )
create table Address ( addressId bigint not null primary key )

第 8 章 關聯關係映射
190
8.5.3. 多對多(many-to-many)
下面是一個雙向多對多關聯的例子。
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<set name="addresses" table="PersonAddress">
<key column="personId"/>
<many-to-many column="addressId"
class="Address"/>
</set>
</class>
<class name="Address">
<id name="id" column="addressId">
<generator class="native"/>
</id>
<set name="people" inverse="true" table="PersonAddress">
<key column="addressId"/>
<many-to-many column="personId"
class="Person"/>
</set>
</class
>
create table Person ( personId bigint not null primary key )
create table PersonAddress ( personId bigint not null, addressId bigint not null, primary key
(personId, addressId) )
create table Address ( addressId bigint not null primary key )

8.6. 更復雜的關聯映射
更復雜的關聯鏈接極爲罕見。經過在映射文檔中嵌入 SQL 片段,Hibernate 也能夠處理更
爲複雜的狀況。好比,倘若包含歷史賬戶數據的表定義了 accountNumber、effectiveEndDate 和
effectiveStartDate 字段,按照下面映射:
<properties name="currentAccountKey">
<property name="accountNumber" type="string" not-null="true"/>
<property name="currentAccount" type="boolean">
<formula
>case when effectiveEndDate is null then 1 else 0 end</formula>
</property>
</properties>
<property name="effectiveEndDate" type="date"/>
<property name="effectiveStateDate" type="date" not-null="true"/>
更復雜的關聯映射
191
那麼咱們能夠對目前(current)實例(其 effectiveEndDate 爲 null)使用這樣的關聯映射:
<many-to-one name="currentAccountInfo"
property-ref="currentAccountKey"
class="AccountInfo">
<column name="accountNumber"/>
<formula
>'1'</formula>
</many-to-one
>
在更復雜的例子中,假想 Employee 和 Organization 之間的關聯是經過一個
Employment 中間表維護的,而中間表中填充了不少歷史僱員數據。那「僱員的最新僱主」這個關
聯(最新僱主就是具備最新的 startDate 的那個)能夠這樣映射:
<join>
<key column="employeeId"/>
<subselect>
select employeeId, orgId
from Employments
group by orgId
having startDate = max(startDate)
</subselect>
<many-to-one name="mostRecentEmployer"
class="Organization"
column="orgId"/>
</join
>
使用這一功能時能夠充滿創意和靈活性,但一般更加實用的是用 HQL 或條件查詢來處理這些情
況。
192
第 9
193
組件(Component)映射
組件(Component)這個概念在 Hibernate 中幾處不一樣的地方爲了避免同的目的被重複使用。
9.1. 依賴對象(Dependent objects)
組件(Component)是一個被包含的對象,在持久化的過程當中,它被看成值類型,而並不是一個實體
的引用。在這篇文檔中,組件這一術語指的是面向對象的合成概念(而並非系統構架層次上的
組件的概念)。舉個例子,你對人(Person)這個概念能夠像下面這樣來建模:
public class Person {
private java.util.Date birthday;
private Name name;
private String key;
public String getKey() {
return key;
}
private void setKey(String key) {
this.key=key;
}
public java.util.Date getBirthday() {
return birthday;
}
public void setBirthday(java.util.Date birthday) {
this.birthday = birthday;
}
public Name getName() {
return name;
}
public void setName(Name name) {
this.name = name;
}
......
......
}
public class Name {
char initial;
String first;
String last;
public String getFirst() {
return first;
}
void setFirst(String first) {
this.first = first;
}
public String getLast() {
return last;
}
void setLast(String last) {
this.last = last;
}
第 9 章 組件(Component)映射
194
public char getInitial() {
return initial;
}
void setInitial(char initial) {
this.initial = initial;
}
}
在持久化的過程當中,姓名(Name)能夠做爲人(Person)的一個組件。須要注意的是:你應該爲姓
名的持久化屬性定義 getter 和 setter 方法,可是你不須要實現任何的接口或申明標識符字段。
如下是這個例子的 Hibernate 映射文件:
<class name="eg.Person" table="person">
<id name="Key" column="pid" type="string">
<generator class="uuid"/>
</id>
<property name="birthday" type="date"/>
<component name="Name" class="eg.Name"
> <!-- class attribute optional -->
<property name="initial"/>
<property name="first"/>
<property name="last"/>
</component>
</class
>
人員(Person)表中將包括 pid,birthday,initial,first和 last 等字段。
就像全部的值類型同樣,組件不支持共享引用。換句話說,兩我的可能重名,可是兩個 Person
對象應該包含兩個獨立的 Name 對象,只不過這兩個 Name 對象具備「一樣」的值。組件的值可
覺得空,其定義以下。 每當 Hibernate 從新加載一個包含組件的對象,若是該組件的全部字段
爲空,Hibernate 將假定整個組件爲空。在大多數狀況下,這樣假定應該是沒有問題的。
組件的屬性能夠是任意一種 Hibernate 類型(包括集合,多對多關聯,以及其它組件等等)。嵌
套組件不該該被看成一種特殊的應用(Nested components should not be considered an exotic
usage)。Hibernate 傾向於支持細顆粒度的(fine-grained)對象模型。
<component> 元素容許加入一個 <parent> 子元素,在組件類內部就能夠有一個指向其容器的實體
的反向引用。
<class name="eg.Person" table="person">
<id name="Key" column="pid" type="string">
<generator class="uuid"/>
</id>
<property name="birthday" type="date"/>
<component name="Name" class="eg.Name" unique="true">
<parent name="namedPerson"/> <!-- reference back to the Person -->
<property name="initial"/>
<property name="first"/>
<property name="last"/>
在集合中出現的依賴對象(Collections of dependent objects)
195
</component>
</class
>
9.2. 在集合中出現的依賴對象(Collections of dependent
objects)
Hibernate 支持組件的集合(例如:一個元素是姓名 Name 這種類型的數組)。你能夠使用
<composite-element> 標籤替代 <element> 標籤來定義你的組件集合。
<set name="someNames" table="some_names" lazy="true">
<key column="id"/>
<composite-element class="eg.Name"
> <!-- class attribute required -->
<property name="initial"/>
<property name="first"/>
<property name="last"/>
</composite-element>
</set
>
重要
注意,若是你定義的 Set 包含組合元素(composite-element),正確地實現
equals() 和 hashCode() 是很是重要的。
組合元素能夠包含組件,可是不能包含集合。若是你的組合元素自身包含組件,你必須使用
<nested-composite-element> 標籤。這是一個至關特殊的案例 — 在一個組件的集合裏,那些組件
自己又能夠包含其餘的組件。這個時候你就應該考慮一下使用 one-to-many 關聯是否會更恰當。
嘗試對這個組合元素從新建模爲一個實體 — 可是須要注意的是,雖然 Java 模型和從新建模前
是同樣的,關係模型和持久性語義會有細微的變化。
請注意若是你使用 <set> 標籤,一個組合元素的映射不支持可能爲空的屬性. 當刪除對象
時,Hibernate 必須使用每個字段的值來肯定一條記錄(在組合元素表中,沒有單獨的關鍵字
段),若是有爲 null 的字段,這樣作就不可能了。你必須做出一個選擇,要麼在組合元素中使
用不能爲空的屬性,要麼選擇使用 <list>,<map>,<bag> 或者 <idbag> 而不是 <set>。
組合元素有個特別的用法是它能夠包含一個<many-to-one>元素。相似這樣的映射容許你將一個
many-to-many 關聯表的額外字段映射爲組合元素類。接下來的的例子是從 Order 到 Item 的一個
多對多的關聯關係,關聯屬性是 purchaseDate,price 和 quantity 。
<class name="eg.Order" .... >
....
<set name="purchasedItems" table="purchase_items" lazy="true">
<key column="order_id">
第 9 章 組件(Component)映射
196
<composite-element class="eg.Purchase">
<property name="purchaseDate"/>
<property name="price"/>
<property name="quantity"/>
<many-to-one name="item" class="eg.Item"/> <!-- class attribute is optional -->
</composite-element>
</set>
</class
>
固然,當你定義 Item 時,你沒法引用這些 purchase,所以你沒法實現雙向關聯查詢。記住組件
是值類型,而且不容許共享引用。某一個特定的 Purchase 能夠放在 Order 的集合中,但它不能同
時被 Item 所引用。
其實組合元素的這個用法能夠擴展到三重或多重關聯:
<class name="eg.Order" .... >
....
<set name="purchasedItems" table="purchase_items" lazy="true">
<key column="order_id">
<composite-element class="eg.OrderLine">
<many-to-one name="purchaseDetails class="eg.Purchase"/>
<many-to-one name="item" class="eg.Item"/>
</composite-element>
</set>
</class
>
在查詢中,表達組合元素的語法和關聯到其餘實體的語法是同樣的。
9.3. 組件做爲 Map 的索引(Components as Map
indices )
<composite-map-key> 元素容許你映射一個組件類做爲一個 Map 的 key,前提是你必須正確的在這
個類中重寫了 hashCode() 和 equals() 方法。
9.4. 組件做爲聯合標識符(Components as composite
identifiers)
你能夠使用一個組件做爲一個實體類的標識符。你的組件類必須知足如下要求:
•它必須實現 java.io.Serializable 接口
•它必須從新實現 equals() 和 hashCode() 方法,始終和組合關鍵字在數據庫中的概念保持一致
組件做爲聯合標識符(Components as composite identifiers)
197
注意
注意:在 Hibernate3 中,第二個要求並不是是 Hibernate 強制必須的。但最好這
樣作。
你不能使用一個 IdentifierGenerator 產生組合關鍵字。一個應用程序必須分配它本身的標識符。
使用 <composite-id> 標籤(而且內嵌 <key-property> 元素)代替一般的 <id> 標籤。比
如,OrderLine 類具備一個主鍵,這個主鍵依賴於 Order 的(聯合)主鍵。
<class name="OrderLine">
<composite-id name="id" class="OrderLineId">
<key-property name="lineId"/>
<key-property name="orderId"/>
<key-property name="customerId"/>
</composite-id>
<property name="name"/>
<many-to-one name="order" class="Order"
insert="false" update="false">
<column name="orderId"/>
<column name="customerId"/>
</many-to-one>
....
</class
>
如今,任何指向 OrderLine 的外鍵都是複合的。在你的映射文件中,必須爲其餘類也這樣聲明。
例如,一個指向 OrderLine 的關聯可能被這樣映射:
<many-to-one name="orderLine" class="OrderLine">
<!-- the "class" attribute is optional, as usual -->
<column name="lineId"/>
<column name="orderId"/>
<column name="customerId"/>
</many-to-one
>
提示
注意在各個地方 column 標籤都是 column 屬性的替代寫法。使用 column 元素只是
給出一個更詳細的選項,在使用 hbm2ddl 時會更有用。
第 9 章 組件(Component)映射
198
指向 OrderLine 的多對多關聯也使用聯合外鍵:
<set name="undeliveredOrderLines">
<key column name="warehouseId"/>
<many-to-many class="OrderLine">
<column name="lineId"/>
<column name="orderId"/>
<column name="customerId"/>
</many-to-many>
</set
>
在 Order 中,OrderLine 的集合則是這樣:
<set name="orderLines" inverse="true">
<key>
<column name="orderId"/>
<column name="customerId"/>
</key>
<one-to-many class="OrderLine"/>
</set
>
與一般同樣,<one-to-many> 元素不聲明任何列。
倘若 OrderLine 自己擁有一個集合,它也具備組合外鍵。
<class name="OrderLine">
....
....
<list name="deliveryAttempts">
<key
> <!-- a collection inherits the composite key type -->
<column name="lineId"/>
<column name="orderId"/>
<column name="customerId"/>
</key>
<list-index column="attemptId" base="1"/>
<composite-element class="DeliveryAttempt">
...
</composite-element>
</set>
</class
>
9.5. 動態組件(Dynamic components)
你甚至能夠映射 Map 類型的屬性:
動態組件(Dynamic components)
199
<dynamic-component name="userAttributes">
<property name="foo" column="FOO" type="string"/>
<property name="bar" column="BAR" type="integer"/>
<many-to-one name="baz" class="Baz" column="BAZ_ID"/>
</dynamic-component
>
從 <dynamic-component> 映射的語義上來說,它和 <component> 是相同的。這種映射類型的優
點在於經過修改映射文件,就能夠具備在部署時檢測真實屬性的能力。利用一個 DOM 解析器,
也能夠在程序運行時操做映射文件。更好的是,你能夠經過 Configuration 對象來訪問(或者修
改)Hibernate 的運行時元模型。
200
第 10
201
繼承映射(Inheritance Mapping)
10.1. 三種策略
Hibernate 支持三種基本的繼承映射策略:
•每一個類分層結構一張表(table per class hierarchy)
•table per subclass
•每一個具體類一張表(table per concrete class)
此外,Hibernate 還支持第四種稍有不一樣的多態映射策略:
•隱式多態(implicit polymorphism)
對於同一個繼承層次內的不一樣分支,能夠採用不一樣的映射策略,而後用隱式多 態來完成跨越
整個層次的多態。可是在同一個 <class> 根元素下,Hibernate 不支持混合了元素
<subclass>、<joined-subclass> 和 <union-subclass> 的映射。在同一個 <class> 元素下,能夠混
合使用「每一個類分層結構一張表」(table per hierarchy)和「每一個子類一張表」(table per
subclass) 這兩種映射策略,這是經過結合元素 <subclass> 和 <join> 來實現的(見後)。
在多個映射文件中,能夠直接在 hibernate-mapping 根下定義 subclass,union-subclass 和 joinedsubclass。也就是說,你能夠僅加入一個新的映射文件來擴展類層次。你必須在
subclass 的映射
中指明 extends 屬性,給出一個以前定義的超類的名字。注意,在之前,這一功能對映射文件的
順序有嚴格的要求,從 Hibernate 3 開始,使用 extends 關鍵字的時侯,對映射文件的順序不
再有要求;但在每一個映射文件裏,超類必須在子類以前定義。
<hibernate-mapping>
<subclass name="DomesticCat" extends="Cat" discriminator-value="D">
<property name="name" type="string"/>
</subclass>
</hibernate-mapping
>
10.1.1. 每一個類分層結構一張表(Table per class hierarchy)
假設咱們有接口Payment和它的幾個實現類: CreditCardPayment, CashPayment 和ChequePayment。
則「每一個類分層結構一張表」(Table per class hierarchy)的映射代碼以下所示:
<class name="Payment" table="PAYMENT">
<id name="id" type="long" column="PAYMENT_ID">
<generator class="native"/>
</id>
第 10 章 繼承映射(Inheritance Mapping)
202
<discriminator column="PAYMENT_TYPE" type="string"/>
<property name="amount" column="AMOUNT"/>
...
<subclass name="CreditCardPayment" discriminator-value="CREDIT">
<property name="creditCardType" column="CCTYPE"/>
...
</subclass>
<subclass name="CashPayment" discriminator-value="CASH">
...
</subclass>
<subclass name="ChequePayment" discriminator-value="CHEQUE">
...
</subclass>
</class
>
採用這種策略只須要一張表便可。它有一個很大的限制:要求那些由子類定義的字段, 如
CCTYPE,不能有非空(NOT NULL)約束。
10.1.2. 每一個子類一張表(Table per subclass)
對於上例中的幾個類而言,採用「每一個子類一張表」的映射策略,代碼以下所示:
<class name="Payment" table="PAYMENT">
<id name="id" type="long" column="PAYMENT_ID">
<generator class="native"/>
</id>
<property name="amount" column="AMOUNT"/>
...
<joined-subclass name="CreditCardPayment" table="CREDIT_PAYMENT">
<key column="PAYMENT_ID"/>
<property name="creditCardType" column="CCTYPE"/>
...
</joined-subclass>
<joined-subclass name="CashPayment" table="CASH_PAYMENT">
<key column="PAYMENT_ID"/>
...
</joined-subclass>
<joined-subclass name="ChequePayment" table="CHEQUE_PAYMENT">
<key column="PAYMENT_ID"/>
...
</joined-subclass>
</class
>
須要四張表。三個子類表經過主鍵關聯到超類表(於是關係模型其實是一對一關聯)。
10.1.3. 每一個子類一張表(Table per subclass),使用辨別標誌
(Discriminator)
注意,對「每一個子類一張表」的映射策略,Hibernate 的實現不須要辨別字段,而其餘的對象/
關係映射工具使用了一種不一樣於Hibernate的實現方法,該方法要求在超類表中有一個類型辨別字
混合使用「每一個類分層結構一張表」和「每一個子類一張表」
203
段(type discriminator column)。Hibernate 採用的方法更難實現,但從關係(數據庫)的角
度來看,按理說它更正確。若你願意使用帶有辨別字段的「每一個子類一張表」的策略,你能夠結
合使用 <subclass> 與<join>,以下所示:
<class name="Payment" table="PAYMENT">
<id name="id" type="long" column="PAYMENT_ID">
<generator class="native"/>
</id>
<discriminator column="PAYMENT_TYPE" type="string"/>
<property name="amount" column="AMOUNT"/>
...
<subclass name="CreditCardPayment" discriminator-value="CREDIT">
<join table="CREDIT_PAYMENT">
<key column="PAYMENT_ID"/>
<property name="creditCardType" column="CCTYPE"/>
...
</join>
</subclass>
<subclass name="CashPayment" discriminator-value="CASH">
<join table="CASH_PAYMENT">
<key column="PAYMENT_ID"/>
...
</join>
</subclass>
<subclass name="ChequePayment" discriminator-value="CHEQUE">
<join table="CHEQUE_PAYMENT" fetch="select">
<key column="PAYMENT_ID"/>
...
</join>
</subclass>
</class
>
可選的聲明 fetch="select",是用來告訴 Hibernate,在查詢超類時,不要使用外部鏈接(outer
join)來抓取子類 ChequePayment 的數據。
10.1.4. 混合使用「每一個類分層結構一張表」和「每一個子類一張表」
你甚至能夠採起以下方法混和使用「每一個類分層結構一張表」和「每一個子類一張表」這兩種策
略:
<class name="Payment" table="PAYMENT">
<id name="id" type="long" column="PAYMENT_ID">
<generator class="native"/>
</id>
<discriminator column="PAYMENT_TYPE" type="string"/>
<property name="amount" column="AMOUNT"/>
...
<subclass name="CreditCardPayment" discriminator-value="CREDIT">
<join table="CREDIT_PAYMENT">
<property name="creditCardType" column="CCTYPE"/>
...
</join>
第 10 章 繼承映射(Inheritance Mapping)
204
</subclass>
<subclass name="CashPayment" discriminator-value="CASH">
...
</subclass>
<subclass name="ChequePayment" discriminator-value="CHEQUE">
...
</subclass>
</class
>
對上述任何一種映射策略而言,指向根類 Payment 的關聯是使用 <many-to-one> 進行映射的。
<many-to-one name="payment" column="PAYMENT_ID" class="Payment"/>
10.1.5. 每一個具體類一張表(Table per concrete class)
對於「每一個具體類一張表」的映射策略,能夠採用兩種方法。第一種方法是使用 <unionsubclass>。
<class name="Payment">
<id name="id" type="long" column="PAYMENT_ID">
<generator class="sequence"/>
</id>
<property name="amount" column="AMOUNT"/>
...
<union-subclass name="CreditCardPayment" table="CREDIT_PAYMENT">
<property name="creditCardType" column="CCTYPE"/>
...
</union-subclass>
<union-subclass name="CashPayment" table="CASH_PAYMENT">
...
</union-subclass>
<union-subclass name="ChequePayment" table="CHEQUE_PAYMENT">
...
</union-subclass>
</class
>
這裏涉及三張與子類相關的表。每張表爲對應類的全部屬性(包括從超類繼承的屬性)定義相應
字段。
這種方式的侷限在於,若是一個屬性在超類中作了映射,其字段名必須與全部子類表中定義的相
同。(咱們可能會在 Hibernate 的後續發佈版本中放寬此限制。)不容許在聯合子類(union
subclass)的繼承層次中使用標識生成器策略(identity generator strategy),實際上,主鍵
的種子(primary key seed)不得不爲同一繼承層次中的所有被聯合子類所共用。
倘若超類是抽象類,請使用 abstract="true"。固然,倘若它不是抽象的,須要一個額外的表(上
面的例子中,默認是 PAYMENT),來保存超類的實例。
每一個具體類一張表,使用隱式多態
205
10.1.6. 每一個具體類一張表,使用隱式多態
另外一種可供選擇的方法是採用隱式多態:
<class name="CreditCardPayment" table="CREDIT_PAYMENT">
<id name="id" type="long" column="CREDIT_PAYMENT_ID">
<generator class="native"/>
</id>
<property name="amount" column="CREDIT_AMOUNT"/>
...
</class>
<class name="CashPayment" table="CASH_PAYMENT">
<id name="id" type="long" column="CASH_PAYMENT_ID">
<generator class="native"/>
</id>
<property name="amount" column="CASH_AMOUNT"/>
...
</class>
<class name="ChequePayment" table="CHEQUE_PAYMENT">
<id name="id" type="long" column="CHEQUE_PAYMENT_ID">
<generator class="native"/>
</id>
<property name="amount" column="CHEQUE_AMOUNT"/>
...
</class
>
請注意,這裏沒有顯性地說起 Payment 接口。Payment 的屬性映射到每一個子類。若是你想避免
重複,請考慮使用 XML 實體(如:DOCTYPE 聲明裏的 [ <!ENTITY allproperties SYSTEM
"allproperties.xml"> ] 和映射裏的 &allproperties;)。
這種方法的缺陷在於,在 Hibernate 執行多態查詢時(polymorphic queries)沒法生成帶 UNION
的 SQL 語句。
對於這種映射策略而言,一般用 <any> 來實現到 Payment 的多態關聯映射。
<any name="payment" meta-type="string" id-type="long">
<meta-value value="CREDIT" class="CreditCardPayment"/>
<meta-value value="CASH" class="CashPayment"/>
<meta-value value="CHEQUE" class="ChequePayment"/>
<column name="PAYMENT_CLASS"/>
<column name="PAYMENT_ID"/>
</any
>
第 10 章 繼承映射(Inheritance Mapping)
206
10.1.7. 隱式多態和其餘繼承映射混合使用
對這一映射還有一點須要注意。由於每一個子類都在各自獨立的元素 <class> 中映射(而且
Payment 只是一個接口),每一個子類能夠很容易的成爲另外一個繼承體系中的一部分!(你仍然能夠
對接口 Payment 使用多態查詢。)
<class name="CreditCardPayment" table="CREDIT_PAYMENT">
<id name="id" type="long" column="CREDIT_PAYMENT_ID">
<generator class="native"/>
</id>
<discriminator column="CREDIT_CARD" type="string"/>
<property name="amount" column="CREDIT_AMOUNT"/>
...
<subclass name="MasterCardPayment" discriminator-value="MDC"/>
<subclass name="VisaPayment" discriminator-value="VISA"/>
</class>
<class name="NonelectronicTransaction" table="NONELECTRONIC_TXN">
<id name="id" type="long" column="TXN_ID">
<generator class="native"/>
</id>
...
<joined-subclass name="CashPayment" table="CASH_PAYMENT">
<key column="PAYMENT_ID"/>
<property name="amount" column="CASH_AMOUNT"/>
...
</joined-subclass>
<joined-subclass name="ChequePayment" table="CHEQUE_PAYMENT">
<key column="PAYMENT_ID"/>
<property name="amount" column="CHEQUE_AMOUNT"/>
...
</joined-subclass>
</class
>
咱們仍是沒有明確的提到 Payment。若是咱們針對接口 Payment 執行查詢 — 如 from
Payment — Hibernate 自動返回 CreditCardPayment(和它的子類,由於 它們也實現了接口
Payment)、CashPayment 和 Chequepayment 的實例,但不返回 NonelectronicTransaction 的實例。
10.2. 限制
對「每一個具體類映射一張表」(table per concrete-class)的映射策略而言,隱式多態的方式
有必定的限制。而 <union-subclass> 映射的限制則沒有那麼嚴格。
下面表格中列出了在 Hibernte 中「每一個具體類一張表」的策略和隱式多態的限制。
限制
207
表 10.1. 繼承映射特性(Features of inheritance mappings)
繼承
策略
(Inheritance
strategy)
多態多
對多
多態一
對一
多態一
對多
多態多
對多
Polymorphic
load()/
get()
多態查

多態
鏈接
(join)
支持
外鏈接
(Outer
join)
讀取。
每一個類
分層
結構
一張表
(table
per
class
hierarchy)
<manyto-one>
<one-toone>
<one-tomany>
<manyto-many>
s.get(Payment.class,
id)
from
Payment
p
from
Order
o join
o.payment
p
supported
table
per
subclass
<manyto-one>
<one-toone>
<one-tomany>
<manyto-many>
s.get(Payment.class,
id)
from
Payment
p
from
Order
o join
o.payment
p
supported
每一個具
體類
一張表
(unionsubclass)
<manyto-one>
<one-toone>
<one-tomany>
(僅適
用於
inverse="true")
<manyto-many>
s.get(Payment.class,
id)
from
Payment
p
from
Order
o join
o.payment
p
supported
每一個具
體類
一張表
(隱式
多態)
<any> not
supported
not
supported
<manyto-any>
s.createCriteria(Payment.class).add( Restrictions.idEq(id) ).uniqueResult() from
Payment
p
not
supported
not
supported
208
第 11
209
與對象共事
Hibernate 是完整的對象/關係映射解決方案,它提供了對象狀態管理(state management)的功
能,使開發者再也不須要理會底層數據庫系統的細節。也就是說,相對於常見的 JDBC/SQL 持久層
方案中須要管理 SQL 語句,Hibernate 採用了更天然的面向對象的視角來持久化 Java 應用中的
數據。
換句話說,使用 Hibernate 的開發者應該老是關注對象的狀態(state),沒必要考慮 SQL 語句的
執行。這部分細節已經由 Hibernate 掌管穩當,只有開發者在進行系統性能調優的時候才須要進
行了解。
11.1. Hibernate 對象狀態(object states)
Hibernate 定義並支持下列對象狀態(state):
•瞬時(Transient) — 由 new 操做符建立,且還沒有與Hibernate
Session 關聯的對象被認定爲瞬時(Transient)的。瞬時(Transient)對象不會被持久化到數
據庫中,也不會被賦予持久化標識(identifier)。 若是瞬時(Transient)對象在程序中沒
有被引用,它會被垃圾回收器(garbage collector)銷燬。 使用 Hibernate Session能夠將其
變爲持久(Persistent)狀態。(Hibernate會自動執行必要的SQL語句)
•持久(Persistent) — 持久(Persistent)的實例在數據庫中有對應的記錄,並擁有一
個持久化標識(identifier)。 持久(Persistent)的實例多是剛被保存的,或剛被加載
的,不管哪種,按定義,它存在於相關聯的Session做用範圍內。 Hibernate會檢測處處於持
久(Persistent)狀態的對象的任何改動,在當前操做單元(unit of work)執行完畢時將對
象數據(state)與數據庫同步(synchronize)。 開發者不須要手動執行UPDATE。將對象從持
久(Persistent)狀態變成瞬時(Transient)狀態一樣也不須要手動執行 DELETE 語句。
•脫管(Detached) — 與持久(Persistent)對象關聯的Session被關閉後,對象就變爲脫管
(Detached)的。對脫管(Detached)對象的引用依然有效,對象可繼續被修改。脫管
(Detached)對象若是從新關聯到某個新的 Session 上, 會再次轉變爲持久(Persistent)的
(在Detached其間的改動將被持久化到數據庫)。 這個功能使得一種編程模型,即中間會給用
戶思考時間(user think-time)的長時間運行的操做單元(unit of work)的編程模型成爲可
能。咱們稱之爲應用程序事務,即從用戶觀點看是一個操做單元(unit of work)。
接下來咱們來細緻地討論下狀態(states)及狀態間的轉換(state transitions)(以及觸發狀
態轉換的 Hibernate 方法)。
11.2. 使對象持久化
Hibernate 認爲持久化類(persistent class)新實例化的對象是瞬時(Transient)的。咱們可
經過將瞬時(Transient)對象與 session 關聯而把它變爲持久的(Persistent)。
DomesticCat fritz = new DomesticCat();
第 11 章 與對象共事
210
fritz.setColor(Color.GINGER);
fritz.setSex('M');
fritz.setName("Fritz");
Long generatedId = (Long) sess.save(fritz);
若是 Cat 的持久化標識(identifier)是 generated 類型的, 那麼該標識(identifier)會自
動在 save() 被調用時產生並分配給 cat。若是 Cat 的持久化標識(identifier)是assigned類型
的,或是一個複合主鍵(composite key),那麼該標識(identifier)應當在調用 save() 以前
手動賦予給 cat。你也能夠按照 EJB3 early draft 中定義的語義,使用 persist() 替代save()。
•persist() 使一個臨時實例持久化。然而,它不保證當即把標識符值分配給持久性實例,這會發
生在沖刷(flush)的時候。persist() 也保證它在事務邊界外調用時不會執行 INSERT 語句。這
對於長期運行的帶有擴展會話/持久化上下文的會話是頗有用的。
•save() 保證返回一個標識符。若是須要運行 INSERT 來獲取標識符(如 "identity" 而非
"sequence" 生成器),這個 INSERT 將當即執行,無論你是否在事務內部仍是外部。這對於長
期運行的帶有擴展會話/持久化上下文的會話來講會出現問題。
此外,你能夠用一個重載版本的 save() 方法。
DomesticCat pk = new DomesticCat();
pk.setColor(Color.TABBY);
pk.setSex('F');
pk.setName("PK");
pk.setKittens( new HashSet() );
pk.addKitten(fritz);
sess.save( pk, new Long(1234) );
若是你持久化的對象有關聯的對象(associated objects)(例如上例中的 kittens 集合) 那麼
對這些對象(譯註:pk 和 kittens)進行持久化的順序是任意的(也就是說能夠先對 kittens 進
行持久化也能夠先對 pk 進行持久化), 除非你在外鍵列上有 NOT NULL 約束。 Hibernate 不會違
反外鍵約束,可是若是你用錯誤的順序持久化對象(譯註:在 pk 持久化以前持久化kitten),
那麼可能會違反 NOT NULL 約束。
一般你不會爲這些細節煩心,由於你極可能會使用 Hibernate 的傳播性持久化(transitive
persistence)功能自動保存相關聯那些對象。這樣連違反 NOT NULL 約束的狀況都不會出現了 —
Hibernate 會管好全部的事情。傳播性持久化(transitive persistence)將在本章稍後討論。
11.3. 裝載對象
若是你知道某個實例的持久化標識(identifier),你就能夠使用 Session 的 load() 方法來獲取
它。load() 的另外一個參數是指定類的對象。本方法會建立指定類的持久化實例,並從數據庫加載
其數據(state)。
Cat fritz = (Cat) sess.load(Cat.class, generatedId);
裝載對象
211
// you need to wrap primitive identifiers
long id = 1234;
DomesticCat pk = (DomesticCat) sess.load( DomesticCat.class, new Long(id) );
此外,你能夠把數據(state)加載到指定的對象實例上(覆蓋掉該實例原來的數據)。
Cat cat = new DomesticCat();
// load pk's state into cat
sess.load( cat, new Long(pkId) );
Set kittens = cat.getKittens();
請注意若是沒有匹配的數據庫記錄,load() 方法可能拋出沒法恢復的異常(unrecoverable
exception)。若是類的映射使用了代理(proxy),load() 方法會返回一個未初始化的代理,直
到你調用該代理的某方法時纔會去訪問數據庫。 若你但願在某對象中建立一個指向另外一個對象的
關聯,又不想在從數據庫中裝載該對象時同時裝載相關聯的那個對象,那麼這種操做方式就用得
上的了。若是爲相應類映射關係設置了 batch-size,那麼使用這種操做方式容許多個對象被一批
裝載(由於返回的是代理,無需從數據庫中抓取全部對象的數據)。
若是你不肯定是否有匹配的行存在,應該使用 get() 方法,它會馬上訪問數據庫,若是沒有對應
的記錄,會返回 null。
Cat cat = (Cat) sess.get(Cat.class, id);
if (cat==null) {
cat = new Cat();
sess.save(cat, id);
}
return cat;
你甚至能夠選用某個 LockMode,用 SQL 的 SELECT ... FOR UPDATE 裝載對象。 請查閱 API 文
檔以獲取更多信息。
Cat cat = (Cat) sess.get(Cat.class, id, LockMode.UPGRADE);
注意,任何關聯的對象或者包含的集合都不會被以 FOR UPDATE 方式返回, 除非你指定了 lock 或
者 all 做爲關聯(association)的級聯風格(cascade style)。
任什麼時候候均可以使用 refresh() 方法強迫裝載對象和它的集合。若是你使用數據庫觸發器功能來
處理對象的某些屬性,這個方法就頗有用了。
sess.save(cat);
sess.flush(); //force the SQL INSERT
sess.refresh(cat); //re-read the state (after the trigger executes)
第 11 章 與對象共事
212
How much does Hibernate load from the database and how many SQL SELECTs will it use?
This depends on the fetching strategy. This is explained in 第  21.1 節 「抓取策略
(Fetching strategies)」.
11.4. 查詢
若是不知道所要尋找的對象的持久化標識,那麼你須要使用查詢。Hibernate 支持強大且易於使
用的面向對象查詢語言(HQL)。若是但願經過編程的方式建立查詢,Hibernate 提供了完善的按
條件(Query By Criteria,QBC)以及按樣例(Query By Example,QBE)進行查詢的功能。你也
能夠用原生 SQL(native SQL)描述查詢,Hibernate 額外提供了將結果集(result set)轉化
爲對象的支持。
11.4.1. 執行查詢
HQL 和原生 SQL(native SQL)查詢要經過爲 org.hibernate.Query 的實例來表達。 這個接口提供
了參數綁定、結果集處理以及運行實際查詢的方法。你老是能夠經過當前 Session 獲取一個 Query
對象:
List cats = session.createQuery(
"from Cat as cat where cat.birthdate < ?")
.setDate(0, date)
.list();
List mothers = session.createQuery(
"select mother from Cat as cat join cat.mother as mother where cat.name = ?")
.setString(0, name)
.list();
List kittens = session.createQuery(
"from Cat as cat where cat.mother = ?")
.setEntity(0, pk)
.list();
Cat mother = (Cat) session.createQuery(
"select cat.mother from Cat as cat where cat = ?")
.setEntity(0, izi)
.uniqueResult();]]
Query mothersWithKittens = (Cat) session.createQuery(
"select mother from Cat as mother left join fetch mother.kittens");
Set uniqueMothers = new HashSet(mothersWithKittens.list());
一個查詢一般在調用 list() 時被執行,執行結果會徹底裝載進內存中的一個集合
(collection)。查詢返回的對象處於持久(persistent)狀態。若是你知道的查詢只會返回一
個對象,可以使用 list() 的快捷方式 uniqueResult()。注意,使用集合預先抓取的查詢每每會返回
屢次根對象(他們的集合類都被初始化了)。你能夠經過一個集合(Set)來過濾這些重複對象。
執行查詢
213
11.4.1.1. 迭代式獲取結果(Iterating results)
某些狀況下,你能夠使用 iterate() 方法獲得更好的性能。 這一般是你預期返回的結果在
session,或二級緩存(second-level cache)中已經存在時的狀況。如若否則,iterate() 會比
list() 慢,並且可能簡單查詢也須要進行屢次數據庫訪問:iterate() 會首先使用 1 條語句獲得
全部對象的持久化標識(identifiers),再根據持久化標識執行 n 條附加的 select 語句實例
化實際的對象。
// fetch ids
Iterator iter = sess.createQuery("from eg.Qux q order by q.likeliness").iterate();
while ( iter.hasNext() ) {
Qux qux = (Qux) iter.next(); // fetch the object
// something we couldnt express in the query
if ( qux.calculateComplicatedAlgorithm() ) {
// delete the current instance
iter.remove();
// dont need to process the rest
break;
}
}
11.4.1.2. 返回元組(tuples)的查詢
(譯註:元組(tuples)指一條結果行包含多個對象) Hibernate 查詢有時返回元組
(tuples),每一個元組(tuples)以數組的形式返回:
Iterator kittensAndMothers = sess.createQuery(
"select kitten, mother from Cat kitten join kitten.mother mother")
.list()
.iterator();
while ( kittensAndMothers.hasNext() ) {
Object[] tuple = (Object[]) kittensAndMothers.next();
Cat kitten = (Cat) tuple[0];
Cat mother = (Cat) tuple[1];
....
}
11.4.1.3. 標量(Scalar)結果
查詢可在 select 從句中指定類的屬性,甚至能夠調用 SQL 統計(aggregate)函數。屬性或統計
結果被認定爲"標量(Scalar)"的結果(而不是持久(persistent state)的實體)。
Iterator results = sess.createQuery(
"select cat.color, min(cat.birthdate), count(cat) from Cat cat " +
"group by cat.color")
.list()
.iterator();
第 11 章 與對象共事
214
while ( results.hasNext() ) {
Object[] row = (Object[]) results.next();
Color type = (Color) row[0];
Date oldest = (Date) row[1];
Integer count = (Integer) row[2];
.....
}
11.4.1.4. 綁定參數
接口 Query 提供了對命名參數(named parameters)、JDBC 風格的問號(?)參數進行綁定的方
法。不一樣於 JDBC,Hibernate 對參數從 0 開始計數。 命名參數(named parameters)在查詢字
符串中是形如 :name 的標識符。命名參數(named parameters)的優勢是:
•命名參數(named parameters)與其在查詢串中出現的順序無關
•它們可在同一查詢串中屢次出現
•它們自己是自我說明的
//named parameter (preferred)
Query q = sess.createQuery("from DomesticCat cat where cat.name = :name");
q.setString("name", "Fritz");
Iterator cats = q.iterate();
//positional parameter
Query q = sess.createQuery("from DomesticCat cat where cat.name = ?");
q.setString(0, "Izi");
Iterator cats = q.iterate();
//named parameter list
List names = new ArrayList();
names.add("Izi");
names.add("Fritz");
Query q = sess.createQuery("from DomesticCat cat where cat.name in (:namesList)");
q.setParameterList("namesList", names);
List cats = q.list();
11.4.1.5. 分頁
若是你須要指定結果集的範圍(但願返回的最大行數/或開始的行數),應該使用 Query 接口提供
的方法:
Query q = sess.createQuery("from DomesticCat cat");
q.setFirstResult(20);
q.setMaxResults(10);
執行查詢
215
List cats = q.list();
Hibernate 知道如何將這個有限定條件的查詢轉換成你的數據庫的原生 SQL(native SQL)。
11.4.1.6. 可滾動遍歷(Scrollable iteration)
若是你的 JDBC 驅動支持可滾動的 ResuleSet,Query 接口能夠使用 ScrollableResults,容許你在
查詢結果中靈活遊走。
Query q = sess.createQuery("select cat.name, cat from DomesticCat cat " +
"order by cat.name");
ScrollableResults cats = q.scroll();
if ( cats.first() ) {
// find the first name on each page of an alphabetical list of cats by name
firstNamesOfPages = new ArrayList();
do {
String name = cats.getString(0);
firstNamesOfPages.add(name);
}
while ( cats.scroll(PAGE_SIZE) );
// Now get the first page of cats
pageOfCats = new ArrayList();
cats.beforeFirst();
int i=0;
while( ( PAGE_SIZE > i++ ) && cats.next() ) pageOfCats.add( cats.get(1) );
}
cats.close()
請注意,使用此功能須要保持數據庫鏈接(以及遊標(cursor))處於一直打開狀態。若是你需
要斷開鏈接使用分頁功能,請使用 setMaxResult()/setFirstResult()。
11.4.1.7. 外置命名查詢(Externalizing named queries)
Queries can also be configured as so called named queries using annotations or Hibernate
mapping documents. @NamedQuery and @NamedQueries can be defined at the class level as seen
in 例 11.1 「Defining a named query using @NamedQuery」 . However their definitions are
global to the session factory/entity manager factory scope. A named query is defined
by its name and the actual query string.
例 11.1. Defining a named query using @NamedQuery
@Entity
@NamedQuery(name="night.moreRecentThan", query="select n from Night n where n.date >= :date")
public class Night {
...
}
第 11 章 與對象共事
216
public class MyDao {
doStuff() {
Query q = s.getNamedQuery("night.moreRecentThan");
q.setDate( "date", aMonthAgo );
List results = q.list();
...
}
...
}
Using a mapping document can be configured using the <query> node. Remember to use a
CDATA section if your query contains characters that could be interpreted as markup.
例 11.2. Defining a named query using <query>
<query name="ByNameAndMaximumWeight"><![CDATA[
from eg.DomesticCat as cat
where cat.name = ?
and cat.weight > ?
] ]></query>
Parameter binding and executing is done programatically as seen in 例 11.3 「Parameter
binding of a named query」.
例 11.3. Parameter binding of a named query
Query q = sess.getNamedQuery("ByNameAndMaximumWeight");
q.setString(0, name);
q.setInt(1, minWeight);
List cats = q.list();
請注意實際的程序代碼與所用的查詢語言無關,你也可在元數據中定義原生 SQL(native SQL)
查詢,或將原有的其餘的查詢語句放在配置文件中,這樣就可讓 Hibernate 統一管理,達到遷
移的目的。
也請注意在 <hibernate-mapping> 元素中聲明的查詢必須有一個全局惟一的名字,而
在 <class> 元素中聲明的查詢自動具備全局名,是經過類的全名加以限定的。好比
eg.Cat.ByNameAndMaximumWeight。
11.4.2. 過濾集合
集合過濾器(filter)是一種用於一個持久化集合或者數組的特殊的查詢。查詢字符串中能夠使
用 "this" 來引用集合中的當前元素。
Collection blackKittens = session.createFilter(
pk.getKittens(),
條件查詢(Criteria queries)
217
"where this.color = ?")
.setParameter( Color.BLACK, Hibernate.custom(ColorUserType.class) )
.list()
);
返回的集合能夠被認爲是一個包(bag,無順序可重複的集合(collection)),它是所給集合的
副本。 原來的集合不會被改動(這與「過濾器(filter)」的隱含的含義不符,不過與咱們期待
的行爲一致)。
請注意過濾器(filter)並不須要 from 子句(固然須要的話它們也能夠加上)。過濾器
(filter)不限定於只能返回集合元素自己。
Collection blackKittenMates = session.createFilter(
pk.getKittens(),
"select this.mate where this.color = eg.Color.BLACK.intValue")
.list();
即便無條件的過濾器(filter)也是有意義的。例如,用於加載一個大集合的子集:
Collection tenKittens = session.createFilter(
mother.getKittens(), "")
.setFirstResult(0).setMaxResults(10)
.list();
11.4.3. 條件查詢(Criteria queries)
HQL 極爲強大,可是有些人但願可以動態的使用一種面向對象 API 建立查詢,而非在他們的 Java
代碼中嵌入字符串。對於那部分人來講,Hibernate 提供了直觀的 Criteria 查詢 API。
Criteria crit = session.createCriteria(Cat.class);
crit.add( Restrictions.eq( "color", eg.Color.BLACK ) );
crit.setMaxResults(10);
List cats = crit.list();
The Criteria and the associated Example API are discussed in more detail in 第 17 章
條件查詢(Criteria Queries).
11.4.4. 使用原生 SQL 的查詢
你能夠使用 createSQLQuery() 方法,用 SQL 來描述查詢,並由 Hibernate 將結果集轉換成對象。
請注意,你能夠在任什麼時候候調用 session.connection() 來得到並使用 JDBC Connection 對象。 如
果你選擇使用 Hibernate 的 API,你必須把 SQL 別名用大括號包圍起來:
List cats = session.createSQLQuery("SELECT {cat.*} FROM CAT {cat} WHERE ROWNUM<10")
第 11 章 與對象共事
218
.addEntity("cat", Cat.class)
.list();
List cats = session.createSQLQuery(
"SELECT {cat}.ID AS {cat.id}, {cat}.SEX AS {cat.sex}, " +
"{cat}.MATE AS {cat.mate}, {cat}.SUBCLASS AS {cat.class}, ... " +
"FROM CAT {cat} WHERE ROWNUM<10")
.addEntity("cat", Cat.class)
.list()
SQL queries can contain named and positional parameters, just like Hibernate queries.
More information about native SQL queries in Hibernate can be found in 第 18 章 Native
SQL 查詢.
11.5. 修改持久對象
事務中的持久實例(就是經過 session 裝載、保存、建立或者查詢出的對象) 被應用程序操做所
形成的任何修改都會在 Session 被刷出(flushed)的時候被持久化(本章後面會詳細討論)。這
裏不須要調用某個特定的方法(好比 update(),設計它的目的是不一樣的)將你的修改持久化。所
以最直接的更新一個對象的方法就是在 Session 處於打開狀態時 load() 它,而後直接修改便可:
DomesticCat cat = (DomesticCat) sess.load( Cat.class, new Long(69) );
cat.setName("PK");
sess.flush(); // changes to cat are automatically detected and persisted
有時這種程序模型效率低下,由於它在同一 Session 裏須要一條 SQL SELECT 語句(用於加載對
象) 以及一條 SQL UPDATE 語句(持久化更新的狀態)。爲此 Hibernate 提供了另外一種途徑,使
用脫管(detached)實例。
11.6. 修改脫管(Detached)對象
不少程序須要在某個事務中獲取對象,而後將對象發送到界面層去操做,最後在一個新的事務保
存所作的修改。在高併發訪問的環境中使用這種方式,一般使用附帶版本信息的數據來保證這
些「長「工做單元之間的隔離。
Hibernate 經過提供 Session.update() 或 Session.merge() 從新關聯脫管實例的辦法來支持這種
模型。
// in the first session
Cat cat = (Cat) firstSession.load(Cat.class, catId);
Cat potentialMate = new Cat();
firstSession.save(potentialMate);
// in a higher layer of the application
cat.setMate(potentialMate);
自動狀態檢測
219
// later, in a new session
secondSession.update(cat); // update cat
secondSession.update(mate); // update mate
若是具備 catId 持久化標識的 Cat 以前已經被另外一Session(secondSession)裝載了, 應用程序進
行重關聯操做(reattach)的時候會拋出一個異常。
若是你肯定當前 session 沒有包含與之具備相同持久化標識的持久實例,使用 update()。若是
想隨時合併你的的改動而不考慮 session 的狀態,使用 merge()。換句話說,在一個新 session
中一般第一個調用的是 update() 方法,以便保證從新關聯脫管(detached)對象的操做首先被執
行。
The application should individually update() detached instances that are reachable from
the given detached instance only if it wants their state to be updated. This can
be automated using transitive persistence. See 第 11.11 節 「傳播性持久化(transitive
persistence)」 for more information.
lock() 方法也容許程序從新關聯某個對象到一個新 session 上。不過,該脫管(detached)的對
象必須是沒有修改過的。
//just reassociate:
sess.lock(fritz, LockMode.NONE);
//do a version check, then reassociate:
sess.lock(izi, LockMode.READ);
//do a version check, using SELECT ... FOR UPDATE, then reassociate:
sess.lock(pk, LockMode.UPGRADE);
請注意,lock() 能夠搭配多種 LockMode,更多信息請閱讀 API 文檔以及關於事務處理
(transaction handling)的章節。從新關聯不是 lock() 的惟一用途。
Other models for long units of work are discussed in 第  13.3  節 「樂觀併發控制
(Optimistic concurrency control)」.
11.7. 自動狀態檢測
Hibernate 的用戶曾要求一個既可自動分配新持久化標識(identifier)保存瞬時(transient)
對象,又可更新/從新關聯脫管(detached)實例的通用方法。saveOrUpdate() 方法實現了這個功
能。
// in the first session
Cat cat = (Cat) firstSession.load(Cat.class, catID);
// in a higher tier of the application
Cat mate = new Cat();
cat.setMate(mate);
// later, in a new session
secondSession.saveOrUpdate(cat); // update existing state (cat has a non-null id)
第 11 章 與對象共事
220
secondSession.saveOrUpdate(mate); // save the new instance (mate has a null id)
saveOrUpdate() 用途和語義可能會使新用戶感到迷惑。首先,只要你沒有嘗試在某個 session 中
使用來自另外一 session 的實例,你就應該不須要使用 update(), saveOrUpdate(),或 merge()。
有些程序歷來不用這些方法。
一般下面的場景會使用 update() 或 saveOrUpdate():
•程序在第一個 session 中加載對象
•該對象被傳遞到表現層
•對象發生了一些改動
•該對象被返回到業務邏輯層
•程序調用第二個 session 的 update() 方法持久這些改動
saveOrUpdate() 作下面的事:
•若是對象已經在本 session 中持久化了,不作任何事
•若是另外一個與本 session 關聯的對象擁有相同的持久化標識(identifier),拋出一個異常
•若是對象沒有持久化標識(identifier)屬性,對其調用 save()
•若是對象的持久標識(identifier)代表其是一個新實例化的對象,對其調用 save()。
•若是對象是附帶版本信息的(經過 <version> 或 <timestamp>)而且版本屬性的值代表其是一個
新實例化的對象,save() 它。
•不然 update() 這個對象
merge() 可很是不一樣:
•若是 session 中存在相同持久化標識(identifier)的實例,用用戶給出的對象的狀態覆蓋舊
有的持久實例
•若是 session 沒有相應的持久實例,則嘗試從數據庫中加載,或建立新的持久化實例
•最後返回該持久實例
•用戶給出的這個對象沒有被關聯到 session 上,它依舊是脫管的
11.8. 刪除持久對象
使用 Session.delete() 會把對象的狀態從數據庫中移除。固然,你的應用程序可能仍然持有
一個指向已刪除對象的引用。因此,最好這樣理解:delete() 的用途是把一個持久實例變成瞬時
(transient)實例。
sess.delete(cat);
你能夠用你喜歡的任何順序刪除對象,不用擔憂外鍵約束衝突。固然,若是你搞錯了順序,仍是
有可能引起在外鍵字段定義的 NOT NULL 約束衝突。例如你刪除了父對象,可是忘記刪除其子對
象。
在兩個不一樣數據庫間複製對象
221
11.9. 在兩個不一樣數據庫間複製對象
偶爾會用到不從新生成持久化標識(identifier),將持久實例以及其關聯的實例持久到不一樣的
數據庫中的操做。
//retrieve a cat from one database
Session session1 = factory1.openSession();
Transaction tx1 = session1.beginTransaction();
Cat cat = session1.get(Cat.class, catId);
tx1.commit();
session1.close();
//reconcile with a second database
Session session2 = factory2.openSession();
Transaction tx2 = session2.beginTransaction();
session2.replicate(cat, ReplicationMode.LATEST_VERSION);
tx2.commit();
session2.close();
ReplicationMode 決定在和數據庫中已存在記錄由衝突時,replicate() 如何處理。
•ReplicationMode.IGNORE:當某個現有數據庫記錄具備相同標識符時忽略它
•ReplicationMode.OVERWRITE:用相同的標識符覆蓋現有數據庫記錄
•ReplicationMode.EXCEPTION:當某個現有數據庫記錄具備相同標識符時拋出異常
•ReplicationMode.LATEST_VERSION:若是當前的版本較新,則覆蓋,不然忽略
這個功能的用途包括使錄入的數據在不一樣數據庫中一致,產品升級時升級系統配置信息,回滾
non-ACID 事務中的修改等等。(譯註,non-ACID,非
ACID;ACID,Atomic,Consistent,Isolated and Durable 的縮寫)
11.10. Session 刷出(flush)
每間隔一段時間,Session 會執行一些必需的 SQL 語句來把內存中的對象的狀態同步到 JDBC 連
接中。這個過程被稱爲刷出(flush),默認會在下面的時間點執行:
•在某些查詢執行以前
•在調用 org.hibernate.Transaction.commit() 的時候
•在調用 Session.flush() 的時候
涉及的 SQL 語句會按照下面的順序發出執行:
1. 全部對實體進行插入的語句,其順序按照對象執行 Session.save() 的時間順序
2. 全部對實體進行更新的語句
3. 全部進行集合刪除的語句
4. 全部對集合元素進行刪除,更新或者插入的語句
5. 全部進行集合插入的語句
第 11 章 與對象共事
222
6. 全部對實體進行刪除的語句,其順序按照對象執行 Session.delete() 的時間順序
有一個例外是,若是對象使用 native 方式來生成 ID(持久化標識)的話,它們一執行 save 就
會被插入。
除非你明確地發出了 flush() 指令,關於 Session 什麼時候會執行這些 JDBC 調用是徹底沒法保證
的,只能保證它們執行的先後順序。固然,Hibernate 保證,Query.list(..) 絕對不會返回已經失
效的數據,也不會返回錯誤數據。
It is possible to change the default behavior so that flush occurs less frequently.
The FlushMode class defines three different modes: only flush at commit time when the
Hibernate Transaction API is used, flush automatically using the explained routine,
or never flush unless flush() is called explicitly. The last mode is useful for long
running units of work, where a Session is kept open and disconnected for a long time
(see 第 13.3.2 節 「擴展週期的 session 和自動版本化」).
sess = sf.openSession();
Transaction tx = sess.beginTransaction();
sess.setFlushMode(FlushMode.COMMIT); // allow queries to return stale state
Cat izi = (Cat) sess.load(Cat.class, id);
izi.setName(iznizi);
// might return stale data
sess.find("from Cat as cat left outer join cat.kittens kitten");
// change to izi is not flushed!
...
tx.commit(); // flush occurs
sess.close();
During flush, an exception might occur (e.g. if a DML operation violates a constraint).
Since handling exceptions involves some understanding of Hibernate's transactional
behavior, we discuss it in 第 13 章 事務和併發 .
11.11. 傳播性持久化(transitive persistence)
對每個對象都要執行保存,刪除或重關聯操做讓人感受有點麻煩,尤爲是在處理許多彼此關聯
的對象的時候。一個常見的例子是父子關係。考慮下面的例子:
若是一個父子關係中的子對象是值類型(value typed)(例如,地址或字符串的集合)的,他們
的生命週期會依賴於父對象,能夠享受方便的級聯操做(Cascading),不須要額外的動做。父對
象被保存時,這些值類型(value typed)子對象也將被保存;父對象被刪除時,子對象也將被刪
除。這對將一個子對象從集合中移除是一樣有效:Hibernate 會檢測到,而且由於值類型(value
typed)的對象不可能被其餘對象引用,因此 Hibernate 會在數據庫中刪除這個子對象。
如今考慮一樣的場景,不過父子對象都是實體(entities)類型,而非值類型(value typed)
(例如,類別與個體,或母貓和小貓)。實體有本身的生命期,容許共享對其的引用(所以從集
閤中移除一個實體,不意味着它能夠被刪除),而且實體到其餘關聯實體之間默認沒有級聯操做
傳播性持久化(transitive persistence)
223
的設置。 Hibernate 默認不實現所謂的可到達即持久化(persistence by reachability)的策
略。
每一個 Hibernate session 的基本操做 — 包括 persist(), merge(), saveOrUpdate(), delete(),
lock(), refresh(), evict(), replicate() — 都有對應的級聯風格(cascade style)。這些級
聯風格(cascade style)風格分別命名爲 create, merge, save-update, delete, lock, refresh,
evict, replicate。若是你但願一個操做被順着關聯關係級聯傳播,你必須在映射文件中指出這一
點。例如:
<one-to-one name="person" cascade="persist"/>
級聯風格(cascade style)是可組合的:
<one-to-one name="person" cascade="persist,delete,lock"/>
你能夠使用 cascade="all" 來指定所有操做都順着關聯關係級聯(cascaded)。默認值是
cascade="none",即任何操做都不會被級聯(cascaded)。
In case you are using annotatons you probably have noticed the cascade attribute taking
an array of CascadeType as a value. The cascade concept in JPA is very is similar to
the transitive persistence and cascading of operations as described above, but with
slightly different semantics and cascading types:
•CascadeType.PERSIST: cascades the persist (create) operation to associated entities
persist() is called or if the entity is managed
•CascadeType.MERGE: cascades the merge operation to associated entities if merge() is
called or if the entity is managed
•CascadeType.REMOVE: cascades the remove operation to associated entities if delete()
is called
•CascadeType.REFRESH: cascades the refresh operation to associated entities if refresh()
is called
•CascadeType.DETACH: cascades the detach operation to associated entities if detach()
is called
•CascadeType.ALL: all of the above
注意
CascadeType.ALL also covers Hibernate specific operations like saveupdate,
lock etc...
第 11 章 與對象共事
224
A special cascade style, delete-orphan, applies only to one-to-many associations, and
indicates that the delete() operation should be applied to any child object that
is removed from the association. Using annotations there is no CascadeType.DELETEORPHAN
equivalent. Instead you can use the attribute orphanRemoval as seen in 例  11.4
「@OneToMany with orphanRemoval」. If an entity is removed from a @OneToMany collection
or an associated entity is dereferenced from a @OneToOne association, this associated
entity can be marked for deletion if orphanRemoval is set to true.
例 11.4. @OneToMany with orphanRemoval
@Entity
public class Customer {
private Set<Order> orders;
@OneToMany(cascade=CascadeType.ALL, orphanRemoval=true)
public Set<Order> getOrders() { return orders; }
public void setOrders(Set<Order> orders) { this.orders = orders; }
[...]
}
@Entity
public class Order { ... }
Customer customer = em.find(Customer.class, 1l);
Order order = em.find(Order.class, 1l);
customer.getOrders().remove(order); //order will be deleted by cascade
建議:
•It does not usually make sense to enable cascade on a many-to-one or many-to-many
association. In fact the @ManyToOne and @ManyToMany don't even offer a orphanRemoval
attribute. Cascading is often useful for one-to-one and one-to-many associations.
•If the child object's lifespan is bounded by the lifespan of the parent
object, make it a life cycle object by specifying cascade="all,deleteorphan"(@OneToMany(cascade=CascadeType.ALL,
orphanRemoval=true)).
•其餘狀況,你可根本不須要級聯(cascade)。可是若是你認爲你會常常在某個事務中同時用到
父對象與子對象,而且你但願少打點兒字,能夠考慮使用 cascade="persist,merge,saveupdate"。
能夠使用 cascade="all" 將一個關聯關係(不管是對值對象的關聯,或者對一個集合的關聯)標記
爲父/子關係的關聯。 這樣對父對象進行 save/update/delete 操做就會致使子對象也進行 save/
update/delete 操做。
Furthermore, a mere reference to a child from a persistent parent will result in
save/update of the child. This metaphor is incomplete, however. A child which becomes
unreferenced by its parent is not automatically deleted, except in the case of a one-to-
使用元數據
225
many association mapped with cascade="delete-orphan". The precise semantics of cascading
operations for a parent/child relationship are as follows:
•若是父對象被 persist(),那麼全部子對象也會被 persist()
•若是父對象被 merge(),那麼全部子對象也會被 merge()
•若是父對象被 save(),update() 或 saveOrUpdate(),那麼全部子對象則會被 saveOrUpdate()
•若是某個持久的父對象引用了瞬時(transient)或者脫管(detached)的子對象,那麼子對象
將會被 saveOrUpdate()
•若是父對象被刪除,那麼全部子對象也會被 delete()
•除非被標記爲 cascade="delete-orphan"(刪除「孤兒」模式,此時不被任何一個父對象引用的
子對象會被刪除),不然子對象失掉父對象對其的引用時,什麼事也不會發生。若是有特殊需
要,應用程序可經過顯式調用 delete() 刪除子對象。
最後,注意操做的級聯多是在調用期(call time)或者寫入期(flush time)做用到對象圖上
的。全部的操做,若是容許,都在操做被執行的時候級聯到可觸及的關聯實體上。然而,saveupate
和 delete-orphan 是在Session flush 的時候才做用到全部可觸及的被關聯對象上的。
11.12. 使用元數據
Hibernate 中有一個很是豐富的元級別(meta-level)的模型,含有全部的實體和值類型數據的
元數據。 有時這個模型對應用程序自己也會很是有用。好比說,應用程序可能在實現一種「智
能」的深度拷貝算法時,經過使用 Hibernate 的元數據來了解哪些對象應該被拷貝(好比,可變
的值類型數據),那些不該該(不可變的值類型數據,也許還有某些被關聯的實體)。
Hibernate 提供了 ClassMetadata 接口,CollectionMetadata 接口和 Type 層次體系來訪問元數據。
能夠經過 SessionFactory 獲取元數據接口的實例。
Cat fritz = ......;
ClassMetadata catMeta = sessionfactory.getClassMetadata(Cat.class);
Object[] propertyValues = catMeta.getPropertyValues(fritz);
String[] propertyNames = catMeta.getPropertyNames();
Type[] propertyTypes = catMeta.getPropertyTypes();
// get a Map of all properties which are not collections or associations
Map namedValues = new HashMap();
for ( int i=0; i<propertyNames.length; i++ ) {
if ( !propertyTypes[i].isEntityType() && !propertyTypes[i].isCollectionType() ) {
namedValues.put( propertyNames[i], propertyValues[i] );
}
}
226
第 12
227
Read-only entities
重要
Hibernate's treatment of read-only entities may differ from what you may
have encountered elsewhere. Incorrect usage may cause unexpected results.
When an entity is read-only:
•Hibernate does not dirty-check the entity's simple properties or single-ended
associations;
•Hibernate will not update simple properties or updatable single-ended associations;
•Hibernate will not update the version of the read-only entity if only simple properties
or single-ended updatable associations are changed;
In some ways, Hibernate treats read-only entities the same as entities that are not
read-only:
•Hibernate cascades operations to associations as defined in the entity mapping.
•Hibernate updates the version if the entity has a collection with changes that dirties
the entity;
•A read-only entity can be deleted.
Even if an entity is not read-only, its collection association can be affected if it
contains a read-only entity.
For details about the affect of read-only entities on different property and association
types, see 第 12.2 節 「Read-only affect on property type」.
For details about how to make entities read-only, see 第 12.1 節 「Making persistent
entities read-only」
Hibernate does some optimizing for read-only entities:
•It saves execution time by not dirty-checking simple properties or single-ended
associations.
•It saves memory by deleting database snapshots.
第 12 章 Read-only entities
228
12.1. Making persistent entities read-only
Only persistent entities can be made read-only. Transient and detached entities must
be put in persistent state before they can be made read-only.
Hibernate provides the following ways to make persistent entities read-only:
•you can map an entity class as immutable; when an entity of an immutable class is made
persistent, Hibernate automatically makes it read-only. see 第 12.1.1 節 「Entities
of immutable classes」 for details
•you can change a default so that entities loaded into the session by Hibernate are
automatically made read-only; see 第 12.1.2 節 「Loading persistent entities as readonly」
for details
•you can make an HQL query or criteria read-only so that entities loaded when the query
or criteria executes, scrolls, or iterates, are automatically made read-only; see
第 12.1.3 節 「Loading read-only entities from an HQL query/criteria」 for details
•you can make a persistent entity that is already in the in the session read-only;
see 第 12.1.4 節 「Making a persistent entity read-only」 for details
12.1.1. Entities of immutable classes
When an entity instance of an immutable class is made persistent, Hibernate automatically
makes it read-only.
An entity of an immutable class can created and deleted the same as an entity of a
mutable class.
Hibernate treats a persistent entity of an immutable class the same way as a read-only
persistent entity of a mutable class. The only exception is that Hibernate will not
allow an entity of an immutable class to be changed so it is not read-only.
12.1.2. Loading persistent entities as read-only
注意
Entities of immutable classes are automatically loaded as read-only.
To change the default behavior so Hibernate loads entity instances of mutable classes
into the session and automatically makes them read-only, call:
Session.setDefaultReadOnly( true );
Loading read-only entities from an HQL query/criteria
229
To change the default back so entities loaded by Hibernate are not made read-only, call:
Session.setDefaultReadOnly( false );
You can determine the current setting by calling:
Session.isDefaultReadOnly();
If Session.isDefaultReadOnly() returns true, entities loaded by the following are
automatically made read-only:
•Session.load()
•Session.get()
•Session.merge()
•executing, scrolling, or iterating HQL queries and criteria; to override this setting
for a particular HQL query or criteria see 第 12.1.3 節 「Loading read-only entities
from an HQL query/criteria」
Changing this default has no effect on:
•persistent entities already in the session when the default was changed
•persistent entities that are refreshed via Session.refresh(); a refreshed persistent
entity will only be read-only if it was read-only before refreshing
•persistent entities added by the application via Session.persist(), Session.save(),
and Session.update() Session.saveOrUpdate()
12.1.3. Loading read-only entities from an HQL query/
criteria
注意
Entities of immutable classes are automatically loaded as read-only.
If Session.isDefaultReadOnly() returns false (the default) when an HQL query or criteria
executes, then entities and proxies of mutable classes loaded by the query will not
be read-only.
第 12 章 Read-only entities
230
You can override this behavior so that entities and proxies loaded by an HQL query or
criteria are automatically made read-only.
For an HQL query, call:
Query.setReadOnly( true );
Query.setReadOnly( true ) must be called before Query.list(), Query.uniqueResult(),
Query.scroll(), or Query.iterate()
For an HQL criteria, call:
Criteria.setReadOnly( true );
Criteria.setReadOnly( true ) must be called before Criteria.list(), Criteria.uniqueResult(),
or Criteria.scroll()
Entities and proxies that exist in the session before being returned by an HQL query
or criteria are not affected.
Uninitialized persistent collections returned by the query are not affected. Later,
when the collection is initialized, entities loaded into the session will be read-only
if Session.isDefaultReadOnly() returns true.
Using Query.setReadOnly( true ) or Criteria.setReadOnly( true ) works well when a single HQL
query or criteria loads all the entities and intializes all the proxies and collections
that the application needs to be read-only.
When it is not possible to load and initialize all necessary entities in a single
query or criteria, you can temporarily change the session default to load entities as
read-only before the query is executed. Then you can explicitly initialize proxies and
collections before restoring the session default.
Session session = factory.openSession();
Transaction tx = session.beginTransaction();
setDefaultReadOnly( true );
Contract contract =
( Contract ) session.createQuery(
"from Contract where customerName = 'Sherman'" )
.uniqueResult();
Hibernate.initialize( contract.getPlan() );
Hibernate.initialize( contract.getVariations() );
Hibernate.initialize( contract.getNotes() );
setDefaultReadOnly( false );
...
tx.commit();
Making a persistent entity read-only
231
session.close();
If Session.isDefaultReadOnly() returns true, then you can use Query.setReadOnly( false )
and Criteria.setReadOnly( false ) to override this session setting and load entities
that are not read-only.
12.1.4. Making a persistent entity read-only
注意
Persistent entities of immutable classes are automatically made read-only.
To make a persistent entity or proxy read-only, call:
Session.setReadOnly(entityOrProxy, true)
To change a read-only entity or proxy of a mutable class so it is no longer readonly,
call:
Session.setReadOnly(entityOrProxy, false)
重要
When a read-only entity or proxy is changed so it is no longer readonly,
Hibernate assumes that the current state of the read-only entity
is consistent with its database representation. If this is not true, then
any non-flushed changes made before or while the entity was read-only,
will be ignored.
To throw away non-flushed changes and make the persistent entity consistent with its
database representation, call:
session.refresh( entity );
To flush changes made before or while the entity was read-only and make the database
representation consistent with the current state of the persistent entity:
// evict the read-only entity so it is detached
session.evict( entity );
第 12 章 Read-only entities
232
// make the detached entity (with the non-flushed changes) persistent
session.update( entity );
// now entity is no longer read-only and its changes can be flushed
s.flush();
12.2. Read-only affect on property type
The following table summarizes how different property types are affected by making
an entity read-only.
表 12.1. Affect of read-only entity on property types
Property/Association Type Changes flushed to DB?
Simple
(第 12.2.1 節 「Simple properties」)
no*
Unidirectional one-to-one
Unidirectional many-to-one
(第  12.2.2.1 節 「Unidirectional one-toone
and many-to-one」)
no*
no*
Unidirectional one-to-many
Unidirectional many-to-many
(第  12.2.2.2 節 「Unidirectional one-tomany
and many-to-many」)
yes
yes
Bidirectional one-to-one
(第  12.2.3.1  節 「Bidirectional one-toone」)
only if the owning entity is not read-only*
Bidirectional one-to-many/many-to-one
inverse collection
non-inverse collection
(第  12.2.3.2  節 「Bidirectional one-tomany/many-to-one」)
only added/removed entities that are not
read-only*
yes
Bidirectional many-to-many
(第  12.2.3.3 節 「Bidirectional many-tomany」)
yes
Simple properties
233
* Behavior is different when the entity having the property/association is read-only,
compared to when it is not read-only.
12.2.1. Simple properties
When a persistent object is read-only, Hibernate does not dirty-check simple properties.
Hibernate will not synchronize simple property state changes to the database. If you
have automatic versioning, Hibernate will not increment the version if any simple
properties change.
Session session = factory.openSession();
Transaction tx = session.beginTransaction();
// get a contract and make it read-only
Contract contract = ( Contract ) session.get( Contract.class, contractId );
session.setReadOnly( contract, true );
// contract.getCustomerName() is "Sherman"
contract.setCustomerName( "Yogi" );
tx.commit();
tx = session.beginTransaction();
contract = ( Contract ) session.get( Contract.class, contractId );
// contract.getCustomerName() is still "Sherman"
...
tx.commit();
session.close();
12.2.2. Unidirectional associations
12.2.2.1. Unidirectional one-to-one and many-to-one
Hibernate treats unidirectional one-to-one and many-to-one associations in the same
way when the owning entity is read-only.
We use the term unidirectional single-ended association when referring to functionality
that is common to unidirectional one-to-one and many-to-one associations.
Hibernate does not dirty-check unidirectional single-ended associations when the owning
entity is read-only.
If you change a read-only entity's reference to a unidirectional single-ended
association to null, or to refer to a different entity, that change will not be flushed
to the database.
第 12 章 Read-only entities
234
注意
If an entity is of an immutable class, then its references to
unidirectional single-ended associations must be assigned when that entity
is first created. Because the entity is automatically made read-only,
these references can not be updated.
If automatic versioning is used, Hibernate will not increment the version due to local
changes to unidirectional single-ended associations.
In the following examples, Contract has a unidirectional many-to-one association with
Plan. Contract cascades save and update operations to the association.
The following shows that changing a read-only entity's many-to-one association reference
to null has no effect on the entity's database representation.
// get a contract with an existing plan;
// make the contract read-only and set its plan to null
tx = session.beginTransaction();
Contract contract = ( Contract ) session.get( Contract.class, contractId );
session.setReadOnly( contract, true );
contract.setPlan( null );
tx.commit();
// get the same contract
tx = session.beginTransaction();
contract = ( Contract ) session.get( Contract.class, contractId );
// contract.getPlan() still refers to the original plan;
tx.commit();
session.close();
The following shows that, even though an update to a read-only entity's many-to-one
association has no affect on the entity's database representation, flush still cascades
the save-update operation to the locally changed association.
// get a contract with an existing plan;
// make the contract read-only and change to a new plan
tx = session.beginTransaction();
Contract contract = ( Contract ) session.get( Contract.class, contractId );
session.setReadOnly( contract, true );
Plan newPlan = new Plan( "new plan"
contract.setPlan( newPlan);
tx.commit();
// get the same contract
tx = session.beginTransaction();
contract = ( Contract ) session.get( Contract.class, contractId );
Bidirectional associations
235
newPlan = ( Contract ) session.get( Plan.class, newPlan.getId() );
// contract.getPlan() still refers to the original plan;
// newPlan is non-null because it was persisted when
// the previous transaction was committed;
tx.commit();
session.close();
12.2.2.2. Unidirectional one-to-many and many-to-many
Hibernate treats unidirectional one-to-many and many-to-many associations owned by a
read-only entity the same as when owned by an entity that is not read-only.
Hibernate dirty-checks unidirectional one-to-many and many-to-many associations;
The collection can contain entities that are read-only, as well as entities that are
not read-only.
Entities can be added and removed from the collection; changes are flushed to the
database.
If automatic versioning is used, Hibernate will update the version due to changes in
the collection if they dirty the owning entity.
12.2.3. Bidirectional associations
12.2.3.1. Bidirectional one-to-one
If a read-only entity owns a bidirectional one-to-one association:
•Hibernate does not dirty-check the association.
•updates that change the association reference to null or to refer to a different
entity will not be flushed to the database.
•If automatic versioning is used, Hibernate will not increment the version due to
local changes to the association.
注意
If an entity is of an immutable class, and it owns a bidirectional one-toone
association, then its reference must be assigned when that entity is
first created. Because the entity is automatically made read-only, these
references cannot be updated.
When the owner is not read-only, Hibernate treats an association with a read-only
entity the same as when the association is with an entity that is not read-only.
第 12 章 Read-only entities
236
12.2.3.2. Bidirectional one-to-many/many-to-one
A read-only entity has no impact on a bidirectional one-to-many/many-to-one association
if:
•the read-only entity is on the one-to-many side using an inverse collection;
•the read-only entity is on the one-to-many side using a non-inverse collection;
•the one-to-many side uses a non-inverse collection that contains the read-only entity
When the one-to-many side uses an inverse collection:
•a read-only entity can only be added to the collection when it is created;
•a read-only entity can only be removed from the collection by an orphan delete or
by explicitly deleting the entity.
12.2.3.3. Bidirectional many-to-many
Hibernate treats bidirectional many-to-many associations owned by a read-only entity
the same as when owned by an entity that is not read-only.
Hibernate dirty-checks bidirectional many-to-many associations.
The collection on either side of the association can contain entities that are readonly,
as well as entities that are not read-only.
Entities are added and removed from both sides of the collection; changes are flushed
to the database.
If automatic versioning is used, Hibernate will update the version due to changes in
both sides of the collection if they dirty the entity owning the respective collections.
第 13
237
事務和併發
Hibernate 的事務和併發控制很容易掌握。Hibernate 直接使用 JDBC 鏈接和 JTA 資源,不添加
任何附加鎖定行爲。咱們強烈推薦你花點時間瞭解 JDBC 編程,ANSI SQL 查詢語言和你使用的數
據庫系統的事務隔離規範。
Hibernate 不鎖定內存中的對象。你的應用程序會按照你的數據庫事務的隔離級別規定的那樣運
做。幸好有了 Session,使得 Hibernate 經過標識符查找,和實體查詢(不是返回標量值的報
表查詢)提供了可重複的讀取(Repeatable reads)功能,Session 同時也是事務範圍內的緩存
(cache)。
除了對自動樂觀併發控制提供版本管理,針對行級悲觀鎖定,Hibernate 也提供了輔助的(較小
的)API,它使用了 SELECT FOR UPDATE 的 SQL 語法。本章後面會討論樂觀併發控制和這個API。
咱們從 Configuration層、SessionFactory 層,和 Session 層開始討論 Hibernate 的並行控制、
數據庫事務和應用程序的長事務。
13.1. Session 和事務範圍(transaction scope)
SessionFactory 對象的建立代價很昂貴,它是線程安全的對象,它爲全部的應用程序線程所共
享。它只建立一次,一般是在應用程序啓動的時候,由一個 Configuraion 的實例來建立。
Session 對象的建立代價比較小,是非線程安全的,對於單個請求,單個會話、單個的 工做單元
而言,它只被使用一次,而後就丟棄。只有在須要的時候,一個 Session 對象 纔會獲取一個 JDBC
的 Connection(或一個Datasource)對象,所以倘若不使用的時候它不消費任何資源。
此外咱們還要考慮數據庫事務。數據庫事務應該儘量的短,下降數據庫中的鎖爭用。數據庫長
事務會阻止你的應用程序擴展到高的併發負載。所以,倘若在用戶思考期間讓數據庫事務開着,
直到整個工做單元完成才關閉這個事務,這毫不是一個好的設計。
一個操做單元(Unit of work)的範圍是多大?單個的 Hibernate Session 能跨越多個數據庫事
務嗎?仍是一個 Session 的做用範圍對應一個數據庫事務的範圍?應該什麼時候打開 Session,什麼時候關
閉 Session,你又如何劃分數據庫事務的邊界呢?咱們將在後續章節解決這些問題。
13.1.1. 操做單元(Unit of work)
First, let's define a unit of work. A unit of work is a design pattern described by
Martin Fowler as 「 [maintaining] a list of objects affected by a business transaction
and coordinates the writing out of changes and the resolution of concurrency problems.
」[PoEAA] In other words, its a series of operations we wish to carry out against
the database together. Basically, it is a transaction, though fulfilling a unit of
work will often span multiple physical database transactions (see 第 13.1.2 節 「長
對話」). So really we are talking about a more abstract notion of a transaction. The
term "business transaction" is also sometimes used in lieu of unit of work.
首先,別用 session-per-operation 這種反模式了,也就是說,在單個線程中, 不要由於一次
簡單的數據庫調用,就打開和關閉一次 Session!數據庫事務也是如此。 應用程序中的數據庫調
第 13 章 事務和併發
238
用是按照計劃好的次序,分組爲原子的操做單元。(注意,這也意味着,應用程 序中,在單個的
SQL 語句發送以後,自動事務提交(auto-commit)模式失效了。這種模式專門爲SQL控制檯操做
設計的。 Hibernate 禁止當即自動事務提交模式,或者指望應用服務器禁止當即自動事務提交模
式。)數據庫事務毫不是無關緊要的,任何與數據庫之間的通信都必須在某個事務中進行,無論
你是在讀仍是在寫數據。對讀數據而言,應該避免 auto-commit 行爲,由於不少小的事務比一個
清晰定義的工做單元性能差。後者也更容易維護和擴展。
在多用戶的 client/server 應用程序中,最經常使用的模式是 每一個請求一個會話(session-perrequest)。
在這種模式下,來自客戶端的請求被髮送到服務器端(即 Hibernate 持久化層運行
的地方),一個新的 Hibernate Session 被打開,而且執行這個操做單元中全部的數據庫操做。
一旦操做完成(同時對客戶端的響應也準備就緒),session 被同步,而後關閉。你也能夠使用
單 個數據庫事務來處理客戶端請求,在你打開 Session 以後啓動事務,在你關閉 Session 以前提
交事務。會話和請求之間的關係是一對一的關係,這種模式對 於大多數應用程序來講是很棒的。
實現纔是真正的挑戰。Hibernate 內置了對"當前 session(current session)" 的管理,用於簡
化此模式。你要作的一切就是在服務器端要處理請求的時候,開啓事務,在響應發送給客戶以前
結束事務。你能夠用任何方式來完成這一操做,一般的方案有 ServletFilter,在 service 方法中
進行 pointcut 的 AOP 攔截器,或者 proxy/interception 容器。EJB 容器是實現橫切諸如 EJB
session bean 上的事務分界,用 CMT 對事務進行聲明等方面的標準手段。倘若你決定使用編程
式的事務分界,請參考本章後面講到的 Hibernate Transaction API,這對易用性和代碼可移植性
都有好處。
Your application code can access a "current session" to process the request by calling
sessionFactory.getCurrentSession(). You will always get a Session scoped to the current
database transaction. This has to be configured for either resource-local or JTA
environments, see 第 2.3 節 「上下文相關的會話(Contextual Session)」.
有時,將 Session 和數據庫事務的邊界延伸到"展現層被渲染後"會帶來便利。有些
serlvet 應用程序在對請求進行處理後,有個單獨的渲染期,這種延伸對這種程序特別有用。假
若你實現你本身的攔截器,把事務邊界延伸到展現層渲染結束後很是容易。然而,倘若你依賴有
容器管理事務的 EJB,這就不太容易了,由於事務會在 EJB 方法返回後結束,而那是在任何展現
層渲染開始以前。請訪問 Hibernate 網站和論壇,你能夠找到 Open Session in View 這一模
式的提示和示例。
13.1.2. 長對話
session-per-request 模式不只僅是一個能夠用來設計操做單元的有用概念。不少業務處理都需
要一系列完整的與用戶之間的交互,而這些用戶是指對數據庫有交叉訪問的用戶。在基於 web 的
應用和企業應用中,跨用戶交互的數據庫事務是沒法接受的。考慮下面的例子:
•在界面的第一屏,打開對話框,用戶所看到的數據是被一個特定的 Session 和數據 庫事務載入
(load)的。用戶能夠隨意修改對話框中的數據對象。
•5 分鐘後,用戶點擊「保存」,指望所作出的修改被持久化;同時他也指望本身是惟一修改這
個信息的人,不會出現修改衝突。
從用戶的角度來看,咱們把這個操做單元稱爲長時間運行的對話(conversation),或者應用事
務(application transaction)。在你的應用程序中,能夠有不少種方法來實現它。
關注對象標識(Considering object identity)
239
頭一個幼稚的作法是,在用戶思考的過程當中,保持 Session 和數據庫事務是打開的,保持數據庫
鎖定,以阻止併發修改,從而保證數據庫事務隔離級別和原子操做。這種方式固然是一個反模
式,由於鎖爭用會致使應用程序沒法擴展併發用戶的數目。
很明顯,咱們必須使用多個數據庫事務來實現這個對話。在這個例子中,維護業務處理的 事務隔
離變成了應用程序層的部分責任。一個對話一般跨越多個數據庫事務。若是僅僅只有一個數據庫
事務(最後的那個事務)保存更新過的數據,而全部其餘事務只是單純的讀取數據(例如在一個
跨越多個請求/響應週期的嚮導風格的對話框中),那麼應用程序事務將保證其原子性。這種方式
比聽起來還要容易實現,特別是當你使用了 Hibernate 的下述特性的時候:
•自動版本化:Hibernate 可以自動進行樂觀併發控制,若是在用戶思考的過程當中發生併發修
改,Hibernate 可以自動檢測到。通常咱們只在對話結束時才檢查。
•脫管對象(Detached Objects):若是你決定採用前面已經討論過的 session-per-request 模
式,全部載入的實例在用戶思考的過程當中都處於與 Session 脫離的狀態。Hibernate 容許你
把與 Session 脫離的對象從新關聯到 Session 上,而且對修改進行持久化,這種模式被稱爲
session-per-request-with-detached-objects。自動版本化被用來隔離併發修改。
•Extended (or Long) Session:Hibernate 的 Session 能夠在數據庫事務提交以後和底層的
JDBC 鏈接斷開,當一個新的客戶端請求到來的時候,它又從新鏈接上底層的 JDBC 鏈接。這種
模式被稱之爲session-per-conversation,這種狀況可 能會形成沒必要要的 Session 和 JDBC 連
接的從新關聯。自動版本化被用來隔離併發修改,Session 一般不容許自動 flush,而是顯性地
flush。
session-per-request-with-detached-objects 和 session-per-conversation 各有優缺點,我
們在本章後面樂觀併發控制那部分再進行討論。
13.1.3. 關注對象標識(Considering object identity)
應用程序可能在兩個不一樣的 Session 中併發訪問同一持久化狀態,可是,一個持久化類的實例無
法在兩個 Session 中共享。所以有兩種不一樣的標識語義:
數據庫標識
foo.getId().equals( bar.getId() )
JVM 標識
foo==bar
對於那些關聯到 特定 Session(也就是在單個 Session 的範圍內)上的對象來講,這兩種標識的
語義是等價的,與數據庫標識對應的 JVM 標識是由 Hibernate 來保證的。不過,當應用程序在
兩個不一樣的 session 中併發訪問具備同一持久化標識的業務對象實例的時候,這個業務對象的兩
個實例事實上是不相同的(從 JVM 識別來看)。這種衝突能夠經過在同步和提交的時候使用自動
版本化和樂觀鎖定方法來解決。
這種方式把關於併發的頭疼問題留給了 Hibernate 和數據庫;因爲在單個線程內,操做單元中的
對象識別不 須要代價昂貴的鎖定或其餘意義上的同步,所以它同時能夠提供最好的可伸縮性。只
要在單個線程只持有一個 Session,應用程序就不須要同步任何業務對象。在 Session 的範圍內,
應用程序能夠放心的使用 == 進行對象比較。
第 13 章 事務和併發
240
不過,應用程序在 Session 的外面使用 == 進行對象比較可能會 致使沒法預
期的結果。在一些沒法預料的場合,例如,若是你把兩個脫管對象實例放進同一個 Set 的
時候,就可能發生。這兩個對象實例可能有同一個數據庫標識(也就是說, 他們表明了表的同
一行數據),從 JVM 標識的定義上來講,對脫管的對象而言,Hibernate 沒法保證
他們 的的 JVM 標識一致。開發人員必須覆蓋持久化類的 equals() 方法和
hashCode() 方法,從而實現自定義的對象相等語義。警告:不要使用數據庫標識來實現對象相
等,應該使用業務鍵值,由惟一的,一般不變的屬性組成。當一個瞬時對象被持久化的時候,它
的數據庫標識會發生改變。若是一個瞬時對象(一般也包括脫管對象實例)被放入一個 Set,改
變它的 hashcode 會致使與這個 Set 的關係中斷。雖 然業務鍵值的屬性不象數據庫主鍵那樣穩定
不變,可是你只須要保證在同一個 Set 中的對象屬性的穩定性就足夠了。請到 Hibernate 網站去
尋求這個問題更多的詳細的討論。請注意,這不是一個有關 Hibernate 的問題,而僅僅是一個關
於 Java 對象標識和判等行爲如何實現的問題。
13.1.4. 常見問題
決不要使用反模式 session-per-user-session 或者 session-per-application(固然,這個規
定幾乎沒有例外)。請注意,下述一些問題可能也會出如今咱們推薦的模式中,在你做出某個設
計決定以前,請務必理解該模式的應用前提。
•Session 對象是非線程安全的。若是一個 Session 實例容許共享的話,那些支持併發運行的東
東,例如 HTTP request,session beans 或者是 Swing workers,將會致使出現資源爭用(race
condition)。若是在 HttpSession 中有 Hibernate 的 Session 的話(稍後討論),你應該考
慮同步訪問你的 Http session。 不然,只要用戶足夠快的點擊瀏覽器的「刷新」,就會致使
兩個併發運行線程使用同一個 Session。
•一個由 Hibernate 拋出的異常意味着你必須當即回滾數據庫事務,並當即關閉 Session(稍後
會展開討論)。若是你的 Session 綁定到一個應用程序上,你必須中止該應用程序。回滾數據
庫事務並不會把你的業務對象退回到事務啓動時候的狀態。這意味着數據庫狀態和業務對象狀
態不一樣步。一般狀況下,這不是什麼問題,由於異常是不可恢復的,你必須在回滾以後從新開
始執行。
•The Session caches every object that is in a persistent state (watched and checked
for dirty state by Hibernate). If you keep it open for a long time or simply load too
much data, it will grow endlessly until you get an OutOfMemoryException. One solution
is to call clear() and evict() to manage the Session cache, but you should consider
a Stored Procedure if you need mass data operations. Some solutions are shown in
第 15 章 批量處理(Batch processing). Keeping a Session open for the duration of a
user session also means a higher probability of stale data.
13.2. 數據庫事務聲明
數據庫(或者系統)事務的聲明老是必須的。在數據庫事務以外,就沒法和數據庫通信(這可能
會讓那些習慣於自動提交事務模式的開發人員感到迷惑)。永遠使用清晰的事務聲明,即便只讀
操做也是如此。進行 顯式的事務聲明並不老是須要的,這取決於你的事務隔離級別和數據庫的能
力,但無論怎麼說,聲明事務總歸有益無害。固然,一個單獨的數據庫事務老是比不少瑣碎的事
務性能更好,即時對讀數據而言也是同樣。
非託管環境
241
一個 Hibernate 應用程序能夠運行在非託管環境中(也就是獨立運行的應用程序,簡單 Web 應
用程序,或者Swing圖形桌面應用程序),也能夠運行在託管的 J2EE 環境中。在一個非託管環境
中,Hibernate 一般本身負責管理數據庫鏈接池。應用程序開發人員必須手工設置事務聲明,換
句話說,就是手工啓 動,提交,或者回滾數據庫事務。一個託管的環境一般提供了容器管理事務
(CMT),例如事務裝配經過可聲明的方式定義在 EJB session beans 的部署描述符中。可編程
式事務聲明再也不須要,即便是 Session 的同步也能夠自動完成。
讓持久層具有可移植性是人們的理想,這種移植髮生在非託管的本地資源環境,與依賴 JTA 可是
使用 BMT 而非 CMT 的系統之間。在兩種狀況下你均可以使用編程式的事務管理。Hibernate
提供了一套稱爲 Transaction 的封裝 API, 用來把你的部署環境中的本地事務管理系統轉換到
Hibernate 事務上。這個 API 是可選的,可是咱們強烈推薦你使用,除非你用 CMT session
bean。
一般狀況下,結束 Session 包含了四個不一樣的階段:
•同步 session(flush,刷出到磁盤)
•提交事務
•關閉 session
•處理異常
session 的同步(flush,刷出)前面已經討論過了,咱們如今進一步考察在託管和非託管環境下
的事務聲明和異常處理。
13.2.1. 非託管環境
若是 Hibernat 持久層運行在一個非託管環境中,數據庫鏈接一般由 Hibernate 的簡單(即非
DataSource)鏈接池機制 來處理。session/transaction 處理方式以下所示:
// Non-managed environment idiom
Session sess = factory.openSession();
Transaction tx = null;
try {
tx = sess.beginTransaction();
// do some work
...
tx.commit();
}
catch (RuntimeException e) {
if (tx != null) tx.rollback();
throw e; // or display error message
}
finally {
sess.close();
}
你不須要顯式 flush() Session — 對 commit() 的調用會自動觸發 session 的同步(取決於
session 的 第  11.10  節 「Session 刷出(flush)」)。調用 close() 標誌 session 的結
第 13 章 事務和併發
242
束。close() 方法重要的暗示是,session 釋放了 JDBC 鏈接。這段 Java 代碼在非託管環境下和
JTA 環境下均可以運行。
更加靈活的方案是 Hibernate 內置的 "current session" 上下文管理,前文已經講過:
// Non-managed environment idiom with getCurrentSession()
try {
factory.getCurrentSession().beginTransaction();
// do some work
...
factory.getCurrentSession().getTransaction().commit();
}
catch (RuntimeException e) {
factory.getCurrentSession().getTransaction().rollback();
throw e; // or display error message
}
你極可能從未在一個一般的應用程序的業務代碼中見過這樣的代碼片段:致命的(系統)異常應
該老是 在應用程序「頂層」被捕獲。換句話說,執行 Hibernate 調用的代碼(在持久層)和處
理 RuntimeException 異常的代碼(一般只能清理和退出應用程序)應該在不一樣 的應用程序邏輯
層。Hibernate 的當前上下文管理能夠極大地簡化這一設計,你全部的一切就是 SessionFactory。
異常處理將在本章稍後進行討論。
請注意,你應該選擇 org.hibernate.transaction.JDBCTransactionFactory (這是默認選項),對第
二個例子來講,hibernate.current_session_context_class應該是 "thread"。
13.2.2. 使用 JTA
若是你的持久層運行在一個應用服務器中(例如,在 EJB session beans 的後面),Hibernate
獲取的每一個數據源鏈接將自動成爲全局 JTA 事務的一部分。你能夠安裝一個獨立的 JTA 實現,
使用它而不使用 EJB。Hibernate 提供了兩種策略進行 JTA 集成。
若是你使用 bean 管理事務(BMT),能夠經過使用 Hibernate 的 Transaction API 來告訴應用
服務器啓動和結束 BMT 事務。所以,事務管理代碼和在非託管環境下是同樣的。
// BMT idiom
Session sess = factory.openSession();
Transaction tx = null;
try {
tx = sess.beginTransaction();
// do some work
...
tx.commit();
}
catch (RuntimeException e) {
if (tx != null) tx.rollback();
使用 JTA
243
throw e; // or display error message
}
finally {
sess.close();
}
若是你但願使用與事務綁定的 Session,也就是使用 getCurrentSession() 來簡化上下文管理,你
將不得不直接使用 JTA UserTransaction API。
// BMT idiom with getCurrentSession()
try {
UserTransaction tx = (UserTransaction)new InitialContext()
.lookup("java:comp/UserTransaction");
tx.begin();
// Do some work on Session bound to transaction
factory.getCurrentSession().load(...);
factory.getCurrentSession().persist(...);
tx.commit();
}
catch (RuntimeException e) {
tx.rollback();
throw e; // or display error message
}
在 CMT 方式下,事務聲明是在 session bean 的部署描述符中,而不須要編程。所以,代碼被簡
化爲:
// CMT idiom
Session sess = factory.getCurrentSession();
// do some work
...
在 CMT/EJB 中甚至會自動 rollback,由於倘若有未捕獲的 RuntimeException 從 session bean
方法中拋出,這就會通知容器把全局事務回滾。這就意味着,在 BMT 或者 CMT 中,你根本就不
須要使用 Hibernate Transaction API,你自動獲得了綁定到事務的「當前」 Session。
注意,當你配置 Hibernate 的 transaction factory 的時候,在直接使用
JTA 的時候(BMT),你應該選擇 org.hibernate.transaction.JTATransactionFactory,在 CMT
session bean 中選擇 org.hibernate.transaction.CMTTransactionFactory。記得也要設置
hibernate.transaction.manager_lookup_class。還有,確認你的
hibernate.current_session_context_class 未設置(爲了向下兼容),或者設置爲 "jta"。
getCurrentSession()在 JTA 環境中有一個弊端。對 after_statement 鏈接釋放方式有一個警告,
這是被默認使用的。由於 JTA 規範的一個很愚蠢的限制,Hibernate 不可能自動清理任何未關閉
的 ScrollableResults 或者Iterator,它們是由 scroll() 或 iterate() 產生的。你 must 經過在
第 13 章 事務和併發
244
finally 塊中,顯式調用 ScrollableResults.close() 或者 Hibernate.close(Iterator) 方法來釋放
底層數據庫遊標。(固然,大部分程序徹底能夠很容易的避免在 JTA 或 CMT 代碼中出現 scroll()
或 iterate()。)
13.2.3. 異常處理
若是 Session 拋出異常(包括任何 SQLException),你應該當即回滾數據庫事務,調用
Session.close() ,丟棄該 Session 實例。Session 的某些方法可能會致使 session 處於不一致的
狀態。全部由 Hibernate 拋出的異常都視爲不能夠恢復的。確保在 finally 代碼塊中調用 close()
方法,以關閉掉 Session。
HibernateException 是一個非檢查期異常(這不一樣於 Hibernate 老的版本),它封裝了 Hibernate
持久層可能出現的大多數錯誤。咱們的觀點是,不該該強迫應用程序開發人員 在底層捕獲沒法恢
復的異常。在大多數軟件系統中,非檢查期異常和致命異常都是在相應方法調用 的堆棧的頂層被
處理的(也就是說,在軟件上面的邏輯層),而且提供一個錯誤信息給應用軟件的用戶 (或者採
取其餘某些相應的操做)。請注意,Hibernate 也有可能拋出其餘並不屬於 HibernateException
的非檢查期異常。這些異常一樣也是沒法恢復的,應該 採起某些相應的操做去處理。
在和數據庫進行交互時,Hibernate 把捕獲的 SQLException 封裝爲 Hibernate 的 JDBCException。
事實上,Hibernate 嘗試把異常轉換爲更有實際含義的 JDBCException 異常的子類。底層
的 SQLException 能夠經過 JDBCException.getCause() 來獲得。Hibernate 經過使用關聯到
SessionFactory 上的 SQLExceptionConverter 來把 SQLException 轉換爲一個對應的 JDBCException
異常的子類。默認狀況下,SQLExceptionConverter 能夠經過配置 dialect 選項指定;此外,也可
以使用用戶自定義的實現類(參考 javadocs SQLExceptionConverterFactory 類來了解詳情)。標
準的 JDBCException 子類型是:
•JDBCConnectionException:指明底層的 JDBC 通信出現錯誤。
•SQLGrammarException:指明發送的 SQL 語句的語法或者格式錯誤。
•ConstraintViolationException:指明某種類型的約束違例錯誤
•LockAcquisitionException:指明瞭在執行請求操做時,獲取所需的鎖級別時出現的錯誤。
•GenericJDBCException:不屬於任何其餘種類的原生異常。
13.2.4. 事務超時
EJB 這樣的託管環境有一項極爲重要的特性,而它從未在非託管環境中提供過,那就是事務超
時。在出現錯誤的事務行爲的時候,超時能夠確保不會無限掛起資源、對用戶沒有交代。在託管
(JTA)環境以外,Hibernate 沒法徹底提供這一功能。可是,Hiberante 至少能夠控制數據訪
問,確保數據庫級別的死鎖,和返回巨大結果集的查詢被限定在一個規定的時間內。在託管環境
中,Hibernate 會把事務超時轉交給 JTA。這一功能經過 Hibernate Transaction 對象進行抽象。
Session sess = factory.openSession();
try {
//set transaction timeout to 3 seconds
sess.getTransaction().setTimeout(3);
sess.getTransaction().begin();
樂觀併發控制(Optimistic concurrency control)
245
// do some work
...
sess.getTransaction().commit()
}
catch (RuntimeException e) {
sess.getTransaction().rollback();
throw e; // or display error message
}
finally {
sess.close();
}
注意 setTimeout() 不該該在 CMT bean 中調用,此時事務超時值應該是被聲明式定義的。
13.3. 樂觀併發控制(Optimistic concurrency control)
惟一可以同時保持高併發和高可伸縮性的方法就是使用帶版本化的樂觀併發控制。版本檢查使用
版本號、 或者時間戳來檢測更新衝突(而且防止更新丟失)。Hibernate 爲使用樂觀併發控制的
代碼提供了三種可 能的方法,應用程序在編寫這些代碼時,能夠採用它們。咱們已經在前面應用
程序對話那部分展現了 樂觀併發控制的應用場景,此外,在單個數據庫事務範圍內,版本檢查也
提供了防止更新丟失的好處。
13.3.1. 應用程序級別的版本檢查(Application version
checking)
未能充分利用 Hibernate 功能的實現代碼中,每次和數據庫交互都須要一個新的 Session,並且
開發人員必須在顯示數據以前從數據庫中從新載入全部的持久化對象實例。這種方式迫使應用程
序本身實現版本檢查來確保對話事務的隔離,從數據訪問的角度來講是最低效的。這種使用方式
和 entity EJB 最類似。
// foo is an instance loaded by a previous Session
session = factory.openSession();
Transaction t = session.beginTransaction();
int oldVersion = foo.getVersion();
session.load( foo, foo.getKey() ); // load the current state
if ( oldVersion != foo.getVersion() ) throw new StaleObjectStateException();
foo.setProperty("bar");
t.commit();
session.close();
version 屬性使用 <version> 來映射,若是對象是髒數據,在同步的時候,Hibernate 會自動增長
版本號。
固然,若是你的應用是在一個低數據併發環境下,並不須要版本檢查的話,你照樣能夠使用這種
方式,只不過跳過版本檢查就是了。在這種狀況下,最晚提交生效 (last commit wins)就是你
第 13 章 事務和併發
246
的長對話的默認處理策略。請記住這種策略可能會讓應用軟件的用戶感到困惑,由於他們有可能
會碰上更新丟失掉卻沒有出錯信息,或者須要合併更改衝突的狀況。
很明顯,手工進行版本檢查只適合於某些軟件規模很是小的應用場景,對於大多數軟件應用場景
來講並不現實。一般狀況下,不只是單個對象實例須要進行版本檢查,整個被修改過的關聯對象
圖也都須要進行版本檢查。做爲標準設計範例,Hibernate 使用擴展週期的 Session 的方式,或
者脫管對象實例的方式來提供自動版本檢查。
13.3.2. 擴展週期的 session 和自動版本化
單個 Session 實例和它所關聯的全部持久化對象實例都被用於整個對話,這被稱爲 sessionper-conversation。Hibernate
在同步的時候進行對象實例的版本檢查,若是檢測到併發修改則
拋出異常。由開發人員來決定是否須要捕獲和處理這個異常(一般的抉擇是給用戶 提供一個合併
更改,或者在無髒數據狀況下從新進行業務對話的機會)。
在等待用戶交互的時候, Session 斷開底層的 JDBC 鏈接。這種方式以數據庫訪問的角度來講是最
高效的方式。應用程序不須要關心版本檢查或脫管對象實例的從新關聯,在每一個數據庫事務中,
應用程序也不須要載入讀取對象實例。
// foo is an instance loaded earlier by the old session
Transaction t = session.beginTransaction(); // Obtain a new JDBC connection, start transaction
foo.setProperty("bar");
session.flush(); // Only for last transaction in conversation
t.commit(); // Also return JDBC connection
session.close(); // Only for last transaction in conversation
foo 對象知道它是在哪一個 Session 中被裝入的。在一箇舊 session 中開啓一個新的數據庫事
務,會致使 session 獲取一個新的鏈接,並恢復 session 的功能。將數據庫事務提交,使得
session 從 JDBC 鏈接斷開,並將此鏈接交還給鏈接池。在從新鏈接以後,要強制對你沒有更新
的數據進行一次版本檢查,你能夠對全部可能被其餘事務修改過的對象,使用參數 LockMode.READ
來調用 Session.lock()。你不用 lock 任何你正在更新的數據。通常你會在擴展的 Session 上設
置 FlushMode.NEVER,所以只有最後一個數據庫事務循環纔會真正的把整個對話中發生的修改發送
到數據庫。所以,只有這最後一次數據庫事務纔會包含 flush() 操做,而後在整個對話結束後,
還要 close() 這個 session。
若是在用戶思考的過程當中,Session 由於太大了而不能保存,那麼這種模式是有問題的。舉例來
說,一個 HttpSession 應該儘量的小。因爲 Session 是一級緩存,而且保持了全部被載入過的
對象,所以咱們只應該在那些少許的 request/response 狀況下使用這種策略。你應該只把一個
Session 用於單個對話,由於它很快就會出現髒數據。
注意
注意,早期的 Hibernate 版本須要明確的對 Session 進行 disconnect 和
reconnect。這些方法如今已通過時了,打開事務和關閉事務會起到一樣的效果。
脫管對象(deatched object)和自動版本化
247
此外,也請注意,你應該讓與數據庫鏈接斷開的 Session 對持久層保持關閉狀態。換句話說,在
三層環境中,使用有狀態的 EJB session bean 來持 有Session, 而不要把它傳遞到 web 層(甚
至把它序列化到一個單獨的層),保存在 HttpSession 中。
擴展 session 模式,或者被稱爲每次對話一個session(session-per-conversation),自動管
理當前 session 上下文聯用的時候會更困難。你須要提供你本身的 CurrentSessionContext 實現。
請參閱 Hibernate Wiki 以得到示例。
13.3.3. 脫管對象(deatched object)和自動版本化
這種方式下,與持久化存儲的每次交互都發生在一個新的 Session 中。然而,同一持久化對象實
例能夠在屢次與數據庫的交互中重用。應用程序操縱脫管對象實例 的狀態,這個脫管對象實例最
初是在另外一個 Session 中載入的,而後調用 Session.update(),Session.saveOrUpdate(),或者
Session.merge() 來從新關聯該對象實例。
// foo is an instance loaded by a previous Session
foo.setProperty("bar");
session = factory.openSession();
Transaction t = session.beginTransaction();
session.saveOrUpdate(foo); // Use merge() if "foo" might have been loaded already
t.commit();
session.close();
Hibernate 會再一次在同步的時候檢查對象實例的版本,若是發生更新衝突,就拋出異常。
若是你確信對象沒有被修改過,你也能夠調用 lock() 來設置 LockMode.READ(繞過全部的緩存,
執行版本檢查),從而取代 update() 操做。
13.3.4. 定製自動版本化行爲
對於特定的屬性和集合,經過爲它們設置映射屬性 optimistic-lock 的值爲 false,來禁止
Hibernate 的版本自動增長。這樣的話,若是該屬性髒數據,Hibernate 將再也不增長版本號。
遺留系統的數據庫 Schema 一般是靜態的,不可修改的。或者,其餘應用程序也可能訪問同一數
據庫,根本沒法得知如何處理版本號,甚至時間戳。在以上的全部場景中,實現版本化不能依靠
數據庫表的某個特定列。在 <class> 的映射中設置 optimistic-lock="all" 能夠在沒有版本或者時
間戳屬性映射的狀況下實現版本檢查,此時 Hibernate 將比較一行記錄的每一個字段的狀態。請注
意,只有當 Hibernate 可以比較新舊狀態的狀況下,這種方式才能生效,也就是說,你必須使用
單個長生命週期 Session 模式,而不能使用 session-per-request-with-detached-objects 模
式。
有些狀況下,只要更改不發生交錯,併發修改也是容許的。當你在 <class> 的映射中設置
optimistic-lock="dirty",Hibernate 在同步的時候將只比較有髒數據的字段。
在以上全部場景中,無論是專門設置一個版本/時間戳列,仍是進行所有字段/髒數據字段比
較,Hibernate 都會針對每一個實體對象發送一條 UPDATE(帶有相應的 WHERE 語句 )的 SQL 語
句來執行版本檢查和數據更新。若是你對關聯實體 設置級聯關係使用傳播性持久化(transitive
第 13 章 事務和併發
248
persistence),那麼 Hibernate 可能會執行沒必要 要的update語句。這一般不是個問題,可是數
據庫裏面對 on update 點火 的觸發器可能在脫管對象沒有任何更改的狀況下被觸發。所以,你可
以在 <class> 的映射中,經過設置select-before-update="true" 來定製這一行爲,強制 Hibernate
SELECT 這個對象實例,從而保證,在更新記錄以前,對象的確是被修改過。
13.4. 悲觀鎖定(Pessimistic Locking)
用戶其實並不須要花不少精力去擔憂鎖定策略的問題。一般狀況下,只要爲 JDBC 鏈接指定一下
隔離級別,而後讓數據庫去搞定一切就夠了。然而,高級用戶有時候但願進行一個排它的悲觀鎖
定,或者在一個新的事務啓動的時候,從新進行鎖定。
Hibernate 老是使用數據庫的鎖定機制,從不在內存中鎖定對象。
類 LockMode 定義了 Hibernate 所需的不一樣的鎖定級別。一個鎖定能夠經過如下的機制來設置:
•當 Hibernate 更新或者插入一行記錄的時候,鎖定級別自動設置爲 LockMode.WRITE。
•當用戶顯式的使用數據庫支持的 SQL 格式 SELECT ... FOR UPDATE 發送 SQL 的時候,鎖定級
別設置爲 LockMode.UPGRADE。
•當用戶顯式的使用 Oracle 數據庫的 SQL 語句 SELECT ... FOR UPDATE NOWAIT 的時候,鎖定級
別設置 LockMode.UPGRADE_NOWAIT。
•當 Hibernate 在「可重複讀」或者是「序列化」數據庫隔離級別下讀取數據的時候,鎖定模式
自動設置爲 LockMode.READ。這種模式也能夠經過用戶顯式指定進行設置。
•LockMode.NONE 表明無需鎖定。在 Transaction 結束時, 全部的對象都切換到該模式上來。與
session 相關聯的對象經過調用 update() 或者 saveOrUpdate() 脫離該模式。
"顯式的用戶指定"能夠經過如下幾種方式之一來表示:
•調用 Session.load() 的時候指定鎖定模式(LockMode)。
•調用 Session.lock()。
•調用 Query.setLockMode()。
若是在 UPGRADE 或者 UPGRADE_NOWAIT 鎖定模式下調用 Session.load(),而且要讀取的對象還沒有被
session 載入過,那麼對象經過 SELECT ... FOR UPDATE 這樣的 SQL 語句被載入。若是爲一個對象
調用 load() 方法時,該對象已經在另外一個較少限制的鎖定模式下被載入了,那麼 Hibernate 就
對該對象調用 lock() 方法。
若是指定的鎖定模式是 READ,UPGRADE 或 UPGRADE_NOWAIT,那麼 Session.lock() 就執行版本號檢
查。(在 UPGRADE 或者 UPGRADE_NOWAIT 鎖定模式下,執行 SELECT ... FOR UPDATE這樣的SQL語
句。)
若是數據庫不支持用戶設置的鎖定模式,Hibernate 將使用適當的替代模式(而不是扔出異
常)。這一點能夠確保應用程序的可移植性。
13.5. 鏈接釋放模式(Connection Release Modes)
Hibernate 關於 JDBC 鏈接管理的舊(2.x)行爲是,Session 在第一次須要的時候獲取一個鏈接,
在 session 關閉以前一直會持有這個鏈接。Hibernate 引入了鏈接釋放的概念,來告訴 session
鏈接釋放模式(Connection Release Modes)
249
如何處理它的 JDBC 鏈接。注意,下面的討論只適用於採用配置 ConnectionProvider 來提供鏈接
的狀況,用戶本身提供的鏈接與這裏的討論無關。經過 org.hibernate.ConnectionReleaseMode 的不
同枚舉值來使用不用的釋放模式:
•ON_CLOSE:基本上就是上面提到的老式行爲。Hibernate session 在第一次須要進行 JDBC 操做
的時候獲取鏈接,而後持有它,直到 session 關閉。
•AFTER_TRANSACTION:在 org.hibernate.Transaction 結束後釋放鏈接。
•AFTER_STATEMENT(也被稱作積極釋放):在每一條語句被執行後就釋放鏈接。但倘若語句留下
了與 session 相關的資源,那就不會被釋放。目前惟一的這種情形就是使用
org.hibernate.ScrollableResults。
hibernate.connection.release_mode 配置參數用來指定使用哪種釋放模式。可能的值有:
•auto(默認):這一選擇把釋放模式委派給
org.hibernate.transaction.TransactionFactory.getDefaultReleaseMode() 方法。對
JTATransactionFactory 來講,它會返回 ConnectionReleaseMode.AFTER_STATEMENT;對
 JDBCTransactionFactory
 來講,則是ConnectionReleaseMode.AFTER_TRANSACTION。不多須要修改這一默認行爲,由於
倘若設置不當,就會帶來 bug,或者給用戶代碼帶來誤導。
•on_close:使用 ConnectionReleaseMode.ON_CLOSE。這種方式是爲了向下兼容的,可是已經徹底
不被鼓勵使用了。
•after_transaction:使用 ConnectionReleaseMode.AFTER_TRANSACTION。這一設置不該該在 JTA
環境下使用。也要注意,使用 ConnectionReleaseMode.AFTER_TRANSACTION 的時候,倘若
session 處於 auto-commit 狀態,鏈接會像 AFTER_STATEMENT 那樣被釋放。
•after_statement:使用 ConnectionReleaseMode.AFTER_STATEMENT。除此以外,會查詢配
置的 ConnectionProvider,是否它支持這一設置(supportsAggressiveRelease())。倘若不支
持,釋放模式會被設置爲 ConnectionReleaseMode.AFTER_TRANSACTION。只有在你每次調用
ConnectionProvider.getConnection() 獲取底層 JDBC 鏈接的時候,均可以確信得到同一個鏈接的
時候,這一設置纔是安全的;或者在 auto-commit 環境中,你能夠無論是否每次都得到同一個
鏈接的時候,這纔是安全的。
250
第 14
251
攔截器與事件(Interceptors and
events)
應用程序可以響應 Hibernate 內部產生的特定事件是很是有用的。這樣就容許實現某些通用的功
能以及容許對 Hibernate 功能進行擴展。
14.1. 攔截器(Interceptors)
Interceptor 接口提供了從會話(session)回調(callback)應用程序(application)的機
制, 這種回調機制能夠容許應用程序在持久化對象被保存、更新、刪除或是加載以前,檢查並
(或)修改其 屬性。一個可能的用途,就是用來跟蹤審覈(auditing)信息。例如:下面的這
個攔截器,會在一個實現了 Auditable 接口的對象被建立時自動地設置 createTimestamp 屬性,並
在實現了 Auditable 接口的對象被更新時,同步更新 lastUpdateTimestamp 屬性。
你能夠直接實現 Interceptor 接口,也能夠(最好)繼承自 EmptyInterceptor。
package org.hibernate.test;
import java.io.Serializable;
import java.util.Date;
import java.util.Iterator;
import org.hibernate.EmptyInterceptor;
import org.hibernate.Transaction;
import org.hibernate.type.Type;
public class AuditInterceptor extends EmptyInterceptor {
private int updates;
private int creates;
private int loads;
public void onDelete(Object entity,
Serializable id,
Object[] state,
String[] propertyNames,
Type[] types) {
// do nothing
}
public boolean onFlushDirty(Object entity,
Serializable id,
Object[] currentState,
Object[] previousState,
String[] propertyNames,
Type[] types) {
if ( entity instanceof Auditable ) {
updates++;
for ( int i=0; i < propertyNames.length; i++ ) {
if ( "lastUpdateTimestamp".equals( propertyNames[i] ) ) {
第 14 章 攔截器與事件(Interceptors an...
252
currentState[i] = new Date();
return true;
}
}
}
return false;
}
public boolean onLoad(Object entity,
Serializable id,
Object[] state,
String[] propertyNames,
Type[] types) {
if ( entity instanceof Auditable ) {
loads++;
}
return false;
}
public boolean onSave(Object entity,
Serializable id,
Object[] state,
String[] propertyNames,
Type[] types) {
if ( entity instanceof Auditable ) {
creates++;
for ( int i=0; i<propertyNames.length; i++ ) {
if ( "createTimestamp".equals( propertyNames[i] ) ) {
state[i] = new Date();
return true;
}
}
}
return false;
}
public void afterTransactionCompletion(Transaction tx) {
if ( tx.wasCommitted() ) {
System.out.println("Creations: " + creates + ", Updates: " + updates, "Loads: " + loads);
}
updates=0;
creates=0;
loads=0;
}
}
攔截器能夠有兩種:Session 範圍內的和 SessionFactory 範圍內的。
當使用某個重載的 SessionFactory.openSession() 使用 Interceptor 做爲參數調用打開一個
session 的時候,就指定了 Session 範圍內的攔截器。
Session session = sf.openSession( new AuditInterceptor() );
事件系統(Event system)
253
SessionFactory 範圍內的攔截器要經過 Configuration 中註冊,而這必須在建立 SessionFactory 之
前。在這種狀況下,給出的攔截器會被這個 SessionFactory 所打開的全部 session 使用了;除
非 session 打開時明確指明瞭使用的攔截器。SessionFactory 範圍內的攔截器,必須是線程安全
的。確保你沒有保存 session 專有的狀態,由於多個 session 可能併發使用這個攔截器。
new Configuration().setInterceptor( new AuditInterceptor() );
14.2. 事件系統(Event system)
若是須要響應持久層的某些特殊事件,你也能夠使用 Hibernate3 的事件框架。該事件系統能夠
用來替代攔截器,也能夠做爲攔截器的補充來使用。
基本上,Session 接口的每一個方法都有相對應的事件。好比 LoadEvent,FlushEvent,等等(查
閱 XML 配置文件的 DTD,以及 org.hibernate.event 包來得到全部已定義的
事件的列表)。當某個方 法被調用時,Hibernate Session會生成一個相對應的事件並激活
所 有配置好的事件監聽器。系統預設的監聽器實現的處理過程就是被監聽的方法要作的(被監聽
的方法所作的其實僅僅是激活監聽器,「實際」的工做是由監聽器完成的)。不過,你能夠自由
地選擇實現 一個本身定製的監聽器(好比,實現並註冊用來處理處理 LoadEvent 的
LoadEventListener 接口), 來負責處理全部的調用 Session 的 load() 方法的請求。
監聽器應該被看做是單例(singleton)對象,也就是說,全部同類型的事件的處理共享同一個監
聽器實例,所以監聽器不該該保存任何狀態(也就是不該該使用成員變量)。
用戶定製的監聽器應該實現與所要處理的事件相對應的接口,或者從一個合適的基類繼承(甚至
是從 Hibernate 自帶的默認事件監聽器類繼承,爲了方便你這樣作,這些類都被聲明成 nonfinal
的了)。用戶定製的監聽器能夠經過編程使用 Configuration 對象 來註冊,也能夠在
Hibernate的 XML 格式的配置文件中進行聲明(不支持在 Properties 格式的配置文件聲明監聽
器)。下面是一個用戶定製的加載事件(load event)的監聽器:
public class MyLoadListener implements LoadEventListener {
// this is the single method defined by the LoadEventListener interface
public void onLoad(LoadEvent event, LoadEventListener.LoadType loadType)
throws HibernateException {
if ( !MySecurity.isAuthorized( event.getEntityClassName(), event.getEntityId() ) ) {
throw MySecurityException("Unauthorized access");
}
}
}
你還須要修改一處配置,來告訴 Hibernate,除了默認的監聽器,還要附加選定的監聽器。
<hibernate-configuration>
<session-factory>
...
<event type="load">
<listener class="com.eg.MyLoadListener"/>
第 14 章 攔截器與事件(Interceptors an...
254
<listener class="org.hibernate.event.def.DefaultLoadEventListener"/>
</event>
</session-factory>
</hibernate-configuration
>
或者,你能夠經過編程的方式來註冊它:
Configuration cfg = new Configuration();
LoadEventListener[] stack = { new MyLoadListener(), new DefaultLoadEventListener() };
cfg.EventListeners().setLoadEventListeners(stack);
經過在XML配置文件聲明而註冊的監聽器不能共享實例。若是在多個 <listener/> 節點中使
用 了相同的類的名字,則每個引用都將會產生一個獨立的實例。若是你須要在多個監聽器類型
之間共享 監聽器的實例,則你必須使用編程的方式來進行註冊。
爲何咱們實現了特定監聽器的接口,在註冊的時候還要明確指出咱們要註冊哪一個事件的監聽器
呢? 這是由於一個類可能實現多個監聽器的接口。在註冊的時候明確指定要監聽的事件,可讓
啓用或者禁用對某個事件的監聽的配置工做簡單些。
14.3. Hibernate 的聲明式安全機制
一般,Hibernate 應用程序的聲明式安全機制由會話外觀層(session facade)所管理。現
在,Hibernate3容許某些特定的行爲由 JACC 進行許可管理,由 JAAS 進行受權管理。本功能是
一個創建在事件框架之上的可選的功能。
首先,你必需要配置適當的事件監聽器(event listener),來激活使用 JAAS 管理受權的功能。
<listener type="pre-delete" class="org.hibernate.secure.JACCPreDeleteEventListener"/>
<listener type="pre-update" class="org.hibernate.secure.JACCPreUpdateEventListener"/>
<listener type="pre-insert" class="org.hibernate.secure.JACCPreInsertEventListener"/>
<listener type="pre-load" class="org.hibernate.secure.JACCPreLoadEventListener"/>
注意,<listener type="..." class="..."/> 只是 <event type="..."><listener class="..."/></event>
的簡寫,對每個事件類型都必須嚴格的有一個監聽器與之對應。
接下來,仍然在 hibernate.cfg.xml 文件中,綁定角色的權限:
<grant role="admin" entity-name="User" actions="insert,update,read"/>
<grant role="su" entity-name="User" actions="*"/>
這些角色的名字就是你的 JACC provider 所定義的角色的名字。
第 15
255
批量處理(Batch processing)
使用 Hibernate 將 100,000 條記錄插入到數據庫的一個很天真的作法多是這樣的:
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
for ( int i=0; i<100000; i++ ) {
Customer customer = new Customer(.....);
session.save(customer);
}
tx.commit();
session.close();
這段程序大概運行到 50,000 條記錄左右會失敗並拋出內存溢出異常(OutOfMemoryException) 。
這是由於 Hibernate 把全部新插入的客戶(Customer)實例在 session 級別的緩存區進行了緩存
的緣故。
咱們會在本章告訴你如何避免此類問題。首先,若是你要執行批量處理而且想要達到一個理想的
性能,那麼使用 JDBC 的批量(batching)功能是相當重要。將 JDBC 的批量抓取數量(batch
size)參數設置到一個合適值(好比,10 - 50 之間):
hibernate.jdbc.batch_size 20
注意,倘若你使用了 identiy 標識符生成器,Hibernate 在 JDBC 級別透明的關閉插入語句的批
量執行。
你也可能想在執行批量處理時徹底關閉二級緩存:
hibernate.cache.use_second_level_cache false
可是,這不是絕對必須的,由於咱們能夠顯式設置 CacheMode 來關閉與二級緩存的交互。
15.1. 批量插入(Batch inserts)
若是要將不少對象持久化,你必須經過常常的調用 flush() 以及稍後調用 clear() 來控制第一級
緩存的大小。
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
for ( int i=0; i<100000; i++ ) {
Customer customer = new Customer(.....);
session.save(customer);
if ( i % 20 == 0 ) { //20, same as the JDBC batch size
第 15 章 批量處理(Batch processing)
256
//flush a batch of inserts and release memory:
session.flush();
session.clear();
}
}
tx.commit();
session.close();
15.2. 批量更新(Batch updates)
此方法一樣適用於檢索和更新數據。此外,在進行會返回不少行數據的查詢時,你須要使用
scroll() 方法以便充分利用服務器端遊標所帶來的好處。
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
ScrollableResults customers = session.getNamedQuery("GetCustomers")
.setCacheMode(CacheMode.IGNORE)
.scroll(ScrollMode.FORWARD_ONLY);
int count=0;
while ( customers.next() ) {
Customer customer = (Customer) customers.get(0);
customer.updateStuff(...);
if ( ++count % 20 == 0 ) {
//flush a batch of updates and release memory:
session.flush();
session.clear();
}
}
tx.commit();
session.close();
15.3. StatelessSession(無狀態 session)接口
做爲選擇,Hibernate 提供了基於命令的 API,能夠用 detached object 的形式把數據以流的方
法加入到數據庫,或從數據庫輸出。StatelessSession 沒有持久化上下文,也不提供多少高層的
生命週期語義。特別是,無狀態 session 不實現第一級 cache,也不和第二級緩存,或者查詢緩
存交互。它不實現事務化寫,也不實現髒數據檢查。用 stateless session 進行的操做甚至不級
聯到關聯實例。stateless session 忽略集合類(Collections)。經過 stateless session 進
行的操做不觸發 Hibernate 的事件模型和攔截器。無狀態 session 對數據的混淆現象免疫,因
爲它沒有第一級緩存。無狀態 session 是低層的抽象,和低層 JDBC 至關接近。
StatelessSession session = sessionFactory.openStatelessSession();
Transaction tx = session.beginTransaction();
ScrollableResults customers = session.getNamedQuery("GetCustomers")
.scroll(ScrollMode.FORWARD_ONLY);
while ( customers.next() ) {
DML(數據操做語言)風格的操做(DML-style operations)
257
Customer customer = (Customer) customers.get(0);
customer.updateStuff(...);
session.update(customer);
}
tx.commit();
session.close();
注意在上面的例子中,查詢返回的 Customer 實例當即被脫管(detach)。它們與任何持久化上下
文都沒有關係。
StatelessSession 接口定義的 insert(), update() 和 delete() 操做是直接的數據庫行級別操做,
其結果是馬上執行一條 INSERT, UPDATE 或 DELETE 語句。所以,它們的語義和 Session 接口定義
的 save(), saveOrUpdate() 和delete() 操做有很大的不一樣。
15.4. DML(數據操做語言)風格的操做(DML-style
operations)
就像已經討論的那樣,自動和透明的對象/關係映射(object/relational mapping)關注於管理
對象的狀態。這就意味着對象的狀態存在於內存,所以直接操做(使用 SQL Data Manipulation
Language(DML,數據操做語言)語句 :INSERT ,UPDATE 和 DELETE) 數據庫中的數據將不會影響內
存中的對象狀態和對象數據。不過,Hibernate 提供經過 Hibernate 查詢語言(HQL)來執行大
批量 SQL 風格的 DML 語句的方法。
UPDATE 和 DELETE 語句的僞語法爲:( UPDATE | DELETE ) FROM? EntityName (WHERE
where_conditions)?。
要注意的事項:
•在 FROM 子句(from-clause)中,FROM 關鍵字是可選的
•在 FROM 子句(from-clause)中只能有一個實體名,它能夠是別名。若是實體名是別名,那麼
任何被引用的屬性都必須加上此別名的前綴;若是不是別名,那麼任何有前綴的屬性引用都是
非法的。
•不能在大批量 HQL 語句中使用 joins 鏈接(顯式或者隱式的都不行)。不過在 WHERE 子句中
能夠使用子查詢。能夠在 where 子句中使用子查詢,子查詢自己能夠包含 join。
•整個 WHERE 子句是可選的。
舉個例子,使用 Query.executeUpdate() 方法執行一個 HQL UPDATE語句(方法命名是來源於 JDBC
的 PreparedStatement.executeUpdate()):
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
String hqlUpdate = "update Customer c set c.name = :newName where c.name = :oldName";
// or String hqlUpdate = "update Customer set name = :newName where name = :oldName";
int updatedEntities = s.createQuery( hqlUpdate )
.setString( "newName", newName )
.setString( "oldName", oldName )
第 15 章 批量處理(Batch processing)
258
.executeUpdate();
tx.commit();
session.close();
HQL UPDATE 語句,默認不會影響更新實體的 version 或 the timestamp 屬性值。這和 EJB3 規
範是一致的。可是,經過使用 versioned update,你能夠強制 Hibernate 正確的重置version 或者
timestamp 屬性值。這經過在 UPDATE 關鍵字後面增長 VERSIONED 關鍵字來實現的。
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
String hqlVersionedUpdate = "update versioned Customer set name = :newName where name = :oldName";
int updatedEntities = s.createQuery( hqlUpdate )
.setString( "newName", newName )
.setString( "oldName", oldName )
.executeUpdate();
tx.commit();
session.close();
注意,自定義的版本類型(org.hibernate.usertype.UserVersionType)不容許和 update versioned
語句聯用。
執行一個 HQL DELETE,一樣使用 Query.executeUpdate() 方法:
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
String hqlDelete = "delete Customer c where c.name = :oldName";
// or String hqlDelete = "delete Customer where name = :oldName";
int deletedEntities = s.createQuery( hqlDelete )
.setString( "oldName", oldName )
.executeUpdate();
tx.commit();
session.close();
由 Query.executeUpdate() 方法返回的整型值代表了受此操做影響的記錄數量。注意這個數值可
能與數據庫中被(最後一條 SQL 語句)影響了的「行」數有關,也可能沒有。一個大批量 HQL
操做可能致使多條實際的SQL語句被執行,舉個例子,對 joined-subclass 映射方式的類進行的
此類操做。這個返回值表明了實際被語句影響了的記錄數量。在那個 joined-subclass 的例子
中, 對一個子類的刪除實際上可能不只僅會刪除子類映射到的表並且會影響「根」表,還有可能
影響與之有繼承關係的 joined-subclass 映射方式的子類的表。
INSERT 語句的僞碼是:INSERT INTO EntityName properties_list select_statement。要注意的是:
•只支持 INSERT INTO ... SELECT ... 形式,不支持 INSERT INTO ... VALUES ... 形式。
properties_list 和 SQL INSERT 語句中的字段定義(column speficiation)相似。對參與繼承樹
映射的實體而言,只有直接定義在給定的類級別的屬性才能直接在 properties_list 中使用。
超類的屬性不被支持;子類的屬性無心義。換句話說,INSERT 天生不支持多態性。
DML(數據操做語言)風格的操做(DML-style operations)
259
•select_statement 能夠是任何合法的 HQL 選擇查詢,不過要保證返回類型必須和要插入的類型
徹底匹配。目前,這一檢查是在查詢編譯的時候進行的,而不是把它交給數據庫。注意,在
HibernateType 間若是隻是等價(equivalent)而非相等(equal),會致使問題。定義爲
org.hibernate.type.DateType 和 org.hibernate.type.TimestampType 的兩個屬性可能會產生類型
不匹配錯誤,雖然數據庫級可能不加區分或者能夠處理這種轉換。
•對 id 屬性來講,insert 語句給你兩個選擇。你能夠明確地在 properties_list
表中指定 id 屬性(這樣它的值是從對應的 select 表達式中得到),或者在
properties_list 中省略它(此時使用生成指)。後一種選擇只有當使用在數據庫中生成值的
id 產生器時才能使用;若是是「內存」中計算的類型生成器,在解析時會拋出一個異常。注
意,爲了說明這一問題,數據庫產生值的生成器是 org.hibernate.id.SequenceGenerator(和它
的子類),以及任何 org.hibernate.id.PostInsertIdentifierGenerator 接口的實現。這兒最值得
注意的意外是 org.hibernate.id.TableHiLoGenerator,它不能在此使用,由於它沒有獲得其值的
途徑。
•對映射爲 version 或 timestamp 的屬性來講,insert 語句也給你兩個選擇,你能夠
在 properties_list 表中指定(此時其值從對應的 select 表達式中得到),或者在
properties_list 中省略它(此時,使用在 org.hibernate.type.VersionType 中定義的 seed
value(種子值))。
下面是一個執行 HQL INSERT 語句的例子:
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
String hqlInsert = "insert into DelinquentAccount (id, name) select c.id, c.name from Customer
c where ...";
int createdEntities = s.createQuery( hqlInsert )
.executeUpdate();
tx.commit();
session.close();
260
第 16
261
HQL: Hibernate 查詢語言
Hibernate 配備了一種很是強大的查詢語言,這種語言看上去很像 SQL。可是不要被語法結構上
的類似所迷惑,HQL 是很是有意識的被設計爲徹底面向對象的查詢,它能夠理解如繼承、多態和
關聯之類的概念。
16.1. 大小寫敏感性問題
除了 Java 類與屬性的名稱外,查詢語句對大小寫並不敏感。 因此 SeLeCT 與 sELEct 以及 SELECT
是相同的,可是 org.hibernate.eg.FOO 並不等價於 org.hibernate.eg.Foo 而且 foo.barSet 也不等
價於 foo.BARSET。
本手冊中的 HQL 關鍵字將使用小寫字母。不少用戶發現使用徹底大寫的關鍵字會使查詢語句的可
讀性更強,但咱們發現,當把查詢語句嵌入到 Java 語句中的時候使用大寫關鍵字比較難看。
16.2. from 子句
Hibernate 中最簡單的查詢語句的形式以下:
from eg.Cat
該子句簡單的返回 eg.Cat 類的全部實例。一般咱們不須要使用類的全限定名,由於 autoimport(自動引入)是缺省的狀況。因此咱們幾乎只使用以下的簡單寫法:
from Cat
爲了在這個查詢的其餘部分裏引用 Cat,你將須要分配一個別名。例如:
from Cat as cat
這個語句把別名 cat 指定給類Cat 的實例,這樣咱們就能夠在隨後的查詢中使用此別名了。關鍵
字 as 是可選的,咱們也能夠這樣寫:
from Cat cat
子句中能夠同時出現多個類,其查詢結果是產生一個笛卡兒積或產生跨表的鏈接。
from Formula, Parameter
第 16 章 HQL: Hibernate 查詢語言
262
from Formula as form, Parameter as param
查詢語句中別名的開頭部分小寫被認爲是實踐中的好習慣,這樣作與 Java 變量的命名標準保持
了一致(好比,domesticCat)。
16.3. 關聯(Association)與鏈接(Join)
咱們也能夠爲相關聯的實體甚至是對一個集合中的所有元素指定一個別名,這時要使用關鍵字
join。
from Cat as cat
inner join cat.mate as mate
left outer join cat.kittens as kitten
from Cat as cat left join cat.mate.kittens as kittens
from Formula form full join form.parameter param
受支持的鏈接類型是從 ANSI SQL 中借鑑來的:
•inner join
•left outer join
•right outer join
•full join(全鏈接,並不經常使用)
語句 inner join,left outer join 以及 right outer join 能夠簡寫。
from Cat as cat
join cat.mate as mate
left join cat.kittens as kitten
經過 HQL 的 with 關鍵字,你能夠提供額外的 join 條件。
from Cat as cat
left join cat.kittens as kitten
with kitten.bodyWeight
> 10.0
A "fetch" join allows associations or collections of values to be initialized along
with their parent objects using a single select. This is particularly useful in the
join 語法的形式
263
case of a collection. It effectively overrides the outer join and lazy declarations of
the mapping file for associations and collections. See 第 21.1 節 「抓取策略(Fetching
strategies)」 for more information.
from Cat as cat
inner join fetch cat.mate
left join fetch cat.kittens
一個 fetch 鏈接一般不須要被指定別名,由於相關聯的對象不該當被用在 where 子句(或其它任
何子句)中。同時,相關聯的對象並不在查詢的結果中直接返回,但能夠經過他們的父對象來訪
問到他們。
from Cat as cat
inner join fetch cat.mate
left join fetch cat.kittens child
left join fetch child.kittens
倘若使用 iterate() 來調用查詢,請注意 fetch 構造是不能使用的(scroll() 能夠使用)。fetch
也不該該與 setMaxResults() 或 setFirstResult() 共用,這是由於這些操做是基於結果集的,而
在預先抓取集合類時可能包含重複的數據,也就是說沒法預先知道精確的行數。fetch 還不能與
獨立的 with 條件一塊兒使用。經過在一次查詢中 fetch 多個集合,能夠製造出笛卡爾積,所以請
多加註意。對 bag 映射來講,同時 join fetch 多個集合角色可能在某些狀況下給出並不是預期的
結果,也請當心。最後注意,使用 full join fetch 與 right join fetch 是沒有意義的。
若是你使用屬性級別的延遲獲取(lazy fetching)(這是經過從新編寫字節碼實現的),能夠使
用 fetch all properties 來強制 Hibernate 當即取得那些本來須要延遲加載的屬性(在第一個查
詢中)。
from Document fetch all properties order by name
from Document doc fetch all properties where lower(doc.name) like '%cats%'
16.4. join 語法的形式
HQL 支持兩種關聯 join 的形式:implicit(隱式) 與 explicit(顯式)。
上一節中給出的查詢都是使用 explicit(顯式)形式的,其中 form 子句中明確給出了 join 關鍵
字。這是建議使用的方式。
implicit(隱式)形式不使用 join 關鍵字。關聯使用"點號"來進行「引用」。implicit join 能夠
在任何 HQL 子句中出現。implicit join 在最終的 SQL 語句中以 inner join 的方式出現。
第 16 章 HQL: Hibernate 查詢語言
264
from Cat as cat where cat.mate.name like '%s%'
16.5. 引用 identifier 屬性
一般有兩種方法來引用實體的 identifier 屬性:
•特殊屬性(lowercase)id 能夠用來引用實體的 identifier 屬性 假設這個實體沒有定義用
non-identifier 屬性命名的 id。
•若是這個實體定義了 identifier 屬性,你能夠使用屬性名。
對組合 identifier 屬性的引用遵循相同的命名規則。若是實體有一個 non-identifier 屬性命
名的 id,這個組合 identifier 屬性只能用本身定義的名字來引用;不然,特殊 id 屬性能夠用
來引用 identifier 屬性。
重要
注意:從 3.2.2 版本開始,這已經改變了不少。在前面的版本里,無論實際的名
字,id 老是指向 identifier 屬性;而用 non-identifier 屬性命名的 id 就從
來不在 Hibernate 查詢裏引用。
16.6. select 子句
select 子句選擇將哪些對象與屬性返回到查詢結果集中。考慮以下狀況:
select mate
from Cat as cat
inner join cat.mate as mate
該語句將選擇其它 Cat 的 mate(其餘貓的配偶)。實際上,你能夠更簡潔的用如下的查詢語句表
達相同的含義:
select cat.mate from Cat cat
查詢語句能夠返回值爲任何類型的屬性,包括返回類型爲某種組件(Component)的屬性:
select cat.name from DomesticCat cat
where cat.name like 'fri%'
select cust.name.firstName from Customer as cust
彙集函數
265
查詢語句能夠返回多個對象和(或)屬性,存放在 Object[] 隊列中,
select mother, offspr, mate.name
from DomesticCat as mother
inner join mother.mate as mate
left outer join mother.kittens as offspr
或存放在一個 List 對象中:
select new list(mother, offspr, mate.name)
from DomesticCat as mother
inner join mother.mate as mate
left outer join mother.kittens as offspr
假設類 Family 有一個合適的構造函數 - 做爲實際的類型安全的 Java 對象:
select new Family(mother, mate, offspr)
from DomesticCat as mother
join mother.mate as mate
left join mother.kittens as offspr
你能夠使用關鍵字 as 給「被選擇了的表達式」指派別名:
select max(bodyWeight) as max, min(bodyWeight) as min, count(*) as n
from Cat cat
這種作法在與子句 select new map 一塊兒使用時最有用:
select new map( max(bodyWeight) as max, min(bodyWeight) as min, count(*) as n )
from Cat cat
該查詢返回了一個 Map 的對象,內容是別名與被選擇的值組成的名-值映射。
16.7. 彙集函數
HQL 查詢甚至能夠返回做用於屬性之上的彙集函數的計算結果:
select avg(cat.weight), sum(cat.weight), max(cat.weight), count(cat)
from Cat cat
受支持的彙集函數以下:
第 16 章 HQL: Hibernate 查詢語言
266
•avg(...), sum(...), min(...), max(...)
•count(*)
•count(...), count(distinct ...), count(all...)
你能夠在選擇子句中使用數學操做符、鏈接以及通過驗證的 SQL 函數:
select cat.weight + sum(kitten.weight)
from Cat cat
join cat.kittens kitten
group by cat.id, cat.weight
select firstName||' '||initial||' '||upper(lastName) from Person
關鍵字 distinct 與 all 也能夠使用,它們具備與 SQL 相同的語義。
select distinct cat.name from Cat cat
select count(distinct cat.name), count(cat) from Cat cat
16.8. 多態查詢
一個以下的查詢語句:
from Cat as cat
不只返回 Cat 類的實例,也同時返回子類 DomesticCat 的實例。Hibernate 能夠在 from 子句中
指定任何 Java 類或接口。查詢會返回繼承了該類的全部持久化子類的實例或返回聲明瞭該接口
的全部持久化類的實例。下面的查詢語句返回全部的被持久化的對象:
from java.lang.Object o
接口 Named 可能被各類各樣的持久化類聲明:
from Named n, Named m where n.name = m.name
注意,最後的兩個查詢將須要超過一個的 SQL SELECT。這代表 order by 子句沒有對整個結果集進
行正確的排序。(這也說明你不能對這樣的查詢使用 Query.scroll() 方法。)
where 子句
267
16.9. where 子句
where 子句容許你將返回的實例列表的範圍縮小。若是沒有指定別名,你能夠使用屬性名來直接
引用屬性:
from Cat where name='Fritz'
若是指派了別名,須要使用完整的屬性名:
from Cat as cat where cat.name='Fritz'
返回名爲(屬性 name 等於)'Fritz' 的 Cat 類的實例。
下面的查詢:
select foo
from Foo foo, Bar bar
where foo.startDate = bar.date
將返回全部知足下面條件的 Foo 類的實例: 存在以下的 bar 的一個實例,其 date 屬性等於 Foo
的 startDate 屬性。複合路徑表達式使得 where 子句很是的強大,考慮以下狀況:
from Cat cat where cat.mate.name is not null
該查詢將被翻譯成爲一個含有錶鏈接(內鏈接)的 SQL 查詢。若是你打算寫像這樣的查詢語句:
from Foo foo
where foo.bar.baz.customer.address.city is not null
在 SQL 中,你爲達此目的將須要進行一個四表鏈接的查詢。
= 運算符不只能夠被用來比較屬性的值,也能夠用來比較實例:
from Cat cat, Cat rival where cat.mate = rival.mate
select cat, mate
from Cat cat, Cat mate
where cat.mate = mate
第 16 章 HQL: Hibernate 查詢語言
268
The special property (lowercase) id can be used to reference the unique identifier of
an object. See 第 16.5 節 「引用 identifier 屬性 」 for more information.
from Cat as cat where cat.id = 123
from Cat as cat where cat.mate.id = 69
第二個查詢是有效的。此時不須要進行錶鏈接。
一樣也能夠使用複合標識符。好比 Person 類有一個複合標識符,它由 country 屬性與
medicareNumber 屬性組成:
from bank.Person person
where person.id.country = 'AU'
and person.id.medicareNumber = 123456
from bank.Account account
where account.owner.id.country = 'AU'
and account.owner.id.medicareNumber = 123456
第二個查詢也不須要進行錶鏈接。
See 第 16.5 節 「引用 identifier 屬性 」 for more information regarding referencing
identifier properties)
一樣的,特殊屬性 class 在進行多態持久化的狀況下被用來存取一個實例的鑑別值
(discriminator value)。一個嵌入到 where 子句中的 Java 類的名字將被轉換爲該類的鑑別
值。
from Cat cat where cat.class = DomesticCat
You can also use components or composite user types, or properties of said component
types. See 第 16.17 節 「組件」 for more information.
一個「任意」類型有兩個特殊的屬性 id 和 class,來容許咱們按照下面的方式表達一個鏈接
(AuditLog.item 是一個屬性,該屬性被映射爲 <any>)。
from AuditLog log, Payment payment
where log.item.class = 'Payment' and log.item.id = payment.id
注意,在上面的查詢與句中,log.item.class 和 payment.class 將涉及到徹底不一樣的數據庫中的
列。
表達式
269
16.10. 表達式
在 where 子句中容許使用的表達式包括 大多數你能夠在 SQL 使用的表達式種類:
•數學運算符 +,-,*,/
•二進制比較運算符 =, >=, <=, <>, !=, like
•邏輯運算符 and,or,not
•括號 ( ),表示分組
•in, not in, between, is null, is not null, is empty, is not empty, member of and not member of
•"Simple" case, case ... when ... then ... else ... end, and "searched" case, case when ...
then ... else ... end
•字符串鏈接符 ...||... or concat(...,...)
•current_date(), current_time(), and current_timestamp()
•second(...)、minute(...)、hour(...)、day(...)、month(...) 和 year(...)
•EJB-QL 3.0 定義的任何功能或操做符:substring(), trim(), lower(), upper(), length(),
locate(), abs(), sqrt(), bit_length(), mod()
•coalesce() 和 nullif()
•str() 把數字或者時間值轉換爲可讀的字符串
•cast(... as ...),其第二個參數是某 Hibernate 類型的名字,以及 extract(... from ...),只
要 ANSI cast() 和 extract() 被底層數據庫支持
•HQL index() 函數,做用於 join 的有序集合的別名。
•HQL 函數,把集合做爲參數:size(), minelement(), maxelement(), minindex(), maxindex(),還
有特別的 elements() 和 indices 函數,能夠與數量詞加以限定:some, all, exists, any, in。
•任何數據庫支持的 SQL 標量函數,好比 sign(), trunc(), rtrim(), sin()
•JDBC 風格的參數傳入 ?
•命名參數 :name,:start_date,:x1
•SQL 直接常量 'foo', 69, 6.66E+2, '1970-01-01 10:00:01.0'
•Java public static final 類型的常量 eg.Color.TABBY
關鍵字 in 與 between 可按以下方法使用:
from DomesticCat cat where cat.name between 'A' and 'B'
from DomesticCat cat where cat.name in ( 'Foo', 'Bar', 'Baz' )
並且否認的格式也能夠以下書寫:
from DomesticCat cat where cat.name not between 'A' and 'B'
from DomesticCat cat where cat.name not in ( 'Foo', 'Bar', 'Baz' )
第 16 章 HQL: Hibernate 查詢語言
270
一樣,子句 is null 與 is not null 能夠被用來測試空值(null)。
在 Hibernate 配置文件中聲明 HQL「查詢替代(query substitutions)」以後,布爾表達式
(Booleans)能夠在其餘表達式中輕鬆的使用:
<property name="hibernate.query.substitutions"
>true 1, false 0</property
>
系統將該 HQL 轉換爲 SQL 語句時,該設置代表將用字符 1 和 0 來取代關鍵字 true 和 false:
from Cat cat where cat.alive = true
你能夠用特殊屬性 size,或是特殊函數 size() 測試一個集合的大小。
from Cat cat where cat.kittens.size
> 0
from Cat cat where size(cat.kittens)
> 0
對於索引了(有序)的集合,你能夠使用 minindex 與 maxindex 函數來引用到最小與最大的索引
序數。同理,你能夠使用 minelement 與 maxelement 函數來引用到一個基本數據類型的集合中最
小與最大的元素。例如:
from Calendar cal where maxelement(cal.holidays)
> current_date
from Order order where maxindex(order.items)
> 100
from Order order where minelement(order.items)
> 10000
在傳遞一個集合的索引集或者是元素集(elements 與 indices 函數)或者傳遞一個子查詢的結果
的時候,能夠使用 SQL 函數 any, some,all, exists, in:
select mother from Cat as mother, Cat as kit
表達式
271
where kit in elements(foo.kittens)
select p from NameList list, Person p
where p.name = some elements(list.names)
from Cat cat where exists elements(cat.kittens)
from Player p where 3
> all elements(p.scores)
from Show show where 'fizard' in indices(show.acts)
注意,在 Hibernate3 中,這些結構變量 —
size,elements,indices,minindex,maxindex,minelement,maxelement — 只能在 where 子句中
使用。
一個被索引過的(有序的)集合的元素(arrays,lists,maps)能夠在其餘索引中被引用(只能
在 where 子句中):
from Order order where order.items[0].id = 1234
select person from Person person, Calendar calendar
where calendar.holidays['national day'] = person.birthDay
and person.nationality.calendar = calendar
select item from Item item, Order order
where order.items[ order.deliveredItemIndices[0] ] = item and order.id = 11
select item from Item item, Order order
where order.items[ maxindex(order.items) ] = item and order.id = 11
在 [] 中的表達式甚至能夠是一個算數表達式:
select item from Item item, Order order
where order.items[ size(order.items) - 1 ] = item
第 16 章 HQL: Hibernate 查詢語言
272
對於一個一對多的關聯(one-to-many association)或是值的集合中的元素,HQL 也提供內建的
index() 函數。
select item, index(item) from Order order
join order.items item
where index(item) < 5
若是底層數據庫支持標量的 SQL 函數,它們也能夠被使用:
from DomesticCat cat where upper(cat.name) like 'FRI%'
若是你還不能對全部的這些深信不疑,想一想下面的查詢。若是使用 SQL,語句長度會增加多少,
可讀性會降低多少:
select cust
from Product prod,
Store store
inner join store.customers cust
where prod.name = 'widget'
and store.location.name in ( 'Melbourne', 'Sydney' )
and prod = all elements(cust.currentOrder.lineItems)
提示: 會像以下的語句
SELECT cust.name, cust.address, cust.phone, cust.id, cust.current_order
FROM customers cust,
stores store,
locations loc,
store_customers sc,
product prod
WHERE prod.name = 'widget'
AND store.loc_id = loc.id
AND loc.name IN ( 'Melbourne', 'Sydney' )
AND sc.store_id = store.id
AND sc.cust_id = cust.id
AND prod.id = ALL(
SELECT item.prod_id
FROM line_items item, orders o
WHERE item.order_id = o.id
AND cust.current_order = o.id
)
16.11. order by 子句
查詢返回的列表(list)能夠按照一個返回的類或組件(components)中的任何屬性
(property)進行排序:
group by 子句
273
from DomesticCat cat
order by cat.name asc, cat.weight desc, cat.birthdate
可選的 asc 或 desc 關鍵字指明瞭按照升序或降序進行排序。
16.12. group by 子句
一個返回彙集值(aggregate values)的查詢能夠按照一個返回的類或組件(components)中的
任何屬性(property)進行分組:
select cat.color, sum(cat.weight), count(cat)
from Cat cat
group by cat.color
select foo.id, avg(name), max(name)
from Foo foo join foo.names name
group by foo.id
having 子句在這裏也容許使用。
select cat.color, sum(cat.weight), count(cat)
from Cat cat
group by cat.color
having cat.color in (eg.Color.TABBY, eg.Color.BLACK)
若是底層的數據庫支持的話(例如不能在 MySQL 中使用),SQL 的通常函數與彙集函數也能夠出
如今 having 與 order by 子句中。
select cat
from Cat cat
join cat.kittens kitten
group by cat.id, cat.name, cat.other, cat.properties
having avg(kitten.weight)
> 100
order by count(kitten) asc, sum(kitten.weight) desc
注意 group by 子句與 order by 子句中都不能包含算術表達式(arithmetic expressions)。也
要注意 Hibernate 目前不會擴展 group 的實體,所以你不能寫 group by cat,除非 cat 的全部
屬性都不是彙集的(non-aggregated)。你必須明確的列出全部的非彙集屬性。
第 16 章 HQL: Hibernate 查詢語言
274
16.13. 子查詢
對於支持子查詢的數據庫,Hibernate 支持在查詢中使用子查詢。一個子查詢必須被圓括號包圍
起來(常常是 SQL 彙集函數的圓括號)。甚至相互關聯的子查詢(引用到外部查詢中的別名的子
查詢)也是容許的。
from Cat as fatcat
where fatcat.weight
> (
select avg(cat.weight) from DomesticCat cat
)
from DomesticCat as cat
where cat.name = some (
select name.nickName from Name as name
)
from Cat as cat
where not exists (
from Cat as mate where mate.mate = cat
)
from DomesticCat as cat
where cat.name not in (
select name.nickName from Name as name
)
select cat.id, (select max(kit.weight) from cat.kitten kit)
from Cat as cat
注意,HQL 自查詢只能夠在 select 或者 where 子句中出現。
Note that subqueries can also utilize row value constructor syntax. See 第 16.18 節 「Row
value 構造函數語法」 for more information.
16.14. HQL 示例
Hibernate 查詢能夠很是的強大與複雜。實際上,Hibernate 的一個主要賣點就是查詢語句的威
力。這裏有一些例子,它們與我在最近的一個項目中使用的查詢很是類似。注意你能用到的大多
數查詢比這些要簡單的多。
HQL 示例
275
下面的查詢對於某個特定的客戶的全部未支付的帳單,在給定給最小總價值的狀況下,返回訂單
的 id,條目的數量和總價值,返回值按照總價值的結果進行排序。爲了決訂價格,查詢使用了當
前目錄。做爲轉換結果的 SQL 查詢,使用了ORDER,ORDER_LINE,PRODUCT,CATALOG 和 PRICE 庫表。
select order.id, sum(price.amount), count(item)
from Order as order
join order.lineItems as item
join item.product as product,
Catalog as catalog
join catalog.prices as price
where order.paid = false
and order.customer = :customer
and price.product = product
and catalog.effectiveDate < sysdate
and catalog.effectiveDate
>= all (
select cat.effectiveDate
from Catalog as cat
where cat.effectiveDate < sysdate
)
group by order
having sum(price.amount)
> :minAmount
order by sum(price.amount) desc
這簡直是一個怪物!實際上,在現實生活中,我並不熱衷於子查詢,因此個人查詢語句看起來更
像這個:
select order.id, sum(price.amount), count(item)
from Order as order
join order.lineItems as item
join item.product as product,
Catalog as catalog
join catalog.prices as price
where order.paid = false
and order.customer = :customer
and price.product = product
and catalog = :currentCatalog
group by order
having sum(price.amount)
> :minAmount
order by sum(price.amount) desc
下面一個查詢計算每一種狀態下的支付的數目,除去全部處於 AWAITING_APPROVAL 狀態的支付,因
爲在該狀態下 當前的用戶做出了狀態的最新改變。該查詢被轉換成含有兩個內鏈接以及一個相關
聯的子選擇的 SQL 查詢,該查詢使用了表 PAYMENT,PAYMENT_STATUS 以及 PAYMENT_STATUS_CHANGE。
select count(payment), status.name
from Payment as payment
join payment.currentStatus as status
join payment.statusChanges as statusChange
第 16 章 HQL: Hibernate 查詢語言
276
where payment.status.name <
> PaymentStatus.AWAITING_APPROVAL
or (
statusChange.timeStamp = (
select max(change.timeStamp)
from PaymentStatusChange change
where change.payment = payment
)
and statusChange.user <
> :currentUser
)
group by status.name, status.sortOrder
order by status.sortOrder
若是我把 statusChanges 實例集映射爲一個列表(list)而不是一個集合(set),書寫查詢語句
將更加簡單。
select count(payment), status.name
from Payment as payment
join payment.currentStatus as status
where payment.status.name <
> PaymentStatus.AWAITING_APPROVAL
or payment.statusChanges[ maxIndex(payment.statusChanges) ].user <
> :currentUser
group by status.name, status.sortOrder
order by status.sortOrder
下面一個查詢使用了 MS SQL Server 的 isNull() 函數用以返回當前用戶所屬組織的組織賬號及組
織未支付的帳。它被轉換成一個對錶
ACCOUNT,PAYMENT,PAYMENT_STATUS,ACCOUNT_TYPE,ORGANIZATION 以及 ORG_USER 進行的三個內連
接,一個外鏈接和一個子選擇的 SQL 查詢。
select account, payment
from Account as account
left outer join account.payments as payment
where :currentUser in elements(account.holder.users)
and PaymentStatus.UNPAID = isNull(payment.currentStatus.name, PaymentStatus.UNPAID)
order by account.type.sortOrder, account.accountNumber, payment.dueDate
對於一些數據庫,咱們須要棄用(相關的)子選擇。
select account, payment
from Account as account
join account.holder.users as user
left outer join account.payments as payment
where :currentUser = user
and PaymentStatus.UNPAID = isNull(payment.currentStatus.name, PaymentStatus.UNPAID)
order by account.type.sortOrder, account.accountNumber, payment.dueDate
批量的 UPDATE 和 DELETE
277
16.15. 批量的 UPDATE 和 DELETE
HQL now supports update, delete and insert ... select ... statements. See 第  15.4 節
「DML(數據操做語言)風格的操做(DML-style operations)」 for more information.
16.16. 小技巧 & 小竅門
你能夠統計查詢結果的數目而沒必要實際的返回他們:
( (Integer) session.createQuery("select count(*) from ....").iterate().next() ).intValue()
若想根據一個集合的大小來進行排序,能夠使用以下的語句:
select usr.id, usr.name
from User as usr
left join usr.messages as msg
group by usr.id, usr.name
order by count(msg)
若是你的數據庫支持子選擇,你能夠在你的查詢的 where 子句中爲選擇的大小(selection
size)指定一個條件:
from User usr where size(usr.messages)
>= 1
若是你的數據庫不支持子選擇語句,使用下面的查詢:
select usr.id, usr.name
from User usr
join usr.messages msg
group by usr.id, usr.name
having count(msg)
>= 1
由於內鏈接(inner join)的緣由,這個解決方案不能返回含有零個信息的 User 類的實例,因此
這種狀況下使用下面的格式將是有幫助的:
select usr.id, usr.name
from User as usr
left join usr.messages as msg
group by usr.id, usr.name
having count(msg) = 0
第 16 章 HQL: Hibernate 查詢語言
278
JavaBean 的屬性能夠被綁定到一個命名查詢(named query)的參數上:
Query q = s.createQuery("from foo Foo as foo where foo.name=:name and foo.size=:size");
q.setProperties(fooBean); // fooBean has getName() and getSize()
List foos = q.list();
經過將接口 Query 與一個過濾器(filter)一塊兒使用,集合(Collections)是能夠分頁的:
Query q = s.createFilter( collection, "" ); // the trivial filter
q.setMaxResults(PAGE_SIZE);
q.setFirstResult(PAGE_SIZE * pageNumber);
List page = q.list();
經過使用查詢過濾器(query filter)能夠將集合(Collection)的元素分組或排序:
Collection orderedCollection = s.filter( collection, "order by this.amount" );
Collection counts = s.filter( collection, "select this.type, count(this) group by this.type" );
不用經過初始化,你就能夠知道一個集合(Collection)的大小:
( (Integer) session.createQuery("select count(*) from ....").iterate().next() ).intValue();
16.17. 組件
在 HQL 查詢裏,組件能夠和簡單值類型同樣使用。它們能夠出如今 select 子句裏:
select p.name from Person p
select p.name.first from Person p
在這裏,Person 的 name 屬性是一個組件。組件也能夠用在 where 子句裏:
from Person p where p.name = :name
from Person p where p.name.first = :firstName
組件也能夠用在 order by 子句裏:
Row value 構造函數語法
279
from Person p order by p.name
from Person p order by p.name.first
組件的另一個常見用法是在 第  16.18 節 「Row value 構造函數語法」 行值(row value)
構造函數裏。
16.18. Row value 構造函數語法
HQL 支持 ANSI SQL row value constructor 語法(有時也叫
做 tuple 語法),即便底層數據庫可能不支持這個概念。在這裏咱們一般指的是多值(multivalued)的比較,典型地是和組件相關聯。來看看一個定義了
name 組件的實體 Person:
from Person p where p.name.first='John' and p.name.last='Jingleheimer-Schmidt'
那是有效的語法,雖然有點冗長。咱們能夠使它更加簡潔一點,並使用 row value constructor 語
法:
from Person p where p.name=('John', 'Jingleheimer-Schmidt')
在 select 子句裏指定這個也是頗有用的:
select p.name from Person p
當使用須要比較多個值的子查詢時,採用 row value constructor 語法也頗有用處:
from Cat as cat
where not ( cat.name, cat.color ) in (
select cat.name, cat.color from DomesticCat cat
)
決定是否使用這個語法的一件因素就是:這個查詢將依賴於元數據裏的組件子屬性(subproperties)的順序。
280
第 17
281
條件查詢(Criteria Queries)
具備一個直觀的、可擴展的條件查詢 API 是 Hibernate 的特點。
17.1. 建立一個 Criteria 實例
org.hibernate.Criteria接口表示特定持久類的一個查詢。Session 是 Criteria 實例的工廠。
Criteria crit = sess.createCriteria(Cat.class);
crit.setMaxResults(50);
List cats = crit.list();
17.2. 限制結果集內容
一個單獨的查詢條件是 org.hibernate.criterion.Criterion 接口的一個實
例。org.hibernate.criterion.Restrictions 類定義了得到某些內置 Criterion 類型的工廠方法。
List cats = sess.createCriteria(Cat.class)
.add( Restrictions.like("name", "Fritz%") )
.add( Restrictions.between("weight", minWeight, maxWeight) )
.list();
約束能夠按邏輯分組。
List cats = sess.createCriteria(Cat.class)
.add( Restrictions.like("name", "Fritz%") )
.add( Restrictions.or(
Restrictions.eq( "age", new Integer(0) ),
Restrictions.isNull("age")
) )
.list();
List cats = sess.createCriteria(Cat.class)
.add( Restrictions.in( "name", new String[] { "Fritz", "Izi", "Pk" } ) )
.add( Restrictions.disjunction()
.add( Restrictions.isNull("age") )
.add( Restrictions.eq("age", new Integer(0) ) )
.add( Restrictions.eq("age", new Integer(1) ) )
.add( Restrictions.eq("age", new Integer(2) ) )
) )
.list();
Hibernate 提供了至關多的內置 criterion 類型(Restrictions 子類),可是尤爲有用的是能夠
容許你直接使用 SQL。
第 17 章 條件查詢(Criteria Queries)
282
List cats = sess.createCriteria(Cat.class)
.add( Restrictions.sqlRestriction("lower({alias}.name) like lower(?)", "Fritz
%", Hibernate.STRING) )
.list();
{alias} 佔位符應當被替換爲被查詢實體的列別名。
Property 實例是得到一個條件的另一種途徑。你能夠經過調用 Property.forName() 建立一個
Property:
Property age = Property.forName("age");
List cats = sess.createCriteria(Cat.class)
.add( Restrictions.disjunction()
.add( age.isNull() )
.add( age.eq( new Integer(0) ) )
.add( age.eq( new Integer(1) ) )
.add( age.eq( new Integer(2) ) )
) )
.add( Property.forName("name").in( new String[] { "Fritz", "Izi", "Pk" } ) )
.list();
17.3. 結果集排序
你能夠使用 org.hibernate.criterion.Order 來爲查詢結果排序。
List cats = sess.createCriteria(Cat.class)
.add( Restrictions.like("name", "F%")
.addOrder( Order.asc("name") )
.addOrder( Order.desc("age") )
.setMaxResults(50)
.list();
List cats = sess.createCriteria(Cat.class)
.add( Property.forName("name").like("F%") )
.addOrder( Property.forName("name").asc() )
.addOrder( Property.forName("age").desc() )
.setMaxResults(50)
.list();
17.4. 關聯
經過使用 createCriteria() 對關聯進行導航,你能夠指定相關實體的約束。
List cats = sess.createCriteria(Cat.class)
關聯
283
.add( Restrictions.like("name", "F%") )
.createCriteria("kittens")
.add( Restrictions.like("name", "F%") )
.list();
注意第二個 createCriteria() 返回一個新的 Criteria 實例,該實例引用 kittens 集合中的元素。
接下來,替換形態在某些狀況下也是頗有用的。
List cats = sess.createCriteria(Cat.class)
.createAlias("kittens", "kt")
.createAlias("mate", "mt")
.add( Restrictions.eqProperty("kt.name", "mt.name") )
.list();
(createAlias() 並不建立一個新的 Criteria 實例。)
Cat 實例所保存的以前兩次查詢所返回的 kittens 集合是 沒有被條件預過濾的。若是你但願只獲
得符合條件的 kittens,你必須使用 ResultTransformer。
List cats = sess.createCriteria(Cat.class)
.createCriteria("kittens", "kt")
.add( Restrictions.eq("name", "F%") )
.setResultTransformer(Criteria.ALIAS_TO_ENTITY_MAP)
.list();
Iterator iter = cats.iterator();
while ( iter.hasNext() ) {
Map map = (Map) iter.next();
Cat cat = (Cat) map.get(Criteria.ROOT_ALIAS);
Cat kitten = (Cat) map.get("kt");
}
此外,你能夠用一個 left outer join 來操縱結果集:
List cats = session.createCriteria( Cat.class )
.createAlias("mate", "mt", Criteria.LEFT_JOIN, Restrictions.like("mt.name",
"good%") )
.addOrder(Order.asc("mt.age"))
.list();


這將返回配偶的名字以 "good" 起始的全部 Cat,並根據其配偶的年齡進行排序。當須要在返回復
雜/大型結果集前進行排序或限制、在多個查詢必須執行且結果經過 Java 在內存裏組合從而刪除
許多實例時,這頗有用。
若是沒有這個功能,那麼沒有配偶的貓就須要在一次查詢裏進行加載。
第 17 章 條件查詢(Criteria Queries)
284
第二個查詢將須要獲取配偶名以 "good" 起始並按照配偶年齡排序的貓。
第三點,列表須要在內存中進行手工聯合。
17.5. 動態關聯抓取
你能夠使用 setFetchMode() 在運行時定義動態關聯抓取的語義。
List cats = sess.createCriteria(Cat.class)
.add( Restrictions.like("name", "Fritz%") )
.setFetchMode("mate", FetchMode.EAGER)
.setFetchMode("kittens", FetchMode.EAGER)
.list();
This query will fetch both mate and kittens by outer join. See 第 21.1 節 「抓取策略
(Fetching strategies)」 for more information.
17.6. 查詢示例
org.hibernate.criterion.Example 類容許你經過一個給定實例構建一個條件查詢。
Cat cat = new Cat();
cat.setSex('F');
cat.setColor(Color.BLACK);
List results = session.createCriteria(Cat.class)
.add( Example.create(cat) )
.list();
版本屬性、標識符和關聯被忽略。默認狀況下值爲 null 的屬性將被排除。
你能夠自行調整 Example 使之更實用。
Example example = Example.create(cat)
.excludeZeroes() //exclude zero valued properties
.excludeProperty("color") //exclude the property named "color"
.ignoreCase() //perform case insensitive string comparisons
.enableLike(); //use like for string comparisons
List results = session.createCriteria(Cat.class)
.add(example)
.list();
你甚至能夠使用 examples 在關聯對象上放置條件。
List results = session.createCriteria(Cat.class)
.add( Example.create(cat) )
.createCriteria("mate")
.add( Example.create( cat.getMate() ) )
投影(Projections)、聚合(aggregation)和分組(grouping)
285
.list();
17.7. 投影(Projections)、聚合(aggregation)和分組
(grouping)
org.hibernate.criterion.Projections 是 Projection 的實例工廠。咱們經過調用 setProjection()
應用投影到一個查詢。
List results = session.createCriteria(Cat.class)
.setProjection( Projections.rowCount() )
.add( Restrictions.eq("color", Color.BLACK) )
.list();
List results = session.createCriteria(Cat.class)
.setProjection( Projections.projectionList()
.add( Projections.rowCount() )
.add( Projections.avg("weight") )
.add( Projections.max("weight") )
.add( Projections.groupProperty("color") )
)
.list();
在一個條件查詢中沒有必要顯式的使用 "group by" 。某些投影類型就是被定義爲分組投影,他
們也出如今 SQL 的 group by 子句中。
你能夠選擇把一個別名指派給一個投影,這樣能夠使投影值被約束或排序所引用。下面是兩種不
同的實現方式:
List results = session.createCriteria(Cat.class)
.setProjection( Projections.alias( Projections.groupProperty("color"), "colr" ) )
.addOrder( Order.asc("colr") )
.list();
List results = session.createCriteria(Cat.class)
.setProjection( Projections.groupProperty("color").as("colr") )
.addOrder( Order.asc("colr") )
.list();
alias() 和 as() 方法簡便的將一個投影實例包裝到另一個 別名的 Projection 實例中。簡而言
之,當你添加一個投影到一個投影列表中時你能夠爲它指定一個別名:
List results = session.createCriteria(Cat.class)
.setProjection( Projections.projectionList()
第 17 章 條件查詢(Criteria Queries)
286
.add( Projections.rowCount(), "catCountByColor" )
.add( Projections.avg("weight"), "avgWeight" )
.add( Projections.max("weight"), "maxWeight" )
.add( Projections.groupProperty("color"), "color" )
)
.addOrder( Order.desc("catCountByColor") )
.addOrder( Order.desc("avgWeight") )
.list();
List results = session.createCriteria(Domestic.class, "cat")
.createAlias("kittens", "kit")
.setProjection( Projections.projectionList()
.add( Projections.property("cat.name"), "catName" )
.add( Projections.property("kit.name"), "kitName" )
)
.addOrder( Order.asc("catName") )
.addOrder( Order.asc("kitName") )
.list();
你也能夠使用 Property.forName() 來表示投影:
List results = session.createCriteria(Cat.class)
.setProjection( Property.forName("name") )
.add( Property.forName("color").eq(Color.BLACK) )
.list();
List results = session.createCriteria(Cat.class)
.setProjection( Projections.projectionList()
.add( Projections.rowCount().as("catCountByColor") )
.add( Property.forName("weight").avg().as("avgWeight") )
.add( Property.forName("weight").max().as("maxWeight") )
.add( Property.forName("color").group().as("color" )
)
.addOrder( Order.desc("catCountByColor") )
.addOrder( Order.desc("avgWeight") )
.list();
17.8. 離線(detached)查詢和子查詢
DetachedCriteria 類使你在一個 session 範圍以外建立一個查詢,而且能夠使用任意的 Session
來執行它。
DetachedCriteria query = DetachedCriteria.forClass(Cat.class)
.add( Property.forName("sex").eq('F') );
Session session = ....;
Transaction txn = session.beginTransaction();
List results = query.getExecutableCriteria(session).setMaxResults(100).list();
根據天然標識查詢(Queries by natural identifier)
287
txn.commit();
session.close();
DetachedCriteria 也能夠用以表示子查詢。條件實例包含子查詢能夠經過 Subqueries 或者
Property 得到。
DetachedCriteria avgWeight = DetachedCriteria.forClass(Cat.class)
.setProjection( Property.forName("weight").avg() );
session.createCriteria(Cat.class)
.add( Property.forName("weight").gt(avgWeight) )
.list();
DetachedCriteria weights = DetachedCriteria.forClass(Cat.class)
.setProjection( Property.forName("weight") );
session.createCriteria(Cat.class)
.add( Subqueries.geAll("weight", weights) )
.list();
甚至相互關聯的子查詢也是有可能的:
DetachedCriteria avgWeightForSex = DetachedCriteria.forClass(Cat.class, "cat2")
.setProjection( Property.forName("weight").avg() )
.add( Property.forName("cat2.sex").eqProperty("cat.sex") );
session.createCriteria(Cat.class, "cat")
.add( Property.forName("weight").gt(avgWeightForSex) )
.list();
17.9. 根據天然標識查詢(Queries by natural
identifier)
對大多數查詢,包括條件查詢而言,由於查詢緩存的失效(invalidation)發生得太頻繁,查詢
緩存不是很是高效。然而,有一種特別的查詢,能夠經過不變的天然鍵優化緩存的失效算法。在
某些應用中,這種類型的查詢比較常見。條件查詢 API 對這種用例提供了特別規約。
首先,你應該對你的 entity 使用 <natural-id> 來映射天然鍵,而後打開第二級緩存。
<class name="User">
<cache usage="read-write"/>
<id name="id">
<generator class="increment"/>
</id>
<natural-id>
<property name="name"/>
<property name="org"/>
</natural-id>
<property name="password"/>
第 17 章 條件查詢(Criteria Queries)
288
</class
>
注意,此功能對具備mutable天然鍵的 entity 並不適用。
如今,咱們能夠用 Restrictions.naturalId() 來使用更加高效的緩存算法。
session.createCriteria(User.class)
.add( Restrictions.naturalId()
.set("name", "gavin")
.set("org", "hb")
).setCacheable(true)
.uniqueResult();
第 18
289
Native SQL 查詢
你也能夠使用你的數據庫的 Native SQL 語言來查詢數據。這對你在要使用數據庫的某些特性的
時候(好比說在查詢提示或者 Oracle 中的 CONNECT 關鍵字),這是很是有用的。這就可以掃清
你把原來直接使用 SQL/JDBC 的程序遷移到基於 Hibernate 應用的道路上的障礙。
Hibernate3 容許你使用手寫的 sql 來完成全部的 create、update、delete 和 load 操做(包
括存儲過程)
18.1. 使用 SQLQuery
對原生 SQL 查詢執行的控制是經過 SQLQuery 接口進行的,經過執行Session.createSQLQuery()獲取
這個接口。下面來描述如何使用這個 API 進行查詢。
18.1.1. 標量查詢(Scalar queries)
最基本的 SQL 查詢就是得到一個標量(數值)的列表。
sess.createSQLQuery("SELECT * FROM CATS").list();
sess.createSQLQuery("SELECT ID, NAME, BIRTHDATE FROM CATS").list();
它們都將返回一個 Object 數組(Object[])組成的 List,數組每一個元素都是 CATS 表的一個字
段值。Hibernate 會使用 ResultSetMetadata 來斷定返回的標量值的實際順序和類型。
若是要避免過多的使用 ResultSetMetadata,或者只是爲了更加明確的指名返回值,能夠使用
addScalar():
sess.createSQLQuery("SELECT * FROM CATS")
.addScalar("ID", Hibernate.LONG)
.addScalar("NAME", Hibernate.STRING)
.addScalar("BIRTHDATE", Hibernate.DATE)
這個查詢指定:
•SQL 查詢字符串
•要返回的字段和類型
它仍然會返回 Object 數組,可是此時再也不使用 ResultSetMetdata,而是明確的將 ID,NAME 和
BIRTHDATE 按照 Long,String 和 Short 類型從 resultset 中取出。同時,也指明瞭就算 query
是使用 * 來查詢的,可能得到超過列出的這三個字段,也僅僅會返回這三個字段。
對所有或者部分的標量值不設置類型信息也是能夠的。
sess.createSQLQuery("SELECT * FROM CATS")
第 18 章 Native SQL 查詢
290
.addScalar("ID", Hibernate.LONG)
.addScalar("NAME")
.addScalar("BIRTHDATE")
基本上這和前面一個查詢相同,只是此時使用 ResultSetMetaData 來決定 NAME 和 BIRTHDATE 的類
型,而 ID 的類型是明確指出的。
關於從 ResultSetMetaData 返回的 java.sql.Types 是如何映射到
Hibernate 類型,是由方言(Dialect)控制的。倘若某個指定的類型沒有被映射,或者不是你所
預期的類型,你能夠經過 Dialet 的 registerHibernateType 調用自行定義。
18.1.2. 實體查詢(Entity queries)
上面的查詢都是返回標量值的,也就是從 resultset 中返回的「裸」數據。下面展現如何經過
addEntity() 讓原生查詢返回實體對象。
sess.createSQLQuery("SELECT * FROM CATS").addEntity(Cat.class);
sess.createSQLQuery("SELECT ID, NAME, BIRTHDATE FROM CATS").addEntity(Cat.class);
這個查詢指定:
•SQL 查詢字符串
•要返回的實體
假設 Cat 被映射爲擁有 ID,NAME 和 BIRTHDATE 三個字段的類,以上的兩個查詢都返回一個
List,每一個元素都是一個 Cat 實體。
倘若實體在映射時有一個 many-to-one 的關聯指向另一個實體,在查詢時必須也返回那個實
體,不然會致使發生一個 "column not found" 的數據庫錯誤。這些附加的字段能夠使用 * 標註
來自動返回,但咱們但願仍是明確指明,看下面這個具備指向 Dog 的 many-to-one 的例子:
sess.createSQLQuery("SELECT ID, NAME, BIRTHDATE, DOG_ID FROM CATS").addEntity(Cat.class);
這樣 cat.getDog() 就能正常運做。
18.1.3. 處理關聯和集合類(Handling associations and
collections)
經過提早抓取將 Dog 鏈接得到,而避免初始化 proxy 帶來的額外開銷也是可能的。這是經過
addJoin() 方法進行的,這個方法可讓你將關聯或集合鏈接進來。
sess.createSQLQuery("SELECT c.ID, NAME, BIRTHDATE, DOG_ID, D_ID, D_NAME FROM CATS c, DOGS d
WHERE c.DOG_ID = d.D_ID")
返回多個實體(Returning multiple entities)
291
.addEntity("cat", Cat.class)
.addJoin("cat.dog");
上面這個例子中,返回的 Cat 對象,其 dog 屬性被徹底初始化了,再也不須要數據庫的額外操做。
注意,咱們加了一個別名("cat"),以便指明 join 的目標屬性路徑。經過一樣的提早鏈接也可
以做用於集合類,例如,倘若 Cat 有一個指向 Dog 的一對多關聯。
sess.createSQLQuery("SELECT ID, NAME, BIRTHDATE, D_ID, D_NAME, CAT_ID FROM CATS c, DOGS d WHERE
c.ID = d.CAT_ID")
.addEntity("cat", Cat.class)
.addJoin("cat.dogs");
到此爲止,咱們碰到了天花板:若不對 SQL 查詢進行加強,這些已是在 Hibernate 中使用原
生 SQL 查詢所能作到的最大可能了。下面的問題即將出現:返回多個一樣類型的實體怎麼辦?或
者默認的別名/字段不夠又怎麼辦?
18.1.4. 返回多個實體(Returning multiple entities)
到目前爲止,結果集字段名被假定爲和映射文件中指定的的字段名是一致的。倘若 SQL 查詢鏈接
了多個表,同一個字段名可能在多個表中出現屢次,這就會形成問題。
下面的查詢中須要使用字段別名注射(這個例子自己會失敗):
sess.createSQLQuery("SELECT c.*, m.* FROM CATS c, CATS m WHERE c.MOTHER_ID = m.ID")
.addEntity("cat", Cat.class)
.addEntity("mother", Cat.class)
這個查詢的本意是但願每行返回兩個 Cat 實例,一個是 cat,另外一個是它的媽媽。可是由於它們
的字段名被映射爲相同的,並且在某些數據庫中,返回的字段別名是「c.ID」,"c.NAME" 這樣的
形式,而它們和在映射文件中的名字("ID" 和 "NAME")不匹配,這就會形成失敗。
下面的形式能夠解決字段名重複:
sess.createSQLQuery("SELECT {cat.*}, {m.*} FROM CATS c, CATS m WHERE c.MOTHER_ID = m.ID")
.addEntity("cat", Cat.class)
.addEntity("mother", Cat.class)
這個查詢指定:
•SQL 查詢語句,其中包含佔位附來讓 Hibernate 注射字段別名
•查詢返回的實體
上面使用的 {cat.*} 和 {mother.*} 標記是做爲「全部屬性」的簡寫形式出現的。
固然你也能夠明確地羅列出字段名,但在這個例子裏面咱們讓 Hibernate 來爲每一個屬性注射
第 18 章 Native SQL 查詢
292
SQL 字段別名。字段別名的佔位符是屬性名加上表別名的前綴。在下面的例子中,咱們從另一
個表(cat_log)中經過映射元數據中的指定獲取 Cat 和它的媽媽。注意,要是咱們願意,咱們
甚至能夠在 where 子句中使用屬性別名。
String sql = "SELECT ID as {c.id}, NAME as {c.name}, " +
"BIRTHDATE as {c.birthDate}, MOTHER_ID as {c.mother}, {mother.*} " +
"FROM CAT_LOG c, CAT_LOG m WHERE {c.mother} = c.ID";
List loggedCats = sess.createSQLQuery(sql)
.addEntity("cat", Cat.class)
.addEntity("mother", Cat.class).list()
18.1.4.1. 別名和屬性引用(Alias and property references)
大多數狀況下,都須要上面的屬性注射,但在使用更加複雜的映射,好比複合屬性、經過標識符
構造繼承樹,以及集合類等等狀況下,也有一些特別的別名,來容許 Hibernate 注入合適的別
名。
下表列出了使用別名注射參數的不一樣可能性。注意:下面結果中的別名只是示例,實用時每一個別
名須要惟一而且不一樣的名字。
表 18.1. 別名注射(alias injection names)
描述 語法 示例
簡單屬性 {[aliasname].
[propertyname]
A_NAME as {item.name}
複合屬性 {[aliasname].
[componentname].
[propertyname]}
CURRENCY as {item.amount.currency}, VALUE as
{item.amount.value}
實體辨別器
(Discriminator
of an entity)
{[aliasname].class} DISC as {item.class}
實體的全部屬性 {[aliasname].*} {item.*}
集合鍵
(collection
key)
{[aliasname].key} ORGID as {coll.key}
集合 id {[aliasname].id} EMPID as {coll.id}
集合元素 {[aliasname].element}XID as {coll.element}
集合元素的屬性 {[aliasname].element.
[propertyname]}
NAME as {coll.element.name}
集合元素的全部屬

{[aliasname].element.*}{coll.element.*}
集合的全部屬性 {[aliasname].*} {coll.*}
返回非受管實體(Returning non-managed entities)
293
18.1.5. 返回非受管實體(Returning non-managed entities)
能夠對原生 sql 查詢使用 ResultTransformer。這會返回不受 Hibernate 管理的實體。
sess.createSQLQuery("SELECT NAME, BIRTHDATE FROM CATS")
.setResultTransformer(Transformers.aliasToBean(CatDTO.class))
這個查詢指定:
•SQL 查詢字符串
•結果轉換器(result transformer)
上面的查詢將會返回 CatDTO 的列表,它將被實例化而且將 NAME 和 BIRTHDAY 的值注射入對應的
屬性或者字段。
18.1.6. 處理繼承(Handling inheritance)
原生 SQL 查詢倘若其查詢結果實體是繼承樹中的一部分,它必須包含基類和全部子類的全部屬
性。
18.1.7. 參數(Parameters)
原生查詢支持位置參數和命名參數:
Query query = sess.createSQLQuery("SELECT * FROM CATS WHERE NAME like ?").addEntity(Cat.class);
List pusList = query.setString(0, "Pus%").list();
query = sess.createSQLQuery("SELECT * FROM CATS WHERE NAME like :name").addEntity(Cat.class);
List pusList = query.setString("name", "Pus%").list();
18.2. 命名 SQL 查詢
Named SQL queries can also be defined in the mapping document and called in exactly
the same way as a named HQL query (see 第 11.4.1.7 節 「外置命名查詢(Externalizing
named queries)」). In this case, you do not need to call addEntity().
例 18.1. Named sql query using the <sql-query> maping element
<sql-query name="persons">
<return alias="person" class="eg.Person"/>
SELECT person.NAME AS {person.name},
person.AGE AS {person.age},
person.SEX AS {person.sex}
第 18 章 Native SQL 查詢
294
FROM PERSON person
WHERE person.NAME LIKE :namePattern
</sql-query>
例 18.2. Execution of a named query
List people = sess.getNamedQuery("persons")
.setString("namePattern", namePattern)
.setMaxResults(50)
.list();
<return-join> 和 <load-collection> 元素是用來鏈接關聯以及將查詢定義爲預先初始化各個集合
的。
例 18.3. Named sql query with association
<sql-query name="personsWith">
<return alias="person" class="eg.Person"/>
<return-join alias="address" property="person.mailingAddress"/>
SELECT person.NAME AS {person.name},
person.AGE AS {person.age},
person.SEX AS {person.sex},
address.STREET AS {address.street},
address.CITY AS {address.city},
address.STATE AS {address.state},
address.ZIP AS {address.zip}
FROM PERSON person
JOIN ADDRESS address
ON person.ID = address.PERSON_ID AND address.TYPE='MAILING'
WHERE person.NAME LIKE :namePattern
</sql-query>
一個命名查詢可能會返回一個標量值。你必須使用 <return-scalar> 元素來指定字段的別名和
Hibernate 類型:
例 18.4. Named query returning a scalar
<sql-query name="mySqlQuery">
<return-scalar column="name" type="string"/>
<return-scalar column="age" type="long"/>
SELECT p.NAME AS name,
p.AGE AS age,
FROM PERSON p WHERE p.NAME LIKE 'Hiber%'
</sql-query>
你能夠把結果集映射的信息放在外部的 <resultset> 元素中,這樣就能夠在多個命名查詢間,或
者經過 setResultSetMapping() API 來訪問。
命名 SQL 查詢
295
例 18.5. <resultset> mapping used to externalize mapping information
<resultset name="personAddress">
<return alias="person" class="eg.Person"/>
<return-join alias="address" property="person.mailingAddress"/>
</resultset>
<sql-query name="personsWith" resultset-ref="personAddress">
SELECT person.NAME AS {person.name},
person.AGE AS {person.age},
person.SEX AS {person.sex},
address.STREET AS {address.street},
address.CITY AS {address.city},
address.STATE AS {address.state},
address.ZIP AS {address.zip}
FROM PERSON person
JOIN ADDRESS address
ON person.ID = address.PERSON_ID AND address.TYPE='MAILING'
WHERE person.NAME LIKE :namePattern
</sql-query>
另外,你能夠在 java 代碼中直接使用 hbm 文件中的結果集定義信息。
例 18.6. Programmatically specifying the result mapping information
List cats = sess.createSQLQuery(
"select {cat.*}, {kitten.*} from cats cat, cats kitten where kitten.mother = cat.id"
)
.setResultSetMapping("catAndKitten")
.list();
So far we have only looked at externalizing SQL queries using Hibernate mapping files.
The same concept is also available with anntations and is called named native queries.
You can use @NamedNativeQuery (@NamedNativeQueries) in conjunction with @SqlResultSetMapping
(@SqlResultSetMappings). Like @NamedQuery, @NamedNativeQuery and @SqlResultSetMapping can be
defined at class level, but their scope is global to the application. Lets look at
a view examples.
例 18.7 「Named SQL query using @NamedNativeQuery together with @SqlResultSetMapping」
shows how a resultSetMapping parameter is defined in @NamedNativeQuery. It represents
the name of a defined @SqlResultSetMapping. The resultset mapping declares the entities
retrieved by this native query. Each field of the entity is bound to an SQL alias
(or column name). All fields of the entity including the ones of subclasses and the
foreign key columns of related entities have to be present in the SQL query. Field
definitions are optional provided that they map to the same column name as the one
declared on the class property. In the example 2 entities, Night and Area, are returned
and each property is declared and associated to a column name, actually the column
name retrieved by the query.
第 18 章 Native SQL 查詢
296
In 例 18.8 「Implicit result set mapping」 the result set mapping is implicit. We only
describe the entity class of the result set mapping. The property / column mappings
is done using the entity mapping values. In this case the model property is bound
to the model_txt column.
Finally, if the association to a related entity involve a composite primary key, a
@FieldResult element should be used for each foreign key column. The @FieldResult name is
composed of the property name for the relationship, followed by a dot ("."), followed
by the name or the field or property of the primary key. This can be seen in 例 18.9
「Using dot notation in @FieldResult for specifying associations 」.
例 18.7. Named SQL query using @NamedNativeQuery together with @SqlResultSetMapping
@NamedNativeQuery(name="night&area", query="select night.id nid, night.night_duration, "
+ " night.night_date, area.id aid, night.area_id, area.name "
+ "from Night night, Area area where night.area_id = area.id",
resultSetMapping="joinMapping")
@SqlResultSetMapping(name="joinMapping", entities={
@EntityResult(entityClass=Night.class, fields = {
@FieldResult(name="id", column="nid"),
@FieldResult(name="duration", column="night_duration"),
@FieldResult(name="date", column="night_date"),
@FieldResult(name="area", column="area_id"),
discriminatorColumn="disc"
}),
@EntityResult(entityClass=org.hibernate.test.annotations.query.Area.class, fields = {
@FieldResult(name="id", column="aid"),
@FieldResult(name="name", column="name")
})
}
)
例 18.8. Implicit result set mapping
@Entity
@SqlResultSetMapping(name="implicit",
entities=@EntityResult(entityClass=SpaceShip.class))
@NamedNativeQuery(name="implicitSample",
query="select * from SpaceShip",
resultSetMapping="implicit")
public class SpaceShip {
private String name;
private String model;
private double speed;
@Id
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
命名 SQL 查詢
297
}
@Column(name="model_txt")
public String getModel() {
return model;
}
public void setModel(String model) {
this.model = model;
}
public double getSpeed() {
return speed;
}
public void setSpeed(double speed) {
this.speed = speed;
}
}
例 18.9. Using dot notation in @FieldResult for specifying associations
@Entity
@SqlResultSetMapping(name="compositekey",
entities=@EntityResult(entityClass=SpaceShip.class,
fields = {
@FieldResult(name="name", column = "name"),
@FieldResult(name="model", column = "model"),
@FieldResult(name="speed", column = "speed"),
@FieldResult(name="captain.firstname", column = "firstn"),
@FieldResult(name="captain.lastname", column = "lastn"),
@FieldResult(name="dimensions.length", column = "length"),
@FieldResult(name="dimensions.width", column = "width")
}),
columns = { @ColumnResult(name = "surface"),
@ColumnResult(name = "volume") } )
@NamedNativeQuery(name="compositekey",
query="select name, model, speed, lname as lastn, fname as firstn, length, width, length
* width as surface from SpaceShip",
resultSetMapping="compositekey")
} )
public class SpaceShip {
private String name;
private String model;
private double speed;
private Captain captain;
private Dimensions dimensions;
@Id
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
第 18 章 Native SQL 查詢
298
}
@ManyToOne(fetch= FetchType.LAZY)
@JoinColumns( {
@JoinColumn(name="fname", referencedColumnName = "firstname"),
@JoinColumn(name="lname", referencedColumnName = "lastname")
} )
public Captain getCaptain() {
return captain;
}
public void setCaptain(Captain captain) {
this.captain = captain;
}
public String getModel() {
return model;
}
public void setModel(String model) {
this.model = model;
}
public double getSpeed() {
return speed;
}
public void setSpeed(double speed) {
this.speed = speed;
}
public Dimensions getDimensions() {
return dimensions;
}
public void setDimensions(Dimensions dimensions) {
this.dimensions = dimensions;
}
}
@Entity
@IdClass(Identity.class)
public class Captain implements Serializable {
private String firstname;
private String lastname;
@Id
public String getFirstname() {
return firstname;
}
public void setFirstname(String firstname) {
this.firstname = firstname;
}
@Id
public String getLastname() {
return lastname;
}
使用 return-property 來明確地指定字段/別名
299
public void setLastname(String lastname) {
this.lastname = lastname;
}
}
提示
If you retrieve a single entity using the default mapping, you can specify
the resultClass attribute instead of resultSetMapping:
@NamedNativeQuery(name="implicitSample", query="select * from
SpaceShip", resultClass=SpaceShip.class)
public class SpaceShip {
In some of your native queries, you'll have to return scalar values, for example
when building report queries. You can map them in the @SqlResultsetMapping through
@ColumnResult. You actually can even mix, entities and scalar returns in the same native
query (this is probably not that common though).
例 18.10. Scalar values via @ColumnResult
@SqlResultSetMapping(name="scalar", columns=@ColumnResult(name="dimension"))
@NamedNativeQuery(name="scalar", query="select length*width as dimension from
SpaceShip", resultSetMapping="scalar")
An other query hint specific to native queries has been introduced: org.hibernate.callable
which can be true or false depending on whether the query is a stored procedure or not.
18.2.1. 使用 return-property 來明確地指定字段/別名
使用 <return-property> 你能夠明確的告訴 Hibernate 使用哪些字段別名,這取代了使用 {}-語
法 來讓 Hibernate 注入它本身的別名。例如:
<sql-query name="mySqlQuery">
<return alias="person" class="eg.Person">
<return-property name="name" column="myName"/>
<return-property name="age" column="myAge"/>
<return-property name="sex" column="mySex"/>
</return>
SELECT person.NAME AS myName,
person.AGE AS myAge,
person.SEX AS mySex,
FROM PERSON person WHERE person.NAME LIKE :name
第 18 章 Native SQL 查詢
300
</sql-query>
<return-property> 也可用於多個字段,它解決了使用 {}-語法不能細粒度控制多個字段的限制。
<sql-query name="organizationCurrentEmployments">
<return alias="emp" class="Employment">
<return-property name="salary">
<return-column name="VALUE"/>
<return-column name="CURRENCY"/>
</return-property>
<return-property name="endDate" column="myEndDate"/>
</return>
SELECT EMPLOYEE AS {emp.employee}, EMPLOYER AS {emp.employer},
STARTDATE AS {emp.startDate}, ENDDATE AS {emp.endDate},
REGIONCODE as {emp.regionCode}, EID AS {emp.id}, VALUE, CURRENCY
FROM EMPLOYMENT
WHERE EMPLOYER = :id AND ENDDATE IS NULL
ORDER BY STARTDATE ASC
</sql-query>
注意在這個例子中,咱們使用了 <return-property> 結合 {} 的注入語法。容許用戶來選擇如何引
用字段以及屬性。
若是你映射一個識別器(discriminator),你必須使用 <return-discriminator> 來指定識別器字
段。
18.2.2. 使用存儲過程來查詢
Hibernate 3 引入了對存儲過程查詢(stored procedure)和函數(function)的支持。如下的
說明中,這兩者通常都適用。存儲過程/函數必須返回一個結果集,做爲 Hibernate 可以使用的
第一個外部參數。下面是一個 Oracle9 和更高版本的存儲過程例子。
CREATE OR REPLACE FUNCTION selectAllEmployments
RETURN SYS_REFCURSOR
AS
st_cursor SYS_REFCURSOR;
BEGIN
OPEN st_cursor FOR
SELECT EMPLOYEE, EMPLOYER,
STARTDATE, ENDDATE,
REGIONCODE, EID, VALUE, CURRENCY
FROM EMPLOYMENT;
RETURN st_cursor;
END;
在 Hibernate 裏要要使用這個查詢,你須要經過命名查詢來映射它。
<sql-query name="selectAllEmployees_SP" callable="true">
<return alias="emp" class="Employment">
定製 SQL 用來 create,update 和 delete
301
<return-property name="employee" column="EMPLOYEE"/>
<return-property name="employer" column="EMPLOYER"/>
<return-property name="startDate" column="STARTDATE"/>
<return-property name="endDate" column="ENDDATE"/>
<return-property name="regionCode" column="REGIONCODE"/>
<return-property name="id" column="EID"/>
<return-property name="salary">
<return-column name="VALUE"/>
<return-column name="CURRENCY"/>
</return-property>
</return>
{ ? = call selectAllEmployments() }
</sql-query>
注意存儲過程當前僅僅返回標量和實體如今。不支持 <return-join> 和 <load-collection>。
18.2.2.1. 使用存儲過程的規則和限制
爲了在 Hibernate 中使用存儲過程,你必須遵循一些規則。不遵循這些規則的存儲過程將不可
用。若是你仍然想使用他們,你必須經過 session.connection() 來執行他們。這些規則針對於不
同的數據庫。由於數據庫提供商有各類不一樣的存儲過程語法和語義。
對存儲過程進行的查詢沒法使用 setFirstResult()/setMaxResults() 進行分頁。
建議採用的調用方式是標準 SQL92: { ? = call functionName(<parameters>) } 或者 { ? = call
procedureName(<parameters>) }。原生調用語法不被支持。
對於 Oracle 有以下規則:
•函數必須返回一個結果集。存儲過程的第一個參數必須是 OUT,它返回一個結果集。這是經過
Oracle 9 或 10 的 SYS_REFCURSOR 類型來完成的。在 Oracle 中你須要定義一個 REF CURSOR
類型,參見 Oracle 的手冊。
對於 Sybase 或者 MS SQL server 有以下規則:
•存儲過程必須返回一個結果集。注意這些 servers 可能返回多個結果集以及更新的數
目。Hibernate 將取出第一條結果集做爲它的返回值,其餘將被丟棄。
•若是你可以在存儲過程裏設定 SET NOCOUNT ON,這可能會效率更高,但這不是必需的。
18.3. 定製 SQL 用來 create,update 和 delete
Hibernate3 can use custom SQL for create, update, and delete operations. The SQL can be
overridden at the statement level or inidividual column level. This section describes
statement overrides. For columns, see 第 5.6 節 「Column transformers: read and write
expressions」. 例  18.11 「Custom CRUD via annotations」 shows how to define custom
SQL operatons using annotations.
例 18.11. Custom CRUD via annotations
@Entity
第 18 章 Native SQL 查詢
302
@Table(name="CHAOS")
@SQLInsert( sql="INSERT INTO CHAOS(size, name, nickname, id) VALUES(?,upper(?),?,?)")
@SQLUpdate( sql="UPDATE CHAOS SET size = ?, name = upper(?), nickname = ? WHERE id = ?")
@SQLDelete( sql="DELETE CHAOS WHERE id = ?")
@SQLDeleteAll( sql="DELETE CHAOS")
@Loader(namedQuery = "chaos")
@NamedNativeQuery(name="chaos", query="select id, size, name, lower( nickname ) as nickname from
CHAOS where id= ?", resultClass = Chaos.class)
public class Chaos {
@Id
private Long id;
private Long size;
private String name;
private String nickname;
@SQLInsert, @SQLUpdate, @SQLDelete, @SQLDeleteAll respectively override the INSERT, UPDATE,
DELETE, and DELETE all statement. The same can be achieved using Hibernate mapping files
and the <sql-insert>, <sql-update> and <sql-delete> nodes. This can be seen in 例 18.12
「Custom CRUD XML」.
例 18.12. Custom CRUD XML
<class name="Person">
<id name="id">
<generator class="increment"/>
</id>
<property name="name" not-null="true"/>
<sql-insert>INSERT INTO PERSON (NAME, ID) VALUES ( UPPER(?), ? )</sql-insert>
<sql-update>UPDATE PERSON SET NAME=UPPER(?) WHERE ID=?</sql-update>
<sql-delete>DELETE FROM PERSON WHERE ID=?</sql-delete>
</class>
If you expect to call a store procedure, be sure to set the callable attribute to true.
In annotations as well as in xml.
To check that the execution happens correctly, Hibernate allows you to define one of
those three strategies:
•none: no check is performed: the store procedure is expected to fail upon issues
•count: use of rowcount to check that the update is successful
•param: like COUNT but using an output parameter rather that the standard mechanism
To define the result check style, use the check parameter which is again available in
annoations as well as in xml.
You can use the exact same set of annotations respectively xml nodes to override the
collection related statements -see 例 18.13 「Overriding SQL statements for collections
using annotations」.
定製 SQL 用來 create,update 和 delete
303
例 18.13. Overriding SQL statements for collections using annotations
@OneToMany
@JoinColumn(name="chaos_fk")
@SQLInsert( sql="UPDATE CASIMIR_PARTICULE SET chaos_fk = ? where id = ?")
@SQLDelete( sql="UPDATE CASIMIR_PARTICULE SET chaos_fk = null where id = ?")
private Set<CasimirParticle> particles = new HashSet<CasimirParticle>();
提示
The parameter order is important and is defined by the order Hibernate
handles properties. You can see the expected order by enabling debug
logging for the org.hibernate.persister.entity level. With this level enabled
Hibernate will print out the static SQL that is used to create, update,
delete etc. entities. (To see the expected sequence, remember to not
include your custom SQL through annotations or mapping files as that will
override the Hibernate generated static sql)
Overriding SQL statements for secondary tables is also possible using
@org.hibernate.annotations.Table and either (or all) attributes sqlInsert, sqlUpdate,
sqlDelete:
例 18.14. Overriding SQL statements for secondary tables
@Entity
@SecondaryTables({
@SecondaryTable(name = "`Cat nbr1`"),
@SecondaryTable(name = "Cat2"})
@org.hibernate.annotations.Tables( {
@Table(appliesTo = "Cat", comment = "My cat table" ),
@Table(appliesTo = "Cat2", foreignKey = @ForeignKey(name="FK_CAT2_CAT"), fetch = FetchMode.SELECT,
sqlInsert=@SQLInsert(sql="insert into Cat2(storyPart2, id) values(upper(?), ?)") )
} )
public class Cat implements Serializable {
The previous example also shows that you can give a comment to a given table (primary
or secondary): This comment will be used for DDL generation.
提示
The SQL is directly executed in your database, so you can use any dialect
you like. This will, however, reduce the portability of your mapping if
you use database specific SQL.
第 18 章 Native SQL 查詢
304
Last but not least, stored procedures are in most cases required to return the number
of rows inserted, updated and deleted. Hibernate always registers the first statement
parameter as a numeric output parameter for the CUD operations:
例 18.15. Stored procedures and their return value
CREATE OR REPLACE FUNCTION updatePerson (uid IN NUMBER, uname IN VARCHAR2)
RETURN NUMBER IS
BEGIN
update PERSON
set
NAME = uname,
where
ID = uid;
return SQL%ROWCOUNT;
END updatePerson;
18.4. 定製裝載 SQL
You can also declare your own SQL (or HQL) queries for entity loading. As with inserts,
updates, and deletes, this can be done at the individual column level as described
in 第 5.6 節 「Column transformers: read and write expressions」 or at the statement
level. Here is an example of a statement level override:
<sql-query name="person">
<return alias="pers" class="Person" lock-mode="upgrade"/>
SELECT NAME AS {pers.name}, ID AS {pers.id}
FROM PERSON
WHERE ID=?
FOR UPDATE
</sql-query>
這只是一個前面討論過的命名查詢聲明,你能夠在類映射裏引用這個命名查詢。
<class name="Person">
<id name="id">
<generator class="increment"/>
</id>
<property name="name" not-null="true"/>
<loader query-ref="person"/>
</class>
這也能夠用於存儲過程
你甚至能夠定一個用於集合裝載的查詢:
定製裝載 SQL
305
<set name="employments" inverse="true">
<key/>
<one-to-many class="Employment"/>
<loader query-ref="employments"/>
</set>
<sql-query name="employments">
<load-collection alias="emp" role="Person.employments"/>
SELECT {emp.*}
FROM EMPLOYMENT emp
WHERE EMPLOYER = :id
ORDER BY STARTDATE ASC, EMPLOYEE ASC
</sql-query>
你甚至還能夠定義一個實體裝載器,它經過鏈接抓取裝載一個集合:
<sql-query name="person">
<return alias="pers" class="Person"/>
<return-join alias="emp" property="pers.employments"/>
SELECT NAME AS {pers.*}, {emp.*}
FROM PERSON pers
LEFT OUTER JOIN EMPLOYMENT emp
ON pers.ID = emp.PERSON_ID
WHERE ID=?
</sql-query>
The annotation equivalent <loader> is the @Loader annotation as seen in 例 18.11 「Custom
CRUD via annotations」.
306
第 19
307
過濾數據
Hibernate3 提供了一種創新的方式來處理具備「顯性(visibility)」規則的數據,那就是使
用Hibernate 過濾器。Hibernate 過濾器是全局有效的、具備名字、能夠帶參數的過濾器,對於
某個特定的 Hibernate session 您能夠選擇是否啓用(或禁用)某個過濾器。
19.1. Hibernate 過濾器(filters)
Hibernate3 新增了對某個類或者集合使用預先定義的過濾器條件(filter criteria)的功能。
過濾器條件至關於定義一個 很是相似於類和各類集合上的「where」屬性的約束子句,可是過濾
器條件能夠帶參數。 應用程序能夠在運行時決定是否啓用給定的過濾器,以及使用什麼樣的參數
值。過濾器的用法很像數據庫視圖,只不過是在應用程序中肯定使用什麼樣的參數的。
Using annotatons filters are defined via @org.hibernate.annotations.FilterDef or
@org.hibernate.annotations.FilterDefs. A filter definition has a name() and an array of
parameters(). A parameter will allow you to adjust the behavior of the filter at
runtime. Each parameter is defined by a @ParamDef which has a name and a type. You
can also define a defaultCondition() parameter for a given @FilterDef to set the default
condition to use when none are defined in each individual @Filter. @FilterDef(s) can be
defined at the class or package level.
We now need to define the SQL filter clause applied to either the entity load or the
collection load. @Filter is used and placed either on the entity or the collection
element. The connection between @FilterName and @Filter is a matching name.
例 19.1. @FilterDef and @Filter annotations
@Entity
@FilterDef(name="minLength", parameters=@ParamDef( name="minLength", type="integer" ) )
@Filters( {
@Filter(name="betweenLength", condition=":minLength <= length and :maxLength >= length"),
@Filter(name="minLength", condition=":minLength <= length")
} )
public class Forest { ... }
When the collection use an association table as a relational representation, you might
want to apply the filter condition to the association table itself or to the target
entity table. To apply the constraint on the target entity, use the regular @Filter
annotation. However, if you want to target the association table, use the @FilterJoinTable
annotation.
例 19.2. Using @FilterJoinTable for filterting on the association table
@OneToMany
@JoinTable
第 19 章 過濾數據
308
//filter on the target entity table
@Filter(name="betweenLength", condition=":minLength <= length and :maxLength >= length")
//filter on the association table
@FilterJoinTable(name="security", condition=":userlevel >= requredLevel")
public Set<Forest> getForests() { ... }
Using Hibernate mapping files for defining filters the situtation is very similar. The
filters must first be defined and then attached to the appropriate mapping elements.
To define a filter, use the <filter-def/> element within a <hibernate-mapping/> element:
例 19.3. Defining a filter definition via <filter-def>
<filter-def name="myFilter">
<filter-param name="myFilterParam" type="string"/>
</filter-def>
This filter can then be attached to a class or collection (or, to both or multiples
of each at the same time):
例 19.4. Attaching a filter to a class or collection using <filter>
<class name="myClass" ...>
...
<filter name="myFilter" condition=":myFilterParam = MY_FILTERED_COLUMN"/>
<set ...>
<filter name="myFilter" condition=":myFilterParam = MY_FILTERED_COLUMN"/>
</set>
</class>
Session 對象中會用到的方法有:enableFilter(String filterName),getEnabledFilter(String
filterName),和 disableFilter(String filterName)。Session 中默認是不啓用過濾器的,必須經過
Session.enabledFilter() 方法顯式的啓用。該方法返回被啓用的 Filter 的實例。以上文定義的過
濾器爲例:
session.enableFilter("myFilter").setParameter("myFilterParam", "some-value");
注意,org.hibernate.Filter 的方法容許鏈式方法調用。(相似上面例子中啓用 Filter 以後設
定 Filter 參數這個「方法鏈」) Hibernate 的其餘部分也大多有這個特性。
下面是一個比較完整的例子,使用了記錄生效日期模式過濾有時效的數據:
<filter-def name="effectiveDate">
<filter-param name="asOfDate" type="date"/>
</filter-def>
Hibernate 過濾器(filters)
309
<class name="Employee" ...>
...
<many-to-one name="department" column="dept_id" class="Department"/>
<property name="effectiveStartDate" type="date" column="eff_start_dt"/>
<property name="effectiveEndDate" type="date" column="eff_end_dt"/>
...
<!--
Note that this assumes non-terminal records have an eff_end_dt set to
a max db date for simplicity-sake
-->
<filter name="effectiveDate"
condition=":asOfDate BETWEEN eff_start_dt and eff_end_dt"/>
</class>
<class name="Department" ...>
...
<set name="employees" lazy="true">
<key column="dept_id"/>
<one-to-many class="Employee"/>
<filter name="effectiveDate"
condition=":asOfDate BETWEEN eff_start_dt and eff_end_dt"/>
</set>
</class>
定義好後,若是想要保證取回的都是目前處於生效期的記錄,只需在獲取僱員數據的操做以前先
開啓過濾器便可:
Session session = ...;
session.enableFilter("effectiveDate").setParameter("asOfDate", new Date());
List results = session.createQuery("from Employee as e where e.salary > :targetSalary")
.setLong("targetSalary", new Long(1000000))
.list();
在上面的 HQL 中,雖然咱們僅僅顯式的使用了一個薪水條件,但由於啓用了過濾器,查詢將僅返
回那些目前僱用關係處於生效期的,而且薪水高於一百萬美圓的僱員的數據。
注意:若是你打算在使用外鏈接(或者經過 HQL 或 load fetching)的同時使用過濾器,要注意
條件表達式的方向(左仍是右)。最安全的方式是使用左外鏈接(left outer joining)。而且
一般來講,先寫參數,而後是操做符,最後寫數據庫字段名。
在 Filter 定義以後,它可能被附加到多個實體和/或集合類,每一個都有本身的條件。倘若這些條件
都是同樣的,每次都要定義就顯得很繁瑣。所以,<filter-def/> 被用來定義一個默認條件,它可
能做爲屬性或者 CDATA 出現:
<filter-def name="myFilter" condition="abc > xyz">...</filter-def>
<filter-def name="myOtherFilter">abc=xyz</filter-def>
當這個 filter 被附加到任何目的地,而又沒有指明條件時,這個缺省條件就會被使用。注意,
換句話說,你能夠經過給 filter 附加特別的條件來重載默認條件。
310
第 20
311
XML 映射
XML Mapping is an experimental feature in Hibernate 3.0 and is currently under active
development.
20.1. 用 XML 數據進行工做
Hibernate 使得你能夠用 XML 數據來進行工做,恰如你用持久化的 POJO 進行工做那樣。解析過
的 XML 樹 能夠被認爲是代替 POJO 的另一種在對象層面上表示關係型數據的途徑。
Hibernate 支持採用 dom4j 做爲操做 XML 樹的 API。你能夠寫一些查詢從數據
庫中檢索出 dom4j 樹,隨後你對這顆樹作的任何修改都將自動同步回數據庫。你甚至能夠用
dom4j 解析 一篇 XML 文檔,而後使用 Hibernate 的任一基本操做將它寫入數據
庫:persist(),saveOrUpdate(),merge(),delete(),replicate() (合併操做merge()目前還不支
持)。
這一特性能夠應用在不少場合,包括數據導入導出,經過 JMS 或 SOAP 具體化實體數據以及 基
於 XSLT 的報表。
一個單一的映射就能夠將類的屬性和 XML 文檔的節點同時映射到數據庫。若是不須要映射類,它
也能夠用來只映射 XML 文檔。
20.1.1. 指定同時映射 XML 和類
這是一個同時映射 POJO 和 XML 的例子:
<class name="Account"
table="ACCOUNTS"
node="account">

<id name="accountId"
column="ACCOUNT_ID"
node="@id"/>

<many-to-one name="customer"
column="CUSTOMER_ID"
node="customer/@id"
embed-xml="false"/>

<property name="balance"
column="BALANCE"
node="balance"/>

...

</class
>
第 20 章 XML 映射
312
20.1.2. 只定義 XML 映射
這是一個不映射 POJO 的例子:
<class entity-name="Account"
table="ACCOUNTS"
node="account">

<id name="id"
column="ACCOUNT_ID"
node="@id"
type="string"/>

<many-to-one name="customerId"
column="CUSTOMER_ID"
node="customer/@id"
embed-xml="false"
entity-name="Customer"/>

<property name="balance"
column="BALANCE"
node="balance"
type="big_decimal"/>

...

</class
>
這個映射使得你既能夠把數據做爲一棵 dom4j 樹那樣訪問,又能夠做爲由屬性鍵值對(java
Map)組成的圖那樣訪問。屬性名字純粹是邏輯上的結構,你能夠在 HQL 查詢中引用它。
20.2. XML 映射元數據
許多 Hibernate 映射元素具備 node 屬性。這使你能夠指定用來保存 屬性或實體數據的 XML 屬
性或元素。node 屬性必須是下列格式之一:
•"element-name":映射爲指定的 XML 元素
•"@attribute-name":映射爲指定的 XML 屬性
•".":映射爲父元素
•"element-name/@attribute-name":映射爲指定元素的指定屬性
對於集合和單值的關聯,有一個額外的 embed-xml 屬性可用。這個屬性的缺省值是真(embedxml="true")。若是
embed-xml="true",則對應於被關聯實體或值類型的集合的XML樹將直接嵌入
擁有這些關聯的實體的 XML 樹中。不然,若是 embed-xml="false",那麼對於單值的關聯,僅被引
用的實體的標識符出如今 XML 樹中(被引用實體自己不出現),而集合則根本不出現。
你應該當心,不要讓太多關聯的 embed-xml 屬性爲真(embed-xml="true"),由於 XML 不能很好
地處理循環引用。
XML 映射元數據
313
<class name="Customer"
table="CUSTOMER"
node="customer">

<id name="id"
column="CUST_ID"
node="@id"/>

<map name="accounts"
node="."
embed-xml="true">
<key column="CUSTOMER_ID"
not-null="true"/>
<map-key column="SHORT_DESC"
node="@short-desc"
type="string"/>
<one-to-many entity-name="Account"
embed-xml="false"
node="account"/>
</map>

<component name="name"
node="name">
<property name="firstName"
node="first-name"/>
<property name="initial"
node="initial"/>
<property name="lastName"
node="last-name"/>
</component>

...

</class
>
在這個例子中,咱們決定嵌入賬目號碼(account id)的集合,但不嵌入實際的賬目數據。下面
的 HQL 查詢:
from Customer c left join fetch c.accounts where c.lastName like :lastName
返回的數據集將是這樣:
<customer id="123456789">
<account short-desc="Savings"
>987632567</account>
<account short-desc="Credit Card"
>985612323</account>
<name>
<first-name
>Gavin</first-name>
<initial
第 20 章 XML 映射
314
>A</initial>
<last-name
>King</last-name>
</name>
...
</customer
>
若是你把一對多映射 <one-to-many> 的 embed-xml 屬性置爲真(embed-xml="true"),則數據看上
去就像這樣:
<customer id="123456789">
<account id="987632567" short-desc="Savings">
<customer id="123456789"/>
<balance
>100.29</balance>
</account>
<account id="985612323" short-desc="Credit Card">
<customer id="123456789"/>
<balance
>-2370.34</balance>
</account>
<name>
<first-name
>Gavin</first-name>
<initial
>A</initial>
<last-name
>King</last-name>
</name>
...
</customer
>
20.3. 操做 XML 數據
你也能夠從新讀入和更新應用程序中的 XML 文檔。經過獲取一個 dom4j 會話能夠作到這一點:
Document doc = ....;
Session session = factory.openSession();
Session dom4jSession = session.getSession(EntityMode.DOM4J);
Transaction tx = session.beginTransaction();
List results = dom4jSession
.createQuery("from Customer c left join fetch c.accounts where c.lastName like :lastName")
.list();
for ( int i=0; i<results.size(); i++ ) {
//add the customer data to the XML document
Element customer = (Element) results.get(i);
doc.add(customer);
}
操做 XML 數據
315
tx.commit();
session.close();
Session session = factory.openSession();
Session dom4jSession = session.getSession(EntityMode.DOM4J);
Transaction tx = session.beginTransaction();
Element cust = (Element) dom4jSession.get("Customer", customerId);
for ( int i=0; i<results.size(); i++ ) {
Element customer = (Element) results.get(i);
//change the customer name in the XML and database
Element name = customer.element("name");
name.element("first-name").setText(firstName);
name.element("initial").setText(initial);
name.element("last-name").setText(lastName);
}
tx.commit();
session.close();
將這一特點與 Hibernate 的 replicate() 操做結合起來對於實現的基於 XML 的數據導入/導出將
很是有用。
316
第 21
317
提高性能
21.1. 抓取策略(Fetching strategies)
當應用程序須要在(Hibernate實體對象圖的)關聯關係間進行導航的時候,Hibernate 使用 抓
取策略(fetching strategy) 獲取關聯對象。抓取策略能夠在 O/R 映射的元數據中聲明,也可
以在特定的 HQL 或條件查詢(Criteria Query)中重載聲明。
Hibernate3 定義了以下幾種抓取策略:
•鏈接抓取(Join fetching):Hibernate 經過在 SELECT 語句使用 OUTER JOIN(外鏈接)來獲
得對象的關聯實例或者關聯集合。
•查詢抓取(Select fetching):另外發送一條 SELECT 語句抓取當前對象的關聯實體或集合。
除非你顯式的指定 lazy="false" 禁止 延遲抓取(lazy fetching),不然只有當你真正訪問關
聯關係的時候,纔會執行第二條 select 語句。
•子查詢抓取(Subselect fetching):另外發送一條 SELECT 語句抓取在前面查詢到(或者
抓取到)的全部實體對象的關聯集合。除非你顯式的指定 lazy="false" 禁止延遲抓取(lazy
fetching),不然只有當你真正訪問關聯關係的時候,纔會執行第二條 select 語句。
•批量抓取(Batch fetching):對查詢抓取的優化方案,經過指定一個主鍵或外鍵列
表,Hibernate 使用單條 SELECT 語句獲取一批對象實例或集合。
Hibernate 會區分下列各類狀況:
•Immediate fetching,當即抓取:當宿主被加載時,關聯、集合或屬性被當即抓取。
•Lazy collection fetching,延遲集合抓取:直到應用程序對集合進行了一次操做時,集合才
被抓取(對集合而言這是默認行爲)。
•"Extra-lazy" collection fetching,"Extra-lazy" 集合抓取:對集合類中的每一個元素而言,
都是直到須要時纔去訪問數據庫。除非絕對必要,Hibernate 不會試圖去把整個集合都抓取到
內存裏來(適用於很是大的集合)。
•Proxy fetching,代理抓取:對返回單值的關聯而言,當其某個方法被調用,而非對其關鍵字
進行 get 操做時才抓取。
•"No-proxy" fetching,非代理抓取:對返回單值的關聯而言,當實例變量被訪問的時候進行抓
取。與上面的代理抓取相比,這種方法沒有那麼「延遲」得厲害(就算只訪問標識符,也會導
致關聯抓取)可是更加透明,由於對應用程序來講,再也不看到 proxy。這種方法須要在編譯期
間進行字節碼加強操做,所以不多須要用到。
•Lazy attribute fetching,屬性延遲加載:對屬性或返回單值的關聯而言,當其實例變量被訪
問的時候進行抓取。須要編譯期字節碼強化,所以這一方法不多是必要的。
這裏有兩個正交的概念:關聯什麼時候被抓取,以及被如何抓取(會採用什麼樣的 SQL 語句)。注意
不要混淆它們。咱們使用抓取來改善性能。咱們使用延遲來定義一些契約,對某特定類的某個脫
管的實例,知道有哪些數據是能夠使用的。
第 21 章 提高性能
318
21.1.1. 操做延遲加載的關聯
默認狀況下,Hibernate 3 對集合使用延遲 select 抓取,對返回單值的關聯使用延遲代理抓取。
對幾乎是全部的應用而言,其絕大多數的關聯,這種策略都是有效的。
倘若你設置了 hibernate.default_batch_fetch_size,Hibernate 會對延遲加載採起批量抓取優化措
施(這種優化也可能會在更細化的級別打開)。
然而,你必須瞭解延遲抓取帶來的一個問題。在一個打開的 Hibernate session 上下文以外調用
延遲集合會致使一次意外。好比:
s = sessions.openSession();
Transaction tx = s.beginTransaction();
User u = (User) s.createQuery("from User u where u.name=:userName")
.setString("userName", userName).uniqueResult();
Map permissions = u.getPermissions();
tx.commit();
s.close();
Integer accessLevel = (Integer) permissions.get("accounts"); // Error!
在 Session 關閉後,permessions 集合將是未實例化的、再也不可用,所以沒法正常載入其狀態。
Hibernate 對脫管對象不支持延遲實例化。這裏的修改方法是將 permissions 讀取數據的代碼移
到事務提交以前。
除此以外,經過對關聯映射指定 lazy="false",咱們也能夠使用非延遲的集合或關聯。可是,對
絕大部分集合來講,更推薦使用延遲方式抓取數據。若是在你的對象模型中定義了太多的非延遲
關聯,Hibernate 最終幾乎須要在每一個事務中載入整個數據庫到內存中。
可是,另外一方面,在一些特殊的事務中,咱們也常常須要使用到鏈接抓取(它自己上就是非延遲
的),以代替查詢抓取。 下面咱們將會很快明白如何具體的定製 Hibernate 中的抓取策略。在
Hibernate3 中,具體選擇哪一種抓取策略的機制是和選擇 單值關聯或集合關聯相一致的。
21.1.2. 調整抓取策略(Tuning fetch strategies)
查詢抓取(默認的)在 N+1 查詢的狀況下是極其脆弱的,所以咱們可能會要求在映射文檔中定義
使用鏈接抓取:
<set name="permissions"
fetch="join">
<key column="userId"/>
<one-to-many class="Permission"/>
</set
<many-to-one name="mother" class="Cat" fetch="join"/>
單端關聯代理(Single-ended association proxies)
319
在映射文檔中定義的抓取策略將會對如下列表條目產生影響:
•經過 get() 或 load() 方法取得數據。
•只有在關聯之間進行導航時,纔會隱式的取得數據。
•條件查詢
•使用了 subselect 抓取的 HQL 查詢
無論你使用哪一種抓取策略,定義爲非延遲的類圖會被保證必定裝載入內存。注意這可能意味着在
一條 HQL 查詢後緊跟着一系列的查詢。
一般狀況下,咱們並不使用映射文檔進行抓取策略的定製。更多的是,保持其默認值,而後在特
定的事務中, 使用 HQL 的左鏈接抓取(left join fetch) 對其進行重載。這將通知 Hibernate在
第一次查詢中使用外部關聯(outer join),直接獲得其關聯數據。在條件查詢 API 中,應該調
用 setFetchMode(FetchMode.JOIN)語句。
也許你喜歡僅僅經過條件查詢,就能夠改變 get() 或 load() 語句中的數據抓取策略。例如:
User user = (User) session.createCriteria(User.class)
.setFetchMode("permissions", FetchMode.JOIN)
.add( Restrictions.idEq(userId) )
.uniqueResult();
這就是其餘 ORM 解決方案的「抓取計劃(fetch plan)」在 Hibernate 中的等價物。
大相徑庭的一種避免 N+1 次查詢的方法是,使用二級緩存。
21.1.3. 單端關聯代理(Single-ended association proxies)
在 Hinerbate 中,對集合的延遲抓取的採用了本身的實現方法。可是,對於單端關聯的延遲抓
取,則須要採用 其餘不一樣的機制。單端關聯的目標實體必須使用代理,Hihernate 在運行期二進
制級(經過優異的 CGLIB 庫), 爲持久對象實現了延遲載入代理。
默認的,Hibernate3 將會爲全部的持久對象產生代理(在啓動階段),而後使用他們實現 多對一
(many-to-one)關聯和一對一(one-to-one) 關聯的延遲抓取。
在映射文件中,能夠經過設置 proxy 屬性爲目標 class 聲明一個接口供代理接口使用。 默認
的,Hibernate 將會使用該類的一個子類。注意:被代理的類必須實現一個至少包可見的默認構
造函數,咱們建議全部的持久類都應擁有這樣的構造函數。
在如此方式定義一個多態類的時候,有許多值得注意的常見性的問題,例如:
<class name="Cat" proxy="Cat">
......
<subclass name="DomesticCat">
第 21 章 提高性能
320
.....
</subclass>
</class>
首先,Cat 實例永遠不能夠被強制轉換爲 DomesticCat,即便它自己就是 DomesticCat 實例。
Cat cat = (Cat) session.load(Cat.class, id); // instantiate a proxy (does not hit the db)
if ( cat.isDomesticCat() ) { // hit the db to initialize the proxy
DomesticCat dc = (DomesticCat) cat; // Error!
....
}
其次,代理的「==」可能再也不成立。
Cat cat = (Cat) session.load(Cat.class, id); // instantiate a Cat proxy
DomesticCat dc =
(DomesticCat) session.load(DomesticCat.class, id); // acquire new DomesticCat proxy!
System.out.println(cat==dc); // false
雖然如此,但實際狀況並無看上去那麼糟糕。雖然咱們如今有兩個不一樣的引用,分別指向這兩
個不一樣的代理對象,但實際上,其底層應該是同一個實例對象:
cat.setWeight(11.0); // hit the db to initialize the proxy
System.out.println( dc.getWeight() ); // 11.0
第三,你不能對 final 類或具備 final 方法的類使用 CGLIB 代理。
最後,若是你的持久化對象在實例化時須要某些資源(例如,在實例化方法、默認構造方法
中),那麼代理對象也一樣須要使用這些資源。實際上,代理類是持久化類的子類。
這些問題都源於 Java 的單根繼承模型的天生限制。若是你但願避免這些問題,那麼你的每一個持
久化類必須實現一個接口, 在此接口中已經聲明瞭其業務方法。而後,你須要在映射文檔中再指
定這些接口,如 CatImpl 實現 Cat 而 DomesticCatImpl 實現 DomesticCat 接口。例如:
<class name="CatImpl" proxy="Cat">
......
<subclass name="DomesticCatImpl" proxy="DomesticCat">
.....
</subclass>
</class>
而後,load() 和 iterate() 永遠也不會返回 Cat 和 DomesticCat 實例的代理。
Cat cat = (Cat) session.load(CatImpl.class, catid);
實例化集合和代理(Initializing collections and proxies)
321
Iterator iter = session.createQuery("from CatImpl as cat where cat.name='fritz'").iterate();
Cat fritz = (Cat) iter.next();
注意
list() 一般不返回代理。
這裏,對象之間的關係也將被延遲載入。這就意味着,你應該將屬性聲明爲 Cat,而不是
CatImpl。
有些方法中是不須要代理初始化的:
•equals() 方法,若是持久類沒有重載 equals() 方法。
•hashCode():若是持久類沒有重載 hashCode() 方法。
•標誌符的 getter 方法。
Hibernate 將會識別出那些重載了 equals()、或 hashCode() 方法的持久化類。
若選擇 lazy="no-proxy" 而非默認的 lazy="proxy",咱們能夠避免類型轉換帶來的問題。然而,這
樣咱們就須要編譯期字節碼加強,而且全部的操做都會致使馬上進行代理初始化。
21.1.4. 實例化集合和代理(Initializing collections and
proxies)
在 Session 範圍以外訪問未初始化的集合或代理,Hibernate 將會拋出
LazyInitializationException 異常。也就是說,在分離狀態下,訪問一個實體所擁有的集合,或者
訪問其指向代理的屬性時,會引起此異常。
有時候咱們須要保證某個代理或者集合在 Session 關閉前就已經被初始化了。固然,咱們能夠通
過強行調用 cat.getSex() 或者 cat.getKittens().size() 之類的方法來確保這一點。 可是這樣的
程序會形成讀者的疑惑,也不符合一般的代碼規範。
靜態方法 Hibernate.initialized() 爲你的應用程序提供了一個便捷的途徑來延遲加載集合或代
理。 只要它的 Session 處於 open 狀態,Hibernate.initialize(cat) 將會爲 cat 強制對代理實
例化。一樣,Hibernate.initialize(cat.getKittens()) 對 kittens 的集合具備一樣的功能。
還有另一種選擇,就是保持 Session 一直處於 open 狀態,直到全部須要的集合或代理都被載
入。 在某些應用架構中,特別是對於那些使用 Hibernate 進行數據訪問的代碼,以及那些在不
同應用層和不一樣物理進程中使用 Hibernate 的代碼。 在集合實例化時,如何保證 Session 處於
open 狀態常常會是一個問題。有兩種方法能夠解決此問題:
•在一個基於 Web 的應用中,能夠利用 servlet 過濾器(filter),在用戶請求(request)
結束、頁面生成 結束時關閉 Session(這裏使用了在展現層保持打開 Session 模式(Open
Session in View)),固然,這將依賴於應用框架中異常須要被正確的處理。在返回界面給用
第 21 章 提高性能
322
戶以前,乃至在生成界面過程當中發生異常的狀況下,正確關閉 Session 和結束事務將是很是重
要的, 請參見 Hibernate wiki 上的 "Open Session in View" 模式,你能夠找到示例。
•在一個擁有單獨業務層的應用中,業務層必須在返回以前,爲 web 層「準備」好其所需的數
據集合。這就意味着 業務層應該載入全部表現層/web 層所需的數據,並將這些已實例化完畢
的數據返回。一般,應用程序應該爲 web 層所需的每一個集合調用 Hibernate.initialize()(這
個調用必須發生咱 session 關閉以前);或者使用帶有 FETCH 從句,或 FetchMode.JOIN
的 Hibernate 查詢,事先取得全部的數據集合。若是你在應用中使用了 Command 模式,代替
Session Facade,那麼這項任務將會變得簡單的多。
•你也能夠經過 merge() 或 lock() 方法,在訪問未實例化的集合(或代理)以前,爲先前載入的
對象綁定一個新的 Session。顯然,Hibernate 將不會,也不該該自動完成這些任務,由於這將
引入一個特殊的事務語義。
有時候,你並不須要徹底實例化整個大的集合,僅須要瞭解它的部分信息(例如其大小)、或者
集合的部份內容。
你能夠使用集合過濾器獲得其集合的大小,而沒必要實例化整個集合:
( (Integer) s.createFilter( collection, "select count(*)" ).list().get(0) ).intValue()
這裏的 createFilter() 方法也能夠被用來有效的抓取集合的部份內容,而無需實例化整個集合:
s.createFilter( lazyCollection, "").setFirstResult(0).setMaxResults(10).list();
21.1.5. 使用批量抓取(Using batch fetching)
Hibernate 能夠充分有效的使用批量抓取,也就是說,若是僅一個訪問代理(或集合),那麼
Hibernate 將不載入其餘未實例化的代理。批量抓取是延遲查詢抓取的優化方案,你能夠在兩種
批量抓取方案之間進行選擇:在類級別和集合級別。
類/實體級別的批量抓取很容易理解。假設你在運行時將須要面對下面的問題:你在一個 Session
中載入了 25 個 Cat 實例,每一個 Cat 實例都擁有一個引用成員 owner,其指向 Person,而 Person
類是代理,同時 lazy="true"。若是你必須遍歷整個 cats 集合,對每一個元素調用 getOwner() 方
法,Hibernate 將會默認的執行 25 次 SELECT 查詢, 獲得其 owner 的代理對象。這時,你能夠
經過在映射文件的 Person 屬性,顯式聲明 batch-size,改變其行爲:
<class name="Person" batch-size="10">...</class>
隨之,Hibernate 將只須要執行三次查詢,分別爲 十、十、 5。
你也能夠在集合級別定義批量抓取。例如,若是每一個 Person 都擁有一個延遲載入的 Cats 集合,
如今,Sesssion 中載入了 10 個 person 對象,遍歷 person 集合將會引發 10 次 SELECT 查詢,
使用子查詢抓取(Using subselect fetching)
323
每次查詢都會調用 getCats() 方法。若是你在 Person 的映射定義部分,容許對 cats 批量抓取,
那麼,Hibernate 將能夠預先抓取整個集合。請看例子:
<class name="Person">
<set name="cats" batch-size="3">
...
</set>
</class>
若是整個的 batch-size 是 3,那麼 Hibernate 將會分四次執行 SELECT 查詢, 按照 三、三、三、1
的大小分別載入數據。這裏的每次載入的數據量還具體依賴於當前 Session 中未實例化集合的個
數。
若是你的模型中有嵌套的樹狀結構,例如典型的賬單-原料結構(bill-of-materials
pattern),集合的批量抓取是很是有用的。(儘管在更多狀況下對樹進行讀取時,嵌套集合
(nested set)或原料路徑(materialized path)多是更好的解決方法。)
21.1.6. 使用子查詢抓取(Using subselect fetching)
倘若一個延遲集合或單值代理須要抓取,Hibernate 會使用一個 subselect 從新運行原來的查
詢,一次性讀入全部的實例。這和批量抓取的實現方法是同樣的,不會有破碎的加載。
21.1.7. Fetch profile(抓取策略)
Another way to affect the fetching strategy for loading associated objects is through
something called a fetch profile, which is a named configuration associated with
the org.hibernate.SessionFactory but enabled, by name, on the org.hibernate.Session. Once
enabled on a org.hibernate.Session, the fetch profile will be in affect for that
org.hibernate.Session until it is explicitly disabled.
So what does that mean? Well lets explain that by way of an example which show the
different available approaches to configure a fetch profile:
例 21.1. Specifying a fetch profile using @FetchProfile
@Entity
@FetchProfile(name = "customer-with-orders", fetchOverrides = {
@FetchProfile.FetchOverride(entity = Customer.class, association = "orders", mode = FetchMode.JOIN)
})
public class Customer {
@Id
@GeneratedValue
private long id;
private String name;
private long customerNumber;
第 21 章 提高性能
324
@OneToMany
private Set<Order> orders;
// standard getter/setter
...
}
例 21.2. Specifying a fetch profile using <fetch-profile> outside <class> node
<hibernate-mapping>
<class name="Customer">
...
<set name="orders" inverse="true">
<key column="cust_id"/>
<one-to-many class="Order"/>
</set>
</class>
<class name="Order">
...
</class>
<fetch-profile name="customer-with-orders">
<fetch entity="Customer" association="orders" style="join"/>
</fetch-profile>
</hibernate-mapping>
例 21.3. Specifying a fetch profile using <fetch-profile> inside <class> node
<hibernate-mapping>
<class name="Customer">
...
<set name="orders" inverse="true">
<key column="cust_id"/>
<one-to-many class="Order"/>
</set>
<fetch-profile name="customer-with-orders">
<fetch association="orders" style="join"/>
</fetch-profile>
</class>
<class name="Order">
...
</class>
</hibernate-mapping>
Now normally when you get a reference to a particular customer, that customer's set of
orders will be lazy meaning we will not yet have loaded those orders from the database.
Normally this is a good thing. Now lets say that you have a certain use case where it
is more efficient to load the customer and their orders together. One way certainly
is to use "dynamic fetching" strategies via an HQL or criteria queries. But another
option is to use a fetch profile to achieve that. The following code will load both
the customer andtheir orders:
使用延遲屬性抓取(Using lazy property fetching)
325
例 21.4. Activating a fetch profile for a given Session
Session session = ...;
session.enableFetchProfile( "customer-with-orders" ); // name matches from mapping
Customer customer = (Customer) session.get( Customer.class, customerId );
注意
@FetchProfile definitions are global and it does not matter on which class
you place them. You can place the @FetchProfile annotation either onto a
class or package (package-info.java). In order to define multiple fetch
profiles for the same class or package @FetchProfiles can be used.
目前只有 join 風格的抓取策略被支持,但其餘風格也將被支持。更多細節請參考 HHH-3414
[http://opensource.atlassian.com/projects/hibernate/browse/HHH-3414]。
21.1.8. 使用延遲屬性抓取(Using lazy property fetching)
Hibernate3 對單獨的屬性支持延遲抓取,這項優化技術也被稱爲組抓取(fetch groups)。 請注
意,該技術更多的屬於市場特性。在實際應用中,優化行讀取比優化列讀取更重要。可是,僅載
入類的部分屬性在某些特定狀況下會有用,例如在原有表中擁有幾百列數據、數據模型沒法改動
的狀況下。
能夠在映射文件中對特定的屬性設置 lazy,定義該屬性爲延遲載入。
<class name="Document">
<id name="id">
<generator class="native"/>
</id>
<property name="name" not-null="true" length="50"/>
<property name="summary" not-null="true" length="200" lazy="true"/>
<property name="text" not-null="true" length="2000" lazy="true"/>
</class>
屬性的延遲載入要求在其代碼構建時加入二進制指示指令(bytecode instrumentation),若是
你的持久類代碼中未含有這些指令, Hibernate 將會忽略這些屬性的延遲設置,仍然將其直接載
入。
你能夠在 Ant 的 Task 中,進行以下定義,對持久類代碼加入「二進制指令。」
<target name="instrument" depends="compile">
<taskdef name="instrument" classname="org.hibernate.tool.instrument.InstrumentTask">
<classpath path="${jar.path}"/>
<classpath path="${classes.dir}"/>
<classpath refid="lib.class.path"/>
第 21 章 提高性能
326
</taskdef>
<instrument verbose="true">
<fileset dir="${testclasses.dir}/org/hibernate/auction/model">
<include name="*.class"/>
</fileset>
</instrument>
</target>
還有一種能夠優化的方法,它使用 HQL 或條件查詢的投影(projection)特性,能夠避免讀取非
必要的列, 這一點至少對只讀事務是很是有用的。它無需在代碼構建時「二進制指令」處理,因
此是一個更加值得選擇的解決方法。
有時你須要在 HQL 中經過抓取全部屬性,強行抓取全部內容。
21.2. 二級緩存(The Second Level Cache)
Hibernate 的 Session 在事務級別進行持久化數據的緩存操做。 固然,也有可能分別爲每一個類
(或集合),配置集羣、或 JVM 級別(SessionFactory 級別)的緩存。你甚至能夠爲之插入一個
集羣的緩存。注意,緩存永遠不知道其餘應用程序對持久化倉庫(數據庫)可能進行的修改 (即
使能夠將緩存數據設定爲按期失效)。
You have the option to tell Hibernate which caching implementation to use by specifying
the name of a class that implements org.hibernate.cache.CacheProvider using the property
hibernate.cache.provider_class. Hibernate is bundled with a number of built-in integrations
with the open-source cache providers that are listed in 表 21.1 「緩存策略提供商(Cache
Providers)」. You can also implement your own and plug it in as outlined above. Note
that versions prior to Hibernate 3.2 use EhCache as the default cache provider.
表 21.1. 緩存策略提供商(Cache Providers)
Cache Provider class Type Cluster
Safe
Query
Cache
Supported
Hashtable
(not
intended
for
production
use)
org.hibernate.cache.HashtableCacheProvidermemory yes
EHCache org.hibernate.cache.EhCacheProvider memory,
disk,
transactional,
clustered
yes yes
OSCache org.hibernate.cache.OSCacheProvider memory,disk yes
緩存映射(Cache mappings)
327
Cache Provider class Type Cluster
Safe
Query
Cache
Supported
SwarmCache org.hibernate.cache.SwarmCacheProvider clustered
(ip
multicast)
yes
(clustered
invalidation)
JBoss
Cache 1.x
org.hibernate.cache.TreeCacheProvider clustered
(ip
multicast),
transactional
yes
(replication)
yes (clock
sync req.)
JBoss
Cache 2
org.hibernate.cache.jbc.JBossCacheRegionFactory clustered
(ip
multicast),
transactional
yes
(replication
or
invalidation)
yes (clock
sync req.)
21.2.1. 緩存映射(Cache mappings)
As we have done in previous chapters we are looking at the two different possibiltites to
configure caching. First configuration via annotations and then via Hibernate mapping
files.
By default, entities are not part of the second level cache and we recommend you to
stick to this setting. However, you can override this by setting the shared-cache-mode
element in your persistence.xml file or by using the javax.persistence.sharedCache.mode
property in your configuration. The following values are possible:
•ENABLE_SELECTIVE (Default and recommended value): entities are not cached unless
explicitly marked as cacheable.
•DISABLE_SELECTIVE: entities are cached unless explicitly marked as not cacheable.
•ALL: all entities are always cached even if marked as non cacheable.
•NONE: no entity are cached even if marked as cacheable. This option can make sense
to disable second-level cache altogether.
The cache concurrency strategy used by default can be set globaly via the
hibernate.cache.default_cache_concurrency_strategy configuration property. The values for
this property are:
•read-only
•read-write
第 21 章 提高性能
328
•nonstrict-read-write
•transactional
注意
It is recommended to define the cache concurrency strategy per entity
rather than using a global one. Use the @org.hibernate.annotations.Cache
annotation for that.
例 21.5. Definition of cache concurrency strategy via @Cache
@Entity
@Cacheable
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Forest { ... }
Hibernate also let's you cache the content of a collection or the identifiers if the
collection contains other entities. Use the @Cache annotation on the collection property.
例 21.6. Caching collections using annotations
@OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
@JoinColumn(name="CUST_ID")
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public SortedSet<Ticket> getTickets() {
return tickets;
}
例 21.7 「@Cache annotation with attributes」shows the @org.hibernate.annotations.Cache
annotations with its attributes. It allows you to define the caching strategy and
region of a given second level cache.
例 21.7. @Cache annotation with attributes
@Cache(
CacheConcurrencyStrategy usage();
String region() default "";
String include() default "all";
)
usage: the given cache concurrency strategy (NONE, READ_ONLY, NONSTRICT_READ_WRITE,
READ_WRITE, TRANSACTIONAL)
策略:只讀緩存(Strategy:read only)
329
region (optional): the cache region (default to the fqcn of the class or the fq
role name of the collection)
include (optional): all to include all properties, non-lazy to only include non
lazy properties (default all).
Let's now take a look at Hibernate mapping files. There the <cache> element of a class
or collection mapping is used to configure the second level cache. Looking at 例 21.8
「The Hibernate <cache> mapping element」 the parallels to anotations is obvious.
例 21.8. The Hibernate <cache> mapping element
<cache
usage="transactional|read-write|nonstrict-read-write|read-only"
region="RegionName"
include="all|non-lazy"
/>
usage(必須)說明了緩存的策略:transactional、 read-write、nonstrict-read-write 或
read-only。
region (可選,默認爲類或者集合的名字(class or collection role name)) 指定第二
級緩存的區域名(name of the second level cache region)
include(可選,默認爲 all) non-lazy 當屬性級延遲抓取打開時,標記爲 lazy="true" 的實
體的屬性可能沒法被緩存
Alternatively to <cache>, you can use <class-cache> and <collection-cache> elements in
hibernate.cfg.xml.
Let's now have a closer look at the different usage strategies
21.2.2. 策略:只讀緩存(Strategy:read only)
若是你的應用程序只需讀取一個持久化類的實例,而無需對其修改, 那麼就能夠對其進行只讀 緩
存。這是最簡單,也是實用性最好的方法。甚至在集羣中,它也能完美地運做。
21.2.3. 策略:讀寫/緩存(Strategy:read/write)
若是應用程序須要更新數據,那麼使用讀/寫緩存 比較合適。 若是應用程序要求「序列化事
務」的隔離級別(serializable transaction isolation level),那麼就決不能使用這種緩存
策略。 若是在 JTA 環境中使用緩存,你必須指定 hibernate.transaction.manager_lookup_class
屬性的值, 經過它,Hibernate 才能知道該應用程序中 JTA 的TransactionManager的具體
策略。 在其它環境中,你必須保證在 Session.close()、或 Session.disconnect() 調用前,
整個事務已經結束。 若是你想在集羣環境中使用此策略,你必須保證底層的緩存實現支持鎖定
(locking)。Hibernate 內置的緩存策略並不支持鎖定功能。
第 21 章 提高性能
330
21.2.4. 策略:非嚴格讀/寫緩存(Strategy:nonstrict read/
write)
若是應用程序只偶爾須要更新數據(也就是說,兩個事務同時更新同一記錄的狀況很不常見),
也不須要十分嚴格的事務隔離,那麼比較適合使用非嚴格讀/寫緩存策略。若是在 JTA 環境中使
用該策略,你必須爲其指定 hibernate.transaction.manager_lookup_class 屬性的值,在其它環境
中,你必須保證在Session.close()、或 Session.disconnect() 調用前,整個事務已經結束。
21.2.5. 策略:事務緩存(transactional)
Hibernate 的事務緩存策略提供了全事務的緩存支持,例如對 JBoss TreeCache 的支持。這樣的
緩存只能用於 JTA 環境中,你必須指定爲其 hibernate.transaction.manager_lookup_class 屬性。
21.2.6. 各類緩存提供商/緩存併發策略的兼容性
重要
沒有一種緩存提供商可以支持上列的全部緩存併發策略。下表中列出了各類提供
器、及其各自適用的併發策略。
沒有一種緩存提供商可以支持上列的全部緩存併發策略。下表中列出了各類提供器、及其各自適
用的併發策略。
表  21.2.  各類緩存提供商對緩存併發策略的支持狀況(Cache Concurrency
Strategy Support)
Cache read-only nonstrict-readwrite
read-write transactional
Hashtable (not
intended for
production
use)
yes yes yes
EHCache yes yes yes yes
OSCache yes yes yes
SwarmCache yes yes
JBoss Cache 1.x yes yes
JBoss Cache 2 yes yes
21.3. 管理緩存(Managing the caches)
不管什麼時候,當你給 save()、update() 或 saveOrUpdate() 方法傳遞一個對象時,或使用
load()、get()、list()、iterate() 或 scroll() 方法得到一個對象時,該對象都將被加入到
Session 的內部緩存中。
管理緩存(Managing the caches)
331
當隨後 flush() 方法被調用時,對象的狀態會和數據庫取得同步。若是你不但願此同步操做發
生,或者你正處理大量對象、須要對有效管理內存時,你能夠調用 evict() 方法,從一級緩存中
去掉這些對象及其集合。
例 21.9. Explcitly evicting a cached instance from the first level cache
using Session.evict()
ScrollableResult cats = sess.createQuery("from Cat as cat").scroll(); //a huge result set
while ( cats.next() ) {
Cat cat = (Cat) cats.get(0);
doSomethingWithACat(cat);
sess.evict(cat);
}
Session 還提供了一個 contains() 方法,用來判斷某個實例是否處於當前 session 的緩存中。
如若要把全部的對象從 session 緩存中完全清除,則須要調用 Session.clear()。
對於二級緩存來講,在 SessionFactory 中定義了許多方法,清除緩存中實例、整個類、集合實例
或者整個集合。
例  21.10.  Second-level cache eviction via SessionFactoty.evict() and
SessionFacyory.evictCollection()
sessionFactory.evict(Cat.class, catId); //evict a particular Cat
sessionFactory.evict(Cat.class); //evict all Cats
sessionFactory.evictCollection("Cat.kittens", catId); //evict a particular collection of kittens
sessionFactory.evictCollection("Cat.kittens"); //evict all kitten collections
CacheMode 參數用於控制具體的 Session 如何與二級緩存進行交互。
•CacheMode.NORMAL:從二級緩存中讀、寫數據。
•CacheMode.GET:從二級緩存中讀取數據,僅在數據更新時對二級緩存寫數據。
•CacheMode.PUT:僅向二級緩存寫數據,但不從二級緩存中讀數據。
•CacheMode.REFRESH:僅向二級緩存寫數據,但不從二級緩存中讀數據。經過
hibernate.cache.use_minimal_puts 的設置,強制二級緩存從數據庫中讀取數據,刷新緩存內
容。
如若須要查看二級緩存或查詢緩存區域的內容,你能夠使用統計(Statistics) API。
例 21.11. Browsing the second-level cache entries via the Statistics API
Map cacheEntries = sessionFactory.getStatistics()
.getSecondLevelCacheStatistics(regionName)
第 21 章 提高性能
332
.getEntries();
此時,你必須手工打開統計選項。可選的,你可讓 Hibernate 更人工可讀的方式維護緩存內
容。
例 21.12. Enabling Hibernate statistics
hibernate.generate_statistics true
hibernate.cache.use_structured_entries true
21.4. 查詢緩存(The Query Cache)
查詢的結果集也能夠被緩存。只有當常用一樣的參數進行查詢時,這纔會有些用處。
21.4.1. 啓用查詢緩存
按照應用程序的事務性處理過程,查詢結果的緩存將產生一些負荷。例如,若是緩存針對 Person
的查詢結果,在 Person 發生了修改時,Hibernate 將須要跟蹤這些結果何時失效。由於大
多數應用程序不會從緩存查詢結果中受益,因此 Hibernate 在缺省狀況下將禁用緩存。要使用查
詢緩存,你首先須要啓用查詢緩存:
hibernate.cache.use_query_cache true
這個設置建立了兩個新的緩存 region:
•org.hibernate.cache.StandardQueryCache,保存緩存的查詢結果
•org.hibernate.cache.UpdateTimestampsCache,保存對可查詢表的最近更新的時間戳。它們用於檢
驗查詢結果。
重要
If you configure your underlying cache implementation to use expiry or
timeouts is very important that the cache timeout of the underlying cache
region for the UpdateTimestampsCache be set to a higher value than the
timeouts of any of the query caches. In fact, we recommend that the the
UpdateTimestampsCache region not be configured for expiry at all. Note,
in particular, that an LRU cache expiry policy is never appropriate.
如上面所說起的,絕大多數的查詢並不能從查詢緩存中受益,因此 Hibernate 默認是不進行查詢
緩存的。如若須要進行緩存,請調用 org.hibernate.Query.setCacheable(true)方法。這個調用會
讓查詢在執行過程當中時先從緩存中查找結果,並將本身的結果集放到緩存中去。
查詢緩存區
333
注意
查詢緩存不會緩存緩存中實際實體的狀態;它只緩存標識符值和值類型的結果。
出於這個緣由,對於那些做爲查詢結果緩存的一部分(和集合緩存同樣)進行緩
存的實體,查詢緩存應該和二級緩存一塊兒使用。
21.4.2. 查詢緩存區
若是你要對查詢緩存的失效政策進行精確的控制,你必須調用 Query.setCacheRegion() 方法,爲
每一個查詢指定其命名的緩存區域。
List blogs = sess.createQuery("from Blog blog where blog.blogger = :blogger")
.setEntity("blogger", blogger)
.setMaxResults(15)
.setCacheable(true)
.setCacheRegion("frontpages")
.list();
若是查詢須要強行刷新其查詢緩存區域,那麼你應該調用
org.hibernate.Query.setCacheMode(CacheMode.REFRESH)方法。 這對在其餘進程中修改底層數據
(例如,不經過Hibernate修改數據),或對那些須要選擇性更新特定查詢結果集的狀況特別有
用。這是對 org.hibernate.SessionFactory.evictQueries() 的更爲有效的替代方案,一樣能夠清除
查詢緩存區域。
21.5. 理解集合性能(Understanding Collection
performance)
在前面的章節裏咱們已經討論了集合和相關應用程序。在本節我麼將探索運行時集合的更多問
題。
21.5.1. 分類(Taxonomy)
Hibernate 定義了三種基本類型的集合:
•值數據集合
•一對多關聯(One-to-many Associations)
•多對多關聯
這個分類是區分了不一樣的表和外鍵關係類型,可是它沒有告訴咱們關係模型的全部內容。 要徹底
理解他們的關係結構和性能特色,咱們必須同時考慮「用於 Hibernate 更新或刪除集合行數據的
主鍵的結構」。所以獲得了以下的分類:
•有序集合類
第 21 章 提高性能
334
•集合(sets)
•包(bags)
全部的有序集合類(maps,lists,arrays)都擁有一個由 <key> 和 <index> 組成的主鍵。這種情
況下集合類的更新是很是高效的 — 主鍵已經被有效的索引,所以當 Hibernate 試圖更新或刪除
一行時,能夠迅速找到該行數據。
集合(sets)的主鍵由 <key> 和其餘元素字段構成。對於有些元素類型來講,這很低效,特別是
組合元素或者大文本、大二進制字段;數據庫可能沒法有效的對複雜的主鍵進行索引。另外一方
面,對於一對多、多對多關聯,特別是合成的標識符來講,集合也能夠達到一樣的高效性能。
( 附註:若是你但願 SchemaExport 爲你的 <set> 建立主鍵,你必須把全部的字段都聲明爲 notnull="true"。)
<idbag> 映射定義了代理鍵,所以它老是能夠很高效的被更新。事實上,<idbag> 擁有着最好的性
能表現。
Bag 是最差的。由於 bag 容許重複的元素值,也沒有索引字段,所以不可能定義主鍵。
Hibernate 沒法判斷出重複的行。當這種集合被更改時,Hibernate 將會先完整地移除 (經過一
個(in a single DELETE))整個集合,而後再從新建立整個集合。所以 Bag 是很是低效的。
請注意:對於一對多關聯來講,「主鍵」極可能並非數據庫表的物理主鍵。但就算在此狀況
下,上面的分類仍然是有用的。(它仍然反映了 Hibernate 在集合的各數據行中是如何進行「定
位」的。)
21.5.2. Lists,maps 和 sets 用於更新效率最高
根據咱們上面的討論,顯然有序集合類型和大多數 set 均可以在增長、刪除、修改元素中擁有最
好的性能。
可論證的是對於多對多關聯、值數據集合而言,有序集合類比集合(set)有一個好處。由於 Set
的內在結構, 若是「改變」了一個元素,Hibernate 並不會更新(UPDATE)這一行。對於 Set 來
說,只有在插入(INSERT)和刪除(DELETE) 操做時「改變」纔有效。再次強調:這段討論對「一
對多關聯」並不適用。
注意到數組沒法延遲載入,咱們能夠得出結論,list,map 和 idbags 是最高效的(非反向)集合
類型,set 則緊隨其後。 在 Hibernate 中,set 應該時最通用的集合類型,這時由於「set」的
語義在關係模型中是最天然的。
可是,在設計良好的 Hibernate 領域模型中,咱們一般能夠看到更多的集合事實上是帶有
inverse="true" 的一對多的關聯。對於這些關聯,更新操做將會在多對一的這一端進行處理。因
此對於此類狀況,無需考慮其集合的更新性能。
21.5.3. Bag 和 list 是反向集合類中效率最高的
在把 bag 扔進水溝以前,你必須瞭解,在一種狀況下,bag 的性能(包括list)要比 set 高得
多:對於指明瞭 inverse="true" 的集合類(好比說,標準的雙向的一對多關聯),咱們能夠在未
初始化(fetch)包元素的狀況下直接向 bag 或 list 添加新元素! 這是由於 Collection.add())
一次性刪除(One shot delete)
335
或者 Collection.addAll() 方法對 bag 或者 List 老是返回 true(這點與與 Set 不一樣)。所以
對於下面的相同代碼來講,速度會快得多。
Parent p = (Parent) sess.load(Parent.class, id);
Child c = new Child();
c.setParent(p);
p.getChildren().add(c); //no need to fetch the collection!
sess.flush();
21.5.4. 一次性刪除(One shot delete)
偶爾的,逐個刪除集合類中的元素是至關低效的。Hibernate 並沒那麼笨,若是你想要把整個集
合都刪除(好比說調用 list.clear()),Hibernate 只須要一個 DELETE 就搞定了。
假設咱們在一個長度爲20的集合類中新增長了一個元素,而後再刪除兩個。Hibernate 會安排一
條 INSERT 語句和兩條 DELETE 語句(除非集合類是一個 bag)。這固然是使人滿意的。
可是,假設咱們刪除了 18 個數據,只剩下 2 個,而後新增 3 個。則有兩種處理方式:
•逐一的刪除這 18 個數據,再新增三個;
•刪除整個集合類(只用一句 DELETE 語句),而後逐一添加 5 個數據。
Hibernate 還沒那麼聰明,知道第二種選擇可能會比較快。(也許讓 Hibernate 不這麼聰明也是
好事,不然可能會引起意外的「數據庫觸發器」之類的問題。)
幸運的是,你能夠強制使用第二種策略。你須要取消原來的整個集合類(解除其引用),而後再
返回一個新的實例化的集合類,只包含須要的元素。有些時候這是很是有用的。
顯然,一次性刪除並不適用於被映射爲 inverse="true" 的集合。
21.6. 監測性能(Monitoring performance)
沒有監測和性能參數而進行優化是毫無心義的。Hibernate 爲其內部操做提供了一系列的示意
圖,所以能夠從 每一個 SessionFactory 抓取其統計數據。
21.6.1. 監測 SessionFactory
你能夠有兩種方式訪問 SessionFactory 的數據記錄,第一種就是本身直接調用
sessionFactory.getStatistics() 方法讀取、顯示統計數據。
此外,若是你打開 StatisticsService MBean 選項,那麼 Hibernate 則能夠使用 JMX 技
術 發佈其數據記錄。你可讓應用中全部的 SessionFactory 同時共享一個 MBean,也能夠每一個
SessionFactory 分配一個 MBean。下面的代碼便是其演示代碼:
// MBean service registration for a specific SessionFactory
第 21 章 提高性能
336
Hashtable tb = new Hashtable();
tb.put("type", "statistics");
tb.put("sessionFactory", "myFinancialApp");
ObjectName on = new ObjectName("hibernate", tb); // MBean object name
StatisticsService stats = new StatisticsService(); // MBean implementation
stats.setSessionFactory(sessionFactory); // Bind the stats to a SessionFactory
server.registerMBean(stats, on); // Register the Mbean on the server
// MBean service registration for all SessionFactory's
Hashtable tb = new Hashtable();
tb.put("type", "statistics");
tb.put("sessionFactory", "all");
ObjectName on = new ObjectName("hibernate", tb); // MBean object name
StatisticsService stats = new StatisticsService(); // MBean implementation
server.registerMBean(stats, on); // Register the MBean on the server
你能夠經過如下方法打開或關閉 SessionFactory 的監測功能:
•在配置期間,將 hibernate.generate_statistics 設置爲 true 或 false;
•在運行期間,則能夠能夠經過 sf.getStatistics().setStatisticsEnabled(true) 或
hibernateStatsBean.setStatisticsEnabled(true)
你也能夠在程序中調用 clear() 方法重置統計數據,調用 logSummary() 在日誌中記錄(info 級
別)其總結。
21.6.2. 數據記錄(Metrics)
Hibernate 提供了一系列數據記錄,其記錄的內容包括從最基本的信息到與具體場景的特殊信
息。全部的測量值均可以由 Statistics 接口 API 進行訪問,主要分爲三類:
•使用 Session 的普通數據記錄,例如打開的 Session 的個數、取得的 JDBC 的鏈接數等;
•實體、集合、查詢、緩存等內容的統一數據記錄。
•和具體實體、集合、查詢、緩存相關的詳細數據記錄
例如:你能夠檢查緩存的命中成功次數,緩存的命中失敗次數,實體、集合和查詢的使用機率,
查詢的平均時間等。請注意 Java 中時間的近似精度是毫秒。Hibernate 的數據精度和具體的 JVM
有關,在有些平臺上其精度甚至只能精確到 10 秒。
你能夠直接使用 getter 方法獲得全局數據記錄(例如,和具體的實體、集合、緩存區無關的
數據),你也能夠在具體查詢中經過標記實體名、 或 HQL、SQL 語句獲得某實體的數據記
錄。請參考 Statistics、EntityStatistics、CollectionStatistics、SecondLevelCacheStatistics 和
QueryStatistics 的 API 文檔以抓取更多信息。下面的代碼則是個簡單的例子:
數據記錄(Metrics)
337
Statistics stats = HibernateUtil.sessionFactory.getStatistics();
double queryCacheHitCount = stats.getQueryCacheHitCount();
double queryCacheMissCount = stats.getQueryCacheMissCount();
double queryCacheHitRatio =
queryCacheHitCount / (queryCacheHitCount + queryCacheMissCount);
log.info("Query Hit ratio:" + queryCacheHitRatio);
EntityStatistics entityStats =
stats.getEntityStatistics( Cat.class.getName() );
long changes =
entityStats.getInsertCount()
+ entityStats.getUpdateCount()
+ entityStats.getDeleteCount();
log.info(Cat.class.getName() + " changed " + changes + "times" );
若是你想獲得全部實體、集合、查詢和緩存區的數據,你能夠經過如下方法得到實體、集合、查
詢和緩存區列表:getQueries()、getEntityNames()、getCollectionRoleNames() 和
getSecondLevelCacheRegionNames()。
338
第 22
339
工具箱指南
能夠經過一系列 Eclipse 插件、命令行工具和 Ant 任務來進行與 Hibernate 關聯的轉換。
除了 Ant 任務外,當前的 Hibernate Tools 也包含了 Eclipse IDE 的插件,用於與現存數據
庫的逆向工程。
•Mapping Editor: Hibernate XML 映射文件的編輯器,支持自動完成和語法高亮。它也支持對
類名和屬性/字段名的語義自動完成,比一般的 XML 編輯器方便得多。
•Console: Console 是 Eclipse 的一個新視圖。除了對你的 console 配置的樹狀概覽,你還可
以得到對你持久化類及其關聯的交互式視圖。Console 容許你對數據庫執行 HQL 查詢,並直接
在 Eclipse 中瀏覽結果。
•Development Wizards: 在 Hibernate Eclipse tools 中還提供了幾個嚮導;你能夠用嚮導快
速生成 Hibernate 配置文件(cfg.xml),你甚至還能夠同現存的數據庫 schema 中反向工程
出 POJO 源代碼與 Hibernate 映射文件。反向工程支持可定製的模版。

要獲得更多信息,請查閱 Hibernate Tools 包及其文檔。
同時,Hibernate 主發行包還附帶了一個集成的工具(它甚至能夠在 Hibernate「內部」快速運
行)SchemaExport ,也就是 hbm2ddl。
22.1. Schema 自動生成(Automatic schema generation)
能夠從你的映射文件使用一個 Hibernate 工具生成 DDL。生成的 schema 包含有對實體和集合類
表的完整性引用約束(主鍵和外鍵)。涉及到的標示符生成器所需的表和 sequence 也會同時生
成。
在使用這個工具的時候,你必須 經過 hibernate.dialet 屬性指定一個 SQL 方言(Dialet),由於
DDL 是與供應商高度相關的。
首先,要定製你的映射文件,來改善生成的 schema。下章將涵蓋 schema 定製。
22.1.1. 對 schema 定製化(Customizing the schema)
不少 Hibernate 映射元素定義了可選的 length、precision 或者 scale 屬性。你能夠經過這個屬
性設置字段的長度、精度、小數點位數。
<property name="zip" length="5"/>
<property name="balance" precision="12" scale="2"/>
第 22 章 工具箱指南
340
有些 tag 還接受 not-null 屬性(用來在表字段上生成 NOT NULL 約束)和 unique 屬性(用來在
表字段上生成 UNIQUE 約束)。
<many-to-one name="bar" column="barId" not-null="true"/>
<element column="serialNumber" type="long" not-null="true" unique="true"/>
unique-key 屬性能夠對成組的字段指定一個惟一鍵約束(unique key constraint)。目
前,unique-key 屬性指定的值在生成 DDL 時並不會被看成這個約束的名字,它們只是在用來在映
射文件內部用做區分的。
<many-to-one name="org" column="orgId" unique-key="OrgEmployeeId"/>
<property name="employeeId" unique-key="OrgEmployee"/>
index 屬性會用對應的字段(一個或多個)生成一個 index,它指出了這個 index 的名字。若是
多個字段對應的 index 名字相同,就會生成包含這些字段的 index。
<property name="lastName" index="CustName"/>
<property name="firstName" index="CustName"/>
foreign-key 屬性能夠用來覆蓋任何生成的外鍵約束的名字。
<many-to-one name="bar" column="barId" foreign-key="FKFooBar"/>
不少映射元素還接受 <column> 子元素。這在定義跨越多字段的類型時特別有用。
<property name="name" type="my.customtypes.Name"/>
<column name="last" not-null="true" index="bar_idx" length="30"/>
<column name="first" not-null="true" index="bar_idx" length="20"/>
<column name="initial"/>
</property
>
default 屬性爲字段指定一個默認值(在保存被映射的類的新實例以前,你應該將一樣的值賦於對
應的屬性)。
<property name="credits" type="integer" insert="false">
<column name="credits" default="10"/>
</property
對 schema 定製化(Customizing the schema)
341
>
<version name="version" type="integer" insert="false">
<column name="version" default="0"/>
</property
>
sql-type 屬性容許用戶覆蓋默認的 Hibernate 類型到 SQL 數據類型的映射。
<property name="balance" type="float">
<column name="balance" sql-type="decimal(13,3)"/>
</property
>
check 屬性容許用戶指定一個約束檢查。
<property name="foo" type="integer">
<column name="foo" check="foo
> 10"/>
</property
>
<class name="Foo" table="foos" check="bar < 100.0">
...
<property name="bar" type="float"/>
</class
>
下表總結了這些可選屬性:
表 22.1. 總結
屬性(Attribute) 值(Values) 解釋(Interpretation)
length 數字 字段長度
precision 數字 精度(decimal precision)
scale 數字 小數點位數(decimal scale)
not-null true|false 指明字段是否應該是非空的
unique true|false 指明是否該字段具備唯一約束
index index_name 指明一個(多字段)的索引(index)的名字
unique-key unique_key_name 指明多字段唯一約束的名字(參見上面的說明)
第 22 章 工具箱指南
342
屬性(Attribute) 值(Values) 解釋(Interpretation)
foreign-key foreign_key_name 指明一個外鍵的名字,它是爲關聯生成的,或者是
爲 <one-to-one>, <many-to-one>, <key>, or <manyto-many>
映射元素。注意 inverse="true" 會被
SchemaExport 忽略。
sql-type SQL column type 覆蓋默認的字段類型(只能用於 <column> 屬性)
default SQL 表達式 爲字段指定默認值
check SQL 表達式 對字段或表加入 SQL 約束檢查
<comment> 元素可讓你在生成的 schema 中加入註釋。
<class name="Customer" table="CurCust">
<comment
>Current customers only</comment>
...
</class
>
<property name="balance">
<column name="bal">
<comment
>Balance in USD</comment>
</column>
</property
>
結果是在生成的 DDL 中包含 comment on table 或者 comment on column 語句(倘若支持的話)。
22.1.2. 運行該工具
SchemaExport 工具把 DDL 腳本寫到標準輸出,同時/或者執行 DDL 語句。
下表顯示了 SchemaExport 命令行選項
java -cp hibernate_classpaths org.hibernate.tool.hbm2ddl.SchemaExport options mapping_files
表 22.2. SchemaExport 命令行選項
選項 描述
--quiet 不要把腳本輸出到 stdout
--drop 只進行 drop tables 的步驟
--create 只建立表
--text 不執行在數據庫中運行的步驟
--output=my_schema.ddl 把輸出的 ddl 腳本輸出到一個文件
屬性(Properties)
343
選項 描述
--naming=eg.MyNamingStrategy 選擇 NamingStrategy
--config=hibernate.cfg.xml 從 XML 文件讀入 Hibernate 配置
--properties=hibernate.properties 從文件讀入數據庫屬性
--format 把腳本中的 SQL 語句對齊和美化
--delimiter=; 爲腳本設置行結束符
你甚至能夠在你的應用程序中嵌入 SchemaExport 工具:
Configuration cfg = ....;
new SchemaExport(cfg).create(false, true);
22.1.3. 屬性(Properties)
能夠經過以下方式指定數據庫屬性:
•經過 -D<property> 系統參數
•在 hibernate.properties 文件中
•位於一個其它名字的 properties 文件中,而後用 --properties 參數指定
所需的參數包括:
表 22.3. SchemaExport 鏈接屬性
屬性名 描述
hibernate.connection.driver_class jdbc driver class
hibernate.connection.url jdbc url
hibernate.connection.username database user
hibernate.connection.password user password
hibernate.dialect 方言(dialect)
22.1.4. 使用 Ant(Using Ant)
你能夠在你的 Ant build 腳本中調用 SchemaExport:
<target name="schemaexport">
<taskdef name="schemaexport"
classname="org.hibernate.tool.hbm2ddl.SchemaExportTask"
classpathref="class.path"/>

<schemaexport
properties="hibernate.properties"
quiet="no"
第 22 章 工具箱指南
344
text="no"
drop="no"
delimiter=";"
output="schema-export.sql">
<fileset dir="src">
<include name="**/*.hbm.xml"/>
</fileset>
</schemaexport>
</target
>
22.1.5. 對 schema 的增量更新(Incremental schema updates)
SchemaUpdate 工具對已存在的 schema 採用"增量"方式進行更新。注意 SchemaUpdate 嚴重依賴於
JDBC metadata API,因此它並不是對全部 JDBC 驅動都有效。
java -cp hibernate_classpaths org.hibernate.tool.hbm2ddl.SchemaUpdate options mapping_files
表 22.4. SchemaUpdate 命令行選項
選項 描述
--quiet 不要把腳本輸出到 stdout
--text 不把腳本輸出到數據庫
--naming=eg.MyNamingStrategy 選擇 NamingStrategy
--properties=hibernate.properties 從文件讀入數據庫屬性
--config=hibernate.cfg.xml 指定一個 .cfg.xml 文件
你能夠在你的應用程序中嵌入 SchemaUpdate 工具:
Configuration cfg = ....;
new SchemaUpdate(cfg).execute(false);
22.1.6. 用 Ant 來增量更新 schema(Using Ant for incremental
schema updates)
你能夠在 Ant 腳本中調用 SchemaUpdate:
<target name="schemaupdate">
<taskdef name="schemaupdate"
classname="org.hibernate.tool.hbm2ddl.SchemaUpdateTask"
classpathref="class.path"/>

<schemaupdate
properties="hibernate.properties"
quiet="no">
<fileset dir="src">
<include name="**/*.hbm.xml"/>
Schema 校驗
345
</fileset>
</schemaupdate>
</target
>
22.1.7. Schema 校驗
SchemaValidator 工具會比較數據庫現狀是否與映射文檔「匹配」。注意,SchemaValidator 嚴重依
賴於 JDBC 的 metadata API,所以不是對全部的 JDBC 驅動都適用。這一工具在測試的時候特別
有用。
java -cp hibernate_classpaths org.hibernate.tool.hbm2ddl.SchemaValidator options
mapping_files
下表顯示了 SchemaValidator 命令行參數:
表 22.5. SchemaValidator 命令行參數
選項 描述
--naming=eg.MyNamingStrategy 選擇 NamingStrategy
--properties=hibernate.properties 從文件讀入數據庫屬性
--config=hibernate.cfg.xml 指定一個 .cfg.xml 文件
你能夠在你的應用程序中嵌入 SchemaValidator:
Configuration cfg = ....;
new SchemaValidator(cfg).validate();
22.1.8. 使用 Ant 進行 schema 校驗
你能夠在 Ant 腳本中調用 SchemaValidator:
<target name="schemavalidate">
<taskdef name="schemavalidator"
classname="org.hibernate.tool.hbm2ddl.SchemaValidatorTask"
classpathref="class.path"/>

<schemavalidator
properties="hibernate.properties">
<fileset dir="src">
<include name="**/*.hbm.xml"/>
</fileset>
</schemavalidator>
</target
>
346
第 23
347
Additional modules
Hibernate Core also offers integration with some external modules/projects. This
includes Hibernate Validator the reference implementation of Bean Validation (JSR 303)
and Hibernate Search.
23.1. Bean Validation
Bean Validation standardizes how to define and declare domain model level constraints.
You can, for example, express that a property should never be null, that the account
balance should be strictly positive, etc. These domain model constraints are declared
in the bean itself by annotating its properties. Bean Validation can then read them
and check for constraint violations. The validation mechanism can be executed in
different layers in your application without having to duplicate any of these rules
(presentation layer, data access layer). Following the DRY principle, Bean Validation
and its reference implementation Hibernate Validator has been designed for that purpose.
The integration between Hibernate and Bean Validation works at two levels. First, it
is able to check in-memory instances of a class for constraint violations. Second, it
can apply the constraints to the Hibernate metamodel and incorporate them into the
generated database schema.
Each constraint annotation is associated to a validator implementation responsible for
checking the constraint on the entity instance. A validator can also (optionally) apply
the constraint to the Hibernate metamodel, allowing Hibernate to generate DDL that
expresses the constraint. With the appropriate event listener, you can execute the
checking operation on inserts, updates and deletes done by Hibernate.
When checking instances at runtime, Hibernate Validator returns information about
constraint violations in a set of ConstraintViolations. Among other information, the
ConstraintViolation contains an error description message that can embed the parameter
values bundle with the annotation (eg. size limit), and message strings that may be
externalized to a ResourceBundle.
23.1.1. Adding Bean Validation
To enable Hibernate's Bean Validation integration, simply add a Bean Validation provider
(preferably Hibernate Validation 4) on your classpath.
23.1.2. Configuration
By default, no configuration is necessary.
The Default group is validated on entity insert and update and the database model is
updated accordingly based on the Default group as well.
第 23 章 Additional modules
348
You can customize the Bean Validation integration by setting the validation mode.
Use the javax.persistence.validation.mode property and set it up for example in your
persistence.xml file or your hibernate.cfg.xml file. Several options are possible:
•auto (default): enable integration between Bean Validation and Hibernate (callback
and ddl generation) only if Bean Validation is present in the classpath.
•none: disable all integration between Bean Validation and Hibernate
•callback: only validate entities when they are either inserted, updated or deleted.
An exception is raised if no Bean Validation provider is present in the classpath.
•ddl: only apply constraints to the database schema when generated by Hibernate. An
exception is raised if no Bean Validation provider is present in the classpath. This
value is not defined by the Java Persistence spec and is specific to Hibernate.
注意
You can use both callback and ddl together by setting the property to
callback, dll
<persistence ...>
<persistence-unit ...>
...
<properties>
<property name="javax.persistence.validation.mode"
value="callback, ddl"/>
</properties>
</persistence-unit>
</persistence>
This is equivalent to auto except that if no Bean Validation provider is
present, an exception is raised.
If you want to validate different groups during insertion, update and deletion, use:
•javax.persistence.validation.group.pre-persist: groups validated when an entity is about
to be persisted (default to Default)
•javax.persistence.validation.group.pre-update: groups validated when an entity is about
to be updated (default to Default)
•javax.persistence.validation.group.pre-remove: groups validated when an entity is about
to be deleted (default to no group)
•org.hibernate.validator.group.ddl: groups considered when applying constraints on the
database schema (default to Default)
Catching violations
349
Each property accepts the fully qualified class names of the groups validated separated
by a comma (,)
例 23.1. Using custom groups for validation
<persistence ...>
<persistence-unit ...>
...
<properties>
<property name="javax.persistence.validation.group.pre-update"
value="javax.validation.group.Default, com.acme.group.Strict"/>
<property name="javax.persistence.validation.group.pre-remove"
value="com.acme.group.OnDelete"/>
<property name="org.hibernate.validator.group.ddl"
value="com.acme.group.DDL"/>
</properties>
</persistence-unit>
</persistence>
注意
You can set these properties in hibernate.cfg.xml, hibernate.properties or
programmatically.
23.1.3. Catching violations
If an entity is found to be invalid, the list of constraint violations is propagated
by the ConstraintViolationException which exposes the set of ConstraintViolations.
This exception is wrapped in a RollbackException when the violation happens at commit
time. Otherwise the ConstraintViolationException is returned (for example when calling
flush(). Note that generally, catchable violations are validated at a higher level (for
example in Seam / JSF 2 via the JSF - Bean Validation integration or in your business
layer by explicitly calling Bean Validation).
An application code will rarely be looking for a ConstraintViolationException raised by
Hibernate. This exception should be treated as fatal and the persistence context should
be discarded (EntityManager or Session).
23.1.4. Database schema
Hibernate uses Bean Validation constraints to generate an accurate database schema:
•@NotNull leads to a not null column (unless it conflicts with components or table
inheritance)
•@Size.max leads to a varchar(max) definition for Strings
第 23 章 Additional modules
350
•@Min, @Max lead to column checks (like value <= max)
•@Digits leads to the definition of precision and scale (ever wondered which is which?
It's easy now with @Digits :) )
These constraints can be declared directly on the entity properties or indirectly by
using constraint composition.
For more information check the Hibernate Validator reference documentation [http://
docs.jboss.org/hibernate/stable/validator/reference/en-US/html/].
23.2. Hibernate Search
23.2.1. Description
Full text search engines like Apache Lucene™ are a very powerful technology to
bring free text/efficient queries to applications. If suffers several mismatches when
dealing with a object domain model (keeping the index up to date, mismatch between the
index structure and the domain model, querying mismatch...) Hibernate Search indexes
your domain model thanks to a few annotations, takes care of the database / index
synchronization and brings you back regular managed objects from free text queries.
Hibernate Search is using Apache Lucene [http://lucene.apache.org] under the cover.
23.2.2. Integration with Hibernate Annotations
Hibernate Search integrates with Hibernate Core transparently provided that
the Hibernate Search jar is present on the classpath. If you do not
wish to automatically register Hibernate Search event listeners, you can set
hibernate.search.autoregister_listeners to false. Such a need is very uncommon and not
recommended.
Check the Hibernate Search reference documentation [http://docs.jboss.org/hibernate/
stable/search/reference/en-US/html/] for more information.
第 24
351
示例:父子關係(Parent/Child)
剛剛接觸 Hibernate 的人大可能是從父子關係(parent / child type relationship)的建模入手
的。父子關係的建模有兩種方法。因爲種種緣由,最方便的方法是把 Parent 和 Child 都建模成實
體類,並建立一個從 Parent 指向 Child 的 <one-to-many> 關聯,對新手來講尤爲如此。還有一
種方法,就是將 Child 聲明爲一個 <composite-element>(組合元素)。 事實上在 Hibernate 中
one to many 關聯的默認語義遠沒有 composite element 貼近 parent / child 關係的一般語義。
下面咱們會闡述如何使用帶有級聯的雙向一對多關聯(idirectional one to many association
with cascades)去創建有效、優美的 parent / child 關係。
24.1. 關於 collections 須要注意的一點
Hibernate collections 被看成其所屬實體而不是其包含實體的一個邏輯部分。這很是重要,它
主要體現爲如下幾點:
•當刪除或增長 collection 中對象的時候,collection 所屬者的版本值會遞增。
•若是一個從 collection 中移除的對象是一個值類型(value type)的實例,好比
composite element,那麼這個對象的持久化狀態將會終止,其在數據庫中對應的記錄會被刪
除。一樣的,向 collection 增長一個 value type 的實例將會使之當即被持久化。
•另外一方面,若是從一對多或多對多關聯的 collection 中移除一個實體,在缺省狀況下這個對
象並不會被刪除。這個行爲是徹底合乎邏輯的--改變一個實體的內部狀態不該該使與它關聯
的實體消失掉。一樣的,向 collection 增長一個實體不會使之被持久化。
實際上,向 Collection 增長一個實體的缺省動做只是在兩個實體之間建立一個鏈接而已,一樣
移除的時候也只是刪除鏈接。這種處理對於全部的狀況都是合適的。對於父子關係則是徹底不適
合的,在這種關係下,子對象的生存綁定於父對象的生存週期。
24.2. 雙向的一對多關係(Bidirectional one-to-many)
假設咱們要實現一個簡單的從 Parent 到 Child 的 <one-to-many> 關聯。
<set name="children">
<key column="parent_id"/>
<one-to-many class="Child"/>
</set
>
若是咱們運行下面的代碼:
Parent p = .....;
Child c = new Child();
p.getChildren().add(c);
session.save(c);
第 24 章 示例:父子關係(Parent/Child)
352
session.flush();
Hibernate 會產生兩條 SQL 語句:
•一條 INSERT 語句,爲 c 建立一條記錄
•一條 UPDATE 語句,建立從 p 到 c 的鏈接
這樣作不只效率低,並且違反了 parent_id 列 parent_id 非空的限制。咱們能夠經過在集合類映
射上指定 not-null="true" 來解決違反非空約束的問題:
<set name="children">
<key column="parent_id" not-null="true"/>
<one-to-many class="Child"/>
</set
>
然而,這並不是是推薦的解決方法。
這種現象的根本緣由是從 p 到 c 的鏈接(外鍵 parent_id)沒有被看成 Child 對象狀態的一部
分,於是沒有在 INSERT 語句中被建立。所以解決的辦法就是把這個鏈接添加到 Child 的映射中。
<many-to-one name="parent" column="parent_id" not-null="true"/>
你還須要爲類 Child 添加 parent 屬性。
如今實體 Child 在管理鏈接的狀態,爲了使 collection 不更新鏈接,咱們使用 inverse 屬性:
<set name="children" inverse="true">
<key column="parent_id"/>
<one-to-many class="Child"/>
</set
>
下面的代碼是用來添加一個新的 Child:
Parent p = (Parent) session.load(Parent.class, pid);
Child c = new Child();
c.setParent(p);
p.getChildren().add(c);
session.save(c);
session.flush();
如今,只會有一條 INSERT 語句被執行。
級聯生命週期(Cascading lifecycle)
353
爲了讓事情變得層次分明,能夠爲 Parent 加一個 addChild() 方法。
public void addChild(Child c) {
c.setParent(this);
children.add(c);
}
如今,添加 Child 的代碼就是這樣:
Parent p = (Parent) session.load(Parent.class, pid);
Child c = new Child();
p.addChild(c);
session.save(c);
session.flush();
24.3. 級聯生命週期(Cascading lifecycle)
須要顯式調用 save() 仍然很麻煩,咱們能夠用級聯來解決這個問題。
<set name="children" inverse="true" cascade="all">
<key column="parent_id"/>
<one-to-many class="Child"/>
</set
>
這樣上面的代碼能夠簡化爲:
Parent p = (Parent) session.load(Parent.class, pid);
Child c = new Child();
p.addChild(c);
session.flush();
一樣的,保存或刪除 Parent 對象的時候並不須要遍歷其子對象。下面的代碼會刪除對象 p 及其
全部子對象對應的數據庫記錄。
Parent p = (Parent) session.load(Parent.class, pid);
session.delete(p);
session.flush();
然而,這段代碼:
Parent p = (Parent) session.load(Parent.class, pid);
Child c = (Child) p.getChildren().iterator().next();
第 24 章 示例:父子關係(Parent/Child)
354
p.getChildren().remove(c);
c.setParent(null);
session.flush();
不會從數據庫刪除c;它只會刪除與 p 之間的鏈接(而且會致使違反 NOT NULL 約束,在這個例子
中)。你須要顯式調用 delete() 來刪除 Child。
Parent p = (Parent) session.load(Parent.class, pid);
Child c = (Child) p.getChildren().iterator().next();
p.getChildren().remove(c);
session.delete(c);
session.flush();
在咱們的例子中,若是沒有父對象,子對象就不該該存在,若是將子對象從 collection 中移
除,實際上咱們是想刪除它。要實現這種要求,就必須使用 cascade="all-delete-orphan"。
<set name="children" inverse="true" cascade="all-delete-orphan">
<key column="parent_id"/>
<one-to-many class="Child"/>
</set
>
注意:即便在 collection 一方的映射中指定 inverse="true",級聯仍然是經過遍歷
collection 中的元素來處理的。若是你想要經過級聯進行子對象的插入、刪除、更新操做,就必
須把它加到 collection 中,只調用 setParent() 是不夠的。
24.4. 級聯與未保存值(unsaved-value)
Suppose we loaded up a Parent in one Session, made some changes in a UI action and wanted
to persist these changes in a new session by calling update(). The Parent will contain
a collection of children and, since the cascading update is enabled, Hibernate needs
to know which children are newly instantiated and which represent existing rows in
the database. We will also assume that both Parent and Child have generated identifier
properties of type Long. Hibernate will use the identifier and version/timestamp property
value to determine which of the children are new. (See 第 11.7 節 「自動狀態檢測」.)
In Hibernate3, it is no longer necessary to specify an unsaved-value explicitly.
下面的代碼會更新 parent 和 child 對象,而且插入 newChild 對象。
//parent and child were both loaded in a previous session
parent.addChild(child);
Child newChild = new Child();
parent.addChild(newChild);
session.update(parent);
session.flush();
結論
355
這對於自動生成標識的狀況是很是好的,可是自分配的標識和複合標識怎麼辦呢?這是有點麻
煩,由於 Hibernate 沒有辦法區分新實例化的對象(標識被用戶指定了)和前一個 Session 裝
入的對象。在這種狀況下,Hibernate 會使用 timestamp 或 version 屬性,或者查詢第二級緩
存,或者最壞的狀況,查詢數據庫,來確認是否此行存在。
24.5. 結論
這裏有很多東西須要融會貫通,可能會讓新手感到迷惑。可是在實踐中它們都工做地很是好。大
部分 Hibernate 應用程序都會常常用到父子對象模式。
在第一段中咱們曾經提到另外一個方案。上面的這些問題都不會出如今 <composite-element> 映射
中,它準確地表達了父子關係的語義。很不幸複合元素還有兩個重大限制:複合元素不能擁有
collections,而且,除了用於唯一的父對象外,它們不能再做爲其它任何實體的子對象。
356
第 25
357
示例:Weblog 應用程序
25.1. 持久化類(Persistent Classes)
下面的持久化類表示一個 weblog 和在其中張貼的一個貼子。他們是標準的父/子關係模型,可是
咱們會用一個有序包(ordered bag)而非集合(set)。
package eg;
import java.util.List;
public class Blog {
private Long _id;
private String _name;
private List _items;
public Long getId() {
return _id;
}
public List getItems() {
return _items;
}
public String getName() {
return _name;
}
public void setId(Long long1) {
_id = long1;
}
public void setItems(List list) {
_items = list;
}
public void setName(String string) {
_name = string;
}
}
package eg;
import java.text.DateFormat;
import java.util.Calendar;
public class BlogItem {
private Long _id;
private Calendar _datetime;
private String _text;
private String _title;
private Blog _blog;
public Blog getBlog() {
return _blog;
}
public Calendar getDatetime() {
第 25 章 示例:Weblog 應用程序
358
return _datetime;
}
public Long getId() {
return _id;
}
public String getText() {
return _text;
}
public String getTitle() {
return _title;
}
public void setBlog(Blog blog) {
_blog = blog;
}
public void setDatetime(Calendar calendar) {
_datetime = calendar;
}
public void setId(Long long1) {
_id = long1;
}
public void setText(String string) {
_text = string;
}
public void setTitle(String string) {
_title = string;
}
}
25.2. Hibernate 映射
下列的 XML 映射應該是很直白的。例如:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="eg">
<class
name="Blog"
table="BLOGS">
<id
name="id"
column="BLOG_ID">
<generator class="native"/>
</id>
<property
name="name"
column="NAME"
not-null="true"
unique="true"/>
Hibernate 映射
359
<bag
name="items"
inverse="true"
order-by="DATE_TIME"
cascade="all">
<key column="BLOG_ID"/>
<one-to-many class="BlogItem"/>
</bag>
</class>
</hibernate-mapping
>
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="eg">
<class
name="BlogItem"
table="BLOG_ITEMS"
dynamic-update="true">
<id
name="id"
column="BLOG_ITEM_ID">
<generator class="native"/>
</id>
<property
name="title"
column="TITLE"
not-null="true"/>
<property
name="text"
column="TEXT"
not-null="true"/>
<property
name="datetime"
column="DATE_TIME"
not-null="true"/>
<many-to-one
name="blog"
column="BLOG_ID"
not-null="true"/>
第 25 章 示例:Weblog 應用程序
360
</class>
</hibernate-mapping
>
25.3. Hibernate 代碼
下面的類演示了咱們能夠使用 Hibernate 對這些類進行的一些操做:
package eg;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Iterator;
import java.util.List;
import org.hibernate.HibernateException;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.hibernate.tool.hbm2ddl.SchemaExport;
public class BlogMain {
private SessionFactory _sessions;
public void configure() throws HibernateException {
_sessions = new Configuration()
.addClass(Blog.class)
.addClass(BlogItem.class)
.buildSessionFactory();
}
public void exportTables() throws HibernateException {
Configuration cfg = new Configuration()
.addClass(Blog.class)
.addClass(BlogItem.class);
new SchemaExport(cfg).create(true, true);
}
public Blog createBlog(String name) throws HibernateException {
Blog blog = new Blog();
blog.setName(name);
blog.setItems( new ArrayList() );
Session session = _sessions.openSession();
Transaction tx = null;
try {
tx = session.beginTransaction();
session.persist(blog);
tx.commit();
}
catch (HibernateException he) {
Hibernate 代碼
361
if (tx!=null) tx.rollback();
throw he;
}
finally {
session.close();
}
return blog;
}
public BlogItem createBlogItem(Blog blog, String title, String text)
throws HibernateException {
BlogItem item = new BlogItem();
item.setTitle(title);
item.setText(text);
item.setBlog(blog);
item.setDatetime( Calendar.getInstance() );
blog.getItems().add(item);
Session session = _sessions.openSession();
Transaction tx = null;
try {
tx = session.beginTransaction();
session.update(blog);
tx.commit();
}
catch (HibernateException he) {
if (tx!=null) tx.rollback();
throw he;
}
finally {
session.close();
}
return item;
}
public BlogItem createBlogItem(Long blogid, String title, String text)
throws HibernateException {
BlogItem item = new BlogItem();
item.setTitle(title);
item.setText(text);
item.setDatetime( Calendar.getInstance() );
Session session = _sessions.openSession();
Transaction tx = null;
try {
tx = session.beginTransaction();
Blog blog = (Blog) session.load(Blog.class, blogid);
item.setBlog(blog);
blog.getItems().add(item);
tx.commit();
}
catch (HibernateException he) {
if (tx!=null) tx.rollback();
throw he;
}
finally {
session.close();
第 25 章 示例:Weblog 應用程序
362
}
return item;
}
public void updateBlogItem(BlogItem item, String text)
throws HibernateException {
item.setText(text);
Session session = _sessions.openSession();
Transaction tx = null;
try {
tx = session.beginTransaction();
session.update(item);
tx.commit();
}
catch (HibernateException he) {
if (tx!=null) tx.rollback();
throw he;
}
finally {
session.close();
}
}
public void updateBlogItem(Long itemid, String text)
throws HibernateException {
Session session = _sessions.openSession();
Transaction tx = null;
try {
tx = session.beginTransaction();
BlogItem item = (BlogItem) session.load(BlogItem.class, itemid);
item.setText(text);
tx.commit();
}
catch (HibernateException he) {
if (tx!=null) tx.rollback();
throw he;
}
finally {
session.close();
}
}
public List listAllBlogNamesAndItemCounts(int max)
throws HibernateException {
Session session = _sessions.openSession();
Transaction tx = null;
List result = null;
try {
tx = session.beginTransaction();
Query q = session.createQuery(
"select blog.id, blog.name, count(blogItem) " +
"from Blog as blog " +
"left outer join blog.items as blogItem " +
"group by blog.name, blog.id " +
"order by max(blogItem.datetime)"
Hibernate 代碼
363
);
q.setMaxResults(max);
result = q.list();
tx.commit();
}
catch (HibernateException he) {
if (tx!=null) tx.rollback();
throw he;
}
finally {
session.close();
}
return result;
}
public Blog getBlogAndAllItems(Long blogid)
throws HibernateException {
Session session = _sessions.openSession();
Transaction tx = null;
Blog blog = null;
try {
tx = session.beginTransaction();
Query q = session.createQuery(
"from Blog as blog " +
"left outer join fetch blog.items " +
"where blog.id = :blogid"
);
q.setParameter("blogid", blogid);
blog = (Blog) q.uniqueResult();
tx.commit();
}
catch (HibernateException he) {
if (tx!=null) tx.rollback();
throw he;
}
finally {
session.close();
}
return blog;
}
public List listBlogsAndRecentItems() throws HibernateException {
Session session = _sessions.openSession();
Transaction tx = null;
List result = null;
try {
tx = session.beginTransaction();
Query q = session.createQuery(
"from Blog as blog " +
"inner join blog.items as blogItem " +
"where blogItem.datetime
> :minDate"
);
Calendar cal = Calendar.getInstance();
cal.roll(Calendar.MONTH, false);
q.setCalendar("minDate", cal);
第 25 章 示例:Weblog 應用程序
364
result = q.list();
tx.commit();
}
catch (HibernateException he) {
if (tx!=null) tx.rollback();
throw he;
}
finally {
session.close();
}
return result;
}
}
第 26
365
示例:複雜映射實例
本章展現了一些較爲複雜的關係映射。
26.1. Employer(僱主)/Employee(僱員)
下面關於 Employer 和 Employee 的關係模型使用了一個真實的實體類(Employment)來表述,這是
由於對於相同的僱員和僱主可能會有多個僱傭時間段。對於金額和僱員姓名,用 Components 建
模。
映射文件多是這樣:
<hibernate-mapping>

<class name="Employer" table="employers">
<id name="id">
<generator class="sequence">
<param name="sequence"
>employer_id_seq</param>
</generator>
</id>
<property name="name"/>
</class>
<class name="Employment" table="employment_periods">
<id name="id">
<generator class="sequence">
<param name="sequence"
>employment_id_seq</param>
</generator>
</id>
<property name="startDate" column="start_date"/>
<property name="endDate" column="end_date"/>
<component name="hourlyRate" class="MonetaryAmount">
<property name="amount">
<column name="hourly_rate" sql-type="NUMERIC(12, 2)"/>
第 26 章 示例:複雜映射實例
366
</property>
<property name="currency" length="12"/>
</component>
<many-to-one name="employer" column="employer_id" not-null="true"/>
<many-to-one name="employee" column="employee_id" not-null="true"/>
</class>
<class name="Employee" table="employees">
<id name="id">
<generator class="sequence">
<param name="sequence"
>employee_id_seq</param>
</generator>
</id>
<property name="taxfileNumber"/>
<component name="name" class="Name">
<property name="firstName"/>
<property name="initial"/>
<property name="lastName"/>
</component>
</class>
</hibernate-mapping
>
用 SchemaExport 生成表結構。
create table employers (
id BIGINT not null,
name VARCHAR(255),
primary key (id)
)
create table employment_periods (
id BIGINT not null,
hourly_rate NUMERIC(12, 2),
currency VARCHAR(12),
employee_id BIGINT not null,
employer_id BIGINT not null,
end_date TIMESTAMP,
start_date TIMESTAMP,
primary key (id)
)
create table employees (
id BIGINT not null,
firstName VARCHAR(255),
initial CHAR(1),
lastName VARCHAR(255),
taxfileNumber VARCHAR(255),
primary key (id)
)
alter table employment_periods
Author(做家)/Work(做品)
367
add constraint employment_periodsFK0 foreign key (employer_id) references employers
alter table employment_periods
add constraint employment_periodsFK1 foreign key (employee_id) references employees
create sequence employee_id_seq
create sequence employment_id_seq
create sequence employer_id_seq
26.2. Author(做家)/Work(做品)
考慮下面的 Work,Author 和 Person 模型的關係。咱們用多對多關係來描述 Work 和 Author,用
一對一關係來描述 Author 和 Person,另外一種可能性是 Author 繼承 Person。
下面的映射文件正確的描述了這些關係:
<hibernate-mapping>
<class name="Work" table="works" discriminator-value="W">
<id name="id" column="id">
<generator class="native"/>
</id>
<discriminator column="type" type="character"/>
<property name="title"/>
<set name="authors" table="author_work">
<key column name="work_id"/>
<many-to-many class="Author" column name="author_id"/>
</set>
<subclass name="Book" discriminator-value="B">
<property name="text"/>
</subclass>
第 26 章 示例:複雜映射實例
368
<subclass name="Song" discriminator-value="S">
<property name="tempo"/>
<property name="genre"/>
</subclass>
</class>
<class name="Author" table="authors">
<id name="id" column="id">
<!-- The Author must have the same identifier as the Person -->
<generator class="assigned"/>
</id>
<property name="alias"/>
<one-to-one name="person" constrained="true"/>
<set name="works" table="author_work" inverse="true">
<key column="author_id"/>
<many-to-many class="Work" column="work_id"/>
</set>
</class>
<class name="Person" table="persons">
<id name="id" column="id">
<generator class="native"/>
</id>
<property name="name"/>
</class>
</hibernate-mapping
>
映射中有 4 個表。works,authors 和 persons 分別保存着 work,author 和 person 的數
據。author_work 是 authors 和 works 的關聯表。表結構是由 SchemaExport 生成的:
create table works (
id BIGINT not null generated by default as identity,
tempo FLOAT,
genre VARCHAR(255),
text INTEGER,
title VARCHAR(255),
type CHAR(1) not null,
primary key (id)
)
create table author_work (
author_id BIGINT not null,
work_id BIGINT not null,
primary key (work_id, author_id)
)
create table authors (
id BIGINT not null generated by default as identity,
Customer(客戶)/Order(訂單)/Product(產品)
369
alias VARCHAR(255),
primary key (id)
)
create table persons (
id BIGINT not null generated by default as identity,
name VARCHAR(255),
primary key (id)
)
alter table authors
add constraint authorsFK0 foreign key (id) references persons
alter table author_work
add constraint author_workFK0 foreign key (author_id) references authors
alter table author_work
add constraint author_workFK1 foreign key (work_id) references works
26.3. Customer(客戶)/Order(訂單)/Product(產品)
如今來考慮 Customer,Order, LineItem 和 Product 關係的模型。Customer 和 Order 之間 是一
對多的關係,可是咱們怎麼來描述 Order / LineItem / Product呢? 我能夠把 LineItem 做爲描述
Order 和 Product 多對多關係的關聯類,在 Hibernate,這叫作組合元素。
映射文件以下:
<hibernate-mapping>
<class name="Customer" table="customers">
<id name="id">
<generator class="native"/>
</id>
<property name="name"/>
<set name="orders" inverse="true">
<key column="customer_id"/>
<one-to-many class="Order"/>
</set>
</class>
<class name="Order" table="orders">
<id name="id">
<generator class="native"/>
</id>
<property name="date"/>
<many-to-one name="customer" column="customer_id"/>
第 26 章 示例:複雜映射實例
370
<list name="lineItems" table="line_items">
<key column="order_id"/>
<list-index column="line_number"/>
<composite-element class="LineItem">
<property name="quantity"/>
<many-to-one name="product" column="product_id"/>
</composite-element>
</list>
</class>
<class name="Product" table="products">
<id name="id">
<generator class="native"/>
</id>
<property name="serialNumber"/>
</class>
</hibernate-mapping
>
customers,orders,line_items 和 products 分別保存着 customer,order,order line item 和
product 的數據。 line_items 也做爲鏈接 orders 和 products 的關聯表。
create table customers (
id BIGINT not null generated by default as identity,
name VARCHAR(255),
primary key (id)
)
create table orders (
id BIGINT not null generated by default as identity,
customer_id BIGINT,
date TIMESTAMP,
primary key (id)
)
create table line_items (
line_number INTEGER not null,
order_id BIGINT not null,
product_id BIGINT,
quantity INTEGER,
primary key (order_id, line_number)
)
create table products (
id BIGINT not null generated by default as identity,
serialNumber VARCHAR(255),
primary key (id)
)
alter table orders
add constraint ordersFK0 foreign key (customer_id) references customers
alter table line_items
add constraint line_itemsFK0 foreign key (product_id) references products
alter table line_items
雜例
371
add constraint line_itemsFK1 foreign key (order_id) references orders
26.4. 雜例
這些例子所有來自於 Hibernate 的 test suite,同時你也能夠找到其餘有用的例子。能夠參考
Hibernate 的 test 目錄。
26.4.1. "Typed" 一對一關聯
<class name="Person">
<id name="name"/>
<one-to-one name="address"
cascade="all">
<formula
>name</formula>
<formula
>'HOME'</formula>
</one-to-one>
<one-to-one name="mailingAddress"
cascade="all">
<formula
>name</formula>
<formula
>'MAILING'</formula>
</one-to-one>
</class>
<class name="Address" batch-size="2"
check="addressType in ('MAILING', 'HOME', 'BUSINESS')">
<composite-id>
<key-many-to-one name="person"
column="personName"/>
<key-property name="type"
column="addressType"/>
</composite-id>
<property name="street" type="text"/>
<property name="state"/>
<property name="zip"/>
</class
>
26.4.2. 組合鍵示例
<class name="Customer">
<id name="customerId"
length="10">
<generator class="assigned"/>
</id>
<property name="name" not-null="true" length="100"/>
第 26 章 示例:複雜映射實例
372
<property name="address" not-null="true" length="200"/>
<list name="orders"
inverse="true"
cascade="save-update">
<key column="customerId"/>
<index column="orderNumber"/>
<one-to-many class="Order"/>
</list>
</class>
<class name="Order" table="CustomerOrder" lazy="true">
<synchronize table="LineItem"/>
<synchronize table="Product"/>

<composite-id name="id"
class="Order$Id">
<key-property name="customerId" length="10"/>
<key-property name="orderNumber"/>
</composite-id>

<property name="orderDate"
type="calendar_date"
not-null="true"/>

<property name="total">
<formula>
( select sum(li.quantity*p.price)
from LineItem li, Product p
where li.productId = p.productId
and li.customerId = customerId
and li.orderNumber = orderNumber )
</formula>
</property>

<many-to-one name="customer"
column="customerId"
insert="false"
update="false"
not-null="true"/>

<bag name="lineItems"
fetch="join"
inverse="true"
cascade="save-update">
<key>
<column name="customerId"/>
<column name="orderNumber"/>
</key>
<one-to-many class="LineItem"/>
</bag>

</class>

<class name="LineItem">

<composite-id name="id"
class="LineItem$Id">
共有組合鍵屬性的多對多(Many-to-many with shared composite key attribute)
373
<key-property name="customerId" length="10"/>
<key-property name="orderNumber"/>
<key-property name="productId" length="10"/>
</composite-id>

<property name="quantity"/>

<many-to-one name="order"
insert="false"
update="false"
not-null="true">
<column name="customerId"/>
<column name="orderNumber"/>
</many-to-one>

<many-to-one name="product"
insert="false"
update="false"
not-null="true"
column="productId"/>

</class>
<class name="Product">
<synchronize table="LineItem"/>
<id name="productId"
length="10">
<generator class="assigned"/>
</id>

<property name="description"
not-null="true"
length="200"/>
<property name="price" length="3"/>
<property name="numberAvailable"/>

<property name="numberOrdered">
<formula>
( select sum(li.quantity)
from LineItem li
where li.productId = productId )
</formula>
</property>

</class
>
26.4.3. 共有組合鍵屬性的多對多(Many-to-many with shared
composite key attribute)
<class name="User" table="`User`">
<composite-id>
<key-property name="name"/>
<key-property name="org"/>
第 26 章 示例:複雜映射實例
374
</composite-id>
<set name="groups" table="UserGroup">
<key>
<column name="userName"/>
<column name="org"/>
</key>
<many-to-many class="Group">
<column name="groupName"/>
<formula
>org</formula>
</many-to-many>
</set>
</class>

<class name="Group" table="`Group`">
<composite-id>
<key-property name="name"/>
<key-property name="org"/>
</composite-id>
<property name="description"/>
<set name="users" table="UserGroup" inverse="true">
<key>
<column name="groupName"/>
<column name="org"/>
</key>
<many-to-many class="User">
<column name="userName"/>
<formula
>org</formula>
</many-to-many>
</set>
</class>
26.4.4. 基於內容的識別
<class name="Person"
discriminator-value="P">

<id name="id"
column="person_id"
unsaved-value="0">
<generator class="native"/>
</id>


<discriminator
type="character">
<formula>
case
when title is not null then 'E'
when salesperson is not null then 'C'
else 'P'
end
</formula>
</discriminator>
備用鍵的聯合
375
<property name="name"
not-null="true"
length="80"/>

<property name="sex"
not-null="true"
update="false"/>

<component name="address">
<property name="address"/>
<property name="zip"/>
<property name="country"/>
</component>

<subclass name="Employee"
discriminator-value="E">
<property name="title"
length="20"/>
<property name="salary"/>
<many-to-one name="manager"/>
</subclass>

<subclass name="Customer"
discriminator-value="C">
<property name="comments"/>
<many-to-one name="salesperson"/>
</subclass>

</class
>
26.4.5. 備用鍵的聯合
<class name="Person">

<id name="id">
<generator class="hilo"/>
</id>

<property name="name" length="100"/>

<one-to-one name="address"
property-ref="person"
cascade="all"
fetch="join"/>

<set name="accounts"
inverse="true">
<key column="userId"
property-ref="userId"/>
<one-to-many class="Account"/>
</set>

<property name="userId" length="8"/>
</class>
第 26 章 示例:複雜映射實例
376
<class name="Address">
<id name="id">
<generator class="hilo"/>
</id>
<property name="address" length="300"/>
<property name="zip" length="5"/>
<property name="country" length="25"/>
<many-to-one name="person" unique="true" not-null="true"/>
</class>
<class name="Account">
<id name="accountId" length="32">
<generator class="uuid"/>
</id>

<many-to-one name="user"
column="userId"
property-ref="userId"/>

<property name="type" not-null="true"/>

</class>第 27377最佳實踐(Best Practices)設計細顆粒度的持久類而且使用 <component> 來實現映射:使用一個 Address 持久類來封裝 street,suburb,state,postcode。 這將有利於代碼重用和簡化代碼重構(refactoring)的工做。對持久類聲明標識符屬性(identifier properties):Hibernate 中標識符屬性是可選的,不過有不少緣由來講明你應該使用標識符屬性。咱們建議標識符應該是「人造」的(自動生成,不涉及業務含義)。使用天然鍵(natural keys)標識:對全部的實體都標識出天然鍵,用 <natural-id> 進行映射。實現 equals() 和 hashCode(),在其中用組成天然鍵的屬性進行比較。爲每一個持久類寫一個映射文件:不要把全部的持久類映射都寫到一個大文件中。把 com.eg.Foo 映射到 com/eg/Foo.hbm.xml中。在團隊開發環境中,這一點尤爲重要。把映射文件做爲資源加載:把映射文件和他們的映射類放在一塊兒進行部署。考慮把查詢字符串放在程序外面:若是你的查詢中調用了非 ANSI 標準的 SQL 函數,那麼這條實踐經驗對你適用。把查詢字符串放在映射文件中可讓程序具備更好的可移植性。使用綁定變量就像在 JDBC 編程中同樣,應該老是用佔位符 "?" 來替換很是量值,不要在查詢中用字符串值來構造很是量值。你也應該考慮在查詢中使用命名參數。不要本身來管理 JDBC 鏈接:Hibernate 容許應用程序本身來管理 JDBC 鏈接,可是應該做爲最後沒有辦法的辦法。如果你不能使用 Hibernate 內建的 connections providers,那麼考慮實現本身來實現org.hibernate.connection.ConnectionProvider。考慮使用用戶自定義類型(custom type):假設你有一個 Java 類型,來自某些類庫,須要被持久化,可是該類沒有提供映射操做須要的存取方法。那麼你應該考慮實現 org.hibernate.UserType 接口。這種辦法使程序代碼寫起來更加自如,再也不須要考慮類與 Hibernate type 之間的相互轉換。在性能瓶頸的地方使用硬編碼的 JDBC:在系統中對性能要求很嚴格的一些部分,某些操做也許直接使用 JDBC 會更好。可是請先確認這的確是一個瓶頸,而且不要想固然認爲 JDBC 必定會更快。若是確實須要直接使用 JDBC,那麼最好打開一個 Hibernate Session 而後將 JDBC 操做包裹爲 org.hibernate.jdbc.Work 並使用 JDBC 鏈接。按照這種辦法你仍然能夠使用一樣的transaction 策略和底層的 connection provider。第 27 章 最佳實踐(Best Practices)378理解 Session 沖刷(flushing):Session 會不時的向數據庫同步持久化狀態,若是這種操做進行的過於頻繁,性能會受到一定的影響。有時候你能夠經過禁止自動 flushing,儘可能最小化非必要的 flushing 操做,或者更進一步,在一個特定的 transaction 中改變查詢和其它操做的順序。在三層結構中,考慮使用脫管對象(detached object):當使用一個 servlet / session bean 類型的架構的時候, 你能夠把已加載的持久對象在session bean 層和 servlet / JSP 層之間來回傳遞。使用新的 session 來爲每一個請求服務,使用 Session.merge() 或者 Session.saveOrUpdate() 來與數據庫同步。在兩層結構中,考慮使用長持久上下文(long persistence contexts):爲了獲得最佳的可伸縮性,數據庫事務(Database Transaction)應該儘量的短。可是,程序經常須要實現長時間運行的「應用程序事務(Application Transaction)」,包含一個從用戶的觀點來看的原子操做。這個應用程序事務可能跨越屢次從用戶請求到獲得反饋的循環。用脫管對象(與 session 脫離的對象)來實現應用程序事務是常見的。或者,尤爲在兩層結構中,把 Hibernate Session 從 JDBC 鏈接中脫離開,下次須要用的時候再鏈接上。絕不要把一個 Session 用在多個應用程序事務(Application Transaction)中,不然你的數據可能會過時失效。不要把異常當作可恢復的:這一點甚至比「最佳實踐」還要重要,這是「必備常識」。當異常發生的時候,必需要回滾Transaction ,關閉 Session。若是你不這樣作的話,Hibernate 沒法保證內存狀態精確的反應持久狀態。尤爲不要使用 Session.load() 來判斷一個給定標識符的對象實例在數據庫中是否存在,應該使用 Session.get() 或者進行一次查詢。對於關聯優先考慮 lazy fetching:謹慎的使用主動抓取(eager fetching)。對於關聯來講,若其目標是沒法在第二級緩存中徹底緩存全部實例的類,應該使用代理(proxies)與/或具備延遲加載屬性的集合(lazycollections)。若目標是能夠被緩存的,尤爲是緩存的命中率很是高的狀況下,應該使用lazy="false",明確的禁止掉 eager fetching。若是那些特殊的確實適合使用 join fetch 的場合,請在查詢中使用 left join fetch。使用 open session in view 模式,或者執行嚴格的裝配期(assembly phase)策略來避免再次抓取數據帶來的問題:Hibernate 讓開發者們擺脫了繁瑣的 Data Transfer Objects(DTO)。在傳統的 EJB 結構中,DTO 有雙重做用:首先,他們解決了 entitybean 沒法序列化的問題;其次,他們隱含地定義了一個裝配期,在此期間,全部在 view層須要用到的數據,都被抓取、集中到了 DTO 中,而後控制才被裝到表示層。Hibernate 終結了第一個做用。然而,除非你作好了在整個渲染過程當中都維護一個打開的持久化上下文(session)的準備,你仍然須要一個裝配期(想象一下,你的業務方法與你的表示層有嚴格的契約,數據老是被放置到脫管對象中)。這並不是是 Hibernate 的限制,這是實現安全的事務化數據訪問的基本需求。考慮把 Hibernate 代碼從業務邏輯代碼中抽象出來:把 Hibernate 的數據存取代碼隱藏到接口(interface)的後面,組合使用 DAO 和 ThreadLocal Session 模式。經過 Hibernate 的UserType,你甚至能夠用硬編碼的 JDBC 來持久化379那些本該被 Hibernate 持久化的類。然而,該建議更適用於規模足夠大應用軟件中,對於那些只有 5 張表的應用程序並不適合。不要用怪異的鏈接映射:多對多鏈接用得好的例子實際上至關少見。大多數時候你在「鏈接表」中須要保存額外的信息。這種狀況下,用兩個指向中介類的一對多的鏈接比較好。實際上,咱們認爲絕大多數的鏈接是一對多和多對一的。i所以,你應該謹慎使用其它鏈接風格。偏心雙向關聯:單向關聯更加難於查詢。在大型應用中,幾乎全部的關聯必須在查詢中能夠雙向導航。380第 28381數據庫移植性考量28.1. 移植性基礎Hibernate(其實是整個 Object/Relational Mapping)的一個賣點是數據庫的移植性。這意味着內部的 IT 用戶能夠改變數據庫供應商,或者可部署的應用程序/框架使用 Hibernate 來同時使用多個數據庫產品。不考慮具體的應用情景,這裏的基本概念是 Hibernate 可幫助你運行多種數據庫而無需修改你的代碼,理想狀況下甚至不用修改映射元數據。28.2. DialectHibernate 的移植性的首要問題是方言(dialect),也就是 org.hibernate.dialect.Dialect 合約的具體實例。方言封裝了 Hibernate 和特定數據庫通信以完成某些任務如獲取序列值或構建SELECT 查詢等的全部差別。Hibernate 捆綁了用於許多最經常使用的數據庫的方言。若是你發現本身使用的數據庫不在其中,編寫自定義的方言也不是很困難的事情。28.3. 方言的使用最開始,Hibernate 老是要求用戶指定所使用的方言(dialect)。在用戶但願同時使用多個數據庫時就會出現問題。一般這要求用戶配置 Hibernate 方言或者定義本身設置這個值的方法。從版本 3.2 開始,Hibernate 引入了方言的自動檢測,它基於從該數據庫的 java.sql.Connection上得到的 java.sql.DatabaseMetaData。這是一個更好的方案,但它侷限於 Hibernate 已知的數據庫且沒法進行配置和覆蓋。Starting with version 3.3, Hibernate has a fare more powerful way to automaticallydetermine which dialect to should be used by relying on a series of delegates whichimplement the org.hibernate.dialect.resolver.DialectResolver which defines only a singlemethod:public Dialect resolveDialect(DatabaseMetaData metaData) throws JDBCConnectionExceptionThe basic contract here is that if the resolver 'understands' the givendatabase metadata then it returns the corresponding Dialect; if not it returnsnull and the process continues to the next resolver. The signature alsoidentifies org.hibernate.exception.JDBCConnectionException as possibly being thrown. AJDBCConnectionException here is interpreted to imply a "non transient" (aka nonrecoverable)connection problem and is used to indicate an immediate stop to resolutionattempts. All other exceptions result in a warning and continuing on to the nextresolver.這些解析者最棒的功能是用戶也能夠註冊自定義的解析者,它們將在內置的解析者以前被調用。在許多狀況下這可能頗有用:它能夠輕易地集成內置方言以外的方言的自動檢測;它讓你能夠使用自定義的方言等。要註冊一個或多個解析者,只要用 'hibernate.dialect_resolvers' 配置設第 28 章 數據庫移植性考量382置指定它們(由逗號、製表符或空格隔開)就能夠了(請參考 org.hibernate.cfg.Environment 上的 DIALECT_RESOLVERS)。28.4. 標識符的生成When considering portability between databases, another important decision is selectingthe identifier generation stratagy you want to use. Originally Hibernate provided thenative generator for this purpose, which was intended to select between a sequence,identity, or table strategy depending on the capability of the underlying database.However, an insidious implication of this approach comes about when targtetting somedatabases which support identity generation and some which do not. identity generationrelies on the SQL definition of an IDENTITY (or auto-increment) column to manage theidentifier value; it is what is known as a post-insert generation strategy becauase theinsert must actually happen before we can know the identifier value. Because Hibernaterelies on this identifier value to uniquely reference entities within a persistencecontext it must then issue the insert immediately when the users requests the entitiy beassociated with the session (like via save() e.g.) regardless of current transactionalsemantics.注意Hibernate was changed slightly once the implication of this was betterunderstood so that the insert is delayed in cases where that is feasible.The underlying issue is that the actual semanctics of the application itself changesin these cases.Starting with version 3.2.3, Hibernate comes with a set of enhanced [http://in.relation.to/2082.lace] identifier generators targetting portability in a muchdifferent way.注意There are specifically 2 bundled enhancedgenerators:•org.hibernate.id.enhanced.SequenceStyleGenerator•org.hibernate.id.enhanced.TableGeneratorThe idea behind these generators is to port the actual semantics of theidentifer value generation to the different databases. For example, theorg.hibernate.id.enhanced.SequenceStyleGenerator mimics the behavior of a sequence ondatabases which do not support sequences by using a table.數據庫函數38328.5. 數據庫函數警告這是 Hibernate 須要提升的一個領域。從可移植性來講,這個功能能夠很好地處理 HQL 的內容,但在其餘方面就有所欠缺。用戶能夠以多種方式引用 SQL 函數。然而,不是全部的數據庫都支持相同的函數集。Hibernate 提供了一種映射邏輯函數名到代理的方法,這個代理知道如何解析特定的函數,甚至可能使用徹底不一樣的物理函數調用。重要從技術上來說,這個函數註冊是經過org.hibernate.dialect.function.SQLFunctionRegistry 類進行處理的,它的目的是允許用戶提供自定義的函數定義而無需提供自定義的方言。這種特殊的行爲目前還未所有開發完畢。其中一些功能已經實現,如用戶能夠在程序裏用 org.hibernate.cfg.Configuration註冊函數且這些函數可被 HQL 識別。28.6. 類型映射本節內容仍未完成...384385參考資料[PoEAA] Patterns of Enterprise Application Architecture. 0-321-12742-0. 由 MartinFowler. 版權 © 2003 Pearson Education, Inc.. Addison-Wesley Publishing Company.[JPwH] Java Persistence with Hibernate. Second Edition of Hibernate in Action.1-932394-88-5. http://www.manning.com/bauer2 . 由 Christian Bauer和Gavin King.版權 © 2007 Manning Publications Co.. Manning Publications Co..386

相關文章
相關標籤/搜索